SVM算法编程练习
什么是支持向量机
对于线性可分两类数据,支持向量机就是条直线(对于高维数据点就是一个超平面), 两类数据点中的的分割线有无数条,SVM就是这无数条中最完美的一条,怎么样才算最完美呢?就是这条线距离两类数据点越远,则当有新的数据点的时候我们使用这条线将其分类的结果也就越可信。例如下图中的三条直线都可以将A中的数据分类,那条可以有最优的分类能力呢?
- 我们需要线找到数据点中距离分割超平面距离最近的点(找最小)
- 然后尽量使得距离超平面最近的点的距离的绝对值尽量的大(求最大)
这里的数据点到超平面的距离就是间隔(margin), 当间隔越大,我们这条线(分类器)也就越健壮。
那些距离分割平面最近的点就是支持向量(Support Vectors).
如何找到超平面
机器学习算法实践-支持向量机(SVM)算法原理
函数间隔
在超平面wx+b=0确定的情况下,|wx+b|能够表示点x到距离超平面的远近,而通过观察wx+b的符号与类标记y的符号是否一致可判断分类是否正确,所以,可以用(y(w*x+b))的正负性来判定或表示分类的正确性。于此,我们便引出了函数间隔(functional margin)的概念。定义函数间隔为:
但是这个函数间隔有个问题,就是我成倍的增加w和b的值,则函数值也会跟着成倍增加,但这个超平面没有改变。所以有函数间隔还不够,需要一个几何间隔。
几何间隔
我们把w做一个约束条件,假定对于一个点 x ,令其垂直投影到超平面上的对应点为 x0 ,w 是垂直于超平面的一个向量,为样本x到超平面的距离,如下图所示:
根据平面几何知识,有
分割超平面
分割超平面
将一维直线和二维平面拓展到任意维, 分割超平面可以表示成:
其中 w和b就是SVM的参数,不同的w和b 确定不同的分割面.
这里我们可以回忆一下Logistic回归,在Logistic回归模型中,我们也同样将 放 入到sigmoid函数中来做极大似然估计获取最有的参数w,其中logistic模型中w 中的 便对应着现在我们这里的截距b 。
但是与Logistic回归中我们将 代入到sigmoid函数中获取的值为1或者0也就是数据标签为0或1。而在SVM中我们对于二分类,不再使用0/1而是使用+1/-1作为数据类型标签。之所以使用+1/-1是为了能方便的使用间隔公式来表示数据点到分割面的间隔。
数据点与超平面的间隔
根据数据点到分割超平面的距离公式:
可见,在距离公式中有两个绝对值,其中分母上是常量,分子上则是与数据点相关的,如果数据点在分割平面上方, ; 数据点在分割平面下方, 。
这样我们在表示任意数据点到分割面的距离就会很麻烦,但是我们通过将数据标签设为+1/-1来讲距离统一用一个公式表示:
这样,当数据点在分割面上方时, , 且数据点距离分割面越远 越大;
当数据点在分割面下方时, , 仍然大于0, 且数据点距离分割面越远越大。
Soft Margin SVM
机器学习入门——支持向量机图文介绍
在Hard Margin SVM 中本质就是求解一个这样有条件最小化问题。但是如果某类的样本点分布比较奇怪,如
此时Hard Margin SVM要做的是找出一条直线分开这两种类别:
虽然它正确的分开了蓝色样本和红色样本,但是离红色样本太近的。对于大多数的蓝色样本点都集中在左下角位置,只有一个outlier在右边的位置。很可能这个outlier是错误的点。哪怕它是正确的点,它也不能代表一般的点。
很可能绿色这个决策边界才是比较好的,虽然它错误的分类了outlier的点,但是可能在真实情况下表现的更好。也就是泛化能力更强。
SVM算法要有一定的容错能力,在一些情况下可以把某些点进行错误的分类,尽量保证泛化能力较高。
这种SVM就叫Soft Margin SVM,我们上面说Hard Margin SVM的条件是
它的意思是对于margin区域里,必须是任何数据点都没有的。
现在我们宽松个条件,这个宽松量记为
把条件宽松为:
也就是说,宽松条件后,样本点可以出现在虚线与平行于决策边界的直线之间。上面是与之间。
同时,这个
为了防止设的过大,我们需要对最小化的式子做一个限制,增加一个正则项:
这是Soft Margin SVM完整的式子:
这里增加了一个超参数C来控制正则项的重要程度。C越小容错空间越大。
通过代码来理解一下:
首先训练数据点
import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.preprocessing import StandardScaler
from sklearn.svm import LinearSVC
iris = datasets.load_iris()
X = iris.data
y = iris.target
X = X [y<2,:2] #只取y<2的类别,也就是0 1 并且只取前两个特征
y = y[y<2] # 只取y<2的类别
# 分别画出类别0和1的点
plt.scatter(X[y==0,0],X[y==0,1],color='green')
plt.scatter(X[y==1,0],X[y==1,1],color='pink')
plt.show()
# 标准化
standardScaler = StandardScaler()
standardScaler.fit(X) #计算训练数据的均值和方差
X_standard = standardScaler.transform(X) #再用scaler中的均值和方差来转换X,使X标准化
svc = LinearSVC(C=1e9) #线性SVM分类器
svc.fit(X_standard,y) # 训练svm
结果如图:这个是原始的数据点的分布
然后绘制决策边界
def plot_decision_boundary(model, axis):
x0, x1 = np.meshgrid(
np.linspace(axis[0], axis[1], int((axis[1]-axis[0])*100)).reshape(-1,1),
np.linspace(axis[2], axis[3], int((axis[3]-axis[2])*100)).reshape(-1,1)
)
X_new = np.c_[x0.ravel(), x1.ravel()]
y_predict = model.predict(X_new)
zz = y_predict.reshape(x0.shape)
from matplotlib.colors import ListedColormap
custom_cmap = ListedColormap(['#EF9A9A','#FFF59D','#90CAF9'])
plt.contourf(x0, x1, zz, linewidth=5, cmap=custom_cmap)
# 绘制决策边界
plot_decision_boundary(svc,axis=[-3,3,-3,3]) # x,y轴都在-3到3之间
# 绘制原始数据
plt.scatter(X_standard[y==0,0],X_standard[y==0,1],color='red')
plt.scatter(X_standard[y==1,0],X_standard[y==1,1],color='blue')
plt.show()
结果如下:
然后更换C的值为0.001,观察不同
svc2 = LinearSVC(C=0.001)
svc2.fit(X_standard,y)
plot_decision_boundary(svc2,axis=[-3,3,-3,3]) # x,y轴都在-3到3之间
# 绘制原始数据
plt.scatter(X_standard[y==0,0],X_standard[y==0,1],color='green')
plt.scatter(X_standard[y==1,0],X_standard[y==1,1],color='pink')
plt.show()
可以看到结果和上面的明显不同,有一个绿点是分类错误的。
C越小容错空间越大。
使用核函数
RBF核函数
它的式子如下:
这里的也是超参数。
我们看下高斯函数的式子,发现高斯核函数和高斯函数很像。
高斯核函数也叫RBF核(Radia Basis Function Kernel)。
高斯核函数的本质是将每个样本点映射到一个无穷多维度的特征空间中。
核函数都是依靠升维使得原本线性不可分的数据变得线性可分。
在多项式特征中,假如原本的数据是x xx的话,我们把原来的数据都变成,这样就变得线性可分了。
原本的数据,是一维的,它是线性不可分的,我们无法只画一条直线将它们分开。
然后对他升维
我们用代码实现一下:
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(-4,5,1)#生成测试数据
y = np.array((x >= -2 ) & (x <= 2),dtype='int')
plt.scatter(x[y==0],[0]*len(x[y==0]))# x取y=0的点, y取0,有多少个x,就有多少个y
plt.scatter(x[y==1],[0]*len(x[y==1]))
plt.show()
生成一维数据
然后用高斯核函数来进行升维:
# 高斯核函数
def gaussian(x,l):
gamma = 1.0
return np.exp(-gamma * (x -l)**2)
l1,l2 = -1,1
X_new = np.empty((len(x),2)) #len(x) ,2
for i,data in enumerate(x):
X_new[i,0] = gaussian(data,l1)
X_new[i,1] = gaussian(data,l2)
plt.scatter(X_new[y==0,0],X_new[y==0,1])
plt.scatter(X_new[y==1,0],X_new[y==1,1])
plt.show()
可以看到进行升维后,明显可以把这些数据从不可分变为可分。
超参数,这个超参数和前面的C值作用差不多。
在高斯函数中, 越大,分布就越胖。
而核函数中的 类似于 。
所以,越大,高斯分布越窄;越小,高斯分布越宽。
用代码来演示下的取值对结果有什么影响。
代码如下:
import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
X,y = datasets.make_moons(noise=0.15,random_state=777)
plt.scatter(X[y==0,0],X[y==0,1])
plt.scatter(X[y==1,0],X[y==1,1])
plt.show()
同样的生成数据
定义一个RBF核的SVM
代码如下:
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC
from sklearn.pipeline import Pipeline
def RBFKernelSVC(gamma=1.0):
return Pipeline([
('std_scaler',StandardScaler()),
('svc',SVC(kernel='rbf',gamma=gamma))
])
svc = RBFKernelSVC(100)
svc.fit(X,y)
plot_decision_boundary(svc,axis=[-1.5,2.5,-1.0,1.5])
plt.scatter(X[y==0,0],X[y==0,1])
plt.scatter(X[y==1,0],X[y==1,1])
plt.show()
这里我设置的为100的结果
在这个位置改变的值
取值越大,就是高斯分布的钟形图越窄,这里相当于每个样本点都形成了钟形图。很明显这样是过拟合的。γ取值越小,在取到0.1的时候,此时它是欠拟合的。
因此,我们可以看出值相当于在调整模型的复杂度。