AI/ML 详解:深度学习基础

Day 17 · 2026-06-03 · 难度 ★★★☆☆
面向:有编程经验的非 AI 方向工程师

反向传播Backpropagation

核心机制链式法则
一句话类比

反向传播就是神经网络的「分布式归因」(attribution)。前向传播像一次请求穿过微服务调用链,最终产生一个响应(预测)和一笔误差(loss)。反向传播则像出故障后沿调用链反向追责:算出每个服务(参数)该为最终误差负多大责任,再按责任大小修正它。它不是什么神秘魔法——本质就是对计算图做一次反向遍历

它解决什么问题 + 工作机制

痛点:一个网络有上亿个参数,怎么知道每个参数往哪个方向、调多少才能降低 loss?暴力法是逐个参数微调试错——上亿次前向传播,不可能。反向传播用链式法则(chain rule)一次反向遍历就把所有参数的梯度全算出来,成本仅约等于一次前向传播。

核心数学就一个式子。设一条路径 w → z → y → L,要求 loss 对权重 w 的敏感度:

∂L/∂w = (∂L/∂y) · (∂y/∂z) · (∂z/∂w)
「整体误差对 w 的敏感度」= 沿路径的局部敏感度连乘

直觉:∂L/∂w 读作「w 动一点点,L 会跟着动多少」。它无法直接算,但每一段局部导数都好算(z 对 w、y 对 z…都是单步运算)。链式法则把全局问题分解成「沿路径局部导数连乘」——和分布式 tracing 把端到端延迟拆成每一跳的耗时,是同一个思想。反向走的原因:先算出靠近 loss 的 ∂L/∂y,往回每一层复用上一层的结果,避免重复计算。

前向 vs 反向(同一张计算图,两个方向)

前向 → w,xz=wx+by=σ(z)L=loss
反向 ← ∂L/∂w∂L/∂z∂L/∂y1
↑ 责任从 loss 端反向「流」回每个参数,每层只做一次本地乘法
代码示例
import numpy as np
# 手写一个神经元的反向传播:y = sigmoid(w*x + b),看清「责任连乘」
x, y_true = 1.5, 1.0
w, b = 0.3, 0.0

# --- 前向:一路存下中间量,反向时要复用 ---
z = w * x + b
y = 1 / (1 + np.exp(-z))          # sigmoid 激活
L = 0.5 * (y - y_true) ** 2          # 均方误差 loss

# --- 反向:链式法则逐段相乘 ---
dL_dy = (y - y_true)                 # ∂L/∂y
dy_dz = y * (1 - y)                  # sigmoid 导数
dz_dw = x                            # ∂z/∂w
grad_w = dL_dy * dy_dz * dz_dw       # 三段连乘 = ∂L/∂w

w -= 0.1 * grad_w                    # 用梯度更新(下一概念详解)
print(f"grad_w={grad_w:.4f}  new_w={w:.4f}")
常见误区 + 实践场景
误区:「反向传播是一种学习算法」——不准确。反向传播只负责算梯度(每个参数的责任),怎么用梯度更新是优化器(下一节)的事。两者是分工:backprop = 算账,optimizer = 花钱。把它们混为一谈,会让你在调试「loss 不降」时找错地方。
📌 BigCat 场景:你做分布式系统的 root-cause 分析时,本质也是「反向归因」——从一个 SLA 违约反推每个环节的贡献。理解 backprop 后你会发现:神经网络训练 = 自动化的、可微分的归因系统。这个心智模型能帮你判断哪些工程问题适合「端到端可微」来解。
Takeaway + 思考题
💡 反向传播不是黑魔法,是计算图上的一次反向遍历 + 链式法则连乘,把「整体误差」公平摊派给每个参数。
🤔 链式法则是「局部导数连乘」。如果路径很长(很深的网络),连乘一堆小于 1 的数会发生什么?(这正是下一节激活函数要解决的痛点)

梯度下降Gradient Descent / SGD / Adam

优化迭代逼近
一句话类比

梯度下降 = 蒙眼下山。你站在山坡上看不见全局,但能用脚感知脚下哪个方向最陡(梯度),于是朝最陡的下坡方向迈一步,反复直到走到谷底(loss 最小)。后端类比:像一个反馈控制环(control loop)——测量误差、按误差方向调参数、再测量,迭代逼近目标值。「步子迈多大」就是学习率(learning rate)

它解决什么问题 + 工作机制

反向传播给了每个参数的梯度(责任方向),但梯度只告诉你往哪走,不告诉你走多远、怎么走稳。优化器解决这个。最朴素的更新规则:

θ ← θ − η · ∇L(θ)
θ=参数 η=学习率(步长) ∇L=梯度(指向上坡,取负 = 下坡)

梯度 ∇L 是「各方向偏导数组成的向量」,指向 loss 上升最快的方向,所以取负号往下走。三个关键演进:

  • SGD(随机梯度下降):不用全量数据算梯度(太贵),而是每次抽一小批(mini-batch)估计梯度。像用抽样统计估计全表聚合——省 100 倍算力,代价是梯度有噪声(路径抖动),但噪声反而能帮你跳出浅坑
  • Momentum(动量):给下山一点惯性。把历史梯度做指数移动平均(EMA)——和你做监控指标平滑是同一招——抑制抖动、加速穿过平坦区;
  • Adam:当今默认优化器。同时维护梯度的 EMA(一阶动量 m,给方向加惯性)和梯度平方的 EMA(二阶 v,估计每个参数的「波动幅度」),用 m̂/(√v̂+ε) 更新——效果是每个参数自适应步长:常波动的参数小步走,稳定的参数大步走。类似 TCP 拥塞控制按反馈自适应窗口。
学习率 η 是最关键的旋钮
η 太小 收敛极慢,几千步还在半山腰
η 合适 稳步下降到谷底 ✓
η 太大 一步迈过谷底,来回震荡甚至发散
└ 调 η 是训练第一要务:loss 爆成 NaN ≈ η 太大;loss 纹丝不动 ≈ η 太小
代码示例
import torch
# 用 PyTorch 拟合 y = 2x,对比 SGD 与 Adam 的「下山」过程
x = torch.linspace(-1, 1, 64).unsqueeze(1)
y = 2 * x
w = torch.zeros(1, 1, requires_grad=True)

opt = torch.optim.Adam([w], lr=0.1)  # 换成 SGD([w], lr=0.1) 对比收敛速度
for step in range(50):
    pred = x @ w
    loss = ((pred - y) ** 2).mean()  # MSE
    opt.zero_grad()                  # 清空上一轮梯度(否则会累加)
    loss.backward()                   # ← 这一步就是反向传播,自动算梯度
    opt.step()                        # 按梯度更新 w(θ ← θ − η·∇L)
print(f"learned w={w.item():.3f}  (target=2.0)")
常见误区 + 实践场景
误区:「Adam 自适应学习率,所以不用调 η 了」——错。Adam 调的是每个参数的相对步长,全局基准学习率仍要你设。Adam 默认 3e-4 是个常用起点,但绝非万能。忘了 zero_grad() 让梯度跨步累加,是新手最常见的「loss 莫名其妙不对」的 bug。
📌 BigCat 场景:梯度下降是「带反馈的迭代逼近」这个心智模型,可迁移到任何参数寻优——调系统配置、做 A/B 实验、甚至个人决策。关键洞见:步长(学习率)决定成败。在不确定的领域用小步快跑(小 η + 多迭代),比一次押大注更稳——这和你做渐进式发布(canary)是同一个直觉。
Takeaway + 思考题
💡 优化器 = 怎么用梯度走路。SGD 的噪声是特性不是 bug,Adam 给每个参数配了自适应步长。学习率是第一旋钮。
🤔 SGD 的随机噪声「反而有用」——它帮模型跳出尖锐的局部最优。这和退火算法、甚至进化的「随机突变」有什么共通的搜索哲学?

激活函数Activation Functions

非线性表达力
一句话类比

激活函数是夹在每层之间的非线性「门」。没有它,再深的网络也会坍缩成一层——就像一串纯转发的代理服务,无论叠多少层,整体行为还是一个线性变换。激活函数像电路里的晶体管(非线性开关):正是这点非线性,让网络能表达 if/else 式的复杂逻辑,而不只是 y = ax + b

它解决什么问题 + 工作机制

关键事实:线性的复合还是线性。两层不带激活的网络 W₂(W₁x) = (W₂W₁)x,两个矩阵相乘还是一个矩阵——等价于单层。叠 100 层也一样,毫无意义。激活函数在每层后插入一个非线性弯折,网络才能逼近任意复杂函数。常见三种:

  • Sigmoid σ(x)=1/(1+e⁻ˣ):把输入压到 (0,1)。问题:导数最大只有 0.25,深网络里链式法则连乘一堆 ≤0.25 的数 → 梯度趋近 0,即梯度消失(vanishing gradient),深层学不动。这正是上一节留的思考题;
  • ReLU max(0,x):现代默认。正区导数恒为 1,连乘不衰减,根治梯度消失;计算极简(一个比较)。代价:负区输出恒 0、导数恒 0,若一个神经元长期落在负区会永久「死亡」(dead ReLU),再也不更新;
  • GELU / SwiGLU:Transformer 时代主流。形状像「平滑的 ReLU」,负区不是硬切到 0 而是平滑过渡,缓解死亡问题、梯度更顺滑。
为什么深网络偏爱 ReLU:看导数连乘

Sigmoid 路径: 0.25 × 0.25 × 0.25 × 0.25 ≈ 0.004 (4 层就几乎归零 → 梯度消失)
ReLU 路径:  1 × 1 × 1 × 1 = 1      (正区导数恒 1,梯度畅通无阻)

坍缩风险 无激活 → 多层 = 一层 | ReLU 非线性 + 梯度不消失
代码示例
import numpy as np
# 直观验证:sigmoid 导数会消失,ReLU 不会
sigmoid = lambda x: 1 / (1 + np.exp(-x))
d_sigmoid = lambda x: sigmoid(x) * (1 - sigmoid(x))
relu = lambda x: np.maximum(0, x)
d_relu = lambda x: (x > 0).astype(float)

# 模拟 6 层网络:把每层的激活导数连乘(链式法则)
xs = np.array([0.5, -0.3, 1.2, 0.8, -1.0, 0.6])
print("sigmoid 连乘:", np.prod(d_sigmoid(xs)))  # → ~0.0007,几乎消失
print("relu 连乘:   ", np.prod(d_relu(xs)))     # → 1.0(全为正时)或 0(碰到死神经元)
# 结论:深网络默认用 ReLU 系,不是品味问题,是数学必需
常见误区 + 实践场景
误区:「激活函数是给输出加个范围限制」——本末倒置。它的根本作用是引入非线性,限制范围只是副作用。真正的工程后果:选错激活(深网络用 sigmoid)会让模型训不动,且现象隐蔽——loss 缓慢下降然后卡死,看起来像数据问题,其实是梯度消失。
📌 BigCat 场景:「线性叠加 = 单层」这个洞见有普适性——任何纯线性的流水线(一串只做加权求和的环节)都可以折叠成一步。系统设计里也一样:若每一层只做线性转发,分层就没价值。真正的表达力来自非线性环节(判断、分支、门控)——这是你设计任何多层系统时值得问的问题。
Takeaway + 思考题
💡 没有激活函数,深度学习的「深」就是假的——多层会坍缩成一层。ReLU 战胜 sigmoid 的核心是「正区导数恒 1 → 不梯度消失」。
🤔 ReLU 把负区直接砍成 0,丢掉了一半信息却效果更好。「适度的信息丢弃反而提升系统」——这和缓存淘汰、有损压缩的哲学是否相通?

正则化Regularization (L2 / Dropout)

泛化抗过拟合
一句话类比

Dropout 就是给神经网络做 Chaos Engineering。Netflix 的 Chaos Monkey 随机杀掉生产节点,逼整个系统不依赖任何单点、学会冗余容错。Dropout 在训练时随机「宕机」一部分神经元,逼网络不依赖任何单个神经元记答案——于是它学到的是鲁棒的、分布式的特征,而不是脆弱的死记硬背。

它解决什么问题 + 工作机制

痛点叫过拟合(overfitting):模型在训练集上近乎满分,一上真实数据就崩——它背下了训练样本(包括噪声),而没学到泛化规律。这是 Goodhart 定律的机器版:「为指标优化过头,指标就失去意义」。两个最常用的解药:

  • L2 正则 / 权重衰减(weight decay):在 loss 上加一项 λ·‖w‖²,对大权重罚款。求导后等价于每步把权重乘以一个略小于 1 的数(衰减)。直觉:像给「资源占用」收税——逼模型用小而分散的权重,而非靠几个巨大权重死记某些样本。小权重 = 平滑函数 = 更可能泛化;
  • Dropout:训练时每个神经元以概率 p(常 0.1–0.5)被随机置零,每个 batch 都是一张不同的「瘦身网络」。等价于同时训练指数级多个子网络并在测试时集成(ensemble)它们。测试时不丢弃,但把输出按比例缩放保持期望一致。
Dropout = 训练期的 Chaos Monkey

完整网络  n1 n2 n3 n4 n5 测试时全部在线
本 batch  n1 n3 n5 随机「宕机」n2/n4,逼 n1/n3/n5 顶上
下 batch  n2 n3 n4  换一批宕机 → 没有神经元能当「单点」

结果:网络被迫学冗余、分布式的特征 → 泛化更强
代码示例
import torch.nn as nn
# 同时用上两种正则:Dropout 层 + 优化器的 weight_decay(=L2)
model = nn.Sequential(
    nn.Linear(784, 256),
    nn.ReLU(),
    nn.Dropout(p=0.3),          # 训练时随机丢 30% 神经元
    nn.Linear(256, 10),
)
# weight_decay 就是 L2 正则的强度 λ(对大权重罚款)
opt = torch.optim.AdamW(model.parameters(), lr=1e-3, weight_decay=1e-2)

model.train()   # 训练模式:Dropout 生效
# ... 训练循环 ...
model.eval()    # 评估模式:Dropout 关闭,用完整网络(关键!别忘)
常见误区 + 实践场景
误区:「正则化越强越好,能彻底防过拟合」——错。正则化是偏差-方差权衡:加太多会矫枉过正变成欠拟合(连训练集都学不好)。另一个高频 bug:忘了 model.eval(),导致评估时 Dropout 还在随机丢神经元,结果忽高忽低、不可复现。Dropout 训练开、推理关,是铁律。
📌 BigCat 场景:「随机注入故障来提升鲁棒性」是跨域通用的智慧——Chaos Engineering(系统)、Dropout(模型)甚至生物的有性繁殖(基因重组打散依赖)都是一个母题。反过来想个人成长:刻意制造不确定、不依赖单一路径,是否也能让你的技能组合更「泛化」、更抗变化?这是「反脆弱」的工程化版本。
Takeaway + 思考题
💡 过拟合 = 死记硬背。L2 给大权重收税逼其平滑,Dropout 用 Chaos Monkey 逼其冗余——目标都是泛化而非记忆。
🤔 Dropout、L2、SGD 的噪声,本质都是「故意往训练里加干扰」却让模型更强。为什么「适度的混乱」反而是泛化的朋友?这对「过度优化必有代价」有什么启示?

深入资源Further Reading

深入思考Deep Questions

1. 本期四个概念其实是一条因果链——能把它们串成一个完整的「学习循环」吗?
能,而且这条链就是每一步训练的完整剧本:(1) 前向传播算出预测和 loss;(2) 激活函数在每层提供非线性,让这个预测有表达力(否则网络坍缩成线性、根本没东西可学);(3) 反向传播沿计算图把 loss 的责任摊派给每个参数,得到梯度;(4) 梯度下降/优化器用梯度更新参数走一步;(5) 正则化全程在背后施压,确保学到的是泛化规律而非死记。四者缺一不可:没有激活,深度是假的;没有 backprop,不知道往哪调;没有优化器,梯度只是一堆没用的数;没有正则,模型只会背答案。它们不是四个孤立知识点,而是一台机器的四个咬合齿轮。理解这条链,你就理解了「神经网络如何学习」的全部骨架——剩下的架构(CNN/Transformer)都只是在换齿轮的形状,循环本身不变。
2. 梯度消失(sigmoid 连乘趋零)和梯度爆炸是同一枚硬币的两面吗?深层网络到底难在哪?
是的,两者都是链式法则连乘的后果,只是方向相反。反向传播要把几十上百层的局部导数连乘:若每层导数普遍 <1(如 sigmoid 的 ≤0.25),连乘指数衰减 → 梯度消失,深层几乎收不到学习信号;若每层导数普遍 >1(权重初始化过大),连乘指数放大 → 梯度爆炸,loss 直接变 NaN。这就是为什么 2006 年前「深」网络几乎训不动。三个工程突破联手破解:(a) ReLU 让正区导数恒为 1,连乘不衰减(本期);(b) 合理的权重初始化(如 He/Xavier)让初始连乘维持在 1 附近;(c) 归一化层(BatchNorm/LayerNorm,后续主题)和残差连接(给梯度开一条「高速公路」绕过连乘)。深层难,本质难在「信号要穿过太多次乘法还能保持量级稳定」——这其实和分布式系统里「信号穿过多跳还要保持 SLA」是同构的难题。
3. SGD 的噪声、Dropout 的随机丢弃、L2 的权重惩罚——为什么「往训练里加干扰」反而提升泛化?
核心矛盾:模型有足够容量完美记住训练集(包括噪声),但记忆 ≠ 理解。这些「干扰」都在用不同方式阻止死记、逼出鲁棒结构:(a) SGD 噪声——mini-batch 的梯度是真实梯度的带噪估计,这种抖动让优化器避开尖锐的局部最优(那种「恰好背住这批数据」的解),偏好平坦的最优(对输入扰动不敏感 = 泛化好);(b) Dropout——随机失活逼网络学冗余特征,等价于集成指数级子网络,任何「孤注一掷依赖单点」的解都活不下来;(c) L2——惩罚大权重 = 偏好平滑函数,平滑函数在没见过的点上更可能合理外推。共同哲学:泛化的本质是「在不确定下依然稳健」,而获得稳健的方法恰恰是「在训练时主动暴露于不确定」。这与生物进化(有性繁殖打散基因依赖)、反脆弱(Taleb)、甚至禅修(在干扰中保持觉知)是同一个深层模式——稳定不来自消除扰动,而来自在扰动中依然成立。
4. 反向传播要求整条计算图「处处可微」。这个约束有多根本?它框住了今天 AI 能做什么、不能做什么吗?
极其根本——它几乎定义了现代深度学习的能力边界与盲区。可微 = loss 对每个参数有连续梯度,才能用梯度下降优化。代价是:任何离散、不可导的操作(如「采样一个 token」「做一次硬性 if 判断」「检索数据库」)都会切断梯度流,backprop 没法穿过去。这逼出一堆精巧的「绕路」工程:用 softmax 把硬选择软化成可导的概率分布、用 Gumbel/重参数化技巧让随机采样可导、用 REINFORCE/策略梯度处理不可微的奖励(强化学习的核心)。深层启示:今天的 AI 之所以擅长连续模式匹配、却不擅长离散符号推理,部分根源就在这——梯度下降天然适合「平滑可调」的世界,不适合「非黑即白」的逻辑。这也是为什么 LLM 要靠海量参数把离散的语言「嵌入」成连续向量才能学。可微性既是深度学习的超能力,也是它的牢笼——理解这条边界,你就能预判哪些问题适合端到端训练、哪些得用混合架构(神经 + 符号)来解。