多分类SVM中某类近乎全为支持向量是否正常?附鸢尾花实验
问题描述
使用scikit-learn的SVC结合One-vs-Rest策略进行鸢尾花数据集的多分类任务时,发现versicolor类的35个训练样本几乎全部是支持向量,且各分类器的训练误差均为0。根据SVM的理论认知,这一情况超出预期,特此问询该现象是否正常,或是参数设置、代码实现存在问题。
实验代码
import pandas as pd import numpy as np from sklearn.svm import SVC from sklearn.multiclass import OneVsRestClassifier # 读取Excel文件 df = pd.read_excel("Classification iris.xlsx", index_col="instance_id") # 划分训练集和测试集 train_data = pd.concat([ df.loc[1:35], df.loc[51:85], df.loc[101:135] ]) test_data = pd.concat([ df.loc[36:50], df.loc[86:100], df.loc[136:150] ]) # 分离特征和目标变量 train_features = train_data.drop(columns=['class']) train_target = train_data['class'] test_features = test_data.drop(columns=['class']) test_target = test_data['class'] # 构建One-vs-Rest策略的支持向量机 svm = OneVsRestClassifier(SVC(kernel='linear', C=1e5)) svm.fit(train_features, train_target) # 生成预测结果 targets_train_pred = svm.predict(train_features) targets_test_pred = svm.predict(test_features) # 计算总误差 total_training_error = sum(targets_train_pred != train_target) / len(train_data) total_testing_error = sum(targets_test_pred != test_target) / len(test_data) # 打印总训练和测试误差 print(f"Q2.2.2 使用标准SVM模型计算: 总训练误差: {total_training_error:.10f}, 总测试误差: {total_testing_error:.10f}") # 初始化结果存储列表 classes = ['setosa', 'versicolor', 'virginica'] linear_separable = [] for i, class_name in enumerate(classes): # 获取当前类的二分类器 classifier = svm.estimators_[i] # 计算每个类的误差 train_class_error = sum((targets_train_pred == class_name) != (train_target == class_name)) / len(train_data) test_class_error = sum((targets_test_pred == class_name) != (test_target == class_name)) / len(test_data) # 获取权重向量w和偏置b w = classifier.coef_[0] b = classifier.intercept_[0] support_vector_indices = classifier.support_ # 按格式打印每个类的结果 print(f" {class_name}类:") print(f"训练误差: {train_class_error:.10f}, 测试误差: {test_class_error:.10f}") print(f"w: [{', '.join([f'{x:.10f}' for x in w])}]") print(f"b: {b:.10f}") print(f"支持向量索引: {support_vector_indices.tolist()}") # 检查类是否线性可分 if train_class_error == 0: linear_separable.append(class_name) print(f" 线性可分类别: {', '.join(linear_separable)}")
实验输出
Q2.2.2 使用标准SVM模型计算: 总训练误差: 0.0571428571, 总测试误差: 0.0888888889 setosa类: 训练误差: 0.0000000000, 测试误差: 0.0000000000 w: [0.0097327108, 0.5377790363, -0.8273513712, -0.3820427629] b: 0.7734548984 支持向量索引: [42, 23, 24] versicolor类: 训练误差: 0.0000000000, 测试误差: 0.0000000000 w: [1.8485997715, -4.5023738999, -1.1043393026, 0.3212849949] b: 5.6773042297 支持向量索引: [1, 2, 8, 9, 13, 25, 27, 34, 71, 72, 73, 77, 78, 80, 81, 86, 88, 89, 90, 91, 92, 93, 96, 99, 100, 102, 103, 104, 35, 36, 37, 39, 40, 41, 42, 43, 44, 46, 47, 48, 49, 50, 52, 55, 58, 59, 60, 61, 62, 63, 65, 66, 67, 68, 69] virginica类: 训练误差: 0.0000000000, 测试误差: 0.0000000000 w: [-3.6465034341, -5.1763639697, 7.4285254512, 11.0024158268] b: -17.5703922240 支持向量索引: [55, 57, 62, 68, 96, 97, 99, 103] 线性可分类别: setosa, versicolor, virginica
问题解答
现象是否正常?
这种现象是正常的,并非代码实现错误,核心原因在于参数设置和数据集特性的共同作用。
关键原因分析
1. 正则化参数C设置过大
你使用的C=1e5属于极高的惩罚系数:
- 在SVM中,C控制模型对训练误分类的容忍度。C越大,模型越追求完全拟合所有训练样本,甚至不惜让决策边界过度复杂,将大量样本纳入支持向量。
- 当C趋近于无穷大时,线性SVM退化为硬间隔SVM,必须找到一个能完全分离两类的超平面(若数据线性可分),此时所有位于间隔边界或误分类区域的样本都会成为支持向量。
2. Versicolor与Virginica类的特征重叠
鸢尾花数据集中,versicolor和virginica的特征重叠程度很高,而setosa与另外两类完全线性可分。在One-vs-Rest策略下,针对versicolor的二分类器需要区分versicolor和“非versicolor”(setosa+virginica):
- 由于两类重叠多,硬间隔SVM(大C值)需要将几乎所有versicolor样本作为支持向量,才能构建出恰好分离两类的超平面,确保训练集无任何误分类。
3. 训练误差为0的合理性
三个二分类器训练误差均为0,是大C值强制模型完全拟合训练数据的结果:
- Setosa本身与其他两类线性可分,轻松实现0训练误差;
- Versicolor和Virginica在硬间隔约束下,模型通过调整超平面刚好实现训练集全对,代价就是大量支持向量。
代码优化建议
你的代码实现逻辑正确,但有可优化之处:
- 样本划分:手动按instance_id划分易引入分布偏差,推荐使用
sklearn.model_selection.train_test_split进行随机划分; - 参数调整:过大的C易导致过拟合,尝试减小C(如C=1、C=10),可观察到支持向量数量减少,测试误差可能降低;
- 误差计算:当前的
train_class_error基于多分类结果,更准确的二分类器训练误差应直接用该分类器的预测结果(classifier.predict(train_features))与train_target == class_name对比计算,但不影响现有结论。
验证方案
- 调整C为较小值(如C=1)重新训练,versicolor类的支持向量数量会大幅减少;
- 使用
sklearn.inspection.DecisionBoundaryDisplay绘制决策边界,直观观察不同C值下超平面的变化。
内容的提问来源于stack exchange,提问作者paul
相关产品推荐
相关产品推荐

