We continue with the data and the features of the previous page. The best Poisson regression needed an interaction between the hour and the day type that we found by looking at the data. A network can learn such interactions from the one-hot columns alone.
the data and the one-hot features of the previous page
The output is the rate \(\lambda(x) = \exp(f_\theta(x))\) and the loss is the negative Poisson log-likelihood, as for the Poisson regression, with a network \(f_\theta\) instead of a linear function.
A smooth variant of the relu, \(\mathrm{gelu}(a) = a\,\Phi(a)\) with \(\Phi\) the standard normal cumulative distribution function.
2
Batch normalization: every hidden neuron is standardized with the mean and the standard deviation of its activity in the current batch, then scaled and shifted by two learned parameters. In evaluation mode it uses running averages of these statistics instead.
3
Without bias, because a bias right after batch normalization would be redundant with its learned shift.
4
The rate, never negative.
5
The negative Poisson log-likelihood without the term \(\log y!\), which does not depend on the parameters. The small constant avoids \(\log 0\).
The learning curve
Before cross-validating, we train once on the first 80% of the hours and watch the loss on the last 20%. This is how we chose the width, the batch size, the learning rate and the number of epochs.
training by hand for 30 epochs and recording both losses
fig, ax = plt.subplots(figsize=(6, 3.2))ax.plot(range(1, 31), [c[0] for c in curve], label="training")ax.plot(range(1, 31), [c[1] for c in curve], label="validation")ax.set(xlabel="epoch", ylabel="Poisson deviance", ylim=(0, None))ax.legend()plt.show()
Figure 46.1: Poisson deviance on the first 80% of the hours, which we train on, and on the last 20%, after every epoch.
The training deviance keeps falling, the validation deviance levels off much higher.
Cross-validation with skorch
To cross-validate the network exactly like the regressions, with the preprocessing inside the folds, we wrap it with the skorch package. Its NeuralNetRegressor has the fit and predict methods of an sklearn model and runs the training loop above inside fit.
On the previous page the regression with one-hot hours had about 38, and the one with an hour by day type interaction about 19. The network gets there from the one-hot columns without being told about the interaction.
the training deviance of the same network, fitted to all rows
The training deviance is much lower than the cross-validated one, so the network overfits. Weight decay, the L2 regularization of week 7, is a hyper-parameter of the optimizer.
for wd in [1e-2, 1e-1]: torch.manual_seed(1) model = poisson_net(optimizer__weight_decay=wd) dev =-cross_val_score(model, X, y, cv=cv, scoring="neg_mean_poisson_deviance")print(f"weight decay {wd}: CV deviance {dev.mean():.1f}")
1
Arguments that start with optimizer__ go to AdamW.
Figure 46.2: Left, the cross-validated prediction of every hour against the observed count. Right, the mean residual (observed minus predicted) per hour of the day, for the network and for the regression with one-hot hours.
The residuals of the regression with one-hot hours vary systematically with the hour, which is the interaction it cannot express. Those of the network stay closer to zero.
The final model
The cross-validation chose the network with weight decay 0.1. For predictions on new hours, we fit it once more on all the data.