动手学深度学习(十五)——Dropout

文章目录

    • 一、从其他方向审视过拟合
    • 二、使用Dropout的动机及其定义
    • 三、从零开始实现dropout
    • 四、使用pytorch简洁实现
    • 五、总结

说明:此博客是笔者根据李沐沐神2021年动手学深度学习进行的笔记整理,其中也包含了个人的一些学习理解和代码。如有错误请指正!

一、从其他方向审视过拟合

  1. 当面对更多的特征数量有限时,线性模型往往不能考虑特征之间的交互作用,模型会更容易发生过拟合。(也就是模型学习到了量数据的部分特征,而可能忽略了其中更为普遍的规律)

  2. 在传统的说法中,范化性和灵活性之间的这种基本权衡可以描述为“偏差-方差权衡”(bias-variance trade off),简单的线性模型有很大的偏差:其仅仅能拟合一小类简单的函数,但是这些模型的方差很低(也就是说对于这些模型而言,给定不同的随机数据,其结果类似,精确度基本不受数据的影响),对于这些简单的函数过拟合的风险较小。

  3. 对于深度神经网络,偏差-方差谱位于另一端。神经网络其不局限于单独查看某一个特征,而是更广泛地学习了各种不同特征之间的交互关系。所以即使我们样本数量远大于特征数量,深度神经网络也可能出现过拟合(简单想象多个特征的组合排列就可以想到这一点了,样本数量增长远不及这些特征排列组合的关系增长多,加入一个新的特征其交互关系可能是爆炸增长)

  4. 深度神经网络有令人费解的范化性质,但是这些性质的数学基本原理仍然未知。


二、使用Dropout的动机及其定义

一个好的模型需要对输入数据的扰动鲁棒,参数的范数代表了一种有用的简单性度量,简单性的另一个又用角度是平滑性,即函数不应该对输入的微小变化敏感

  • 1995年克里斯托弗.毕晓谱证明了具有输入噪音的训练等价于Tikhonov正则

在毕晓普的工作中,他将高斯噪声添加到线性模型的输入中。在每次训练迭代中,他将从均值为零的分布 ϵ ∼ N ( 0 , σ 2 ) \epsilon \sim \mathcal{N}(0,\sigma^2) ϵN(0,σ2)采样噪声添加到输入 x \mathbf{x} x,从而产生扰动点 x ′ = x + ϵ \mathbf{x}' = \mathbf{x} + \epsilon x=x+ϵ。预期是 E [ x ′ ] = x E[\mathbf{x}'] = \mathbf{x} E[x]=x

  • 2014年斯里瓦斯塔瓦等人将上述方法应用于网络的内部层提出一个新的想法“丢弃法”:在层之间加入噪音。
  • 那么我们希望加入噪音之后得到的新的输入:

E [ x ′ ] = x E[\mathbf{x}'] = \mathbf{x} E[x]=x

  • 在标准dropout正则化中,通过按保留(未丢弃)的节点的分数进行归一化来消除每一层的偏差。换言之,每个中间激活值 h h h丢弃概率 p p p由随机变量 h ′ h' h替换,如下所示:

h ′ = { 0  概率为  p h 1 − p  其他情况 \begin{aligned} h' = \begin{cases} 0 & \text{ 概率为 } p \\ \frac{h}{1-p} & \text{ 其他情况} \end{cases} \end{aligned} h={01ph 概率为 p 其他情况

请注意:

E [ h ′ ] = p ∗ 0 + ( 1 − p ) ∗ h 1 − p = h E[\mathbf{h}'] = p*0 +(1-p)*\frac{h}{1-p} = h E[h]=p0+(1p)1ph=h

根据设计,期望值保持不变,即 E [ h ′ ] = h E[h'] = h E[h]=h


动手学深度学习(十五)——Dropout_第1张图片

注意:正则项(Dropout)只在训练过程中使用,因为其会影响模型参数的更新
所以在推理过程中,丢弃法直接返回输入

h = d r o p o u t ( h ) h = dropout(h) h=dropout(h)

  • 测试应该保证输出的确定性

三、从零开始实现dropout

要实现单层的dropout函数,我们必须从伯努利(二元)随机变量中提取与我们的层的维度一样多的样本,其中随机变量以概率 1 − p 1-p 1p取值 1 1 1(保持),以概率 p p p取值 0 0 0(丢弃)。实现这一点的一种简单方式是首先从均匀分布 U [ 0 , 1 ] U[0, 1] U[0,1]中抽取样本。那么我们可以保留那些对应样本大于 p p p的节点,把剩下的丢弃。

在下面的代码中,(我们实现 dropout_layer 函数,该函数以dropout的概率丢弃张量输入X中的元素),如上所述重新缩放剩余部分:将剩余部分除以1.0-dropout

import torch
from torch import nn 
from d2l import torch as d2l
import torchvision
from torchvision import transforms
from torch.utils import data

def dropout_layer(X,dropout):
    assert 0<=dropout <= 1
    # 如果dropout概设置为1,全部元素被丢弃
    if dropout == 1:
        return torch.zeros_like(X)
    # 如果dropout概设置为0,全部元素被保留
    if dropout == 0:
        return X
    mask = (torch.rand(X.shape)>dropout).float() #使用mask而不是直接置零是为了提高计算效率
    return mask *X/(1.0-dropout)
# 测试dropout layer层
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))

测试Dropout的结果:

动手学深度学习(十五)——Dropout_第2张图片

# 定义数据加载函数
def get_dataloader_workers():
    """使用四个进程读取数据"""
    return 4

def load_data_fashion_mnist(batch_size,resize=None):
    """下载Fashion-MNIST数据集,并将其保存至内存中"""
    trans = [transforms.ToTensor()]
    if resize:
        trans.insert(0,transforms.Resize(resize)) # transforms.Resize将图片最小的一条边缩放到指定大小,另一边缩放对应比例
    trans = transforms.Compose(trans) # compose用于串联多个操作
    mnist_train = torchvision.datasets.FashionMNIST(root="./data",
                                                    train=True,
                                                    transform=trans,
                                                    download=True)
    mnist_test = torchvision.datasets.FashionMNIST(root="./data",
                                                   train=False,
                                                   transform=trans,
                                                   download=True)
    return (data.DataLoader(mnist_train,batch_size,shuffle=True,
                           num_workers=get_dataloader_workers()),
           data.DataLoader(mnist_test,batch_size,shuffle=True,
                          num_workers = get_dataloader_workers()))
# 使用Fashion-MNIST数据进行测试
num_inputs, num_outputs, num_hiddens1, num_hiddens2 = 784, 10, 1024, 1024
# 定义模型
dropout1, dropout2 = 0.5, 0.9

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))))
        # 只有在训练模型时才使用dropout
        if self.training == True:
            # 在第一个全连接层之后添加一个dropout层
            H1 = dropout_layer(H1, dropout1)
        H2 = self.relu(self.lin2(H1))
        if self.training == True:
            # 在第二个全连接层之后添加一个dropout层
            H2 = dropout_layer(H2, dropout2)
        out = self.lin3(H2)
        return out
net = Net(num_inputs, num_outputs, num_hiddens1, num_hiddens2)
# 训练和测试
num_epochs, lr, batch_size = 20, 0.5, 256
loss = nn.CrossEntropyLoss()
train_iter,test_iter = load_data_fashion_mnist(batch_size)
trainer = torch.optim.SGD(net.parameters(), lr=lr)

不加dropout(设置dropout值为0):前面net的参数设置中

# 不加dropout(设置dropout值为0)
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)

动手学深度学习(十五)——Dropout_第3张图片
加上dropout(设置第一层dropout 0.2,第二层dropout 0.5)

# 加上dropout(设置第一层dropout 0.2,第二层dropout 0.5)
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)

动手学深度学习(十五)——Dropout_第4张图片
加上dropout(设置第一层dropout 0.5,第二层dropout 0.9,相应每一个隐藏层的神经元都增加一部分)

# 加上dropout(设置第一层dropout 0.5,第二层dropout 0.9,相应每一个隐藏层的神经元都增加一部分)
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)

动手学深度学习(十五)——Dropout_第5张图片

四、使用pytorch简洁实现

加载数据、定义损失函数、更新方法(SGD)等如同前面

net = nn.Sequential(nn.Flatten(), nn.Linear(784, 1024), nn.ReLU(),
                    # 在第一个全连接层之后添加一个dropout层
                    nn.Dropout(dropout1), nn.Linear(1024, 1024), nn.ReLU(),
                    # 在第二个全连接层之后添加一个dropout层
                    nn.Dropout(dropout2), nn.Linear(1024, 10))

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

net.apply(init_weights);

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

动手学深度学习(十五)——Dropout_第6张图片

五、总结

  1. Dropout 将一些输出项随置零来控制模型复杂度
  2. 常作用在多层感知机的隐藏层输出上
  3. 丢弃的概率是控制模型复杂度的超参数
  4. Dropout一般用于全连接神经网络,而CNN常用CN等
  5. 在预测/推理的时候是不需要进行Dropout的,只有在训练时才会用上
  6. 发散以下思维:如果层能够Dropout,那么label、input、output都可以进行Dropout的
  7. 注意Dropout的定义,其对输入进行丢失时,保证了数据的统计分布(E(x))
  8. 相对于权重衰退,dropout更容易调整参数

你可能感兴趣的:(动手学深度学习:pytorch,Dropout,pytorch,过拟合,深度学习,机器学习)