This notebook shows some more advanced features of skorch
. More examples will be added with time.
import torch
from torch import nn
import torch.nn.functional as F
torch.manual_seed(0);
We load a toy classification task from sklearn
.
import numpy as np
from sklearn.datasets import make_classification
X, y = make_classification(1000, 20, n_informative=10, random_state=0)
X = X.astype(np.float32)
X.shape, y.shape, y.mean()
((1000, 20), (1000,), 0.5)
pytorch
classification module
¶We define a vanilla neural network with two hidden layers. The output layer should have 2 output units since there are two classes. In addition, it should have a softmax nonlinearity, because later, when calling predict_proba
, the output from the forward
call will be used.
from skorch.net import NeuralNetClassifier
class ClassifierModule(nn.Module):
def __init__(
self,
num_units=10,
nonlin=F.relu,
dropout=0.5,
):
super(ClassifierModule, self).__init__()
self.num_units = num_units
self.nonlin = nonlin
self.dropout = dropout
self.dense0 = nn.Linear(20, num_units)
self.nonlin = nonlin
self.dropout = nn.Dropout(dropout)
self.dense1 = nn.Linear(num_units, 10)
self.output = nn.Linear(10, 2)
def forward(self, X, **kwargs):
X = self.nonlin(self.dense0(X))
X = self.dropout(X)
X = F.relu(self.dense1(X))
X = F.softmax(self.output(X), dim=-1)
return X
Callbacks are a powerful and flexible way to customize the behavior of your neural network. They are all called at specific points during the model training, e.g. when training starts, or after each batch. Have a look at the skorch.callbacks
module to see the callbacks that are already implemented.
Although skorch
comes with a handful of useful callbacks, you may find that you would like to write your own callbacks. Doing so is straightforward, just remember these rules:
skorch.callbacks.Callback
.on_
-methods provided by the parent class (e.g. on_batch_begin
or on_epoch_end
).on_
-methods first get the NeuralNet
instance, and, where appropriate, the local data (e.g. the data from the current batch). The method should also have **kwargs
in the signature for potentially unused arguments.initialize
method.Here is an example of a callback that remembers at which epoch the validation accuracy reached a certain value. Then, when training is finished, it calls a mock Twitter API and tweets that epoch. We proceed as follows:
__init__
.initialize
.from skorch.callbacks import Callback
def tweet(msg):
print("~" * 60)
print("*tweet*", msg, "#skorch #pytorch")
print("~" * 60)
class AccuracyTweet(Callback):
def __init__(self, min_accuracy):
self.min_accuracy = min_accuracy
def initialize(self):
self.critical_epoch_ = -1
def on_epoch_end(self, net, **kwargs):
if self.critical_epoch_ > -1:
return
# look at the validation accuracy of the last epoch
if net.history[-1, 'valid_acc'] >= self.min_accuracy:
self.critical_epoch_ = len(net.history)
def on_train_end(self, net, **kwargs):
if self.critical_epoch_ < 0:
msg = "Accuracy never reached {} :(".format(self.min_accuracy)
else:
msg = "Accuracy reached {} at epoch {}!!!".format(
self.min_accuracy, self.critical_epoch_)
tweet(msg)
Now we initialize a NeuralNetClassifier
and pass your new callback in a list to the callbacks
argument. After that, we train the model and see what happens.
net = NeuralNetClassifier(
ClassifierModule,
max_epochs=10,
lr=0.02,
warm_start=True,
callbacks=[AccuracyTweet(min_accuracy=0.7)],
)
net.fit(X, y)
epoch train_loss valid_acc valid_loss dur ------- ------------ ----------- ------------ ------ 1 0.6908 0.5950 0.6842 0.1078 2 0.6876 0.5950 0.6815 0.0561 3 0.6853 0.6100 0.6789 0.0580 4 0.6882 0.5950 0.6769 0.0551 5 0.6780 0.6000 0.6743 0.0570 6 0.6730 0.6100 0.6717 0.0538 7 0.6664 0.6150 0.6698 0.0496 8 0.6670 0.6100 0.6670 0.0522 9 0.6667 0.6300 0.6646 0.0560 10 0.6624 0.6350 0.6624 0.0575 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ *tweet* Accuracy never reached 0.7 :( #skorch #pytorch ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<skorch.net.NeuralNetClassifier at 0x7faf55866048>
Oh no, our model never reached a validation accuracy of 0.7. Let's train some more (this is possible because we set warm_start=True
):
net.fit(X, y)
11 0.6647 0.6500 0.6598 0.0816 12 0.6573 0.6650 0.6575 0.0675 13 0.6458 0.6700 0.6549 0.0454 14 0.6528 0.6750 0.6525 0.0905 15 0.6476 0.6700 0.6502 0.0854 16 0.6483 0.6750 0.6476 0.0878 17 0.6514 0.6800 0.6452 0.0741 18 0.6365 0.6850 0.6422 0.0545 19 0.6335 0.7000 0.6390 0.0531 20 0.6381 0.7100 0.6363 0.0477 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ *tweet* Accuracy reached 0.7 at epoch 19!!! #skorch #pytorch ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<skorch.net.NeuralNetClassifier at 0x7faf55866048>
Finally, the validation score exceeded 0.7. Hooray!
Say you would like to use a learning rate schedule with your neural net, but you don't know what parameters are best for that schedule. Wouldn't it be nice if you could find those parameters with a grid search? With skorch
, this is possible. Below, we show how to access the parameters of your callbacks.
To simplify the access to your callback parameters, it is best if you give your callback a name. This is achieved by passing the callbacks
parameter a list of name, callback tuples, such as:
callbacks=[
('scheduler', LearningRateScheduler)),
...
],
This way, you can access your callbacks using the double underscore semantics (as, for instance, in an sklearn
Pipeline
):
callbacks__scheduler__epoch=50,
So if you would like to perform a grid search on, say, the number of units in the hidden layer and the learning rate schedule, it could look something like this:
param_grid = {
'module__num_units': [50, 100, 150],
'callbacks__scheduler__epoch': [10, 50, 100],
}
Note: If you would like to refresh your knowledge on grid search, look here, here, or in the Basic_Usage notebok.
Below, we show how accessing the callback parameters works our AccuracyTweet
callback:
net = NeuralNetClassifier(
ClassifierModule,
max_epochs=10,
lr=0.1,
warm_start=True,
callbacks=[
('tweet', AccuracyTweet(min_accuracy=0.7)),
],
callbacks__tweet__min_accuracy=0.6,
)
net.fit(X, y)
epoch train_loss valid_acc valid_loss dur ------- ------------ ----------- ------------ ------ 1 0.7139 0.5500 0.6933 0.1162 2 0.6916 0.5950 0.6873 0.0457 3 0.6829 0.5800 0.6814 0.0516 4 0.6671 0.6050 0.6718 0.0440 5 0.6670 0.6200 0.6639 0.1035 6 0.6622 0.6350 0.6546 0.0561 7 0.6371 0.6550 0.6429 0.0387 8 0.6293 0.6700 0.6312 0.0680 9 0.6170 0.6650 0.6200 0.0500 10 0.6204 0.6750 0.6119 0.0477 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ *tweet* Accuracy reached 0.6 at epoch 4!!! #skorch #pytorch ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<skorch.net.NeuralNetClassifier at 0x7faf5585b9b0>
As you can see, by passing callbacks__tweet__min_accuracy=0.6
, we changed that parameter. The same can be achieved by calling the set_params
method with the corresponding arguments:
net.set_params(callbacks__tweet__min_accuracy=0.75)
<skorch.net.NeuralNetClassifier at 0x7faf5585b9b0>
net.fit(X, y)
epoch train_loss valid_acc valid_loss dur ------- ------------ ----------- ------------ ------ 11 0.5845 0.7000 0.6016 0.0998 12 0.5831 0.7050 0.5915 0.0762 13 0.5854 0.7200 0.5788 0.0597 14 0.5582 0.7150 0.5729 0.0520 15 0.5601 0.7150 0.5692 0.0486 16 0.5468 0.7250 0.5662 0.0479 17 0.5333 0.7300 0.5583 0.0576 18 0.5592 0.7200 0.5555 0.0493 19 0.5295 0.7300 0.5488 0.0495 20 0.5232 0.7300 0.5428 0.0532 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ *tweet* Accuracy never reached 0.75 :( #skorch #pytorch ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<skorch.net.NeuralNetClassifier at 0x7faf5585b9b0>