Scikit-learn中线性SVM基于对偶系数计算截距的实现疑问
线性SVM截距计算:Scikit-learn实现与教材差异解析
问题描述
尝试使用对偶系数计算线性核SVM分类器的截距,因此选择SVC类而非LinearSVC(后者无dual_coef_属性)。测试代码如下:
from sklearn import svm from sklearn.utils import shuffle from sklearn.inspection import DecisionBoundaryDisplay import matplotlib.pyplot as plt import numpy as np from numpy.testing import assert_array_almost_equal seed = np.random.seed(123) # Data m = 1000 c2 = (-1, 1) c1 = (2, 2.5) r1 = 1.5 r2 = 3.5 rand_1_theta = np.random.uniform(0, 1, size=m) rand_1_radius = np.random.uniform(0, 1, size=m) ds1_x = (c1[0] + np.sqrt(rand_1_radius * r1) * np.cos(rand_1_theta * 2 * np.pi)) ds1_y = (c1[1] + np.sqrt(rand_1_radius * r1) * np.sin(rand_1_theta * 2 * np.pi)) rand_2_theta = np.random.uniform(0, 1, size=m) rand_2_radius = np.random.uniform(0, 1, size=m) ds2_x = (c2[0] + np.sqrt(rand_2_radius * r2) * np.cos(rand_2_theta * 2 * np.pi)) ds2_y = (c2[1] + np.sqrt(rand_2_radius * r2) * np.sin(rand_2_theta * 2 * np.pi)) X = np.stack((np.concatenate((ds1_x, ds2_x)), np.concatenate((ds1_y, ds2_y))), axis=1) y = np.concatenate((np.zeros(m), np.ones(m))) rgb = [(1,0,0) if val==0 else (0,0,1) for val in y] # Preprocessing X, y, rgb = shuffle(X, y, rgb, random_state=123) # SVM model = svm.SVC(probability=True, kernel='linear', random_state=123) model.fit(X, y) # Evaluation support_vectors = model.support_vectors_ support_indexes = model.support_ coef = model.coef_ dual_coeff = model.dual_coef_ intercept = model.intercept_ primal_coeff = np.matmul(dual_coeff, support_vectors) print(primal_coeff ) print(coef) y_support = y[support_indexes] all_bias = y_support - np.dot(support_vectors, primal_coeff.squeeze()) intercept_estimate_1 = np.mean(all_bias) intercept_estimate_2 = np.min(all_bias) print(intercept) print(intercept_estimate_1) print(intercept_estimate_2)
运行代码后发现,通过支持向量计算得到的偏置均值(intercept_estimate_1)与Scikit-learn输出的截距不符,而偏置最小值(intercept_estimate_2)更接近模型的intercept_值。但多数教材中截距应为所有偏置的均值,疑惑Scikit-learn的实现逻辑。
原因解析
1. 标签映射错误导致计算偏差
Scikit-learn的SVC在二分类任务中,会自动将输入的0/1标签映射为SVM理论标准的**-1/1**标记,但你在计算偏置时仍使用原始的0/1标签,这直接导致了计算结果错误。
修正代码中的标签映射问题:
# 将原始0/1标签转换为SVM内部使用的-1/1 y_support_svm = np.where(y_support == 0, -1, 1) all_bias = y_support_svm - np.dot(support_vectors, primal_coeff.squeeze())
修正后重新计算均值,会发现结果与model.intercept_高度吻合。
2. Scikit-learn的截距计算逻辑
SVC底层依赖LIBSVM库,其截距计算并非简单取所有支持向量的偏置均值,而是遵循以下逻辑:
- 仅考虑自由支持向量:即拉格朗日乘子$\alpha_i$满足$0 < \alpha_i < C$的支持向量,这类点恰好落在分类间隔的边界上,严格满足$y_i(w\cdot x_i + b) = 1$
- 计算这些自由支持向量对应的$b_i = y_i - w\cdot x_i$,最终取它们的平均值作为模型的截距$b$
这种做法是为了保证截距的稳定性,避免受那些因软间隔松弛变量影响、不严格落在间隔边界上的支持向量干扰。
内容的提问来源于stack exchange,提问作者André
相关产品推荐
相关产品推荐

