-
Notifications
You must be signed in to change notification settings - Fork 50
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
DOC: Add history attribute to migration #228
Conversation
Codecov Report
@@ Coverage Diff @@
## master #228 +/- ##
=======================================
Coverage 99.71% 99.71%
=======================================
Files 6 6
Lines 693 693
=======================================
Hits 691 691
Misses 2 2 Continue to review full report at Codecov.
|
📝 Docs preview for commit 34b29a1 at: https://www.adriangb.com/scikeras/refs/pull/228/merge/ |
…b/scikeras into add-history-to-migration
Previously calling ``fit`` on wrappers returned a Keras ``history`` object. | ||
To make wrappers more compatible with Scikit-Learn tools, ``fit`` now returns and instance of the estimator itself. | ||
Instead, the history is saved in the ``history_`` attribute. | ||
Calling ``fit`` resets this attribute, calling ``partial_fit`` on the other hand extends it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would this edit be a little clearer?
In TF, calling
Keras.fit
returned a history object. However, in SciKeras, calling ``est.fitreturns the estimator to conform to the Scikit-learn API. The Keras history object is available through the
history_` attribute of the estimator:def get_model(): ... return model - model = get_model() - hist = keras.fit(model, ...) + clf = KerasClassifier(model=get_model, ...) + hist = clf.fit(...).history_
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah that does seem a bit nicer, I'll incorporate it. Thank you.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This example isn't 100% correct; see #227 (comment). Maybe the diff should be this?
- model = get_model()
- hist = keras.fit(model, ...).history
+ clf = KerasClassifier(model=get_model, ...)
+ hist = clf.fit(...).history_
losses = hist["loss"]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep you're right! The nuance of Keras returning a History
object (which is a callback) that then contains the history
attribute (which is a dict) was lost on me. Thank you for tracking it down and pointing it out!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this warrants adding a history
attribute to the wrapper. I think a warning should be added that history
is an unstable attribute that can be deleted without warning. I think this is warranted because it explicitly violates a core piece of the Scikit-learn API.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have three main qualms with it:
- In principle, I do not like replacing a complex object (
tf.keras.callbacks.History
) with another complex object (self/BaseWrapper
) + a thin wrapper (DOC: Add history attribute to migration #228 (comment)) and expecting it to be backwards compatible. - In practice, DOC: Add history attribute to migration #228 (comment) does not work if a user used to do
clf.fit(...).set_params
before since it wouldtf.keras.callbacks.History.set_params
(this is documented method) but now would raise anAttributeError
. - I think it is okay to be backwards incompatible for this change, especially if we document it.
The tl;dr is that instead of trying to be somewhat backwards compatible, but probably not being fully backwards compatible, I feel that it is better to just document the change and move on.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How 'bout this implementation?
class BaseWrapper:
...
@property
def history(self):
raise AttributeError(
"class 'BaseWrapper' has no attribute 'history'.\n\n"
"Similar attributes include an attribute named `history_`."
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would you want to put that in permanently, or say for 1 minor version?
I still think that a breaking change is okay, especially across packages, so we don't necessarily need to add this, the documentation should be enough.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would you want to put that in permanently, or say for 1 minor version?
Doesn't matter. The same error type is raised, and the first sentence of the message is the same.
I still think that a breaking change is okay, especially across packages, so we don't necessarily need to add this
Agreed. Documentation should be enough, but often is not enough (RTFD).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the original issue was about documentation, and because I think the error is not strictly necessary, I would rather leave it out. Let's wait to see if the documentation is enough or if we get more issues before we add more code. If we do get more related issues, we can totally add something like that at a later date. Sound good?
Closes #227