为什么需要正样本-无标签(PU)学习?原理、代码实战与避坑指南

在传统的监督学习中,我们通常假设拥有的数据集是完美的——既有正样本也有负样本。然而,当我们真正深入到工业界的实战项目中,你会发现这种假设往往过于奢侈。你是否也曾遇到过这样的困境:你需要构建一个反洗钱系统,但手头只有经过确认的“洗钱账户”,而海量的“正常账户”躺在那里,既没有被标记为“正常”,也没有被标记为“洗钱”,只是单纯的“无标签”。获取负样本不仅昂贵,甚至可能因为隐私法规(如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 或现代对比学习来解决这个顽疾?”

希望这些经验能帮助你在未来的欺诈检测、推荐系统或医疗诊断项目中,即使面对残缺的数据,也能训练出强大的模型。

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。如需转载,请注明文章出处豆丁博客和来源网址。https://shluqu.cn/44368.html
点赞
0.00 平均评分 (0% 分数) - 0