在传统的监督学习中,我们通常假设拥有的数据集是完美的——既有正样本也有负样本。然而,当我们真正深入到工业界的实战项目中,你会发现这种假设往往过于奢侈。你是否也曾遇到过这样的困境:你需要构建一个反洗钱系统,但手头只有经过确认的“洗钱账户”,而海量的“正常账户”躺在那里,既没有被标记为“正常”,也没有被标记为“洗钱”,只是单纯的“无标签”。获取负样本不仅昂贵,甚至可能因为隐私法规(如GDPR)或数据量过大而变得不可行。
这就是我们今天要探讨的核心问题:当我们只有正样本和大量的无标签数据时,我们该如何训练模型?尤其是在2026年的今天,随着生成式AI和边缘计算的普及,数据形态更加复杂。这篇文章将带你深入浅出地理解正样本-无标签(PU)学习,并提供符合现代开发理念的硬核实战经验。
什么是正样本-无标签(PU)学习?
正样本-无标签学习,也被称为仅正样本学习,是一种专门应对现实世界数据缺陷的机器学习范式。在这个范式里,我们处理的数据集不再是泾渭分明的“正”与“负”,而是由两部分组成:
- 正样本:这些是我们明确感兴趣的实例(例如:确诊患病的病例、被标记的欺诈交易)。
- 无标签数据:这部分数据的类别标签是未知的。关键在于,这些数据虽然被称作“无标签”,但实际上是一个混合体——其中可能隐藏着未被发现的正样本,同时也包含了绝大多数的负样本。
这种情况在欺诈检测、罕见病诊断、情感分析以及推荐系统中极其常见。在这些场景下,获取负样本(例如“所有没有患病的健康人”或“所有没有发生的欺诈行为”)往往充满挑战、代价高昂,或者根本不可行。在我们最近处理的一个金融科技项目中,甚至只有不到 0.1% 的数据被标记,传统的监督学习完全失效,而 PU 学习成为了破局的关键。
PU 学习的核心挑战:负样本从何而来?
我们面临的根本挑战是:在无法获取确定的负样本进行训练的情况下,如何训练一个能够准确区分正实例和负实例的分类器?
如果我们将所有无标签数据都视为负样本,会怎么样?这是初学者最容易犯的错误——“噪声负样本”陷阱。如果无标签数据中实际上包含了一部分正样本(比如未被医生发现的罕见病患者),而我们强行把它们当作负样本来训练,模型就会产生严重的标签噪声,导致分类边界模糊,性能大幅下降。
因此,PU 学习的核心目标变成了:如何有效地利用无标签数据中蕴含的信息,识别出其中的负样本特征,同时校正因为数据缺失带来的模型偏差?
2026年技术趋势下的 PU 学习新范式
随着我们步入 2026 年,PU 学习的应用场景和技术实现方式也在发生演变。现代开发不再仅仅是“训练一个模型”,而是构建一个能够自我演进、符合 AI Native 原则的系统。
#### 深度整合 LLM 的自动标注
在过去,我们可能需要依赖人工清洗无标签数据。但在现代工作流中,我们可以利用大语言模型(LLM)作为“第一道防线”。你可能会问:“LLM 会不会产生幻觉?”确实会,但我们可以将其视为一种弱监督工具。
我们可以在代码中集成 LLM API(如 OpenAI 或开源的 Llama 3),将无标签数据发送给 LLM 进行“可能性判断”。 虽然我们不能完全信任 LLM 的结果,但 LLM 可以提供一个先验概率。例如,对于一段评论,我们可以问 LLM:“这段评论看起来像是在推广产品吗?”如果 LLM 说“极不可能”,我们就可以将其作为“可靠的负样本”初始化我们的 PU 训练集。
这种方法极大地减少了冷启动时的数据清洗成本。我们不再是从零开始,而是站在巨人的肩膀上进行微调。
#### AI 原生开发与 Vibe Coding(氛围编程)
在现代开发环境中,编写 PU 学习的代码不再是枯燥的独行。利用 Cursor 或 Windsurf 等 AI IDE,我们采用了一种被称为“氛围编程”的新范式。
让我们看一个例子。在编写 PU Bagging 算法时,我们不需要手动去推导每一个索引变换。我们可以这样与结对编程的 AI 沟通:“帮我写一个类,实现 PU Bagging 逻辑,重点是从无标签数据中动态采样负样本,并处理索引对齐问题,防止数据泄露。” AI 会瞬间生成基础架构,而我们作为人类专家,则专注于审查逻辑的正确性和业务逻辑的约束,比如确保验证集的数据绝对没有被用于筛选负样本。
这种协作模式让我们能更快地迭代算法,将更多精力投入到“算法策略”而非“语法实现”上。
深入算法:实战中的常用策略与代码实现
让我们通过标准的流程和现代 Python 实践来看看 PU 学习算法是如何处理这个问题的。为了让你更直观地理解,我们将使用 Python 来模拟一个 PU Learning 的场景。
#### 示例 1:数据模拟与“噪声负样本”陷阱演示
首先,让我们创建一个数据集,看看直接把无标签数据当负样本会有多糟糕。
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import classification_report, confusion_matrix
# 设置随机种子以保证结果可复述
np.random.seed(42)
# 1. 生成模拟数据:10000个样本,2个特征
# 我们生成两个类别的数据,正类和负类
X, y = make_classification(
n_samples=10000,
n_features=2,
n_redundant=0,
n_informative=2,
random_state=42,
n_clusters_per_class=1,
class_sep=1.5
)
# 2. 模拟真实场景:我们只有一部分正样本被标记了
# 假设我们有 500 个正样本被明确标记
# 剩下的数据全部变成了“无标签”
positive_indices = np.where(y == 1)[0]
negative_indices = np.where(y == 0)[0]
# 随机选取 500 个正样本作为“已知的正样本”
labeled_pos_size = 500
np.random.shuffle(positive_indices)
labeled_pos_indices = positive_indices[:labeled_pos_size]
# 剩下的正样本(未被标记)和所有负样本混合在一起,构成“无标签数据”
unlabeled_indices = np.concatenate([
positive_indices[labeled_pos_size:],
negative_indices
])
# 划分 PU 学习的训练集
X_pu = X[labeled_pos_indices.tolist() + unlabeled_indices.tolist()]
y_pu = np.array([1] * labeled_pos_size + [-1] * len(unlabeled_indices)) # -1 代表无标签
# 这是一个“上帝视角”的测试集,用于评估真实性能
_, X_test, _, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
print(f"已知正样本数量: {labeled_pos_size}")
print(f"无标签数据数量: {len(unlabeled_indices)}")
print(f"注意:无标签数据中实际上包含了 {len(positive_indices) - labeled_pos_size} 个隐藏的正样本!")
# --- 错误示范:将无标签数据直接视为负样本 ---
print("
--- 错误示范:将无标签直接视为负样本 ---")
y_naive = np.where(y_pu == -1, 0, y_pu)
model_naive = LogisticRegression()
model_naive.fit(X_pu, y_naive)
y_pred_naive = model_naive.predict(X_test)
print(classification_report(y_test, y_pred_naive))
print("注意观察 Recall,因为无标签里混有正样本,导致模型学习到了错误的负样本特征。")
在这个例子中,你可能会发现模型的精确率尚可,但召回率会受到严重影响。这是因为模型被迫将隐藏的正样本学习为负样本,从而导致决策边界向正类方向偏移。
#### 示例 2:企业级 PU Bagging 实现 (兼容 Scikit-Learn)
一个经典的 PU Learning 策略类似于 Bagging。让我们编写一个稳健的、符合现代工程标准的分类器。我们实现了 INLINECODEc489b8f1 和 INLINECODE520baa0b 方法,使其可以无缝融入 Scikit-Learn 的 Pipeline 中。
from sklearn.tree import DecisionTreeClassifier
from sklearn.base import BaseEstimator, ClassifierMixin
class EnterprisePUBagging(BaseEstimator, ClassifierMixin):
def __init__(self, n_estimators=50, max_depth=3):
self.n_estimators = n_estimators
self.max_depth = max_depth
def fit(self, X, y_pu):
# 分离出确定的正样本和无标签样本 (标记为 -1 或 0,视约定而定)
P_idx = np.where(y_pu == 1)[0]
U_idx = np.where(y_pu != 1)[0] # 假设非1即为无标签
X_P = X[P_idx]
X_U = X[U_idx]
if len(X_U) == 0:
raise ValueError("无标签数据集为空,无法进行 PU Learning")
self.models = []
# 记录每个无标签样本被预测为负类的频率
self.neg_scores = np.zeros(len(X_U))
for i in range(self.n_estimators):
# 1. 随机采样:重采样正样本
# 2. 关键策略:随机抽取与正样本等量的无标签数据作为“临时负样本”
# 这基于一个假设:无标签数据中负样本占多数
sample_U_idx = np.random.choice(len(X_U), size=min(len(X_P), len(X_U)), replace=False)
X_train = np.vstack([X_P, X_U[sample_U_idx]])
y_train = np.array([1] * len(X_P) + [0] * len(sample_U_idx))
# 使用决策树作为基学习器,便于捕捉非线性关系
model = DecisionTreeClassifier(max_depth=self.max_depth)
model.fit(X_train, y_train)
self.models.append(model)
# 对所有无标签数据进行预测,统计被预测为0(负类)的概率
preds = model.predict(X_U)
self.neg_scores[sample_U_idx] += (preds == 0).astype(int)
# 归一化分数
self.neg_scores /= self.n_estimators
# 3. 筛选“可靠的负样本”
# 我们取分数最高的 80% 样本作为可靠负样本
threshold = np.percentile(self.neg_scores, 80)
reliable_neg_mask = self.neg_scores >= threshold
reliable_U_idx = np.where(reliable_neg_mask)[0]
if len(reliable_U_idx) > 0:
# 最终训练:使用 P + 可靠的负样本
X_final = np.vstack([X_P, X_U[reliable_U_idx]])
y_final = np.array([1] * len(X_P) + [0] * len(reliable_U_idx))
# 使用逻辑回归作为最终的强分类器,输出概率
self.final_model = LogisticRegression()
self.final_model.fit(X_final, y_final)
else:
raise RuntimeError("无法找到足够的可靠负样本,请检查数据分布或减少筛选阈值")
return self
def predict(self, X):
if hasattr(self, ‘final_model‘):
return self.final_model.predict(X)
else:
return np.zeros(len(X)) # 默认返回负类
#### 示例 3:应用现代 PU Bagging
让我们将上述企业级代码应用到我们的模拟数据中,并展示其相对于传统方法的优势。
# --- 训练企业级 PU Bagging 模型 ---
print("
--- 企业级 PU Bagging 测试 ---")
try:
pu_model = EnterprisePUBagging(n_estimators=100, max_depth=4)
pu_model.fit(X_pu, y_pu)
y_pred_pu = pu_model.predict(X_test)
print(classification_report(y_test, y_pred_pu))
except Exception as e:
print(f"训练出错: {e}")
2026年视角下的生产环境最佳实践
在代码跑通之后,我们作为工程师,还需要考虑如何在 2026 年的复杂技术栈中稳健地部署和维护这类系统。
#### 1. 云原生与可观测性
在现代云原生架构中,模型往往部署在 Serverless 环境或边缘节点。PU 学习模型的一个显著风险是:数据漂移。如果“无标签数据”中的正负样本比例随时间发生变化(例如节假日欺诈模式改变),模型可能会崩溃。
我们建议在代码中集成 OpenTelemetry 等可观测性工具。
# 模拟在推理函数中添加监控
from opentelemetry import trace
def predict_with_monitoring(model, X):
tracer = trace.get_tracer(__name__)
with tracer.start_as_current_span("pu_inference"):
# 获取预测概率,而不仅仅是类别
probs = model.final_model.predict_proba(X)[:, 1]
# 动态监控预测分数的分布
# 如果平均概率异常飙升,可能意味着无标签数据中混入了大量未被发现的正样本
avg_prob = np.mean(probs)
print(f"Current avg positive prob: {avg_prob:.4f}")
return (probs > 0.5).astype(int)
通过监控 predict_proba 的输出分布,我们可以在模型失效前收到警报,触发自动重训练流程。
#### 2. 安全左移与隐私计算
在处理金融或医疗数据时,我们不能随意地将所有无标签数据视为负样本并进行处理,因为这可能涉及将敏感的“正样本”误判为“负样本”从而泄露隐私或违反审计规定。
在 2026 年,我们更倾向于使用联邦学习结合 PU Learning。无标签数据不需要离开本地边缘节点,只有模型参数的更新才会聚合到中心服务器。这确保了即便在只有正样本(中心)和无标签数据(边缘)的情况下,我们也能合规地训练模型。
#### 3. 替代方案对比:什么时候不用 PU Learning?
虽然 PU Learning 很强大,但它不是万能药。在我们的经验中,如果无标签数据中正样本的比例过高(超过 30%-40%),PU Learning 的效果会急剧下降,甚至不如简单的半监督学习。
另一种 2026 年流行的方案是:对比学习。 我们可以利用 SimCLR 或 MoCo 的思想,不依赖标签,而是通过学习样本之间的相似性结构。正样本之间互相拉近,正样本与无标签数据推远。这种方法在高维数据(如图像、文本嵌入)上往往比传统的 PU 分类器效果更好,因为它不强制假设负样本的存在。
总结与展望
在这篇文章中,我们一起探索了当负样本难以获取时,如何利用正样本-无标签(PU)学习来打破僵局。我们不仅回顾了“为什么不能把无标签数据直接当负样本”这一经典陷阱,还结合 2026 年的技术背景,探讨了 LLM 辅助标注、企业级代码封装以及云原生部署策略。
PU 学习不仅仅是一种技术手段,更是一种应对现实数据缺陷的工程思维。随着 AI 越来越多地融入开发流程,我们可以利用 AI 帮助我们更快地识别可靠的负样本,构建更鲁棒的分类器。下次当你面对一堆只有部分标签的数据时,不要惊慌,试着问自己:“我能否利用 PU Learning 或现代对比学习来解决这个顽疾?”
希望这些经验能帮助你在未来的欺诈检测、推荐系统或医疗诊断项目中,即使面对残缺的数据,也能训练出强大的模型。