I'm going through Daniel Nouri's tutorial on facial recognition using CNN's, and I've come across a bit of the code which I don't understand. Daniel is defining a Class to be called at the end of each iteration during the training of the network, that will decide whether or not the training should stop early:
class EarlyStopping(object):
def __init__(self, patience=100):
self.patience = patience
self.best_valid = np.inf
self.best_valid_epoch = 0
self.best_weights = None
def __call__(self, nn, train_history):
current_valid = train_history[-1]['valid_loss']
current_epoch = train_history[-1]['epoch']
if current_valid < self.best_valid:
self.best_valid = current_valid
self.best_valid_epoch = current_epoch
self.best_weights = nn.get_all_params_values()
elif self.best_valid_epoch + self.patience < current_epoch:
print("Early stopping.")
print("Best valid loss was {:.6f} at epoch {}.".format(
self.best_valid, self.best_valid_epoch))
nn.load_params_from(self.best_weights)
raise StopIteration()
This makes some sense, however the actual implementation in the code looks like:
net8 = NeuralNet(
# ...
on_epoch_finished=[
AdjustVariable('update_learning_rate', start=0.03, stop=0.0001),
AdjustVariable('update_momentum', start=0.9, stop=0.999),
EarlyStopping(patience=200),
],
# ...
)
Clearly, Daniel is calling the class as a function. However, I don't understand how he is calling it without the arguments shown in __call__(args). Is this just how things are meant to be implemented in the source code of nolearn? I'm confused as to how the network knows to use nn and train_history without those being passed into the function.
He isn't invoking
__call__withEarlyStopping(patience=200), rather, he's invoking*EarlyStopping.__init__with a signature of:and providing an alternate value for
patience; this fully matches the arguments available for__init__.EarlyStopping.__call__is invoked on the instance; that is, if the sequence of calls was:an appropriate error would be raised.
*The parentheses that throw you off are actually making a call. The call isn't made to
EarlyStopping.__call__but totype.__call__, the (meta)class ofEarlyStopping.type.__call__is the first action performed by Python when you initialize an object, it gets called accepting any arguments passed and then (after some other actions) calls__new__and__init__in that order; in essence__init__is invoked indirectly with an argument ofpatience=100.