cleanlab benchmarking 噪声标签合成指南:noise_generation 模块全解析
【免费下载链接】cleanlabCleanlab's open-source library is the standard>项目地址: https://gitcode.com/GitHub_Trending/cl/cleanlab
导读
本文聚焦 cleanlab 仓库中 cleanlab/benchmarking 子包及其核心模块 noise_generation.py(对应文档 docs/source/cleanlab/benchmarking/index.rst 与 noise_generation.rst)。该模块专为基准测试(benchmarking)cleanlab 核心算法而设计,负责向分类数据集的标签中注入可控的合成噪声:生成"可学习"的噪声矩阵、依据噪声矩阵批量生成带噪标签,并支持按 trace(矩阵迹)精确控制噪声总量。读完本文,你将掌握噪声矩阵的数学定义与合法性判定规则,能够用几行代码构造任意类别数、任意噪声强度的受控实验数据,从而客观评估 cleanlab 在标签噪声下的纠错与学习能力。
一、模块定位:为基准测试而生
cleanlab.benchmarking是 cleanlab 中专门服务于基准测试与算法评估的子包,其__init__.py仅一行:from . import noise_generation,即对外暴露的唯一功能模块。官方 API 文档通过 Sphinx 的automodule指令自动渲染该模块的完整成员签名与 docstring(见 index.rst),因此本文介绍的所有函数签名、参数默认值均与源码一一对应。
从模块 docstring 可以明确其设计目标:
Helper methods that are useful for benchmarking cleanlab's core algorithms. These methods introduce synthetic noise into the labels of a classification dataset.
即:通过人为向标签中注入合成噪声,构造带噪数据集,用于检验 cleanlab 核心算法(如CleanLearning、标签问题发现等)在受控噪声条件下的表现。该模块提供四个公开函数,彼此构成一条完整的"生成噪声矩阵 → 校验合法性 → 注入带噪标签"流水线:
| 函数 | 作用 |
|---|---|
noise_matrix_is_valid(noise_matrix, py, verbose=False) | 判定噪声矩阵是否"可学习"(learnable) |
generate_noisy_labels(true_labels, noise_matrix) | 依据噪声矩阵把完美标签翻转为带噪标签 |
generate_noise_matrix_from_trace(K, trace, ...) | 生成指定 trace 的随机噪声矩阵 |
generate_n_rand_probabilities_that_sum_to_m(n, m, ...) | 生成 n 个和为 m 的随机概率(底层工具) |
randomly_distribute_N_balls_into_K_bins(N, K, ...) | 把 N 个球均匀随机分配到 K 个桶(底层工具) |
二、核心概念:什么是噪声矩阵(Noise Matrix)
2.1 数学定义
噪声矩阵是理解整个模块的钥匙。设数据集有K个类别,噪声矩阵是形状为(K, K)的二维数组,其元素定义为:
noise_matrix[k_s][k_y] = P(label = k_s | true_label = k_y)即真实类别为k_y的样本被标注为k_s的条件概率。noise_matrix[k][k](对角线)表示真实类别k被正确标注的概率;非对角线元素noise_matrix[i][j](i != j)表示类别j的样本被错误标为i的噪声率(noise rate)。
2.2 两个结构性约束
从源码noise_matrix_is_valid的实现(noise_generation.py)可看出,合法噪声矩阵必须满足两个结构条件:
- 列和为 1(左随机矩阵,left stochastic matrix):每一列代表给定真实标签下所有可能标注结果的条件概率分布,必须满足
sum(noise_matrix[:, k]) = 1。源码注释明确"Assumes columns of noise_matrix sum to 1"。 - 联合概率矩阵合法:设
py为真实标签先验P(true_label=k),则联合分布joint_noise = noise_matrix * py必须满足|joint_noise.sum() - 1.0| < 1e-6,其中1e-6来自 cleanlab/internal/constants.py 定义的FLOATING_POINT_COMPARISON浮点比较阈值。
2.3 "可学习"(learnable)条件
仅满足结构约束还不够。noise_matrix_is_valid的真正目的是判断:给定这个噪声矩阵,理论上能否从带噪标签中学到优于随机猜测的性能。其判定条件为:对每个类别k,必须满足
p(label=k) * p(true_label=k) < p(true_label=k, label=k)对应源码:
ps = np.dot(noise_matrix, py) # P(label=k) joint_noise = np.multiply(noise_matrix, py) # P(label=k, true_label=k') ... if not (ps[i] * py[i] < joint_noise[i][i]): return False直观理解:对角线上的联合概率(样本真实类别与标注一致)必须"足够大",即正确标注的样本在概率意义上占主导,否则标签噪声已大到无法从中学习任何有效信号。这就是"对于给定噪声量,平均而言能取得优于随机的表现"的数学刻画。
三、判定函数:noise_matrix_is_valid 的用法与输出
3.1 函数签名与参数
def noise_matrix_is_valid(noise_matrix, py, *, verbose=False) -> boolnoise_matrix:形状(K, K)的条件概率矩阵,列和须为 1。py:形状(K,)的真实类别先验,py[k] = P(true_label = k)。verbose:默认为False;置为True时打印每个类别的诊断信息(如E1E2/C、E1、E2、C、O及不等式左右两侧数值),便于排查矩阵不可学习的原因。
返回True表示矩阵可学习;否则返回False。源码中verbose输出示例如下(noise_generation.py):
print("E1E2/C", round(E1 * E2 / C), "E1", round(E1), "E2", round(E2), "C", round(C), "|", round(E1 * E2 / C + E1 + E2 + C), "|", round(E1 * E2 / C), "<", round(O)) print(round(ps[i] * py[i]), "<", round(joint_noise[i][i]), ":", ps[i] * py[i] < joint_noise[i][i])3.2 测试验证:非法矩阵的判定
test_noise_generation.py 给出了多组非法矩阵样例,可用于自测对判定的理解。例如:
nm = np.array([[0.2, 0.5], [0.8, 0.5]]) # 列和不为 1(第 2 列 0.5+0.5=1,第 1 列 0.2+0.8=1) py = [0.1, 0.8] assert not noise_generation.noise_matrix_is_valid(nm, py, verbose=verbose)更典型的不可学习矩阵(噪声率过高、对角占优不足):
nm = np.array([[0.1, 0.9], [0.9, 0.1]]) # trace = 0.2,对角概率极低 py = [0.1, 0.9] assert not noise_generation.noise_matrix_is_valid(nm, py) # 返回 False测试还覆盖了verbose=True/False两种模式(test_invalid_inputs_verify参数化用例),说明该函数在两种模式下行为一致,仅输出诊断日志不同。
四、核心生成函数:generate_noise_matrix_from_trace
这是模块中使用频率最高的函数,负责生成一个"对角线之和恰好等于指定 trace"的随机噪声矩阵。trace 是控制标签噪声总强度的关键旋钮。
4.1 函数签名与完整参数表
def generate_noise_matrix_from_trace( K, trace, *, max_trace_prob=1.0, min_trace_prob=1e-5, max_noise_rate=1 - 1e-5, min_noise_rate=0.0, valid_noise_matrix=True, py=None, frac_zero_noise_rates=0.0, seed=0, max_iter=10000, ) -> Optional[np.ndarray]| 参数 | 默认值 | 含义与取值约束 |
|---|---|---|
K | 必填 | 类别数,生成(K, K)矩阵;要求K >= 2,否则抛ValueError("K must be >= 2") |
trace | 必填 | 返回矩阵对角线元素之和(np.trace(noise_matrix)),即所有类别的平均正确标注概率之和 |
max_trace_prob | 1.0 | 对角线任一元素的最大概率 |
min_trace_prob | 1e-5 | 对角线任一元素的最小概率 |
max_noise_rate | 1 - 1e-5 | 任一非对角线元素(噪声率)的上限 |
min_noise_rate | 0.0 | 任一非对角线元素的下限 |
valid_noise_matrix | True | 是否保证矩阵"可学习";为True时要求trace > 1,且K > 2时必须传入py |
py | None | 真实类别先验,valid_noise_matrix=True且K > 2时必填,否则抛ValueError("py must be provided...") |
frac_zero_noise_rates | 0.0 | 非对角线元素中被置 0 的比例(0~1),用于构造"结构化稀疏噪声" |
seed | 0 | 随机数种子,保证实验可复现 |
max_iter | 10000 | 尝试生成合法矩阵的最大迭代次数;超限返回None |
返回值:形状(K, K)的噪声矩阵(条件概率矩阵、左随机矩阵);若max_iter用尽仍未生成满足条件的矩阵,返回None。
4.2 参数约束与报错行为(源码级)
源码开头集中处理参数合法性(noise_generation.py):
valid_noise_matrix=True且trace <= 1:抛ValueError,错误信息为"trace = {}. trace > 1 is necessary for a valid noise matrix to be returned"。这与"可学习"条件直接相关:trace 不大于 1 意味着平均正确标注概率不超过 1/K,噪声过大。测试 test_trace_less_than_1_error 与test_trace_equals_1_error分别用trace=0.5、trace=1验证了该错误。valid_noise_matrix=True且py is None且K > 2:抛ValueError("py must be provided (not None)..."),对应测试test_valid_no_py_error。K <= 1:抛ValueError("K must be >= 2, but K = {}."),对应测试test_one_class_error。max_iter < 1:直接返回None,对应测试test_max_iter中max_iter=0的用例。
4.3 生成算法内部原理
生成过程是"随机采样 + 合法性过滤"的迭代框架(for z in range(max_iter)循环,noise_generation.py):
- 生成对角线:调用
generate_n_rand_probabilities_that_sum_to_m(n=K, m=trace, ...)随机生成 K 个和为 trace 的对角概率,填入主对角线。 - 分配零噪声率:根据
frac_zero_noise_rates计算需要置零的非对角线元素个数,再用randomly_distribute_N_balls_into_K_bins把零噪声率按列均匀分配(每列最多K-2个零,因为每列需保留对角线元素且列和须为 1)。 - 生成非对角线噪声率:对每一列,用
generate_n_rand_probabilities_that_sum_to_m(n=num_noise, m=1 - nm_diagonal[col])生成该列噪声率(和为1 - 对角线值),再随机选择非对角线行号填入。 - 合法性过滤:若
valid_noise_matrix=False直接返回;否则调用noise_matrix_is_valid校验,通过才返回。若迭代max_iter次仍未通过,返回None。
K=2 的特殊快速路径(noise_generation.py):源码注释指出"Every 2x2 noise matrix with trace > 1 is valid because p(y) is not used",即二分类时任何 trace > 1 的矩阵都自动可学习,无需py。当frac_zero_noise_rates >= 0.5时直接构造含单个零噪声率的闭式解矩阵;否则用 Dirichlet 分布采样对角线后对称填充。
4.4 复现实验的标准用法
结合 test_noise_generation.py 的主流程测试,一个完整的受控噪声实验如下:
import numpy as np from cleanlab.benchmarking.noise_generation import ( generate_noise_matrix_from_trace, generate_noisy_labels, noise_matrix_is_valid, ) trace = 1.5 # 对角线之和,>1 才可能可学习 py = [0.1, 0.1, 0.2, 0.6] # 真实标签先验,K=4 K = len(py) n = 10 # 每类样本数(示例用小值,实际可放大) # 构造真实标签 y y = [z for i, p in enumerate(py) for z in [i] * int(p * n)] # 生成 trace=1.5 的合法噪声矩阵 nm = generate_noise_matrix_from_trace( K=K, trace=trace, py=py, seed=0, valid_noise_matrix=True, frac_zero_noise_rates=0.0, ) # 依据噪声矩阵注入带噪标签 s = generate_noisy_labels(y, nm) # 自检:矩阵合法性 + 结构约束 assert noise_matrix_is_valid(nm, py) assert abs(trace - np.trace(nm)) < 1e-2 # trace 正确 assert abs(nm.sum() - K) < 1e-4 # 所有元素和为 K(列和为 1) assert all(abs(nm.sum(axis=0) - 1) < 1e-4) # 每列和为 1 assert abs(np.sum(nm * py) - 1) < 1e-4 # 联合分布和为 1上述断言全部来自test_main_pipeline,是验证噪声矩阵正确性的黄金标准。仓库测试中还展示了两种常见参数模式:
- 高稀疏噪声:
frac_zero_noise_rates=0.75(见test_main_pipeline_fraczero_high),适合构造"部分类别间完全无噪声"的现实场景。 - 平均 trace 设定:
trace=avg_trace * K(见 test_classification.py 与 test_multiannotator.py),其中avg_trace表示平均每类的正确标注概率,语义更直观。
五、注入噪声:generate_noisy_labels
generate_noisy_labels把"完美标签"转换为"恰好产生给定噪声矩阵"的带噪标签。
5.1 签名与参数
def generate_noisy_labels(true_labels, noise_matrix) -> np.ndarraytrue_labels:形状(N,)的完美标签数组,类别为0, 1, ..., K-1。noise_matrix:形状(K, K)的条件概率矩阵(列和为 1)。
返回形状(N,)的带噪标签数组,其中被翻转的样本数与noise_matrix的期望翻转数量精确匹配。
5.2 实现原理:按联合计数精确翻转
源码实现(noise_generation.py)采用"期望计数 + 无放回随机采样"策略:
true_labels = np.asarray(true_labels) K = len(noise_matrix) py = value_counts(true_labels) / float(len(true_labels)) # 从数据估计先验 count_joint = (noise_matrix * py * len(true_labels)).astype(int) # 期望联合计数 np.fill_diagonal(count_joint, 0) # 对角线不涉及翻转,清零 labels = np.array(true_labels) for k in range(K): # 对每个真实类别 k labels_per_class = np.where(count_joint[:, k] != 0)[0] label_counts = count_joint[labels_per_class, k] noise = [labels_per_class[i] for i, c in enumerate(label_counts) for z in range(c)] idx_flip = np.where((labels == k) & (true_labels == k))[0] if len(idx_flip) and len(noise) and len(idx_flip) >= len(noise): labels[np.random.choice(idx_flip, len(noise), replace=False)] = noise要点:
- 先验
py直接从true_labels用value_counts(定义于 cleanlab/internal/util.py)估计,因此true_labels的类别分布会直接影响翻转数量。 count_joint给出每对(真实类, 标注类)的期望样本数(四舍五入为整数),对角元素清零后即"需要翻转的样本配额"。- 对每个真实类别
k,从其样本中无放回随机挑选配额数量的样本,改写为指定的带噪标签,从而保证生成的标签与noise_matrix高度吻合(docstring 中注释掉的校验代码显示可进一步用混淆矩阵验证)。
docstring 还附带了等价伪代码(noise_generation.py),展示双层循环逐步翻转标签的逻辑,便于理解算法行为,但实际实现因其 O(K) 而非 O(K²) 的向量化设计而更快。
5.3 在真实实验中的调用方式
仓库多处测试用它构造"带噪训练集",最典型的是 test_classification.py:
noise_matrix = generate_noise_matrix_from_trace(K, trace=avg_trace * K, py=py, valid_noise_matrix=True, seed=seed) s = generate_noisy_labels(true_labels_train, noise_matrix) # s 即为带噪标签随后即可把(X_train, s)交给CleanLearning训练并对比在干净标签上的性能(cleanlab/classification.py)。此外:
- tests/test_filter_count.py 用它生成带噪标签后直接调用
find_label_issues验证标签问题发现能力; - tests/datalab/datalab/test_multilabel.py 在多标签场景下对标签索引做同样的注入(
noisy_labels_idx),再映射回多标签集合,说明该模块可作为多标签数据基准测试的噪声源; - tests/test_multiannotator.py 对每位标注者重复调用
generate_noisy_labels,模拟多标注者各自带有不同噪声标签的现实场景。
六、底层工具函数
6.1 generate_n_rand_probabilities_that_sum_to_m
生成n个和为m的随机概率数组(noise_generation.py),是上述生成流程的基石。
def generate_n_rand_probabilities_that_sum_to_m(n, m, *, max_prob=1.0, min_prob=0.0) -> np.ndarray- 默认参数下等价于
np.random.dirichlet(np.ones(n)) * m(源码注释明确说明)。 - 当
max_prob < m/n时抛ValueError("max_prob must be greater or equal to m / n...");当min_prob > m/n时抛ValueError("min_prob must be less or equal to m / n...")。因为若单元素平均值为m/n,任何元素都不可能超过max_prob或低于min_prob而仍保持总和为m。 - 算法采用 Dirichlet 采样后用"挤出-补偿"循环把越界元素压回
[min_prob, max_prob]区间,且每次补偿都引入随机扰动(adjustment = (max_prob - new_min) * np.random.rand()),避免结果退化。 n=0时返回空数组(测试test_gen_probs_sum_empty)。
6.2 randomly_distribute_N_balls_into_K_bins
返回长度为K、和为N的均匀随机整数数组,用于在生成噪声矩阵时把"零噪声率"配额分配到各列(noise_generation.py)。
def randomly_distribute_N_balls_into_K_bins(N, K, *, max_balls_per_bin=None, min_balls_per_bin=None) -> np.ndarraymax_balls_per_bin默认取min(传入值, N),min_balls_per_bin默认取min(传入值, N/K),保证约束可行。- 实现思路:先生成和为 1 的 K 个概率再乘以 N 并四舍五入,随后通过"最大值减一/最小值加一"的微调循环保证最终整数和严格等于 N(源码中
while sum(arr) != N循环)。 - 测试
test_balls_params对max_balls_per_bin、min_balls_per_bin的多种组合验证了返回数组长度、总和与上下界约束。
七、实战工作流:一次完整的噪声基准实验
综合以上全部要素,一个可复现的端到端噪声基准实验流程如下:
import numpy as np from sklearn.linear_model import LogisticRegression from cleanlab.classification import CleanLearning from cleanlab.benchmarking.noise_generation import ( generate_noise_matrix_from_trace, generate_noisy_labels, noise_matrix_is_valid, ) SEED = 1 np.random.seed(SEED) # 1. 构造三分类高斯数据(每类 100 样本) means = [[3, 2], [7, 7], [0, 8]] sizes = [100, 100, 100] X = np.vstack([np.random.multivariate_normal(m, np.eye(2) * 2, n) for m, n in zip(means, sizes)]) true_labels = np.hstack([[k] * n for k, n in enumerate(sizes)]) # 2. 估计先验并生成平均 trace=0.8 的合法噪声矩阵 py = np.bincount(true_labels) / float(len(true_labels)) K = len(py) noise_matrix = generate_noise_matrix_from_trace( K, trace=0.8 * K, py=py, valid_noise_matrix=True, seed=SEED, ) assert noise_matrix_is_valid(noise_matrix, py) # 3. 注入噪声得到带噪标签 noisy_labels = generate_noisy_labels(true_labels, noise_matrix) print("噪声率约为:", 1 - np.mean(noisy_labels == true_labels)) # 4. 用 CleanLearning 在带噪标签上训练并对比干净标签基线 cl = CleanLearning(clf=LogisticRegression(), seed=SEED) cl.fit(X, noisy_labels)调节trace(取值越大噪声越小)与frac_zero_noise_rates(取值越大噪声越稀疏结构化),即可系统性地扫描 cleanlab 在不同噪声条件下的性能曲线。
八、注意事项与边界
valid_noise_matrix=True时 trace 必须大于 1,这是数学上"可学习"的必要条件;若只需要任意随机噪声矩阵(不保证可学习),可设valid_noise_matrix=False,此时py可不传。- 生成可能失败:在极端参数组合下(如高 trace 与高
frac_zero_noise_rates冲突、或类别数多而max_iter过小),函数可能返回None。调用方应做好空值判断;仓库测试test_max_iter验证了max_iter=0返回None的行为。 seed控制可复现性:函数内部调用np.random.seed(seed),同一seed与参数组合会得到完全相同的噪声矩阵,这是基准测试可比性的基础;但同时也会影响调用方的全局随机状态,实验设计中需留意。- 该模块仅面向单标签多分类:虽然多标签测试(test_multilabel.py)借助标签索引复用了它,但函数本身的输入输出语义是单标签整数类别;多标签场景需自行做索引映射。
- 本文所有函数签名、默认值与报错信息均以当前仓库源码 noise_generation.py 为准;相关行为均有 test_noise_generation.py 等测试用例佐证,可作为自行扩展基准测试时的参考实现。
九、总结
cleanlab.benchmarking.noise_generation是 cleanlab 基准测试基础设施的基石:generate_noise_matrix_from_trace用 trace 精确控制噪声总量并保证矩阵可学习,generate_noisy_labels按联合计数把噪声精确注入标签,noise_matrix_is_valid为每一步提供数学合法性校验,两个底层工具函数则保证了随机采样的均匀性与约束满足。整套工具让研究人员可以像调节旋钮一样控制标签噪声的强度与结构,从而在完全受控的条件下验证 cleanlab 各项算法在真实脏数据场景中的表现。
【免费下载链接】cleanlabCleanlab's open-source library is the standard>项目地址: https://gitcode.com/GitHub_Trending/cl/cleanlab
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考