《动手深度学习》4.5 权重衰减Weight Decay

4.5 权重衰减Weight Decay

  • 理论
      • 硬性限制W:
      • 柔性限制W:
      • 看图说明正则项(惩罚项)对最优解的影响
      • 参数更新的过程
  • 代码从零实现
      • 生成人工数据
      • 参数初始化
      • 定义L2范数惩罚( λ \lambda λ后续添加)
      • 训练部分
      • 训练结果
      • ⭐⭐ λ \lambda λ的选择
  • 代码简洁实现

理论

目的: 使用正则化技术缓解过拟合,而不必再去寻找更多的训练数据!

缓解过拟合的方法:

  1. 限制模型容量(限制特征的数量):eg:调整拟合多项式的阶数
  2. 限制参数的可选范围:eg:限制W和b的范围

权重衰减就是限制了W的范围!

硬性限制W:

  • 使用均方范数作为硬性限制:直接限制W的范数小于某个值。
    在这里插入图片描述
  • 通常不限制b,因为b只是影响曲线的上下平移,对曲线的形状没有实际作用。
  • θ \theta θ越小,意味着这个正则项越强。

柔性限制W:

  • 使用均方范数作为柔性限制
    对于每个 θ \theta θ,都可以找到一个 λ \lambda λ使得之前的目标函数等价于下式:
    在这里插入图片描述

  • 超参数 λ \lambda λ控制了正则项的重要程度

    • λ = 0 \lambda = 0 λ=0:无作用
    • λ → ∞ \lambda → ∞ λ:表示w*→0
  • 为什么选择L2范数作为正则项?

    • 使用L2范数的一个原因是它对权重向量的大分量施加了巨大的惩罚。 这使得我们的学习算法偏向于在大量特征上均匀分布权重的模型。在实践中,这可能使它们对单个变量中的观测误差更为稳定。
  • 相比之下,L1惩罚会导致模型将权重集中在一小部分特征上, 而将其他权重清除为零。 这称为特征选择(feature selection),这可能是其他场景下需要的。

看图说明正则项(惩罚项)对最优解的影响

  • 要保证权重向量比较小, 最常用方法是将其范数作为惩罚项加到最小化损失的问题中。
  • 将原来的训练目标最小化训练标签上的预测损失, 调整为最小化预测损失和惩罚项之和
    《动手深度学习》4.5 权重衰减Weight Decay_第1张图片

参数更新的过程

  • 计算梯度:
    在这里插入图片描述

  • 更新参数:
    在这里插入图片描述

  • 与未正则化的更新过程做对比:
    在这里插入图片描述
    会发现正则化后只是多了一项 ( − η λ ) W (-\eta\lambda)W (ηλ)Wt
    《动手深度学习》4.5 权重衰减Weight Decay_第2张图片

  • 通常, η λ < 1 \eta\lambda < 1 ηλ<1,也就是说每次会先给权重W减去一部分,然后再按梯度进行下降。这就是名字"权重衰退"的来由!!!

代码从零实现

权重衰减是最广泛使用的正则化的技术之一

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

生成人工数据

《动手深度学习》4.5 权重衰减Weight Decay_第3张图片

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]

定义L2范数惩罚( λ \lambda λ后续添加)

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

训练部分

和之前的区别在于,要接受一个输入参数
λ \lambda λ
l = loss(net(X), y) + lambd * l2_penalty(w) #此处引入了惩罚项!这是和之前train唯一的区别!

def train(lambd):
    w, b = init_params() #参数初始化
    net, loss = lambda X: d2l.linreg(X, w, b), d2l.squared_loss #定义模型结构为简单的线性回归,loss为平方损失
    num_epochs, lr = 100, 0.003
    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:
            with torch.enable_grad():
                l = loss(net(X), y) + lambd * l2_penalty(w) #此处引入了惩罚项!这是和之前train唯一的区别!
            l.sum().backward()
            d2l.sgd([w, b], lr, batch_size)
        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范数是:', torch.norm(w).item())

训练结果

  • 不使用正则化,直接训练:严重过拟合!
    《动手深度学习》4.5 权重衰减Weight Decay_第4张图片
  • 选择比较大一点的lambda,使用权重衰减
    由于训练集只有20,太小了,而且lambda比较小,对W的限制性一般,所以拟合效果还是比较差。
    《动手深度学习》4.5 权重衰减Weight Decay_第5张图片
  • 保持 λ = 3 \lambda = 3 λ=3,数据集大小改成200
    果然,数据集足够大可以很好的避免过拟合。
    《动手深度学习》4.5 权重衰减Weight Decay_第6张图片
  • 保持数据集不变,调大 λ \lambda λ的值

过拟合有所减轻,但是发现 λ \lambda λ也不能太大,会出现欠拟合的结果。
《动手深度学习》4.5 权重衰减Weight Decay_第7张图片
《动手深度学习》4.5 权重衰减Weight Decay_第8张图片

⭐⭐ λ \lambda λ的选择

  • 当lambda的值很小时,其惩罚项值不大,还是会出现过拟合现象
  • 当lambda的值逐渐调大的时候,过拟合现象的程度越来越低,
  • 但是当labmda的值超过一个阈值时,就会出现欠拟合现象,因为其惩罚项太大,导致丢失太多的特征,甚至一些比较重要的特征。

lamda值是提升模型泛化能力的,但是不能设置过高,否则也会导致梯度消失,也不能设置过低,将会导致梯度爆炸

调参的时候,可以通过网格搜索来确定最佳的正则化参数。
一般的做法的是,首先在0.0到0.1之间的各个数量级上进行网格搜索,然后在找到某个级别后,再对该级别进行网格搜索

沐神说,一般都设置为e-2,e-3这种的,权重衰减的效果确实有限,如果没有明显效果就只能换别的方法了。

代码简洁实现

区别:把weight decay放在了训练过程里,而没有定义在损失函数loss(或者说目标函数)里。
《动手深度学习》4.5 权重衰减Weight Decay_第9张图片
而计算loss的过程没有变化:l = loss(net(X), y)

这样其实更适合计算机的训练,减少了求导的复杂性。但本质是一样的,因为weighth decay就是每次更新时给w多减了一项而已。

def train_concise(wd):
    net = nn.Sequential(nn.Linear(num_inputs, 1))
    for param in net.parameters():
        param.data.normal_()
    loss = nn.MSELoss()
    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:
            with torch.enable_grad():
                trainer.zero_grad()
                l = loss(net(X), y)
            l.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())

你可能感兴趣的:(动手深度学习)