行阶梯形揭示冗余 AI 特征
使用主元、自由变量和行阶梯形,诊断小型设计矩阵中的冗余特征与非唯一模型参数。
当模型有三列特征时,人们很容易假设它有三个可以学习的独立方向。但一个特征虽然存在于数据中,却可能没有增加任何新信息。如果某一列是其他列的组合,模型就可能用许多不同的参数向量拟合同样的预测。
行阶梯形能让这种隐藏结构显现出来。学完本课后,你将能够定位主元、识别自由变量、描述通解,并使用一个小型 NumPy 实现诊断冗余特征,而不会把数值警告与数学结论混为一谈。
**学习成果:**找出主元、自由变量和通解。
第 23 课:高斯消元保持解集不变介绍了保持方程组解不变的合法行运算。第 18 课:矩阵组织线性规则介绍了系数矩阵。官方 Mathematics for Machine Learning 配套网站 将本课放在当前书籍 PDF的第 2.3 节。本课中的数字、特征场景、代码、可视化和评估题均为原创。
阶梯记录方程组能够确定什么
从一个矩阵 开始。当一个矩阵满足以下三个结构规则时,它处于行阶梯形:
- 每个非零行都位于所有全零行的上方。
- 每个非零行的第一个非零项称为主元,它比上一行的主元更靠右。
- 每个主元下方的所有项都为零。
主元形成一条从左向右的阶梯。主元列是行所约束的方向。没有主元的列是一个自由方向:它的值可以变化,而方程组会据此确定其他变量。
行阶梯形不等同于简化行阶梯形。简化形式还要求每个主元等于 ,并且在其所在列中是唯一的非零项。两种形式都能揭示秩和自由变量;简化形式通常更便于读取参数化答案。
下面的可视化保留了中间状态。最终矩阵有两个主元,位于列 和 ;列 没有主元。
静态行化简记录
主元形成阶梯
每个状态都保留完整矩阵。最后一个非零行的主元比上一行的主元更靠右,而零行已经移到了底部。
原始特征矩阵 行 f₁ f₂ f₃ R1 1 2 3 R2 2 4 6 R3 1 1 2 清除主元下方的第一列 行 f₁ f₂ f₃ R1 1 2 3 R2 0 0 0 R3 0 -1 -1 将非零行上移 行 f₁ f₂ f₃ R1 1 2 3 R2 0 -1 -1 R3 0 0 0 规范化第二个主元 行 f₁ f₂ f₃ R1 1 2 3 R2 0 1 1 R3 0 0 0
高亮条目是最终结构中的主元。列 f₃ 没有主元,因此它是揭示冗余特征的自由方向。
行运算揭示了两个独立的特征方向和一个冗余列。带下划线的条目标记了最终的主元位置。
例题:一个冗余的特征列
设想一个很小的设计矩阵,它总结了三个样本中提取的三种信号:
- :检索匹配强度;
- :验证强度;
- :一个意外被记录为独立特征的组合信号。
矩阵为
在开始化简之前,先注意第三列是前两列之和:。这是一个有用的直觉,但即使某种关系无法通过观察直接看出,行化简仍然提供了有效的步骤。
首先使用左上角的项作为主元。消去它下方的项:
得到
第二行暂时变成了零行,但系统并没有丢失信息:下面的非零行仍然需要上移。交换两行,然后将新的第二行乘以 :
主元位于第 1 列和第 2 列。第 3 列没有主元,因此它是自由列。矩阵的秩是 2 而不是 3:数据包含三个命名特征,但只有两个独立方向。
自由变量描述的是一个族,而不是失败
为了理解缺失的主元意味着什么,为方程组附加一个与相同行关系相容的右端:
相同的运算得到
现在方程为
令自由变量为 。那么
的每个取值都会产生一个给出相同右端的参数向量。例如, 得到 ,而 得到 。两者都满足原来的三个方程。模型的预测是固定的,但仅凭这些观测无法识别各个权重。
零行不同于矛盾
例子中的最后一行变为
这一行是冗余的,因此没有贡献新的约束。不要把它与
混淆。在增广矩阵中,左侧为零而右端非零的行表示不相容,因此没有解。同一个左侧矩阵,根据变换后的右端不同,可能支持无穷多个解、一个解或没有解。
这个区别在数据管道中很重要。重复行或相关行可能减少独立证据的数量,却不会使方程互相矛盾。相反,矛盾行说明观测无法被所提出的线性模型精确解释。
在 NumPy 中实现主元搜索
下面的紧凑实现使用部分主元选取。在每一列中,它会从当前主元行下方选择绝对值最大的候选项,将其交换到位,规范化主元,然后清除其下方的项。容差可以防止仅仅由舍入噪声造成的数值被当作真实主元。
import numpy as np
def row_echelon(matrix, tolerance=1e-12):
a = np.array(matrix, dtype=float, copy=True)
rows, columns = a.shape
pivot_row = 0
pivot_columns = []
for column in range(columns):
if pivot_row == rows:
break
candidate = pivot_row + np.argmax(np.abs(a[pivot_row:, column]))
if abs(a[candidate, column]) <= tolerance:
continue
a[[pivot_row, candidate]] = a[[candidate, pivot_row]]
a[pivot_row] /= a[pivot_row, column]
for row in range(pivot_row + 1, rows):
a[row] -= a[row, column] * a[pivot_row]
a[np.abs(a) <= tolerance] = 0
pivot_columns.append(column)
pivot_row += 1
return a, pivot_columns
features = np.array([
[1, 2, 3],
[2, 4, 6],
[1, 1, 2],
])
echelon, pivots = row_echelon(features)
print(echelon)
print(pivots)
print(np.linalg.matrix_rank(features))
有意义的输出大致为
[[1. 2. 3.]
[0. 1. 1.]
[0. 0. 0.]]
[0, 1]
2
确切的小数取决于数据类型和容差,但这里的结构性结论是稳定的:两个主元列和一个自由列。NumPy 的 matrix_rank 同样会使用围绕奇异值的容差,而不是询问浮点数值是否严格等于零。因此,秩的判断应当连同尺度和容差一起报告,而不应被表述为关于含噪测量的永恒真理。
这对 AI 模型意味着什么
假设线性模型计算
当 时,预测变为
在这个设计中,只有 和 的和是可观测的。因此,不同的参数向量可以在矩阵的每一行上产生完全相同的预测。训练过程仍然可能返回一组权重,因为优化器、初始化、正则化项或数值路径会从这个参数族中选出一个点。这个被选中的点并不能证明数据识别出了每个特征的独立贡献。
把行阶梯形用作诊断工具,而不是验证的替代品。小样本上的秩计算可能会随着预处理、缩放、缺失值处理或新数据的变化而改变。对于更大或含噪的矩阵,还要检查奇异值的尺度和条件数。一个满秩矩阵仍然可能接近相关,从而使其解对微小的测量变化很敏感。
condition = np.linalg.cond(features)
residual = np.linalg.norm(features[:, 2] - features[:, 0] - features[:, 1])
print(condition, residual)
这里,存储值中的残差恰好为零,而浮点运算中的条件数极大(本次运行约为 )。秩严格不足的矩阵在数学上的条件数是无穷大,但浮点 SVD 可能报告一个很大的有限值。这种对比本身就是警告。在生产数据中,应使用与尺度相关的容差:很小的残差可能表示真正的近似依赖,也可能只是低于应用所关心精度的测量。
检查你的理解
问题
显示分步解答
行阶梯形要求每个非零行都位于所有全零行的上方,每个后续主元都向右移动,并且每个主元下方的项都为零。即使主元项不等于 、主元上方的项不为零,矩阵仍然可以满足这些规则。那些额外条件属于简化行阶梯形。
问题
显示分步解答
使用第一行作为主元,并将第二行替换为 :
这里只有一个主元,因此秩为 。第二行没有提供独立约束;它只是第一行放大两倍后的重复。
问题
显示分步解答
第三个变量是自由变量,因为最终的阶梯矩阵在第 1 列和第 2 列有主元,但第 3 列没有。令 后,前两个方程就能用 表示 和 。
问题
显示分步解答
这一行表示 或另一个不可能的陈述。未知数无论取什么值,都无法让左侧从零变成非零,因此方程组不相容。 则不同:它是冗余的,并且不会改变已有的解集。
问题
显示分步解答
当 时,向量为 。三行分别得到 、 和 。这些正好是所选的右端值,因此参数化解确实描述了有效解,而不只是化简后方程的解。
问题
显示分步解答
数据允许存在一族预测效果相同的参数向量。初始化、优化器的路径、正则化或隐含的数值偏好都可能从这个族中选出一个成员。返回的向量是完整训练过程作出的选择;它并不表示观测唯一识别了每个特征权重。
问题
显示分步解答
检查奇异值尺度、条件数、数据单位、测量不确定性以及秩判断使用的容差。即使数学秩为满秩,一个很小的奇异值也会使解对微小变化非常敏感。与其相信许多不稳定的有效数字,不如考虑重新缩放、收集更多独立观测、删除冗余特征或使用正则化形式。
下一课将使用行结构解释为什么数值软件通常直接求解方程组,而不是形成显式逆矩阵。主元不只是纸上的标记:它告诉你数据约束了哪些方向,以及哪些方向仍然含糊不清。