FA_CNN卷积神经网络二十一---丢弃法PyTorch

1. 引言

过拟合是深度学习中绕不开的问题:模型在训练集上表现优异,一到测试集就"翻车"。丢弃法(Dropout) 是应对过拟合最经典、最常用的正则化手段之一。本文从原理出发,先手写 dropout_layer 理解其内部机制,再用 PyTorch 框架的 nn.Dropout 做简洁实现,并在 Fashion-MNIST 上对比训练效果。

2. 丢弃法原理

丢弃法在训练时以概率 ppp 随机将隐藏层单元的输出置零,被"丢弃"的神经元不参与本次前向与反向传播。这样每次迭代都在训练一个结构不同的子网络,相当于对大量子网络做集成,从而降低模型对特定神经元的依赖,缓解过拟合。

关键细节在于缩放 :若某单元以概率 ppp 被置零,则其输出期望变为原来的 (1−p)(1-p)(1−p) 倍。为了让训练与测试时的期望一致,需要对保留下来的输出除以 1−p1-p1−p:

xi′={0,以概率 pxi1−p,以概率 1−p x_i' = \begin{cases} 0, & \text{以概率 } p \\ \dfrac{x_i}{1-p}, & \text{以概率 } 1-p \end{cases} xi′=⎩ ⎨ ⎧0,1−pxi,以概率 p以概率 1−p

这里除以 1−p1-p1−p 是为了 xi′x_i'xi′ 与原来的 xix_ixi 的期望相同。

需要注意:

  • 丢弃法只在训练时启用,测试/推理阶段直接使用完整网络,不做随机丢弃。
  • 丢弃概率 ppp 是超参数,通常隐藏层取 0.2 ~ 0.5,输入层可适当小一些。
  • 输出层一般不加 Dropout。

3. 从零实现丢弃法

3.1 实现 dropout_layer 函数

该函数以 dropout 的概率丢弃张量输入 X 中的元素:

python 复制代码
import torch
from torch import nn
from d2l import torch as d2l


def dropout_layer(X, dropout):
    assert 0 <= dropout <= 1  # dropout 必须在 [0, 1] 区间内,否则报错
    if dropout == 1:
        return torch.zeros_like(X)  # dropout 为 1,则 X 全部置零
    if dropout == 0:
        return X  # dropout 为 0,则原样返回
    # 生成与 X 同形状的均匀分布随机数,大于 dropout 的位置保留
    mask = (torch.rand(X.shape) > dropout).float()
    return mask * X / (1.0 - dropout)

说明:原笔记中此处写作 torch.randn,但 randn 是标准正态分布,取值可能为负,会导致掩码几乎全为 1,达不到丢弃效果。正确做法是使用 torch.rand([0,1)[0,1)[0,1) 均匀分布)。

3.2 测试不同丢弃概率

python 复制代码
X = torch.arange(16, dtype=torch.float32).reshape((2, 8))
print(X)
print(dropout_layer(X, 0.))
print(dropout_layer(X, 0.5))  # 约 50% 的元素被置零
print(dropout_layer(X, 1.))

输出示例:

text 复制代码
tensor([[ 0.,  1.,  2.,  3.,  4.,  5.,  6.,  7.],
        [ 8.,  9., 10., 11., 12., 13., 14., 15.]])
tensor([[ 0.,  1.,  2.,  3.,  4.,  5.,  6.,  7.],
        [ 8.,  9., 10., 11., 12., 13., 14., 15.]])
tensor([[ 0.,  0.,  4.,  6.,  0.,  0.,  0., 14.],
        [16.,  0.,  0.,  0.,  0.,  0.,  0., 30.]])
tensor([[0., 0., 0., 0., 0., 0., 0., 0.],
        [0., 0., 0., 0., 0., 0., 0., 0.]])

可以看到:dropout=0 时输出不变;dropout=0.5 时约一半元素被置零,保留的元素被放大为原来的 2 倍;dropout=1 时全部置零。

3.3 定义带 Dropout 的多层感知机

构建一个含两个隐藏层、每层 256 个单元的多层感知机,两个隐藏层的丢弃概率分别为 0.2 和 0.5:

python 复制代码
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))))
        if self.training:  # 仅在训练时施加 dropout
            H1 = dropout_layer(H1, dropout1)
        H2 = self.relu(self.lin2(H1))
        if self.training:
            H2 = dropout_layer(H2, dropout2)
        out = self.lin3(H2)  # 输出层不施加 dropout
        return out


net = Net(num_inputs, num_outputs, num_hiddens1, num_hiddens2)

3.4 训练与测试

python 复制代码
num_epochs, lr, batch_size = 10, 0.5, 256
loss = nn.CrossEntropyLoss()
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. 简洁实现(使用框架)

PyTorch 已内置 nn.Dropout,无需手写掩码逻辑,直接插入到网络层之间即可:

python 复制代码
import torch
from torch import nn
from d2l import torch as d2l

num_epochs, lr, batch_size = 10, 0.5, 256
dropout1, dropout2 = 0.2, 0.5
loss = nn.CrossEntropyLoss()
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)

net = nn.Sequential(
    nn.Flatten(),
    nn.Linear(784, 256), nn.ReLU(), nn.Dropout(dropout1),
    nn.Linear(256, 256), nn.ReLU(), 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)

nn.Dropout 会自动根据 model.train() / model.eval() 切换行为:训练时按概率丢弃并缩放,评估时直接透传,无需手动判断。

5. 两种实现对比

对比项 从零实现 框架实现
核心逻辑 手写掩码 + 缩放 nn.Dropout 封装
训练/测试切换 手动判断 self.training 自动跟随 train() / eval()
代码量 较多,便于理解原理 简洁,适合工程使用
适用场景 学习原理、自定义变体 实际项目开发

6. 小结

  • 丢弃法通过在训练时随机置零隐藏单元,抑制神经元间的共适应,是缓解过拟合的有效正则化手段。
  • 为保证期望一致,保留的输出需除以 1−p1-p1−p 进行缩放。
  • 丢弃法只在训练阶段生效,测试阶段使用完整网络。
  • 实际开发中优先使用 nn.Dropout,理解原理时手写 dropout_layer 更有帮助。
相关推荐
学IT的周星星1 小时前
本地部署与开源模型:不想花钱怎么办
人工智能·开源·大模型·llm·agent
AI人工智能+1 小时前
炫彩活体检测利用手机屏幕投射随机彩色光,通过分析真实皮肤对光的次表面散射、血红蛋白吸收及纹理细节等光学特性,实现1-2秒内无感核验
人工智能·深度学习·人脸识别·炫彩活体检测
沐梯子1 小时前
MOSS-Transcribe-Diarize 和豆包语音识别哪个好?多人会议录音转写的准确率与说话人归属对比
人工智能·语音识别·xcode
小王师傅661 小时前
分类是科学的基础
人工智能·分类
隔振降噪研究员1 小时前
裁断机一响,楼板为什么会跟着抖?
大数据·linux·数据库·人工智能
东坡肘子1 小时前
快速消失的硬盘容量 -- 肘子的 Swift 周报 #160
人工智能·swiftui·swift
高洁011 小时前
端侧大模型的应用场景与工程实践
人工智能·深度学习·机器学习·transformer·tornado
乐橙开放平台1 小时前
离岗检测怎么实现?通过客流相机或 AI 摄像头识别岗位区域无人状态,再推送告警消息
人工智能·笔记·后端·数码相机·物联网·自动化·音视频
2301_776045231 小时前
山寨币与比特币之间的关系
大数据·人工智能·区块链