Skip to content

Add predict nonlinearity #662

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

Merged
merged 17 commits into from
Jul 28, 2020
Merged

Add predict nonlinearity #662

merged 17 commits into from
Jul 28, 2020

Conversation

BenjaminBossan
Copy link
Collaborator

@BenjaminBossan BenjaminBossan commented Jun 27, 2020

Resolves #637, resolves #661

Supersedes #572 #580

Added a parameter predict_nonlinearity to NeuralNet which allows users to control the nonlinearity to be applied to the module output when calling predict and predict_proba. Also, when using CrossEntropyLoss, softmax is now automatically applied to the output.

This PR implements what was discussed in #661. Regarding the implementation, I used the most simple approach at the moment (no dispatching based on the net class, for instance). Also, I added a get_predict_nonlinearity method to NeuralNet, which handles the case of 'auto' vs None vs callable.

Regarding the naming, I decided against output_nonlinearity. I believe predict_nonlinearity makes it much more obvious that this nonlinearity is not applied to the module output directly (which would affect loss etc.) but only when calling predict and predict_proba.

ping @qtux

BenjaminBossan added 2 commits June 27, 2020 19:08
Fails in PyTorch 1.1

There was one more instance in a unit test that needed fixing.
Copy link
Member

@thomasjpfan thomasjpfan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for working on this @BenjaminBossan !

skorch/net.py Outdated
@@ -1010,6 +1039,46 @@ def infer(self, x, **fit_params):
return self.module_(**x_dict)
return self.module_(x, **fit_params)

def get_predict_nonlinearity(self):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this being public there will be two ways to adjust the nonlinearity:

  1. With the predict_nonlinearity parameter in init.
  2. Subclassing

Are we okay with this?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question, I was also unsure about this. The predict_nonlinearity parameter specifies what nonlinearity I want, whereas the get_predict_nonlinearity specifies how the nonlinearity selection is resolved. I would also be okay with making the latter private (with the possibility to make it public later if the need ever arises).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would go with option 1 for now and having this function be public.

If a user were to override _get_predict_nonlinearity, we would not officially support it and it may break in the future.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to be sure, option 1 for you means making get_predict_nonlinearity private?

Copy link
Member

@thomasjpfan thomasjpfan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Otherwise LGTM

skorch/net.py Outdated
@@ -1010,6 +1039,46 @@ def infer(self, x, **fit_params):
return self.module_(**x_dict)
return self.module_(x, **fit_params)

def get_predict_nonlinearity(self):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would go with option 1 for now and having this function be public.

If a user were to override _get_predict_nonlinearity, we would not officially support it and it may break in the future.

Copy link
Member

@ottonemo ottonemo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM except comments

@@ -289,7 +289,12 @@ def test_custom_loss_does_not_call_sigmoid(
mock = Mock(side_effect=lambda x: x)
monkeypatch.setattr(torch, "sigmoid", mock)

net = net_cls(module_cls, max_epochs=1, lr=0.1, criterion=nn.MSELoss)
# we need to add a custom nonlinearity, otherwise the output won't be 2d
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this comment does not help me understand as it does not answer why the output won't be in 2d

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

# don't want callbacks to trigger side effects
net.callbacks_ = []
net.partial_fit(X, y)
assert not side_effect
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But wouldn't we expect callbacks such as accuracy scoring to call predict?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's why I removed all callbacks two lines earlier, otherwise the test becomes very messy.

def test_predict_nonlinearity_none(
self, net_cls, module_cls, data):
# even though we have CrossEntropyLoss, we don't want the
# output from predict_proba to be modified, since we set
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# output from predict_proba to be modified, since we set
# output from predict_proba to be modified, thus we set

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed


def test_infer_neural_binary_net_classifier_default(
self, infer_predict_nonlinearity, net_bin_clf_cls, module_cls):
# BCEWithLogitsLoss should return valid probabilities
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# BCEWithLogitsLoss should return valid probabilities
# BCEWithLogitsLoss criterion: nonlinearity should return valid probabilities

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

skorch/utils.py Outdated

Sigmoid is applied to x to transform it to probabilities. Then
concatenate the probabilities with 1 - these probabilities to
return a correctly formed ``y_proba``.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe specify who expects this format. Something along the lines of formats outputs for use with BCE loss or something like that

BenjaminBossan and others added 2 commits July 6, 2020 23:22
Improve explanations in comments and docstrings.
@BenjaminBossan
Copy link
Collaborator Author

@ottonemo I addressed your comments.

@ottonemo ottonemo merged commit 5dedb07 into master Jul 28, 2020
@BenjaminBossan BenjaminBossan deleted the feature/predict-nonlinearity branch July 30, 2020 22:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
3 participants