我最早以为 DropPath 就是 Dropout 的换皮版本——反正都是随机丢掉一些东西,让模型别过拟合。直到我在 Vision Transformer 的训练代码里看到 drop_path_rate 这个参数,随手设成 0.5,结果模型直接不收敛了。我才意识到,这东西没那么简单。

后来我把 Dropout、DropConnect、DropPath 三个串起来看,才发现它们其实是同一个思路在不同粒度上的延展。这篇文章就把这件事讲透。
Dropout:把神经元打包放假
Dropout 是 2014 年 Srivastava 等人的论文提出的。方法简单得有点粗暴:训练时,每个神经元以一个概率 p 被暂时“放假”,不参与前向和反向传播。你看着一批数据喂进去,网络里的某些神经元直接沉默,梯度也只走那些活跃的路径。
它的核心思想:不能让网络过度依赖某个神经元。就像团队里不能什么事都靠一个人,偶尔把这个人调走,其他人也得能顶上。Dropout 强行把模型变成一个“多个子网络的集成”,最后的效果比单个完整网络鲁棒得多。
DropConnect:不在神经元下手,在连接上下手
DropConnect 比 Dropout 更早,2013 年 Wan 等人的论文就提出来了。它丢弃的不是神经元,而是神经元之间的连接权重。训练时,矩阵里的每个权重都有概率被置零,相当于把神经元之间的某根线剪断。
神经元还在,但它接收到的输入残缺不全。打个比方:Dropout 是把一个人整天不上班,而 DropConnect 是把他桌上的电话线随机拔掉几根——人还在,但沟通永远是不完整的。
一开始我以为 DropConnect 只是 Dropout 的细节升级,后来发现它其实更暴力:同一个小 batch 里,每个样本看到的连接稀疏模式都不一样。这导致它的正则化强度通常比 Dropout 大,但实现起来也更麻烦,GPU 运算效率不高。所以它在实际工程中反而没有 Dropout 普及。
DropPath:直接跳过一整条路
DropPath 来自 Deep Networks with Stochastic Depth,原名叫 Stochastic Depth(随机深度)。它针对的是残差网络。一个残差块通常长这样:
y = x + f(x)
DropPath 的做法是:训练时,整个 f(x) 这个残差分支以一个概率被跳过,直接让 y = x。比如一个 ResNet 有 100 层,训练时某些层可能整层被跳过,网络瞬间变成一个只有 80 层的瘦子。
对于 Vision Transformer 来说,每个 Transformer Block 就是一个残差块。DropPath 就是随机让某个 Block 计算出来,直接旁路掉。你在 timm 或 DeiT 代码里看到的 drop_path_rate,就是每个 Block 被跳过的概率,通常从浅层到深层逐渐增加。
一张表看清三者
| 方法 | 丢弃单位 | 训练时 | 推理时 |
|---|---|---|---|
| Dropout | 神经元 | 随机置零神经元输出 | 乘概率 p 做缩放 |
| DropConnect | 权重连接 | 随机置零权重 | 用权重矩阵的期望 |
| DropPath | 残差分支(Block) | 随机跳过整个分支 | 保留所有 Block |
注意最后一行:推理时 DropPath 不执行任何跳过,所有 Block 都激活。这也意味着它只在训练时增加随机性,如果测试时你想用 DropPath 来做模型集成,你得自己做多次推理,这不在原始设计里。
为什么 DropPath 在 ViT 里比 Dropout 好用?
我在训练 ViT 时,试过把 Dropout 加进去,效果一般,经常丢到 0.1 以上就训不动了。但 DropPath 可以放心地设到 0.2、0.3,还能稳定收敛,而且明显缓解过拟合。这是为什么?
两个原因。
- Transformer Block 内部结构对随机噪声敏感。如果你在 Attention 或 MLP 模块里用 Dropout,前向传播被扰动得厉害,梯度也容易变得不稳定。但 DropPath 是整体跳过这个 Block,输入直接走恒等映射,梯度可以顺畅穿过去,稳定性好得多。
- DropPath 相当于在训练一系列深度不同的子网络。如果某个 Block 被跳过,剩下的网络仍然能完成前向计算,因为残差结构保证信息不丢失。这等于做了一个深度的隐式集成,增强了泛化能力。
ViT 的原始论文 Dosovitskiy et al. 2021 里就提到,训练时在 Transformer Block 上使用了 DropPath。Swin Transformer、DeiT 这些后续工作,也几乎都把 DropPath 当作默认正则化手段。可以说,在视觉 Transformer 的实践中,DropPath 就是标准的正则化选择。
我最早搞错的一个点
我一直以为 DropPath 和 Dropout 在实现上是同一类:都是 forward 时候随机置零,唯一区别是作用对象不同。直到我自己写了一遍 DropPath 的代码,才发现一个隐藏细节——DropPath 不是直接置零,而是乘了一个随机的 0/1 掩码,然后在训练时对保留下来的分支进行缩放,使得期望一致。
但更重要的是,它的反向传播行为和 Dropout 有本质差异:Dropout 让某些神经元的梯度为 0,而 DropPath 让某个 Block 的 gradient 完全不影响上游参数——因为那个 Block 根本没有被计算。这意味着,在训练的不同 step 里,你的模型结构都在变,而不是参数在变。这让我对“随机深度”这个名字有了新的理解:它随机的是网络深度,而不是单个节点。
常见误区澄清
DropPath 和 Dropout 可以同时用吗?
可以。ViT 里两个都可以加,但要控制总正则强度。一般 DropPath 对效率提升更大,Dropout 可以放在最后一层或 Patch Embedding 之后。注意别叠加太多,模型容易欠拟合。
drop_path_rate 怎么设置?
通常从 0 到 0.5 之间。timm 里的实现是线性递增,每个 Block 的概率为 drop_path_rate * index / (num_blocks - 1)。深层的 Block 更容易被跳过,因为深层更容易过拟合。
推理时能用 DropPath 吗?
原始设计不允许。推理时你必须把 DropPath 关闭,否则结果随机,且性能下降。如果你想在推理时保持训练时的随机性来做测试时增强,得自己做多次前向平均,这不是常规操作。
它到底在解决什么问题?
回到根本:Dropout、DropConnect、DropPath 都是抑制过拟合的。区别在于,它们假设的冗余性在不同层级。全连接层里神经元冗余,所以丢神经元有效。卷积层或 Attention 层的通道也有冗余,但权重连接太多,丢连接不划算。到了残差结构里,层与层之间存在明显的冗余——很多 Block 对最终结果的贡献是相似的,随机抽走几个,剩下的还能顶上。
ViT 特别吃这一套,是因为 ViT 的 Block 数量多(通常是 12 或 24 层),而且每一层的功能分化没有 CNN 那么明显。很多研究表明,ViT 的深层特征存在高度相似性。DropPath 就是在这种冗余性上做文章,训练一个更鲁棒的集成。
换句话说,DropPath 不是万能的。它在深网络里是降维打击,在浅网络里就是自杀式袭击。下次你在代码里看到 drop_path_rate,先数一下你的 Block 有几层,再决定要不要开火。
原创文章,作者:guanweilu,如若转载,请注明出处:https://guanweilu.cn/article/481.html