阅读 AI 推导:找出第一个出错的步骤
学习逐行阅读数学推导,揭示隐藏假设,并使用 NumPy 调试一个错误的均方误差恒等式。
三个模型预测产生了 、 和 的残差。有符号残差相互抵消,所以它们的平均值为零。中间的预测是准确的,但另外两个是错误的,它们的均方误差为 。一个看似合理的推导,可能只需把一个平方移过一个平均运算,就会把这个真实损失变成零。
调试一页代数的最快方法,不是盯着最终答案看,而是从顶部开始,追问每一行为什么能由上一行推出。停在第一个出错的等号处。后面的每一行都可能是在一个已经不是推导声称要计算的表达式上进行的正确算术。
学完本课,你将能够逐行阅读推导,为每个变换说出背后的规则,并揭示关于定义域、除数、符号或索引范围的未声明假设。你还将无需使用微积分或优化,就能在代数和 NumPy 中调试损失计算。
本课结合了前面学过的两项技能。第 6 课:求和与损失聚合说明了上下界和平均如何把带索引的损失变成一个数字。第 9 课:方程、约束与解集说明了代数操作为什么必须保持原数学断言的含义。官方 Mathematics for Machine Learning 配套网站及其导论章节把数学基础作为理解模型、调试方法并让假设显式化的工具。下面的审计方法和示例均为原创。
每一行都提出一个局部断言
推导是一系列数学陈述,其中每个新行都由定义、代数规则、已声明的假设或已经建立的结果支持。良好地阅读推导是一项局部任务。在相邻两行之间,询问四个问题:
- 发生了什么变化:符号、运算、界,还是整个表达式?
- 哪个定义或代数规则允许进行这一精确的变化?
- 该规则在什么条件下才合法?
- 新的一行提出的是同一个断言,还是只提出了一个相关断言?
连接表达式的符号告诉你要审计的是哪一种断言。
| 符号 | 读作 | 在推导中的作用 |
|---|---|---|
| “定义为” | 引入名称或约定;右侧指定左侧的含义 | |
| “与……具有相同的值” | 在当前定义域和假设下断言相等 | |
| “当且仅当” | 断言两个条件恰好具有相同的解 | |
| “推出” | 只保留一个方向;反向需要单独的理由 | |
| “近似等于” | 报告数值近似,而不是精确恒等式 |
这个方法有意区分两个问题。“作者是否得到了预期答案?”是在检验终点。“每一步是否保留了断言?”是在检验路线。错误的推理仍可能到达正确的终点,就像第一次代入错误后,许多后续步骤都可能是正确的,却仍会到达错误的终点。
移动一个平方会抹去真实损失
对于 个示例,设 是第 个预测值, 是其目标值, 是其残差。我们定义
并将均方误差(MSE)定义为
这一条件不是装饰:算术平均要除以项数,因此按照此定义,空批次没有 MSE。
现在使用三个残差:
打开这个推导图中的步骤。状态词和符号会重复颜色提示,而且每个摘要都能通过浏览器原生的展开控件使用键盘操作。
打开任意一行以查看规则及其假设。第 4 行包含第一个出错的等号。
有效第 1 行
L_MSE := (1/3) Σᵢ₌₁³ rᵢ²- 为什么这一步有效或无效
- 这一行将 N = 3 代入 MSE 的定义。
- 需要检查的条件
- 批次恰好包含三个残差,因此 N = 3 > 0。
有效第 2 行
= (1/3)((−2)² + 0² + 2²)- 为什么这一步有效或无效
- 代入三个残差值,并展开有限和。
- 需要检查的条件
- 索引范围仍按相同顺序保持为 i = 1, 2, 3。
有效第 3 行
= (1/3)(4 + 0 + 4) = 8/3- 为什么这一步有效或无效
- 分别对残差求平方,加上三个平方值,然后除以 3。
- 需要检查的条件
- 使用普通实数算术。
无效第 4 行
= ((1/3) Σᵢ₌₁³ rᵢ)²- 为什么这一步有效或无效
- 平方一般不能移到和或平均值之外。新表达式是残差平均值的平方,而不是 MSE。
- 需要检查的条件
- 等号成立需要这些残差满足特殊条件;这里没有声明该条件,而且它在本例中为假。
对新表达式有效第 5 行
= ((−2 + 0 + 2)/3)²- 为什么这一步有效或无效
- 这一行正确展开了第 4 行引入的残差平均值。
- 需要检查的条件
- 三个残差和分母都没有改变。
关于 MSE 的结论无效第 6 行
= 0² = 0,因此 L_MSE = 0- 为什么这一步有效或无效
- 残差平均值的平方为零是正确的,但第 4 行已经切断了它与 MSE 的联系。
- 需要检查的条件
- 这个结论暗中假设了第 4 行的无效等号。
前面三行计算出了实际损失:
第 4 行把它替换成了另一个量:
这里 是残差平均值的平方的名称。平方之前,相反的误差会相互抵消。MSE 先求平方,所以 和 都贡献 。运算顺序改变了这个数字所衡量的内容。
| 示例 | 残差 | 对 的贡献 | 对 的贡献 |
|---|---|---|---|
| 1 | |||
| 2 | |||
| 3 | |||
| 总计 |
展开和的平方也能看出这一步是非法的:
交叉项不会自动消失,外层平均还会贡献 而不是 。因此移动平方同时改变了项和尺度。
规则必须带着它们的条件一起移动
许多推导把最重要的信息藏在“当 时”或“在实数上”这样的短语中。去掉条件,合法的恒等式就可能变成错误的普遍断言。
考虑约分:
当 时,左侧未定义。同样,把方程 两边除以 会得到 ,但这只在附加假设 下成立。快速浏览可能会让人以为 和 是解。只要把原方程化简,行审计就会拒绝这一猜测:
对 做看似合理的除法,虽然最终从 得到了 ,但这一步本身却假设了 。它的结果与自己的许可条件相矛盾。安全的路线是在不除以可能为零的表达式的情况下重新整理方程。
平方也需要检查方向。如果 ,那么 。没有更多信息时反向推导并不成立: 允许 或 。因此,对方程两边平方可能会增加必须回到原方程检查的候选值。
平方根也带有符号条件。对于实数 ,
并不总是 。代入 得到 ,而未声明地替换为 会得到 。因此, 有两个实数解 和 ;只写 会丢失负解。
这些条件并不会让推导变得吹毛求疵。它们标明了论证实际覆盖哪些输入。
上下界和定义必须保持不变
带索引的推导还有一种隐藏状态:它的范围。下面两个表达式通常并不相等:
第二个表达式漏掉了示例 。重命名哑索引没有问题:
因为上下界和项的模式保持不变。改变上界、分母或运算是数学上的改变,需要给出理由。
定义也需要同样的纪律。一旦 ,后面的行就不能悄悄使用 。求平方会掩盖这一特定的符号反转,因为 ,但未平方的平均值会改变符号。一个推导如果只能依靠后续运算掩盖定义不一致而成立,它仍然是错误的。
NumPy 让顺序变得可见
NumPy 将 np.square 记录为逐元素运算:每个残差都被平方,同时保留输入形状。它将 np.mean 记录为沿所选轴进行的算术归约,默认则对展平数组归约。因此,二者的嵌套顺序编码了推导的顺序。
import numpy as np
predictions = np.array([8.0, 10.0, 12.0])
targets = np.array([10.0, 10.0, 10.0])
residuals = predictions - targets
mse = np.mean(np.square(residuals))
squared_mean_residual = np.square(np.mean(residuals))
print(residuals)
print(mse)
print(squared_mean_residual)
[-2. 0. 2.]
2.6666666666666665
0.0
np.mean(np.square(residuals)) 表示“先对每个值求平方,再求平均”。np.square(np.mean(residuals)) 表示“先求平均,再对这一个结果求平方”。括号是一棵紧凑的推导树;交换调用顺序就会改变所计算的量。
数值测试的逻辑力量是不对称的。上面的反例足以证伪这个普遍断言:
一次失败可以反驳“对每一个”。一次成功却不能证明它。对于残差 ,两个表达式都等于 ,但这个通过的案例不能证明其他序列也满足该恒等式。一般证明需要覆盖所声明定义域的代数;测试非常适合寻找反例和实现错误。
把审计带入 AI 代码和论文
模型代码中的损失方程经常把多个步骤压缩在一起:定义残差、逐元素求平方、沿示例轴归约,并除以计数。逐行审计让每一步都可以检查。写出预期公式,让一个代码操作对应一个数学操作,在同一次归约中保持相同的示例,并说明批次为空时会发生什么。
同样的习惯可以扩展到更长的推导,而不需要更高级的数学。为规则及其条件留出边注。数量获得名称时标记 :=。只有当两个表达式在当前假设下具有相同的值时,才使用 。只有当两个方向都保留相同解集时,才使用 。当路线出错时,报告第一个错误的变换,而不只是一个可疑的最终数字。
这种做法连接了先修代数与课程的下一个模块。当每一行都必须回答一个小问题——发生了什么变化,以及为什么允许这样变化——密集的记号就变得易于处理。
检查你的理解
问题 1
分类说明这些连接符:a := b + 1;x/x = 1(x ≠ 0);x² = 9 ⇔ x = 3;以及 1/3 ≈ 0.333。解释每个符号提出了什么断言。
显示分步解答
- 在 中, 引入了一个定义:表达式 指定了新名称 的含义。
- 在 且 时, 表示一个条件等式。它在声明的非零定义域上有效,而在 时未定义。
- 断言 在实数范围内是错误的。左侧条件还包含 ,所以两侧没有相同的解集。有效的替换是 。
- 在 中, 标记的是近似值。显示的小数接近三分之一,但并不与三分之一完全相等。
每个连接符都作出不同类型的承诺;如果把它们都当作普通等号来读,就会丢失重要信息。
问题 2
对于残差(−1、2、2),计算均方误差和残差平均值的平方。指出两种计算第一次产生差异的运算。
显示分步解答
MSE 先对每个残差求平方:
残差平均值的平方先求平均:
第一次差异出现在直接施加于残差的运算:MSE 逐元素求平方,而残差平均值的平方在平方前先求和并除法。数值 和 证实这两种运算顺序不可互换。
问题 3
审计 N = 3 时的推导:Σᵢ₌₁³ rᵢ² = r₁² + r₂² + r₃² = r₁² + r₂²。哪个等号首先出错?加入什么未声明假设后它才会成立?
显示分步解答
第一个等号是有效的,因为它展开了包含端点的索引范围 :
第二个等号删去了 ,所以它是第一个出错的等号。只有加入使被省略项为零的条件后,它才会成立:
没有这个条件,推导就悄悄把固定的索引范围从三个示例改成了两个有效项。
问题 4
在实数范围内求解 x(x + 1) = x。解释为什么两边除以 x 不是求解的有效路线,即使化简后的方程看起来会得到 x = 0。
显示分步解答
先从两边减去 并化简:
代入可以检查这个解:。
原方程两边除以 需要 ,并得到 ,因此 。这个结果违反了除法所要求的非零条件。这条路线虽然符号最后回到了原方程的解,却在其假设的定义域中没有有效候选。安全的推导从不除以可能为零的表达式。
问题 5
一个在实数上进行的推导把 √(z²) = 5 变成 z = 5。找出遗漏的情形,并指出无效的替换。
显示分步解答
对于实数 ,主平方根非负,并且
因此方程是
它的解为 和 。二者都能通过检查,因为 且 。这个推导在没有假设 的情况下错误地把 替换成了 ,因而丢失了负数情形。
问题 6
一名学生测试残差(2、2、2),发现 MSE 等于残差平均值的平方。这是否证明两个公式相同?给出一个反例并解释其中的逻辑。
显示分步解答
对于 ,两个量确实都是 :
这只建立了对一个输入序列的相等性,并没有覆盖所有允许的序列。使用 作为反例:
因为普遍恒等式声称每个序列都相等,一个反例就能证伪它。仅凭有限个通过的示例,无法证明普遍的代数断言。
问题 7
调试这行计算均方误差的 NumPy 代码:loss = np.square(np.mean(predictions - targets))。写出修正后的代码,并把其中的运算对应到公式。
显示分步解答
原代码计算的是残差平均值的平方,因为 np.mean 在 np.square 之前执行。修正后的 MSE 是
residuals = predictions - targets
loss = np.mean(np.square(residuals))第一行实现 。内部调用 np.square(residuals) 实现逐元素的 。外部的 np.mean(...) 把这些平方项相加,并除以所包含元素的数量。现在代码顺序与
相匹配。
非空输入条件仍然是实现契约的一部分。
问题 8
一个推导先把 Σᵢ₌₁ᴺ aᵢ 重命名为 Σⱼ₌₁ᴺ aⱼ,然后将其替换为 Σⱼ₌₀ᴺ aⱼ。审计这两个步骤。
显示分步解答
第一步是有效的:
索引字母是局部占位符。把 重命名为 ,既没有改变上下界,也没有改变每个位置选取的项。
第二步把下界从 改成了 :
只有在 已定义并明确假设 时,它才等于原来的和。否则新和式多出一项;如果序列没有索引 ,它甚至可能未定义。因此,第一个出错的等号就是改变下界的那一个。