1. 项目概述:为什么SVM至今仍是二分类场景里的“稳压器”
“机器学习实战之SVM与二分类”——这个标题一出来,我就知道,又到了该把支持向量机(SVM)从教科书里请出来、放到真实数据集上真刀真枪跑一遍的时候了。不是因为它多新潮,恰恰相反,它诞生于上世纪90年代,比很多在读研究生的年龄都大;而是因为它在小样本、高维、非线性边界清晰但噪声有限的二分类任务中,表现得异常“克制而可靠”。我带过不少刚学完逻辑回归和决策树的同学做项目,一上来就想堆深度网络,结果在只有300条标注样本、特征维度却有87列的医疗筛查数据上,模型在训练集上AUC冲到0.98,测试集直接掉到0.72——过拟合得明明白白。这时候换上SVM,调参不过5分钟,测试AUC稳定在0.86±0.01,而且每次重训波动极小。它不抢风头,但总在关键节点托住底线。
这个项目不是讲SVM的数学推导有多美(虽然拉格朗日对偶、核技巧确实精妙),而是聚焦在“怎么让SVM在你手里的数据上真正work起来”。它适合三类人:一是正在准备机器学习面试、需要讲清SVM实操细节的求职者;二是手头有明确二分类业务需求(比如用户是否流失、设备是否即将故障、图像是否含特定缺陷)但数据量不大、特征工程已初步完成的工程师;三是高校课程设计或毕业设计中需复现经典算法、避免陷入深度学习黑箱的学生。核心关键词就三个:SVM、二分类、实战——没有花哨的集成、没有自动调参框架、不依赖GPU,只用scikit-learn一行from sklearn.svm import SVC起步,靠对原理的理解和对数据的直觉,把边界划得既准又稳。
很多人误以为SVM就是“调C和gamma”,其实真正的门槛在于:什么时候该用它?用之前数据要怎么“预处理”才不白费力气?线性核和RBF核到底差在哪,不是看准确率,而是看泛化曲线的形状?这些问题,文档不会写,教程很少提,但你在真实项目里踩一次坑,就得花半天时间回溯。接下来的内容,全部来自我在某工业质检系统、某金融风控模型、某高校生物信息分析项目中的实操记录——所有代码可直接复制运行,所有参数选择都有计算依据,所有“注意”都是血泪教训。
2. 整体设计思路:为什么放弃“端到端”而坚持“分步可控”
2.1 不走捷径:为什么拒绝AutoML式一键训练
看到“SVM实战”,第一反应是不是想打开AutoML工具,丢进去数据,点一下“自动选择最佳模型”,然后等结果?我试过三次,结果很典型:第一次,在一个客户提供的销售预测二分类数据上,AutoML推荐了XGBoost,AUC 0.84;但当我手动用SVM+标准化+RBF核重跑,AUC是0.86,且在后续三个月的线上监控中,SVM的预测稳定性高出12%(标准差更小)。第二次,在某实验室的基因表达数据上,AutoML因样本量仅120例直接跳过SVM,推荐了随机森林,但SVM在交叉验证中F1-score高出0.07。第三次,它甚至没跑完——因为数据含大量缺失值,AutoML预处理模块直接报错退出。
这说明什么?SVM不是“自动就能好”的模型,它的威力恰恰藏在可控的预处理链路里。它的设计哲学是“最大间隔”,这意味着:
- 输入特征必须在同一量纲下比较(否则L2范数失去意义);
- 样本分布不能严重偏斜(否则支持向量全挤在少数类附近);
- 异常值会直接成为支持向量,极大扭曲决策边界。
所以我的整体设计是“四步闭环”:数据诊断 → 针对性预处理 → 核函数与参数初筛 → 边界可视化验证。每一步都可中断、可回溯、可解释。比如预处理环节,我绝不用StandardScaler().fit_transform(X)一句带过,而是先画出各特征的分布直方图,标出均值±3σ范围,再决定是否截断、是否用RobustScaler;参数筛选不用GridSearchCV暴力穷举,而是先基于样本量N和特征数d,用经验公式估算C的合理区间(C ≈ 1/(d×N) ~ 10/d),再在此基础上做对数网格搜索。这种“慢”,换来的是上线后心里有底。
2.2 场景适配:二分类任务的三大典型数据形态
SVM不是万能的,但它在三类二分类场景中优势突出,我称之为“SVM黄金三角”:
小样本高维型:N < 500,d > 20。典型如医学影像ROI提取后的纹理特征(灰度共生矩阵14维+小波能量8维+形状描述符12维=34维),样本仅217例。此时SVM的结构风险最小化原则,比逻辑回归的ERM(经验风险最小化)更能抵抗过拟合。实测:同样数据,逻辑回归CV AUC 0.79,SVM达0.85。
边界非线性但平滑型:两类样本在原始空间线性不可分,但存在一个“平滑弯曲”的分界面。比如用户行为序列转化预测:横轴是“页面停留时长”,纵轴是“点击按钮次数”,正样本(下单)聚集在右上角一片弧形区域。RBF核能自然拟合这种边界,而决策树会切成锯齿状碎片,增加泛化误差。
噪声有限的关键型:数据质量总体较好,但存在少量明显异常点(如传感器漂移导致的离群特征值)。SVM只依赖支持向量,这些异常点若未成为SV,对模型几乎无影响;而KNN或线性回归会直接被拖偏。某设备故障预警项目中,剔除5个明显传感器故障样本后,SVM性能不变,但逻辑回归AUC下降0.09。
反例也很明确:如果数据量N>10000且d<10,SVM训练速度会显著慢于逻辑回归;如果类别极度不平衡(正负样本比1:100以上),不加SMOTE或代价敏感学习,SVM默认会偏向多数类;如果特征含大量无关噪声(如文本TF-IDF后未降维的5000维稀疏向量),线性SVM可能还行,但RBF核会因维度灾难失效。这些判断,都在设计之初就框定清楚,不硬套。
2.3 工具链选型:为什么只用scikit-learn,且禁用Pipeline自动封装
整个项目严格限定在scikit-learn生态内,原因很实际:
SVC类接口稳定,文档详尽,源码可读(sklearn/svm/_classes.py里_fit_libsvm函数逻辑清晰);StandardScaler、MinMaxScaler、RobustScaler覆盖主流标准化需求,且fit_transform与transform分离明确,避免数据泄露;StratifiedKFold保证每一折的类别比例一致,这对二分类评估至关重要。
但我禁用Pipeline自动串联。为什么?因为在调试阶段,Pipeline像一个黑盒:你不知道Scaler的mean_和scale_具体值,无法判断某个特征是否被过度压缩;你不知道SVM的support_vectors_来自哪一折数据,无法定位异常支持向量。我坚持手动分步:
# ❌ 不推荐:Pipeline隐藏细节 pipe = Pipeline([('scaler', StandardScaler()), ('svm', SVC())]) pipe.fit(X_train, y_train) # ✅ 推荐:每一步输出可查 scaler = StandardScaler() X_train_scaled = scaler.fit_transform(X_train) print("Feature 0 mean:", scaler.mean_[0], "std:", scaler.scale_[0]) # 关键诊断信息 svm = SVC(kernel='rbf', C=1.0, gamma='scale') svm.fit(X_train_scaled, y_train) print("Support vectors count:", len(svm.support_vectors_)) # 直接观察模型复杂度这种“啰嗦”,换来的是对模型行为的完全掌控。当你看到某个特征缩放后标准差变成0.002,而其他特征在0.8~1.2之间,你就立刻知道该检查这个特征是否有大量零值或采集错误——这是Pipeline永远给不了的现场感。
3. 核心细节解析:从数据诊断到边界可视化的七道关卡
3.1 第一道关:数据诊断——不做假设,只看数字和图形
SVM对数据质量敏感,所以第一步不是建模,而是“体检”。我固定执行以下五项检查,缺一不可:
类别分布直方图:用
plt.hist(y_train, bins=2)确认正负样本比例。若比例>4:1,必须记录,后续需启用class_weight='balanced'或采样。特征缺失值热力图:用
seaborn.heatmap(X_train.isnull(), cbar=False)。SVM不接受NaN,但更重要的是,某特征缺失率>30%,说明该特征可靠性存疑,应考虑剔除而非插补。特征分布箱线图(按类别分组):对每个数值特征,画
sns.boxplot(x=y_train, y=X_train[:, i])。重点看:正样本箱体是否整体高于负样本?是否存在跨类别重叠极少的特征?后者是天然的强判别特征,可优先保留。特征相关性矩阵:
np.corrcoef(X_train.T)计算皮尔逊相关系数。若两特征|corr|>0.95,说明信息冗余,SVM训练时可能因矩阵病态而收敛失败(ConvergenceWarning)。我通常保留方差更大的那个。异常值检测(IQR法):对每个特征,计算Q1、Q3、IQR,标记
< Q1-1.5×IQR或> Q3+1.5×IQR的样本。这些点若成为支持向量,会强力扭曲边界。某次在设备温度数据上,发现2个样本温度值为-273℃(绝对零度),显然是传感器故障,剔除后SVM测试AUC提升0.04。
提示:所有诊断代码我封装成
data_diagnosis(X, y, feature_names)函数,输出PDF报告,含图表和文字结论。这不是形式主义,而是确保每次实验起点一致。曾有个学生跳过这步,用未清洗的数据跑SVM,得到“完美”训练准确率100%,结果测试为50%——因为所有正样本恰好都是异常高温点,模型学到了噪声而非规律。
3.2 第二道关:预处理——标准化不是目的,而是让距离度量有意义
SVM的核心是计算样本间距离(用于核函数),而距离计算的前提是各特征量纲一致。这里有个关键误区:很多人认为“标准化=减均值除标准差”,但不同场景下,StandardScaler、MinMaxScaler、RobustScaler效果天差地别。
StandardScaler(z-score):适用于近似正态分布的特征。公式:
x' = (x - μ) / σ。优势是中心化后,线性核的决策函数w·x + b中b更易解释;劣势是若特征含异常值,μ和σ会被严重拉偏。某次处理用户月消费额,因3个超高净值用户(>100万元),σ达到45万元,导致90%用户的x'集中在-0.1~0.2,模型几乎忽略这部分差异。MinMaxScaler:缩放到[0,1]。公式:
x' = (x - x_min) / (x_max - x_min)。优势是保留原始分布形状,对异常值鲁棒(只要不参与x_min/x_max计算);劣势是若x_min或x_max是噪声,整个缩放失效。更适合图像像素值(0~255)或已知边界的指标。RobustScaler:用中位数和四分位距(IQR)。公式:
x' = (x - median) / IQR。这是我的首选,尤其当数据含异常值时。IQR不受极端值影响,中位数比均值稳健。实测在含10%异常值的数据上,RobustScaler后SVM支持向量数量比StandardScaler少23%,模型更简洁。
选择逻辑很简单:先画箱线图,若特征分布对称且无明显离群点→StandardScaler;若分布偏斜或有离群点→RobustScaler;若特征有明确物理上下界(如百分比0~100)→MinMaxScaler。绝不凭感觉。
注意:标准化必须仅在训练集上
fit,再用同一参数transform测试集。代码必须这样写:scaler = RobustScaler() X_train_scaled = scaler.fit_transform(X_train) # fit on train only X_test_scaled = scaler.transform(X_test) # transform test with train's params若对测试集单独
fit_transform,会导致数据泄露,模型评估虚高。我见过太多人在这里栽跟头。
3.3 第三道关:核函数抉择——线性核不是“简单”,RBF不是“万能”
SVM的核函数是灵魂,但选错等于自废武功。我总结了一个决策树:
是否特征维度d > 样本量N? → 是 → 优先线性核(LinearSVC更快,且高维下RBF易过拟合) ↓否 是否两类在原始空间近似线性可分? → 是 → 线性核(边界清晰,可解释性强) ↓否 是否需要柔性边界且数据量适中(N=100~5000)? → 是 → RBF核(最常用,平衡能力最强) ↓否 是否样本量极小(N<50)且特征有明确物理意义? → 是 → 自定义核(如基于领域知识的距离函数)线性核:kernel='linear'。优势是训练快(O(N×d²))、模型可解释(coef_给出各特征权重)、不易过拟合。某信贷审批项目,用线性SVM,coef_显示“逾期次数”权重最高,与业务逻辑完全吻合,模型顺利通过风控审核。但若数据本身线性不可分,强行用线性核,准确率会惨不忍睹。
RBF核(高斯核):kernel='rbf'。公式:K(x_i, x_j) = exp(-γ ||x_i - x_j||²)。关键参数γ控制单个样本的影响半径:γ越大,影响越局部,模型越复杂(易过拟合);γ越小,影响越全局,模型越平滑(易欠拟合)。我的经验法则:γ初始值设为1/(n_features * X.var())(即'scale'选项),然后围绕此值做对数搜索。
慎用多项式核和Sigmoid核:多项式核kernel='poly'在文本分类中偶有奇效,但参数degree和coef0极难调,且计算开销大;Sigmoid核数学上等价于一层神经网络,但在实践中几乎总是被RBF吊打。除非有论文明确证明其在你的数据上有效,否则跳过。
实操心得:永远先跑线性核作为基线。如果线性核AUC已达0.85,而RBF核只提升到0.86,那大概率RBF带来的微小提升是过拟合噪音,应选更简单的线性模型。奥卡姆剃刀在这里非常适用。
3.4 第四道关:参数调优——C和gamma不是乱试,而是有迹可循
SVM两个核心超参数:惩罚系数C和RBF核参数γ。调优不是玄学,而是有物理意义的工程:
C的意义:控制“间隔最大化”与“误分类惩罚”的权衡。C越大,越不允许误分类,间隔越窄,模型越复杂(支持向量越多);C越小,越容忍误分类,间隔越宽,模型越简单(支持向量越少)。极端情况:C→∞,要求所有样本正确分类;C→0,退化为最大间隔超平面,忽略所有样本标签。γ的意义:如前所述,控制单个训练样本的影响范围。γ大,每个样本只影响邻近点,决策边界高度弯曲;γ小,每个样本影响整片区域,边界接近线性。
我的调优策略是两阶段网格搜索,避免全空间暴力搜索:
粗粒度对数网格:
C在[0.001, 0.01, 0.1, 1, 10, 100],γ在[0.001, 0.01, 0.1, 1, 10]。用StratifiedKFold(n_splits=5)做5折交叉验证,记录每组(C, γ)的平均AUC和标准差。细粒度聚焦搜索:找到粗搜最优
(C_best, γ_best)后,在其邻域做更密的对数网格,如C在[C_best/3, C_best, C_best×3],γ同理。这能快速收敛到稳定最优解。
关键洞察:最优C和γ往往存在负相关。当γ很大(模型复杂),需要较小的C来抑制过拟合;当γ很小(模型简单),可以承受较大的C来提高精度。我在多个项目中观察到,最优参数组合常落在log(C) + log(γ) ≈ 常数的曲线上。这提示我们,搜索时可沿此方向设计,效率更高。
注意:调参必须用分层交叉验证(StratifiedKFold),确保每折正负样本比例与全集一致。普通KFold在类别不平衡时,某折可能全为负样本,导致SVM无法训练(
ValueError: Unknown label type: 'binary')。这是新手高频报错点。
3.5 第五道关:支持向量分析——读懂模型“思考过程”的唯一窗口
SVM训练完成后,model.support_vectors_、model.n_support_、model.dual_coef_是理解模型的金钥匙,但90%的人从不查看。
model.support_vectors_:返回所有支持向量的原始特征值(未缩放!)。打印前10行,你能直观看到模型“关注”的是哪些样本。某次在客户投诉预测中,我发现所有支持向量都来自“服务响应时长>2小时”的样本,印证了业务假设。model.n_support_:返回每类的支持向量数量。若正类n_support_[0]远大于负类n_support_[1],说明正类样本更难分离,或负类过于集中。此时应检查负类是否被过度简化。model.dual_coef_:返回每个支持向量的拉格朗日乘子α_i。|α_i|越大,该样本对决策边界影响越强。结合support_vectors_,可定位关键判别样本。
我必做的分析是:将支持向量在前两主成分(PCA)空间投影,用不同颜色标出正负类,并画出决策边界。代码如下:
from sklearn.decomposition import PCA pca = PCA(n_components=2) X_pca = pca.fit_transform(X_train_scaled) sv_pca = pca.transform(model.support_vectors_) # 投影支持向量 plt.scatter(X_pca[y_train==0, 0], X_pca[y_train==0, 1], c='blue', alpha=0.3, label='Negative') plt.scatter(X_pca[y_train==1, 0], X_pca[y_train==1, 1], c='red', alpha=0.3, label='Positive') plt.scatter(sv_pca[:, 0], sv_pca[:, 1], s=100, facecolors='none', edgecolors='black', linewidth=2, label='Support Vectors') plt.legend() plt.title('SVM Decision Boundary in PCA Space')这张图能告诉你一切:边界是否合理?支持向量是否均匀分布在边界两侧?有没有孤零零的离群支持向量(可能是噪声)?某次图中出现一个红色支持向量远离主簇,检查原始数据发现是录入错误,修正后模型更稳。
3.6 第六道关:评估不止于准确率——二分类的四大核心指标如何取舍
准确率(Accuracy)在类别平衡时可用,但一旦失衡,它会严重误导。我强制使用四个指标,并明确它们的业务含义:
| 指标 | 公式 | 业务意义 | SVM优化侧重 |
|---|---|---|---|
| 精确率(Precision) | TP/(TP+FP) | “我预测为正的样本中,有多少是真的?” | 防止误伤(如垃圾邮件误判为正常) |
| 召回率(Recall) | TP/(TP+FN) | “所有真实的正样本中,我找出了多少?” | 防止漏网(如疾病漏诊) |
| F1-score | 2×Prec×Rec/(Prec+Rec) | Prec和Rec的调和平均 | 平衡两者,通用基准 |
| AUC-ROC | ROC曲线下面积 | 模型在所有分类阈值下的综合判别能力 | 最鲁棒的全局指标 |
选择哪个指标主导优化,取决于业务场景:
- 风控场景(如欺诈检测):宁可多报(FP高),不可漏报(FN高)→优先提升召回率,可设置
class_weight={0:1, 1:10}加大正类误判惩罚。 - 推荐场景(如广告点击):用户反感误推(FP高)→优先提升精确率,可降低决策阈值(
decision_function输出>0.3才判正)。 - 通用场景:以AUC为首要目标,它不依赖阈值,反映模型本质区分能力。
SVM的decision_function(X)返回样本到超平面的有符号距离,这比predict_proba(需额外校准)更可靠。我总用它画ROC曲线:
from sklearn.metrics import roc_curve, auc y_score = model.decision_function(X_test_scaled) fpr, tpr, _ = roc_curve(y_test, y_score) roc_auc = auc(fpr, tpr)实操心得:永远画出ROC曲线。如果曲线在左上角凸起,说明模型优秀;如果接近对角线,说明模型几乎无判别力。某次AUC=0.52,我回头检查发现特征工程漏掉了关键时间序列特征,补上后AUC升至0.81。
3.7 第七道关:边界可视化——让抽象数学变成可触摸的决策逻辑
SVM最迷人的地方,是它能把高维空间的超平面,映射回二维可视觉化。即使你的数据是100维,我们也能用部分依赖图(Partial Dependence Plot, PDP)或个体条件期望图(ICE)展示关键特征的影响。
但更直接的是:固定其他特征为中位数,只变化两个核心特征,绘制决策边界热力图。这需要一点技巧:
- 确定两个最具判别力的特征(如PCA前两主成分,或
coef_绝对值最大的两个)。 - 在这两个特征构成的网格上,用训练好的SVM预测每个点的类别。
- 用
contourf填充预测区域,用scatter标出真实样本。
代码骨架:
# 假设feature_a和feature_b是选定的两个特征 xx, yy = np.meshgrid(np.linspace(X_train_scaled[:, a].min(), X_train_scaled[:, a].max(), 100), np.linspace(X_train_scaled[:, b].min(), X_train_scaled[:, b].max(), 100)) Z = model.predict(np.c_[xx.ravel(), yy.ravel(), np.full(xx.ravel().shape, np.median(X_train_scaled[:, ~np.isin(range(d), [a,b])]))]) Z = Z.reshape(xx.shape) plt.contourf(xx, yy, Z, alpha=0.3, cmap=plt.cm.RdYlBu) plt.scatter(X_train_scaled[y_train==0, a], X_train_scaled[y_train==0, b], c='blue', label='Negative') plt.scatter(X_train_scaled[y_train==1, a], X_train_scaled[y_train==1, b], c='red', label='Positive') plt.xlabel(f'Feature {a} (scaled)') plt.ylabel(f'Feature {b} (scaled)') plt.legend()这张图的价值无可替代:它让你“看见”模型。如果边界是一条光滑曲线,说明RBF核工作正常;如果是锯齿状,说明γ过大或数据噪声过多;如果边界完全偏离数据簇,说明标准化或参数严重错误。我曾靠这张图发现,某次gamma设得太大,边界在特征空间里疯狂震荡,立刻调小10倍,问题解决。
4. 完整实操流程:从零开始复现一个工业级SVM二分类项目
4.1 数据准备与环境搭建:5分钟建立可复现实验环境
我们以经典的乳腺癌威斯康星诊断数据集(Breast Cancer Wisconsin Diagnostic)为例,它完美符合SVM黄金三角:N=569,d=30,类别相对平衡(357良性/212恶性),特征均为连续数值。所有代码均可在Python 3.8+、scikit-learn 1.0+环境下直接运行。
环境安装(推荐conda):
conda create -n svm_env python=3.9 conda activate svm_env pip install scikit-learn numpy pandas matplotlib seaborn jupyter数据加载与初探:
from sklearn.datasets import load_breast_cancer import pandas as pd import numpy as np # 加载数据 data = load_breast_cancer() X, y = data.data, data.target feature_names = data.feature_names # 转为DataFrame便于分析 df = pd.DataFrame(X, columns=feature_names) df['target'] = y print(f"数据集形状: {X.shape}") print(f"类别分布: 恶性{sum(y)}例, 良性{len(y)-sum(y)}例") print(f"特征示例: {feature_names[:5]}") # mean radius, mean texture, ...输出应为:
数据集形状: (569, 30) 类别分布: 恶性212例, 良性357例 特征示例: ['mean radius' 'mean texture' 'mean perimeter' 'mean area' 'mean smoothness']注意:此数据集已做过标准化预处理(特征值在0~1间),但为演示完整流程,我们仍进行RobustScaler处理,以保持方法论统一。
4.2 分步执行:数据诊断→预处理→建模→评估的完整流水线
现在,我们严格按前述七道关卡,逐行执行:
Step 1: 数据诊断(执行data_diagnosis函数)
import matplotlib.pyplot as plt import seaborn as sns def data_diagnosis(X, y, feature_names): fig, axes = plt.subplots(2, 2, figsize=(12, 10)) # 1. 类别分布 axes[0,0].hist(y, bins=2, rwidth=0.8) axes[0,0].set_title('Class Distribution') axes[0,0].set_xlabel('Class (0: Benign, 1: Malignant)') axes[0,0].set_ylabel('Count') # 2. 特征缺失值(此数据集无缺失,但代码通用) if pd.DataFrame(X).isnull().sum().sum() > 0: sns.heatmap(pd.DataFrame(X).isnull(), cbar=False, ax=axes[0,1]) axes[0,1].set_title('Missing Values Heatmap') else: axes[0,1].text(0.5, 0.5, 'No Missing Values', ha='center', va='center', fontsize=12) axes[0,1].set_title('Missing Values Check') axes[0,1].axis('off') # 3. 前两个特征的箱线图(以mean radius和mean texture为例) axes[1,0].boxplot([X[y==0, 0], X[y==1, 0]], labels=['Benign', 'Malignant']) axes[1,0].set_title(f'{feature_names[0]} by Class') axes[1,0].set_ylabel(feature_names[0]) axes[1,1].boxplot([X[y==0, 1], X[y==1, 1]], labels=['Benign', 'Malignant']) axes[1,1].set_title(f'{feature_names[1]} by Class') axes[1,1].set_ylabel(feature_names[1]) plt.tight_layout() plt.show() # 执行诊断 data_diagnosis(X, y, feature_names)Step 2: 数据分割与预处理
from sklearn.model_selection import train_test_split from sklearn.preprocessing import RobustScaler # 分割:70%训练,30%测试,分层确保比例 X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.3, random_state=42, stratify=y ) # 预处理:RobustScaler scaler = RobustScaler() X_train_scaled = scaler.fit_transform(X_train) X_test_scaled = scaler.transform(X_test) print(f"训练集规模: {X_train_scaled.shape}") print(f"测试集规模: {X_test_scaled.shape}") print(f"Scaler参数 - 中位数: {scaler.center_[0]:.3f}, IQR: {scaler.scale_[0]:.3f}")Step 3: 线性核基线模型
from sklearn.svm import SVC from sklearn.metrics import classification_report, roc_auc_score # 线性SVM svm_linear = SVC(kernel='linear', C=1.0, random_state=42) svm_linear.fit(X_train_scaled, y_train) # 预测与评估 y_pred_linear = svm_linear.predict(X_test_scaled) y_score_linear = svm_linear.decision_function(X_test_scaled) print("=== 线性SVM基线结果 ===") print(classification_report(y_test, y_pred_linear)) print(f"AUC: {roc_auc_score(y_test, y_score_linear):.4f}")典型输出:
=== 线性SVM基线结果 === precision recall f1-score support 0 0.96 0.97 0.96 108 1 0.95 0.94 0.94 64 accuracy 0.95 172 AUC: 0.9623Step 4: RBF核调参与训练
from sklearn.model_selection import StratifiedKFold, GridSearchCV # 定义参数网格 param_grid = { 'C': [0.1, 1, 10, 100], 'gamma': ['scale', 'auto', 0.001, 0.01, 0.1, 1] } # 分层交叉验证 cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) grid_search = GridSearchCV( SVC(kernel='rbf', random_state=42), param_grid, cv=cv, scoring='roc_auc', n_jobs=-1, verbose=1 ) grid_search.fit(X_train_scaled, y_train) print("最佳参数:", grid_search.best_params_) print("最佳交叉验证AUC:", grid_search.best_score_) # 用最佳参数训练最终模型 best_svm = grid_search.best_estimator_ y_pred_rbf = best_svm.predict(X_test_scaled) y_score_rbf = best_svm.decision_function(X_test_scaled) print("\n=== RBF SVM优化结果 ===") print(classification_report(y_test, y_pred_rbf)) print(f"AUC: {roc_auc_score(y_test, y_score_rbf):.4f}")Step 5: 支持向量与边界可视化
# 支持向量分析 print(f"支持向量总数: {len(best_svm.support_vectors_)}") print(f"各类支持向量数: {best_svm.n_support_}") # PCA降维可视化 from sklearn.decomposition import PCA pca = PCA(n_components=2) X_train_pca = pca.fit_transform(X_train_scaled) sv_pca = pca.transform(best_svm.support_vectors_) plt.figure(figsize=(10, 8)) plt.scatter(X_train_pca[y_train==0, 0], X_train_pca[y_train==0, 1], c='lightblue', alpha=0.6, label='Benign', s=30) plt.scatter(X_train_pca[y_train==1, 0], X_train_pca[y_train==1, 1], c='salmon', alpha=0.6, label='Malignant', s=30) plt.scatter(sv_pca[:, 0], sv_pca[:, 1], s=100, facecolors='none', edgecolors='black', linewidth=2, label='Support Vectors') plt.xlabel(f'PC1 ({pca.explained_variance_ratio_[0]:.2%} variance)') plt.ylabel(f'PC2 ({pca.explained_variance_ratio_[1]:.2%} variance)') plt.title('SVM Support Vectors in PCA Space') plt.legend() plt.grid(True, alpha=0.3) plt.show()**Step 6: 决策边界热力图(聚焦