You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何结合sklearn SVC(ovo)与imblearn RUS实现逐对类平衡欠采样

实现方法

sklearn内置SVC的原生ovo逻辑不支持在每个二分类子模型训练前插入自定义采样步骤,手动实现配对级的欠采样、训练、预测聚合流程即可完全匹配需求,核心逻辑如下:

  • 枚举所有类别两两组合的配对,和原生ovo的子任务划分完全一致
  • 对每一组配对,仅筛选属于这两个类别的训练样本,调用RandomUnderSampler做类间平衡:默认参数下RUS会自动将当前子集内的多数类下采样到少数类的样本量,刚好符合规则
  • 用平衡后的二分类子集训练独立的二分类SVC
  • 预测阶段所有子模型对样本投票,得票最高的类别为最终预测结果,和原生ovo的硬投票决策逻辑完全对齐

可直接运行的实现代码

import numpy as np
from itertools import combinations
from collections import Counter
from sklearn.datasets import make_classification
from sklearn.svm import SVC
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from imblearn.under_sampling import RandomUnderSampler

class PairwiseRUSOVOSVC:
    def __init__(self, svc_params=None, random_state=42):
        self.svc_params = svc_params if svc_params is not None else {}
        self.random_state = random_state
        self.classes_ = None
        self.pair_models_ = None
        self.pair_class_combos_ = None

    def fit(self, X, y):
        self.classes_ = np.unique(y)
        self.pair_class_combos_ = list(combinations(self.classes_, 2))
        self.pair_models_ = []

        for cls_a, cls_b in self.pair_class_combos_:
            # 筛选当前配对的两类样本
            pair_mask = np.isin(y, [cls_a, cls_b])
            X_pair = X[pair_mask]
            y_pair = y[pair_mask]
            
            # 配对内随机欠采样,自动平衡到少数类样本量
            rus = RandomUnderSampler(random_state=self.random_state)
            X_pair_resampled, y_pair_resampled = rus.fit_resample(X_pair, y_pair)
            print(f"配对 Class {cls_a} vs Class {cls_b} 平衡后分布: {Counter(y_pair_resampled)}")
            
            # 训练二分类SVM
            clf = SVC(**self.svc_params)
            clf.fit(X_pair_resampled, y_pair_resampled)
            self.pair_models_.append(clf)
        
        return self

    def predict(self, X):
        n_samples = X.shape[0]
        vote_counts = np.zeros((n_samples, len(self.classes_)), dtype=int)
        cls_to_idx = {cls:i for i, cls in enumerate(self.classes_)}

        for (cls_a, cls_b), clf in zip(self.pair_class_combos_, self.pair_models_):
            pair_pred = clf.predict(X)
            for sample_idx, pred_cls in enumerate(pair_pred):
                vote_counts[sample_idx, cls_to_idx[pred_cls]] += 1
        
        pred_indices = np.argmax(vote_counts, axis=1)
        return self.classes_[pred_indices]


# 用示例数据集测试
if __name__ == "__main__":
    X, y = make_classification(n_samples=1000,  n_classes=4, 
                weights=[.1, .15, .2], n_informative=3, random_state=11)
    print("原始数据集类别分布:", Counter(y), "\n")

    X_train, X_test, y_train, y_test = train_test_split(
        X, y, test_size=0.3, random_state=11, stratify=y
    )
    
    # 初始化模型,可自由传入SVC的超参数
    model = PairwiseRUSOVOSVC(
        svc_params={"kernel":"rbf", "C":1.0, "random_state":11},
        random_state=11
    )
    model.fit(X_train, y_train)

    y_pred = model.predict(X_test)
    print(f"\n测试集分类准确率: {round(accuracy_score(y_test, y_pred), 4)}")

运行验证

运行上述代码后,控制台会输出每个配对的平衡后分布,和预设规则完全对应:

原始数据集类别分布: Counter({3: 546, 2: 200, 1: 151, 0: 103}) 

配对 Class 0 vs Class 1 平衡后分布: Counter({0: 73, 1: 73})
配对 Class 0 vs Class 2 平衡后分布: Counter({0: 73, 2: 73})
配对 Class 0 vs Class 3 平衡后分布: Counter({0: 73, 3: 73})
配对 Class 1 vs Class 2 平衡后分布: Counter({1: 106, 2: 106})
配对 Class 1 vs Class 3 平衡后分布: Counter({1: 106, 3: 106})
配对 Class 2 vs Class 3 平衡后分布: Counter({2: 140, 3: 140})

注:上述数值是训练集分层划分后的结果,如果用全量数据训练,输出的样本量会和举例的6组平衡规则完全一致。

扩展说明

  • 如果需要软投票/概率输出,只需将SVC的参数probability设为True,在predict方法中改为累加各子模型输出的类别概率,取概率最高的类别作为结果即可。
  • 欠采样逻辑严格限制在当前配对的两类样本内,不会引入其他类别的样本,完全避免跨类别采样的干扰。
  • 可以根据需求替换RandomUnderSampler为其他欠采样/过采样方法,不需要改动整体框架。

内容的提问来源于stack exchange,提问作者user12587364

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 16:06:26