动手学深度学习笔记---4.3 解决过拟合_权重衰减与Dropout

一、权重衰退(Weight_decay)

正则限制

针对模型训练时出现的过拟合问题,限制模型容量是一种很好的解决方法,目前常用的方法有以下两种:

  1. 缩小模型参数量,例如降低模型层数,使得模型更加轻量化, L 1 L1 L1正则化
  2. 通过限制参数值的选择范围来控制模型容量, L 2 L2 L2正则化:
    • 使用均方范数作为硬性限制,即对于参数 w , b w,b w,b,假设其损失函数为 l ( w , b ) l(w,b) l(w,b),则其优化目标为 m i n   L ( w , b ) s u b j e c t   t o   ∣ ∣ w ∣ ∣ 2 ≤ θ min \ L(w,b) \quad subject \ to \ ||w||^2 \leq \theta min L(w,b)subject to ∣∣w2θ
      • 在上述约束中通常不对 b b b进行约束,因为 b b b是针对 0 0 0点的偏移,且在实际中,限不限制并无区别。
      • θ \theta θ越小时,正则项的约束越强。
    • 使用均方范数作为柔性限制,通常来说,硬性限制的优化较为困难,因此通常采用柔性限制方法,即对于每一个 θ \theta θ均可找到一个 λ \lambda λ 使得之前的硬性限制目标函数等价于: m i n l ( w , b ) + λ 2 ∣ ∣ w ∣ ∣ 2 min l(w,b)+\frac{\lambda}{2}||w||^2 minl(w,b)+2λ∣∣w2,可用拉格朗日乘子证明。
      • 正则项的重要程度通过超参数 λ \lambda λ 进行控制,当 λ = 0 \lambda=0 λ=0时,整个正则项不起作用,相当于 θ \theta θ 趋向于无穷大时;当 λ → ∞ \lambda \rightarrow \infty λ,等价于 θ → 0 \theta \rightarrow 0 θ0,渐渐使得 w ∗ → 0 w^* \rightarrow 0 w0

动手学深度学习笔记---4.3 解决过拟合_权重衰减与Dropout_第1张图片

上图中,绿线部分为 L ( w , b ) L(w,b) L(w,b),在未引入正则惩罚项 λ 2 ∣ ∣ w ∣ ∣ 2 \frac {\lambda}{2}||w||^2 2λ∣∣w2 时, L L L的最优解在圆心(绿点)处,而引入正则惩罚项后,绿点处的值相对于黄线部分来说非常大,此时不再是最优解。

w ~ ∗ \tilde{w}^* w~向原点处移动时, L ( w , b ) L(w,b) L(w,b)的值会增大,但惩罚项的值会缩小(离原点越近,正则项越小)。因此在绿点处,惩罚项对最优点的拉动力大于 L ( w , b ) L(w,b) L(w,b) 的拉力,使得最优点逐渐移动到黄点位置,假设在此时, L ( w , b ) L(w,b) L(w,b)和惩罚项达到平衡,两侧的减少程度不足以满足另一项目的增加程度,此时达到最优。在这个过程中, w w w的绝对值不断降低,但不会像 L 1 L1 L1正则化那样降低到 0,达到稀疏矩阵的效果。在这个过程中,越小的参数就会带来越简单的模型。

1.“为什么越小的参数值会带来越简单的模型?”

答: 一方面,较大的参数值会放大相似输入之间的差异,使得网络对训练数据敏感,从而出现过拟合现象,而小权重缩小了这种差异,从而提供了更好的泛化性。另一方面,越复杂的模型,越倾向于拟合全部样本,甚至异常样本,此时这种拟合会使得函数曲线变化剧烈,因此复杂的模型往往是过拟合的结果,其参数值就会较大。

2.“为什么切点处是最小值?”

答: 采用拉格朗日乘子法求解约束极值问题时,例如 m i n   L ( w , b ) s u b j e c t   t o   ∣ ∣ w ∣ ∣ 2 ≤ θ min \ L(w,b) \quad subject \ to \ ||w||^2 \leq \theta min L(w,b)subject to ∣∣w2θ,通常会引入拉格朗日乘子构建新的函数 L ( w , b , λ ) = L ( w , b ) + λ g ( w ) L(w,b, \lambda) = L(w,b) + \lambda g(w) L(w,b,λ)=L(w,b)+λg(w),然后通过求解下述方程组,可得最优解:

{ ▽ w L ( w , b , λ ) = L w ′ ( w , b ) = 0 ▽ b L ( w , b , λ ) = L b ′ ( w , b ) = 0 L λ ′ = g ( w ) = 0 \begin{cases} \triangledown_w L(w,b,\lambda) = L_w'(w,b)=0 \\ \triangledown_b L(w,b,\lambda) = L_b'(w,b)=0 \\ L_\lambda'= g(w) = 0 \end{cases} wL(w,b,λ)=Lw(w,b)=0bL(w,b,λ)=Lb(w,b)=0Lλ=g(w)=0

当曲面 L ( w , b ) L(w,b) L(w,b)即图中绿色等高线部分与曲线 g ( w ) g(w) g(w)即图中黄色部分相切时,切点即为最优解 P P P,因为二者相切时,约束曲线 g ( w ) g(w) g(w) P P P 处法向量 ▽ g \triangledown g g与该处目标函数的梯度 ▽ L \triangledown L L相等且反向(共线),也就是 L ( w , b ) L(w,b) L(w,b)和惩罚项达到平衡时。

参数更新法则

动手学深度学习笔记---4.3 解决过拟合_权重衰减与Dropout_第2张图片

动手学深度学习笔记---4.3 解决过拟合_权重衰减与Dropout_第3张图片

未引入惩罚项时, w w w的梯度为 ∂   L ( w , b ) ∂   w \frac{\partial \ L(w,b)}{\partial \ w}  w L(w,b),时间 t t t 处更新参数公式为 w t + 1 = w t − η ∂   L ( w , b ) ∂   w w_{t+1}=w_t - \eta \frac{\partial \ L(w,b)}{\partial \ w} wt+1=wtη w L(w,b)
因此引入惩罚项后, w t w_t wt前增加了 ( 1 − η λ ) (1-\eta \lambda) (1ηλ)项目,通常 η λ < 1 \eta \lambda < 1 ηλ<1,使得每一次更新时,当前的权重 w t w_t wt会进行一次缩小,这种情况就是 权重衰退。

总结:

  • 权重衰退通过 L 2 L2 L2 正则项使得模型参数不会过大,从而控制模型复杂度。
  • 正则项权重是控制模型复杂度的超参数。

代码实现:

%matplotlib inline
import torch
from torch import nn
from d2l import torch as d2l

初始化模型参数,并手动生成训练样本数为20,测试样本数为100,输入维度为200,batch_size为5的数据集,过小的训练样本集与过大的输入维度,可以使得模型过拟合效果更加明显,数据集的生成公式为:
y = 0.05 + ∑ i = 1 d 0.01 x i + ϵ w h e r e ϵ ∼ N ( 0 , 0.0 1 2 ) y = 0.05 + \sum_{i=1}^d 0.01x_i + \epsilon \qquad where \quad \epsilon \sim N(0, 0.01^2) y=0.05+i=1d0.01xi+ϵwhereϵN(0,0.012)

n_train, n_test, num_inputs, batch_size = 20, 100, 200, 5
true_w, true_b = torch.ones((num_inputs, 1)) * 0.01, 0.05
train_data = d2l.synthetic_data(true_w, true_b, n_train)    # 生成满足 y = Xw + b + noise的值
train_iter = d2l.load_array(train_data, batch_size)         # 按batch_size将训练数据划分为iter
test_data = d2l.synthetic_data(true_w, true_b, n_test)
test_iter = d2l.load_array(test_data, batch_size, is_train=False)

对模型参数进行初始化,w满足正态分布,b为全零矩阵

def init_params():
    w = torch.normal(0, 1, size=(num_inputs, 1), requires_grad=True)
    b = torch.zeros(1, requires_grad=True)
    return [w, b]

定义 L2 惩罚项,为方便修改 λ \lambda λ 值,不将该超参数写入函数内。

def l2_penalty(w):
    return torch.sum(w.pow(2)) / 2      # ||w||^2 / 2

训练代码实现

def train(lambd):
    w, b = init_params()
    # 模型为一个简单的线性回归函数,loss函数为均方误差MSE
    net, loss = lambda X: d2l.linreg(X, w, b), d2l.squared_loss     
    num_epochs, lr = 100, 0.003     # 定义epoch与学习率
    animator = d2l.Animator(xlabel='epochs', ylabel='loss', yscale='log',
                            xlim=[5, num_epochs], legend=['train', 'test'])     # 绘制图像
    for epoch in range(num_epochs):
        for X, y in train_iter:
            # 增加了L2范数惩罚项,
            # 广播机制使l2_penalty(w)成为一个长度为batch_size的向量
            # L = L(w,b) + lambda / 2 * ||w||^2
            l = loss(net(X), y) + lambd * l2_penalty(w)         
            l.sum().backward()      # 反向传播
            d2l.sgd([w, b], lr, batch_size)     # 采用SGD优化函数作为优化器
        if (epoch + 1) % 5 == 0:
            # 每5轮在图像上绘制出一个最新的loss点
            animator.add(epoch + 1, (d2l.evaluate_loss(net, train_iter, loss),
                                     d2l.evaluate_loss(net, test_iter, loss)))
    print('w的L2范数是:', torch.norm(w).item())
train(lambd=0)

动手学深度学习笔记---4.3 解决过拟合_权重衰减与Dropout_第4张图片
此时惩罚项并未发挥作用,随epoch增加,train_loss与test_loss之间的差距越来越大,此时模型发生了过拟合。

train(lambd=3)

动手学深度学习笔记---4.3 解决过拟合_权重衰减与Dropout_第5张图片

此时可以看到,随epoch的增加,train_loss与test_loss之间的差距开始缩小,证明增加惩罚项后,模型的过拟合现象得到缓解。

train(lambd=10)

动手学深度学习笔记---4.3 解决过拟合_权重衰减与Dropout_第6张图片

相较于 λ = 3 \lambda = 3 λ=3,此时模型的train_loss与test_loss曲线间的差距进一步减少,并且都发生了很好的收敛,有效缓解了过拟合现象。

train(lambd=100)

动手学深度学习笔记---4.3 解决过拟合_权重衰减与Dropout_第7张图片

再次增大 λ \lambda λ后,w的L2范数降低到非常小的值,此时发现train_loss与test_loss之前趋于稳定。

def train(epoch_iter):
    lambd = 0
    num_epochs, lr = 100, 0.003     # 定义epoch与学习率
    animator = d2l.Animator(xlabel='lambd', ylabel='GAP', yscale='log',
                            xlim=[5, num_epochs], legend='GAP')     # 绘制图像
    for e_iter in range(epoch_iter):
        w, b = init_params()
        net, loss = lambda X: d2l.linreg(X, w, b), d2l.squared_loss
        for epoch in range(num_epochs):
            for X, y in train_iter:
                # 增加了L2范数惩罚项,
                # 广播机制使l2_penalty(w)成为一个长度为batch_size的向量
                l = loss(net(X), y) + lambd * l2_penalty(w)         # L = L(w,b) + lambda / 2 * ||w||^2
                l.sum().backward()      # 反向传播
                d2l.sgd([w, b], lr, batch_size)     # 采用SGD优化函数作为优化器
        
        lambd = lambd + 10
        animator.add(lambd, (d2l.evaluate_loss(net, test_iter, loss) - d2l.evaluate_loss(net, train_iter, loss)))   
train(epoch_iter=10)

动手学深度学习笔记---4.3 解决过拟合_权重衰减与Dropout_第8张图片
动手学深度学习笔记---4.3 解决过拟合_权重衰减与Dropout_第9张图片

可以看到,从上图中可以看到,随 λ \lambda λ 的增加,train_loss与test_loss之间的差距不断缩小,直到 λ = 80 \lambda=80 λ=80左右时,达到稳定。

二、Dropout

正如一个好的身体可以抵抗多种病毒,一个好的模型也需要做到对多种输入数据所带来的扰动鲁棒。以图片为例,不管图中加入多少噪音,在模糊一点的情况下,人依旧能识别出这张图片,忽略噪声带来的干扰。深度学习模型也是如此。

因此,一个好的模型能在未知数据上具备很好的泛化性,需要满足两点要求:

  • 简单性: 经典泛化理论认为,为了缩小训练和测试性能之间的差距,应该以简单的模型为目标。参数的范数也可以理解为模型的简单性度量。
  • 平滑性:一个好的函数不应对输入时的微小变化过分敏感,即对于模型推理时,添加一些噪声到数据中,对于模型的结果应该是基本无影响的。

克里斯托弗·毕晓普等人证明,对于数据来说,使用有噪声的数据,实质上等价于一个Tikhonov 正则,属于在数据中增加噪音的方法。用数学证实了“要求函数光滑”和“要求函数对输入的随机噪声具有适应性”之间的联系。

丢弃法则与上述数据噪音不同,丢弃法不在输入时加噪音,而是在层之间增加无差别噪音。 因为当训练一个有多层的深层网络时,注入噪声只会在输入-输出映射上增强平滑性。

无差别加入噪音

对于数据中加噪音的方法,输入值 x x x增加噪音得到结果 x ′ x' x,通常希望加入噪音后,数据的期望不变,即 E [ x ′ ] = x E[x']=x E[x]=x

而丢弃法则是对于概率 p p p,为每个元素增加如下扰动:

x i ′ = { 0 w i t h   p x i 1 − p o t h e r w i s e x_i' = \begin{cases} 0 \qquad with \ p \\ \frac{x_i}{1-p} \qquad otherwise \end{cases} xi={0with p1pxiotherwise

此时仍满足期望不变性。

E [ x i ′ ] = p ∗ 0 + ( 1 − p ) ∗ x i 1 − p = x i E[x'_i] = p * 0 + (1-p)* \frac{x_i}{1-p} = x_i E[xi]=p0+(1p)1pxi=xi

丢弃法的使用

通常在训练使用时,Dropout常被作用于全连接层的隐藏层上:
动手学深度学习笔记---4.3 解决过拟合_权重衰减与Dropout_第10张图片

在使用Dropout后,隐藏层中的部分节点可能被随机抛弃,设置为0。而且该过程是一个随机的过程,每次Dropout时所抛弃的节点不一定相同。

但是在推理时,通常不会使用Dropout,因为其本质也可理解为一个正则项,而正则项因为影响模型参数的更新,通常只在训练时使用。

因此在预测时,参数无需变化,Dropout输出的时其输入本身,即 h = D r o p o u t ( h ) h=Dropout(h) h=Dropout(h) ,从而保证确定性的输出结果。

而在早期Hinton提出的Dropout中,具体思路与当今方法并不相同,Hinton将之理解为一个类似集成学习的ensemble问题,通过将神经网络中节点随机去掉后,形成多个不同的子神经网络,然后集成这些网络结果做一个平均,获得最终结果。

后续实验中发现,Dropout的结果很符合正则项的结果,因此人们逐渐将其转为正则问题看待。(玄学)

总结:

  • 丢弃法将一些输出项随机设置为0,来以此控制模型复杂度
  • 丢弃法通常作用于多层感知机(全连接层)的隐藏层输出上
  • 丢弃概率 p p p是用于控制模型复杂度的超参数,类似权重衰退中的 λ \lambda λ,通常设置为0.5

代码实现

import torch
from torch import nn
from d2l import torch as d2l

# 定义Dropout层
def dropout_layer(X, dropout):
    # 丢弃概率不在(0,1)之间时,触发断言退出函数
    assert 0 <= dropout <= 1
    # 在本情况中,所有元素都被丢弃
    if dropout == 1:
        return torch.zeros_like(X)
        # 在本情况中,所有元素都被保留
    if dropout == 0:
        return X
        # 生成mask矩阵,根据该矩阵将对应位置x值置0
    mask = (torch.rand(X.shape) > dropout).float()
    return mask * X / (1.0 - dropout)
# 定义模型参数
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))))
        # 只有在训练模型时才使用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 = 10, 0.5, 256
loss = nn.CrossEntropyLoss(reduction='none')
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)

动手学深度学习笔记---4.3 解决过拟合_权重衰减与Dropout_第11张图片
交换两个丢弃率后,图像如下:
动手学深度学习笔记---4.3 解决过拟合_权重衰减与Dropout_第12张图片

可以看到将丢弃率由 【0.2,0.5】更换为 【0.5,0.2】后,test_loss发生了一些波动,但最终结果无显著差异。

为放大这一问题,将丢弃率由【0.1,0.9】修改为【0.9,0.1】,图像如下:
动手学深度学习笔记---4.3 解决过拟合_权重衰减与Dropout_第13张图片动手学深度学习笔记---4.3 解决过拟合_权重衰减与Dropout_第14张图片

可以看到模型出现了明显的过拟合现象,train_loss也下降缓慢。

初步猜测,由于多层感知机中采用全连接方法,层数越多提取的特征越精细,模型学习到过于精细的特征后,就会对微小特征过分敏感,产生过拟合现象。而【0.1,0.9】抛弃率Dropout的使用,使得模型少量抛弃了前期粗糙的部分冗余特征或噪声,大量抛弃精细特征,提高了模型鲁棒性。而【0.9,0.1】抛弃率的使用,会使得模型大量丢弃前期采集到的特征,降低了模型鲁棒性,致使test_acc 不增反减,train_loss也过高导致无法运行,改为【0.8,0.2】时仍会出现类似问题。

个人认为,过高的抛弃率对多层感知机的第一层影响最大,大量信息的丢失会使得模型产生很差的训练效果。但同样的高抛弃率对第二个隐藏层(也可能是最后一个隐藏层)影响较小,产生类似减少一个隐藏层的效果,反而降低了模型复杂度。

你可能感兴趣的:(深度学习,机器学习,人工智能)