news 2026/10/2 17:31:42

《动手学深度学习》暂退法(Dropout)完全指南:从过拟合原理到四种框架实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
《动手学深度学习》暂退法(Dropout)完全指南:从过拟合原理到四种框架实战
  • 人工智能
  • 深度学习
  • 机器学习
  • 教程

【免费下载链接】d2l-zh

《动手学深度学习》:面向中文读者、能运行、可讨论。中英文版被70多个国家的500多所大学用于教学。

项目地址:https://gitcode.com/GitHub_Trending/d2/d2l-zh
点击查看免费下载

暂退法(Dropout)是深度学习中最常用的正则化技术之一,本文以《动手学深度学习》中文版"多层感知机"一章中的暂退法章节(chapter_multilayer-perceptrons/dropout.md)为主体,系统讲解其背后的过拟合动机、无偏噪声注入的数学原理,并给出 MXNet、PyTorch、TensorFlow、PaddlePaddle 四种框架下从零实现与高层 API 简洁实现的完整可运行代码。读完本文,你将能理解暂退法的期望保持特性、掌握dropout_layer的核心实现逻辑,并能在 Fashion-MNIST 数据集上独立训练出带暂退正则化的多层感知机。

从过拟合重新审视泛化难题

在讨论暂退法之前,先回到一个基本问题:为什么深度模型需要正则化?

在 chapter_multilayer-perceptrons/weight-decay.md 中,我们已通过惩罚权重的 $L_2$ 范数来正则化统计模型。从概率角度看,这相当于假设权重先验来自均值为 0 的高斯分布;更直观地说,它鼓励模型把权重分散到更多特征上,而不是过度依赖少数潜在虚假的关联。

偏差-方差权衡(bias-variance tradeoff)是理解这一问题的核心框架:

  • 线性模型:偏差高(只能表示一小类函数),但方差低(在不同随机数据样本上结果相似)。代价是无法刻画特征之间的交互作用——对每个特征,线性模型只能指定正或负的权重,忽略其他特征的上下文。
  • 深度神经网络:处于偏差-方差谱的另一端,能学习特征组合的交互关系(例如"尼日利亚"和"西联汇款"同时出现在邮件中才是垃圾邮件的强信号,单独出现则不是)。代价是即使在样本远多于特征的情况下,也可能严重过拟合。

一个著名的实证来自 2017 年:研究人员在随机标记的图像上训练深度网络,随机梯度下降仍能完美标记训练集中的每一幅图像。若标签在 10 个类别上均匀随机分配,任何分类器在留出数据上都不可能超过 10% 的精度,而训练集上达到 100%,泛化差距高达 90%——这就是极端过拟合。深度网络泛化性质的数学基础至今仍是悬而未决的研究问题,但实践工具(如暂退法)已被证明能有效改善深层网络的泛化性。

扰动的稳健性:暂退法的思想源头

什么样的模型才算"好"?经典泛化理论认为,为缩小训练与测试性能差距,应追求简单模型:简单性可以体现为维度小(见模型选择中对线性模型单项式基函数的讨论),也可以体现为参数范数小(权重衰减)。另一个重要视角是平滑性:函数不应对其输入的微小变化敏感——例如给图像像素添加随机噪声,分类结果应基本不变。

这条思路有清晰的学术脉络:

  1. 1995 年,Christopher Bishop证明了带输入噪声的训练等价于 Tikhonov 正则化(论文见 d2l.bib 中的Bishop.1995条目),在数学上建立了"函数光滑"与"对输入随机噪声具有适应性"之间的联系。
  2. 2014 年,Srivastava 等人(Srivastava.Hinton.Krizhevsky.ea.2014)将这一想法推广到网络的内部层:在训练过程中,计算后续层之前向每一层注入噪声,从而增强输入-输出映射的平滑性。

这就是暂退法(dropout):前向传播时,在计算每一内部层的同时"丢弃"(drop out)一些神经元——每次训练迭代中,在计算下一层之前将当前层中的一部分节点置零。原始论文还给出了一个有趣的生物学类比:神经网络过拟合源于每一层依赖前一层特定激活模式(称为共适应性,co-adaptation),暂退法破坏这种共适应性,正如有性生殖破坏共适应的基因。

无偏注入:期望保持的数学设计

注入噪声的关键挑战是以无偏(unbiased)方式进行,使固定其他层时,每一层的期望值等于无噪声时的值。Bishop 的做法是对线性模型输入 $\mathbf{x}$ 添加高斯噪声 $\epsilon \sim \mathcal{N}(0,\sigma^2)$,得到扰动点 $\mathbf{x}' = \mathbf{x} + \epsilon$,期望满足 $E[\mathbf{x}'] = \mathbf{x}$。

标准暂退法通过按保留节点分数归一化来消除每层的偏差:设暂退概率为 $p$,每个中间活性值 $h$ 被随机变量 $h'$ 替换:

$$ h' = \begin{cases} 0 & \text{ 概率为 } p \ \frac{h}{1-p} & \text{ 其他情况} \end{cases} $$

根据设计,期望保持不变,即 $E[h'] = h$。这一"丢弃后除以 $1-p$ 放大"的缩放,正是下面所有实现代码的核心。

实践中的暂退法

回想多层感知机(含 1 个隐藏层、5 个隐藏单元,图示见 img/dropout2.svg):以概率 $p$ 将隐藏单元置零后,结果等价于一个只包含原始神经元子集的网络。如图中删除了 $h_2$ 和 $h_5$,输出的计算不再依赖二者,反向传播时它们各自的梯度也消失——输出层因此不能过度依赖 $h_1, \ldots, h_5$ 中的任何一个元素。

测试时通常不使用暂退法:给定训练好的模型和新样本,不丢弃任何节点,因此也无需归一化。例外情况是部分研究者会在测试时使用暂退法来估计预测的"不确定性"——如果多种不同暂退掩码下的预测结果一致,可以认为网络发挥更稳定。

从零实现:单层暂退函数

实现单层暂退法,需要从均匀分布 $U[0, 1]$ 中抽取与层维度数量相同的样本,保留对应样本值大于 $p$ 的节点,丢弃其余节点。四种框架的dropout_layer实现如下,核心都是"生成掩码 + 幸存者除以1.0 - dropout重缩放":

MXNet 实现(完整代码见 chapter_multilayer-perceptrons/dropout.md):

from d2l import mxnet as d2l from mxnet import autograd, gluon, init, np, npx from mxnet.gluon import nn npx.set_np() def dropout_layer(X, dropout): assert 0 <= dropout <= 1 # 在本情况中,所有元素都被丢弃 if dropout == 1: return np.zeros_like(X) # 在本情况中,所有元素都被保留 if dropout == 0: return X mask = np.random.uniform(0, 1, X.shape) > dropout return mask.astype(np.float32) * X / (1.0 - dropout)

PyTorch 实现:

from d2l import torch as d2l import torch from torch import nn 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)

TensorFlow 实现:

from d2l import tensorflow as d2l import tensorflow as tf def dropout_layer(X, dropout): assert 0 <= dropout <= 1 # 在本情况中,所有元素都被丢弃 if dropout == 1: return tf.zeros_like(X) # 在本情况中,所有元素都被保留 if dropout == 0: return X mask = tf.random.uniform( shape=tf.shape(X), minval=0, maxval=1) < 1 - dropout return tf.cast(mask, dtype=tf.float32) * X / (1.0 - dropout)

PaddlePaddle 实现:

import warnings warnings.filterwarnings(action='ignore') import paddle from paddle import nn import random warnings.filterwarnings("ignore", category=DeprecationWarning) from d2l import paddle as d2l def dropout_layer(X, dropout): assert 0 <= dropout <= 1 # 在本情况中,所有元素都被丢弃。 if dropout == 1: return paddle.zeros_like(X) # 在本情况中,所有元素都被保留。 if dropout == 0: return X mask = (paddle.to_tensor(paddle.uniform(X.shape)) > dropout).astype('float32') return mask * X / (1.0 - dropout)

几个值得注意的实现细节:

  • assert 0 <= dropout <= 1在进入时校验暂退概率的合法取值范围;
  • 边界情况dropout == 1时返回全零张量(全部丢弃),dropout == 0时原样返回(全部保留),避免出现除零;
  • 掩码从均匀分布采样并与阈值比较得到布尔张量,再转为float32参与乘法运算;
  • 每次前向传播都重新采样掩码,因此暂退是逐迭代随机的。

测试 dropout_layer 函数

将输入X(形状为 2×8 的arange(16)张量)分别以暂退概率 0、0.5、1 通过暂退操作(以 PyTorch 为例):

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.))
  • dropout=0:输出与X完全一致;
  • dropout=0.5:约一半元素被置零,幸存元素被放大到原来的 2 倍(除以 0.5);
  • dropout=1:输出全零。

定义模型参数与模型结构

与本书其他章节一致,这里使用 Fashion-MNIST 数据集(加载实现见 d2l/torch.py 中的load_data_fashion_mnist)。模型为具有两个隐藏层的多层感知机,每层 256 个单元,输入 784(28×28 像素展平)、输出 10 类。

从零实现:手动管理参数

MXNet需要显式创建并附加梯度:

num_inputs, num_outputs, num_hiddens1, num_hiddens2 = 784, 10, 256, 256 W1 = np.random.normal(scale=0.01, size=(num_inputs, num_hiddens1)) b1 = np.zeros(num_hiddens1) W2 = np.random.normal(scale=0.01, size=(num_hiddens1, num_hiddens2)) b2 = np.zeros(num_hiddens2) W3 = np.random.normal(scale=0.01, size=(num_hiddens2, num_outputs)) b3 = np.zeros(num_outputs) params = [W1, b1, W2, b2, W3, b3] for param in params: param.attach_grad()

PyTorch / PaddlePaddle只需声明维度常量(参数由框架自动管理),TensorFlow同理。

从零实现:将暂退法嵌入前向传播

暂退法应用于每个隐藏层的输出(激活函数之后),且可以为每一层单独设置暂退概率。常见技巧是靠近输入层设置较低的暂退概率——下面模型将第一个隐藏层设为 0.2、第二个设为 0.5,并且暂退法只在训练期间生效。

PyTorch 实现(通过自定义Net类和is_training开关控制):

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)

MXNet 实现(利用autograd.is_training()判断训练模式):

dropout1, dropout2 = 0.2, 0.5 def net(X): X = X.reshape(-1, num_inputs) H1 = npx.relu(np.dot(X, W1) + b1) # 只有在训练模型时才使用dropout if autograd.is_training(): # 在第一个全连接层之后添加一个dropout层 H1 = dropout_layer(H1, dropout1) H2 = npx.relu(np.dot(H1, W2) + b2) if autograd.is_training(): # 在第二个全连接层之后添加一个dropout层 H2 = dropout_layer(H2, dropout2) return np.dot(H2, W3) + b3

TensorFlow 实现(call方法接收training标志):

dropout1, dropout2 = 0.2, 0.5 class Net(tf.keras.Model): def __init__(self, num_outputs, num_hiddens1, num_hiddens2): super().__init__() self.input_layer = tf.keras.layers.Flatten() self.hidden1 = tf.keras.layers.Dense(num_hiddens1, activation='relu') self.hidden2 = tf.keras.layers.Dense(num_hiddens2, activation='relu') self.output_layer = tf.keras.layers.Dense(num_outputs) def call(self, inputs, training=None): x = self.input_layer(inputs) x = self.hidden1(x) # 只有在训练模型时才使用dropout if training: # 在第一个全连接层之后添加一个dropout层 x = dropout_layer(x, dropout1) x = self.hidden2(x) if training: # 在第二个全连接层之后添加一个dropout层 x = dropout_layer(x, dropout2) x = self.output_layer(x) return x net = Net(num_outputs, num_hiddens1, num_hiddens2)

PaddlePaddle 实现与 PyTorch 结构一致(继承nn.Layer,使用nn.Linear与nn.ReLU)。

训练与测试

训练流程与此前多层感知机的训练完全一致(d2l/torch.py 中train_ch3会调用train_epoch_ch3逐周期更新参数、再以evaluate_accuracy在测试集上评估精度,并断言训练损失小于 0.5、训练与测试精度均高于 0.7)。以 PyTorch 为例:

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)
  • MXNet:loss = gluon.loss.SoftmaxCrossEntropyLoss(),优化器为d2l.sgd(params, lr, batch_size);
  • TensorFlow:loss = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),优化器为tf.keras.optimizers.SGD(learning_rate=lr)(注意 Keras 的SparseCategoricalCrossentropy接收参数顺序为(y, y_hat),见 d2l/tensorflow.py 中对内置损失的适配);
  • PaddlePaddle:loss = nn.CrossEntropyLoss(reduction='none'),优化器为paddle.optimizer.SGD(learning_rate=lr, parameters=net.parameters())。

数据加载统一通过 d2l/torch.py 中的load_data_fashion_mnist(batch_size):PyTorch 侧由torchvision.datasets.FashionMNIST下载并以DataLoader提供批次(默认 4 个工作进程读取数据),TensorFlow 侧直接使用tf.keras.datasets.fashion_mnist.load_data()并做归一化处理。

简洁实现:高层 API 一行接入

对于深度学习框架的高层 API,只需在每个全连接层之后添加一个Dropout层,把暂退概率作为构造函数的唯一参数。训练时该层按指定概率随机丢弃上一层的输出(即下一层的输入),测试时仅透传数据。

PyTorch 简洁实现:

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

MXNet 简洁实现:

net = nn.Sequential() net.add(nn.Dense(256, activation="relu"), # 在第一个全连接层之后添加一个dropout层 nn.Dropout(dropout1), nn.Dense(256, activation="relu"), # 在第二个全连接层之后添加一个dropout层 nn.Dropout(dropout2), nn.Dense(10)) net.initialize(init.Normal(sigma=0.01))

TensorFlow 简洁实现:

net = tf.keras.models.Sequential([ tf.keras.layers.Flatten(), tf.keras.layers.Dense(256, activation=tf.nn.relu), # 在第一个全连接层之后添加一个dropout层 tf.keras.layers.Dropout(dropout1), tf.keras.layers.Dense(256, activation=tf.nn.relu), # 在第二个全连接层之后添加一个dropout层 tf.keras.layers.Dropout(dropout2), tf.keras.layers.Dense(10), ])

PaddlePaddle 简洁实现(注意其初始化方式:通过paddle.ParamAttr(initializer=paddle.nn.initializer.Normal(std=0.01))为每个Linear层指定正态初始化权重):

weight_attr = paddle.ParamAttr(initializer=paddle.nn.initializer.Normal(std=0.01)) net = nn.Sequential(nn.Flatten(), nn.Linear(784, 256, weight_attr=weight_attr), nn.ReLU(), # 在第一个全连接层之后添加一个dropout层 nn.Dropout(dropout1), nn.Linear(256, 256, weight_attr=weight_attr), nn.ReLU(), # 在第二个全连接层之后添加一个dropout层 nn.Dropout(dropout2), nn.Linear(256, 10, weight_attr=weight_attr))

高层 API 版本省去了手动判断训练模式的代码——Dropout层会依据框架的训练/评估模式自动决定是否丢弃。随后沿用相同的训练代码(d2l.train_ch3)即可完成训练与测试。

值得一提的是,暂退法并非只出现在本章:从 d2l/torch.py 的源码可以看到,它作为标准化组件被广泛复用于注意力打分函数(AdditiveAttention、DotProductAttention)、多头注意力(MultiHeadAttention)、Transformer 的位置编码与AddNorm残差结构、乃至 BERT 编码器中——理解本节从零实现,等于掌握了这些后续章节中Dropout层的全部语义。

小结

  • 暂退法在前向传播过程中,计算每一内部层的同时丢弃一些神经元。
  • 暂退法可以避免过拟合,它通常与控制权重向量的维数和大小(如权重衰减)结合使用。
  • 暂退法将活性值 $h$ 替换为具有期望值 $h$ 的随机变量(除以 $1-p$ 实现无偏)。
  • 暂退法仅在训练期间使用,测试时通常直接透传。

延伸练习

原文提供了 7 道练习,可作为读者验证理解与动手实验的路线图:

  1. 更改第一层和第二层的暂退概率会发生什么?若交换两层(如第一层 0.5、第二层 0.2)呢?设计实验定量描述结果并总结定性结论。
  2. 增加训练轮数,比较使用与不使用暂退法的结果。
  3. 应用/不应用暂退法时,每个隐藏层激活值的方差各是多少?绘制方差随时间变化的曲线。
  4. 为什么测试时通常不使用暂退法?
  5. 以本节模型为例,比较暂退法与权重衰减(见 chapter_multilayer-perceptrons/weight-decay.md)的效果;两者同时使用时结果是累加、收益递减还是相互抵消?
  6. 如果将暂退法应用到权重矩阵的各个权重而非激活值,会发生什么?
  7. 发明一种不同于标准暂退法的每层随机噪声注入技术,尝试在 Fashion-MNIST(固定架构)上取得优于暂退法的表现。

运行环境提示:以上代码以from d2l import torch as d2l(或 mxnet/tensorflow/paddle)方式导入本书配套工具库,完整工具函数(load_data_fashion_mnist、train_ch3、sgd等)分别定义在 d2l/torch.py、d2l/mxnet.py、d2l/tensorflow.py、d2l/paddle.py 中。安装与运行方式可参考 chapter_installation/index.md 与 chapter_appendix-tools-for-deep-learning/d2l.md,数据集默认下载缓存于../data目录。各框架对应的讨论与答疑入口见原文档末尾的:begin_tab:区块。

  • 人工智能
  • 深度学习
  • 机器学习
  • 教程

【免费下载链接】d2l-zh

《动手学深度学习》:面向中文读者、能运行、可讨论。中英文版被70多个国家的500多所大学用于教学。

项目地址:https://gitcode.com/GitHub_Trending/d2/d2l-zh
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 17:31:41

江西南昌竞天汽车改装实力怎么样

江西南昌竞天汽车改装实力怎么样?江西竞天汽车服务有限公司(简称江西竞天改装)给出了一个清晰的答案&#xff1a; 手机&#xff1a;18679126262 官网地址&#xff1a;http://www.jxjtgz.com/ 作为江西本土全流程自营的高端汽车内饰定制改装源头工厂&#xff0c;专注豪车、超跑…

作者头像 李华
网站建设 2026/10/2 17:28:24

层次infomap

上一个帖子介绍了infomap的基本逻辑infomap-CSDN博客&#xff0c;这个帖子介绍层次infomap。基本的infomap只能得到单层模块&#xff0c;但是现实世界中也存在着“模块套模块”的情况&#xff0c;例如对某一个个人&#xff0c;他可能在xx企业的xx国家的分公司的xx城市分部工作。…

作者头像 李华