当前位置: 首页 > article >正文

跟着李沐老师学习深度学习(七)

权重衰退 + 丢弃法

权重衰退(Weight Decay)

回忆:如何控制一个模型的容量(方法1:把模型变得比较小;方法2:使得模型参数的选择范围比较小)
是一种正则化模型的技术;

使用均方范数作为硬性限制

  • 通过限制参数值的选择范围来控制模型的容量
    在这里插入图片描述
    (最小化损失的时候,限制权重向量 w 的范数平方不能超过 θ)
    • 通常不限制偏移b(限不限制都差不多)
    • 小的 θ 意味着更强的正则项
    • 直观解释理解: θ 确实是把w限制在一个范围里面,但实际上使用以下的柔性限制。

使用均方范数作为柔性限制

  • 对每个 θ ,都可以找到 λ 使得之前的目标函数等价于下面:
    在这里插入图片描述
    • 可以通过拉格朗日乘子来证明
  • 超参数λ控制了正则项的重要程度
    • λ = 0:无作用
    • λ → ∞,w* → 0

对最优解的影响

在这里插入图片描述
理解:橙色是惩罚项:lamda/2*||w||平方的图像,绿色是原来损失函数的图像;曲线代表损失函数,有几个圈就是对不同的w来说,一个圈对应一个参数,最中间的点是参数的最优解。
大概应该就是:新的损失函数由两项组成,此时求导后,梯度有两项了,一项将w向绿线中心拉,一项将w向原点拉进,最后将在w*点达到一个平衡

  • 参数更新法则

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

  • 时间 t 更新参数
    在这里插入图片描述

    • 通常 nλ<1,在深度学习中通常叫做权重衰退
    • 主要变化:先把当前的值 缩小再 沿着负梯度方向学习

总结

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

代码实现- 从零实现

# 权重衰退

# 像之前一样 导入相关包
import torch
from torch import nn
from d2l import torch as d2l

# 像以前一样生成一些数据:
# ![image.png](attachment:image.png)

# 将训练数据设置的很小(当训练数据比较少的时候容易 过拟合)
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)

# 从零开始实现

#下面我们将从头开始实现权重衰减,只需将 𝐿2 的平方惩罚添加到原始目标函数中

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范数惩罚
# 实现这一惩罚最方便的方法是对所有项求平方后并将它们求和
def l2_penalty(w):
    return torch.sum(w.pow(2)) / 2

# 定义训练代码实现:
def train(lambd):
    w,b = init_params()
    # 定义了一个线性回归模型,
    net, loss = lambda X: d2l.linreg(X, w, b), d2l.squared_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:
            # 增加了L2范数惩罚项
            # 广播机制使 l2_penalty(w) 成为了长度为batch_size的向量
            l = loss(net(X), y) + lambd * l2_penalty(w) # 唯一的区别
            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())

# 忽略正则化进行训练
train(lambd = 0)
# 这里训练误差有了减少,但测试误差没有减少, 这意味着出现了严重的过拟合。

# 使用权重衰退进行 训练
train(lambd=3)
# 这里 训练误差增大,但测试误差减小;

忽略正则化得到的结果:
在这里插入图片描述
当没有进行正则化的时候,训练误差有了减少,但测试误差没有减少, 这意味着出现了严重的过拟合

使用权重衰退得到的结果:
在这里插入图片描述
可以看出:过拟合有一定的缓解;可能经过多次迭代会得到更好的效果;
也可以进行将lambd调整更大一些,效果会更好;(或者增加迭代次数)
在这里插入图片描述

代码- 简洁实现

# 简洁实现
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)
    在这里插入图片描述

  • 加权重衰退 train_concise(3)

在这里插入图片描述
由以上结果可以看到结果和之前是差不多的。

丢弃法(dropout)

动机

  • 一个好的模型需要对输入数据的扰动鲁棒
    • 使用噪音的数据等价于Tikhonov 正则
    • 丢弃法:在层之间加入噪音
      (输入数据加入随机扰动可以防止过拟合,泛化性更好,等价于一种正则方法。现在对噪音的添加方式从输入位置放到了层间位置)

无偏差的加入噪音

  • 对x加入噪音得到x’, 我们希望(不改变期望):
    在这里插入图片描述

  • 丢弃法对每个元素进行如下扰动:(给定一个概率p)
    在这里插入图片描述

为什么要出一个1-p: 对向量的每个元素操作,每个元素都有p的概率变为0,又有1-p的概率变成它的1/(1-p),这样才能保持期望不变

使用丢弃法

  • 通常将丢弃法作用在隐藏全连接层的输出上
    在这里插入图片描述

推理中的丢弃法

  • 正则项只在训练中使用:他们影响模型参数的更新
  • 在推理中,丢弃法直接返回输入:
    在这里插入图片描述
    • 这样保证确定性的输出

比较正则化的实验结果,因此丢弃法可看作是正则化方法;

总结

  • 丢弃法将一些输出项随机置0控制模型复杂度
  • 常作用在多层感知机的隐藏层输出
  • 丢弃概率是控制模型复杂度的超参数

代码实现

# Dropout

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.randn(X.shape) > dropout).float()
    # mask 是一个与输入张量 X 形状相同的浮点类型张量,其中元素的值要么是 1.0,要么是 0.0
    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.))

# 定义模型参数

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_hiddend1, 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)
  • 结果:
    在这里插入图片描述

  • 使用 dropout 都是0 的情况:
    在这里插入图片描述

代码-简洁实现


# 简洁实现
# 只需在每个全连接层之后添加一个Dropout层, 将暂退概率作为唯一的参数传递给它的构造函数。

net = nn.Sequential(nn.Flatten(),
                    nn.Linear(784, 256),
                    nn.ReLU(),
                    # 在第一个全连接层之后添加一个dropout层
                    nn.Dropout(dropout1),
                    nn.Linear(256, 256),
                    nn.ReLU(),
                    # 在第二个全连接层之后添加一个dropout层
                    nn.Dropout(dropout2),
                    nn.Linear(256, 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主要是针对过拟合问题的,这个模型没有过拟合所以没必要简化模型。


http://www.kler.cn/a/543172.html

相关文章:

  • 最新消息 | 德思特荣获中国创新创业大赛暨广州科技创新创业大赛三等奖!
  • 游戏引擎学习第98天
  • CNN-LSTM卷积神经网络长短期记忆神经网络多变量多步预测,光伏功率预测
  • 蓝桥杯算法日记|贪心、双指针
  • 交叉编译工具链下载和使用
  • 如何免费白嫖 Deepseek API 接口
  • 三角测量——用相机运动估计特征点的空间位置
  • JavaSE基本知识补充 -Map集合
  • DeepSeek与核货宝订货系统的协同进化:智能商业范式重构
  • AI大模型介绍yolo
  • P5:使用pytorch实现运动鞋识别
  • 碰一碰发视频源码技术开发,支持OEM
  • 蓝桥杯 Java B 组之排序算法(冒泡、选择、插入排序)
  • 如何在VSCode中免费使用DeepSeek R1:本地大模型编程助手全攻略
  • Visual Studio 使用 “Ctrl + /”键设置注释和取消注释
  • 【问】强学如何支持 迁移学习呢?
  • 使用Python爬虫获取淘宝Custom API接口数据
  • 极坐标 径向位置
  • DataBase【MySQL基础夯实使用说明(中)】
  • 数据集笔记:SINPA 新加坡停车场数量数据集
  • 国产编辑器EverEdit - 书签功能介绍
  • 大促备战中稳定性建设策略与总结
  • ffmpeg -buildconf
  • AI前端开发:赋能开发者,提升解决实际问题的能力
  • 25、深度学习-自学之路-卷积神经网络基于MNIST数据集的程序展示
  • 企业的文档安全怎么防护?