丢弃法
深度学习模型常常使用丢弃法来应对过拟合问题。
丢弃法有一些变体,本节中提到的丢弃法特指倒置丢弃法。
方法
设丢弃概率为p,那么有p的概率hi会被清零,有1-p的概率hi会除以1-p做拉伸。
丢弃概率是丢弃法的超参数。具体来说,设随机变量εi为0和1的概率分别为p和1-p。计算新的隐藏单元hi’
hi’ = εi*hi/1-p
由于εi的期望为1-p,所以
hi’ 的期望仍是hi
所以丢弃法不会改变输入的期望值。
由于在训练中隐藏层神经元的丢弃是随机的,所以每一个神经元都有可能被清零,输出层的计算无法过度依赖其中的任意一个,从而在训练模型时起到正则化的作用,可以用来应对过拟合。
在测试模型时,为了拿到更加确定性的结果,一般不使用丢弃法。
从零开始实现
使用dropout函数将以drop_prob的概率丢弃x中的元素。
%matplotlib inline
import torch
import torch.nn as nn
import numpy as np
sys.path.append("...")
import d2lzh_pytorch as d2l
def dropout(X, drop_prob):
X = X.float()
assert 0 <= drop_prob <= 1
keep_prob = 1-drop_prob
if keep_prob == 0:
#相当于丢弃所有隐藏单元
return torch.zeros_like(X):
mask = (torch.rand(X.shape) < keep_prob).float()
#均匀分布,每个样本特征保留的概率都是keep_prob
return mask * X/keep_prob
测试dropout函数
X = torch.arange(16).view(2,8)
dropout(X, 0.5)
输出
tensor([[ 0., 0., 0., 6., 8., 0., 0., 14.],
[16., 18., 20., 0., 0., 26., 0., 30.]])
定义模型函数
定义一个包含两个隐藏层的多层感知机,其中两个隐藏层的输出个数都是256
num_inputs, num_outpus, num_hiddens1, num_hiddens2 = 784,10,256,256
W1 =torch.tensor(np.random.normal(0, 0.01, size= (num_inputs, num_hiddens1), dtype = torch.float, requires_grad = True)
b1 =torch.zeros(num_hiddens1, requires_grad = True)
W2 =torch.tensor(np.random.normal(0, 0.01, size= (num_hiddens1, num_hiddens2), dtype = torch.float, requires_grad = True)
b2 =torch.zeros(num_hiddens2, requires_grad = True)
W3 =torch.tensor(np.random.normal(0, 0.01, size= (num_hiddens2, num_outputs), dtype = torch.float, requires_grad = True)
b3 =torch.zeros(num_outputs, requires_grad = True)
params = [W1, b1, W2, b2, W3, b3]
定义模型
将全连接层与激活函数ReLU串起来,并对每个激活函数的输出使用丢弃法。
可以分别设置每个层的丢弃概率,通常的建议是吧靠近输入层的丢弃概率设的小一点。
第一个隐藏层的丢弃概率设为0.2, 第二个隐藏层的丢弃概率设为0.5.
可以通过参数is_training来判断运行模式为训练还是测试,并只需在训练模式下使用丢弃法。
drop_prob1, drop_prob2 = 0.2, 0.5
def net(X, is_training = True):
X = X.view(-1, num_inputs)
H1 = (torch.matmul(X, W1) + b1).relu()
if is_trainingL
#只在训练模型使用丢弃法
H1 = dropout(H1, drop_prob1)
H2 = (torch.matmul(H1, W2) + b2).relu()
if is_training:
H2 = dropout(H2, drop_prob2)
return torch.matmul(H2, W2) + b3
在对模型评估的时候不应该使用丢弃法,所以修改一下evaluate-accuracy函数
def evaluate_accuracy(data_iter, net):
acc_sum, n = 0.0, 0
for X,y in data_iter:
if isinstance(net, torch.nn.Module):
#判断模型是否是自定义的,net是否是nn.Module的类型
net.eval()
#评估模式 这会关闭dropout
acc_sum += (net(X).argmax(dim = 1) == y).float().sum().item()
net.train()
#将net改回训练模式
else:
if('is_training' in net.__code__.co_varnames):
#如果有is_training这个参数
acc_sum += (net(X, is_training = False).argmax(dim =1) == y).float().sum().item()
#将其改为False
else:
acc_sum += (net(X).argmax(dim = 1) == y).float().sum().item()
n += y.shape[0]
return acc_sum/n
训练模型
num_epochs, lr, batch_size = 5, 100.0, 256
loss = torch.nn.CrossEntropyLoss()
train_iter, teat_iter = d2l.load_data_fashion_mnist(batch_size)
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, batch_size, parmas, lr)
这里的lr很大,因为optimizer使用的是自定义的SGD,迭代时除以了batch_size
简洁实现
只需要在全连接层后添加Dropout层并指定丢弃概率,在训练模型时,Dropout以指定的丢弃概率随机丢弃上一层的输出元素,在测试模型时(model.eval()), Dropout层并不发挥作用
net = nn.Sequential(
d2l.FlattenLayer(),
nn.Linear(num_inputs, num_hiddens1),
nn.ReLU(),
nn.Dropout(drop_prob1),
nn.Linear(num_hiddens1, num_hiddens2),
nn.ReLU(),
nn.Dropout(drop_prob2),
nn.Linear(num_hiddens2, 10)
)
for params in net.parameters():
nn.init.normal_(param, mean = 0, std = 0.01)
训练并测试模型
optimizer = torch.optim.SGD(net.parameters, lr= 0.5)
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, batch_size, None, None, optimizer)