1. 线性可分与线性不可分
上图左边为线性可分的,通过一条直线就可以把两类分开;而右边是线性不可分的,如何用一条线都不能把红黑两个分离开。对于线性不可分的我们可以采取升维的方式来解决,比如有如下十个样本。
红色的是一类,绿色的是另一类,它在二维平面上是线性不可分的;我们现在加上一维,如果它是红色的,第三维设为1,否则设为0。这样我们通过升维把线性不可分的变为线性可分的,如下图所示。
从正方向看下来,三维还原成二维,就变为:
支持向量机(Support Vector Machine,以下简称SVM),作为传统机器学习的一个非常重要的分类算法,它是一种通用的前馈网络类型,最早是由Vladimir N.Vapnik 和 Alexey Ya.Chervonenkis在1963年提出,目前的版本(softmargin)是CorinnaCortes 和 Vapnik在1993年提出,1995年发表。深度学习(2012)出现之前,如果不考虑集成学习的算法,不考虑特定的训练数据集,在分类算法中的表现SVM说是排第一估计是没有什么异议的。
SVM本来是一种线性分类和非线性分类都支持的二元分类算法,但经过演变,现在也支持多分类问题,也能应用到了回归问题。
一些线性不可分的问题可能是非线性可分的,即特征空间存在超曲面(hypersurface)将正类和负类分开。使用非线性函数可以将非线性可分问题从原始的特征空间映射至更高维的希尔伯特空间(Hilbert space),从而转化为线性可分问题。
支持向量机通过某非线性变换 φ( x) ,将输入空间映射到高维特征空间。特征空间的维数可能非常高。如果支持向量机的求解只用到内积运算,而在低维输入空间又存在某个函数 K(x, x') ,它恰好等于在高维空间中这个内积,即K( x, x') =<φ( x) ⋅φ( x') > 。那么支持向量机就不用计算复杂的非线性变换,而由这个函数 K(x, x')直接得到非线性变换的内积,使大大简化了计算。这样的函数 K(x, x') 称为核函数。
核函数一般包括以下几个:
我们下面来看代码
# 导入NumPy库
import numpy as np
# 导入画图工具
import matplotlib.pyplot as plt
from sklearn import svm
from sklearn.datasets import make_blobs
def SVM_linear():
#创建50个数据点,分成2类
X , y = make_blobs(n_samples=50,random_state=6,centers=2)
#创建一个线性内核的支持向量
clf = svm.SVC(kernel='linear',C=1000)# C-SVC的惩罚参数C,默认值是1.0
clf.fit(X,y)
# 画出数据点
plt.scatter(X[:,0],X[:,1],c=y,cmap=plt.cm.Paired,s=30)
# 建立图像坐标
ax = plt.gca() #当前的图表和子图可以使用plt.gcf()和plt.gca()获得
xlim = ax.get_xlim() #返回当前Axes视图的x的上下限
ylim = ax.get_ylim() #返回当前Axes视图的y的上下限
# 生成等差数列
xx = np.linspace(xlim[0],xlim[1],30)
yy = np.linspace(ylim[0],ylim[1],30)
YY , XX = np.meshgrid(yy,xx) # meshgrid函数用两个坐标轴上的点在平面上画网格。
xy = np.vstack([XX.ravel(),YY.ravel()]).T# np.hstack():横向拼接,增加特征量;np.vstack():纵向拼接,增加样本个数
Z = clf.decision_function(xy). reshape(XX.shape)
#decision_function:计算样本点到分割超平面的函数距离
#shape是查看数据有多少行多少列
#reshape()是数组array中的方法,作用是将数据重新组织
# 把分类决定边界画出来
ax.contour(XX,YY,Z,colors='k',levels=[-1,0,1],alpha=0.5,linestyles=['--','-','--'])
#绘制等高线
ax.scatter(clf.support_vectors_[:,0],clf.support_vectors_[:,1],s=100,linewidth=1,facecolors='none')
plt.show()
以上展示的是SVC线性核的模型。中间这条“线”叫做最大边界超平面(Maximum Margin Separating Hyperplane)(二维时为线,三维时为面,多维时为超平面)。这条线到和所有支持向量的距离都是最大的。离这个超平面最近的点就是“支持向量”,点到超平面的距离叫做间隔,支持向量机的意思就是使超平面和支持向量之间的间隔尽可能的大。
f(x) = wTx+b
w = (w1,w2,w3,…,wn)为法向量
f(x) = 0:超平面上
> 0 :超平面一边的一个点
< 0 :超平面另一边的一个点
注:
SVM适合于有监督学习的分类与回归算法,如下图。
wTxi+b>=1=>yi=1
wTxi+b<=-1=>yi=-1
下面我们通过代码来看一下:SVC with Linear Kernel、SVC with RBF Kernel、SVC with sigmoid Kernel和SVC with poly Kernel的曲线。
import matplotlib.pyplot as plt
from sklearn import svm
from sklearn.datasets import make_blobs
def SVM_base(mykernel,title):
#创建50个数据点,分成2类
X , y = make_blobs(n_samples=50,random_state=6,centers=2)
#创建一个线性内核的支持向量
if mykernel == 'LinearSVC':
clf = svm.LinearSVC()
else:
clf = svm.SVC(kernel=mykernel,C=1000)# C-SVC的惩罚参数C,默认值是1.0
clf.fit(X,y)
# 画出数据点
plt.scatter(X[:,0],X[:,1],c=y,cmap=plt.cm.Paired,s=30)
# 建立图像坐标
ax = plt.gca() #当前的图表和子图可以使用plt.gcf()和plt.gca()获得
xlim = ax.get_xlim() #返回当前Axes视图的x的上下限
ylim = ax.get_ylim() #返回当前Axes视图的y的上下限
# 生成等差数列
xx = np.linspace(xlim[0],xlim[1],30)
yy = np.linspace(ylim[0],ylim[1],30)
YY , XX = np.meshgrid(yy,xx) # meshgrid函数用两个坐标轴上的点在平面上画网格。
xy = np.vstack([XX.ravel(),YY.ravel()]).T# np.hstack():横向拼接,增加特征量;np.vstack():纵向拼接,增加样本个数
Z = clf.decision_function(xy). reshape(XX.shape)
#decision_function:计算样本点到分割超平面的函数距离
#shape是查看数据有多少行多少列
#reshape()是数组array中的方法,作用是将数据重新组织
# 把分类决定边界画出来
ax.set_title(title)
ax.contour(XX,YY,Z,colors='k',levels=[-1,0,1],alpha=0.5,linestyles=['--','-','--'])#绘制等高线
if mykernel != 'LinearSVC':
ax.scatter(clf.support_vectors_[:,0],clf.support_vectors_[:,1],s=100,linewidth=1,facecolors='none')
def SVM_for_all_model():
models = ['linear','rbf','sigmoid','poly','LinearSVC']
titles = ['SVC with Linear Kernel','SVC with RBF Kernel','SVC with sigmoid Kernel','SVC with poly Kernel','LinearSVC']
figure, axs = plt.subplots(2,3,figsize =(10,3))
figure.suptitle('All Model of SVM')
i = 0
for model in models:
plt.subplot(2,3,i+1)
SVM_base(model,titles[i])
i = i+1
plt.show()
我们再通过程序绘制Linear Kernel、RBF Kernel和poly Kernel 和LinearSVC的对红酒数据画出边界曲线。
from sklearn import datasets
#定义一个函数来画图
def make_meshgrid(x, y, h=.02):
x_min,x_max = x.min()-1,x.max()+1
y_min,y_max = y.min()-1,y.max()+1
xx, yy = np.meshgrid(np.arange(x_min, x_max, h),np.arange(y_min, y_max, h))
return xx,yy
#定义一个绘制等高线的函数
def plot_contour(ax, clf, xx,yy,**params) :
Z = clf.predict(np.c_[xx.ravel(),yy.ravel()])
Z = Z.reshape(xx.shape)
out = ax.contourf(xx,yy,Z,**params)
return out
#不同核函数的SVM对比
def compare_with_different_SVM():
wine = datasets.load_wine()
#选取数据集前两个特征
X = wine.data[:,:2]
y = wine.target
C = 2.5 #SVM参数正则化
models = (svm.SVC(kernel='linear',C=C),
svm.LinearSVC(C=C),
svm.SVC(kernel='rbf',gamma=0.7,C=C),
svm.SVC(kernel='sigmoid',C=C),
svm.SVC(kernel='poly',degree=3,C=C))
models = (clf.fit(X,y) for clf in models)
#设定图标
titles = ('SVC with Linear Kernel',
'LinearSVC(Linear Kernel)',
'SVC with RBF Kernel',
'SVC with sigmoid Kernel',
'SVC with polynomial(degree 3) Kernel'#设置子图形的个数和排列方式
fig, sub = plt.subplots(2,3,figsize =(10,3))
plt.subplots_adjust(wspace=0.4,hspace=0.4)
#使用前面定义的函数画图
X0, X1 = X[:,0],X[:,1]
xx, yy = make_meshgrid(X0, X1)
for clf, title, ax in zip(models, titles,sub.flatten()):
plot_contour(ax,clf,xx,yy,cmap=plt.cm.plasma,alpha=0.8)
ax.scatter(X0,X1,c=y,cmap=plt.cm.plasma,s=20,edgecolors='k')
ax.set_xlim(xx.min(),xx.max())
ax.set_ylim(yy.min(),yy.max())
ax.set_xlabel('Feature 0')
ax.set_ylabel('Feature 1')
ax.set_xticks(())
ax.set_yticks(())
ax.set_title(title)
plt.show()
由此可以看出:
接下来我们看一下gamma参数。
def gamma_for_RBF():
wine = datasets.load_wine()
#选取数据集前两个特征
X = wine.data[:,:2]
y = wine.target
C = 1.0 #SVM参数正则化
models =(svm.SVC(kernel='rbf',gamma=0.1,C=C),
svm.SVC(kernel='rbf',gamma=1,C=C),
svm.SVC(kernel='rbf',gamma=10,C=C))
models = (clf.fit(X,y) for clf in models)
#设置标题
titles = ('gamma = 0.1',
'gamma = 1',
'gamma = 10')
fig, sub = plt.subplots(1,3,figsize =(10,3))
X0, X1 = X[:,0],X[:,1]
xx, yy = make_meshgrid(X0, X1)
#使用前面定义的函数画图
for clf, title, ax in zip(models,titles,sub.flatten()):
plot_contour(ax,clf,xx,yy,cmap=plt.cm.plasma,alpha=0.8)
ax.scatter(X0,X1,c=y,cmap=plt.cm.plasma,s=20,edgecolors='k')
ax.set_xlim(xx.min(),xx.max())
ax.set_ylim(yy.min(),yy.max())
ax.set_xlabel('Feature 0')
ax.set_ylabel('Feature 1')
ax.set_xticks(())
ax.set_yticks(())
ax.set_title(title)
plt.show()
由此可以看出:
SVM的优势和劣势如下:
SVM需要考虑:
我们下面通过波士顿房价数据例子来讨论一下如何调优的过程。
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
def SVM_for_boston():
boston = datasets.load_boston()
X,y = boston.data,boston.target
X_train,X_test,y_train,y_test = train_test_split(X, y, random_state =8)
for kernel in ['linear','rbf','sigmoid','poly']:
svr = svm.SVR(kernel=kernel)
svr.fit(X_train,y_train)
print(kernel,'核函数的模型训练集得分: {:.3f}'.format(svr.score(X_train,y_train)))
print(kernel,'核函数的模型测试集得分: {:.3f}'.format(svr.score(X_test,y_test)))
输出
linear 核函数的模型训练集得分: 0.709
linear 核函数的模型测试集得分: 0.696
rbf 核函数的模型训练集得分: 0.192
rbf 核函数的模型测试集得分: 0.222
sigmoid 核函数的模型训练集得分: 0.059
sigmoid 核函数的模型测试集得分: 0.075
poly 核函数的模型训练集得分: 0.195
poly 核函数的模型测试集得分: 0.207
这个结果是非常糟糕的。通过下面程序来看一下数据分布图。
ax = plt.gca()
#将特征数字中的最大和最小值一散点图形式画出来
plt.plot(X.min(axis=0),'v',label='min')
plt.plot(X.max(axis=0),'^',label='max')
plt.yscale('log') #纵坐标为对数形式
plt.legend(loc='best') #设置图注位置最佳
ax.set_xlabel('feature')
ax.set_ylabel('featuremagnitude')
plt.show()
图标横坐标是第几个样本,对应纵坐标为数据的最大最小值数据,由此可见数据在10-2到103之间,差距更大。下面我们通过StandardScaler()函数来对训练集和测试集数据进行预处理。
#对训练集和测试集数据进行预处理
scaler = StandardScaler()
scaler.fit(X_train)
X_train_scaler =scaler.transform(X_train)
X_test_scaler = scaler.transform(X_test)
plt.plot(X_train_scaler.min(axis=0),'v',label='trainset min')
plt.plot(X_train_scaler.max(axis=0),'^',label='trainset max')
plt.plot(X_test_scaler.min(axis=0),'v',label='testset min')
plt.plot(X_test_scaler.max(axis=0),'^',label='testset max')
plt.legend(loc='best')
ax.set_xlabel('scaledfeature')
ax.set_ylabel('scaledfeature magnitude')
plt.show()
这样数据集中在-4到10之间。我们通过预处理后的数据进行训练模型。
for kernel in ['linear','rbf','sigmoid','poly']:
svr =svm.SVR(kernel=kernel)
svr.fit(X_train_scaler,y_train)
print(kernel,'预处理后训练集得分:{:.3f}'.format(svr.score(X_train_scaler,y_train)))
print(kernel,'预处理后测试集得分:{:.3f}'.format(svr.score(X_test_scaler,y_test)))
输出:
linear 预处理后训练集得分: 0.706
linear 预处理后测试集得分: 0.698
rbf 预处理后训练集得分: 0.665
rbf 预处理后测试集得分: 0.695
sigmoid 预处理后训练集得分: 0.564
sigmoid 预处理后测试集得分: 0.634
poly 预处理后训练集得分: 0.686
poly 预处理后测试集得分: 0.623
由此可见,Linear变化不大,其他都变化很大。下面我们调节下C与gamma参数,首先设置C=100,gamma=0.1。
# 调节参数
for kernel in ['linear','rbf','sigmoid','poly']:
svr = svm.SVR(kernel=kernel,C=100,gamma=0.1)
svr.fit(X_train_scaler,y_train)
print(kernel,'调节参数后训练集得分: {:.3f}'.format(svr.score(X_train_scaler,y_train)))
print(kernel,'调节参数后测试集得分:{:.3f}'.format(svr.score(X_test_scaler,y_test)))
输出
linear 调节参数后训练集得分: 0.706
linear 调节参数后测试集得分: 0.699
rbf 调节参数后训练集得分: 0.966
rbf 调节参数后测试集得分: 0.894
sigmoid 调节参数后训练集得分: -3768.620
sigmoid 调节参数后测试集得分: -5454.605
poly 调节参数后训练集得分: 0.950
poly 调节参数后测试集得分: 0.318
然后我们看一下C=100,gamma=0.005
# 调节参数
for kernel in ['linear','rbf','sigmoid','poly’]:
svr =svm.SVR(kernel=kernel,C=100,gamma=0.005)
svr.fit(X_train_scaler,y_train)
print(kernel,'调节参数(C=100,gamma=0.005)后训练集得分:{:.3f}'.format(svr.score(X_train_scaler,y_train)))
print(kernel,'调节参数(C=100,gamma=0.005)后测试集得分: {:.3f}'.format(svr.score(X_test_scaler,y_test)))
输出
linear 调节参数后训练集得分: 0.706
linear 调节参数后测试集得分: 0.699
rbf 调节参数后训练集得分: 0.841
rbf 调节参数后测试集得分: 0.829
sigmoid 调节参数后训练集得分: 0.698
sigmoid 调节参数后测试集得分: 0.695
poly 调节参数后训练集得分: 0.240
poly 调节参数后测试集得分: 0.232
最后绘制表格,分析一下输出结果。
模型 | 预处理前 | 预处理后 | 调节参数后(gamma=0.1) | 调节参数(gamma=0.005) |
---|---|---|---|---|
默认gamma与C | C=100 | |||
linear | 0.709 | 0.706 | 0.706 | 0.706 |
0.696 | 0.698 | 0.699(不变) | 0.699(不变) | |
rbf | 0.192 | 0.665 | 0.966 | 0.841 |
0.222 | 0.695 | 0.894(提高) | 0.829(略降) | |
sigmoid | 0.059 | 0.564 | -3768.620 | 0.698(提高) |
0.075 | 0.634 | -5454.605(失控) | 0.695 | |
poly | 0.195 | 0.686 | 0.950 | 0.240(降低,但是不存在过拟合) |
0.207 | 0.623 | 0.318(过拟合) | 0.232 |
通过调节可以看出:
由此可见,选择模型类型和调节参数是非常重要的。