关于支持向量机的一些基础知识:支持向量机SVM(Support Vector Machine)笔记 ,这篇文章总结得很好,视频讲解可以查看b站上面的b板推导。
以下内容是使用代码看看效果
%matplotlib inline import numpy as np import matplotlib.pyplot as plt from scipy import stats # use seaborn plotting defaults import seaborn as sns; sns.set()
SVM基本模型:Hard Margin问题
#随机来点数据 # datasets.samples_generator是sklearn工具包中的数据生成函数 # cluster_std代表数据的离散程度,random_state代表随机状态 # n_samples代表指定样本数目,centers代表数据簇的个数 # 以下代码构建了50个数据点,要进行二分类任务 from sklearn.datasets.samples_generator import make_blobs X, y = make_blobs(n_samples=50, centers=2, random_state=0, cluster_std=0.60) plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn')
<matplotlib.collections.PathCollection at 0x20c4d56e588>
进数据进行二分类,用直线将数据分割开,但是如何找到最合适的决策边界?
xfit = np.linspace(-1, 3.5) plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn') plt.plot([0.6], [2.1], 'x', color='red', markeredgewidth=2, markersize=10) for m, b in [(1, 0.65), (0.5, 1.6), (-0.2, 2.9)]: plt.plot(xfit, m * xfit + b, '-k') plt.xlim(-1, 3.5);
根据SVM的原理,需要找出最宽的边界
xfit = np.linspace(-1, 3.5) plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn') for m, b, d in [(1, 0.65, 0.33), (0.5, 1.6, 0.55), (-0.2, 2.9, 0.2)]: yfit = m * xfit + b plt.plot(xfit, yfit, '-k') plt.fill_between(xfit, yfit - d, yfit + d, edgecolor='none', color='#AAAAAA', alpha=0.4) plt.xlim(-1, 3.5);
- 让我们看一下实际拟合该数据的结果:我们将使用Scikit-Learn的支持向量分类器在此数据上训练SVM模型。
- 目前,我们将使用线性核并将“ C”参数设置为非常大的数字
# 分类任务 from sklearn.svm import SVC # "Support vector classifier" # 线性核函数,相当于不对数据进行变换 model = SVC(kernel='linear', C=1E10) model.fit(X, y)
SVC(C=10000000000.0, break_ties=False, cache_size=200, class_weight=None, coef0=0.0, decision_function_shape='ovr', degree=3, gamma='scale', kernel='linear', max_iter=-1, probability=False, random_state=None, shrinking=True, tol=0.001, verbose=False)
为了更好地可视化此处发生的情况,让我们创建一个快速便捷的函数,该函数将为我们绘制SVM决策边界,示例代码如下:
# 绘图函数 def plot_svc_decision_function(model, ax=None, plot_support=True): """Plot the decision function for a 2D SVC""" if ax is None: ax = plt.gca() xlim = ax.get_xlim() ylim = ax.get_ylim() # 用svm自带的decision_function函数唉绘图 x = np.linspace(xlim[0], xlim[1], 30) y = np.linspace(ylim[0], ylim[1], 30) Y, X = np.meshgrid(y, x) xy = np.vstack([X.ravel(), Y.ravel()]).T P = model.decision_function(xy).reshape(X.shape) # 绘制决策边界 ax.contour(X, Y, P, colors='k', levels=[-1, 0, 1], alpha=0.5, linestyles=['--', '-', '--']) # 绘制支持向量 if plot_support: ax.scatter(model.support_vectors_[:, 0], model.support_vectors_[:, 1], s=300, linewidth=1, facecolors='none'); ax.set_xlim(xlim) ax.set_ylim(ylim) # 将数据点和决策边界一起绘制起来 plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn') plot_svc_decision_function(model);
- 这条线就是我们希望得到的决策边界啦
- 观察发现有3个点做了特殊的标记,它们恰好都是边界上的点
- 它们就是我们的support vectors(支持向量)
- 在Scikit-Learn中, 它们存储在这个位置 support_vectors_(一个属性)
model.support_vectors_
array([[0.44359863, 3.11530945], [2.33812285, 3.43116792], [2.06156753, 1.96918596]])
- 观察可以发现,只需要支持向量我们就可以把模型构建出来
- 接下来我们尝试一下,用不同多的数据点,看看效果会不会发生变化
- 分别使用60个和120个数据点
def plot_svm(N=10, ax=None): X, y = make_blobs(n_samples=200, centers=2, random_state=0, cluster_std=0.60) X = X[:N] y = y[:N] model = SVC(kernel='linear', C=1E10) model.fit(X, y) ax = ax or plt.gca() ax.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn') ax.set_xlim(-1, 4) ax.set_ylim(-1, 6) plot_svc_decision_function(model, ax) fig, ax = plt.subplots(1, 2, figsize=(16, 6)) fig.subplots_adjust(left=0.0625, right=0.95, wspace=0.1) for axi, N in zip(ax, [60, 120]): plot_svm(N, axi) axi.set_title('N = {0}'.format(N))
- 左边是60个点的结果,右边的是120个点的结果
- 观察发现,只要支持向量没变,其他的数据怎么加无所谓!
核函数变换
- 首先我们先用线性的核来看一下在下面这样比较难的数据集上还能分了吗?
from sklearn.datasets.samples_generator import make_circles # 绘制另外的一种数据集 X, y = make_circles(100, factor=.1, noise=.1) # 看看线性函数能否解决 clf = SVC(kernel='linear').fit(X, y) plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn') plot_svc_decision_function(clf, plot_support=False);
显然在二维特征空阿金中做得不好,但如果映射到高维空间中,效果如下:
#加入了新的维度r from mpl_toolkits import mplot3d r = np.exp(-(X ** 2).sum(1)) # 可以想象一下在三维中把环形数据集进行上下拉伸 def plot_3D(elev=30, azim=30, X=X, y=y): ax = plt.subplot(projection='3d') ax.scatter3D(X[:, 0], X[:, 1], r, c=y, s=50, cmap='autumn') ax.view_init(elev=elev, azim=azim) ax.set_xlabel('x') ax.set_ylabel('y') ax.set_zlabel('r') plot_3D(elev=45, azim=45, X=X, y=y)
自定义了一个新的维度,两类数据点就很容易分开了,这就是核函数变换的基本思想
from sklearn.datasets.samples_generator import make_circles # 绘制另外的一种数据集 X, y = make_circles(100, factor=.1, noise=.1) # 看看线性函数能否解决 clf = SVC(kernel='linear').fit(X, y) # 显然直线不能区分环形数据 plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn') plot_svc_decision_function(clf, plot_support=False); #加入高斯核函数 clf = SVC(kernel='rbf', C=1E6) clf.fit(X, y) # 对比之下,可以将两者区分开来 plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn') plot_svc_decision_function(clf) plt.scatter(clf.support_vectors_[:, 0], clf.support_vectors_[:, 1],s=300, lw=1, facecolors='none');
两者的对比如上所示,其中核函数参数选择常用的高斯核函数‘rbf’,其他参数保持不变,可以看见解决了线性不可分的问题
kernel{‘linear’, ‘poly’, ‘rbf’, ‘sigmoid’, ‘precomputed’},
default=’rbf’ Specifies the kernel type to be used in the algorithm.
It must be one of ‘linear’, ‘poly’, ‘rbf’, ‘sigmoid’, ‘precomputed’ or
a callable. If none is given, ‘rbf’ will be used. If a callable is
given it is used to pre-compute the kernel matrix from data matrices;
that matrix should be an array of shape (n_samples, n_samples).
使用这种核支持向量机,我们学习一个合适的非线性决策边界。这种核变换策略在机器学习中经常被使用!
SVM参数选择:Soft Margin问题
C参数
- 当C趋近于无穷大时:意味着分类严格不能有错误
- 当C趋近于很小的时:意味着可以有更大的错误容忍
X, y = make_blobs(n_samples=100, centers=2, random_state=0, cluster_std=0.8) fig, ax = plt.subplots(1, 2, figsize=(16, 6)) fig.subplots_adjust(left=0.0625, right=0.95, wspace=0.1) # 选择了两个松弛因子c进行对比试验,分别是10和0.1 for axi, C in zip(ax, [10.0, 0.1]): model = SVC(kernel='linear', C=C).fit(X, y) axi.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn') plot_svc_decision_function(model, axi) axi.scatter(model.support_vectors_[:, 0], model.support_vectors_[:, 1], s=300, lw=1, facecolors='none'); axi.set_title('C = {0:.1f}'.format(C), size=14)
可以看见,
c=10时,对分类结果较为严格,以分类为前提,再去找最宽的决策边界
而c=0.1时,并不要求分类完全正确,选择的是可以容忍某些错误
gamma参数
在sklearn工具包中,σ对应着gamma参数值,来控制模型的复杂程度。gamma值越大,模型复杂度越高;gamma值越小,模型复杂度越低。
X, y = make_blobs(n_samples=100, centers=2, random_state=0, cluster_std=1.1) fig, ax = plt.subplots(1, 2, figsize=(16, 6)) fig.subplots_adjust(left=0.0625, right=0.95, wspace=0.1) for axi, gamma in zip(ax, [10, 0.1]): model = SVC(kernel='rbf', gamma=gamma).fit(X, y) axi.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn') plot_svc_decision_function(model, axi) axi.scatter(model.support_vectors_[:, 0], model.support_vectors_[:, 1], s=300, lw=1, facecolors='none'); axi.set_title('gamma = {0:.1f}'.format(gamma), size=14)
可见,
左图的模型复杂,gamma值大,出现了过拟合现象;相对之下,右图的模型比较简单,效果更好
SVM人脸识别实例
具体可以参考官方提供:API文档
- 数据读入
# 读取数据集 from sklearn.datasets import fetch_lfw_people # 限制每个人的样本数至少为60 faces = fetch_lfw_people(min_faces_per_person=60) # 看一下数据的规模 print(faces.target_names) print(faces.images.shape)
['Ariel Sharon' 'Colin Powell' 'Donald Rumsfeld' 'George W Bush' 'Gerhard Schroeder' 'Hugo Chavez' 'Junichiro Koizumi' 'Tony Blair'] (1348, 62, 47)
fig, ax = plt.subplots(3, 5) for i, axi in enumerate(ax.flat): axi.imshow(faces.images[i], cmap='bone') axi.set(xticks=[], yticks=[], xlabel=faces.target_names[faces.target[i]])
- 每个图的大小是 [62×47]
- 在这里我们就把每一个像素点当成了一个特征,但是这样特征太多了,用PCA降维一下吧!
- 数据降维及划分
from sklearn.svm import SVC #from sklearn.decomposition import RandomizedPCA from sklearn.decomposition import PCA from sklearn.pipeline import make_pipeline # 降维到150维。基本模型实例化 pca = PCA(n_components=150, whiten=True, random_state=42) svc = SVC(kernel='rbf', class_weight='balanced') # 然后再使用svm建模 model = make_pipeline(pca, svc) # 需要进行模型评估,所以需要进行对数据集划分 from sklearn.model_selection import train_test_split Xtrain, Xtest, ytrain, ytest = train_test_split(faces.data, faces.target, random_state=40)
- svm模型训练
使用grid search cross-validation来选择我们的参数,网络参数选择,决策树中有提及到参数的选择方法
from sklearn.model_selection import GridSearchCV # 参数空间 param_grid = {'svc__C': [1, 5, 10], 'svc__gamma': [0.0001, 0.0005, 0.001]} grid = GridSearchCV(model, param_grid) %time grid.fit(Xtrain, ytrain) print(grid.best_params_)
Wall time: 22.5 s {'svc__C': 5, 'svc__gamma': 0.001}
- 结果预测
model = grid.best_estimator_ yfit = model.predict(Xtest) # yfit.shape fig, ax = plt.subplots(4, 6) for i, axi in enumerate(ax.flat): axi.imshow(Xtest[i].reshape(62, 47), cmap='bone') axi.set(xticks=[], yticks=[]) axi.set_ylabel(faces.target_names[yfit[i]].split()[-1], color='black' if yfit[i] == ytest[i] else 'red') fig.suptitle('Predicted Names; Incorrect Labels in Red', size=14);
结果中,如果是用红色标注的名称就表示是不正常的预测名字
如果想得到各项具体评估指标可以使用classification_report函数
from sklearn.metrics import classification_report print(classification_report(ytest, yfit, target_names=faces.target_names))
precision recall f1-score support Ariel Sharon 0.50 0.50 0.50 16 Colin Powell 0.69 0.81 0.75 54 Donald Rumsfeld 0.83 0.85 0.84 34 George W Bush 0.94 0.88 0.91 136 Gerhard Schroeder 0.72 0.85 0.78 27 Hugo Chavez 0.81 0.72 0.76 18 Junichiro Koizumi 0.87 0.87 0.87 15 Tony Blair 0.85 0.76 0.80 37 accuracy 0.82 337 macro avg 0.78 0.78 0.78 337 weighted avg 0.83 0.82 0.82 337
- 精度(precision) = 正确预测的个数(TP)/被预测正确的个数(TP+FP)
- 召回率(recall)=正确预测的个数(TP)/预测个数(TP+FN)
- F1 = 2精度召回率/(精度+召回率)
from sklearn.metrics import confusion_matrix mat = confusion_matrix(ytest, yfit) sns.heatmap(mat.T, square=True, annot=True, fmt='d', cbar=False, xticklabels=faces.target_names, yticklabels=faces.target_names) plt.xlabel('true label') plt.ylabel('predicted label');
这样显示出来能帮助我们查看哪些人更容易弄混,对角线上的数值表示将一个人正确预测成他自己的样本个数。以Ariel为例,有8个样本预测成功,而有6个样本是被误认为Colin,1个样本被误认为Donald