2

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.

Dimitris Fasarakis Hilliard
  • 150,925
  • 31
  • 268
  • 253

1 Answers1

4

He isn't invoking __call__ with EarlyStopping(patience=200), rather, he's invoking* EarlyStopping.__init__ with a signature of:

def __init__(self, patience=100):

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:

e = EarlyStopping(patience = 200)
e(patience=50)  # TypeError Raised

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 to type.__call__, the (meta)class of EarlyStopping. 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 of patience=100.

Dimitris Fasarakis Hilliard
  • 150,925
  • 31
  • 268
  • 253
  • Ok, so should I presume that the initialized object is then called somewhere in the source code for NeuralNet, with the appropriate arguments? It seems to me like a strange way to implement this feature. Why not just pass a function? – Isaiah Becker-Mayer Oct 30 '16 at 15:31