Cleanlab 内部模块 latent_algebra 解析:噪声矩阵与潜在分布代数运算的数学内核
【免费下载链接】cleanlabCleanlab's open-source library is the standard>项目地址: https://gitcode.com/GitHub_Trending/cl/cleanlab
导读
cleanlab.internal.latent_algebra是 Cleanlab 数据质量与置信学习(Confident Learning)管线中负责潜在分布代数运算的数学内核模块。它集中实现了一组将P(noisy label)、P(noisy label | true label)、P(true label | noisy label)、P(true label)等潜在项相互换算的精确闭式函数。阅读本文后,你将理解噪声矩阵(noise matrix)与逆噪声矩阵(inverse noise matrix)的定义与互算原理、py_method四种潜在先验估计策略的取舍,以及这些函数如何支撑 count.py 中的estimate_latent与 classification.py 中的 CleanLearning 等上游能力。
一、模块定位:精确闭式方程的计算等价物
在 Cleanlab 的整体架构中,latent_algebra属于cleanlab.internal内部模块(完整模块索引见 docs/source/cleanlab/internal/index.rst)。根据 latent_algebra.py 的模块级 docstring,本模块遵循三条核心设计原则:
- 精确性保证:对其中每一个函数,只要输入是精确的,输出就保证精确——每个函数都是一条具有闭式精确形式的数学方程的计算等价物;
- 误差传播:如果输入不精确,误差自然会随运算传播(因此上游常配合裁剪、收敛等数值技巧);
- 统一记号:全模块约定用
K表示分类任务中的类别数。
模块中的核心"潜在项"包括:
| 记号 | 含义 | 形状 |
|---|---|---|
ps | P(labels = k),观测到的含噪标签的类别先验(fraction/prior) | (K,)或(1, K) |
py | P(true_labels = k),潜在真实标签的类别先验 | (K,)或(1, K) |
noise_matrix | P(label=k_s \| true_label=k_y),真实类别被误标为其他类别的比例 | (K, K),列和为 1 |
inverse_noise_matrix | P(true_label=k_y \| label=k_s),含噪类别中来自各类真实标签的比例 | (K, K),列和为 1 |
pyx | P(true_label=k \| x),给定样本x的真实类别后验 | (N, K) |
模块通过Tuple类型标注(见 latent_algebra.py)显式约束这些数组的形状,任何形状不符的输入都会触发ValueError或UserWarning(详见下文各函数)。
二、基础向量关系:噪声矩阵与逆噪声矩阵为什么可以互算
理解本模块的关键在于两条向量层面的恒等关系(代码注释中反复出现,见 latent_algebra.py 与 latent_algebra.py):
- 正向关系:
P(label=k | true_label=k) · p(true_label=k) = P(label=k),即noise_matrix · py = ps。因此给定py与noise_matrix可推出ps; - 反向关系:
P(true_label=k | label=k) · p(label=k) = P(true_label=k),即inverse_noise_matrix · ps = py。因此给定ps与inverse_noise_matrix可推出py。
这两条关系分别对应compute_inv_noise_matrix与compute_noise_matrix_from_inverse两个互逆函数,也是_converge_estimates(count.py)通过迭代迫使三个潜在估计在数值上自洽的理论依据。
三、函数逐一详解
模块共导出 6 个函数(见 latent_algebra.py),下文按"从原始数据到潜在量"的依赖顺序展开。
3.1 compute_ps_py_inv_noise_matrix:从含噪标签一步到位
签名:compute_ps_py_inv_noise_matrix(labels, noise_matrix) -> (ps, py, inverse_noise_matrix)
这是唯一一个直接接受原始标签向量作为输入的入口函数。它内部分两步完成(latent_algebra.py):
ps = value_counts(labels) / float(len(labels)) # p(labels=k) py, inverse_noise_matrix = compute_py_inv_noise_matrix(ps, noise_matrix)输入约束(latent_algebra.py):
labels:离散的含噪标签向量,数据集有K类时标签必须取值于{0,1,...,K-1};noise_matrix:形状(K, K)的条件概率矩阵,假设列和为 1。
ps的计算借助 util.py 中的value_counts,它内部使用np.unique统计频次,并支持num_classes参数为缺失类别补零。
3.2 compute_py_inv_noise_matrix:矩阵求逆求真实类别先验
签名:compute_py_inv_noise_matrix(ps, noise_matrix) -> (py, inverse_noise_matrix)
核心计算一行完成(latent_algebra.py):
py = np.linalg.inv(noise_matrix).dot(ps)这是向量恒等式noise_matrix · py = ps的矩阵求逆解法;当noise_matrix不可逆时(代码注释说明会退化为使用伪逆)。随后执行两步数值处理:
- 裁剪为合法概率(latent_algebra.py):调用
clip_values(py, low=CLIPPING_LOWER_BOUND, high=1.0, new_sum=1.0),保证没有类别概率为 0 且总和归一为 1.0; - 计算逆噪声矩阵:委托给
compute_inv_noise_matrix。
其中CLIPPING_LOWER_BOUND = 1e-6(见 constants.py),注释明确"No class should have probability 0, so we use .000001"。
3.3 compute_inv_noise_matrix:由贝叶斯公式推导逆噪声矩阵
签名:compute_inv_noise_matrix(py, noise_matrix, *, ps=None) -> inverse_noise_matrix
这是模块中最具教学价值的函数,docstring 中给出了逐元素 for 循环版本(latent_algebra.py),直观展示其贝叶斯本质:
K = len(py) # 'ps' 是 p(labels=k) = noise_matrix * p(true_labels=k) if ps is None: ps = noise_matrix.dot(py) inverse_noise_matrix = np.empty(shape=(K, K)) for k_s in range(K): # k_s:含噪标签 label == k_s 的类别值 for k_y in range(K): # k_y:猜测的真实标签 y 的类别值 # P(true_label|label) = P(label|y) * P(true_label) / P(labels) inverse_noise_matrix[k_y][k_s] = noise_matrix[k_s][k_y] * py[k_y] / ps[k_s]向量化实现(latent_algebra.py)等价但更高效:
joint = noise_matrix * py # 联合分布 P(label, true_label) ps = joint.sum(axis=1) if ps is None else ps # 若未预计算 ps,则从联合分布边缘化得到 inverse_noise_matrix = joint.T / np.clip(ps, a_min=TINY_VALUE, a_max=None) return clip_noise_rates(inverse_noise_matrix)值得注意的工程细节:
ps之所以是可选关键字参数,是因为它可以由py与noise_matrix快速推出;若已预计算则传入以省去一次矩阵乘法(latent_algebra.py);- 除法分母用
np.clip(ps, a_min=TINY_VALUE, ...)保护,其中TINY_VALUE = 1e-100(见 constants.py),避免零除; - 结果经过
clip_noise_rates(util.py)将非对角噪声率裁剪到[0, 1):先保留对角线(对角项不是噪声率,而是P(label=k|true_label=k)的一致性概率),将非对角元素np.clip到[0, 0.9999],再按列归一化使列和为 1。
3.4 compute_noise_matrix_from_inverse:逆运算还原噪声矩阵
签名:compute_noise_matrix_from_inverse(ps, inverse_noise_matrix, *, py=None) -> noise_matrix
它是 3.3 的逆变换,基于反向恒等式P(labels|y) = P(true_label|labels) · P(labels) / P(true_label)。for 循环版本(latent_algebra.py):
K = len(ps) # 'py' 是 p(true_label=k) = inverse_noise_matrix * p(label=k) if py is None: py = inverse_noise_matrix.dot(ps) noise_matrix = np.empty(shape=(K, K)) for k_s in range(K): for k_y in range(K): # P(labels|y) = P(true_label|labels) * P(labels) / P(true_label) noise_matrix[k_s][k_y] = inverse_noise_matrix[k_y][k_s] * ps[k_s] / py[k_y]向量化实现(latent_algebra.py):
joint = (inverse_noise_matrix * ps).T py = joint.sum(axis=0) if py is None else py noise_matrix = joint / np.clip(py, a_min=TINY_VALUE, a_max=None) return clip_noise_rates(noise_matrix)返回的noise_matrix列和为 1,代表每个真实类别被标成其他类别的比例。
3.5 compute_py:四种策略估计真实类别先验(核心差异所在)
签名:compute_py(ps, noise_matrix, inverse_noise_matrix, *, py_method="cnt", true_labels_class_counts=None) -> py
这是模块中策略选项最丰富的函数。py_method提供四种取值(latent_algebra.py),默认"cnt"因为它在噪声矩阵估计不佳时依然稳健——它只依赖矩阵对角线而非全部概率,而对角线通常最容易估准。四种方法的具体计算(latent_algebra.py):
py_method | 公式 | 特点 |
|---|---|---|
"cnt"(默认) | py = inverse_noise_matrix.diagonal() / clip(noise_matrix.diagonal()) * ps | 只用对角项,最稳健,等价于(true_labels_class_counts / labels_class_counts) * ps,天然避免除以零噪声率 |
"eqn" | py = np.linalg.inv(noise_matrix).dot(ps) | 直接矩阵求逆,精确但噪声敏感 |
"marginal" | py = true_labels_class_counts / sum(true_labels_class_counts) | 由 confident joint 的列边缘计数直接归一化,必须传入true_labels_class_counts,否则抛出ValueError(latent_algebra.py) |
"marginal_ps" | py = np.dot(inverse_noise_matrix, ps) | 用逆噪声矩阵作用于ps |
输入校验与兜底(latent_algebra.py):
- 若
ps形状既非(K,)也非(1, K)(如(3,1,1)或(K,1)),会发出UserWarning提示"shape should be (K, ) or (1, K)"; - 若
py_method不在四者之列,抛出ValueError,错误信息明确列出合法取值[cnt, eqn, marginal, marginal_ps]; - 无论哪种方法,最后统一经过
clip_values(py, low=CLIPPING_LOWER_BOUND, high=1.0, new_sum=1.0)裁剪归一(latent_algebra.py)。
3.6 compute_pyx:逐样本修正预测概率
签名:compute_pyx(pred_probs, noise_matrix, inverse_noise_matrix) -> pyx
该函数将模型输出的含噪预测pred_probs := P(label=k|x)修正为真实标签后验pyx := P(true_label=k|x),同样采用对角项稳健策略(latent_algebra.py):
pyx = ( pred_probs * inverse_noise_matrix.diagonal() / np.clip(noise_matrix.diagonal(), a_min=TINY_VALUE, a_max=None) ) return np.apply_along_axis( func1d=clip_values, axis=1, arr=pyx, **{"low": 0.0, "high": 1.0, "new_sum": 1.0} )约束与校验:
pred_probs必须是(N, K)的二维数组,否则抛出ValueError(latent_algebra.py),错误信息包含 "but shape should be (N, K)";- 列顺序必须对应类别 0,1,2,...;
- 文档要求
pred_probs应使用 3 折或更高折数的交叉验证计算(防止过拟合导致概率失真); - 输出逐行调用
clip_values归一化为和 1 的合法概率分布。
四、数值安全的三个基石
latent_algebra的数值稳健性依赖 util.py 与 constants.py 中的三个基础工具:
clip_noise_rates(noise_matrix)(util.py):假设列和为 1,将对角线(一致性概率)与噪声率区别对待——噪声率裁剪到[0, 0.9999],对角线原样保留,最后整体按列重新归一化;clip_values(x, low, high, new_sum)(util.py):裁剪到[low, high]后按比例重缩放以保持总和(默认保持原和,也可用new_sum指定新和,如归一化到 1.0);对多维输入会抛出TypeError;- 常量:
TINY_VALUE = 1e-100用于除法分母下界保护,CLIPPING_LOWER_BOUND = 1e-6用于概率下界裁剪(constants.py)。
五、在上游管线中的实际调用
latent_algebra不是孤立模块,它是 Cleanlab 潜在量估计的"计算底座"。
5.1 count.py:estimate_latent 与 _converge_estimates
count.py 从latent_algebra导入compute_py、compute_inv_noise_matrix、compute_noise_matrix_from_inverse等函数。核心消费者是estimate_latent(count.py),它从confident_joint出发按如下步骤组装潜在量:
- 用
value_counts_fill_missing_classes得到ps(count.py); - 由
confident_joint的行/列和分别归一化得到noise_matrix与inv_noise_matrix(count.py); - 调用
compute_py(ps, noise_matrix, inv_noise_matrix, py_method=py_method, true_labels_class_counts=...)求真实类别先验(count.py); - 若
converge_latent_estimates=True,调用_converge_estimates迭代使三者数值自洽(count.py)。
_converge_estimates(count.py)是模块互逆性质最精彩的落地:外层noise_matrix_iterations=3次、内层inv_noise_matrix_iterations=5次的嵌套循环中,反复执行compute_inv_noise_matrix→compute_py→compute_noise_matrix_from_inverse。docstring 特别提醒:迭代次数不宜过高(3–10 次足够),否则小的偏差会被反复放大;且必须先收敛inverse_noise_matrix与py(因为逆噪声矩阵依赖潜在量py),噪声矩阵依赖已知量ps故最后更新。
estimate_latent又被estimate_py_and_noise_matrices_from_probabilities(count.py)与 classification.py 中的 CleanLearning 等更高层 API 调用,构成从"含噪标签 + 交叉验证概率"到"潜在分布估计"的完整链路。
5.2 classification.py:CleanLearning 中的使用
classification.py 导入compute_py_inv_noise_matrix,在拟合流程中(classification.py)据此得到py与逆噪声矩阵,供后续噪声率校准使用。
六、测试验证:数学性质的可复现保证
tests/test_latent_algebra.py 用一组紧凑的用例验证了模块的数学正确性,核心测试数据(tests/test_latent_algebra.py):
s = [0] * 10 + [1] * 5 + [2] * 15 nm = np.array([[1.0, 0.0, 0.2], [0.0, 0.7, 0.2], [0.0, 0.3, 0.6]])关键断言包括:
- 互逆性:
compute_ps_py_inv_noise_matrix的结果满足np.dot(inv, ps) ≈ py且np.dot(nm, py) ≈ ps(误差 < 1e-3,tests/test_latent_algebra.py),即验证两条向量恒等式; - 正逆一致:
compute_inv_noise_matrix(py, nm)与compute_noise_matrix_from_inverse(ps, inv, py=py)都能还原出原始矩阵(tests/test_latent_algebra.py); - 异常路径:
compute_py对非法ps形状发出UserWarning、对缺失true_labels_class_counts的"marginal"模式抛出ValueError(tests/test_latent_algebra.py、tests/test_latent_algebra.py); - pyx 归一性:
compute_pyx输出每行和与 1 的偏差 < 1e-4,且对一维pred_probs输入抛出包含 "should be (N, K)" 的ValueError(tests/test_latent_algebra.py)。
这些测试以极小代价覆盖了"精确输入 → 精确输出"的核心承诺,是理解函数边界条件的最佳入口。
七、使用建议与限制
- 何时直接调用本模块:通常你不需要直接使用
latent_algebra,而是通过cleanlab.count.estimate_latent、estimate_py_and_noise_matrices_from_probabilities或 CleanLearning 间接受益;本模块面向需要自行实现置信学习算法的进阶用户; - 多标签支持:
estimate_latent明确声明不支持多标签分类(count.py),latent_algebra的矩阵公式同样针对单标签多分类设计; - 输入前提:所有
noise_matrix/inverse_noise_matrix输入都假设列和为 1,传入前需确认;pred_probs务必来自交叉验证以避免过拟合(latent_algebra.py); - 数值建议:
py_method优先选择默认"cnt",仅在对矩阵整体估计有信心时尝试"eqn";启用converge_latent_estimates时保持默认迭代次数即可。
综上,latent_algebra以不到 400 行代码,将置信学习所需的全部潜在分布代数运算封装为精确、可逆、数值安全的函数集合,是理解 Cleanlab "从含噪标签中恢复真实分布"原理的必读模块。
【免费下载链接】cleanlabCleanlab's open-source library is the standard>项目地址: https://gitcode.com/GitHub_Trending/cl/cleanlab
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考