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推荐了XGBoostAUC 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 500d 20。典型如医学影像ROI提取后的纹理特征灰度共生矩阵14维小波能量8维形状描述符12维34维样本仅217例。此时SVM的结构风险最小化原则比逻辑回归的ERM经验风险最小化更能抵抗过拟合。实测同样数据逻辑回归CV AUC 0.79SVM达0.85。边界非线性但平滑型两类样本在原始空间线性不可分但存在一个“平滑弯曲”的分界面。比如用户行为序列转化预测横轴是“页面停留时长”纵轴是“点击按钮次数”正样本下单聚集在右上角一片弧形区域。RBF核能自然拟合这种边界而决策树会切成锯齿状碎片增加泛化误差。噪声有限的关键型数据质量总体较好但存在少量明显异常点如传感器漂移导致的离群特征值。SVM只依赖支持向量这些异常点若未成为SV对模型几乎无影响而KNN或线性回归会直接被拖偏。某设备故障预警项目中剔除5个明显传感器故障样本后SVM性能不变但逻辑回归AUC下降0.09。反例也很明确如果数据量N10000且d10SVM训练速度会显著慢于逻辑回归如果类别极度不平衡正负样本比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(kernelrbf, C1.0, gammascale) 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, bins2)确认正负样本比例。若比例4:1必须记录后续需启用class_weightbalanced或采样。特征缺失值热力图用seaborn.heatmap(X_train.isnull(), cbarFalse)。SVM不接受NaN但更重要的是某特征缺失率30%说明该特征可靠性存疑应考虑剔除而非插补。特征分布箱线图按类别分组对每个数值特征画sns.boxplot(xy_train, yX_train[:, i])。重点看正样本箱体是否整体高于负样本是否存在跨类别重叠极少的特征后者是天然的强判别特征可优先保留。特征相关性矩阵np.corrcoef(X_train.T)计算皮尔逊相关系数。若两特征|corr|0.95说明信息冗余SVM训练时可能因矩阵病态而收敛失败ConvergenceWarning。我通常保留方差更大的那个。异常值检测IQR法对每个特征计算Q1、Q3、IQR标记 Q1-1.5×IQR或 Q31.5×IQR的样本。这些点若成为支持向量会强力扭曲边界。某次在设备温度数据上发现2个样本温度值为-273℃绝对零度显然是传感器故障剔除后SVM测试AUC提升0.04。提示所有诊断代码我封装成data_diagnosis(X, y, feature_names)函数输出PDF报告含图表和文字结论。这不是形式主义而是确保每次实验起点一致。曾有个学生跳过这步用未清洗的数据跑SVM得到“完美”训练准确率100%结果测试为50%——因为所有正样本恰好都是异常高温点模型学到了噪声而非规律。3.2 第二道关预处理——标准化不是目的而是让距离度量有意义SVM的核心是计算样本间距离用于核函数而距离计算的前提是各特征量纲一致。这里有个关键误区很多人认为“标准化减均值除标准差”但不同场景下StandardScaler、MinMaxScaler、RobustScaler效果天差地别。StandardScalerz-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 trains params若对测试集单独fit_transform会导致数据泄露模型评估虚高。我见过太多人在这里栽跟头。3.3 第三道关核函数抉择——线性核不是“简单”RBF不是“万能”SVM的核函数是灵魂但选错等于自废武功。我总结了一个决策树是否特征维度d 样本量N → 是 → 优先线性核LinearSVC更快且高维下RBF易过拟合 ↓否 是否两类在原始空间近似线性可分 → 是 → 线性核边界清晰可解释性强 ↓否 是否需要柔性边界且数据量适中N100~5000 → 是 → RBF核最常用平衡能力最强 ↓否 是否样本量极小N50且特征有明确物理意义 → 是 → 自定义核如基于领域知识的距离函数线性核kernellinear。优势是训练快O(N×d²)、模型可解释coef_给出各特征权重、不易过拟合。某信贷审批项目用线性SVMcoef_显示“逾期次数”权重最高与业务逻辑完全吻合模型顺利通过风控审核。但若数据本身线性不可分强行用线性核准确率会惨不忍睹。RBF核高斯核kernelrbf。公式K(x_i, x_j) exp(-γ ||x_i - x_j||²)。关键参数γ控制单个样本的影响半径γ越大影响越局部模型越复杂易过拟合γ越小影响越全局模型越平滑易欠拟合。我的经验法则γ初始值设为1/(n_features * X.var())即scale选项然后围绕此值做对数搜索。慎用多项式核和Sigmoid核多项式核kernelpoly在文本分类中偶有奇效但参数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_splits5)做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_components2) X_pca pca.fit_transform(X_train_scaled) sv_pca pca.transform(model.support_vectors_) # 投影支持向量 plt.scatter(X_pca[y_train0, 0], X_pca[y_train0, 1], cblue, alpha0.3, labelNegative) plt.scatter(X_pca[y_train1, 0], X_pca[y_train1, 1], cred, alpha0.3, labelPositive) plt.scatter(sv_pca[:, 0], sv_pca[:, 1], s100, facecolorsnone, edgecolorsblack, linewidth2, labelSupport Vectors) plt.legend() plt.title(SVM Decision Boundary in PCA Space)这张图能告诉你一切边界是否合理支持向量是否均匀分布在边界两侧有没有孤零零的离群支持向量可能是噪声某次图中出现一个红色支持向量远离主簇检查原始数据发现是录入错误修正后模型更稳。3.6 第六道关评估不止于准确率——二分类的四大核心指标如何取舍准确率Accuracy在类别平衡时可用但一旦失衡它会严重误导。我强制使用四个指标并明确它们的业务含义指标公式业务意义SVM优化侧重精确率PrecisionTP/(TPFP)“我预测为正的样本中有多少是真的”防止误伤如垃圾邮件误判为正常召回率RecallTP/(TPFN)“所有真实的正样本中我找出了多少”防止漏网如疾病漏诊F1-score2×Prec×Rec/(PrecRec)Prec和Rec的调和平均平衡两者通用基准AUC-ROCROC曲线下面积模型在所有分类阈值下的综合判别能力最鲁棒的全局指标选择哪个指标主导优化取决于业务场景风控场景如欺诈检测宁可多报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曲线。如果曲线在左上角凸起说明模型优秀如果接近对角线说明模型几乎无判别力。某次AUC0.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, alpha0.3, cmapplt.cm.RdYlBu) plt.scatter(X_train_scaled[y_train0, a], X_train_scaled[y_train0, b], cblue, labelNegative) plt.scatter(X_train_scaled[y_train1, a], X_train_scaled[y_train1, b], cred, labelPositive) plt.xlabel(fFeature {a} (scaled)) plt.ylabel(fFeature {b} (scaled)) plt.legend()这张图的价值无可替代它让你“看见”模型。如果边界是一条光滑曲线说明RBF核工作正常如果是锯齿状说明γ过大或数据噪声过多如果边界完全偏离数据簇说明标准化或参数严重错误。我曾靠这张图发现某次gamma设得太大边界在特征空间里疯狂震荡立刻调小10倍问题解决。4. 完整实操流程从零开始复现一个工业级SVM二分类项目4.1 数据准备与环境搭建5分钟建立可复现实验环境我们以经典的乳腺癌威斯康星诊断数据集Breast Cancer Wisconsin Diagnostic为例它完美符合SVM黄金三角N569d30类别相对平衡357良性/212恶性特征均为连续数值。所有代码均可在Python 3.8、scikit-learn 1.0环境下直接运行。环境安装推荐condaconda create -n svm_env python3.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, columnsfeature_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, bins2, rwidth0.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(), cbarFalse, axaxes[0,1]) axes[0,1].set_title(Missing Values Heatmap) else: axes[0,1].text(0.5, 0.5, No Missing Values, hacenter, vacenter, fontsize12) axes[0,1].set_title(Missing Values Check) axes[0,1].axis(off) # 3. 前两个特征的箱线图以mean radius和mean texture为例 axes[1,0].boxplot([X[y0, 0], X[y1, 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[y0, 1], X[y1, 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_size0.3, random_state42, stratifyy ) # 预处理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(fScaler参数 - 中位数: {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(kernellinear, C1.0, random_state42) 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(fAUC: {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_splits5, shuffleTrue, random_state42) grid_search GridSearchCV( SVC(kernelrbf, random_state42), param_grid, cvcv, scoringroc_auc, n_jobs-1, verbose1 ) 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(fAUC: {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_components2) 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_train0, 0], X_train_pca[y_train0, 1], clightblue, alpha0.6, labelBenign, s30) plt.scatter(X_train_pca[y_train1, 0], X_train_pca[y_train1, 1], csalmon, alpha0.6, labelMalignant, s30) plt.scatter(sv_pca[:, 0], sv_pca[:, 1], s100, facecolorsnone, edgecolorsblack, linewidth2, labelSupport Vectors) plt.xlabel(fPC1 ({pca.explained_variance_ratio_[0]:.2%} variance)) plt.ylabel(fPC2 ({pca.explained_variance_ratio_[1]:.2%} variance)) plt.title(SVM Support Vectors in PCA Space) plt.legend() plt.grid(True, alpha0.3) plt.show()**Step 6: 决策边界热力图聚焦