def dropout_layer(X, dropout, key):
assert 0 <= dropout <= 1
if dropout == 1: return jnp.zeros_like(X)
mask = jax.random.uniform(key, X.shape) > dropout
return jnp.asarray(mask, dtype=X.dtype) * X / (1.0 - dropout)Dive into Deep Learning · §4.6
Regularizing with dropout
Randomly mask hidden activations during training and evaluate the full network at test time.
Motivation
Modern nets are overparameterized: more weights than training points. Past the interpolation threshold, plain gradient descent can drive training error to zero by memorizing.
We want to retain capacity while discouraging the model from fitting peculiarities of the training set.
The idea
Srivastava, Hinton et al. (2014) gave a simple definition:
Each training step, set each hidden unit to zero independently with probability p, then rescale the survivors by 1/(1-p). At test time, turn it off.
Although dropout removes part of the network on each training step, it can improve generalization and is used in many Transformer configurations.
01
Why It Works
three views: a thinned net, an ensemble, broken co-adaptation
Why It Works
Zeroing units removes them from this step’s forward and backward pass. What is left is a thinned subnetwork; the next step samples a different one.
Here h_2 and h_5 are dropped, so the output cannot depend on them, and no single unit can dominate.
Why It Works
A net with n hidden units has 2^n possible masks, so 2^n thinned subnetworks, all sharing one set of weights. Today’s model has two 256-unit layers: 2^{512} \approx 10^{154} subnetworks.
The ensemble view is historical motivation. In a nonlinear network, the full test-time pass is not generally the arithmetic or geometric mean of the masked networks. Only the activation-level expectation is exact.
Why It Works
Because no unit can count on any specific partner being present, each is pushed to learn a feature that is useful on its own:
Three lenses, one mechanism: structured noise during training.
Why It Works
Replace each activation h with the random variable
h' = \begin{cases} 0 & \text{with probability } p, \\[2pt] \dfrac{h}{1 - p} & \text{otherwise.} \end{cases}
The factor 1/(1-p) is the unique constant that keeps \mathbb{E}[h'] = p\cdot 0 + (1-p)\dfrac{h}{1-p} = h.
Rescaling during training is inverted dropout, what every modern framework implements. The original 2014 formulation instead multiplied the weights by 1-p at test time, equivalent in expectation, but inverting moves all bookkeeping into training, which is exactly why a Dropout layer can be a no-op in eval.
02
From Scratch
mask, rescale, and drop in the forward pass
From Scratch
Sample a Bernoulli keep-mask from a uniform draw, multiply, then rescale the survivors by 1/(1-p) to restore the expectation:
From Scratch
dropout_p = 0: [[ 0. 1. 2. 3. 4. 5. 6. 7.]
[ 8. 9. 10. 11. 12. 13. 14. 15.]]
dropout_p = 0.5: [[ 0. 0. 4. 0. 0. 0. 12. 14.]
[ 0. 0. 20. 22. 24. 26. 0. 30.]]
dropout_p = 1: [[0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0.]]
From Scratch
Apply it to each hidden layer’s output, after the activation:
Linear → ReLU → Dropout → Linear → ReLU → Dropout → Linear
Convention: a smaller rate near the input (low-level features must stay reliable), larger deeper in. Active in training only.
From Scratch
dropout_layer slots into forward right after each hidden activation, guarded by the training flag so evaluation always runs the full, unmasked network:
class DropoutMLPScratch(d2l.Classifier):
def __init__(self, num_outputs, num_hiddens_1, num_hiddens_2,
dropout_1, dropout_2, lr, num_inputs=784, rngs=None):
super().__init__()
self.save_hyperparameters(ignore=['rngs'])
rngs = nnx.Rngs(params=d2l.get_key(), dropout=d2l.get_key()) \
if rngs is None else rngs
self.lin1 = nnx.Linear(num_inputs, num_hiddens_1, rngs=rngs)
self.lin2 = nnx.Linear(num_hiddens_1, num_hiddens_2, rngs=rngs)
self.lin3 = nnx.Linear(num_hiddens_2, num_outputs, rngs=rngs)
self.rngs = rngs
self.deterministic = False
def set_view(self, *, deterministic):
self.deterministic = deterministic
def forward(self, X):
H1 = nnx.relu(self.lin1(X.reshape(X.shape[0], -1)))
if not self.deterministic:
H1 = dropout_layer(H1, self.dropout_1, self.rngs.dropout())
H2 = nnx.relu(self.lin2(H1))
if not self.deterministic:
H2 = dropout_layer(H2, self.dropout_2, self.rngs.dropout())
return self.lin3(H2)From scratch · result
Two 256-unit hidden layers, dropout 0.2 after the first and 0.5 after the second (the gentler-near-the-input convention in action), on Fashion-MNIST:
The displayed train and validation curves track closely across 30 epochs. A causal claim about dropout would require a seed-matched no-dropout run under the same training settings.
03
Concise
one stock layer, train/eval handled for you
Concise
nn.Dropout(p) is a stock layer that also knows the train vs. eval switch: in eval mode it becomes a no-op, with no rescaling needed.
class DropoutMLP(d2l.Classifier):
def __init__(self, num_outputs, num_hiddens_1, num_hiddens_2,
dropout_1, dropout_2, lr, num_inputs=784, rngs=None):
super().__init__()
self.save_hyperparameters(ignore=['rngs'])
rngs = nnx.Rngs(params=d2l.get_key(), dropout=d2l.get_key()) \
if rngs is None else rngs
self.lin1 = nnx.Linear(num_inputs, num_hiddens_1, rngs=rngs)
self.drop1 = nnx.Dropout(dropout_1, rngs=rngs)
self.lin2 = nnx.Linear(num_hiddens_1, num_hiddens_2, rngs=rngs)
self.drop2 = nnx.Dropout(dropout_2, rngs=rngs)
self.lin3 = nnx.Linear(num_hiddens_2, num_outputs, rngs=rngs)
def forward(self, X):
X = X.reshape((X.shape[0], -1))
X = self.drop1(nnx.relu(self.lin1(X)))
X = self.drop2(nnx.relu(self.lin2(X)))
return self.lin3(X)Concise
With the same hyperparameters, the layer performs the masking and rescaling internally:
Current practice
Dropout usage depends on the architecture, dataset size, and training scale.
In the configuration where dropout is placed before batch norm, masking can distort the running variance and create an evaluation-time mismatch (Li et al., 2019).
Dropout is a low-cost regularizer that can be combined with weight decay and data augmentation. It also motivated a broader family of stochastic regularization methods.
Wrap-up
nn.Dropout(p) does it all and respects train/eval.Exercise 5 keeps dropout on at test time: average 20 passes, and you get uncertainty estimates (MC dropout). Next, the Kaggle house-prices section applies the methods from this chapter, deployed on a Kaggle competition.