Fast.ai: Deep Learning 2018 - Lesson 4 (Spreadsheet CNN Dropout)
Lex Toumbourou ·
%load_ext autoreload
%autoreload 2
import numpy as np
import pandas as pd
from matplotlib import pyplot as plt
from pathlib import Path
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.autograd import Variable
from tqdm import tqdm_notebook
from fastai.conv_learner import ConvLearner
from fastai.dataset import ImageClassifierData, get_cv_idxs
from fastai.transforms import tfms_from_model
/Users/lex/anaconda3/envs/fastai-cpu/lib/python3.6/importlib/_bootstrap.py:219: RuntimeWarning: numpy.dtype size changed, may indicate binary incompatibility. Expected 96, got 88 return f(*args, **kwds) /Users/lex/anaconda3/envs/fastai-cpu/lib/python3.6/site-packages/sklearn/ensemble/weight_boosting.py:29: DeprecationWarning: numpy.core.umath_tests is an internal NumPy module and should not be imported. It will be removed in a future NumPy release. from numpy.core.umath_tests import inner1d
%matplotlib inline
Lesson 4 - Adding Dropout to spreadsheet CNN
PATH = Path('./data/mnist')
PATH.mkdir(exist_ok=True)
Dataset
!kaggle competitions download -c digit-recognizer --path={PATH}
df = pd.read_csv(PATH/'train.csv')
Load a single image
img_pixels = df.loc[7, [c for c in df.columns if c.startswith('pixel')]]
img_arr = np.array([int(i) for i in img_pixels])
img_arr = img_arr.reshape((28, 28))
img_arr_float = img_arr / 255.
plt.imshow(img_arr_float, cmap='gray')
plt.show()
img_arr_float = img_arr_float.reshape(1, 28, 28)
img_tensor = torch.from_numpy(img_arr_float)
img_tensor = img_tensor.unsqueeze(0).cpu().float()
Exploring new model
I'll start by improving the model by adding 2 things:
- Much more output channels in CNN, from 2 to 32 in the first layer and 2 to 64 in the second layer.
- An extra fully-connected layer on the end.
Let's make sure the outputs are as we expect:
conv1 = nn.Conv2d(in_channels=1, out_channels=32, kernel_size=3, padding=1)
conv2 = nn.Conv2d(in_channels=32, out_channels=64, kernel_size=3, padding=1)
output = conv1(Variable(img_tensor))
output.shape
torch.Size([1, 32, 28, 28])
Note how I've added 1 pixel of padding on both sides of the output.
This means that the conv will return the same size image output, which is slightly easier to deal with. We then know that the Maxpool will return the size by half:
output = F.max_pool2d(output, 2)
output.shape
torch.Size([1, 32, 14, 14])
We are also adding Dropout. Let's explore what Dropout with 100% probability will look like.
First, let's see the output image:
output[0]
Variable containing:
(0 ,.,.) =
2.1169e-01 2.1169e-01 2.1169e-01 ... 2.1169e-01 2.1169e-01 2.1169e-01
2.1169e-01 2.1169e-01 2.1169e-01 ... 2.1169e-01 2.1169e-01 2.1169e-01
2.1169e-01 2.1169e-01 2.1169e-01 ... 2.1169e-01 2.1169e-01 2.1169e-01
... ⋱ ...
2.1169e-01 2.1409e-01 2.3400e-01 ... 4.1716e-01 2.1169e-01 2.1169e-01
2.1169e-01 2.1169e-01 2.3423e-01 ... 2.1843e-01 2.1169e-01 2.1169e-01
2.1169e-01 2.1169e-01 2.1169e-01 ... 2.1169e-01 2.1169e-01 2.1169e-01
(1 ,.,.) =
9.6191e-02 9.6191e-02 9.6191e-02 ... 9.6191e-02 9.6191e-02 9.6191e-02
9.6191e-02 9.6191e-02 9.6191e-02 ... 9.6191e-02 9.6191e-02 9.6191e-02
9.6191e-02 9.6191e-02 9.6191e-02 ... 9.6191e-02 9.6191e-02 9.6191e-02
... ⋱ ...
9.6191e-02 9.6191e-02 1.0339e-02 ... 1.2410e-01 9.6191e-02 9.6191e-02
9.6191e-02 9.6191e-02 9.6191e-02 ... 9.8780e-02 9.6191e-02 9.6191e-02
9.6191e-02 9.6191e-02 9.6191e-02 ... 9.6191e-02 9.6191e-02 9.6191e-02
(2 ,.,.) =
-2.1987e-01 -2.1987e-01 -2.1987e-01 ... -2.1987e-01 -2.1987e-01 -2.1987e-01
-2.1987e-01 -2.1987e-01 -2.1987e-01 ... -2.1987e-01 -2.1987e-01 -2.1987e-01
-2.1987e-01 -2.1987e-01 -2.1987e-01 ... -2.1987e-01 -2.1987e-01 -2.1987e-01
... ⋱ ...
-2.1987e-01 -2.1047e-01 1.9754e-01 ... -2.1987e-01 -2.1987e-01 -2.1987e-01
-2.1987e-01 -2.1987e-01 -1.3157e-01 ... -2.1987e-01 -2.1987e-01 -2.1987e-01
-2.1987e-01 -2.1987e-01 -2.1987e-01 ... -2.1987e-01 -2.1987e-01 -2.1987e-01
...
(29,.,.) =
2.0226e-01 2.0226e-01 2.0226e-01 ... 2.0226e-01 2.0226e-01 2.0226e-01
2.0226e-01 2.0226e-01 2.0226e-01 ... 2.0226e-01 2.0226e-01 2.0226e-01
2.0226e-01 2.0226e-01 2.0226e-01 ... 2.0226e-01 2.0226e-01 2.0226e-01
... ⋱ ...
2.0226e-01 2.1132e-01 4.1036e-01 ... 2.8769e-01 2.0226e-01 2.0226e-01
2.0226e-01 2.0226e-01 2.8742e-01 ... 2.0525e-01 2.0226e-01 2.0226e-01
2.0226e-01 2.0226e-01 2.0226e-01 ... 2.0226e-01 2.0226e-01 2.0226e-01
(30,.,.) =
1.3100e-01 1.3100e-01 1.3100e-01 ... 1.3100e-01 1.3100e-01 1.3100e-01
1.3100e-01 1.3100e-01 1.3100e-01 ... 1.3100e-01 1.3100e-01 1.3100e-01
1.3100e-01 1.3100e-01 1.3100e-01 ... 1.3100e-01 1.3100e-01 1.3100e-01
... ⋱ ...
1.3100e-01 1.3302e-01 1.4972e-01 ... 4.1116e-01 1.3100e-01 1.3100e-01
1.3100e-01 1.3100e-01 1.4992e-01 ... 1.3935e-01 1.3100e-01 1.3100e-01
1.3100e-01 1.3100e-01 1.3100e-01 ... 1.3100e-01 1.3100e-01 1.3100e-01
(31,.,.) =
-5.5982e-02 -5.5982e-02 -5.5982e-02 ... -5.5982e-02 -5.5982e-02 -5.5982e-02
-5.5982e-02 -5.5982e-02 -5.5982e-02 ... -5.5982e-02 -5.5982e-02 -5.5982e-02
-5.5982e-02 -5.5982e-02 -5.5982e-02 ... -5.5982e-02 -5.5982e-02 -5.5982e-02
... ⋱ ...
-5.5982e-02 -4.8172e-02 4.6473e-02 ... 7.0074e-02 -5.5982e-02 -5.5982e-02
-5.5982e-02 -5.5982e-02 1.7430e-02 ... -4.4968e-02 -5.5982e-02 -5.5982e-02
-5.5982e-02 -5.5982e-02 -5.5982e-02 ... -5.5982e-02 -5.5982e-02 -5.5982e-02
[torch.FloatTensor of size 32x14x14]
Let's now create Dropout with 100% prob:
dropout = nn.Dropout(p=1)
dropout_output = dropout(output)
dropout_output[0]
Variable containing:
(0 ,.,.) =
0 0 0 ... 0 0 0
0 0 0 ... 0 0 0
0 0 0 ... 0 0 0
... ⋱ ...
0 0 0 ... 0 0 0
0 0 0 ... 0 0 0
0 0 0 ... 0 0 0
(1 ,.,.) =
0 0 0 ... 0 0 0
0 0 0 ... 0 0 0
0 0 0 ... 0 0 0
... ⋱ ...
0 0 0 ... 0 0 0
0 0 0 ... 0 0 0
0 0 0 ... 0 0 0
(2 ,.,.) =
-0 -0 -0 ... -0 -0 -0
-0 -0 -0 ... -0 -0 -0
-0 -0 -0 ... -0 -0 -0
... ⋱ ...
-0 -0 0 ... -0 -0 -0
-0 -0 -0 ... -0 -0 -0
-0 -0 -0 ... -0 -0 -0
...
(29,.,.) =
0 0 0 ... 0 0 0
0 0 0 ... 0 0 0
0 0 0 ... 0 0 0
... ⋱ ...
0 0 0 ... 0 0 0
0 0 0 ... 0 0 0
0 0 0 ... 0 0 0
(30,.,.) =
0 0 0 ... 0 0 0
0 0 0 ... 0 0 0
0 0 0 ... 0 0 0
... ⋱ ...
0 0 0 ... 0 0 0
0 0 0 ... 0 0 0
0 0 0 ... 0 0 0
(31,.,.) =
-0 -0 -0 ... -0 -0 -0
-0 -0 -0 ... -0 -0 -0
-0 -0 -0 ... -0 -0 -0
... ⋱ ...
-0 -0 0 ... 0 -0 -0
-0 -0 0 ... -0 -0 -0
-0 -0 -0 ... -0 -0 -0
[torch.FloatTensor of size 32x14x14]
Notice how all the activations are 0? What about 0.5?
dropout = nn.Dropout(p=0.5)
dropout_output = dropout(output)
dropout_output[0]
Variable containing:
(0 ,.,.) =
0.4234 0.4234 0.4234 ... 0.4234 0.0000 0.0000
0.4234 0.4234 0.4234 ... 0.0000 0.4234 0.0000
0.0000 0.0000 0.0000 ... 0.0000 0.4234 0.4234
... ⋱ ...
0.4234 0.0000 0.0000 ... 0.8343 0.0000 0.4234
0.4234 0.4234 0.0000 ... 0.4369 0.4234 0.4234
0.0000 0.0000 0.0000 ... 0.0000 0.0000 0.4234
(1 ,.,.) =
0.0000 0.1924 0.0000 ... 0.0000 0.1924 0.1924
0.1924 0.0000 0.1924 ... 0.0000 0.1924 0.1924
0.1924 0.1924 0.0000 ... 0.1924 0.0000 0.0000
... ⋱ ...
0.1924 0.0000 0.0207 ... 0.0000 0.1924 0.0000
0.1924 0.1924 0.0000 ... 0.1976 0.0000 0.1924
0.1924 0.0000 0.0000 ... 0.0000 0.0000 0.0000
(2 ,.,.) =
-0.0000 -0.0000 -0.4397 ... -0.4397 -0.0000 -0.0000
-0.4397 -0.0000 -0.0000 ... -0.0000 -0.0000 -0.4397
-0.4397 -0.4397 -0.4397 ... -0.0000 -0.0000 -0.4397
... ⋱ ...
-0.4397 -0.0000 0.3951 ... -0.4397 -0.0000 -0.4397
-0.0000 -0.0000 -0.2631 ... -0.4397 -0.4397 -0.4397
-0.0000 -0.0000 -0.0000 ... -0.0000 -0.4397 -0.4397
...
(29,.,.) =
0.0000 0.4045 0.4045 ... 0.4045 0.0000 0.0000
0.0000 0.4045 0.4045 ... 0.0000 0.4045 0.0000
0.0000 0.4045 0.4045 ... 0.0000 0.4045 0.4045
... ⋱ ...
0.4045 0.0000 0.8207 ... 0.5754 0.0000 0.4045
0.0000 0.4045 0.0000 ... 0.0000 0.4045 0.0000
0.0000 0.4045 0.4045 ... 0.0000 0.4045 0.0000
(30,.,.) =
0.0000 0.0000 0.0000 ... 0.2620 0.2620 0.0000
0.0000 0.0000 0.2620 ... 0.2620 0.0000 0.0000
0.0000 0.0000 0.2620 ... 0.2620 0.2620 0.2620
... ⋱ ...
0.0000 0.2660 0.0000 ... 0.0000 0.0000 0.0000
0.0000 0.2620 0.0000 ... 0.0000 0.0000 0.0000
0.2620 0.0000 0.2620 ... 0.0000 0.2620 0.2620
(31,.,.) =
-0.1120 -0.0000 -0.1120 ... -0.1120 -0.0000 -0.0000
-0.1120 -0.0000 -0.1120 ... -0.0000 -0.0000 -0.1120
-0.1120 -0.0000 -0.1120 ... -0.1120 -0.1120 -0.0000
... ⋱ ...
-0.1120 -0.0000 0.0000 ... 0.1401 -0.0000 -0.1120
-0.1120 -0.0000 0.0000 ... -0.0899 -0.1120 -0.0000
-0.0000 -0.0000 -0.0000 ... -0.0000 -0.0000 -0.0000
[torch.FloatTensor of size 32x14x14]
Now, 50% of the activations are dropped out.
class SimpleCNN(nn.Module):
def __init__(self, in_channels=1, p=0.5):
super(SimpleCNN, self).__init__()
self.conv1 = nn.Conv2d(
in_channels=in_channels, out_channels=32, kernel_size=3, padding=1)
self.conv2 = nn.Conv2d(
in_channels=32, out_channels=64, kernel_size=3, padding=1)
self.conv2_dropout = nn.Dropout(p=p)
self.fc1 = nn.Linear(in_features=64 * 7 * 7, out_features=1024)
self.fc1_dropout = nn.Dropout(p=p)
self.fc2 = nn.Linear(in_features=1024, out_features=10)
def forward(self, img_batch):
conv1_output = self.conv1(img_batch)
conv1_relu_output = F.relu(conv1_output)
maxpool1_output = F.max_pool2d(conv1_relu_output, kernel_size=2)
conv2_output = self.conv2(maxpool1_output)
relu2_output = F.relu(conv2_output)
maxpool2_output = F.max_pool2d(relu2_output, kernel_size=2)
dropout2_output = self.conv2_dropout(maxpool2_output)
flattened_output = dropout2_output.view(img_batch.size(0), -1)
fc1_output = self.fc1(flattened_output)
fc1_relu_output = F.relu(fc1_output)
fc1_dropout_output = self.fc1_dropout(fc1_relu_output)
fc2_output = self.fc2(fc1_dropout_output)
return F.log_softmax(fc2_output, dim=1)
Train with Fast.ai ConvLearner (no dropout)
I can then create a ImageClassifierData object as usual, use the from_model_data constructor to create a ConvLearner from my custom model.
train_df = pd.read_csv(PATH/'train_prepared.csv')
val_idx = get_cv_idxs(len(train_df))
Let's start with a CNN that drops activations with a probability of 0% (ie no Dropout).
cnn = SimpleCNN(in_channels=3, p=0.)
cnn
SimpleCNN( (conv1): Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (conv2): Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (conv2_dropout): Dropout(p=0.0) (fc1): Linear(in_features=3136, out_features=1024, bias=True) (fc1_dropout): Dropout(p=0.0) (fc2): Linear(in_features=1024, out_features=10, bias=True) )
data = ImageClassifierData.from_csv(
PATH, 'train', PATH/'train_prepared.csv', tfms=tfms_from_model(cnn, 28), val_idxs=val_idx, suffix='.jpg')
conv_learner = ConvLearner.from_model_data(cnn, data)
conv_learner.lr_find()
HBox(children=(IntProgress(value=0, description='Epoch', max=1), HTML(value='')))
71%|███████ | 374/525 [00:29<00:12, 12.53it/s, loss=1.13]
conv_learner.sched.plot()
conv_learner.fit(0.05, 10)
HBox(children=(IntProgress(value=0, description='Epoch', max=10), HTML(value='')))
epoch trn_loss val_loss accuracy
0 0.089397 0.112462 0.963333
1 0.065815 0.067821 0.980595
2 0.041379 0.061611 0.981071
3 0.036552 0.074447 0.978214
4 0.036247 0.060566 0.985952
5 0.020039 0.048966 0.987976
6 0.034077 0.095807 0.977381
7 0.020217 0.057499 0.986071
8 0.021807 0.075542 0.985595
9 0.047973 0.133513 0.976548
[array([0.13351]), 0.976547619047619]
epoch_num = list(range(conv_learner.sched.epoch))
plt.plot(epoch_num, conv_learner.sched.val_losses, label='Validation loss')
plt.plot(
epoch_num,
[conv_learner.sched.losses[i-1] for i in conv_learner.sched.epochs],
label='Training loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.legend(loc='upper right')
plt.show()
Train with Dropout
Let's add a bit of dropout. Set it initially to 0.4 and see if we can train a bit longer before we start to overfit.
cnn = SimpleCNN(in_channels=3, p=0.5)
cnn
SimpleCNN( (conv1): Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (conv2): Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (conv2_dropout): Dropout(p=0.5) (fc1): Linear(in_features=3136, out_features=1024, bias=True) (fc1_dropout): Dropout(p=0.5) (fc2): Linear(in_features=1024, out_features=10, bias=True) )
conv_learner = ConvLearner.from_model_data(cnn, data)
conv_learner.lr_find()
HBox(children=(IntProgress(value=0, description='Epoch', max=1), HTML(value='')))
epoch trn_loss val_loss accuracy
0 2.480965 3.082173 0.111548
conv_learner.sched.plot()
conv_learner.fit(0.01, 10)
HBox(children=(IntProgress(value=0, description='Epoch', max=10), HTML(value='')))
epoch trn_loss val_loss accuracy
0 0.135178 0.067422 0.98
1 0.097621 0.054712 0.982976
2 0.080287 0.048799 0.984048
3 0.069733 0.040563 0.987381
4 0.05925 0.032146 0.989524
5 0.050756 0.034987 0.98869
6 0.048073 0.029558 0.99131
7 0.03602 0.028385 0.990833
8 0.036472 0.026458 0.991548
9 0.037791 0.027421 0.99119
[array([0.02742]), 0.9911904761904762]
epoch_num = list(range(conv_learner.sched.epoch))
plt.plot(epoch_num, conv_learner.sched.val_losses, label='Validation loss')
plt.plot(
epoch_num,
[conv_learner.sched.losses[i-1] for i in conv_learner.sched.epochs],
label='Training loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.legend(loc='upper right')
plt.show()
Evalute on the test set
test_df = pd.read_csv(PATH/'test.csv')
img_1 = test_df.loc[6, [c for c in df.columns if c.startswith('pixel')]]
img_arr = np.array(img_1)
img_arr = img_arr.reshape((28, 28))
img_float = np.array(img_arr) * (1/255)
img_float = np.stack((img_float,) * 3, axis=-1)
img_float = img_float.transpose((2, 0, 1))
img_tensor = torch.from_numpy(img_float)
img_tensor = img_tensor.unsqueeze(0).cpu().float()
preds = torch.exp(conv_learner.model(Variable(img_tensor)))
plt.imshow(img_arr, cmap='gray')
<matplotlib.image.AxesImage at 0x1c32570e48>
preds
Variable containing: 0.9273 0.0032 0.0162 0.0028 0.0042 0.0016 0.0131 0.0046 0.0128 0.0142 [torch.FloatTensor of size 1x10]
np.argmax(torch.exp(preds.data).numpy())
0