大模型原理 · 基石

神经网络如何逼近任何函数

大语言模型的每一层里都藏着一个结构很简单的网络:乘一个数,加一个数,过一道弯曲的函数,再把结果加起来。这一页从单个神经元讲起,一步步走到通用逼近定理:为什么这样简单的零件,只要数量够多,就能拟合几乎任何曲线。

目标函数 f(x)网络输出 F(x)

第一步

一个神经元

一个神经元只做两件事。先做一次线性运算 z = w·x + b:权重 w 决定灵敏度(斜率),偏置 b 决定平移。再把 z 送进一个非线性的激活函数 σ,得到输出 y = σ(z)。

谁是“参数”?只有 w 和 b。它们是可以调的旋钮,训练就是在找它们的值;平时说的“模型权重”,指的就是全部 w 和 b 的集合(严格说 w 叫权重、b 叫偏置)。σ 则是事先选好、写死的公式(sigmoid、tanh、ReLU……),里面没有要学的数,训练时一直不变。下面的滑块里,你调的也只有 w 和 b;切换 σ 相当于换了一种固定零件。

三种 σ 的固定公式(只有 z 和常数,没有要学的数):
sigmoid:σ(z) = 1 / (1 + e−z) 输出 0~1
tanh:tanh(z) = (ez − e−z) / (ez + e−z) 输出 −1~1
ReLU:ReLU(z) = max(0, z) 负数变 0,正数原样

w、b 各自把曲线怎么变、每种激活函数长什么样,见《w、b 和激活函数,在图上长什么样》。

y = σ( w · x + b )
激活函数 σ
y = σ(z)z = w·x + b(激活之前)
拖动 w,曲线变陡或翻转;拖动 b,曲线左右平移。S 形的中点(或 ReLU 的拐点)在 x = −b/w。

为什么一定要有 σ? 如果去掉激活函数,叠多少层都只是一条直线:w(2)(w(1)x + b(1)) + b(2) = (w(2)w(1))x + (w(2)b(1) + b(2))(右上角数字是层号)。网络能表达的所有“弯曲”,全部来自激活函数。

第二步

一层神经元,加在一起

把多个神经元并排放就是一个隐藏层。同一个输入 x 同时送进每个神经元,每个神经元各自画出一条被拉伸、平移过的 S 形曲线 hi(x)。从这里开始,参数右上角的 (1)、(2) 表示它属于第几层:第 1 层的权重、偏置记作 wi(1)、bi(1)。

最后再放一个输出神经元,把 h1…h5 当作它的输入。它和第 1 层的神经元做的事一样:每条输入线一个权重,加权求和,再加一个偏置。所以它的参数也叫 w 和 b,只是右上角标 (2):权重 wi(2),偏置 b(2)。唯一的区别是它不套激活函数,直接输出加权和。

第 1 层(隐藏神经元 i):hi = σ( wi(1) · x + bi(1) )
第 2 层(输出神经元):F = w1(2)·h1 + … + w5(2)·h5 + b(2)同样是“权重 × 输入 + 偏置”,只是不套 σ
合起来:F(x) = Σi wi(2) · σ( wi(1) x + bi(1) ) + b(2)矩阵写法:h = σ(W(1)x + b(1)),F = W(2)h + b(2)

有的教材把第 2 层的 w(2)、b(2) 另起名字叫 v、c,指的是同一样东西。

想看这个输出神经元是怎么一步步“长”出来的,以及为什么它的权重 w(2) 非有不可,见《多层神经网络是怎么来的》。

正权重负权重线越粗 = |权重| 越大,节点越亮 = 激活值越大各神经元贡献 wᵢ⁽²⁾·hᵢ(x)总和 F(x)
左边是网络,右边是它计算的函数。竖线就是左图此刻送进去的 x。每换一组权重,就得到一条不同的曲线。问题来了:只靠调这些数字,能拼出任意想要的曲线吗?

第三步 · 核心

通用逼近:用台阶拼出任何曲线

答案是可以,而且证明的思路非常直观。只需要三个观察:

① 一个神经元 ≈ 一个台阶

w 很大时,σ(w(x − t)) 在 t 处几乎垂直跳起。t 由偏置决定:b = −w·t。

② 两个台阶相减 = 一个凸起

σ(w(x − t₁)) − σ(w(x − t₂)) 只在 t₁ 到 t₂ 之间为 1,其余地方为 0。

③ 凸起乘上高度,再相加

每个凸起乘以目标函数在这一段的值,加起来就是一条贴着目标走的阶梯。

相邻两个凸起交界处的那一对台阶可以合并成一个。所以把区间切成 N+1 段,只需要 N 个神经元,每个神经元负责一个“台阶”,它在第 2 层的权重 wᵢ⁽²⁾ 就是相邻两段的高度差。

实验台

下面按这个方法直接计算出每个神经元的参数,不做任何训练。增加神经元数量 N,看阶梯怎样越来越细。浅红色区域是误差。选“手绘”后可以直接在图上拖动,改画目标曲线。

目标函数
激活函数
目标 f(x)网络 F(x)单个神经元的贡献误差
下图:最大误差 max|F(x) − f(x)| 随 N 的变化(纵轴是对数刻度)。圆点是当前的 N。切到 ReLU 时,每个神经元贡献一个拐点,网络变成连接采样点的折线,误差下降得快得多。

定理 · Cybenko 1989,Hornik 1991,Leshno 等 1993

设激活函数 σ 连续且不是多项式。对于有界闭集 K ⊂ ℝn 上的任意连续函数 f,以及任意小的误差 ε > 0,都存在一个宽度 N 和一组参数,使单隐藏层网络

F(x) = Σi=1…N wi(2) σ( wi(1) · x + bi(1) ) 满足 |F(x) − f(x)| < ε 对所有 x ∈ K

上面的实验台就是一维情形下的构造证明:想要更小的 ε,就切更多段、用更多神经元。

定理没有说的三件事

不告诉你参数怎么找

它只保证这样的参数存在。我们在实验台里是照着 f 直接算出参数的,可真实任务里 f 是未知的,只有一堆样本。找参数要靠训练,见下一节。

不保证网络小

台阶构造在 n 维输入下要把空间切成小格子,需要大约 (1/ε)n 个神经元,维度一高就爆炸,这叫维数灾难。实践中,把网络加深往往比单纯加宽省得多。

不保证能泛化

定理只管在 K 上贴合 f,不管网络在没见过的数据上表现如何。训练数据外的行为,取决于数据、结构和训练方法。

第四步

训练:让网络自己找参数

真实的网络不会有人替它算参数。它从随机初始值出发,反复做三件事:计算损失 L = 平均( F(xj) − f(xj) )2;用反向传播算出 ∂L/∂wi(1)、∂L/∂bi(1)、∂L/∂wi(2)、∂L/∂b(2);把每个参数往让损失变小的方向挪一小步。这就是梯度下降。被调整的只有这些 w 和 b,激活函数(这里用 tanh)从头到尾不变。

目标函数
上图:灰点是训练样本,细蓝线是每个神经元的贡献(激活函数用 tanh,优化器用 Adam)。下图:损失随迭代下降(对数刻度)。训练出来的神经元不会排成整齐的台阶,它们会自己找到一种更省的分工。训练中途换目标函数,网络会跟着重新调整。

回到大模型

这和 GPT 有什么关系

Transformer 的每一层都有一个前馈网络(FFN),公式和第二步里的一模一样,只是输入输出从一个数变成了一个向量:

FFN(x) = W2 · σ( W1 x + b1 ) + b2下标 1、2 就是本页的 (1)、(2):第 1 层、第 2 层。σ 通常是 GELU,一种平滑版的 ReLU

以 GPT-3 为例:共 96 层,每层的 FFN 把一个 12288 维的向量送进 49152 个隐藏神经元,再映射回 12288 维。每层里 FFN 的参数量约是注意力部分的两倍,也就是说模型大约三分之二的参数都在这些“隐藏层”里。

可以粗略地这样分工理解:注意力负责在词与词之间搬运信息,FFN 负责对每个位置的向量做一次“任意函数”式的加工。通用逼近定理保证了这种零件原则上什么函数都能表达;梯度下降负责在海量文本上找到那组具体的参数。

下一步值得学的是反向传播(梯度到底怎么算出来)和注意力机制(信息怎样在词之间流动)。

练习

学完上面的内容,来检验一下。每题作答后马上看到解析;点「重做练习」清空重来。