少量样本已有标签、其余样本尚未标注时,可以把已知标签与未标注样本一起交给 LabelPropagation。它在样本相似图上传播标签,用 -1 表示未标注;传入的已知标签会被固定。传播结果仍是模型预测,不能直接升级为人工真值。下面从 105 条 Iris 训练样本中保留 15 条标签,检查已知标签和其余 90 条的传播结果。
半监督训练的前提是什么
你需要特征、少量可信标签,以及与目标任务来自相近分布的未标注数据。相似样本应当有相近类别,这是传播能够提供有用结果的前提;若相似度主要反映无关字段,算法也可能把错误沿图扩散。

官方半监督指南说明 -1 是未标注标识,LabelPropagation 采用固定输入标签的 hard clamping;同时区分它与可调整已知标签分布的 LabelSpreading。LabelPropagation API定义 transduction_ 为训练样本的传播标签,label_distributions_ 为类别分布,并提供新样本 predict()。
本文代码于 2026 年 10 月 1 日在 Windows、Python 3.11.15、scikit-learn 1.7.2、NumPy 2.4.6、SciPy 1.17.1 实际运行。Iris 是教学数据;这里故意隐藏一部分已知答案以便核对,不是现实项目的无标签数据案例。可建立独立环境并安装:
python -m venv .venv
.venv\Scripts\python -m pip install "scikit-learn==1.7.2" "numpy==2.4.6" "scipy==1.17.1"
制作标签掩码,拟合相似图
将完整代码保存为 propagate_labels.py,运行 .venv\Scripts\python propagate_labels.py。先留出 45 条测试样本;训练部分每个类别随机保留 5 条标签,其余填 -1。真实标签只用于构造这次隐藏标签实验和事后核对,model.fit() 收到的是 observed。
import numpy as np
from sklearn.datasets import load_iris
from sklearn.metrics import accuracy_score
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.semi_supervised import LabelPropagation
X, truth = load_iris(return_X_y=True)
X_train, X_test, truth_train, truth_test = train_test_split(
X, truth, test_size=0.3, stratify=truth, random_state=7)
rng = np.random.default_rng(7)
known = np.zeros(len(truth_train), dtype=bool)
for label in np.unique(truth_train):
candidates = np.flatnonzero(truth_train == label)
known[rng.choice(candidates, size=5, replace=False)] = True
observed = np.full(len(truth_train), -1, dtype=int)
observed[known] = truth_train[known]
scaler = StandardScaler().fit(X_train)
train_scaled = scaler.transform(X_train)
test_scaled = scaler.transform(X_test)
model = LabelPropagation(kernel="knn", n_neighbors=7,
max_iter=1000, tol=1e-3)
model.fit(train_scaled, observed)
propagated = model.transduction_
assert np.array_equal(propagated[known], observed[known])
assert np.isfinite(model.label_distributions_).all()
assert np.allclose(model.label_distributions_.sum(axis=1), 1.0)
print("已标注:", known.sum(), "待标注:", (~known).sum())
print("已知标签保留:", np.array_equal(propagated[known], observed[known]))
print("传播迭代次数:", model.n_iter_)
print("隐藏标签演示核对准确率:",
f"{accuracy_score(truth_train[~known], propagated[~known]):.4f}")
print("独立测试集准确率:",
f"{accuracy_score(truth_test, model.predict(test_scaled)):.4f}")
confidence = model.label_distributions_.max(axis=1)
uncertain = np.flatnonzero(~known)[np.argsort(confidence[~known])[:5]]
for index in uncertain:
print("待复核训练行:", int(index), "预测:", int(propagated[index]),
"最大分布值:", f"{confidence[index]:.4f}")
StandardScaler 只学习 X_train。半监督训练可以利用训练池中的未标注特征来描述分布,但不能利用留出的测试集。这里选 knn 相似图、7 个邻居;参数只是可运行的教学设置,实际任务应按独立验证数据和样本结构选择。没有调用 fit() 读取 truth_train 作为完整训练标签。
把“标签保留”“传播核对”“新样本测试”分开看
本次实际运行输出:
已标注: 15 待标注: 90
已知标签保留: True
传播迭代次数: 81
隐藏标签演示核对准确率: 0.9444
独立测试集准确率: 0.9111
待复核训练行: 3 预测: 2 最大分布值: 0.5406
待复核训练行: 75 预测: 2 最大分布值: 0.5494
待复核训练行: 40 预测: 2 最大分布值: 0.5580
待复核训练行: 9 预测: 1 最大分布值: 0.5608
待复核训练行: 104 预测: 1 最大分布值: 0.5978
已知标签保留为 True,表明 15 条输入标签没有被改写;90 条被隐藏的训练答案用于核对 transduction_,得到 0.9444;45 条未参与拟合的测试样本通过 predict() 评估,得到 0.9111。这两种准确率的样本范围不同,不能把隐藏训练标签的核对结果当成新样本泛化成绩。
代码还检查所有类别分布是有限数、每行之和接近 1,并列出 5 条最大分布值较低的未标注训练行。例如训练行 3 的预测为类别 2、最大分布值约 0.5406,可作为人工复核候选;这些分布值未经概率校准,不能解释为“54.06% 的可靠正确率”。它们只帮助你挑选需要重新看原始样本的记录。
应用到自己的样本时,怎样保留复核记录
- 为每条样本保存 source_id、特征、人工标签和是否已标注。数字类别不能与 -1 冲突。
- 先固定测试集,训练池中未标注行的 y 填 -1;不要把缺失标签误写为某个真实类别,例如 0。
- 拟合后把 transduction_ 按训练池原始行号回写到“建议标签”字段,保留原人工标签字段。
- 按低最大分布值、业务高风险类别和随机抽样生成复核清单,让人工看原始样本后决定是否接纳。
- 用独立、经过人工确认的测试样本评估;真实未标注数据没有答案时,不应凭输出数量声称传播准确率。
失败和适用边界
如果某个业务类别在可信标签中完全缺席,模型无法凭 -1 样本自动发现并命名该类别。先补充类别代表样本,再训练。若迭代达到上限、分布出现非有限值或全零行,停止接纳建议标签,检查图连通性、邻居数、缩放及标签覆盖;不能删除检查来取得“成功”输出。
官方指南说明 RBF 会构造密集相似矩阵,大样本时内存和迭代计算可能很重;knn 使用稀疏相似图,但也不等于任意规模都能运行。本教程未测试百万样本、线上延迟、真实标注质量,也未证明半监督一定优于只用人工标签的监督模型。
下一步应先选一个可人工复核的小样本池,记录建议标签与人工判定的差异,再决定是否把接纳后的标签用于后续训练。被固定的已知标签如果本身错误,LabelPropagation 也不会自动替你纠正。
Ai菜鸟网。发布者:AI小管家,转载请注明出处:https://www.alyyhw.com/30809.html