🧠 第八章 神经网络训练
📄 一句话概括

784-128-10 的一层手写数字网络,参数 101770 个,其中第一层占 98.7%, 按 fp32 存才 0.39 MB。 前向走一遍,ReLU 把 45.3% 的负数压成 0,输出十个概率加起来正好 1.000000。

📝 本节和前面几节的关系

上一节说了「多层能画非线性边界」。 这一节把它拆开看:一层里到底有哪几样东西,参数量怎么数,数值怎么变。

一、一层里有四样东西

形状:这一层吃多少维、吐多少维。784 进 128 出,就是一个 784×128 的矩阵。 权重和偏置:矩阵里每个数都是可训练参数,偏置是每个输出维度一个。 激活:中间层用 ReLU(负数归零),最后一层用 Softmax(变成概率)。 连接方式:全连接就是「每个输入连到每个输出」,卷积是「只看局部」,这是下一节的内容。 四样里只有权重和偏置会被训练改动。形状和激活是设计时定死的,训练不动它们。

二、参数量怎么数

全连接层的参数量等于「输入维度 × 输出维度 + 输出维度」。后一项是偏置,每个输出一个。 784 进 128 出:784 × 128 + 128 = 100480。128 进 10 出:128 × 10 + 10 = 1290。 加起来 101770。 第一层占了 98.7%。这个比例在图像任务里很典型——输入维度太大,第一层永远是参数大户。 按 fp32 存是 0.39 MB。 这个量级很小,说明「参数多」在不同任务上差别巨大:这个网络 0.4 MB, 一个 70 亿参数的语言模型是 28 GB。 算参数量是训练师的基本功。它直接决定显存、决定训练要多久、决定能不能在一张卡上跑。

三、前向走一遍,看数值怎么变

输入 784 维,均值 0.016、标准差 0.983(我喂的是标准正态)。 过第一层线性变换,均值 0.081、标准差 1.415——标准差变大了,因为 784 个随机数加权求和, 方差会累积。 过 ReLU,均值变成 0.611、标准差降到 0.852。 因为一半负数被压成 0,均值必然为正,离散程度也跟着小一截。 最后过 Softmax,十个概率加起来 1.000000,每个都在 0.10 上下,最大的那个 0.27 左右。 没训练过的模型,本来就和瞎猜差不多。 这条链路值得记:线性层会放大数值,激活层会压缩数值。 所以深网络里要小心初始化,不然几十层之后数值不是爆掉就是归零。

四、为什么第一层之后标准差会变大

784 个数各自乘一个权重再求和。 如果权重是独立同分布、方差是 σ²,输出方差大约是 784 × σ² × 输入方差。 所以想让输出方差和输入差不多, 权重的方差就得取 1/784 这个量级——这就是 He 初始化(除以输入维度)的由来。 Demo 里 W1 用的正是 sqrt(2/784)。 如果不这么初始化,而是简单用 N(0,1),784 维求和之后输出标准差会到 28 左右,再过几层就彻底饱和, 梯度全变成 0。 这个坑在 2010 年之前非常普遍,那时候大家靠调小学习率硬扛。 初始化方案定下来之后,深网络才真正训得动。

五、动手 Demo:一层网络的账:形状、参数量、前向数值

按 784-128-10 搭一个手写数字网络,先算每层参数量和总量; 再用 He 初始化造权重,喂一个 784 维标准正态输入,走一遍前向,记录每一阶段的维度、均值、标准差, 以及 ReLU 置零比例和 Softmax 输出。

W1 = rg.normal(0, math.sqrt(2.0 / 784), (784, 128))   # He 初始化
z1 = x @ W1 + b1
a1 = np.maximum(z1, 0)                                # ReLU
z2 = a1 @ W2 + b2
a2 = H.softmax(z2)

实跑输出:

--------------------------------------------------------------------
一层网络长什么样:形状、参数量、激活
--------------------------------------------------------------------
  层         输入维度   输出维度   参数量   激活
  ------------------------------------------------
  输入       784        784        0        —
  全连接 1   784        128        100480   ReLU
  全连接 2   128        10         1290     Softmax

  总参数量                 101770
  其中第一层占             98.7%
  按 fp32 存               0.39 MB

  前向传播走一遍,看每一层的数值变化:
  阶段      维度     均值   标准差
  -------------------------------
  输入 x     784    0.016    0.983
  线性 z1    128   +0.081    1.415
  激活 a1    128    0.611    0.852
  线性 z2     10   +0.030    1.470
  概率 a2     10    0.100    0.092

  ReLU 后被置零的比例      45.3%
  输出概率之和             1.000000
  最大概率                 0.2679(未训练的模型接近随机)
  ReLU 把一半的负数压成 0,这是它的正常行为,不是 bug。所以它的输出均值一定是正的,标准差比输入小一截。
  最后一层 Softmax 保证十个数加起来等于 1,但没有训练过,所以十个概率都在 0.10 上下,最大的那个也才 0.27——和随机猜差不多。

总参数 101770,第一层占 98.7%。这个比例意味着:想减参数,动第一层最有效; 想加容量,加在中间层最划算。 ReLU 置零比例 45.3%。 这个数跟输入分布有关,标准正态输入下理论值就是 50%,784 维里偏一点很正常。 它不是 bug,是 ReLU 的正常行为。 输出概率之和 1.000000,说明 Softmax 的归一化是对的。如果这里不是 1,那就是实现写错了。 线性层标准差从 0.983 涨到 1.415,激活层又压到 0.852。 这条「放大—压缩—放大—压缩」的节奏是深网络的常态。 这个 Demo 只算前向,没有训练。 所以最大概率才 0.27——训练过的模型在同一个输入上通常会给出 0.9 以上的某个类。

30 秒实验

  1. 把 He 初始化换成 N(0,1),看 z1 的标准差变成多少。
  2. 把隐藏层从 128 改成 256,重算总参数量和第一层占比。
  3. 想一想:为什么偏置的个数等于输出维度,而不是输入维度?

常见误区

  1. 「参数越多模型越强」——参数多只意味着容量大,学不学得到是另一回事。
  2. 「ReLU 置零一半是坏了」——这是它的设计,置零正是非线性的来源。
  3. 「Softmax 输出就是置信度」——它只是归一化后的相对大小,没训练时一样是瞎猜。
自测三问(点开对答案) 1. 全连接层的参数量怎么算? —— 输入维度乘输出维度,再加输出维度(偏置个数)。 2. 为什么第一层通常占参数大头? —— 输入维度最大(比如 784),乘上隐藏层维度后就是最大的一个矩阵。 3. ReLU 之后输出均值为什么一定是正的? —— 所有负数被压成 0,剩下的都是非负值,平均下来不可能为负。

结构看清了,下一节讲怎么让它学:训练循环、优化器、学习率这三件事。

相关笔记