处理过拟合-权重衰退和Dropout

最常见的处理过拟合的方法:控制模型的容量

  • 将模型变得比较小,减少里面的参数的数量
  • 缩小参数值的取值范围

1. 权重衰退(Weight decay)

1.1 方法

在训练参数化机器学习模型时, 权重衰减(weight decay)是最广泛使用的正则化的技术之一, 它通常也被称为 L 2 L_2 L2正则化。 这项技术通过函数与零的距离来衡量函数的复杂度, 因为在所有函数 f f f中,函数 f = 0 f = 0 f=0(所有输入都得到值) 在某种意义上是最简单的。 但是我们应该如何精确地测量一个函数和零之间的距离呢? 没有一个正确的答案。

一种简单的方法是通过线性函数 f ( x ) = W T x f(x) = W^Tx f(x)=WTx中的权重向量的某个范数来度量其复杂性, 例如 ∣ ∣ W ∣ ∣ 2 ||W||^2 ∣∣W2。 要保证权重向量比较小, 最常用方法是将其范数作为惩罚项加到最小化损失的问题中。 将原来的训练目标最小化训练标签上的预测损失, 调整为最小化预测损失和惩罚项之和。 现在,如果我们的权重向量增长的太大, 我们的学习算法可能会更集中于最小化权重范数 ∣ ∣ W ∣ ∣ 2 ||W||^2 ∣∣W2

对于损失函数 L ( W , b ) L(W,b) L(W,b)来说,在这个基础上添加一个 L 2 L_2 L2罚函数项,同时为了平衡这个新的额外惩罚的损失,通过正则项权重 λ \lambda λ来描述这种权衡,因此带有 L 2 L_2 L2罚函数项的新损失函数为:
L ( W , b ) + λ 2 ∣ ∣ W ∣ ∣ 2 L(W,b) + \frac{\lambda}{2}||W||^2 L(W,b)+2λ∣∣W2

正则项权重 λ \lambda λ控制模型复杂度的超参数,其是非负的

1.2 参数更新法则

计算梯度:
∂ ∂ W ( L ( W , b ) + λ 2 ∣ ∣ W ∣ ∣ 2 ) = ∂ L ( W , b ) ∂ W + λ W \frac{\partial}{\partial W} (L(W,b) + \frac{\lambda}{2}||W||^2)= \frac{\partial L(W,b)}{\partial W} + \lambda W W(L(W,b)+2λ∣∣W2)=WL(W,b)+λW
更新参数:
W t + 1 = ( 1 − η λ ) w i − η ∂ L ( W t , b ) ∂ W t W_{t+1} = (1 - \eta \lambda) w_i - \eta\frac{\partial L(W_t,b)}{\partial W_t} Wt+1=(1ηλ)wiηWtL(Wt,b)

通常 η λ \eta \lambda ηλ<1;可以看出,相较于不使用罚函数的参数更新来说,使用了罚函数的会先将 W t W_t Wt减小,再减去对应的梯度值,因此称为权重衰退。

1.3 代码实现

代码来自:动手学深度学习 v2

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

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)
train_iter = d2l.load_array(train_data, batch_size)
test_data = d2l.synthetic_data(true_w, true_b, n_test)
test_iter = d2l.load_array(test_data, batch_size, is_train=False)

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]
    
def train_concise(wd):
    net = nn.Sequential(nn.Linear(num_inputs, 1))
    for param in net.parameters():
        param.data.normal_()
    loss = nn.MSELoss(reduction='none')
    num_epochs, lr = 100, 0.003
    # 偏置参数没有衰减
    trainer = torch.optim.SGD([
        {"params":net[0].weight,'weight_decay': wd},
        {"params":net[0].bias}], lr=lr)
    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:
            trainer.zero_grad()
            l = loss(net(X), y)
            l.mean().backward()
            trainer.step()
        if (epoch + 1) % 5 == 0:
            animator.add(epoch + 1,
                         (d2l.evaluate_loss(net, train_iter, loss),
                          d2l.evaluate_loss(net, test_iter, loss)))
    print('w的L2范数:', net[0].weight.norm().item())
train_concise(0)

λ = 0 \lambda = 0 λ=0时,训练误差有了减少,但测试误差没有减少, 这意味着出现了严重的过拟合。

在这里插入图片描述

train_concise(3)

当使用权重衰减来运行代码。 训练误差虽然会增大,但测试误差减小。 这正是我们期望从正则化中得到的效果。

在这里插入图片描述

2. 丢弃法(Dropout)

对于 X X X中加入噪音 ϵ \epsilon ϵ得到 X ′ X' X,我们希望:
E [ X ′ ] = X E[X'] = X E[X]=X

丢弃法中,对每个元素进行如下的扰动:
x i ′ = { 0 if with probablity  p x i 1 − p otherise x_i' = \begin{cases} 0 & \text{if with probablity }p \\ \frac{x_i}{1-p}& \text{otherise} \end{cases} xi={01pxiif with probablity potherise
这样的得到的模型其期望着不变,因为:
E [ x i ′ ] = p × 0 + x i 1 − p × ( 1 − p ) = x i E[x_i'] = p \times 0 + \frac{x_i}{1-p} \times (1-p) = x_i E[xi]=p×0+1pxi×(1p)=xi

2.1 实际中的丢弃法

当我们将暂退法应用到隐藏层,以 p p p的概率将隐藏单元置为零时, 结果可以看作一个只包含原始神经元子集的网络。比如在下图中,删除了 h 2 h_2 h2 h 5 h_5 h5, 因此输出的计算不再依赖于 h 2 h_2 h2 h 5 h_5 h5,并且它们各自的梯度在执行反向传播时也会消失。 这样,输出层的计算不能过度依赖于 h 1 , ⋯   , h 5 h_1,\cdots,h_5 h1,,h5的任何一个元素。

注意是dropout hidden layer的输出
在这里插入图片描述

2.2 代码实现

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.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)

在这里插入图片描述

2.3 小结

  • dropout仅在训练期间使用。它只会对权重产生影响,在预测的时候,权重不需要发生变化的情况下,不需要正则,所以在推理中不需要正则,确保在推理过程中能够有一个确定性的输出
  • dropout在前向传播过程中,计算每一内部层的同时丢弃一些神经元。
  • dropout常用在MLP的隐藏层输出上,一般是用在全连接层上,在卷积层不会使用

这里是引用

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值