第 17 章:神经网络到底在算什么
本章问题:神经网络是不是一个复杂函数?
前两卷讲完了神经网络的历史和深度学习的崛起,但有一个问题一直没有正面展开:一个神经网络,到底在做什么数学运算?这一章用最短的路径回答它。
17.1 从一句话开始
在前两卷中,我们反复提到了"神经网络"这个词。感知机是一个神经元,AlexNet 是一大群神经元组成的网络,反向传播负责调整所有神经元之间的连接。
但有一点一直没有正面展开:一个神经网络,到底在做什么数学运算?
本章用最短的路径回答这个问题。不需要先修任何机器学习的知识——你只要知道什么是乘法、加法,以及函数的概念。
答案很简单,简单到你可能不信:
神经网络就是一个函数。一个把输入向量变成输出向量的参数化函数。
17.2 网络 = 函数
初中学函数:y = f(x),给一个 x,得到一个 y。
神经网络做的是一模一样的事。只不过:
- 输入 x 不是一个数,而是一组数——一个向量。比如一张 28×28 的灰度图片,可以展平成 784 个数。
- 输出 y 也可能是一组数。比如 10 个数,分别代表图片是 0 到 9 每个数字的概率。
- 函数 f 内部不只是一个数学公式,而是由很多"层"叠成的一条流水线。
用更技术一点的语言说:神经网络是一个由一系列可微分的运算层组成的函数。 输入流过每一层,逐步被变换,最终变成输出。
"可微分"这个词是关键,但我们放到后面再展开——它只意味着一件事:你可以算出"每个参数对最终错误贡献了多少",从而能调整它。反向传播(第 8 章)做的事就是这个。
17.3 一层到底长什么样
神经网络的"层"有很多种,但最基础、出现在几乎每一个网络里的,是全连接层,也叫线性层。
它是一个矩阵乘法加一个向量加法:
如果一个层接收 784 个输入,输出 128 个数,那么——
input是一个长度为 784 的向量weight是一个 784×128 的矩阵(每一个输入到每一个输出的连接上有一个权重)bias是一个长度为 128 的向量(每个输出神经元有一个偏置值)output是一个长度为 128 的向量
每一个输出值是怎么算出来的?
output[j] = input[0]×W[0][j] + input[1]×W[1][j] + ... + input[783]×W[783][j] + b[j]
就是把所有输入做一个加权求和,再加上一个偏移量。这不就是感知机做的事吗?(第 3 章)
没错。一个全连接层的每一个输出神经元,就是一个感知机。
整个神经网络,就是很多层这样的计算叠在一起。但这里有一个容易被忽略的问题——如果每一层都只是"加权求和",叠多少层都没意义。为什么?因为多次线性变换的叠加,在数学上等价于一次线性变换——增加层数没有增加表达能力。
所以需要一个关键组件,插在每一层的加权求和之后——
17.4 激活函数:让直线弯曲
如果每一层都是加权求和,那么无论你叠多少层,整个网络在数学上都只能表示一个线性函数——只能画直线。
现实世界的规律很少是线性的。"如果天阴,带伞概率增加"——线性。"如果天阴且刚被淋过,带伞概率大幅增加"——两个因素产生了交互,不再是简单的线性叠加。
所以需要在每一次加权求和之后插入一个非线性函数。
在数学上,非线性意味着——输出相对于输入的变化,在不同的输入位置会有不同的变化率。它打破了"总变化是各因素变化的简单加权和"的限制。
这个非线性函数被称为激活函数。
最经典的激活函数是 ReLU(Rectified Linear Unit):
输入为正?原样通过。输入为负?输出 0。
它可以被直观理解为一个门的角色——对于每一个神经元输出,ReLU 会说:"如果你算出的那个加权和是负的,那算了,别传了(输出 0);如果是正的,全量传给下一层。"
ReLU 的巧妙之处在于——它的计算极其简单(就一个比较),但在足够多层叠加后,能产生任意复杂的非线性分界。而且它的导数简单(正区域是常数 1),深层网络中梯度不会指数级地消失。
就这样:加权求和(线性层)→ 激活函数(ReLU)→ 再加权求和 → 再激活 → …… 这就是绝大多数神经网络的基本节奏。
17.5 参数:网络中"可以学习"的东西
到目前为止提到的 weight 矩阵和 bias 向量——这些数值,就是神经网络的参数。
在 AlexNet 中有约 60,000,000 个参数。在 GPT-3 中有 175,000,000,000 个参数。无论规模如何,所有的参数在数学上只有一个职责——它们是乘法中的因子和加法中的加数。
训练,就是找到一组参数值,使得网络在训练数据上的表现尽可能地好。参数值本身没有"含义"——weight[3][7] = 0.02 不意味着任何人类能解读的东西。它的意义只有在一个位置——它参与的运算链是否能最终让输出更接近正确答案。
这是"从连接主义到表示学习"那条主线在微观层面的落脚点:智能不是被写在参数里的;它是在参数与输入交互的计算过程中涌现出来的。
17.6 损失:告诉网络"你错得多离谱"
要训练网络,需要先定义什么是"好"。
损失函数(loss function)就是用来做这件事的:输入是网络的预测值和真实答案,输出是一个数——错得越离谱,这个数越大。
对于一个分类任务——输入一张猫的照片,网络应该输出"猫"——最常用的损失函数是交叉熵损失(cross-entropy loss)。它的直觉很简单:如果网络对正确答案赋予高概率,损失就小;如果它自信地选了错的,损失就大到离谱。
数学上,交叉熵衡量的是两个概率分布之间的距离——网络的预测分布和"真正的分布"(标准答案的那个)有多接近。当预测分布完全集中在标准答案所在那个类别上时,交叉熵损失最小。
对于回归任务——输入一套房子的信息,网络应该输出预估房价——最经典的损失是均方误差(MSE):(预测值 - 真实值)² 的平均。
无论具体的损失函数长什么样,它们都服务于同一个目的:把"做得好"和"做得差"变成两个可以直接比较的数。
17.7 训练:反复的"试—错—调"
有了网络(函数)和损失(验收标准),训练就是一个重复三步骤的循环:
第一步是"试",第二步是"打分",第三步是"调"。重复几千次、几万次、几百万次之后,参数逐渐移动到低损失区域——网络的预测越来越准。
在代码里,这三步通常被隐藏在三行调用后面:
但这四行背后对应着本书前面 16 章讲过的每一个核心概念。它们不是魔法——它们是函数求值、矩阵乘法、链式法则的直接应用。
17.8 最小代码:手写一个两层网络
下面的代码用 NumPy 实现一个完整的两层神经网络,在经典的鸢尾花数据集(Iris)上训练。总共约 50 行,包含前向传播、反向传播和训练循环。
这段代码放到 Jupyter Notebook 里可以分步运行,观察 loss 从 ~1.1 降到 ~0.1,测试准确率从 ~30% 一路升到 ~95%。所有东西加起来 50 行,没有框架、没有黑箱——这就是神经网络最基本的训练全貌。
17.9 本章地图
17.10 本章结语:所有东西不过乘法和加法
在你被大模型的神秘感笼罩前,这一章想传递的就一件事:
神经网络在最底层什么也没有——只有乘法、加法、和一个"正数通过负数截断"的非线性变换。
当别人用不可名状的方式谈"AI 魔法"时,你可以记住这里有一个非常平凡的底层事实:一个 175,000,000,000 参数的巨型网络,每一个参数在你训练它的过程中被移动的那一点点,都是通过链式法则算出来的精确调整量。
魔法感褪去后,留下来的是工程和数学——两者都不神秘,但都值得被理解。
下一章,我们把反向传播从直觉版升级为计算版——用纸笔算一遍一个最简单的两层网络里梯度是怎样反向流动的。那是整个训练逻辑在微观上最清楚的展现。
Discussion
留言区 · GitHub-powered comments via Giscus