在这一篇里,“学习”有一个可以直接观察的含义:同一段程序反复处理样本,其中的一组数逐渐变化,预测也随之变化。
这组可以调整的数叫作参数。建模者决定观察什么、预测什么、怎样评价预测,训练过程再根据样本修改参数。后面的网络会有更多层和更多参数,基本动作仍从这里开始。
为什么不继续写规则
假设我们要判断一封邮件是不是垃圾邮件。一种做法是直接写规则:链接超过三个就拦截,出现某个词就加分,发件人在联系人中就放行。
规则清楚、稳定而且可以完整写出时,直接写程序通常更便宜、更可靠,也更容易解释。另一些任务提供了许多示例,却很难穷举其中的有效模式。垃圾邮件会改变措辞,图像中的猫没有固定像素,语音中的同一句话也不会产生完全相同的波形。
这类任务为机器学习提供了用武之地。程序保留通用的计算方式,样本则参与确定参数:
传统程序:人写规则 + 输入 → 输出
机器学习:人定模型与目标 + 样本 → 参数
参数 + 新输入 → 输出
以邮件分类为例,人仍然需要做出至少四个决定:
- 用哪些可测量的信息表示邮件,也就是输入 ;
- 什么算垃圾邮件,也就是真实标签 ;
- 允许模型用什么形式组合信息;
- 怎样给不同的错误评分。
训练负责在这些决定围成的空间里寻找参数。
机器学习留下的边界
这条边界值得在第一行公式之前说清楚。
- 可用信息受输入限制。 如果邮件的真实意图没有在输入中留下痕迹,再复杂的模型也无法恢复它。
- 未来表现依赖环境是否延续。 训练数据和实际环境发生分布变化后,曾经有效的参数可能迅速失效。
- 因果结论需要额外依据。 从“哪些现象一起出现”走到“改变什么会导致什么”,还需要因果假设或实验;Pearl 对关联与因果推断的区分系统说明了这道边界。
- 优化目标包含人的选择。 准确率、收益、安全、公平和用户福祉代表不同的取舍,无法由数据合并成唯一答案。
Wolpert 与 Macready 的无免费午餐定理讨论了算法与问题之间的关系。在定理设定的条件下,没有一种算法能在所有问题上占优。一种方法在特定任务上的表现,取决于它和任务结构是否匹配。
这篇文章集中在一个具体问题上:给定输入、目标和模型以后,怎样用数据找到一组合适的参数?
“神经元”从哪里来
“神经元”这个名字借自生物学。人工神经元只保留少数便于计算的特征,是一种高度理想化的数学模型。
1943 年,Warren McCulloch 和 Walter Pitts 在论文 A Logical Calculus of the Ideas Immanent in Nervous Activity 中,把神经活动理想化为“全或无”的事件。一个单元只有两个状态: 表示发出信号, 表示没有发出信号。
单元之间用箭头相连,这条箭头就是连接。兴奋性连接把上一个单元的信号送给下一个单元;抑制性连接可以阻止下一个单元触发。一个单元收到足够多的兴奋信号,并且没有受到抑制时,输出从 变为 。所需信号的数量叫作阈值。
只用这种简单规则,几个单元就能组成逻辑运算。假设 、 都只能取 或 :
| AND | OR | ||
|---|---|---|---|
- AND 单元接收来自 、 的两条兴奋性连接,并把阈值设为 。两个输入同时出现,它才触发。
- OR 使用同样的两条连接,把阈值设为 。任意一个输入出现,它就触发。
- NOT 可以借助抑制性连接实现。让一个持续活跃的输入维持输出,再让 抑制这个输出; 时输出为 , 时输出为 。
这些小电路还可以继续组合。例如,先计算 A AND B,再把结果与 NOT C 送进一个 OR 单元,就得到 (A AND B) OR (NOT C)。有限的输入模式与输出结果可以写成真值表,网络中的连接方式则把这张表变成一个可运行的电路。这就是当时“网络能表示什么”的具体含义。
从功能上看,这和计算机里的数字电路很接近。两者使用同样的 、 和真值表:数字电路把晶体管组织成逻辑门,McCulloch–Pitts 模型把理想化神经单元组织成逻辑网络。AND、OR、NOT 可以在这两种系统中表达同一种布尔运算,具体实现的材料与研究目的各不相同。
McCulloch 和 Pitts 研究的是预先设计好的电路。每条连接通向哪里、产生兴奋还是抑制、每个单元的阈值是多少,都由设计者提前确定。这一时期的网络更接近一张已经布好线的逻辑电路。接下来的问题是:连接本身能否随过去发生的活动改变?
1949 年,Donald Hebb 在 The Organization of Behavior 中把连接的变化放到中心位置。他所说的经验,指神经活动随时间反复出现:如果细胞 多次参与细胞 的激活,两者之间的连接效能就会持久增强。下一次相似活动到来时, 会更容易推动 触发。过去发生过的活动由此留在连接强度中。
经验怎样留在一组连接里
概念模型:共同活动的频率留下不同连接强度
许多神经元之间原本存在较弱的连接,还没有形成明显的活动痕迹。
Hebb 想研究的范围比一条连接大得多。《行为的组织》试图用神经系统的组织解释知觉、学习与行为,在神经生理学和心理学之间搭一座桥。连接增强是这套理论用来解释记忆如何形成的假设,并非对神经传输过程的逐帧记录。
后来的实验为这个想法找到了部分生理依据。1973 年,Bliss 和 Lømo 在兔海马实验中发现,短暂的重复刺激可以让突触——神经元之间传递信号的接点——提高传递效率,并维持数小时。真实的神经可塑性还会受到许多条件影响。Bi 和 Poo 1998 年的培养神经元实验显示,仅仅改变两个神经元放电的先后顺序,就可能让连接增强或减弱。因此,上面的动效表达的是 Hebb 思想的骨架:活动模式会改变连接;它没有复刻真实大脑的全部细节。图中不同的线最终留下不同粗细,表示它们随经验形成了不同影响力,不代表神经纤维每传递一次信号就会在物理上变粗。
1958 年,Frank Rosenblatt 在感知机论文中进一步研究可以通过示例改善反应的系统。他把可变化的连接带进了一个可以测试的计算系统。用于分类时,系统反复接收刺激和反馈,并据此调整部分连接强度。这里的经验已经有了熟悉的数据形态:看见一个输入,作出一次判断,再根据结果修改连接。
同一个“连接”,在这段历史中逐渐获得了不同含义:
| 阶段 | 连接是什么 | 经验怎样留下来 |
|---|---|---|
| McCulloch–Pitts 模型 | 预先设计的兴奋或抑制通路 | 连接固定 |
| Hebb 的设想 | 可以增强的细胞间作用 | 反复共同活动改变连接效能 |
| Rosenblatt 感知机 | 带有可调强度的输入通路 | 示例和反馈推动连接强度变化 |
| 本文的模型 | 用数字 表示的特征影响力 | 样本误差推动 更新 |
1982 年,John Hopfield 把问题从单个单元推进到网络整体。他提出的 Hopfield 网络是一种联想记忆:多个模式被储存为能量较低的稳定状态,残缺或带噪声的输入可以通过不断降低网络能量,逐步落入最接近的“记忆山谷”。一份记忆对应整个网络的共同状态,分布在许多连接之中。
1985 年前后,Geoffrey Hinton 与 Terrence Sejnowski 在这类思想上发展出 Boltzmann Machine。它学习训练数据中哪些整体状态更可能出现。统计物理中的能量与概率由此成为理解神经网络的工具,隐藏节点也开始承担发现数据内部结构的角色。
到了 2000 年代,Hinton 等人又利用受限玻尔兹曼机逐层训练深层网络,随后一起调整所有层。多层网络由此逐步形成可供分类使用的内部表示。诺贝尔奖委员会的科学背景材料把这段工作称为通往深度学习的里程碑。
2024 年,Hopfield 和 Hinton 因“使人工神经网络机器学习成为可能的基础性发现和发明”共同获得诺贝尔物理学奖。今天广泛使用的网络已经发展出新的结构;这项奖所表彰的,是统计物理关于大量简单单元如何形成整体行为的思想,以及它为现代机器学习建立的基础。
这段历史有许多分支。逻辑、神经生理、心理学、统计学、物理学与优化理论在不同时期交汇,逐渐形成今天的神经网络。沿着本文关心的问题,可以整理出一条线索:
生物神经活动的启发
↓ 抽象
可以组合的阈值计算单元
↓ 加入经验导致的连接变化
可以学习的感知机
↓ 研究许多单元的整体状态
联想记忆与概率生成模型
↓ 叠加层次并学习内部表示
可以共同训练的多层神经网络
研究对象逐渐扩大:一个逻辑单元、一个感知机、一个具有稳定状态的网络,再到许多层共同形成表示。每个阶段都要面对几个基本问题:输入怎样影响一个单元的输出?连接的强弱怎样表示?预测错了以后,连接怎样改变?
我们先取一个能够训练的最小单元,把每一步算清楚。逻辑回归提供了合适的入口。它与神经网络来自不同的历史传统,前向计算却与本文使用的单个神经元相同。
接下来把“输入—连接—输出—改变”落到一条具体样本上。先看输入怎样经过带权连接,变成模型的分数。
输入怎样变成一个分数
假设我们要判断一封邮件是否为垃圾邮件,并从每封邮件中取出两个特征:
- :某类关键词出现的频率;
- :邮件中链接的数量。
一封邮件的这两个值组成输入 :
和 来自样本。模型还需要判断每个特征对结果有多大影响,因此为它们分别准备一个可以调整的数: 对应 , 对应 。这两个数叫作权重,合在一起记为 :
输入 来自样本,权重 保存在模型中。训练开始前,我们先给权重一个初始值;随后根据预测误差反复调整。本文的代码从 开始。
模型还保存一个额外参数 ,叫作偏置。它控制没有任何特征贡献时的基础分数,也会和权重一起在训练中被调整。
现在,模型把每个特征乘上对应的权重,再加上偏置,得到一个分数:
这里的两个特征是 和 ;、 是分别控制它们影响力的两个参数。 越大,模型越倾向于类别 ; 越小,越倾向于类别 。它仍然只是一个没有固定范围的分数: 究竟代表多大把握,仅凭这个数还看不出来。
Sigmoid:把分数压成概率
分类当然可以直接使用一条硬规则: 判为垃圾邮件, 判为普通邮件。这样会丢掉一部分有用信息。刚刚越过边界和远离边界都会得到同一个类别,我们看不出模型是在犹豫,还是非常确定。
更有用的输出会回答:对于具有这些特征的邮件,模型估计它属于类别 的可能性是多少?如果输出 ,可以把它理解为:在模型眼中,大量具有相似特征的邮件里,大约有 属于类别 。
线性分数无法直接承担这个角色,因为它可以从负无穷延伸到正无穷。概率却只能位于 和 之间。连接这两个范围,需要先引入一个中间量:几率。
概率 时,几率为 ,意思是类别 与类别 的比例为 。概率 对应 , 对应 。
几率始终大于 。再对它取对数,便可以覆盖任意实数。逻辑回归在这里作出一个关键约定:前面算出的线性分数 ,表示类别 的对数几率。
这个选择正好与前面的加权求和配合。对数可以把几率的乘法变化改写成分数的加法变化:某个特征让 增加 ,类别 的几率就变成原来的 倍;另一个特征还可以继续在 上增加或减去自己的贡献。权重由此拥有统一的工作方式,每个 都是在修改对数几率。
暂时不需要计算对数。只要记住这条式子给 安排了明确含义: 对应两个类别机会相同;正数表示类别 占优;负数表示类别 占优;绝对值越大,倾向越强。
把上式中的 解出来,就得到 Sigmoid 函数:
σ(z) = 1 / (1 + e⁻ᶻ)这里的 是一个约等于 的数学常数,就像圆周率 一样。实际写代码时,NumPy 会替我们完成指数运算,不需要手算。Sigmoid 做的事情可以更准确地写成:
线性分数 z
↓ 把 z 当作对数几率
类别 1 与类别 0 的相对机会
↓ Sigmoid
类别 1 的预测概率 p
观察几个输入就能看到这种对应关系:
| 输入分数 | 类别 的几率 | Sigmoid 输出 | 直观含义 |
|---|---|---|---|
| 约 | 更偏向类别 | ||
| 两个类别机会相同 | |||
| 约 | 更偏向类别 | ||
| 约 | 强烈偏向类别 |
映射后的变化并不均匀。分数在 附近变化时,概率反应明显;分数已经很大或很小时,同样的变化对概率影响较小。例如 从 增加到 , 从 变为 ; 从 增加到 , 只从 变为 。模型在接近分界线时保留敏感度,在已经非常确定时逐渐趋近 或 。
Sigmoid 有三个重要性质:
- 无论输入多大或多小,输出始终在 和 之间。
- 输入越大,输出也越大,不会打乱模型原来的排序。
- 它的曲线平滑,分数发生小变化时,概率也会连续变化。
因此,概率解释并非只来自“结果恰好落在 和 之间”。我们先把 定义为对数几率,Sigmoid 再把它还原成概率。这个约定也规定了模型能表达的关系:每个特征先对对数几率作线性贡献。
概率随着参数改变
模型不会单独保存一张概率表。每次收到输入,它都使用当前的权重和偏置重新计算:
当前输入 x + 当前参数 w、b
↓
对数几率 z
↓ Sigmoid
当前的概率预测 p
训练开始时,参数还没有从数据中学到东西。本文把 、 和 全部设为 ,所以每封邮件最初都会得到 、。这个 只表示当前的初始化让模型暂时不偏向任何一类,它还没有包含邮件样本提供的经验。
训练数据随后推动权重和偏置发生变化。对于同一封邮件,新的参数会算出新的 ,Sigmoid 也会随之给出新的 。概率的变化可以沿着这条链追踪:
参数改变 → 对数几率改变 → 概率改变
例如,同一条输入第一次得到 、;参数更新后,它可能得到 、。Sigmoid 始终执行同一个转换,模型从数据中学到的内容留在权重和偏置里。
概率是否值得相信,还要看预测与现实能否对应。如果模型对许多邮件都给出 ,其中确实约有 是垃圾邮件,这组预测才称得上可靠。接下来需要一个评分规则,让真实标签推动参数以及由它们产生的概率逐步变化。
这条链在两种语境中有两套名字:
| 逻辑回归中的说法 | 神经元中的说法 | 实际计算 |
|---|---|---|
| 特征 | 输入 | |
| 系数 | 权重 | |
| 截距 | 偏置 | |
| 线性预测值 / logit | 预激活值 | |
| Logistic 逆链接函数 / Sigmoid | 激活函数 | |
| 预测概率 | 神经元输出 |
表格中的计算说明,一个采用 Sigmoid 激活的单个神经元与逻辑回归拥有相同的前向过程。这个结论只覆盖当前单元;其他神经元可以使用不同的激活函数。
算一遍前向过程
取一个具体例子:
第一步是加权求和:
第二步是激活:
到这里,前向过程已经结束,共有两个变换:
输入 x
↓
线性组合 z = wᵀx + b
↓
Sigmoid p = σ(z)
↓
概率 p
规定 时预测为类别 ,这条样本会被分到类别 。这个阈值用于输出最终类别。训练阶段继续使用概率,衡量预测偏离真实答案的程度。
概率预测怎样得到一个损失
刚才的样本得到 。按照 的阈值,它会被判为类别 。如果真实标签确实是 ,分类结果答对了;但训练还需要知道这个预测距离理想答案有多远。
只记录“对”或“错”无法提供足够的信息。 和 都会被判为类别 ,前者刚刚越过边界,后者已经非常确定。真实标签为 时, 和 又都算答错,后者显然错得更自信。
训练需要一条连续的评分规则,把预测概率 和真实标签 变成一个数:
预测概率 p + 真实标签 y
↓ 损失函数
损失 L
这个数叫作损失。损失越小,说明当前预测越符合真实答案;损失越大,说明参数还需要更明显的调整。损失函数规定了模型在训练中怎样看待错误。
先看模型给正确答案多少概率
模型输出的 表示类别 的概率,因此类别 的概率是 。真实标签出现后,我们只需取出模型分给正确类别的概率,记作 :
- 真实标签 时,正确类别的概率是 ;
- 真实标签 时,正确类别的概率是 。
一个合适的损失应该让较大的 得到较小的分数,同时明显惩罚接近 的 。常用的选择是:
是自然对数。暂时不用计算它,只需观察这条评分曲线:
| 正确类别概率 | 损失 | 模型的表现 |
|---|---|---|
| 把全部概率给了正确答案 | ||
| 正确,而且很有把握 | ||
| 无法区分两个类别 | ||
| 把大部分概率给了错误答案 | ||
| 几乎确信错误答案一定会发生 |
正确类别的概率从 降到 时,损失从 增加到 。模型越确信一个错误答案,付出的代价越大。预测接近正确答案时,损失则逐渐靠近 。
对数还有一个适合训练整批数据的性质。把三条样本看作相互独立,假设模型给它们的真实结果分配的概率分别是 、 和 ,那么模型给这三个结果共同分配的概率是三者的乘积:
取负对数后,乘积会变成每条样本损失的和:
于是每条样本都能贡献一份损失,再通过求和或平均形成整批数据的训练目标。提高真实结果的整体概率,与降低这份总损失指向同一个方向。
二元交叉熵由此出现
用于两个类别时,这条评分规则叫作二元交叉熵,也常写作 BCE:
- “二元”来自 只有 和 两种取值;
- “交叉熵”描述用模型给出的概率分布为真实结果评分;
- 在当前二分类问题里,它最终就是正确类别概率的负对数。
把 和 两种情况合进一个式子,得到:
这个公式一次只会留下其中一项:
- 当 时,,所以 ;
- 当 时,,所以 。
回到刚才的 :真实标签若为 ,损失约为 ;真实标签若为 ,正确类别的概率是 ,损失约为 。同一个概率预测会根据真实答案得到不同评分。
在一批 条样本上,训练通常使用平均损失:
准确率适合汇报最终分对了多少样本,二元交叉熵则保留每次预测的确信程度,为训练提供连续信号。到这里,损失函数只完成了评分;参数还没有发生变化。
从损失得到参数调整方向
一次前向计算留下了这条链:
参数 w、b → 分数 z → 概率 p → 损失 L
为了降低损失,我们要知道链中每个数发生小变化时,最终的 会向哪个方向、改变多少。描述这种变化方向和幅度的量叫作梯度。
Sigmoid 与二元交叉熵组合后,损失相对于分数 的梯度恰好可以写成:
左边读作“损失 相对于分数 的变化率”。先用两个例子检查它的方向:
- 若 、,则 。负号表示减小损失需要让下一次的 变大。
- 若 、,则 。正号表示减小损失需要让下一次的 变小。
由权重、输入和偏置共同计算,因此每个权重收到的调整信号还要乘上它对应的输入:
一批 条样本会产生 份调整信号。把所有输入按行排成矩阵 ,预测概率排成向量 ,真实标签排成向量 ,平均梯度可以写成:
得到梯度以后,梯度下降让参数沿着损失减小的方向移动一小步:
其中 是学习率,控制每一步修改多大。计算新的预测、得到误差、修改参数,再用下一批样本重复,这就构成了本文的训练过程。
用 NumPy 训练一个神经元
下面的实验只使用 NumPy 完成前向传播、损失计算和梯度更新。两团二维点分别代表类别 和类别 ;橙色直线是神经元最终学到的 决策边界。
点击“运行”后,Python 会在浏览器的 Web Worker 中执行,数据和代码不会发送到服务器。第一次需要下载 Pyodide、NumPy 与 Matplotlib,因此会比后续运行慢。
运行代码后,文本与图表会显示在这里。
读代码时,可以先看训练循环里的四行:
z = x @ w + b
probability = sigmoid(z)
error = probability - y
dw = x.T @ error / len(x)
它们依次完成线性组合、Sigmoid、输出端误差和权重梯度。每一步都明确写在代码里,可以和前面的公式逐行对照。
运行结果里还有两件事值得观察。
第一,交叉熵随着更新逐渐下降。这说明当前参数产生的预测越来越符合训练标签。第二,二维平面上出现了一条直线。令 等价于令 ,所以决策边界满足:
无论训练多久,这个神经元都只能移动或旋转这条直线,不能把它弯曲。
既然计算相同,为什么还要叫神经元
名称取决于这段计算在系统中扮演什么角色。
单独看时:
x → Linear → Sigmoid → p
它是一套完整的逻辑回归模型。放入网络以后,一个单元的输出可以成为下一层许多单元的输入:
x₁ ─┐ ┌→ h₁ ─┐
├→ weighted sum ─→│ ├→ next layer
x₂ ─┘ └→ h₂ ─┘
图中的 、 是两个单元的输出。连接成网络后,前面的单元先把输入变换成新的表示,后面的单元再基于这些表示做判断。“神经元”这个名称强调了它作为网络组成部分的身份。
如果层与层之间只有线性计算,无论叠多少层,整个网络都可以合并成一次线性变换。激活函数提供的非线性因此成为下一篇的主题。
单个神经元的边界
参数已经可以随数据改变,模型的能力仍受结构和输入限制。
首先,单个 Sigmoid 神经元只能学习线性决策边界。在二维平面上,它只能寻找一条直线;在更高维空间里,也只是寻找一个超平面。如果两类数据必须靠弯曲、包围或多段边界才能分开,再多训练轮次也不会突破模型结构本身的限制。优化只能在模型允许的范围内寻找答案,不能把不存在的表达能力训练出来。
其次,模型收到的只有我们选出的 、。如果这两个特征没有保留区分类别所需的信息,两封不同的邮件在输入空间中可能完全相同。模型能看到的世界由输入表征划定。
最后,训练损失下降说明模型越来越符合这批样本和这套评分规则。理解、因果关系以及在其他时间、语言或人群上的表现,都需要另外检验。损失记录了一次优化的结果,覆盖不了关于世界的所有问题。
这三条边界分别指向深度学习后面的三个核心问题:
- 一条直线不够时,怎样通过非线性与多层结构获得更强的表示能力?
- 原始输入不够时,网络能否逐层形成更有用的内部表示?
- 训练集上的成功不够时,怎样讨论泛化、鲁棒性与评估?
“学习”现在可以被完整地复述
现在可以把全文收进下面这条循环:
输入 x
↓
z = wᵀx + b
↓
p = sigmoid(z)
↓
L = binary_cross_entropy(p, y)
↓
error = p - y
↓
计算 dw、db
↓
更新 w、b
这条链中的每一部分都承担不同责任:
- 输入决定模型能够看见什么;
- 模型结构决定它能够表达什么;
- 损失函数决定什么样的错误更重要;
- 梯度把总误差分配给每个参数;
- 参数更新把这次经验留下来。
整条前向计算独立完成二分类时,它构成一个逻辑回归模型;同样的计算作为网络中的局部单元时,我们称它为 Sigmoid 神经元。后一个名称强调了组合关系。
这也给出了 Basics 系列的起点。下一篇会从一个无法被直线分开的四点问题开始,再观察激活函数和多层结构怎样形成新的表示。更后面的文章将继续追踪误差在多层网络中的传递,以及每条连接如何得到自己的调整信号。
深度学习先设计一个可能规则的空间,再用数据从中选择。网络越深,这个空间越丰富;输入、结构、目标与评估仍由人来设计和检查。
下一步的问题已经出现:当一条线不够时,多个单元的组合会带来什么能力?