如何结合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
相关产品推荐
相关产品推荐

