有一类问题一条直线分不开,比如异或:(0,1) 和 (1,0) 是一类。 直线模型训练两万轮,四个输出全挤在 0.5 附近——不是没训好,是表达不出来。 加两个隐藏神经元,同样两万轮,四个全对(0.008 / 0.993 / 0.991 / 0.007)。
上一篇讲过拟合。这一篇讲一种「怎么调都学不会」的情况,以及它的解法。 教材 4.5 节:深度学习——引发机器学习热潮的火种。 这一节篇幅不长,但它解释了为什么 2012 年之后机器学习突然变得无所不能。 答案不是算法变聪明了,是层数加上了、数据和算力跟上了。
一、先看一个分不开的问题
四个输入组合,对应四个输出:00 得 0,01 得 1,10 得 1,11 得 0。 这个规律叫异或。它的特点是:单独看任何一个输入,都决定不了输出。 输入 a 是 1 的时候,输出可能是 1(b=0)也可能是 0(b=1)。必须两个一起看。
画在平面上:(0,1) 和 (1,0) 归一类,(0,0) 和 (1,1) 归另一类。你画不出任何一条直线把它们分开。 这就是「线性不可分」。上一节那个 y = wx + b 的模型,本质就是画一条直线,所以它必然失败。
二、直线模型试一次
演示真的训了两万轮。四个输出是 0.533、0.500、0.467、0.434。 全挤在 0.5 附近,等于什么都没判断出来。 这不是训练不充分,是模型的表达能力不够。 再训两百万轮也是这个结果。
这种情况最容易被误判成「数据不够」或者「学习率不对」,然后白折腾很久。 判断方法:如果模型在训练集上怎么调都上不去,那就是表达能力的问题,不是训练的问题。
三、加两个隐藏神经元
在输入和输出之间插一层,放两个神经元。每个神经元自己做一次加权求和再过一次 S 形函数。 结构变成:两个输入 → 两个隐藏 → 一个输出。参数从 3 个变成 9 个。 同样训两万轮,结果 0.008、0.993、0.991、0.007。四个全对。
为什么加了就行?因为隐藏层先把输入换了个表示。 原来四个点挤在一个平面上,一条线分不开。 隐藏层把它们映射到一个新空间,在那个空间里,两类点就分得开了。 这件事可以直观看:两个隐藏神经元相当于画了两条线, 输出层再对这两条线的结果做一次组合——两条线能围出一块区域,区域就能分开异或的两类。
四、「深」是什么意思,代价是什么
一层隐藏已经能分开异或。要认人脸、听懂一句话,就得几十上百层。 「深」就是层数多。 每多一层,能表达的形状复杂一档,参数也多一批。 代价有三个:需要更多数据来喂(参数多容易过拟合)、需要更多算力来训、更难解释为什么这么判断。
第三个代价最容易被忽略。一个线性模型你能指着 w=3 说「x 每多 1,y 多 3」; 一个一百层的网络,你说不清它为什么把这张图判成猫。 所以教材说 4.5 节是「火种」,不是「终点」。 深度学习打开了能力上限,但也带来了新的问题:可解释性、数据依赖、算力成本。 这也是为什么现在很多场景仍然用简单模型——能解释、够用、便宜,比「先进」重要。
五、动手 Demo:异或:直线学不会,加两个神经元就会了
先让一个线性模型学异或,训两万轮,看它卡在哪。 再加一层(两个隐藏神经元),同样训两万轮。 手写前向和反向传播,不用任何框架。
def sig(v):
return 1.0 / (1.0 + math.exp(-v))
# 一层隐藏:2 个隐藏神经元 + 1 个输出
for _ in range(20000):
for (a, c), y in zip(X, Y):
h = [sig(W1[j][0]*a + W1[j][1]*c + B1[j]) for j in range(2)]
o = sig(sum(W2[j]*h[j] for j in range(2)) + B2)
do = (o - y) * o * (1 - o) # 输出层误差
for j in range(2):
W2[j] -= lr * do * h[j]
dh = do * W2[j] * h[j] * (1 - h[j]) # 往隐藏层回传
W1[j][0] -= lr * dh * a
W1[j][1] -= lr * dh * c
B2 -= lr * do
实跑输出:
--------------------------------------------------------------------
深度学习:加一层隐藏层,异或就学会了
--------------------------------------------------------------------
① 先看这个问题的形状
--------------------------------------------------------------------
| 输入 a | 输入 b | 想要输出 |
|:---:|:---:|:---:|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
这叫异或。
同样输入 1 的时候,输出要看另一个输入是几——这种「要看两个输入配合」的规律,一条直线分不开。
画在平面上:把 (0,1) 和 (1,0) 归一类、(0,0) 和 (1,1) 归另一类,你画不出任何一条直线把它们分开。
② 直线模型试一次,学不会
--------------------------------------------------------------------
训练两万轮之后:
(0,0) 想要 0,输出 0.533
(0,1) 想要 1,输出 0.500
(1,0) 想要 1,输出 0.467
(1,1) 想要 0,输出 0.434
四个输出全挤在 0.5 附近。它不是没训练好,是这个形状根本表达不出来。
③ 加两个隐藏神经元
--------------------------------------------------------------------
也是两万轮:
(0,0) 想要 0,输出 0.008 对
(0,1) 想要 1,输出 0.993 对
(1,0) 想要 1,输出 0.991 对
(1,1) 想要 0,输出 0.007 对
四个全对。
多出来的那两个神经元,干的活就是先把输入换个表示——原来分不开的两类,换到新坐标里就分得开了。
④ 深度学习的「深」是什么意思
--------------------------------------------------------------------
| 层数 | 能表达的形状 | 参数个数(本例) |
|---|---|---:|
| 0 层(直接线性) | 一条直线 | 3 |
| 1 层隐藏(2 个神经元) | 能拐弯的分界 | 9 |
| 多层 | 任意复杂形状 | 成百上千 |
每加一层,能表达的形状复杂一点,参数也多一批。所谓「深」,就是层数多;「学习」,还是调那些参数。
代价也在这儿:参数一多,就需要更多数据来喂,更多算力来调。
这就是教材 4.5 节说的「引发机器学习热潮」——不是算法突然变聪明了,是数据和算力终于跟得上了。
第一张表就是异或的真值表,四行。看着简单,但它是深度学习史上最著名的小例子。 第二段那四个 0.5 附近的输出,是「表达能力不足」的标准表现。记住这个症状:训练集上怎么调都上不去。 第三段加了两个神经元就全对。注意参数只从 3 个变成 9 个——不是靠更多参数硬堆出来的,是靠结构变了。 最后一段那段反向传播代码是整个深度学习的骨架:算输出误差,按权重回传给隐藏层,各自调整。 所有框架内部都长这样。
30 秒实验
- 把隐藏神经元从 2 个改成 1 个,看还能不能学会异或。
- 把学习率 lr 从 1.0 改成 0.1,看要多少轮才能学会。
- 把隐藏层加到 3 层(每层 2 个),看会不会更快收敛——或者反而更难训。
常见误区
- 「模型学不会就是数据不够。」先看是不是表达不了。线性模型永远学不会异或,给多少数据都没用。
- 「层数越多越好。」层多会带来过拟合、训练困难、不可解释。够用就行。
- 「深度学习取代了传统方法。 」在很多结构化数据的场景里,简单的线性模型或树模型效果一样好,还更便宜。
自测三问(点开对答案)
1. 为什么一条直线分不开异或的两类? —— 因为 (0,1) 和 (1,0) 在对角线上,(0,0) 和 (1,1) 在另一条对角线上。 任何一条直线都无法把这两组点分到两侧。 2. 加了隐藏层之后,为什么就能分开了? —— 隐藏层把输入映射到了一个新的空间。在原空间里线性不可分的问题,在新空间里变得线性可分了。 3. 「深」的代价是什么? —— 三个:需要更多数据、需要更多算力、更难解释。 参数多容易过拟合,训练贵,而且说不清它为什么这么判断。下一篇讲工具与分工:为什么建模只占工程师两成的时间。
- ☐ (本篇完,下一篇 4.4 工具与分工)