📕 第4章 类神经网络的基础
📄 一句话概括

XOR 是单层感知器的死刑判决书:两类点对角交错,任何一条直线都分不开。这一节的 Demo 里, 单层跑 200 轮,末轮 4 个样本全错。 加一个隐层就不一样了: 2-4-1 的网络跑 4000 轮,损失从 0.8279 降到 0.0010,四个点全对,输出 0.0001、0.9985、 0.9990、0.0015。 隐层做的事是换坐标:把原来的 (x₁, x₂) 揉成一组新数, 在那个空间里 XOR 变得可以用一条直线切开。

📝 本节和前面几节的关系

上一节讲了单层怎么学,这一节讲「一层不够怎么办」。 这一节是第 4 章的核心,也是 1969 到 1986 那十七年空白的答案。 代码比前几节长,但结构只有四步:前向、算损失、反向、更新。这四步第 5、6 章会原样重复。

一、XOR 为什么学不会

XOR 的四个样本是 (0,0)→0、(0,1)→1、(1,0)→1、(1,1)→0。 把前两个和后两个分别标在平面上,它们在对角线上交错。 单层感知器的边界是 w₁x₁ + w₂x₂ + b = 0,一条直线。 直线切一刀只能把平面分成两块,而 XOR 要求「相邻的两块同色、 对角的异色」——这是个棋盘格图案,一刀切不出来。 所以单层训不出 XOR 不是参数没调好。参数空间里根本不存在那组解,搜索再久也是在空集里搜。 这一点 1969 年 Minsky 和 Papert 证明得很清楚。

二、加一层隐层

结构变成 2-4-1:输入 2 维、隐层 4 个神经元、输出 1 个。隐层做的事是把输入映射到新的坐标系。 具体一点:隐层那 4 个单元各自算一个加权和再 tanh,相当于在原平面上画 4 条线, 每条线给出一个「在这一侧有多强」的数。原来的 2 维点,变成了 4 维新空间里的一个点。 在这个新空间里,XOR 的四组数可能就变得线性可分了。 输出层只需要在新空间里切一刀——它切的已经不是原来的平面。 这就是「深度」的全部秘密:不是更深就更聪明,是每一层都把坐标换一次, 换到某个坐标下问题变简单为止。

三、训练的四步

第一步前向:H = tanh(XW₁+b₁),p = sigmoid(HW₂+b₂)。算出预测。 第二步算误差:g = (p - y)/n。这里用的是交叉熵配 sigmoid 的导数,恰好化简成 (p-y)/n 这么干净。 第三步反向:gH = (g·W₂)·(1-H²),把误差穿过 tanh 传回隐层。然后 W₁ -= lr·(Xᵀ·gH)。 第四步更新:所有参数按各自梯度挪一小步。四步循环几千次。

四、从 XOR 到狗猫

XOR 是 2 维的玩具问题,狗猫是 19 万维的真实问题,但结构上的困难是同一个: 原始空间里线性不可分。 区别在于:XOR 加 4 个隐单元就够了,狗猫需要卷积、池化、几十层、上百万参数。 但「加层换坐标」这个思路一模一样。 所以第 5 章的卷积不是全新的东西,它是「换坐标」这件事在图像上的高效做法: 不去连所有像素,只连相邻的一小块,因为图像里的规律是局部的。

五、动手 Demo:XOR 从学不会到学会

两段。第一段:单层感知器跑 200 轮,看它末轮错几个。 第二段:2-4-1 的两层网络跑 4000 轮,把四个点的预测值全打出来。

import numpy as np
rg = np.random.default_rng(5)

X = np.array([0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0]).reshape(4, 2)
y = np.array([0.0, 1.0, 1.0, 0.0])          # XOR

W1 = rg.normal(0, 0.8, (2, 4)); b1 = np.zeros(4)
W2 = rg.normal(0, 0.8, (4, 1)); b2 = np.zeros(1)

for ep in range(4000):
    H = np.tanh(X @ W1 + b1)                 # 隐层
    p = 1.0 / (1.0 + np.exp(-(H @ W2 + b2))).ravel()   # 输出层
    g = (p - y) / len(y)                     # 损失对输出的偏导
    gH = np.outer(g, W2.ravel()) * (1.0 - H * H)
    W1 -= 0.5 * (X.T @ gH);  b1 -= 0.5 * gH.sum(0)
    W2 -= 0.5 * (H.T @ g).reshape(-1, 1);  b2 -= 0.5 * g.sum()

实跑输出:

--------------------------------------------------------------------
类神经网络的学习:XOR 从学不会到学会
--------------------------------------------------------------------

  单层感知器跑 200 轮:末轮错 4 个,权重 (-0.20, 0.00),b = 0.20。
  **它不是在慢慢逼近,是在两个错误答案之间来回跳。**

  两层网络(2-4-1,tanh 隐层)跑 4000 轮,损失这样掉:

    第    1 轮   loss 0.827900
    第   10 轮   loss 0.708057
    第  100 轮   loss 0.167641
    第 1000 轮   loss 0.004658
    第 2000 轮   loss 0.002168
    第 4000 轮   loss 0.001043

  x1  x2  想要的  网络输出  判成
  ------------------------------
  0   0   0       0.0001    0
  0   1   1       0.9985    1
  1   0   1       0.9990    1
  1   1   0       0.0015    0

  **四个点全对了。**断掉的那一步就是隐层:4 个隐单元先把 (x1, x2) 揉成一组新坐标,
  在那个坐标里 XOR 变得可以用一条直线切开。

  这就是 1969 年那本书之后,1986 年反向传播真正救回来的东西:
  **单层做不到的事,加一层中间层就能做到——前提是你能把误差传回去。**

单层那行:末轮错 4 个,权重 (-0.20, 0.00)。四个全错,而且权重在来回跳, 说明它一直在两个都不对的答案之间被推来推去。 两层跑 4000 轮:损失 0.827900 → 0.001043。 中间有个加速段——前 10 轮只从 0.83 降到 0.71,第 100 轮才 0.17,第 1000 轮已经 0.0047。 这是典型的「先找方向、再快速下降」。 四个点的输出:0.0001、0.9985、0.9990、0.0015。不是「差不多对」, 是离 0 和 1 都很远——网络对这四个点非常确信。 值得注意的是隐层只有 4 个单元。 2 维输入、4 维隐层,参数量是 2×4+4+4×1+1 = 17 个。 17 个参数就解决了单层用 3 个参数永远解决不了的问题。 这说明「能不能」取决于结构,不取决于参数多少。

30 秒实验

  1. 把隐层从 4 个改成 2 个,还能学会 XOR 吗?(提示:2 个隐单元有时够,有时不够,看你自己的种子)
  2. 把 tanh 换成 sigmoid,训练会变快还是变慢?
  3. 把学习率从 0.5 改成 5.0,损失会震荡还是发散?

常见误区

  1. 以为 XOR 是「数据太少」。四个样本就是 XOR 的全部定义,再多也没有新信息。 问题不在数据量,在模型结构。
  2. 以为隐层越多越好。XOR 用一个隐层 4 个单元就够。 多堆层会增加参数、变慢、更容易过适,而不会让它更对。
  3. 以为单层加上更多训练轮数就能学会 XOR。它的参数空间里没有解, 训一万轮也是在两个错误答案之间跳。
自测三问(点开对答案) 1. 为什么单层感知器学不会 XOR? —— 它的判别边界是一条直线,而 XOR 的两类点在平面上交错排列(像棋盘格), 任何一条直线都只能切出一种结果。 2. 隐层是怎么让 XOR 变得可分的? —— 隐层把 (x₁,x₂) 映射到新的 4 维空间。 在那个空间里,XOR 的四组数变得线性可分,输出层只需切一刀。 3. 训练一个网络的那四步是什么? —— 前向算预测、算损失(或它的导数)、反向传播算各层梯度、按梯度更新参数。 四步循环,直到损失不再下降。

这一节里「损失」一直是个笼统的说法。 下一节把它摊开:平方误差和交叉熵到底差在哪,为什么分类任务几乎都用后者。

相关笔记