Regularization & Dropout
What you’ll learn
- L1 and L2 regularization on a layer’s weights, and
functools.partialfunctools.partialto avoid repeating the same arguments everywhere - Dropout — randomly ignoring neurons during training — and why it works
- MC Dropout, which turns a trained Dropout model into an uncertainty estimator for free
- max-norm regularization, which rescales weights after every training step
Why regularize at all?
Deep nets have “an incredible amount of freedom”: tens of thousands of parameters, sometimes millions. That’s exactly what lets them fit complex datasets — and exactly what lets them overfit the training set instead of learning something that generalizes. You already know one of the best fixes from Chapter 10’s toolkit: early stopping. Batch Normalization also acts as a mild regularizer as a side effect. This page covers three more dedicated techniques.
Reducing model size — the simplest regularizer
Before reaching for L1/L2 penalties or Dropout, Chollet’s suggestion is to try the cheapest fix first: make the model smaller. A model with fewer parameters has less room to simply memorize the training set, so to reduce its loss it’s forced to learn compressed, genuinely predictive patterns instead — exactly what generalizes to new data. There’s no formula for the “right” size; the workflow is to start small and add capacity only until validation loss stops improving.
import tensorflow as tf
def build(units):
return tf.keras.models.Sequential([
tf.keras.layers.Dense(units, activation="relu", input_shape=(20,)),
tf.keras.layers.Dense(units, activation="relu"),
tf.keras.layers.Dense(1, activation="sigmoid"),
])
small_model = build(units=4) # underfits later to overfit, but recovers slower
reference_model = build(units=16)
large_model = build(units=512) # overfits almost immediately, noisy val loss
for name, model in [("small", small_model), ("reference", reference_model), ("large", large_model)]:
print(name, "params:", model.count_params())import tensorflow as tf
def build(units):
return tf.keras.models.Sequential([
tf.keras.layers.Dense(units, activation="relu", input_shape=(20,)),
tf.keras.layers.Dense(units, activation="relu"),
tf.keras.layers.Dense(1, activation="sigmoid"),
])
small_model = build(units=4) # underfits later to overfit, but recovers slower
reference_model = build(units=16)
large_model = build(units=512) # overfits almost immediately, noisy val loss
for name, model in [("small", small_model), ("reference", reference_model), ("large", large_model)]:
print(name, "params:", model.count_params())A model that’s too small won’t overfit at all — it’ll plateau and stay there, which is itself useful information: it tells you to add capacity back before you reach for any of the regularizers below.
L1 and L2 regularization
Just like Ridge and Lasso for linear models, you can penalize a layer’s connection weights directly. L2 regularization shrinks weights toward zero smoothly; L1 pushes many of them all the way to zero, giving you a sparse model.
import tensorflow as tf
layer = tf.keras.layers.Dense(
100,
activation="elu",
kernel_initializer="he_normal",
kernel_regularizer=tf.keras.regularizers.l2(0.01),
)import tensorflow as tf
layer = tf.keras.layers.Dense(
100,
activation="elu",
kernel_initializer="he_normal",
kernel_regularizer=tf.keras.regularizers.l2(0.01),
)Because you’ll typically want the same activation, initializer, and regularizer
on every hidden layer, functools.partialfunctools.partial saves you from repeating yourself
(and from typos that only show up on the third layer):
import tensorflow as tf
from functools import partial
RegularizedDense = partial(
tf.keras.layers.Dense,
activation="elu",
kernel_initializer="he_normal",
kernel_regularizer=tf.keras.regularizers.l2(0.01),
)
model = tf.keras.models.Sequential([
tf.keras.layers.Flatten(input_shape=[28, 28]),
RegularizedDense(300),
RegularizedDense(100),
RegularizedDense(10, activation="softmax", kernel_initializer="glorot_uniform"),
])import tensorflow as tf
from functools import partial
RegularizedDense = partial(
tf.keras.layers.Dense,
activation="elu",
kernel_initializer="he_normal",
kernel_regularizer=tf.keras.regularizers.l2(0.01),
)
model = tf.keras.models.Sequential([
tf.keras.layers.Flatten(input_shape=[28, 28]),
RegularizedDense(300),
RegularizedDense(100),
RegularizedDense(10, activation="softmax", kernel_initializer="glorot_uniform"),
])Dropout
Dropout is one of the most popular — and most surprising — regularizers for
deep nets. At every training step, every neuron (including inputs, but never
outputs) has a probability pp of being temporarily ignored entirely for that
step. The dropout rate pp is usually 10%–50%10%–50%.
flowchart TD A["Full network"] --> B["Step t: random neurons dropped
(dashed = output 0)"] A --> C["Step t+1: a different random subset dropped"] B --> D["Trained weights are shared across
every random sub-network"] C --> D
It sounds destructive, but it works because no neuron can rely on any single neighbor being present — each one has to become independently useful, which produces a network that’s less sensitive to noise and generalizes better. Keras handles the training/prediction asymmetry (scaling outputs by the keep probability) for you automatically:
import tensorflow as tf
model = tf.keras.models.Sequential([
tf.keras.layers.Flatten(input_shape=[28, 28]),
tf.keras.layers.Dropout(rate=0.2),
tf.keras.layers.Dense(300, activation="elu", kernel_initializer="he_normal"),
tf.keras.layers.Dropout(rate=0.2),
tf.keras.layers.Dense(100, activation="elu", kernel_initializer="he_normal"),
tf.keras.layers.Dropout(rate=0.2),
tf.keras.layers.Dense(10, activation="softmax"),
])import tensorflow as tf
model = tf.keras.models.Sequential([
tf.keras.layers.Flatten(input_shape=[28, 28]),
tf.keras.layers.Dropout(rate=0.2),
tf.keras.layers.Dense(300, activation="elu", kernel_initializer="he_normal"),
tf.keras.layers.Dropout(rate=0.2),
tf.keras.layers.Dense(100, activation="elu", kernel_initializer="he_normal"),
tf.keras.layers.Dropout(rate=0.2),
tf.keras.layers.Dense(10, activation="softmax"),
])Since Dropout is only active during training, always compare training loss without dropout (measured after training) against the validation loss — with dropout on, a model can look like it isn’t overfitting even when it is.
MC Dropout
Yarin Gal and Zoubin Ghahramani’s trick: leave dropout on at prediction time and run the same input through the model many times. Because dropout is random, every pass gives a slightly different prediction — average them, and you get both a better point estimate and a genuine uncertainty measure (the standard deviation across passes), all without retraining anything.
import numpy as np
# training=True keeps Dropout active even though we are predicting
y_probas = np.stack([model(X_test_scaled, training=True) for _ in range(100)])
y_proba = y_probas.mean(axis=0) # the MC Dropout point estimate
y_std = y_probas.std(axis=0) # how uncertain each prediction isimport numpy as np
# training=True keeps Dropout active even though we are predicting
y_probas = np.stack([model(X_test_scaled, training=True) for _ in range(100)])
y_proba = y_probas.mean(axis=0) # the MC Dropout point estimate
y_std = y_probas.std(axis=0) # how uncertain each prediction isIf other layers (like BatchNormalization) also behave differently in
training vs. prediction, forcing training=Truetraining=True globally isn’t safe — subclass
DropoutDropout instead so only dropout is forced on:
import tensorflow as tf
class MCDropout(tf.keras.layers.Dropout):
def call(self, inputs):
return super().call(inputs, training=True)import tensorflow as tf
class MCDropout(tf.keras.layers.Dropout):
def call(self, inputs):
return super().call(inputs, training=True)Max-norm regularization
Instead of adding a penalty term to the loss, max-norm regularization
rescales a neuron’s incoming weight vector after every training step, so that
‖w‖₂ ≤ r‖w‖₂ ≤ r. Smaller rr means stronger regularization, and it can also help
tame unstable gradients even without Batch Normalization.
import tensorflow as tf
layer = tf.keras.layers.Dense(
100,
activation="elu",
kernel_initializer="he_normal",
kernel_constraint=tf.keras.constraints.max_norm(1.0),
)import tensorflow as tf
layer = tf.keras.layers.Dense(
100,
activation="elu",
kernel_initializer="he_normal",
kernel_constraint=tf.keras.constraints.max_norm(1.0),
)Visualize it
Each training step, Dropout silently switches a random subset of neurons off (shown dimmed below) — click to re-roll which ones are dropped:
🧪 Try It Yourself
Exercise 1 – L2-Regularize a Dense Layer
Exercise 2 – Add a Dropout Layer
Exercise 3 – Run MC Dropout Predictions
Next
Continue to Learning Rate Scheduling — the last lever for training deep nets faster and reaching a better final solution.
If this helped you, consider buying me a coffee ☕
Buy me a coffeeWas this page helpful?
Let us know how we did
