前言
上回聊了机器学习的大图景——数据驱动、自动调参、从写规则到搭环境。那篇文章里简单提了一嘴神经网络的结构,但没往深了挖。这一篇我们就把镜头拉近,专门看看神经网络这个东西到底是怎么运作的。
如果你对矩阵乘法、链式求导这些概念还有印象,那读完这篇你应该能理解:一个神经网络从头到尾在干什么,以及为什么近十年它突然变得这么能打。
1、反向传播:神经网络的灵魂
上一篇讲过前向传播——数据从输入层流到输出层,一路做矩阵乘法和激活函数。但光有前向传播没用,你还得让模型知道自己错在哪儿、每个参数该往哪个方向调整。这件事靠的就是反向传播。
反向传播的概念在 1986 年就提出来了,但它在很长一段时间里并没有引起轰动。真正让它封神的,是 2012 年之后 GPU 算力爆发,让"深"网络成为可能——而深网络离开反向传播根本没法训练。
它的思路极其优雅:既然前向传播是一层层往前算,那误差的"责任"也可以一层层往回追溯。
假设你有一个三层的网络,最后一层输出错了,这个错误显然和最后一层的参数有关。但倒数第二层的参数有没有责任?有——因为它的输出是最后一层的输入,它的质量决定了最后一层能发挥的上限。依此类推,第一层的参数也要为最终的错误负责,只不过隔了好几层,责任需要通过中间层传递。
这个"责任追溯"的过程在数学上就是链式求导:
最终误差对第一层参数的梯度 =
误差对输出层的梯度 × 输出层对中间层的梯度 × 中间层对第一层的梯度三个梯度连乘,恰好对应三层网络。
抽象成伪代码:
def train(model, batch, labels):
# 1. 前向传播:算预测结果
outputs = model.forward(batch)
# 2. 算误差
loss = loss_function(outputs, labels)
# 3. 反向传播:从最后一层开始,逐层算梯度
grad = loss_gradient(outputs, labels) # 输出层的梯度
for layer in reversed(model.layers):
grad = layer.backward(grad) # 把梯度传给上一层
layer.update_weights(grad) # 用梯度更新自己的参数有没有发现这个 for layer in reversed(model.layers) 很像什么?它和 Java 里 Filter Chain 的 doFilter 递归调用在结构上是对称的——只不过方向反了。Filter Chain 是请求一层层往里走,反向传播是误差一层层往外传。如果你写过拦截器链,理解反向传播的调用逻辑不需要一分钟。
框架帮你做了脏活。在 PyTorch 里,你写的代码长这样:
loss.backward() # 一行代码触发整个反向传播
optimizer.step() # 再用一行更新所有参数loss.backward() 这行背后,是 PyTorch 在构建计算图时记录下的所有运算的梯度函数,顺着图反向执行了一遍。这就是自动微分(autograd)——你只管写前向逻辑,框架帮你算梯度。本质上,它相当于编译器在编译你的计算图时,自动生成了求导代码。如果你用过 Lombok 或者 MapStruct,你应该很熟悉这种感觉:声明意图,代码自动生成。
2、卷积:让计算机"看懂"图像
全连接网络做图像分类有个致命问题:一张 224×224 的 RGB 图片拉平之后是 150,528 个像素。如果第一层有 1000 个神经元,那这一层的参数量就是 1.5 亿个。别说训练了,存都存不下。
卷积神经网络(CNN)用两个关键设计解决了这个问题。
第一个是局部连接。一只猫的耳朵在一个 10×10 的小区域里,你不需要让每个神经元都去看整张图。一个神经元只看一个小窗口(叫"感受野"),然后这个小窗口从左到右、从上到下滑动,扫描整张图。这就跟你用 substring 配合滑动窗口解析日志是一个道理——每次只关注一小段。
第二个是权重共享。无论猫耳朵出现在图片左上角还是右下角,检测"这是不是耳朵"的逻辑应该是一样的。所以所有窗口位置共用同一组参数(叫"卷积核"或"filter")。
一张图胜过千言万语,但这里没有图,所以用一个直接粗暴的代码类比:
// 全连接:每个输入连到每个神经元 → 参数量爆炸
// 卷积:一个滑动窗口,共享同一组权重
public double[][] conv2d(double[][] image, double[][] kernel) {
int h = image.length, w = image[0].length;
int k = kernel.length;
double[][] output = new double[h - k + 1][w - k + 1];
for (int i = 0; i <= h - k; i++) {
for (int j = 0; j <= w - k; j++) {
double sum = 0;
for (int ki = 0; ki < k; ki++)
for (int kj = 0; kj < k; kj++)
sum += image[i + ki][j + kj] * kernel[ki][kj];
output[i][j] = sum;
}
}
return output;
}这段代码就是卷积操作的全部。没有魔法,四个嵌套 for 循环。不同的卷积核学不同的东西——有的学会检测竖边、有的学会检测横边、有的学会检测某种纹理。一层卷积通常会放几十到几百个卷积核,每个学一种模式,输出叠在一起叫"特征图"。
CNN 的经典架构就是:卷积 + 池化(缩小尺寸)+ 再卷积 + 再池化,反复几次后接上全连接层做最终分类。池化的作用是降采样——比如取每个 2×2 区域的最大值,把尺寸砍半。这既能减少计算量,又提供了一定的平移不变性(猫往左移了两个像素,不影响判断)。
3、序列:从 RNN 到 Transformer
图像是空间结构,文本、语音、时间序列是时序结构——下一个词取决于前面的词。全连接网络和 CNN 在这方面抓瞎,因为它们天然假设输入之间是独立的。
循环神经网络(RNN)是第一个专门对付序列的架构。它的核心想法非常直观:在处理第 t 个词的时候,除了看这个词本身,还看一个"记忆向量"——这个向量里装着之前 t-1 个词的累积信息。
// RNN 的核心——一个带状态循环的函数
public StateAndOutput rnnCell(State prevState, Vector input) {
Vector combined = concat(prevState, input);
Vector newState = tanh(linearTransform(combined));
Vector output = linearTransform(newState);
return new StateAndOutput(newState, output);
}你写了一个循环,每一步的输入是"上一步的状态 + 当前的词向量",输出是"新的状态 + 当前步的预测"。for 循环展开来就是一层层 RNN 单元串在一起,从头到尾扫完整句话。
但 RNN 有个严重的工程问题:梯度消失。回忆一下反向传播里梯度连乘的逻辑——如果每一层的梯度都小于 1,乘了 100 次之后梯度就接近 0 了,前面的层根本学不到东西。你写了个 100 层的循环,结果只有最后 10 层在真正学习,前面 90 层躺平了。
LSTM 和 GRU 通过引入"门控机制"缓解了这个问题——你可以粗暴地把它们理解成在 RNN 里加了几个 if 判断:"这个信息重要,保留";"这个信息过时了,丢弃"。这些门不是人为设定的,也是学出来的。LSTM 之于 RNN,就像加了缓存和过期策略的状态管理之于简单的全局变量——多了复杂度,但解决了核心痛点。
然后是 2017 年,Google 发了一篇叫《Attention Is All You Need》的论文,提出了 Transformer 架构。今天你能用上 ChatGPT、Claude、文心一言,全是这篇论文的后代。
Transformer 的核心创新是自注意力机制(Self-Attention)。RNN 是一个词一个词顺序处理的,"我"必须等前面的"今天天气真好"全部算完,才能知道自己应该接什么。Transformer 不一样——它让一句话里所有词同时互相对视:"我"直接看"今天"、看"天气"、看"真好",给每个词分配一个注意力权重,然后加权求和。没有顺序依赖,所以可以并行计算,训练速度快了一个数量级。
用代码的角度理解:RNN 是顺序遍历链表,Transformer 是直接给了一个 HashMap——O(1) 访问任意位置。当然实际的计算复杂度是 O(n²)(每个词都要看所有词),但由于高度并行,在 GPU 上跑起来远远快于 RNN 的 O(n) 串行。
自注意力的计算过程,翻译成伪代码大概是:
def self_attention(sequence):
# 每个词生成三个向量:Query(我要找什么)、Key(我有什么)、Value(我的内容)
Q, K, V = linear_q(sequence), linear_k(sequence), linear_v(sequence)
# 计算注意力分数:Query 和 Key 的点积就是"匹配度"
scores = Q @ K.transpose() / sqrt(dimension)
# softmax 把分数变成概率分布(加起来等于 1)
attention = softmax(scores)
# 用注意力权重对 Value 加权求和
return attention @ VQ @ K.transpose() 这一步就是"每个词和每个词对视"。如果你有两个词向量,它们的点积越大说明方向越接近、语义越相关。所以这个机制本质上就是让语义相关的词互相"看见"对方。
Transformer 用了一堆这样的自注意力头(Multi-Head Attention),每个头关注不同层面的关系——一个头可能关注语法结构、一个头关注语义关联、一个头关注位置关系。然后把它们拼在一起,喂给前馈网络。这个结构堆上几十层,就是今天我们看到的大语言模型的基础骨架。
4、让深度网络跑起来的工程技巧
理论上你可以把网络无脑堆深,但实践中深网络很难训练。梯度消失、梯度爆炸、训练不稳定——训练过程中 loss 突然变成 NaN 是每个 ML 新手的必经之路。以下这些技巧是过去十年里逐渐积累起来的,每一个都对应一个曾经让人头秃的问题。
批归一化(Batch Normalization):训练过程中,每一层的输入分布在不断变化——因为上一层参数在更新,输出也跟着变。这就像你的上游服务每次发版都在改接口格式,下游永远在追。批归一化强行把每一层的输出标准化为均值为 0、方差为 1 的分布,让训练稳定得多,也使更大的学习率成为可能。
残差连接(Residual Connection):这是让深层网络成为可能的关键发明。传统网络里,信息要一层层传,传到第 50 层的时候原始输入的信息基本丢光了。残差连接的做法是给每一层加一条"短路"——输出 = 本层计算结果 + 本层输入。即使这一层什么也没学到(输出接近零),原始信息依然能原封不动地传到下一层。类比一下:传统网络是串联电路,一个灯坏了全线瘫痪;残差网络是每个灯都并了一条导线,坏了的灯不影响电流通过。
# 没有残差连接
output = layer(input)
# 有残差连接
output = layer(input) + input # 这条 + input 就是"短路"ResNet 在 2015 年用 152 层的网络拿下 ImageNet 冠军,靠的就是这个设计。
学习率调度:训练刚开始的时候,模型还很蠢,迈大步快点学;到了后期接近最优解了,步长要逐渐缩小,否则会一直在最优解附近震荡。这个策略就像敏捷开发里的"先快速迭代验证方向,再精细化打磨"。
5、预训练时代:别从头造轮子了
如果你从零开始训练一个神经网络来做文本分类,你需要几百万条标注数据。但如果你拿一个已经在几十亿条文本上"读过书"的预训练模型(比如 BERT),用几百条标注数据微调一下,效果往往比你从零训练还好得多。
这个思路在软件工程里再熟悉不过:你不会自己写一个 HTTP 框架来做 Web 开发,你拿着 Spring Boot 改几行配置就上线了。预训练模型就是 ML 领域的 Spring Boot。
BERT 的训练方式也很有意思。它不用人工标注数据——它自己跟自己玩游戏。一篇文章里随机遮掉几个词(Masked Language Model),让模型猜这些遮掉的是什么。下一句话预测任务:给两句话,判断它们是不是原文里相邻的。全是自监督学习,不需要任何人工标注。等模型读完了几十亿篇文章之后,它对语言的理解已经非常深入了,你再给它具体任务,它只需要微调。
GPT 系列走的是另一条路:单向语言模型,只看左边的词预测下一个词。这听起来更简单,但配合超大规模——GPT-3 有 1750 亿个参数——展现出了惊人的"涌现能力":很多没专门训练过的任务,它也能做得不错。
预训练 + 微调的范式彻底改变了 NLP 领域的工作方式。五年之前,做一个文本分类系统需要标注团队、特征工程师、模型调参师,现在一个人拿着 HuggingFace 上的预训练模型,一个下午就能上线。这不就是"不用重复造轮子"的终极体现么?
结语
神经网络发展到现在,已经形成了一套相当清晰的技术栈:
基础算子:矩阵乘法、卷积、注意力 —— 高频调用的底层运算,交给 GPU/TPU 加速
模块化组件:全连接层、卷积层、自注意力层、归一化层 —— 像搭积木一样组装网络
训练框架:PyTorch、TensorFlow —— 自动微分、分布式训练、混合精度,屏蔽底层复杂度
预训练模型:BERT、GPT、Llama —— 开箱即用的基础能力,拿来微调就行
这个分层结构对一个有架构经验的开发者来说,应该非常亲切。每一层解决一类问题,层与层之间通过明确的接口解耦——这就是软件工程的经典设计模式。
理解神经网络不需要先成为一个数学家。把它当成一套多层抽象的计算系统,从最外层用起来,再逐步深入,这条路是完全可以走通的。当你第一次看着 TensorBoard 上那条稳稳下降的 loss 曲线,意识到这个由矩阵乘法和梯度更新驱动的东西真的在"学习"的时候,那种感觉——说实话,比写出一个高并发的秒杀系统还要爽。
拆开神经网络的黑箱:从反向传播到 Transformer
https://www.lanzlz.cn/archives/1777547347313
评论