《动手学深度学习》暂退法(Dropout)完全指南:从过拟合原理到四种框架实战
发布时间:2026/10/2 17:31:43来源:尧图网络
人工智能深度学习机器学习教程【免费下载链接】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%——这就是极端过拟合。深度网络泛化性质的数学基础至今仍是悬而未决的研究问题但实践工具如暂退法已被证明能有效改善深层网络的泛化性。扰动的稳健性暂退法的思想源头什么样的模型才算好经典泛化理论认为为缩小训练与测试性能差距应追求简单模型简单性可以体现为维度小见模型选择中对线性模型单项式基函数的讨论也可以体现为参数范数小权重衰减。另一个重要视角是平滑性函数不应对其输入的微小变化敏感——例如给图像像素添加随机噪声分类结果应基本不变。这条思路有清晰的学术脉络1995 年Christopher Bishop证明了带输入噪声的训练等价于 Tikhonov 正则化论文见 d2l.bib 中的Bishop.1995条目在数学上建立了函数光滑与对输入随机噪声具有适应性之间的联系。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.mdfrom 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( shapetf.shape(X), minval0, maxval1) 1 - dropout return tf.cast(mask, dtypetf.float32) * X / (1.0 - dropout)PaddlePaddle 实现import warnings warnings.filterwarnings(actionignore) import paddle from paddle import nn import random warnings.filterwarnings(ignore, categoryDeprecationWarning) 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, dtypetorch.float32).reshape((2, 8)) print(X) print(dropout_layer(X, 0.)) print(dropout_layer(X, 0.5)) print(dropout_layer(X, 1.))dropout0输出与X完全一致dropout0.5约一半元素被置零幸存元素被放大到原来的 2 倍除以 0.5dropout1输出全零。定义模型参数与模型结构与本书其他章节一致这里使用 Fashion-MNIST 数据集加载实现见 d2l/torch.py 中的load_data_fashion_mnist。模型为具有两个隐藏层的多层感知机每层 256 个单元输入 78428×28 像素展平、输出 10 类。从零实现手动管理参数MXNet需要显式创建并附加梯度num_inputs, num_outputs, num_hiddens1, num_hiddens2 784, 10, 256, 256 W1 np.random.normal(scale0.01, size(num_inputs, num_hiddens1)) b1 np.zeros(num_hiddens1) W2 np.random.normal(scale0.01, size(num_hiddens1, num_hiddens2)) b2 np.zeros(num_hiddens2) W3 np.random.normal(scale0.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) b3TensorFlow 实现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, activationrelu) self.hidden2 tf.keras.layers.Dense(num_hiddens2, activationrelu) self.output_layer tf.keras.layers.Dense(num_outputs) def call(self, inputs, trainingNone): 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(reductionnone) train_iter, test_iter d2l.load_data_fashion_mnist(batch_size) trainer torch.optim.SGD(net.parameters(), lrlr) d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)MXNetloss gluon.loss.SoftmaxCrossEntropyLoss()优化器为d2l.sgd(params, lr, batch_size)TensorFlowloss tf.keras.losses.SparseCategoricalCrossentropy(from_logitsTrue)优化器为tf.keras.optimizers.SGD(learning_ratelr)注意 Keras 的SparseCategoricalCrossentropy接收参数顺序为(y, y_hat)见 d2l/tensorflow.py 中对内置损失的适配PaddlePaddleloss nn.CrossEntropyLoss(reductionnone)优化器为paddle.optimizer.SGD(learning_ratelr, parametersnet.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, std0.01) net.apply(init_weights);MXNet 简洁实现net nn.Sequential() net.add(nn.Dense(256, activationrelu), # 在第一个全连接层之后添加一个dropout层 nn.Dropout(dropout1), nn.Dense(256, activationrelu), # 在第二个全连接层之后添加一个dropout层 nn.Dropout(dropout2), nn.Dense(10)) net.initialize(init.Normal(sigma0.01))TensorFlow 简洁实现net tf.keras.models.Sequential([ tf.keras.layers.Flatten(), tf.keras.layers.Dense(256, activationtf.nn.relu), # 在第一个全连接层之后添加一个dropout层 tf.keras.layers.Dropout(dropout1), tf.keras.layers.Dense(256, activationtf.nn.relu), # 在第二个全连接层之后添加一个dropout层 tf.keras.layers.Dropout(dropout2), tf.keras.layers.Dense(10), ])PaddlePaddle 简洁实现注意其初始化方式通过paddle.ParamAttr(initializerpaddle.nn.initializer.Normal(std0.01))为每个Linear层指定正态初始化权重weight_attr paddle.ParamAttr(initializerpaddle.nn.initializer.Normal(std0.01)) net nn.Sequential(nn.Flatten(), nn.Linear(784, 256, weight_attrweight_attr), nn.ReLU(), # 在第一个全连接层之后添加一个dropout层 nn.Dropout(dropout1), nn.Linear(256, 256, weight_attrweight_attr), nn.ReLU(), # 在第二个全连接层之后添加一个dropout层 nn.Dropout(dropout2), nn.Linear(256, 10, weight_attrweight_attr))高层 API 版本省去了手动判断训练模式的代码——Dropout层会依据框架的训练/评估模式自动决定是否丢弃。随后沿用相同的训练代码d2l.train_ch3即可完成训练与测试。值得一提的是暂退法并非只出现在本章从 d2l/torch.py 的源码可以看到它作为标准化组件被广泛复用于注意力打分函数AdditiveAttention、DotProductAttention、多头注意力MultiHeadAttention、Transformer 的位置编码与AddNorm残差结构、乃至 BERT 编码器中——理解本节从零实现等于掌握了这些后续章节中Dropout层的全部语义。小结暂退法在前向传播过程中计算每一内部层的同时丢弃一些神经元。暂退法可以避免过拟合它通常与控制权重向量的维数和大小如权重衰减结合使用。暂退法将活性值 $h$ 替换为具有期望值 $h$ 的随机变量除以 $1-p$ 实现无偏。暂退法仅在训练期间使用测试时通常直接透传。延伸练习原文提供了 7 道练习可作为读者验证理解与动手实验的路线图更改第一层和第二层的暂退概率会发生什么若交换两层如第一层 0.5、第二层 0.2呢设计实验定量描述结果并总结定性结论。增加训练轮数比较使用与不使用暂退法的结果。应用/不应用暂退法时每个隐藏层激活值的方差各是多少绘制方差随时间变化的曲线。为什么测试时通常不使用暂退法以本节模型为例比较暂退法与权重衰减见 chapter_multilayer-perceptrons/weight-decay.md的效果两者同时使用时结果是累加、收益递减还是相互抵消如果将暂退法应用到权重矩阵的各个权重而非激活值会发生什么发明一种不同于标准暂退法的每层随机噪声注入技术尝试在 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点击查看免费下载相关推荐《动手学深度学习》暂退法Dropout详解从过拟合原理到多框架实战实现《动手学深度学习》暂退法Dropout详解从过拟合原理到多框架实战实现 暂退法Dropout是深度学习中应用最广泛的正则化技术之一本文基于《动手学深人工智能深度学习机器学习教程动手学深度学习RMSProp 算法原理与四框架实战指南动手学深度学习RMSProp 算法原理与四框架实战指南 导读 RMSProp 算法是《动手学深度学习》d2l zh优化算法章节中承上启下的关键一节它用“人工智能深度学习机器学习教程《动手学深度学习》之汇聚层池化层从原理到四大框架实战《动手学深度学习》之汇聚层池化层从原理到四大框架实战 汇聚层Pooling Layer也译作池化层是卷积神经网络中与卷积层并肩的基石组件。本篇基于《人工智能深度学习机器学习教程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网站建设高端定制企业官网