DropConnect 把「丢」这件事下沉一个粒度:不丢单元的输出,丢单元之间的连线。 区别可以用「一个单元还剩几条路」来量。 Dropout 保留率 0.7 时,一个单元的 64 条输入要么全在、要么全断; DropConnect 时平均还剩 44.8 条,标准差 3.7。 实测两者分数几乎一样:基线 0.8213,Dropout 0.7 得 0.8600,DropConnect 0.7 得 0.8588。 这份数据太小,分不出高下。 但有一条铁律:测试时必须把 mask 摘掉。 继续随机丢连线,五次跑出 0.85、0.85、0.83、0.81、0.87,平均 0.8422。
上一节的 Dropout 丢单元,这一节把同一件事做到更细的粒度。 两者出自不同的论文(Dropout 是 2012 年,DropConnect 是 2013 年),动机却完全一样: 训练时制造随机残缺。 这一节也顺带说明一件工程上的事:理论上更精细的方法,在具体任务上不一定更好,而且往往更贵。
一、两种丢法,差在一个单元还剩几条路
Dropout 是一次掷骰子决定一个单元的生死;DropConnect 是给每条连线各掷一次。
| 保留率 | Dropout 的单元输入数 | DropConnect 的单元输入数 |
|---|---|---|
| 0.7 | 64 或者 0,全有或全无 | 平均 44.8,标准差 3.7 |
| 0.5 | 64 或者 0,全有或全无 | 平均 32.0,标准差 4.0 |
| Dropout 下,一个单元要么整台机器停,要么满负荷。 | ||
| DropConnect 下,它是带伤工作:少了几根线,但还在出力。 | ||
| 保留率 0.7 时,一个隐单元的 64 根线全断光的概率是 0.3 的 64 次方,小到可以当 0。 | ||
| 所以 DropConnect 的子网络数量多到没法数,但它们长得都很像,不像 Dropout 那样连结构都换掉了。 |
二、实测:谁也没碾压谁
还是 100 张训练图、64 到 64 到 32 到 10,两种保留率各跑一次:
| 做法 | 保留率 0.7 | 保留率 0.5 |
|---|---|---|
| 不用任何丢弃 | 0.8213 | 0.8213 |
| Dropout | 0.8600 | 0.8600 |
| DropConnect | 0.8588 | 0.8762 |
| 两者都能把测试准确率从 0.8213 拉到 0.86 以上,量级差不多。 | ||
| 0.5 那一列 DropConnect 略高,但只差一点,不足以下结论。 | ||
| 论文里 DropConnect 主要在 MNIST、CIFAR 这类大任务上比 Dropout 好一点。 | ||
| 这份数据每个数字只有 10 张图,任务太同质,挤不出差距。 | ||
| 如实记下:这次没测出谁更好。 |
三、代价:多一份同形状的 mask
先看 600 步时两者的表现:
| 做法 | 600 步训练准确率 | 测试准确率 |
|---|---|---|
| Dropout 0.7 | 1.0000 | 0.8187 |
| DropConnect 0.7 | 1.0000 | 0.8163 |
| 两行的训练准确率都到 1.0000,600 步就背完了,看不出收敛快慢。 | ||
| 这次没能测出「DropConnect 更慢」——数据集太小,600 步和 2000 步都能拟合。 | ||
| 但它确实更贵,这是结构决定的。 | ||
| Dropout 的 mask 跟激活值同形状,DropConnect 的 mask 跟权重同形状。 | ||
| 大网络里权重矩阵本来就大,这份开销是实打实的,所以用的人比 Dropout 少得多。 |
四、测试时必须把 mask 摘掉
训练靠随机残缺防过拟合,测试要的是一个确定、可复现的答案。 如果测试时还继续随机丢连线,同一个输入每次结果都不一样:
| 做法 | 5 次结果 | 平均 |
|---|---|---|
| 测试时摘掉 mask | 每次都是 0.8588 | 0.8588 |
| 测试时继续随机丢 | 0.85 0.85 0.83 0.81 0.87 | 0.8422 |
| 五次结果彼此不一样,平均低了不到两个百分点。 | ||
| 差距不算大,因为大部分图本来就分得开,切几根线也还认得出来。 | ||
| 但真正的麻烦不是分数,是「同一个输入两次跑出两个答案」。 | ||
| 这在工程上没法用:日志对不上、复现不了、出了问题查不出来。 | ||
| 所以测试时必须是确定性的,这一条对 Dropout 也一样。 |
五、动手 Demo:两种丢法同台、以及测试时不能继续丢
数据同上一节:400 张训练、800 张测试,训练只取 100 张。
网络 64 到 64 到 32 到 10,学习率 0.1,2000 步。
代码里两处 mask 都要照应:Wms 乘在权重上,Ms 乘在激活上,反传时一处乘梯度、
一处乘权重矩阵。
for i in range(len(Ws) - 1):
if keep < 1.0 and kind == "dropconnect":
mk = (rg.random(Ws[i].shape) < keep) / keep # 丢在权重上
else:
mk = np.ones_like(Ws[i])
Wms.append(mk)
a = np.tanh(As[-1] @ (Ws[i] * mk) + bs[i])
if keep < 1.0 and kind == "dropout":
mh = (rg.random(a.shape) < keep) / keep # 丢在单元上
else:
mh = np.ones_like(a)
Zs.append(a)
Ms.append(mh)
As.append(a * mh)
# 反传时两处 mask 都要带上:一处乘在梯度上,一处乘在权重上
for i in range(len(Ws) - 2, -1, -1):
dz = d * Ms[i] * (1.0 - Zs[i] * Zs[i])
Ws[i] -= lr * ((As[i].T @ dz) * Wms[i])
if i:
d = dz @ (Ws[i] * Wms[i]).T
# 测试时继续随机丢连线,同一个输入会跑出不同答案
vals = []
for _ in range(5):
Ws2 = [(w * ((rg.random(w.shape) < 0.7) / 0.7)) for w in m[0]]
vals.append(acc(Xte, yte, (Ws2, m[1])))
print(vals, np.mean(vals))
实跑输出:
--------------------------------------------------------------------
DropConnect:丢的是连线,不是单元
--------------------------------------------------------------------
[1] 两种丢法丢的东西不一样
Dropout 丢的是单元的输出:一个隐单元被选中,它这一轮就整个不出声
DropConnect 丢的是连线:每个权重各自掷硬币,同一个单元还有一部分输入能进来
区别在「一个单元还剩几条路」:
保留率 Dropout 单元输入数 DropConnect 单元输入数
0.7 64 或者 0(全有或全无) 平均 44.8,标准差 3.7
0.5 64 或者 0(全有或全无) 平均 32.0,标准差 4.0
Dropout 是「整台机器停不停」,DropConnect 是「这台机器上每根线断不断」
保留率 0.7 时,一个隐单元 64 根线全断光的概率是 0.3 的 64 次方,可以当 0
所以 DropConnect 的子网络数量多到没法数,但它们长得都很像,
不像 Dropout 那样连结构都换掉了
[2] 三种做法对比:100 张训练图,64-64-32-10
做法 保留率 0.7 保留率 0.5
不用任何丢弃 0.8213 0.8213
Dropout 0.8600 0.8600
DropConnect 0.8588 0.8762
两者都能把测试准确率从基线拉上来,量级差不多
在这份数据上分不出高下——这个任务太小,看不出 DropConnect 的优势
论文里 DropConnect 主要在 MNIST、CIFAR 这类大任务上比 Dropout 好一点
[3] 代价:DropConnect 要多跑一会儿
做法 600 步训练准确率 测试准确率
Dropout 0.7 1.0000 0.8187
DropConnect 0.7 1.0000 0.8163
这两行训练准确率都到 1.0000,600 步就背完了,看不出收敛快慢
这次没能测出「DropConnect 更慢」——数据集太小,2000 步和 600 步都能拟合
但它确实更贵,这是结构决定的:mask 跟权重同形状,要大一份显存
大网络里这份开销是实打实的,所以用的人比 Dropout 少得多
[4] 测试时必须把 mask 摘掉
训练靠随机残缺来防过拟合,测试要的是一个确定、可复现的答案
如果测试时还继续随机丢连线,同一个输入每次结果都不一样:
做法 5 次测试准确率 平均
测试时摘掉 mask(正确) 每次都是 0.8588 0.8588
测试时继续随机丢(错误) 0.85 0.85 0.83 0.81 0.87 0.8422
继续丢的话,五次结果彼此不一样,平均低了不到两个百分点
差距不算大,因为大部分图本来就分得开,切几根线也还认得出来
但真正的麻烦是「同一个输入,两次跑出两个答案」——这在工程上没法用
同样一份权重,测试时必须是确定性的,这一条对 Dropout 也一样
[5] 小结
Dropout 丢单元,DropConnect 丢连线,目标都是逼网络别把宝押在少数特征上
本质上都是「训练时制造随机残缺,让模型学会在残缺下也能干活」
但在咱们这台笔记本上,两者的分数都在 0.86 附近,谁也没碾压谁
选哪个更多看工程开销,不是看理论上谁更高级
下一节收尾,把这一章的五种办法摆到一起看
第一段那个 Wms 和 Ms 的区分是关键。
哪怕只做 Dropout,Wms 也要填成全 1 的矩阵,不然反传那行 Ws[i] * Wms[i] 会报维度错。
这种「两条路共用一套代码」的写法省事,但一定要两个 mask 都初始化。
第二段里 rg 是循环外面那个随机数生成器。
如果每次循环都新建一个 default_rng(88),五次生成的 mask 会一模一样,五次结果也就完全相同,
看不出「随机」这回事。
这是 numpy 新随机接口最容易踩的一个坑。
第四段那张图表的解读要小心:平均 0.8422 只低了 1.7 个百分点。
如果只看分数,会得出「测试时丢一丢也没事」的错误结论。
真正的问题是不可复现,不是准确率。
30 秒实验
- 把 DropConnect 的保留率压到 0.3 再跑。 每个单元平均只剩 19 条线,分数会明显掉——粒度再细,丢太多也不行。
- 把
keep设成 1.0 跑 DropConnect。 结果应该和「不用任何丢弃」完全一致(0.8213),这是验证代码没写错的最快办法。 - 把网络加宽到 64 到 128 到 64,再看两者差距。 参数一多,DropConnect 的显存优势反过来变成劣势,训练时间会明显拉长。
常见误区
- 「DropConnect 是 Dropout 的升级版,肯定更好。」 实测 0.8588 对 0.8600,基本持平;0.5 保留率下它略高一点。 这是同一族方法的两个粒度,不存在谁替代谁。
- 「测试时保持和训练一致,也开着 mask 更稳。」 五次跑出五个不同结果,平均 0.8422。 更要命的是不可复现:同一个输入两次不同答案,线上出了事根本没法查。
- 「DropConnect 只在全连接层能用。」 它本质是「给权重加 mask」,卷积层也做得到(按卷积核的元素丢)。 只是卷积核本来就共享,丢起来语义没全连接那么直观,所以用得少。
自测三问(点开对答案)
1. **Dropout 和 DropConnect 最本质的区别是什么?** —— **是「丢的东西」的粒度:一个丢单元的输出,一个丢连线的权重。** **前者让整个单元下线,后者让单元带伤工作。** **从「子网络数量」看,后者多得多;从「子网络差异」看,前者大得多。** 2. **为什么 DropConnect 保留率 0.7 时,一个单元几乎不可能全断?** —— **它的 64 条线各自独立地以 0.3 的概率被丢,全断的概率是 0.3 的 64 次方。** **这个数约等于 1 后面跟 33 个零分之一,可以当 0。** 3. **测试时继续随机丢,为什么分数只掉了不到两个点,还是不行?** —— **因为分数不是唯一指标。** **同一个输入两次跑出不同答案,意味着系统不可复现——日志、回归测试、线上排查全部失效。** **工程上「稳定的 0.84」比「忽高忽低的 0.85」有用得多。**这一章的五个手段都讲完了。 最后一节把它们一项项加到同一个模型上,看训练与测试那道沟怎么变——结果有一项是负收益。
- ☐ (本节完,下一节 6.6 小结)