基于Support Vector的数据重采样器类别标签错误修复求助
问题:修复One-vs-One场景下重采样器的类别标签混乱问题
我正在实现一个基于支持向量的数据重采样器,核心逻辑是:拟合SVM分类器获取各类别的支持向量,仅保留靠近支持向量的样本以平衡各类别数量。在多类别场景下采用one-against-one(OVO)策略成对重采样,使用OneVsOneClassifier包裹包含重采样器的Pipeline时,发现每对类别标签被自动替换为0和1,导致后续类别标识混乱。
以下是原始代码及错误表现:
原始代码
# required imports import random from collections import Counter from math import dist import numpy as np from sklearn.svm import SVC from sklearn.utils import check_random_state from sklearn.multiclass import OneVsOneClassifier from imblearn.pipeline import Pipeline from sklearn.ensemble import RandomForestClassifier from sklearn.datasets import make_classification np.random.seed(7) random.seed(7) # resampler class class DataUndersampler(): def __init__(self, random_state=None): self.random_state = random_state print('DataUndersampler()') def fit_resample(self, X, y): random_state = check_random_state(self.random_state) # class distribution counter = Counter(y) print(f'Original class distribution: {counter}') maj_class = counter.most_common()[0][0] min_class = counter.most_common()[-1][0] # number of minority examples num_minority = len(X[ y == min_class]) #num_majority = len(X[ y == maj_class]) # check on with maj now svc = SVC(kernel='rbf', random_state=32) svc.fit(X,y) # majority class support vectors maj_sup_vectors = svc.support_vectors_[maj_class] #min_sup_vectors = svc.support_vectors_[min_class] # minority sup vect # compute distances to support vectors' point distances = [] for i, x in enumerate(X[y == maj_class]): #input(f'sv: {maj_sup_vectors}, x: {x}') # check value passed d = dist(maj_sup_vectors, x) distances.append((i, d)) # sort distances (reverse=False -> ascending) distances.sort(reverse=False, key=lambda tup: tup[1]) index = [i for i, d in distances][:num_minority] X_ds = np.concatenate((X[y == maj_class][index], X[y == min_class])) y_ds = np.concatenate((y[y == maj_class][index], y[y == min_class])) print(f"Resampled class distribution ('ovo'): {Counter(y_ds)} \n") return X_ds, y_ds
错误输出表现
运行时输出的类别分布中,原本的多类别(如1、2、3、4)被替换为0和1,导致无法识别真实类别:
Original class distribution: Counter({0: 9924, 1: 15}) # 实际应为 {0: 9924, 2: 15} Resampled class distribution ('ovo'): Counter({0: 15, 1: 15}) # 实际应为 {0: 15, 2: 15}
问题原因
OneVsOneClassifier在处理每一对类别时,会自动将当前处理的两个原始类别映射为0和1传入下游的estimator(即你的Pipeline),因此fit_resample中拿到的y是转换后的二分类标签,而非原始多类别标签。同时,原始代码中获取支持向量的方式错误:二分类SVC的support_vectors_是按顺序存储的所有支持向量,不能直接用类别索引(如maj_class)访问。
修复方案
修改DataUndersampler的fit_resample方法,核心是:
- 记录当前处理的原始类别,避免被转换后的0/1标签混淆
- 正确获取对应原始类别的支持向量
- 重采样后恢复原始类别标签
修改后的完整代码
import random from collections import Counter from math import dist import numpy as np from sklearn.svm import SVC from sklearn.utils import check_random_state from sklearn.multiclass import OneVsOneClassifier from imblearn.pipeline import Pipeline from sklearn.ensemble import RandomForestClassifier from sklearn.datasets import make_classification np.random.seed(7) random.seed(7) class DataUndersampler(): def __init__(self, random_state=None): self.random_state = random_state print('DataUndersampler()') def fit_resample(self, X, y): random_state = check_random_state(self.random_state) # 1. 获取当前处理的两个原始类别(OVO每次只处理一对) original_classes = np.unique(y) assert len(original_classes) == 2, "OVO should pass exactly two classes" # 2. 统计转换后的标签分布,映射回原始类别 counter = Counter(y) # 确定多数/少数对应的原始类别 if counter[0] > counter[1]: maj_transformed = 0 min_transformed = 1 maj_original = original_classes[0] min_original = original_classes[1] else: maj_transformed = 1 min_transformed = 0 maj_original = original_classes[1] min_original = original_classes[0] num_minority = counter[min_transformed] print(f'Original class distribution: {{{maj_original}: {counter[maj_transformed]}, {min_original}: {num_minority}}}') # 3. 拟合SVC并正确获取对应类别的支持向量 svc = SVC(kernel='rbf', random_state=32) svc.fit(X, y) # 二分类SVC的n_support_给出每个类别的支持向量数量 # support_是所有支持向量在X中的索引,前n_support_[0]个属于类别0,剩下的属于类别1 if maj_transformed == 0: maj_sv_indices = svc.support_[:svc.n_support_[0]] else: maj_sv_indices = svc.support_[svc.n_support_[0]:] maj_sup_vectors = X[maj_sv_indices] # 4. 计算多数类样本到支持向量的平均距离(修正原代码中dist只能计算一维距离的问题) distances = [] for i, x in enumerate(X[y == maj_transformed]): # 计算到所有多数类支持向量的平均距离 avg_dist = np.mean([dist(x, sv) for sv in maj_sup_vectors]) distances.append((i, avg_dist)) distances.sort(reverse=False, key=lambda tup: tup[1]) index = [i for i, d in distances][:num_minority] # 5. 重采样并恢复原始类别标签 X_maj_resampled = X[y == maj_transformed][index] X_min = X[y == min_transformed] X_ds = np.concatenate((X_maj_resampled, X_min)) # 恢复原始标签 y_maj_resampled = np.full(len(X_maj_resampled), maj_original) y_min = np.full(len(X_min), min_original) y_ds = np.concatenate((y_maj_resampled, y_min)) print(f"Resampled class distribution ('ovo'): {Counter(y_ds)} \n") return X_ds, y_ds
关键修改点说明
- 记录原始类别:通过
np.unique(y)获取当前一对类别的原始标识,避免被转换后的0/1干扰 - 支持向量获取:利用SVC的
n_support_和support_属性,正确提取对应类别的支持向量(原代码中svc.support_vectors_[maj_class]仅适用于多类别SVC,二分类场景下无效) - 距离计算修正:原代码中直接用
dist(maj_sup_vectors, x)会报错(因为maj_sup_vectors是二维数组),改为计算到所有支持向量的平均距离 - 恢复原始标签:重采样后用
np.full生成对应原始类别的标签数组,替换转换后的0/1
验证效果
运行修改后的代码,输出的类别分布将显示真实的原始类别,例如:
Original class distribution: {0: 9924, 2: 15} Resampled class distribution ('ovo'): Counter({0: 15, 2: 15})
内容的提问来源于stack exchange,提问作者arilwan
相关产品推荐
相关产品推荐

