动机

一个好的模型需要对输入数据的扰动鲁棒

使用有噪音的数据相等价于 Tikhonov 正则

Dropout:在层之间加入噪音

无偏差的加入噪音

x\mathbf{x} 加入噪音得到 x\mathbf{x}^{\prime},希望 E[x]=x\mathbf{E}\left[\mathbf{x}^{\prime}\right]=\mathbf{x}

Dropout 对每个元素进行如下扰动:

xi={0 with probablity pxi1p otherise x_{i}^{\prime}= \begin{cases}0 & \text { with probablity } p \\ \frac{x_{i}}{1-p} & \text { otherise }\end{cases}

E[xi]=0p+xi1p(1p)=xi\mathbf{E}\left[\mathbf{x}_{i}^{\prime}\right]=0\cdot p+\frac{x_{i}}{1-p}\cdot(1-p)=\mathbf{x}_{i}

实践中丢弃率可以尝试 0.5,0.1,0.90.5, 0.1, 0.9 这几个选项

使用 Dropout

通常将 Dropout 作用在隐藏全连接层的输出上

h=σ(W1x+b1)h=dropout(h)o=W2h+b2y=softmax(o)\begin{aligned}\mathbf{h} &=\sigma\left(\mathbf{W}_{1} \mathbf{x}+\mathbf{b}_{1}\right) \\\mathbf{h}^{\prime} &=\operatorname{dropout}(\mathbf{h}) \\\mathbf{o} &=\mathbf{W}_{2} \mathbf{h}^{\prime}+\mathbf{b}_{2} \\\mathbf{y} &=\operatorname{softmax}(\mathbf{o})\end{aligned}

Untitled

推理中的 Dropout

正则项只在训练中使用:他们影响模型参数的更新

在推理过程中,Dropout 直接返回输入

h=dropout(h)\mathbf{h}=\operatorname{dropout}(\mathbf{h})

这样也能保证确定性的输出

总结

  • Dropout 将一些输出项随机置 0 来控制模型复杂度
  • 常作用在多层感知机的隐藏层输出上
  • 丢弃概率是控制模型复杂度的超参数

代码实现

实现 dropout_layer 函数

该函数以dropout的概率丢弃张量输入X中的元素

1
2
3
4
5
6
7
8
9
10
11
12
import torch
from torch import nn
from d2l import torch as d2l

def dropout_layer(X, dropout):
assert 0 <= dropout <= 1
if dropout == 1:
return torch.zeros_like(X)
if dropout == 0:
return X
mask = (torch.randn(X.shape) > dropout).float()
return mask * X / (1.0 - dropout)

测试dropout_layer函数

1
2
3
4
5
X = torch.arange(16, dtype=torch.float32).reshape((2, 8))
print(X)
print(dropout_layer(X, 0.))
print(dropout_layer(X, 0.5))
print(dropout_layer(X, 1.))
1
2
3
4
5
6
7
8
tensor([[ 0.,  1.,  2.,  3.,  4.,  5.,  6.,  7.],
[ 8., 9., 10., 11., 12., 13., 14., 15.]])
tensor([[ 0., 1., 2., 3., 4., 5., 6., 7.],
[ 8., 9., 10., 11., 12., 13., 14., 15.]])
tensor([[ 0., 2., 4., 0., 0., 10., 0., 0.],
[16., 18., 20., 0., 24., 0., 0., 30.]])
tensor([[0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0.]])

定义多层感知机

定义具有两个隐藏层的多层感知机,每个隐藏层包含 256 个单元

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
num_inputs, num_outputs, num_hiddens1, num_hiddens2 = 784, 10, 256, 256

dropout1, dropout2 = 0.2, 0.5

class Net(nn.Module):
def __init__(self, num_inputs, num_outputs, num_hiddens1, num_hiddens2,
is_training=True):
super(Net, self).__init__()
self.num_inputs = num_inputs
self.training = is_training
self.lin1 = nn.Linear(num_inputs, num_hiddens1)
self.lin2 = nn.Linear(num_hiddens1, num_hiddens2)
self.lin3 = nn.Linear(num_hiddens2, num_outputs)
self.relu = nn.ReLU()

def forward(self, X):
H1 = self.relu(self.lin1(X.reshape((-1, self.num_inputs))))
if self.training == True:
H1 = dropout_layer(H1, dropout1)
H2 = self.relu(self.lin2(H1))
if self.training == True:
H2 = dropout_layer(H2, dropout2)
out = self.lin3(H2)
return out

net = Net(num_inputs, num_outputs, num_hiddens1, num_hiddens2)

训练和测试

1
2
3
4
5
num_epochs, lr, batch_size = 10, 0.5, 256
loss = nn.CrossEntropyLoss()
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
trainer = torch.optim.SGD(net.parameters(), lr=lr)
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)

Untitled

简洁实现

1
2
3
4
5
6
7
8
9
net = nn.Sequential(nn.Flatten(), nn.Linear(784, 256), nn.ReLU(),
nn.Dropout(dropout1), nn.Linear(256, 256), nn.ReLU(),
nn.Dropout(dropout2), nn.Linear(256, 10))

def init_weights(m):
if type(m) == nn.Linear:
nn.init.normal_(m.weight, std=0.01)

net.apply(init_weights);

对模型进行训练和测试

1
2
trainer = torch.optim.SGD(net.parameters(), lr=lr)
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)

Untitled