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

基于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方法,核心是:

  1. 记录当前处理的原始类别,避免被转换后的0/1标签混淆
  2. 正确获取对应原始类别的支持向量
  3. 重采样后恢复原始类别标签

修改后的完整代码

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 00:57:03