资讯详情

资讯详情

机器学习-支持向量机

二维二分类看决策边界和支持向量import numpy as np import matplotlib.pyplot as plt from sklearn.datasets import make_moons from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler from sklearn.svm import SVC # 1. 生成数据 X, y make_moons(n_samples200, noise0.25, random_state42) # 2. 标准化 RBF 核 SVM clf Pipeline([ (scaler, StandardScaler()), (svm, SVC(kernelrbf, C1.0, gammascale)) ]) clf.fit(X, y) # 3. 画决策边界 xx, yy np.meshgrid( np.linspace(X[:, 0].min() - 1, X[:, 0].max() 1, 500), np.linspace(X[:, 1].min() - 1, X[:, 1].max() 1, 500) ) Z clf.predict(np.c_[xx.ravel(), yy.ravel()]).reshape(xx.shape) plt.figure(figsize(7, 5)) plt.contourf(xx, yy, Z, alpha0.25, cmapcoolwarm) plt.scatter(X[:, 0], X[:, 1], cy, cmapcoolwarm, edgecolorsk, s30) # 4. 标出支持向量 sv clf.named_steps[svm].support_vectors_ # 注意support_vectors_ 是标准化后的坐标画图要逆变换回去 sv_original clf.named_steps[scaler].inverse_transform(sv) plt.scatter(sv_original[:, 0], sv_original[:, 1], s120, facecolorsnone, edgecolorsgreen, linewidths1.5, label支持向量) plt.legend() plt.title(RBF SVM 决策边界与支持向量) plt.show() print(支持向量数量, len(clf.named_steps[svm].support_vectors_)) print(训练集准确率, clf.score(X, y))目的直观理解“支持向量”到底是什么。python from sklearn.datasets import make_moons from sklearn.svm import SVC import matplotlib.pyplot as plt import numpy as np X, y make_moons(n_samples200, noise0.25, random_state42) clf SVC(kernelrbf, C1.0, gammascale).fit(X, y) # 画出决策边界和支持向量完整代码见资料 看图解释背景颜色分界RBF核画出了一个弯曲的非线性边界。绿圈圈支持向量只有离边界最近的那些点被圈起来了。如果在现实中你把绿圈以外的点全部删掉重新训练画出来的分界线几乎一模一样。这就是支持向量的核心——决定边界的只是少数“关键人物”。C 和 gamma 对决策边界的影响import numpy as np import matplotlib.pyplot as plt from sklearn.datasets import make_moons from sklearn.preprocessing import StandardScaler from sklearn.svm import SVC X, y make_moons(n_samples200, noise0.25, random_state42) X StandardScaler().fit_transform(X) xx, yy np.meshgrid( np.linspace(X[:, 0].min() - 1, X[:, 0].max() 1, 300), np.linspace(X[:, 1].min() - 1, X[:, 1].max() 1, 300) ) grid np.c_[xx.ravel(), yy.ravel()] fig, axes plt.subplots(2, 3, figsize(15, 9)) params [ (0.1, 0.1), (1, 1), (100, 1), (1, 0.1), (1, 10), (100, 10), ] for ax, (C, gamma) in zip(axes.ravel(), params): clf SVC(kernelrbf, CC, gammagamma) clf.fit(X, y) Z clf.predict(grid).reshape(xx.shape) ax.contourf(xx, yy, Z, alpha0.25, cmapcoolwarm) ax.scatter(X[:, 0], X[:, 1], cy, cmapcoolwarm, edgecolorsk, s15) ax.set_title(fC{C}, gamma{gamma}, acc{clf.score(X,y):.2f}) plt.tight_layout() plt.show()用 2x3 的 6 张子图直观感受什么是“过拟合”和“欠拟合”。# 组合不同的 C 和 gamma生成 6 张小图 params [(0.1, 0.1), (1, 1), (100, 1), (1, 0.1), (1, 10), (100, 10)]左上看C小gamma小边界极其平滑像一条直线这叫欠拟合。它连训练集都没分好。右下看C大gamma大边界像碎玻璃一样围着每一个红蓝点绕圈训练准确率极高但遇到新数据肯定抓瞎这叫严重过拟合。看图结论C 相当于“老师多严厉”C越大越不能容忍分错gamma 相当于“学生的视野半径”gamma越大视野越窄只认死理。C和gamma必须配合着调不能单看一个。gamma 太大边界围着每个点转训练准确率高但很扭曲过拟合gamma 太小边界接近直线欠拟合C 太大容错少边界紧贴异常点C 太小容错多边界平滑可能欠拟合C 和 gamma 是交互的不能单独看。多分类 OvO vs OvRSVM 原生只能分 2 类。如果要分 3 类比如鸢尾花sklearn底层是怎么做的OvO一对一3个类别两两配对训练3*2/2 3个二分类器。如果类别有 100 个就要训练 4950 个分类器计算量大。OvR一对多3个类别训练 3 个二分类器A vs BC, B vs AC, C vs AB。注意在SVC里decision_function_shapeovr只是把底层的 OvO 结果聚合成 OvR 的形状方便输出底层训练依然是 OvO。from sklearn.datasets import load_iris from sklearn.model_selection import train_test_split from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler from sklearn.svm import SVC from sklearn.multiclass import OneVsRestClassifier from sklearn.metrics import accuracy_score X, y load_iris(return_X_yTrue) X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, random_state42, stratifyy ) # 1. 默认 SVC底层 OvO ovo_clf Pipeline([ (scaler, StandardScaler()), (svm, SVC(kernelrbf, C1, gammascale)) ]) ovo_clf.fit(X_train, y_train) ovo_pred ovo_clf.predict(X_test) # 2. 显式 OvROneVsRestClassifier 包装 ovr_clf Pipeline([ (scaler, StandardScaler()), (svm, OneVsRestClassifier(SVC(kernelrbf, C1, gammascale))) ]) ovr_clf.fit(X_train, y_train) ovr_pred ovr_clf.predict(X_test) print(OvO 准确率, accuracy_score(y_test, ovo_pred)) print(OvR 准确率, accuracy_score(y_test, ovr_pred)) # 查看底层训练了多少个二分类器 print(OvO 二分类器数量, len(ovo_clf.named_steps[svm].support_)) print(OvR 二分类器数量, len(ovr_clf.named_steps[svm].estimators_))网格搜索调 C 和 gamma目的用GridSearchCV自动找最佳参数。param_grid { svm__C: [0.1, 1, 10, 100], svm__gamma: [0.001, 0.01, 0.1, 1, scale] }from sklearn.datasets import load_iris from sklearn.model_selection import train_test_split, GridSearchCV from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler from sklearn.svm import SVC X, y load_iris(return_X_yTrue) X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, random_state42, stratifyy ) pipe Pipeline([ (scaler, StandardScaler()), (svm, SVC(kernelrbf)) ]) param_grid { svm__C: [0.1, 1, 10, 100], svm__gamma: [0.001, 0.01, 0.1, 1, scale] } grid GridSearchCV(pipe, param_grid, cv5, scoringaccuracy, n_jobs-1) grid.fit(X_train, y_train) print(最优参数, grid.best_params_) print(交叉验证最优分, grid.best_score_) print(测试集准确率, grid.score(X_test, y_test)) # 看每个组合的 CV 分数 import pandas as pd res pd.DataFrame(grid.cv_results_) print(res[[param_svm__C, param_svm__gamma, mean_test_score]] .sort_values(mean_test_score, ascendingFalse).head())你跑出了前五名参数排名。发现C1.0, gamma0.1是冠军平均得分 0.98。为什么不同参数分数一样因为鸢尾花数据集太简单了只要参数在合理范围内比如 C10, gamma0.1 和 C1, gamma0.1都能分对这体现了数据本身的鲁棒性。线性核 vs RBF 核高维稀疏场景在处理高维稀疏数据比如几万个词的文本时RBF 核计算量极大容易过拟合。此时应该用LinearSVC线性核。from sklearn.datasets import fetch_20newsgroups from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.svm import LinearSVC from sklearn.pipeline import Pipeline from sklearn.metrics import classification_report # 只取两个类别减少下载和训练时间 cats [rec.sport.baseball, rec.sport.hockey] train fetch_20newsgroups(subsettrain, categoriescats, remove(headers, footers, quotes)) test fetch_20newsgroups(subsettest, categoriescats, remove(headers, footers, quotes)) clf Pipeline([ (tfidf, TfidfVectorizer(max_features20000, stop_wordsenglish)), (svm, LinearSVC(C1.0)) ]) clf.fit(train.data, train.target) pred clf.predict(test.data) print(classification_report(test.target, pred, target_namescats))进阶版体验真实的工业级代码。使用Pipeline防止数据泄漏开启probabilityTrue输出置信度。import numpy as np import pandas as pd from sklearn.datasets import load_breast_cancer # 换成一个更难的数据集 from sklearn.model_selection import train_test_split, GridSearchCV, cross_val_score from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler from sklearn.svm import SVC # 1. 换数据集乳腺癌预测569个样本30个特征二分类 # 这个数据集比 Iris 难多了更容易看出模型好坏 data load_breast_cancer() X, y data.data, data.target # 2. 划分数据 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy ) # 3. 搭建 Pipeline (注意这里加了 probabilityTrue为了后面输出概率) pipe Pipeline([ (scaler, StandardScaler()), (svm, SVC(kernelrbf, probabilityTrue)) ]) # 4. 缩小网格搜索范围数据集变了原来的参数范围可能不是最优的 param_grid { svm__C: [0.1, 1, 10, 100], svm__gamma: [0.001, 0.01, 0.1, 1] } grid GridSearchCV(pipe, param_grid, cv5, scoringaccuracy, n_jobs-1) grid.fit(X_train, y_train) # 5. 输出最优结果 best_model grid.best_estimator_ print( 最佳参数 ) print(grid.best_params_) print(f测试集准确率{best_model.score(X_test, y_test):.4f}) # # 进阶玩法一全局交叉验证不再依赖单次随机划分 # print(\n 进阶玩法一用交叉验证评估模型 ) # 用最优模型在整个训练集上做 5 折交叉验证 cv_scores cross_val_score(best_model, X_train, y_train, cv5) print(f5次交叉验证的分数{np.round(cv_scores, 4)}) print(f平均分{cv_scores.mean():.4f}标准差{cv_scores.std():.4f}) # 注意如果标准差很小说明模型非常稳定如果标准差大说明数据某些划分太极端。 # # 进阶玩法二输出概率 (predict_proba) # print(\n 进阶玩法二输出概率软判决 ) # 我们挑测试集的前 5 个样本看看 probas best_model.predict_proba(X_test[:5]) preds best_model.predict(X_test[:5]) for i in range(5): # probas[i][0] 是恶性概率probas[i][1] 是良性概率 print(f样本 {i1}: 真实标签{y_test[i]}, 预测标签{preds[i]}, f属于恶性的概率{probas[i][0]:.4f}, 属于良性的概率{probas[i][1]:.4f}) # # 进阶玩法三查看 20 个参数组合的排名表 # print(\n 进阶玩法三参数排名 ) res pd.DataFrame(grid.cv_results_) top_results res[[param_svm__C, param_svm__gamma, mean_test_score]].sort_values(mean_test_score, ascendingFalse) print(top_results.head(5).to_string(indexFalse))输出结果5次交叉验证的分数[0.967 1. 0.956 0.989 0.989]平均分0.9802标准差0.0162 进阶玩法二输出概率软判决 样本 1: 真实标签0, 预测标签0, 属于恶性的概率1.0000, 属于良性的概率0.0000样本 2: 真实标签1, 预测标签1, 属于恶性的概率0.0000, 属于良性的概率1.0000样本 3: 真实标签0, 预测标签0, 属于恶性的概率0.9897, 属于良性的概率0.0103样本 4: 真实标签1, 预测标签1, 属于恶性的概率0.4793, 属于良性的概率0.5207样本 5: 真实标签0, 预测标签0, 属于恶性的概率1.0000, 属于良性的概率0.0000 进阶玩法三参数排名 param_svm__C param_svm__gamma mean_test_score10.0 0.010 0.980220100.0 0.001 0.97802210.0 0.001 0.9736261.0 0.010 0.969231100.0 0.010 0.969231第一部分代码到底在干嘛我们的“AI医生培养计划”换教材之前咱们用 Iris 鸢尾花4个特征闭着眼睛都能分对。现在换成load_breast_cancer乳腺癌数据30个特征包含肿瘤大小、纹理等。这个难度高得多更接近真实世界。分考场代码里的train_test_split把 569 个病人的数据抽 80% 给 AI 当“复习资料”训练集留 20% 藏起来当“期末考试”测试集。建流水线 (Pipeline)StandardScaler负责把数据拉平标准化SVC是 AI 医生的大脑。重点代码里加了probabilityTrue。以前的 AI 医生只会喊“是恶性”或者“是良性”加了这句AI 医生会告诉你“我有 90% 的把握是恶性”。海选教练 (GridSearchCV)咱们给了 AI 医生 16 种不同的“学习态度”C 和 gamma 的 4x4 组合让它在复习资料里自己模拟考交叉验证挑出考得最好的那个态度。第二部分结果一为什么有 5 个分数AI的模拟考成绩5次交叉验证的分数[0.967 1. 0.956 0.989 0.989]平均分0.9802标准差0.0162我们在代码里写了cross_val_score这相当于把复习资料平分成了 5 份。第 1 次用第 1 份当卷子剩下 4 份复习。考了 0.96796.7分。第 2 次用第 2 份当卷子剩下 4 份复习。考了 1.0满分说明那份卷子里的病人特征太典型了。以此类推考了 5 次。为什么看标准差标准差 0.0162 非常小。意思是不管拿哪份卷子考AI 医生都能稳定在 95% 以上。在医疗里稳定比偶尔考满分重要一万倍第三部分结果二概率那堆数字怎么看AI医生在犹豫什么样本 1真实标签0(恶性)预测标签0(恶性)属于恶性的概率1.0000翻译AI 医生看了一眼样本 1拍着胸脯说“这绝对是恶性肿瘤我有 100% 的把握”结果确实对了。重点看样本 4真实标签1(良性)预测标签1(良性)恶性概率0.4793良性概率0.5207翻译这是整段结果里最有价值的一个数据AI 医生看了一眼样本 4挠了挠头说“这个肿瘤长得……有点怪。我觉得它有 52% 的概率是良性但它也有 48% 的概率是恶性。我最终判它良性但我心里非常没底。”这就是probabilityTrue的威力如果只看预测标签医生以为 AI 很有把握。但看概率医生会发现“哎哟48% vs 52%这太接近了这属于极度危险的临界样本”于是医生会立刻把这个病人拉回来重新做个切片化验。这就是概率输出在工业界尤其是风控、医疗最大的作用帮你抓出那些“模型不确定”的疑难杂症。第四部分结果三参数排名为什么大变样现在的冠军C10.0, gamma0.01以前 Iris 数据集的冠军C1.0, gamma0.1你肯定想问为什么换了个数据集冠军参数就变了维度变高了Iris 只有 4 个特征乳腺癌有 30 个特征。特征一多数据在空间里的距离就拉大了。gamma 必须变小0.1 - 0.01gamma 控制每个样本的影响力范围。特征越多影响力越容易被稀释。所以 gamma 变小相当于把 AI 医生的“探照灯”打得更宽一点让它能统观全局。C 必须变大1 - 10C 越大说明模型对错误越不能容忍。面对复杂的乳腺癌数据稍微严厉一点C10反而能让边界刻画得更精准。SVM 优缺点总结优点数学理论极其完美凸二次规划有全局最优解。小样本、高维数据表现极佳。支持向量决定了模型模型体积小泛化能力强。缺点大样本计算极其缓慢O(n2)O(n2)。对缺失数据和特征缩放标准化极度敏感。多分类处理起来较麻烦OvO计算量大。需要精心调参C、gamma、核函数。代码避坑指南只要是 SVM不管你用什么核数据必须标准化StandardScaler如果特征里有 0.01 和 10000距离计算直接崩溃。标准化必须写在Pipeline里面或者只用训练集的均值和方差去fit绝对不能用全部数据去fit然后做交叉验证这叫数据泄露。不要一上来就调大样本的核 SVM先试试LinearSVC跑个 baseline。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →