回顾

简单来说,就是设置一个的丢弃概率(这是一个超参数),当数据在层与层之间传递时,神经元上的数据有概率被我们丢弃不适用,即对应的权重参数为 0,这样就可以使我们的模型变得稀疏,防止其过度依赖某些神经元以起到正则化的作用。同时,通过拉伸操作,保证输入数据的期望值不变。
丢弃法只在训练模型时使用。

从零开始实现

python 断言

参考Python3 assert

  1. assert expression
  2. # 等价于
  3. if not expression:
  4. raise AssertionError
  5. assert expression [, arguments]
  6. # 等价于
  7. if not expression:
  8. raise AssertionError(arguments)

定义Drop-out函数

我们定义的dropout函数读入张量以及丢弃率两个参数。keep_prob实际上就是1减去drop_prob值。assert断言用于确保传入的drop_prob值大于0小于1。如何来实现drop-out呢?其实很简单,先初始化一个与x相同形状的张量,值在0,1区间均匀分布。检查矩阵中初始化的每个元素,若值小于keep_prob,则令其为1,否则为0。将该张量与原始的x相乘,再除以keep_prob值后(为了保持期望不变)

  1. import torch
  2. from torch import nn
  3. import d2lzh
  4. def dropout(x, drop_prob):
  5. x = x.float()
  6. assert 0 <= drop_prob <= 1
  7. keep_prob = 1 - drop_prob
  8. if keep_prob == 0:
  9. return torch.zeros_like(x)
  10. mask = (torch.rand(x.shape) < keep_prob).float()
  11. return mask * x / keep_prob
  12. x = torch.arange(16).view(2, 8)
  13. print(dropout(x, 0))
  14. print(dropout(x, 0.5))
  15. print(dropout(x, 1.0))
  16. 结果:
  17. tensor([[ 0., 1., 2., 3., 4., 5., 6., 7.],
  18. [ 8., 9., 10., 11., 12., 13., 14., 15.]])
  19. tensor([[ 0., 0., 4., 6., 8., 10., 12., 0.],
  20. [16., 0., 0., 22., 0., 0., 28., 0.]])
  21. tensor([[0., 0., 0., 0., 0., 0., 0., 0.],
  22. [0., 0., 0., 0., 0., 0., 0., 0.]])

定义模型参数

  1. num_inputs, num_outputs, num_hiddens1, num_hiddens2 = 784, 10, 256, 256
  2. W1 = torch.normal(mean=0, std=0.01, size=[num_inputs, num_hiddens1], dtype=torch.float, requires_grad=True)
  3. b1 = torch.zeros(num_hiddens1, dtype=torch.float, requires_grad=True)
  4. W2 = torch.normal(mean=0, std=0.01, size=[num_hiddens1, num_hiddens2], dtype=torch.float, requires_grad=True)
  5. b2 = torch.zeros(num_hiddens2, dtype=torch.float, requires_grad=True)
  6. W3 = torch.normal(mean=0, std=0.01, size=[num_hiddens2, num_outputs], dtype=torch.float, requires_grad=True)
  7. b3 = torch.zeros(num_outputs, dtype=torch.float, requires_grad=True)
  8. params = [W1, b1, W2, b2, W3, b3]

定义模型

下面定义的模型将全连接层和激活函数ReLU串起来,并对每个激活函数的输出使用丢弃法。我们可以分别设置各个层的丢弃概率。通常的建议是把靠近输入层的丢弃概率设得小一点。在这个实验中,我们把第一个隐藏层的丢弃概率设为0.2,把第二个隐藏层的丢弃概率设为0.5。我们可以通过参数is_training来判断运行模式为训练还是测试,并只需在训练模式下使用丢弃法。

我们在对模型评估的时候不应该进行丢弃,所以我们修改一下d2lzh_pytorch中的evaluate_accuracy函数:

  1. def net(x, is_training=True):
  2. x = x.view(-1, num_inputs)
  3. h1 = (torch.matmul(x, W1) + b1).relu()
  4. if is_training: # 只在训练模型时使用丢弃法
  5. h1 = dropout(h1, drop_prob1) # 添加丢弃层
  6. h2 = (torch.matmul(h1, W2) + b2).relu() # 添加丢弃层
  7. if is_training:
  8. h2 = dropout(h2, drop_prob2)
  9. return torch.matmul(h2, W3) + b3
  10. def evaluate_accuracy(data_iter, net):
  11. acc_sum, n = 0.0, 0
  12. for X, y in data_iter:
  13. if isinstance(net, torch.nn.Module):
  14. net.eval() # 评估模式, 这会关闭dropout
  15. acc_sum += (net(X).argmax(dim=1) == y).float().sum().item()
  16. net.train() # 改回训练模式
  17. else: # 自定义的模型
  18. if 'is_training' in net.__code__.co_varnames: # 如果有is_training这个参数
  19. # 将is_training设置成False
  20. acc_sum += (net(X, is_training=False).argmax(dim=1) == y).float().sum().item()
  21. else:
  22. acc_sum += (net(X).argmax(dim=1) == y).float().sum().item()
  23. n += y.shape[0]
  24. return acc_sum / n
  25. if __name__ == "__main__":
  26. num_epochs, lr, batch_size = 5, 100.0, 256
  27. loss = torch.nn.CrossEntropyLoss()
  28. train_iter, test_iter = d2lzh.load_data_fashion_mnist(batch_size)
  29. d2lzh.train_ch3(net, train_iter, test_iter, loss, num_epochs, batch_size, params, lr)
  30. 结果:
  31. epoch 1, loss 0.0045, train acc 0.550, test acc 0.697
  32. epoch 2, loss 0.0023, train acc 0.785, test acc 0.725
  33. epoch 3, loss 0.0019, train acc 0.822, test acc 0.802
  34. epoch 4, loss 0.0017, train acc 0.840, test acc 0.824
  35. epoch 5, loss 0.0016, train acc 0.847, test acc 0.843

简洁实现

在PyTorch中,我们只需要在全连接层后添加Dropout层并指定丢弃概率。在训练模型时,Dropout层将以指定的丢弃概率随机丢弃上一层的输出元素;在测试模型时(即model.eval()后),Dropout层并不发挥作用。

完整代码如下:

  1. import torch
  2. from torch import nn
  3. import d2lzh
  4. num_inputs, num_outputs, num_hiddens1, num_hiddens2 = 784, 10, 256, 256
  5. drop_prob1, drop_prob2 = 0.2, 0.5
  6. num_epochs, lr, batch_size = 5, 100.0, 256
  7. train_iter, test_iter = d2lzh.load_data_fashion_mnist(batch_size)
  8. net = nn.Sequential(
  9. d2lzh.FlattenLayer(),
  10. nn.Linear(num_inputs, num_hiddens1),
  11. nn.ReLU(),
  12. nn.Dropout(drop_prob1),
  13. nn.Linear(num_hiddens1, num_hiddens2),
  14. nn.ReLU(),
  15. nn.Dropout(drop_prob2),
  16. nn.Linear(num_hiddens2, 10)
  17. )
  18. for param in net.parameters():
  19. nn.init.normal_(param, mean=0, std=0.01)
  20. loss = nn.CrossEntropyLoss()
  21. optimizer = torch.optim.SGD(net.parameters(), lr=0.5)
  22. d2lzh.train_ch3(net, train_iter, test_iter, loss, num_epochs, batch_size, None, None, optimizer)