Python机器学习笔记:SVM(4)——sklearn实现
完整代码及其数据,请移步小编的GitHub
传送门:请点击我
如果点击有误:https://github.com/LeBron-Jian/MachineLearningNote
上一节我学习了SVM的推导过程,下面学习如何实现SVM,具体的参考链接都在第一篇文章中,SVM四篇笔记链接为:
Python机器学习笔记:SVM(1)——SVM概述
Python机器学习笔记:SVM(2)——SVM核函数
Python机器学习笔记:SVM(3)——证明SVM
Python机器学习笔记:SVM(4)——sklearn实现
对SVM的概念理清楚后,下面我们对其使用sklearn进行实现。
1,Sklearn支持向量机库概述
我们知道SVM相对感知器而言,它可以解决线性不可分的问题,那么它是如何解决的呢?其思想很简单就是对原始数据的维度变换,一般是扩维变换,使得原样本空间中的样本点线性不可分,但是在变维之后的空间中样本点是线性可分的,然后再变换后的高维空间中进行分类。
上面将SVM再赘述了一下,下面学习sklearn中的SVM方法,sklearn中SVM的算法库分为两类,一类是分类的算法库,主要包含LinearSVC,NuSVC和SVC三个类,另一类是回归算法库,包含SVR,NuSVR和LinearSVR三个类,相关模块都包裹在sklearn.svm模块中。
对于SVC,NuSVC和LinearSVC 三个分类的库,SVC和NuSVC差不多,区别仅仅在于对损失的度量方式不同,而LinearSVC从名字就可以看出,他是线性分类,也就是不支持各种低维到高维的核函数,仅仅支持线性核函数,对线性不可分的数据不能使用。
同样的对于SVR,NuSVR和LinearSVR 三个回归的类,SVR和NuSVR差不多,区别也仅仅在于对损失的度量方式不同。LinearSVR是线性回归,只能使用线性核函数。
我们使用这些类的时候,如果有经验知道数据是线性可以拟合的,那么使用LinearSVC去分类或者LinearSVR去回归,他们不需要我们去慢慢的调参选择各种核函数以及对应的参数,速度也快。如果我们对数据分布没有什么经验,一般使用SVC去分类或者SVR去回归,这就需要我们选择核函数以及对核函数调参了。
2,回顾SVM分类算法和回归算法
我们这里仍然先对SVM算法进行回顾,首先对于SVM分类算法,其原始形式如下:
其中 n 为样本个数,我们的样本为(x1, y1),(x2,y2),....(xn, yn),w,b是我们的分离超平面的 wT*xi + b = 0的系数,ξi 为第 i 个样本的松弛系数,C 为惩罚系数,xi (也有时候写为Φ(xi) 为低维到高维的映射函数)为样本数。
通过拉格朗日以及对偶化后的形式为:
其中和原始形式不同的 α 为拉格朗日系数向量,<xi, xj> 为我们要使用的核函数。
对于SVM回归算法,(我自己没有总结,借用刘建平老师的博客),其原始形式如下:
其中 m 为样本个数,我们的样本为(x1, y1),(x2, y2),....,(xm, ym),w,b是我们回归超平面 wT*xi + b = 0 的系数,ξv, ξ^ 为第 i 个样本的松弛系数, C为惩罚系数,ε 为损失边界,到超平面距离小于 ε 的训练集的点没有损失,Φ(xi) 为低维到高维的映射函数
通过拉格朗日函数以及对偶后的形式为:
其中和原始形式不同的 αv, α^ 为拉格朗日系数向量,K(xi, xj) 为我们要使用的核函数。
3,SVM核函数概述
我在第二篇SVM中学习了核函数,有好几种,最常用的就是线性核函数,多项式核函数,高斯核函数和Sigmoid核函数,在scikit-learn中,内置的核函数也刚好有这四种。
3.1,线性核函数(Linear Kernel)
线性核函数表达式为:
就是普通的内积,LinearSVC和LinearSVR只能使用它。
3.2,多项式核函数(Polynomial Kernel)
多项式核函数是线性不可分SVM常用的核函数之一,表达式为:
参数都需要自己调参定义,比较麻烦。
3.3,高斯核函数(Gaussian Kernel)
高斯核函数,在SVM中也称为 径向基核函数(Radial Basisi Function,RBF),它是libsvm默认的核函数,当然也是sklearn默认的核函数,表达式为:
其中 r 大于0,需要自己调参定义,不过一般情况,我们都使用高斯核函数。
3.4,Sigmoid核函数(Sigmoid Kernel)
Sigmoid核函数也是线性不可分SVM常用的核函数之一,表示为:
其中 beta, t 都需要自己调参定义。
一般情况下,对于非线性数据使用默认的高斯核函数会有比较好的效果,如果你不是SVM调参高手的话,建议使用高斯核来做数据分析。
4,SVM分类算法库参数小结
下面我们将具体介绍这三种分类方法都有那些参数值以及不同参数值的含义。
4.1, LinearSVC
其函数原型如下:
class sklearn.svm.LinearSVC(self, penalty='l2', loss='squared_hinge', dual=True, tol=1e-4, C=1.0, multi_class='ovr', fit_intercept=True, intercept_scaling=1, class_weight=None, verbose=0, random_state=None, max_iter=1000)
参数说明
- penalty :正则化参数,L1 和L2两种参数可选,仅LinearSVC有。默认是L2 正则化,如果我们需要产生稀疏的话,可以选择L1正则化,这和线性回归里面的Lasso回归类似
- loss:损失函数,有“hinge” 和“squared_hinge” 两种可选,前者又称为L1损失,后者称为L2损失,默认是“squared_hinge”,其中hinge是SVM的标准损失,squared_hinge是hinge的平方。
- dual:是否转化为对偶问题求解,默认是True。这是一个布尔变量,控制是否使用对偶形式来优化算法。
- tol:残差收敛条件,默认是0.0001,与LR中的一致。
- C:惩罚系数,用来控制损失函数的惩罚系数,类似于LR中的正则化系数。默认为1,一般需要通过交叉验证来选择一个合适的C,一般来说,噪点比较多的时候,C需要小一些。
- multi_class:负责多分类问题中分类策略制定,有‘ovr’和‘crammer_singer’ 两种参数值可选,默认值是’ovr’,'ovr'的分类原则是将待分类中的某一类当作正类,其他全部归为负类,通过这样求取得到每个类别作为正类时的正确率,取正确率最高的那个类别为正类;‘crammer_singer’ 是直接针对目标函数设置多个参数值,最后进行优化,得到不同类别的参数值大小。
- fit_intercept:是否计算截距,与LR模型中的意思一致。
- class_weight:与其他模型中参数含义一样,也是用来处理不平衡样本数据的,可以直接以字典的形式指定不同类别的权重,也可以使用balanced参数值。如果使用“balanced”,则算法会自己计算权重,样本量少的类别所对应的样本权重会高,当然,如果你的样本类别分布没有明显的偏倚,则可以不管这个系数,选择默认的None
- verbose:是否冗余,默认为False
- random_state:随机种子的大小
- max_iter:最大迭代次数,默认为1000.
惩罚系数:
错误项的惩罚系数。C越大,即对分错样本的惩罚程度越大,因此在训练样本中准确率越高,但是泛化能力降低,也就是对测试数据的分类准确率降低。相反,减少C的话,容许训练样本中有一些误分类错误样本,泛化能力强。对于训练样本带有噪音的情况,一般采用后者,把训练样本集中错误分类的样本作为噪音。
4.2,NuSVC
其函数原型如下:
class sklearn.svm.NuSVC(self, nu=0.5, kernel='rbf', degree=3, gamma='auto_deprecated', coef0=0.0, shrinking=True, probability=False, tol=1e-3, cache_size=200, class_weight=None, verbose=False, max_iter=-1, decision_function_shape='ovr', random_state=None)
参数说明:
- nu:训练误差部分的上限和支持向量部分的下限,取值在(0,1)之间,默认是0.5,它和惩罚系数C类似,都可以控制惩罚的力度。
- kernel:核函数,核函数是用来将非线性问题转化为线性问题的一种方法,默认是“rbf”核函数
常用的核函数有以下几种:
- degree:当核函数是多项式核函数(“poly”)的时候,用来控制函数的最高次数。(多项式核函数是将低维的输入空间映射到高维的特征空间),这个参数只对多项式核函数有用,是指多项式核函数的阶数 n。如果给的核函数参数是其他核函数,则会自动忽略该参数。
- gamma:核函数系数,默认是“auto”,即特征维度的倒数。核函数系数,只对rbf poly sigmoid 有效。
- coef0:核函数常数值( y = kx + b 的b值),只有“poly”和“sigmoid” 函数有,默认值是0.
- max_iter:最大迭代次数,默认值是 -1 ,即没有限制。
- probability:是否使用概率估计,默认是False。
- decision_function_shape:与“multi_class”参数含义类似,可以选择“ovo” 或者“ovr”(0.18版本默认是“ovo”,0.19版本为“ovr”) OvR(one vs rest)的思想很简单,无论你是多少元分类,我们都可以看做二元分类,具体的做法是,对于第K类的分类决策,我们把所有第K类的样本作为正例,除第K类样本以外的所有样本作为负类,然后在上面做二元分类,得到第K类的分类模型。 OvO(one vs one)则是每次在所有的T类样本里面选择两类样本出来,不妨记为T1类和T2类,把所有的输出为T1 和 T2的样本放在一起,把T1作为正例,T2 作为负例,进行二元分类,得到模型参数,我们一共需要T(T-1)/2 次分类。从上面描述可以看出,OvR相对简单,但是分类效果略差(这里是指大多数样本分布情况,某些样本分布下OvR可能更好),而OvO分类相对精确,但是分类速度没有OvR快,一般建议使用OvO以达到较好的分类效果
- chache_size:缓冲大小,用来限制计算量大小,默认是200M,如果机器内存大,推荐使用500MB甚至1000MB
4.3,SVC
其函数原型如下:
class sklearn.svm.SVC(self, C=1.0, kernel='rbf', degree=3, gamma='auto_deprecated', coef0=0.0, shrinking=True, probability=False, tol=1e-3, cache_size=200, class_weight=None, verbose=False, max_iter=-1, decision_function_shape='ovr', random_state=None)
参数说明:
- C:惩罚系数(前面有详细学习)
SVC和NuSVC方法基本一致,唯一区别就是损失函数的度量方式不同(NuSVC中的nu参数和SVC中的C参数)即SVC使用惩罚系数C来控制惩罚力度,而NuSVC使用nu来控制惩罚力度。
5,SVM回归算法库参数小结
下面我们将具体介绍这三种分类方法都有那些参数值以及不同参数值的含义。
5.1, LinearSVR
其函数原型如下:
class sklearn.svm.LinearSVR(self, epsilon=0.0, tol=1e-4, C=1.0, loss='epsilon_insensitive', fit_intercept=True, intercept_scaling=1., dual=True, verbose=0, random_state=None, max_iter=1000)
参数说明
- epsilon:距离误差epsilon,即回归模型中的 epsilon,训练集中的样本需要满足:
- loss:损失函数,有“hinge” 和“squared_hinge” 两种可选,前者又称为L1损失,后者称为L2损失,默认是“squared_hinge”,其中hinge是SVM的标准损失,squared_hinge是hinge的平方。
- dual:是否转化为对偶问题求解,默认是True。这是一个布尔变量,控制是否使用对偶形式来优化算法。
- tol:残差收敛条件,默认是0.0001,与LR中的一致。
- C:惩罚系数,用来控制损失函数的惩罚系数,类似于LR中的正则化系数。默认为1,一般需要通过交叉验证来选择一个合适的C,一般来说,噪点比较多的时候,C需要小一些。
- fit_intercept:是否计算截距,与LR模型中的意思一致。
- verbose:是否冗余,默认为False
- random_state:随机种子的大小
- max_iter:最大迭代次数,默认为1000.
5.2,NuSVR
其函数原型如下:
class sklearn.svm.NuSVR(self, nu=0.5, C=1.0, kernel='rbf', degree=3, gamma='auto_deprecated', coef0=0.0, shrinking=True, tol=1e-3, cache_size=200, verbose=False, max_iter=-1)
参数说明:
- nu:训练误差部分的上限和支持向量部分的下限,取值在(0,1)之间,默认是0.5,它和惩罚系数C类似,都可以控制惩罚的力度。
- kernel:核函数,核函数是用来将非线性问题转化为线性问题的一种方法,默认是“rbf”核函数
常用的核函数有以下几种:
- degree:当核函数是多项式核函数(“poly”)的时候,用来控制函数的最高次数。(多项式核函数是将低维的输入空间映射到高维的特征空间),这个参数只对多项式核函数有用,是指多项式核函数的阶数 n。如果给的核函数参数是其他核函数,则会自动忽略该参数。
- gamma:核函数系数,默认是“auto”,即特征维度的倒数。核函数系数,只对rbf poly sigmoid 有效。
- coef0:核函数常数值( y = kx + b 的b值),只有“poly”和“sigmoid” 函数有,默认值是0.
- chache_size:缓冲大小,用来限制计算量大小,默认是200M,如果机器内存大,推荐使用500MB甚至1000MB
5.3,SVR
其函数原型如下:
class sklearn.svm.SVC(self, kernel='rbf', degree=3, gamma='auto_deprecated', coef0=0.0, tol=1e-3, C=1.0, epsilon=0.1, shrinking=True, cache_size=200, verbose=False, max_iter=-1)
参数说明:
SVR和NuSVR方法基本一致,唯一区别就是损失函数的度量方式不同(NuSVR中的nu参数和SVR中的C参数)即SVR使用惩罚系数C来控制惩罚力度,而NuSVR使用nu来控制惩罚力度。
6,SVM的方法与对象
6.1 方法
三种分类的方法基本一致,所以一起来说:
- decision_function(x):获取数据集X到分离超平面的距离
- fit(x , y):在数据集(X,y)上使用SVM模型
- get_params([deep]):获取模型的参数
- predict(X):预测数值型X的标签
- score(X,y):返回给定测试集合对应标签的平均准确率
6.2 对象
- support_:以数组的形式返回支持向量的索引
- support_vectors_:返回支持向量
- n_support_:每个类别支持向量的个数
- dual_coef:支持向量系数
- coef_:每个特征系数(重要性),只有核函数是LinearSVC的是可用,叫权重参数,即w
- intercept_:截距值(常数值),称为偏置参数,即b
加粗的三个属性是我们常用的,后面会举例说明 support_vectors_。
7,SVM类型算法的模型选择
7.1 PPT总结
这里使用(http://staff.ustc.edu.cn/~ketang/PPT/PRLec5.pdf)的PPT进行整理。
7.2 SVM算法库其他调参要点
下面再对其他调参要点做一个小结:
- 1,一般推荐在做训练之前对数据进行归一化,当然测试集的数据也要做归一化
- 2,在特征数非常多的情况下,或者样本数远小于特征数的时候,使用线性核,效果就很好了,并且只需要选择惩罚系数C即可
- 3,在选择核函数的时候,如果线性拟合效果不好,一般推荐使用默认的高斯核(rbf),这时候我们主要对惩罚系数C和核函数参数 gamma 进行调参,经过多轮的交叉验证选择合适的惩罚系数C和核函数参数gamma。
- 4,理论上高斯核不会比线性核差,但是这个理论就建立在要花费更多的时间上调参上,所以实际上能用线性核解决的问题我们尽量使用线性核函数
在SVM中,其中最重要的就是核函数的选取和参数选择了,当然这个需要大量的经验来支撑,这里几个例子只是自己网上找的SVM的小例子。
8,SVM调参实例1
下面学习支持向量机的使用方法以及一些参数的调整,支持向量机的原理就是将低维不可分问题转换为高维可分问题。这里不再赘述。
8.1 线性可分支持向量机
首先做一个简单的线性可分的例子,这里直接使用sklearn.datasets.make_blobs 生成数据。生成数据代码如下:
# 生成数据集 from sklearn.datasets.samples_generator import make_blobs from matplotlib import pyplot as plt # n_samples=50 表示取50个点,centers=2表示将数据分为两类 X, y = make_blobs(n_samples=50, centers=2, random_state=0, cluster_std=0.6) # 画图形 plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn') plt.show()
我们画图展示如下:
我们尝试绘制分离两组数据的直线,从而创建分类模型,对于这里所示的数据,这是我们可以手动完成的任务。但是立马可以看出有很多分界线可以完美的区分两个类。
下面画出决策边界。
# 生成数据集 from sklearn.datasets.samples_generator import make_blobs from matplotlib import pyplot as plt import numpy as np # n_samples=50 表示取50个点,centers=2表示将数据分为两类 X, y = make_blobs(n_samples=50, centers=2, random_state=0, cluster_std=0.6) # 画图形 plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn') # 线性等分详细 xfit = np.linspace(-1, 3.5) 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.show()
图如下:
(注意:这三条直线是我随便画的,其实你可以使用Logistic回归,线性回归等分类,画出线,我这里是为了方便)
这里是三条不同的分割直线,并且这些分割直线能够完全区分这些样例。但是根据支持向量机的思想,哪一条直线是最优的分割线呢?支持向量机并不是简单的绘制一条直线,而是画出边距为一定宽度的直线,直到最近的点。
下面我们对直线进行加粗,代码如下:
# 生成数据集 from sklearn.datasets.samples_generator import make_blobs from matplotlib import pyplot as plt import numpy as np # n_samples=50 表示取50个点,centers=2表示将数据分为两类 X, y = make_blobs(n_samples=50, centers=2, random_state=0, cluster_std=0.6) # 画图形 plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn') # 线性等分详细 xfit = np.linspace(-1, 3.5) plt.plot([0.6], [2.1], 'x', color='red', markeredgewidth=2, markersize=10) 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) # alpha为透明度 plt.show()
如图所示:
在支持向量机中,边距最大化的直线是我们将选择的最优模型。支持向量机是这种最大边距估计器的一个例子。
接下来,我们训练一个基本的SVM,我们使用sklearn的支持向量机,对这些数据训练SVM模型。目前我们将使用一个线性核并将C参数设置为一个默认的数值。如下:
from sklearn.svm import SVC # Support Vector Classifier model = SVC(kernel='linear') # 线性核函数 model.fit(X, y)
我们顺便看看SVC的所有参数情况:
SVC(C=1.0, cache_size=200, class_weight=None, coef0=0.0, decision_function_shape='ovr', degree=3, gamma='auto_deprecated', 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() #get子图 xlim = ax.get_xlim() ylim = ax.get_ylim() # create grid to evaluate model 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) # plot decision boundary and margins ax.contour(X, Y, P, colors='k', levels=[-1, 0, 1], alpha=0.5, linestyles=['--', '-', '--']) # 生成等高线 -- # plot support vectors 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)
下面绘制决策边界:
def train_SVM(): # n_samples=50 表示取50个点,centers=2表示将数据分为两类 X, y = make_blobs(n_samples=50, centers=2, random_state=0, cluster_std=0.6) # 线性核函数 model = SVC(kernel='linear') model.fit(X, y) plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn') plot_SVC_decision_function(model) plt.show() return X, y
结果如图所示:
这是最大化两组点之间的间距的分界线,那中间这条线就是我们最终的决策边界了。请注意:一些训练点碰到了边缘,如图所示,在两个边界上包含两个红点和一个黄点,所以这三个点又称为支持向量,是 alpha 值不为零的,这些点是这种拟合的关键要素,被称为支持向量。在sklearn中,这些点存储在分类器的 support_vectors_ 属性中。
我们通过下面代码可以得出支持向量的结果。
print(model.support_vectors_) ''' [[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.6) X, y = X[:N], y[:N] model = SVC(kernel='linear') model.fit(X, y) ax = ax or plt.gca() ax.scatter(X[:, 0], X[:, 1], c=y, cmap='autumn') ax.set_xlim(-1, 4) ax.set_ylim(-1, 6) plot_SVC_decision_function(model, ax) if __name__ == '__main__': # train_SVM() 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个点获得的模型,可以发现无论使用60,还是使用120个数据点,决策边界都没有发生变换,所有只要支持向量没变,其他的数据怎么加都无所谓。
这个分类器成功的关键在于:为了拟合,只有支持向量的位置是最重要的;任何远离边距的点都不会影响拟合的结果,边界之外的点无论有多少都不会对其造成影响,也就是说不管有多少点是非支持向量,对最终的决策边界都不会产生任何影响。
8.2 线性不可分支持向量机
下面引入核函数,来看看核函数的威力,首先我们导入一个线性不可分的数据集。
def train_svm_plus(): # 二维圆形数据 factor 内外圆比例(0, 1) X, y = make_circles(100, factor=0.1, noise=0.1) clf = SVC(kernel='linear') clf.fit(X, y) plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn') plot_SVC_decision_function(clf, plot_support=False)
数据集如图所示:
很明显,用线性分类器无论怎么画线也不能分好,那咋办呢?下面试试高斯核变换吧。在进行核变换之前,先看看数据在高维空间下的映射:
def plot_3D(X, y, elev=30, azim=30): # 我们加入了新的维度 r r = np.exp(-(X ** 2).sum(1)) 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('z') if __name__ == '__main__': X, y = train_svm_plus() plot_3D(elev=30, azim=30, X=X, y=y)
画出三维图形,如图所示:
见证核变换威力的时候到了,引入径向基函数(也叫高斯核函数),进行核变换:
def train_svm_plus(): # 二维圆形数据 factor 内外圆比例(0, 1) X, y = make_circles(100, factor=0.1, noise=0.1) # 加入径向基函数 clf = SVC(kernel='rbf') clf.fit(X, y) plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn') plot_SVC_decision_function(clf, plot_support=False) return X, y
得到的SVM模型为:
SVC(C=1000000.0, cache_size=200, class_weight=None, coef0=0.0, decision_function_shape='ovr', degree=3, gamma='auto_deprecated', kernel='rbf', max_iter=-1, probability=False, random_state=None, shrinking=True, tol=0.001, verbose=False)
再次进行分类任务,代码如下:
def train_svm_plus(): # 二维圆形数据 factor 内外圆比例(0, 1) X, y = make_circles(100, factor=0.1, noise=0.1) # 加入径向基函数 clf = SVC(kernel='rbf') clf.fit(X, y) plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn') plot_SVC_decision_function(clf, plot_support=False) plt.scatter(clf.support_vectors_[:, 0], clf.support_vectors_[:, 1], s=300, lw=1, facecolors='none') return X, y
分类结果如图所示:
可以清楚的看到效果很好,我们将线性不可分的两对数据分割开来。使用这种核支持向量机,我们学习一个合适的非线性决策边界。这种核变换策略在机器学习中经常被使用。
8.3 线性近似可分支持向量机——软间隔问题
SVM模型有两个非常重要的参数C与gamma,其中C是惩罚系数,即对误差的宽容忍,C越高,说明越不能容忍出现误差,容易过拟合。C越小,容易欠拟合。C过大或过小,泛化能力变差。
gamma 是选择 RBF 函数作为kernel后,该函数自带的一个参数。隐含的决定了数据映射到新的特征空间后的分布,gamma越大,支持向量越小,gamma值越小,支持向量越多。
下面我们分别调剂一下C和gamma来看一下对结果的影响。
首先我们调节C,先做一个有噪音的数据分布
# n_samples=50 表示取50个点,centers=2表示将数据分为两类 X, y = make_blobs(n_samples=100, centers=2, random_state=0, cluster_std=0.8) plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn')
结果如图所示:
上面的分布看起来要划分似乎有点困难,所以我们可以进行软件各调整看看。
# n_samples=50 表示取50个点,centers=2表示将数据分为两类 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) for axi, C in zip(ax, [10.0, 0.1]): model = SVC(kernel='linear', C=C) model.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值比较大,要求比较严格,不能分错东西,隔离带中没有进入任何一个点,但是隔离带的距离比较小,泛化能力比较差。右边这幅图C值比较小,要求相对来说比较松一点,隔离带较大,但是隔离带中进入了很多的黄点和红点。那么C大一些好还是小一些好呢?这需要考虑实际问题,可以进行K折交叉验证来得到最合适的C值。
下面再看看另一个参数gamma值,这个参数值只是在高斯核函数里面才有,这个参数控制着模型的复杂程度,这个值越大,模型越复杂,值越小,模型就越精简。
代码如下:
# n_samples=50 表示取50个点,centers=2表示将数据分为两类 X, y = make_blobs(n_samples=100, centers=2, random_state=0, cluster_std=0.8) fig, ax = plt.subplots(1, 3, figsize=(16, 6)) fig.subplots_adjust(left=0.0625, right=0.95, wspace=0.1) for axi, gamma in zip(ax, [10.0, 1.0, 0.1]): model = SVC(kernel='rbf', gamma=gamma) model.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)
结果如下:
可以看出,当这个参数较大时,可以看出模型分类效果很好,但是泛化能力不太好。当这个参数较小时,可以看出模型里面有些分类是错误的,但是这个泛化能力更好,一般也应有的更多。
通过这个简单的例子,我们对支持向量机在SVM中的基本使用,以及软间隔参数的调整,还有核函数变换和gamma值等一些参数的比较。
完整代码请参考我的GitHub(地址:https://github.com/LeBron-Jian/MachineLearningNote)。
9,SVM调参实例2
下面我们用一个实例学习SVM RBF分类调参(此例子是刘建平老师的博客内容,链接在文后)。
首先,我们生成一些随机数据,为了让数据难一点,我们加入了一些噪音,代码如下:
# _*_coding:utf-8_*_ import numpy as np import matplotlib.pyplot as plt from sklearn.svm import SVC from sklearn.datasets import make_moons, make_circles from sklearn.preprocessing import StandardScaler from matplotlib.colors import ListedColormap X, y = make_circles(noise=0.2, factor=0.5, random_state=1) # 对数据进行标准化 X = StandardScaler().fit_transform(X) # 下面看看数据长什么样子 cm = plt.cm.RdBu cm_birght = ListedColormap(['#FF0000', '#0000FF']) ax = plt.subplot() ax.set_title('Input data') # plot the training points ax.scatter(X[:, 0], X[:, 1], c=y, cmap=cm_birght) ax.set_xticks([]) ax.set_yticks([]) plt.tight_layout() plt.show()
上面代码对数据做了标准化,注意做标准化和不做标准化的差异(不一定所有的数据标准化后的效果更好,但是绝大多数确实更好)。比如下图:
我们看,当不做数据标准化,我们x1的取值范围由0~90不等,当做了数据标准化之后,其取值范围就在-2~2之间了。说明标准化的作用还是很明显的,不多赘述,下面继续。
生成的数据如下(可能下一次运行,就变了哈):
知道数据长什么样了,下面我们要对这个数据集进行SVM RBF分类了,分类时我们采用了网格搜索,在C=(0.1, 1, 10)和 gamma=(1, 0.1, 0.01)形成的9种情况中选择最好的超参数,我们用了4折交叉验证。这里只是一个例子,实际运用中,可能需要更多的参数组合来进行调参。
代码及其结果如下:
# 网格搜索寻找最佳参数 grid = GridSearchCV(SVC(), param_grid={'C': [0.1, 1, 10], 'gamma': [1, 0.1, 0.01]}, cv=4) grid.fit(X, y) print("The best parameters are %s with a score of %0.2f" % (grid.best_params_, grid.best_score_)) # The best parameters are {'C': 10, 'gamma': 0.1} with a score of 0.91
就是说,我们通过网格搜索,在我们给定的9组超参数组合中,C=10, gamma=0.1 分数最高,这就是我们最终的参数候选。
下面我们看看SVM分类后的可视化,这里我们把上面九种组合各个训练后,通过对网格里的点预测来标色,观察分类的效果图,代码如下:
# SVM 分类后进行可视化 x_min, x_max = X[:, 0].min(), X[:, 0].max() + 1 y_min, y_max = X[:, 1].min(), X[:, 1].max() + 1 xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.02), np.arange(y_min, y_max, 0.02)) for i, C in enumerate((0.1, 1, 10)): for j, gamma in enumerate((1, 0.1, 0.01)): # plt.subplot() clf = SVC(C=C, gamma=gamma) clf.fit(X, y) Z = clf.predict(np.c_[xx.ravel(), yy.ravel()]) # put the result into a color plot Z = Z.reshape(xx.shape) plt.contourf(xx, yy, Z, cmap=plt.cm.coolwarm) # Plot also the training points plt.scatter(X[:, 0], X[:, 1], c=y, cmap=plt.cm.coolwarm) plt.xlim(xx.min(), xx.max()) plt.ylim(yy.min(), yy.max()) plt.xticks(()) plt.yticks(()) plt.xlabel(" gamma=" + str(gamma) + " C=" + str(C)) plt.show()
结果如下:
从我测试的结果来看,刘老师的代码还是有一点点问题,显示不出九个,所以这里我打算重新学习一个例子。
完整代码请参考我的GitHub(地址:https://github.com/LeBron-Jian/MachineLearningNote)。
10,SVM调参实例3(非线性支持向量机)
非线性的话,我们一方面可以利用核函数构造出非线性,一方面我们可以自己构造非线性。下面首先学习自己构造非线性。
10.1 自己构造非线性数据
我们构造非线性数据的代码如下:
# _*_coding:utf-8_*_ import numpy as np import matplotlib.pyplot as plt from sklearn.svm import SVC from sklearn.datasets import make_moons, make_circles from sklearn.preprocessing import StandardScaler X1D = np.linspace(-4, 4, 9).reshape(-1, 1) # np.c_是按行连接两个矩阵,就是把两矩阵左右相加,要求行数相等。 X2D = np.c_[X1D, X1D ** 2] y = np.array([0, 0, 1, 1, 1, 1, 1, 0, 0]) plt.figure(figsize=(11, 4)) plt.subplot(121) plt.grid(True, which='both') plt.axhline(y=0, color='k') plt.plot(X1D[:, 0][y == 0], np.zeros(4), 'bs') plt.plot(X1D[:, 0][y == 1], np.zeros(5), 'g*') plt.gca().get_yaxis().set_ticks([]) plt.xlabel(r'$x_1$', fontsize=20) plt.axis([-4.5, 4.5, -0.2, 0.2]) plt.subplot(122) plt.grid(True, which='both') plt.axhline(y=0, color='k') plt.axvline(x=0, color='k') plt.plot(X2D[:, 0][y == 0], X2D[:, 1][y == 0], 'bs') plt.plot(X2D[:, 0][y == 1], X2D[:, 1][y == 1], 'g*') plt.xlabel(r'$x_1$', fontsize=20) plt.ylabel(r'$x_2$', fontsize=20, rotation=0) plt.gca().get_yaxis().set_ticks([0, 4, 8, 12, 16]) plt.plot([-4.5, 4.5], [6.5, 6.5], 'r--', linewidth=3) plt.axis([-4.5, 4.5, -1, 17]) plt.subplots_adjust(right=1) plt.show()
图如下:
从这个图可以看到,我们利用对数据的变换,可以对数据的维度增加起来,变成非线性。
假设我们不使用核函数的思想,先对数据做变换,看能不能达到一个比较好的结果,首先我们做一个测试的数据,代码如下:
# _*_coding:utf-8_*_ import numpy as np import matplotlib.pyplot as plt from sklearn.datasets import make_moons X, y = make_moons(n_samples=100, noise=0.15, random_state=42) def plot_dataset(X, y, axes): plt.plot(X[:, 0][y == 0], X[:, 1][y == 0], 'bs') plt.plot(X[:, 0][y == 1], X[:, 1][y == 1], 'g*') plt.axis(axes) plt.grid(True, which='both') plt.xlabel(r'$x_1$', fontsize=20) plt.ylabel(r'$x_2$', fontsize=20, rotation=0) plot_dataset(X, y, [-1.5, 2.5, -1, 1.5]) plt.show()
生成的图如下:
下面代码将两类数据分出来了:
Polynomial_svm_clf = Pipeline((('poly_features', PolynomialFeatures(degree=3)), ('scaler', StandardScaler()), ('svm_clf', LinearSVC(C=10)) )) Polynomial_svm_clf.fit(X, y) def plot_predictions(clf, axes): x0s = np.linspace(axes[0], axes[1], 100) x1s = np.linspace(axes[2], axes[3], 100) x0, x1 = np.meshgrid(x0s, x1s) X = np.c_[x0.ravel(), x1.ravel()] y_pred = clf.predict(X).reshape(x0.shape) # 下面填充一个等高线, alpha表示透明度 plt.contourf(x0, x1, y_pred, cmap=plt.cm.brg, alpha=0.2) plot_predictions(Polynomial_svm_clf, [-1.5, 2.5, -1, 1.5]) plot_dataset(X, y, [-1.5, 2.5, -1, 1.5]) plt.show()
结果如下:
从结果来看,我们使用线性支持向量机将两类数据区分开是没有问题的。而最重要的是我们如何使用核函数呢?下面继续学习
10.2 如何对非线性数据进行核函数的变换
我们首先看svm的官方文档:
核函数默认是 rbf,也就是径向基核函数。下面分别演示核函数。
我们依旧拿上面的数据,首先取核函数为 多项式核 看看效果(这里对比的是多项式核的degree,也就是多项式核的维度):
# _*_coding:utf-8_*_ import numpy as np import matplotlib.pyplot as plt from sklearn.datasets import make_moons from sklearn.pipeline import Pipeline from sklearn.preprocessing import PolynomialFeatures from sklearn.preprocessing import StandardScaler from sklearn.svm import LinearSVC, SVC X, y = make_moons(n_samples=100, noise=0.15, random_state=42) def plot_dataset(X, y, axes): plt.plot(X[:, 0][y == 0], X[:, 1][y == 0], 'bs') plt.plot(X[:, 0][y == 1], X[:, 1][y == 1], 'g*') plt.axis(axes) plt.grid(True, which='both') plt.xlabel(r'$x_1$', fontsize=20) plt.ylabel(r'$x_2$', fontsize=20, rotation=0) # 展示图像 def plot_predictions(clf, axes): x0s = np.linspace(axes[0], axes[1], 100) x1s = np.linspace(axes[2], axes[3], 100) x0, x1 = np.meshgrid(x0s, x1s) X = np.c_[x0.ravel(), x1.ravel()] y_pred = clf.predict(X).reshape(x0.shape) # 下面填充一个等高线, alpha表示透明度 plt.contourf(x0, x1, y_pred, cmap=plt.cm.brg, alpha=0.2) Poly_kernel_svm_clf = Pipeline((('scaler', StandardScaler()), ('svm_clf', SVC(kernel='poly', degree=3, coef0=1, C=5)) )) Poly_kernel_svm_clf.fit(X, y) # 下面做一个对比试验,看看degree的值的变换 Poly_kernel_svm_clf_plus = Pipeline((('scaler', StandardScaler()), ('svm_clf', SVC(kernel='poly', degree=10, coef0=1, C=5)) )) Poly_kernel_svm_clf_plus.fit(X, y) plt.subplot(121) plot_predictions(Poly_kernel_svm_clf, [-1.5, 2.5, -1, 1.5]) plot_dataset(X, y, [-1.5, 2.5, -1, 1.5]) plt.title(r'$d=3, r=1, C=5$', fontsize=18) plt.subplot(122) plot_predictions(Poly_kernel_svm_clf, [-1.5, 2.5, -1, 1.5]) plot_dataset(X, y, [-1.5, 2.5, -1, 1.5]) plt.title(r'$d=10, r=100, C=5$', fontsize=18) plt.show()
结果如下:
我们是把数据映射到高维空间,然后再拿回来看效果,实际上并没有去高维空间做运算。。这就是我们想要展示的多项式核函数,下面学习高斯核函数。
高斯核函数:利用相似度来变换特征
我们选择一份一维数据,并在 x1=-2, x1=1 处为其添加两个高斯函数,接下来让我门将相似度函数定义为 gamma=0.3 的径向基核函数(RBF):
例如: x1 = -1:它位于距第一个地标距离为1的地方,距离第二个地标距离为2。因此其新特征为 x2 = exp(-0.3*1^2)=0.74 ,并且 x3 = exp(-0.3 * 2^2)=0.3。
图如下:
这里说一下,就是假设 X2和 X3为两个高斯函数,我们看 x这个点距离两个地标的距离。离高斯分布的中心越近,就越发生什么。。经过计算出来距离两个地标的距离,我们就可以依此类推,来计算所有一维坐标相对应的二维坐标。(二维坐标就是距离两个高斯函数的距离)。
我们这里用相似度特征来替换原本的特征。
下面我们做一个实验,我们只看 gamma的变换,高斯函数的开口变化:
X1D = np.linspace(-4, 4, 9).reshape(-1, 1) X2D = np.c_[X1D, X1D ** 2] X, y = make_moons(n_samples=100, noise=0.15, random_state=42) def gaussian_rbf(x, landmark, gamma): return np.exp(-gamma * np.linalg.norm(x - landmark, axis=1) ** 2) gamma = 0.3 # 下面进行训练,得到一个支持向量机的模型(这里我们没有训练,直接画出来了) # 因为测试的数据是我们自己写的,为了方便,我们自己画出来,当然你也可以自己做 xls = np.linspace(-4.5, 4.5, 200).reshape(-1, 1) x2s = gaussian_rbf(xls, -2, gamma) x3s = gaussian_rbf(xls, 1, gamma) XK = np.c_[gaussian_rbf(X1D, -2, gamma), gaussian_rbf(X2D, 1, gamma)] yk = np.array([0, 0, 1, 1, 1, 1, 1, 0, 0]) plt.figure(figsize=(11, 4)) # plt.subplot(121) plt.grid(True, which='both') plt.axhline(y=0, color='k') plt.scatter(x=[-2, 1], y=[0, 0], s=150, alpha=0.5, c='red') plt.plot(X1D[:, 0][yk == 0], np.zeros(4), 'bs') plt.plot(X1D[:, 0][yk == 1], np.zeros(5), 'g*') plt.plot(xls, x2s, 'g--') plt.plot(xls, x3s, 'b:') plt.gca().get_yaxis().set_ticks([0, 0.25, 0.5, 0.75, 1]) plt.xlabel(r'$x_1$', fontsize=20) plt.ylabel(r'Similarity', fontsize=14) plt.annotate(r'$\mathbf{x}$', xy=(X1D[3, 0], 0), xytest=(-0.5, 0.20), ha='center', arrowprops=dict(facecolor='black', shrink=0.1), fontsize=18, ) plt.text(-2, 0.9, "$x_2$", ha='center', fontsize=20) plt.text(1, 0.9, "$x_3$", ha='center', fontsize=20) plt.axis([-4.5, 4.5, -0.1, 1.1])
结果如下(下面我们分别调试gamma,分为0.3 0.8):
理论情况下,我们会得到怎么维特征呢?可以对每一个实例(样本数据点)创建一个地标,此时会将mn 的训练集转换成 mm 的训练集(m表示样本个数,n表示特征维度个数)。
SVM中利用核函数的计算技巧,大大降低了计算复杂度:
- 增加gamma 使高斯曲线变窄,因此每个实例的影响范围都较小,决策边界最终变得不规则,在个别实例周围摆动
- 减少gamma 使高斯曲线变宽,因此实例具有更大的影响范围,并且决策边界更加平滑
下面做一个对比试验(gamma值(0.1 0.5), C值(0.001, 1000)):
rbf_kernel_svm_clf = Pipeline((('scaler', StandardScaler()), ('svm_clf', SVC(kernel='rbf', gamma=5, C=0.001)) )) gamma1, gamma2 = 0.1, 5 C1, C2 = 0.001, 1000 hyperparams = (gamma1, C1), (gamma1, C2), (gamma2, C1), (gamma2, C2) svm_clfs = [] for gamma, C in hyperparams: rbf_kernel_svm_clf.fit(X, y) svm_clfs.append(rbf_kernel_svm_clf) plt.figure(figsize=(11, 7)) for i, svm_clfs in enumerate(svm_clfs): plt.subplot(221 + i) plot_predictions(svm_clfs, [-1.5, 2.5, -1, 1.5]) plot_dataset(X, y, [-1.5, 2.5, -1, 1.5]) gamma, C = hyperparams[i] plt.title(r'$\gamma={}, C={}$'.format(gamma, C), fontsize=16) plt.show()
结果如下:
我们看第一幅图,边界比较平稳,没有过拟合的风险,我们看当 gamma比较大的时候,过拟合的风险却比较大了。所以说最终我们看的还是高斯函数的开口大还是小,大一点,也就是gamma小,过拟合风险小,反之同理。
完整代码请参考我的GitHub(地址:https://github.com/LeBron-Jian/MachineLearningNote)。
参考文献:https://blog.csdn.net/BIT_666/article/details/79979580
https://www.cnblogs.com/tonglin0325/p/6107114.html
https://cloud.tencent.com/developer/article/1146077
https://www.cnblogs.com/xiaoyh/p/11604168.html
https://www.cnblogs.com/pinard/p/6126077.html
https://www.cnblogs.com/pinard/p/6117515.html