MediumNeural Networks

Implement Dropout (Training Mode)

Neural Networks

Medium

Problem

Implement inverted dropout for a NumPy array. During training, independently drop each element with probability p. Scale every retained element by 1 / (1 - p) so the expected output magnitude is unchanged.

Use rng.random(x.shape) when an rng generator is provided. Otherwise, use np.random.random(x.shape). The test panel displays a seed; the runner creates np.random.default_rng(seed) and passes that generator as rng.

Return (output, dropout_pattern), where dropout_pattern is the scaled mask applied to the input. Its entries are 0 for dropped elements and 1 / (1 - p) for retained elements.

Theory

A neural network with millions of parameters can memorize the training data instead of learning general patterns. It achieves near-perfect accuracy on training examples but performs poorly on new, unseen data. This is overfitting.

Signs of overfitting:

Several techniques exist to combat this (weight decay, data augmentation, early stopping), but dropout is one of the most effective and widely used.


What Dropout Does

During each training step, dropout randomly sets a fraction of the neurons to zero. Each neuron has a probability p of being "dropped" (set to zero) and a probability 1 - p of being "kept."

For example, with p = 0.5 and a layer of 4 neurons with values [3.0, 1.0, 4.0, 2.0], one possible outcome is:

Result before scaling: [3.0, 0.0, 4.0, 0.0]

The dropped neurons are chosen randomly and independently. Each training step uses a different random pattern. The network never knows which neurons will be available, so it cannot rely on any single neuron or any specific combination of neurons.


Why Random Dropping Helps

The key intuition: dropout prevents co-adaptation.

Without dropout, neurons can develop complex co-dependencies. Neuron A might learn to rely on neuron B always being there to correct its errors. If B is always present during training, this strategy works on the training set. But it makes the network fragile. Any small perturbation can break the delicate coordination.

With dropout, neuron A cannot count on neuron B being present. On some training steps B is there, on others it is not. So A must learn to be useful on its own. Every neuron is forced to learn features that are independently valuable.

This has several effects:


The Scaling Problem

There is a catch. If you randomly zero out a fraction p of the neurons, the expected sum of the layer's output drops by a factor of (1 - p).

Without dropout, the expected output of a neuron with value x_i is just x_i.

With dropout at rate p, the expected output is:

E[\text{output}_i] = (1 - p) \cdot x_i + p \cdot 0 = (1 - p) \cdot x_i

The expected value is now smaller by a factor of (1 - p). This matters because the next layer in the network expects inputs of a certain magnitude. If the expected magnitude changes between training (with dropout) and inference (without dropout), the network's behavior will be inconsistent.


Inverted Dropout: The Fix

The solution is to scale up the surviving neurons during training to compensate for the dropped ones. Each kept neuron gets multiplied by \frac{1}{1-p}:

\text{output}_i = \begin{cases} 0 & \text{with probability } p \\ x_i \cdot \frac{1}{1-p} & \text{with probability } (1-p) \end{cases}

Now check the expected value:

E[\text{output}_i] = p \cdot 0 + (1-p) \cdot x_i \cdot \frac{1}{1-p} = x_i

The expected value is exactly x_i, the same as without dropout. This is called inverted dropout, and it is the standard implementation used in practice (PyTorch, TensorFlow, etc.).

The alternative (standard dropout) does not scale during training and instead multiplies all outputs by (1 - p) at inference time. Inverted dropout is preferred because it keeps inference simple: at test time, you just use all neurons without any modification.


The Dropout Mask

The randomness in dropout is captured by a mask (also called the dropout pattern). The mask is an array with the same shape as the input:

The output is simply: \text{output} = x \cdot \text{mask} (element-wise multiplication).

For p = 0.5 and input [2.0, 4.0], a possible mask is [0, 2.0], giving output [0, 8.0]. Another possible mask is [2.0, 0], giving [4.0, 0].

Returning the mask alongside the output is useful because:


Training vs. Inference

This is a critical distinction:

During training:

During inference (with inverted dropout):

The network uses its full capacity at test time. Because the training-time scaling already compensated for the missing neurons, no adjustment is needed at inference.


Choosing the Dropout Rate

The dropout rate p controls how aggressively you regularize:

Different layers can use different dropout rates. A common pattern is lighter dropout (or none) on the input layer and heavier dropout on the larger hidden layers.


Where Dropout Shows Up

Examples

Example 1

Input
x = [[1, 2], [3, 4]], p = 0.5, seed = 123
Output
([[0.0, 4.0], [6.0, 8.0]], [[0.0, 2.0], [2.0, 2.0]])
Explanation
The seeded generator drops the first element. Retained elements are multiplied by 2 because p = 0.5.

Example 2

Input
x = [[1, 2], [3, 4]], p = 0.0, seed = 7
Output
([[1.0, 2.0], [3.0, 4.0]], [[1.0, 1.0], [1.0, 1.0]])

Hints

  1. Generate one random value per input element and retain positions below 1 - p.
  2. Build the pattern with 1 / (1 - p) at retained positions and zero elsewhere.

Requirements

Constraints

Starter Code

import numpy as np

def dropout(
    x: list,
    p: float = 0.5,
    rng: np.random.Generator = None,
) -> tuple[np.ndarray, np.ndarray]:
    """
    Returns (output, dropout_pattern) as NumPy arrays matching the shape of x.
    """
    # Write code here
    pass

Test Cases

CaseMatches
Basic 2x2 matrix with 50% dropoutpublic
No dropout p=0Example 1public