{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Dropout\n",
"\n",
"Just now, we introduced the classical approach\n",
"of regularizing statistical models by penalyzing \n",
"the $\\ell_2$ norm of the weights.\n",
"In probabilistic terms, we could justify this technique\n",
"by arguing that we have assumed a prior belief\n",
"that weights take values from a Gaussian distribution with mean $0$.\n",
"More intuitively, we might argue \n",
"that we encouraged the model to spread out its weights \n",
"among many features and rather than depending too much \n",
"on a small number of potentially spurious associations.\n",
"\n",
"## Overfitting Revisited\n",
"\n",
"Given many more features than examples, linear models can overfit. \n",
"But when there are many more examples than features, \n",
"we can generally count on linear models not to overfit. \n",
"Unfortunately, the reliability with which linear models generalize \n",
"comes at a cost:\n",
"Linear models can’t take into account interactions among features. \n",
"For every feature, a linear model must assign \n",
"either a positive or a negative weight.\n",
"They lack the flexibility to account for context.\n",
"\n",
"In more formal texts, you’ll see this fundamental tension\n",
"between generalizability and flexibility\n",
"discussed as the *bias-variance tradeoff*. \n",
"Linear models have high bias \n",
"(they can only represent a small class of functions), \n",
"but low variance (they give similar results \n",
"across different random samples of the data).\n",
"\n",
"Deep neural networks take us to the opposite end \n",
"of the bias-variance spectrum. \n",
"Neural networks are so flexible because \n",
"they aren’t confined to looking at each feature individually. \n",
"Instead, they can learn interactions among groups of features. \n",
"For example, they might infer that “Nigeria” and “Western Union” \n",
"appearing together in an email indicates spam \n",
"but that “Nigeria” without “Western Union” does not.\n",
"\n",
"Even when we only have a small number of features, \n",
"deep neural networks are capable of overfitting. \n",
"In 2017, a group of researchers presented a now well-known\n",
"demonstration of the incredible flexibility of neural networks.\n",
"They presented a neural network with randomly-labeled images\n",
"(there was no true pattern linking the inputs to the outputs)\n",
"and found that the neural network, optimized by SGD,\n",
"could label every image in the training set perfectly.\n",
"\n",
"Consider what this means. \n",
"If the labels are assigned uniformly at random and there are 10 classes, \n",
"then no classifier can get better than 10% accuracy on holdout data. \n",
"Yet even in these situations, when there is no true pattern to be learned, neural networks can perfectly fit the training labels.\n",
"\n",
"## Robustness through Perturbations\n",
"\n",
"Let's think briefly about what we expect from a good statistical model. \n",
"We want it to do well on unseen test data. \n",
"One way we can accomplish this is by asking \n",
"what constitutes a a 'simple' model?\n",
"Simplicity can come in the form \n",
"of a small number of dimensions, \n",
"which is what we did when discussing fitting a model\n",
"with monomial basis functions. \n",
"Simplicity can also come in the form \n",
"of a small norm for the basis functions. \n",
"This led us to weight decay ($\\ell_2$ regularization). \n",
"Yet a third notion of simplicity that we can impose\n",
"is that the function should be robust \n",
"under small changes in the input. \n",
"For instance, when we classify images, \n",
"we would expect that adding some random noise \n",
"to the pixels should be mostly harmless.\n",
"\n",
"\n",
"In 1995, Christopher Bishop formalized \n",
"a form of this idea when he proved \n",
"that [*training with input noise is equivalent to Tikhonov regularization*](https://www.mitpressjournals.org/doi/10.1162/neco.1995.7.1.108). \n",
"In other words, he drew a clear mathematical connection \n",
"between the requirement that a function be smooth (and thus simple),\n",
"as we discussed in the section on weight decay,\n",
"with and the requirement that it be resilient to perturbations in the input. \n",
"\n",
"Then in 2014, [Srivastava et al., 2014](http://jmlr.org/papers/volume15/srivastava14a.old/srivastava14a.pdf),\n",
"developed a clever idea for how to apply Bishop's idea \n",
"to the *internal* layers of the network, too.\n",
"Namely they proposed to inject noise into each layer of the network\n",
"before calculating the subsequent layer during training. \n",
"They realized that when training deep network with many layers, \n",
"enforcing smoothness just on the input-output mapping \n",
"misses out on what is happening internally in the network.\n",
"Their proposed idea is called *dropout*, \n",
"and it is now a standard technique\n",
"that is widely used for training neural networks. \n",
"Throughout trainin, on each iteration,\n",
"dropout regularization consists simply of zeroing out \n",
"some fraction (typically 50%) of the nodes in each layer\n",
"before calculating the subsequent layer.\n",
"\n",
"The key challenge then is how to inject this noise \n",
"without introducing undue statistical *bias*. \n",
"In other words, we want to perturb the inputs \n",
"to each layer during training\n",
"in such a way that the expected value of the layer \n",
"is equal to the value it would have taken \n",
"had we not introduced any noise at all.\n",
"\n",
"In Bishop's case, when we are adding \n",
"Gaussian noise to a linear model, \n",
"this is simple:\n",
"At each training iteration, just add noise \n",
"sampled from a distribution with mean zero\n",
"$\\epsilon \\sim \\mathcal{N}(0,\\sigma^2)$ to the input $\\mathbf{x}$ , \n",
"yielding a perturbed point $\\mathbf{x}' = \\mathbf{x} + \\epsilon$.\n",
"In expectation, $\\mathbf{E}[\\mathbf{x}'] = \\mathbf{x}$. \n",
"\n",
"In the case of dropout regularization,\n",
"one can debias each layer\n",
"by normalizing by the fraction of nodes that were not dropped out.\n",
"In other words, dropout with drop probability $p$ is applied as follows:\n",
"\n",
"$$\n",
"\\begin{aligned}\n",
"h' =\n",
"\\begin{cases}\n",
" 0 & \\text{ with probability } p \\\\\n",
" \\frac{h}{1-p} & \\text{ otherwise}\n",
"\\end{cases}\n",
"\\end{aligned}\n",
"$$\n",
"\n",
"By design, the expectation remains unchanged, \n",
"i.e., $\\mathbf{E}[h'] = h$. \n",
"Intermediate activations $h$ are replaced by a random variable $h'$ \n",
"with matching expectation. \n",
"The name 'dropout' arises from the notion \n",
"that some neurons 'drop out' of the computation \n",
"for the purpose of computing the final result. \n",
"During training, we replace intermediate activations with random variables.\n",
"\n",
"## Dropout in Practice\n",
"\n",
"Recall the [multilayer perceptron](mlp.md) with a hidden layer and 5 hidden units. Its architecture is given by\n",
"\n",
"$$\n",
"\\begin{aligned}\n",
" h & = \\sigma(W_1 x + b_1) \\\\\n",
" o & = W_2 h + b_2 \\\\\n",
" \\hat{y} & = \\mathrm{softmax}(o)\n",
"\\end{aligned}\n",
"$$\n",
"\n",
"When we apply dropout to the hidden layer, \n",
"we are essentially removing each hidden unit with probability $p$,\n",
"(i.e., setting their output to $0$). \n",
"We can view the result as a network containing \n",
"only a subset of the original neurons. \n",
"In the image below, $h_2$ and $h_5$ are removed. \n",
"Consequently, the calculation of $y$ no longer depends on $h_2$ and $h_5$ \n",
"and their respective gradient also vanishes when performing backprop. \n",
"In this way, the calculation of the output layer \n",
"cannot be overly dependent on any one element of $h_1, \\ldots, h_5$. \n",
"Intuitively, deep learning researchers often explain the inutition thusly:\n",
"we do not want the network's output to depend \n",
"too precariously on the exact activation pathway through the network. \n",
"The original authors of the dropout technique \n",
"described their intuition as an effort \n",
"to prevent the *co-adaptation* of feature detectors.\n",
"\n",
"![MLP before and after dropout](../img/dropout2.svg)\n",
"\n",
"At test time, we typically do not use dropout.\n",
"However, we note that there are some exceptions:\n",
"some researchers use dropout at test time as a heuristic appraoch\n",
"for estimating the *confidence* of neural network predictions:\n",
"if the predictions agree across many different dropout masks,\n",
"then we might say that the network is more confident. \n",
"For now we will put off the advanced topic of uncertainty estimation\n",
"for subsequent chapters and volumes.\n",
"\n",
"\n",
"## Implementation from Scratch\n",
"\n",
"To implement the dropout function for a single layer, \n",
"we must draw as many samples from a Bernoulli (binary) random variable \n",
"as our layer has dimensions, where the random variable takes value $1$ (keep) with probability $1-p$ and $0$ (drop) with probability $p$.\n",
"One easy way to implement this is to first draw samples\n",
"from the uniform distribution $U[0,1]$.\n",
"then we can keep those nodes for which the corresponding \n",
"sample is greater than $p$, dropping the rest. \n",
"\n",
"In the following code, we implement a `dropout` function\n",
"that drops out the elements in the NDArray input `X` \n",
"with probability `drop_prob`, \n",
"rescaling the remainder as described above \n",
"(dividing the survivors by `1.0-drop_prob`)."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import sys\n",
"sys.path.insert(0, '..')\n",
"\n",
"import d2l\n",
"from mxnet import autograd, gluon, init, nd\n",
"from mxnet.gluon import loss as gloss, nn\n",
"\n",
"def dropout(X, drop_prob):\n",
" assert 0 <= drop_prob <= 1\n",
" # In this case, all elements are dropped out\n",
" if drop_prob == 1:\n",
" return X.zeros_like()\n",
" mask = nd.random.uniform(0, 1, X.shape) > drop_prob\n",
" return mask * X / (1.0-drop_prob)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can test out the `dropout` function on a few examples. \n",
"In the following lines of code, we pass our input `X` \n",
"through the dropout operation, with probabilities 0, 0.5, and 1, respectively."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"[[ 0. 1. 2. 3. 4. 5. 6. 7.]\n",
" [ 8. 9. 10. 11. 12. 13. 14. 15.]]\n",
"\n",
"\n",
"[[ 0. 0. 0. 0. 8. 10. 12. 0.]\n",
" [16. 0. 20. 22. 0. 0. 0. 30.]]\n",
"\n",
"\n",
"[[0. 0. 0. 0. 0. 0. 0. 0.]\n",
" [0. 0. 0. 0. 0. 0. 0. 0.]]\n",
"\n"
]
}
],
"source": [
"X = nd.arange(16).reshape((2, 8))\n",
"print(dropout(X, 0))\n",
"print(dropout(X, 0.5))\n",
"print(dropout(X, 1))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Defining Model Parameters\n",
"\n",
"Again, we can use the Fashion-MNIST dataset, \n",
"introduced in the section \n",
"[\"Softmax Regression - Starting From Scratch\"](softmax-regression-scratch.md). \n",
"We will define a multilayer perceptron with two hidden layers. \n",
"The two hidden layers both have 256 outputs."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"num_inputs, num_outputs, num_hiddens1, num_hiddens2 = 784, 10, 256, 256\n",
"\n",
"W1 = nd.random.normal(scale=0.01, shape=(num_inputs, num_hiddens1))\n",
"b1 = nd.zeros(num_hiddens1)\n",
"W2 = nd.random.normal(scale=0.01, shape=(num_hiddens1, num_hiddens2))\n",
"b2 = nd.zeros(num_hiddens2)\n",
"W3 = nd.random.normal(scale=0.01, shape=(num_hiddens2, num_outputs))\n",
"b3 = nd.zeros(num_outputs)\n",
"\n",
"params = [W1, b1, W2, b2, W3, b3]\n",
"for param in params:\n",
" param.attach_grad()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Define the Model\n",
"\n",
"The model defined below concatenates the fully-connected layer\n",
" and the activation function ReLU, \n",
" using dropout for the output of each activation function. \n",
" We can set the dropout probability of each layer separately. \n",
" It is generally recommended to set \n",
" a lower dropout probability closer to the input layer. \n",
" Below we set it to 0.2 and 0.5 for the first and second hidden layer respectively. \n",
" By using the `is_training` function described in the \n",
" [\"Autograd\"](../chapter_prerequisite/autograd.md) section, \n",
" we can ensure that dropout is only active during training."
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"drop_prob1, drop_prob2 = 0.2, 0.5\n",
"\n",
"def net(X):\n",
" X = X.reshape((-1, num_inputs))\n",
" H1 = (nd.dot(X, W1) + b1).relu()\n",
" # Use dropout only when training the model\n",
" if autograd.is_training():\n",
" # Add a dropout layer after the first fully connected layer\n",
" H1 = dropout(H1, drop_prob1)\n",
" H2 = (nd.dot(H1, W2) + b2).relu()\n",
" if autograd.is_training():\n",
" # Add a dropout layer after the second fully connected layer\n",
" H2 = dropout(H2, drop_prob2)\n",
" return nd.dot(H2, W3) + b3"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Training and Testing\n",
"\n",
"This is similar to the training and testing of multilayer perceptrons described previously."
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"epoch 1, loss 1.1589, train acc 0.547, test acc 0.791\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"epoch 2, loss 0.5836, train acc 0.784, test acc 0.831\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"epoch 3, loss 0.4976, train acc 0.818, test acc 0.849\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"epoch 4, loss 0.4455, train acc 0.838, test acc 0.857\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"epoch 5, loss 0.4227, train acc 0.846, test acc 0.851\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"epoch 6, loss 0.3991, train acc 0.853, test acc 0.871\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"epoch 7, loss 0.3836, train acc 0.860, test acc 0.868\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"epoch 8, loss 0.3719, train acc 0.864, test acc 0.877\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"epoch 9, loss 0.3578, train acc 0.869, test acc 0.875\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"epoch 10, loss 0.3508, train acc 0.872, test acc 0.871\n"
]
}
],
"source": [
"num_epochs, lr, batch_size = 10, 0.5, 256\n",
"loss = gloss.SoftmaxCrossEntropyLoss()\n",
"train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)\n",
"d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, batch_size,\n",
" params, lr)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Concise Implementation\n",
"\n",
"Using Gluon, all we need to do is add a `Dropout` layer \n",
"(also in the `nn` package)\n",
"after each fully-connected layer, passing in the dropout probability\n",
"as the only argument to its constructor. \n",
"During training, the `Dropout` layer will randomly \n",
"drop out outputs of the previous layer\n",
"(or equivalently, the inputs to the subequent layer)\n",
"according to the specified dropout probability. \n",
"When MXNet is not in training mode, \n",
"the `Dropout` layer simply passes the data through during testing."
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"net = nn.Sequential()\n",
"net.add(nn.Dense(256, activation=\"relu\"),\n",
" # Add a dropout layer after the first fully connected layer\n",
" nn.Dropout(drop_prob1),\n",
" nn.Dense(256, activation=\"relu\"),\n",
" # Add a dropout layer after the second fully connected layer\n",
" nn.Dropout(drop_prob2),\n",
" nn.Dense(10))\n",
"net.initialize(init.Normal(sigma=0.01))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Next, we train and test the model."
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"epoch 1, loss 1.1817, train acc 0.545, test acc 0.781\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"epoch 2, loss 0.5901, train acc 0.781, test acc 0.827\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"epoch 3, loss 0.4891, train acc 0.820, test acc 0.858\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"epoch 4, loss 0.4506, train acc 0.836, test acc 0.857\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"epoch 5, loss 0.4214, train acc 0.846, test acc 0.864\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"epoch 6, loss 0.4028, train acc 0.853, test acc 0.867\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"epoch 7, loss 0.3849, train acc 0.860, test acc 0.872\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"epoch 8, loss 0.3701, train acc 0.865, test acc 0.876\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"epoch 9, loss 0.3614, train acc 0.867, test acc 0.876\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"epoch 10, loss 0.3504, train acc 0.873, test acc 0.877\n"
]
}
],
"source": [
"trainer = gluon.Trainer(net.collect_params(), 'sgd', {'learning_rate': lr})\n",
"d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, batch_size, None,\n",
" None, trainer)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Summary\n",
"\n",
"* Beyond controlling the number of dimensions and the size of the weight vector, dropout is yet another tool to avoid overfitting. Often all three are used jointly.\n",
"* Dropout replaces an activation $h$ with a random variable $h'$ with expected value $h$ and with variance given by the dropout probability $p$.\n",
"* Dropout is only used during training.\n",
"\n",
"\n",
"## Exercises\n",
"\n",
"1. Try out what happens if you change the dropout probabilities for layers 1 and 2. In particular, what happens if you switch the ones for both layers?\n",
"1. Increase the number of epochs and compare the results obtained when using dropout with those when not using it.\n",
"1. Compute the variance of the the activation random variables after applying dropout.\n",
"1. Why should you typically not using dropout?\n",
"1. If changes are made to the model to make it more complex, such as adding hidden layer units, will the effect of using dropout to cope with overfitting be more obvious?\n",
"1. Using the model in this section as an example, compare the effects of using dropout and weight decay. What if dropout and weight decay are used at the same time?\n",
"1. What happens if we apply dropout to the individual weights of the weight matrix rather than the activations?\n",
"1. Replace the dropout activation with a random variable that takes on values of $[0, \\gamma/2, \\gamma]$. Can you design something that works better than the binary dropout function? Why might you want to use it? Why not?\n",
"\n",
"## References\n",
"\n",
"[1] Srivastava, N., Hinton, G., Krizhevsky, A., Sutskever, I., & Salakhutdinov, R. (2014). JMLR\n",
"\n",
"## Scan the QR Code to [Discuss](https://discuss.mxnet.io/t/2343)\n",
"\n",
"![](../img/qr_dropout.svg)"
]
}
],
"metadata": {
"language_info": {
"name": "python"
}
},
"nbformat": 4,
"nbformat_minor": 2
}