说实话,当我在学习笔记目录里看到deeplearningbook_002-2这个编号时,第一反应是有点头皮发麻的。002 指的是那本经典深度学习教材的第二章,而 -2 则意味着这一章被我拆成了两个部分来处理。前半部分线性代数还能靠矩阵乘法硬撑下去,但到了概率论和数值计算这一段,身边一起自学的人几乎走丢了一半。
这个编号背后的内容,本质上是在解决一个很现实的问题:怎么把数学公式和深度学习模型真正对应起来。如果你能看懂我下面写的这些内容,那你大概率能救回不少卡在第二章的朋友,也能让自己从“背公式”切换到“用公式”的状态。这篇笔记适合正在系统读深度学习教材、却被第二章劝退的同学,也适合想补一下概率论和数值计算底子的初学开发者。
1. 002-2 是道坎:概率论和数值计算为什么总在第二章卡人
1.1 这篇笔记到底在聊什么
先把这个编号翻译成人话。deeplearningbook_002-2,在我这套笔记命名规则里,002表示某本深度学习经典教材的第二章,-2表示这一章的第二个学习包。第二个学习包的典型范围就是概率论的基础概念、常见分布、贝叶斯公式,再加上数值计算里的溢出、梯度问题、条件数。
很多人会觉得这部分跟“深度学习”关系不大,因为翻开书全是公式,看起来跟神经网络八竿子打不着。但恰恰是这些数学工具,决定了后面几乎所有模型公式的来路。拿交叉熵损失来说,它本质上就是极大似然估计取负对数;拿 Dropout 来说,它就是给神经网络的隐藏层注入随机性;拿梯度消失来说,它和反向传播里连乘的数值稳定性直接相关。所以这一包内容不是“背景知识”,而是后面每一章的“弹药库”。
我在整理这一包笔记时,给自己定了一个原则:每一个数学概念,必须找到它在深度学习里的一个落点。如果找不到落点,就先标记为“待应用”,不强行背。这个原则帮我省下了很多无效记忆。
1.2 为什么数学基础越急越学不进去
第二章劝退,不是因为内容有多难,而是因为它和“训练模型”这个目标之间隔着一层窗户纸。你刚学会 sigmoid 和梯度下降,满怀期待想看卷积神经网络,结果翻开第二章全是随机变量、概率密度、期望、协方差,大脑直接宕机。
更麻烦的是,很多人一上来就试图把每个公式从头推导一遍。推导本身没错,但如果你连“概率分布到底在描述什么”这种直觉都没有,推三步就会卡住,卡住就会烦躁,烦躁就会跳过,跳过就会心虚,最后变成“好像看完了,又好像啥也没学会”。这是典型的完美主义反噬。
我的破法是先鸟瞰,再细读。鸟瞰的意思是,先把这一章的大纲画出来,搞清楚每一节在回答什么问题。概率论在回答“数据是怎么产生的”,数值计算在回答“计算机算这些数学时为什么出错”。有了这个地图,再往下走,每一步都知道自己在哪里、要去哪里,心态就稳很多。
1.3 我采用的三条腿走路方案
针对这一包内容,我给自己搭了个学习方案,核心是三条腿同时走路:
- 教材原文:作为主线,按顺序阅读,但每天只看一到两个小节,不贪多。
- 建模视角:每学一个公式,立刻追问“深度学习里哪里用到了它”,哪怕答案暂时模糊,也要写下来。比如学到期望时,就联想“损失函数在样本分布上的平均”。
- 最小代码实验:用 Python 把公式跑一遍,哪怕只是一段十几行的 Numpy 脚本,也能让公式从纸面上活过来。
这三条腿缺一不可。只读教材,容易走神;只做实验,容易丢了理论根基;只追求建模关联,容易消化不良。我会在后面第 4 章给出几个可以直接跑的最小实验,现在先回到数学本身,把概率论这块硬骨头嚼碎。
2. 概率论:深度学习中“不确定”的数学翻译
2.1 随机变量和概率分布:模型眼中的世界
概率论一开始就讲随机变量,很多人被这个名字唬住,以为“随机变量”是什么高深的东西。其实它就是一个取值带有不确定性的变量。扔硬币的结果是随机的,所以它是一个取值“正面/反面”的随机变量;明天是否下雨是随机的,它也是一个随机变量;图像里的某个像素点、某个词在文本里是否出现,同样可以建模成随机变量。
在深度学习中,随机变量最常出现的地方是数据生成过程。我们假设训练数据是从某个未知的真实分布里采样出来的。比如手写数字图像,就可以看成从一个 28x28 像素空间上的分布里抽出来的样本。模型的训练目标,就是逼近这个分布,或者说学会这个分布的模式。
你可能会问,那神经网络输出的那个概率值是什么意思?举个手写数字识别的例子,模型对一张图片输出“是数字 7 的概率是 0.92”,这个 0.92 本质上就是在条件分布 P(y|x) 上的一个估计。y 是类别标签,x 是输入的图像,条件概率描述的是“已知图像 x 时,标签取某个值的概率分布”。所以随机变量和分布这两个概念,是理解分类、生成模型、强化学习所有后续内容的地基。
2.2 联合概率、条件概率和贝叶斯公式:掌握推理的杠杆
这三个概念在一起,构成了概率推理的核心骨架。联合概率 P(A,B) 说的是 A 和 B 同时发生的概率;条件概率 P(A|B) 说的是在 B 已发生的前提下 A 发生的概率。两者之间有一个简单的换算关系:P(A,B) = P(A|B)P(B),也可以写成 P(A,B) = P(B|A)P(A)。这两个表达一结合,就得到了贝叶斯公式:
P(A|B) = P(B|A)P(A) / P(B)
很多初学者把贝叶斯公式当成一个需要死记的公式,其实它说的是一件非常自然的事:用新的证据去更新你对一件事的原有看法。P(A) 是你看到证据前对 A 的信念,叫先验;P(A|B) 是你看到证据后对 A 的信念,叫后验;P(B|A) 是“如果 A 是真的,那么 B 有多可能出现”的解释力。
写到这里,必须插一句我自己的体会:贝叶斯公式一开始记混是正常的。我当时是画了一棵事件树才把它彻底搞明白的。先画 A 的两个分支,A 发生和 A 不发生,再在每条分支下画 B 发生和不发生,然后去看 B 发生的那一列,你会发现 P(A|B) 的分子正好是“B 发生时且 A 发生”的那一条路径,而分母是所有“B 发生”的路径总和。对照树图一目了然,比干记公式牢得多。
在深度学习里,贝叶斯视角最直接的应用是贝叶斯神经网络和模型不确定性估计。不过在入门阶段,你只要把“先验-似然-后验”这个结构记在心里就够了,后面学到正则化时,你会再遇到它。
2.3 期望和方差:模型评估的度量标准
期望和方差,这两个词在概率论里出现得极早,也极容易被一笔带过。但它们是理解“模型好不好”的核心。
期望在离散情形下就是加权平均:E[X] = Σ x P(x)。它描述的是一个随机变量“平均来看”会取什么值。放到深度学习里,损失函数在数据分布上的期望,就是所谓的期望风险。我们训练模型时做的,本质上就是希望找到一组参数,让这个期望风险尽量小。当然现实中拿不到真实分布,只能用训练数据上的经验分布去近似,所以有了经验风险最小化,也就是大家常说的把训练集上的平均损失降下去。
方差描述的则是随机变量的离散程度:Var[X] = E[(X - E[X])^2]。它回答的是“这个随机变量的取值稳定不稳定”。在模型训练里,方差对应的是“模型在不同数据子集上表现一致不一致”。一个模型在训练集上表现很好、在验证集上一塌糊涂,从概率视角看,可以理解为它把训练集的噪声也学进去了,导致在新样本上的随机误差很大。这个问题后面会用一个更具体的词来描述:过拟合。
我自己理解期望和方差时,用过一轮骰子模拟实验,跑了 100 万次掷骰子,然后计算均值和方差,看到它接近理论值的那一刻,才真正意识到“概率论说的不是某一个具体结果,而是大量重复后的统计规律”。这个直觉特别重要,因为深度学习中几乎处处都在和“统计规律”打交道。
3. 数值计算:学习算法稳定的隐形地基
3.1 溢出与下溢:为什么深度学习模型经常 NaN
这一节的内容常常被初学者跳过,但训练过模型的人基本都遇到过这种场景:loss 突然变成 nan,前几分钟还好好的,突然整个训练崩了。很多时候问题就出在数值溢出上。
计算机里的浮点数是有上限的,单精度 float32 的最大值大约是 3.4e38,超过这个范围就会变成 inf,再做运算就可能变成 nan。深度学习模型里最常见的溢出场景是 softmax。softmax 的公式是 e^{x_i} / Σ_j e^{x_j},如果输入里有几个数是 1000,那 e^{1000} 直接超出表示范围,算出来就是 inf / inf,结果是 nan,整个损失函数直接炸掉。
解决办法并不复杂,而且是有严谨数学依据的。softmax 对输入同时减去一个常数,结果不变,因为分子分母同时缩小了同样的倍数。实际实现里通常减去输入的最大值,这样最大指数就变成 e^0 = 1,所有指数项都在一个安全的范围内。这个技巧叫 log-sum-exp 的偏移实现,是模型稳定训练的标配。
我第一次在自定义网络里跑 softmax 时没做这个处理,loss 在第几千步突然变成 nan,排查了一个下午。后来把所有中间结果打印出来,才发现是 logits 里出现了几个上千的极端值。这个坑踩过之后,我写任何带 softmax 的模块,第一行都会习惯性地先减 max,已经成了肌肉记忆。
3.2 梯度消失与梯度爆炸:训练不动的元凶
梯度问题,在深度学习教材的数值计算章节里会有一个数学层面的解释,通常和矩阵的特征值有关。反向传播的本质,是把损失函数对每一层参数的导数,通过链式法则一层一层传回去。这个过程里会出现很多矩阵乘法。如果这些矩阵的特征值普遍大于 1,乘的次数多了,梯度就会指数级放大,变成梯度爆炸;如果特征值普遍小于 1,梯度就会指数级衰减,变成梯度消失。
这个“指数级”三个字是关键。深度网络的层数只要到了几十层,哪怕每一层只乘一个 0.9,50 层之后就是 0.9 的 50 次方,大约是 0.005,梯度已经小得几乎不更新;反过来如果每层乘 1.1,50 层后就是 117 倍,直接爆炸。
之前看到很多初学者在这块的反应是“那我不用深层网络不就行了”。说的没错,但你迟早要面对 transformer、残差网络这些深层结构,所以这个知识点跑不掉。后面你会学到 ResNet 的残差连接、注意力机制里的缩放点乘、各种归一化层,它们本质上都在和“让梯度在深层网络中保持稳定”这个问题搏斗。现在在数值计算这一节理解了这个根子,后面看到那些结构时会有一种“原来你是在解决这个问题”的感觉。
3.3 条件数与病态问题:优化器在这里“晕头”
条件数是数值计算里一个重要但容易被忽略的概念。它描述的是一个函数对输入变化的敏感程度。条件数太大的问题被称为病态问题,意思是输入稍微变化一点,输出就会产生巨大的波动。
放到深度学习里,条件数大对应的是**优化曲面特别“狭长”**的情况。你可以想象一个碗,正常碗是圆的,梯度方向四面八方都一样;病态问题的碗是一个特别扁的椭圆,沿着长轴方向梯度很小,沿着短轴方向梯度很大。梯度下降在这个椭圆上会来回震荡,更新路径像在走“之”字形,明明看着在下降,速度却慢得离谱。
这就解释了为什么后来需要引入动量、自适应学习率这类优化器。它们本质上就是在“不同方向的梯度尺度差异很大”的场景下,做一种补偿和平衡。AdaGrad、RMSProp、Adam 这些名字后面,藏的其实是同一个问题。所以你在数值计算这一章里提前知道了病态条件这个概念,后面读优化器章节会轻松很多,否则你会觉得 Adam 那套方均值更新规则像是在变魔术。
4. 实操:用代码和笔记把数学“焊接”到模型上
4.1 从理论到代码:最小概率模型模拟
这一节我会给你几个可以直接跑的实验脚本,配上对应概念。第一个实验用来建立“期望和方差”的直觉。你可以用 Numpy 模拟掷骰子,看看实际统计值和大数定律的关系。
import numpy as np np.random.seed(42) samples = np.random.randint(1, 7, size=100000) theoretical_mean = 3.5 theoretical_var = 35 / 12 # 均匀分布离散情形的方差公式 empirical_mean = samples.mean() empirical_var = samples.var() print(f"理论期望: {theoretical_mean:.4f} 实际均值: {empirical_mean:.4f}") print(f"理论方差: {theoretical_var:.4f} 实际方差: {empirical_var:.4f}")跑完你会发现实际均值和方差非常接近理论值,但不会完全相等。这就是统计规律的本质:样本量越大越接近,但单次采样永远有随机性。我把这个脚本跑过很多次,每次看到结果都在提醒自己:深度学习里用一批数据去估计梯度,本身就是一个带随机性的估计,所以训练过程的 loss 曲线有波动是正常的,不用一看到波动就慌。
第二个实验模拟条件概率和贝叶斯。我之前写过一个极简的“人群患病检测”模拟器,用来理解“检查阳性不代表一定患病”这件事。这个经典例子比任何公式都更能帮你记住贝叶斯公式的分母在做什么。
import numpy as np population = 100000 disease_rate = 0.01 false_positive_rate = 0.05 true_positive_rate = 0.99 has_disease = np.random.rand(population) < disease_rate test_positive = np.zeros(population, dtype=bool) # 患病且检测阳性 test_positive[has_disease] = np.random.rand(has_disease.sum()) < true_positive_rate # 未患病但误报阳性 test_positive[~has_disease] = np.random.rand((~has_disease).sum()) < false_positive_rate positive = test_positive.sum() positive_and_disease = (test_positive & has_disease).sum() print(f"检测阳性人数: {positive}") print(f"阳性确实患病比例(后验): {positive_and_disease / positive:.4f}")这个结果通常会让你吃惊:即使检测准确率高达 99%,真正阳性的概率也可能不到 20%。原因就是人群里患病的人本来就少,误报基数太大。跑完这个实验,P(A|B) 的分子是“真阳性”,分母是“所有阳性”,这个结构就再也忘不掉了。
4.2 数值计算演示:稳定 softmax 的实现
第三个实验,复现 softmax 下溢问题,然后验证减最大值这个技巧。
import numpy as np def softmax_bad(logits): exp = np.exp(logits) return exp / exp.sum() def softmax_stable(logits): shifted = logits - np.max(logits) exp = np.exp(shifted) return exp / exp.sum() logits = np.array([1000, 900, 800, 990]) print("不稳定版本:", softmax_bad(logits)) print("稳定版本:", softmax_stable(logits))不稳定版本一跑就是nan或者inf/inf的警告,稳定版本则输出一个概率向量。你可能会想,减掉最大值后结果真的和原来一样吗?数学上确实是恒等的,因为分子分母同时除以了 e^{max}。但代码上,这一个小改动决定了训练会不会突然崩溃。
我强烈建议你在自己的项目里主动写一次这种“出问题版本”的代码,亲眼目睹 nan 是怎么来的。有过一次触发 nan 的完整经历,你对“数值稳定性”五个字的理解会超过看十遍书。这也是我把这个实验放进笔记里的原因。
4.3 我的笔记模板和复习节奏
代码实验做完之后,还有一个很重要的问题:数学知识太多,怎么记笔记才能不变成抄书?我的做法是给每个概念建一张“概念卡”,每张卡固定四个区块:一句话直觉、数学表达、深度学习落点、易错点。一张典型的概念卡长这样:
- 一句话直觉:牛顿第二定律有很多近似,期望就是大量随机实验结果的平均中心。
- 数学表达:E[X] = Σ x P(x)
- 深度学习落点:交叉熵损失可以被理解为模型在真实数据分布上的负对数似然的期望近似。
- 易错点:期望的线性性 E[aX+b]=aE[X]+b 永远成立,但方差没有线性性,Var[aX+b]=a²Var[X],很多人把系数直接提出来导致算错。
四块写完,这一张卡就算过关。如果哪一个区块写不出来,说明这个知识点还没被消化,我会重新回去看书,而不是硬着头皮往后面翻。
复习节奏我采用的是间隔重复,大致节奏是:当天学完复习一次,第二天复习一次,三天后复习一次,一周后再复习一次。听起来占时间,但每次复习只需要 10 到 15 分钟,而且因为笔记里写的是“自己脑子里的话”,复习效率比重新翻书高得多。这套方法帮我扛过了整本教材前面最枯燥的章节。
5. 自学者最常踩的五个坑及排除方法
5.1 背公式不等于理解数学:三种记忆方式对比
我在带人自学的时候,发现一个特别普遍的做法:把公式抄下来,然后反复默写。这种纯机械记忆在数学学习里几乎毫无用处,因为公式是用来推导和应用的,不是用来默写的。我自己的对比经验是,默写公式一周后基本就忘了,但用“一句话直觉+几何图像+代码实验”三合一记忆,三个月后还能给别人讲明白。
举一个典型例子,很多人背过方差的公式 Var[X] = E[X²] - (E[X])²。如果你只是默写,考试时很可能会把符号记反。但如果你知道它的直觉含义——方差是“偏离均值的平均平方”——你马上就能从定义式 Var[X] = E[(X-E[X])²] 一步一步推到这个简写形式。所以我给自己定的规矩是:教材里的每一个公式,先看它从哪里来、代表什么直觉,再谈记忆。
5.2 矩阵和概率对不上模型:尺寸标注法
另一个高频问题,是符号和模型对不上。书上用大写的 X 表示随机变量,用粗体 W 表示权重矩阵,初学者经常搞混。我用了一个笨但有效的办法:在笔记里给每个符号标注尺寸和含义。比如权重矩阵 W 就写清楚“W 形状是 [输出维度, 输入维度]”,样本 X 就写清楚“X 形状是 [批量大小, 特征维度]”。
这样在学线性层时,你会直接写下 y = Wx + b 然后标尺寸:x 是 [D_in],W 是 [D_out, D_in],y 是 [D_out],一看就知道矩阵乘法为什么是 W 在前。概率符号同理,P(y|x) 就写上“给定 x 时 y 的概率,x 是条件,y 是随机变量”。养成这个习惯之后,你再看到任何公式,第一反应都是“它输入什么形状、输出什么形状、形状怎么变”,数学公式就不再是一堆风干符号了。
5.3 完美主义拖延:用九十一原则
在啃这一章时,我还遇到过一个问题:总想把每个定理的证明都看懂再往下走。结果经常是看一个证明用了两小时,看完还不确定自己有没有看懂,第二天就放弃了。后来我给自己定下一个“九十一原则”:90% 的精力放在理解核心概念和直觉上,10% 的精力放在推导细节上。如果一个证明暂时看不明白,就先用红笔标注“此处待回补”,跳到后面的应用小节。
这个原则帮我绕开了很多无意义的卡死。等到后面在实际模型里用到某个定理时,再回头去看,会突然觉得自己能看懂了。这是因为数学证明只有在你知道结论有什么用之后,它的每个步骤才有意义。我之前在贝叶斯公式的证明上卡了两天,后来在概率模拟实验里用了三分钟,就再也不想纠结那个证明了。
5.4 如何判断自己真的学会了:费曼测试
学完一个概念,最有效的自测方法不是我上一节说的默写公式,而是费曼测试:能不能用大白话把“期望是什么”讲给一个完全没学过概率论的人听,并且让他听懂?如果能,说明你真的懂了;如果不能,说明你还在靠术语填窟窿。
我每次学完一小节,都会在手机备忘录里用语音给自己录一段 60 秒以内的解释,然后重新听一遍。这个习惯听起来有点傻,但它非常管用。当你发现自己在解释里频繁用到“就是那个……你懂吧”这种话时,其实就已经暴露了理解漏洞。这时候不要急着往下学,回去把漏洞补上,效果远好于刷十遍笔记。
5.5 多线作战策略:教材、课程、代码怎么安排
最后一个坑是学习资源太多,反而造成瘫痪。有人既买了教材,又收藏了一堆视频课,还同时想上 Kaggle 刷比赛,结果哪条线都没走完。我自己的经验是,以教材为主线,视频只用来消化学不懂的特定章节,代码实验作为课后作业。这三个角色不能反过来。
具体到第二章,我的节奏是:早晨看教材一小节并做笔记,下午写 20 分钟的 Numpy 小实验验证公式,晚上用十分钟做费曼测试。一周大概推进两个小节,听起来慢,但因为我中途没有反复“推翻重来”,实际两个月后已经能把“概率论和数值计算”相关的数学基础讲得明明白白。这个速度反而比那些一周硬啃五章的人快得多。
写在最后:这一章之后你会获得什么
如果只看教材的目录,002-2 这个编号覆盖的内容就是概率论基础和数值计算,听起来平平无奇。但等你真正把这一包内容消化完,再回到深度学习的主线,会发现到处都是熟悉的影子:softmax 不会让你觉得只是查表用的函数,交叉熵不再是天外飞仙,Adam 优化器也不再是黑洞一样的黑盒。你会开始理解模型训练过程中各种 nan、震荡、不收敛的现象背后的数学来源。
我个人把这一段经历总结成一句口诀:不确定的世界用概率描述,不稳定的计算用数值方法稳住。概率论帮你建模问题、评估效果,数值计算帮你把模型真正跑起来。两者一起,才是深度学习中“做实验”和“调 bug”的共同地基。
如果你也正在啃这一章,我给你一个最踏实的建议:别急着看完。谁先在后边代码里碰到了 nan,谁再来读这一章最合适。现在没遇到也没关系,先扎扎实实把一张张概念卡做出来,等到采坑那天,你会感谢自己当时多花的那一周。