机器学习(十)——支持向量机

支持向量机(Support Vector Machine, SVM)是一类按监督学习(supervised learning)方式对数据进行广义线性分类,其决策边界是对学习样本求解的最大边距超平面(maximum-margin hyperplane)。SVM可以通过核函数进行非线性分类,是常见的核学习(kernel learning)方法之一。

什么是支持向量机

支持向量机(support vector machines)是寻找一个超平面来对样本进行分割,分割的原则是间隔最大化,最终转化为一个凸二次规划问题来求解。它既能解决线性可分又能解决线性不可能,既能解决分类问题又能完成回归问题。

当训练样本线性可分时使用硬间隔最大化(Hard Margin SVM)或者近似线性可分时使用软件最大化(Soft Margin SVM)。当训练样本线性不可分时使用核函数和软间隔最大化。
机器学习(十)——支持向量机_第1张图片
在实际问题中往往都存在着决策边界不唯一的情况,这就是不适定问题。给定训练样本集 D={(x1,y1),(x2,y2),…,(xm,ym)},yi∈{-1,1} 分类算法的基本思想就是基于训练集在样本空间中找到一个划分超平面,但是能将训练样本分开的划分超平面可能有很多,所以,应该努力地去找哪一个?
机器学习(十)——支持向量机_第2张图片
而svm找到的这条直线希望距离最近的红色的点和蓝色的点,距离决策边界尽可能的远,这样就能保证模型的泛化能力。svm尝试寻找一个最优的决策边界,距离两个类别最近的样本最远,图中3个点到决策边界距离相同。这三个点就叫做支持向量(support vector)。而平行于决策边界的两条直线之间的距离就是 margin,svm就是要最大化 margin ,这样就把这个问题转化称为最优化问题。


支持向量机背后的最优化问题

机器学习(十)——支持向量机_第3张图片
机器学习(十)——支持向量机_第4张图片
中间那根直线就是决策边界 wTx+b=0 ,而上下两根直线意味着距离一定大于 d,从而
机器学习(十)——支持向量机_第5张图片
此时,假设两类样本分别为1和-1。然后将上述式子的左右两侧同时除以 d ,得到
机器学习(十)——支持向量机_第6张图片
其中 ||w||d 都是一个数,此时消去分母,得到
机器学习(十)——支持向量机_第7张图片
机器学习(十)——支持向量机_第8张图片


Soft Margin和SVM正则化

如果在实际应用过程中有一个蓝色的点出现在红色的点附近,即两类相对较为接近,但整体跟蓝色的点差异明显,可以看做是一个特殊点或者错误的奇点,此时就会误导,导致最终的hard margin分类边界是直线1,此时模型的泛化能力就值得怀疑。正常来说应该像直线2一样忽略那个极度特殊的蓝点,保证大多数的数据到直线的距离最远,可能这才是最好的分类边界,也就是泛化能力更高。这也能间接地说明如果模型的准确率过高可能会导致模型的过拟合。
机器学习(十)——支持向量机_第9张图片
还有一种更一般的例子,那就是如果有一个蓝色的点混进了红色的点当中,这就导致数据集根本就是线性不可分的情况,根本找不出一条直线能够将这两类分开,在这种情况下Hard margin就不再是泛化能力强不强的问题,而是根本找不出一条直线将其分开。
机器学习(十)——支持向量机_第10张图片
因此不管才以上两种情况的哪个角度出发,都应该考虑给予svm模型部分容错能力。由此引出Soft Margin SVM
机器学习(十)——支持向量机_第11张图片
机器学习(十)——支持向量机_第12张图片


sklearn中的SVM

在实际使用SVM的时候和KNN一样需要对数据进行标准化处理,因为这两者都涉及距离。因为当数据尺度相差过大的话,比如下图横轴0-1,纵轴0-10000。所以先进行标准化是必要的。
机器学习(十)——支持向量机_第13张图片
第一步,准备一个简单二分类数据集:

import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets

iris = datasets.load_iris()

x = iris.data
y = iris.target
# 只做一个简单的二分类
x = x[y<2, :2]
y = y[y<2]

plt.scatter(x[y==0, 0], x[y==0, 1])
plt.scatter(x[y==1, 0], x[y==1, 1])
plt.show()

机器学习(十)——支持向量机_第14张图片
第二步,实现svm,先使用一个比较大的C。

# 标准化数据
from sklearn.preprocessing import StandardScaler
from sklearn.svm import LinearSVC

standardscaler = StandardScaler()
standardscaler.fit(x)
x_standard = standardscaler.transform(x)

svc = LinearSVC(C=1e9)
svc.fit(x_standard, y)

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])
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()

机器学习(十)——支持向量机_第15张图片
第三步:使用一个比较小的C,对比C取不同值的效果。

svc2 = LinearSVC(C=0.01)
svc2.fit(x_standard, y)

plot_decision_boundary(svc2, axis=[-3, 3, -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()

机器学习(十)——支持向量机_第16张图片
对比两幅图可以发现,当C较小时,误将一个红色的点分到蓝色当中,这也再次验证了当C越小,就意味着有更大的容错空间。
第四步:查看线性SVM的截距和系数

svc.coef_
# array([[ 4.03236227, -2.50699771]])
svc.intercept_
# array([0.92736176])

第五步:画出除了决策边界以外的两条跟支持向量相关的直线

def plot_svc_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)
    
    w = model.coef_[0]
    b = model.intercept_[0]
    # w0*x0 + w1*x1 + b = 0
    # x1 = -w0/w1 * x0 - b/w1
    plot_x = np.linspace(axis[0], axis[1], 200)
    up_y = -w[0]/w[1] * plot_x - b/w[1] + 1/w[1]
    down_y = -w[0]/w[1] * plot_x - b/w[1] - 1/w[1]
    
    up_index = (up_y >= axis[2]) & (up_y <= axis[3])
    down_index = (down_y >= axis[2]) & (down_y <= axis[3])
    
    plt.plot(plot_x[up_index], up_y[up_index], color='black')
    plt.plot(plot_x[down_index], down_y[down_index], color='black')

plot_svc_decision_boundary(svc, axis=[-3, 3, -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()

机器学习(十)——支持向量机_第17张图片

plot_svc_decision_boundary(svc2, axis=[-3, 3, -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()

机器学习(十)——支持向量机_第18张图片

svc3 = LinearSVC(C=0.1)
svc3.fit(x_standard, y)
# LinearSVC(C=0.1, class_weight=None, dual=True, fit_intercept=True,
#      intercept_scaling=1, loss='squared_hinge', max_iter=1000,
#      multi_class='ovr', penalty='l2', random_state=None, tol=0.0001,
#      verbose=0)
# 从上述结果可以看出sklearn中对于svm封装的linearSVC默认对于多分类使用ovr,L2正则。
plot_svc_decision_boundary(svc3, axis=[-3, 3, -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()

机器学习(十)——支持向量机_第19张图片


SVM中使用多项式特征

前面一直都在讲的是线性的svm,对于svm来说也可以解决非线性问题,类比线性回归到非线性回归的思想,首先使用多项式特征。

首先生成数据集:

import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets

x, y = datasets.make_moons()
x.shape
# (100, 2)
y.shape
# (100,)
plt.scatter(x[y==0, 0], x[y==0, 1])
plt.scatter(x[y==1, 0], x[y==1, 1])
plt.show()

机器学习(十)——支持向量机_第20张图片
接下来给数据添加一些随机噪声:

x, y = datasets.make_moons(noise=0.15, random_state=666)
plt.scatter(x[y==0, 0], x[y==0, 1])
plt.scatter(x[y==1, 0], x[y==1, 1])
plt.show()

机器学习(十)——支持向量机_第21张图片

from sklearn.preprocessing import PolynomialFeatures, StandardScaler
from sklearn.svm import LinearSVC
from sklearn.pipeline import Pipeline

def PolynomiaSVC(degree, C=1.0):
    return Pipeline([
        ('poly', PolynomialFeatures(degree=degree)),
        ('std_scale', StandardScaler()),
        ('linear_svc', LinearSVC(C=C))
    ])

poly_svc = PolynomiaSVC(degree=3)
poly_svc.fit(x, y)
# Pipeline(memory=None,
#      steps=[('poly', PolynomialFeatures(degree=3, include_bias=True, interaction_only=False)),
# ('std_scale', StandardScaler(copy=True, with_mean=True, with_std=True)), 
# ('linear_svc', LinearSVC(C=1.0, class_weight=None, dual=True, fit_intercept=True,
#      intercept_scaling=1, loss='squared_hinge', max_iter=1000,
#      multi_class='ovr', penalty='l2', random_state=None, tol=0.0001,
#     verbose=0))])

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(poly_svc, axis=[-1.5, 2.5, -1.0, 1.5])
plt.scatter(x1[y1==0, 0], x1[y1==0, 1])
plt.scatter(x1[y1==1, 0], x1[y1==1, 1])
plt.show()

机器学习(十)——支持向量机_第22张图片
除了使用这种增加多项式特征之后再给入线性svc中之外,还有一种方法可以实现类似的功能。

from sklearn.svm import SVC

# 这种方法训练的过程并不完全是先将数据进行标准化,再使用linearSVC这么一个过程
# SVC中默认的C=0
def PolynomiaKernelSVC(degree, C=1.0):
    return Pipeline([
        ('std_scale', StandardScaler()),
        ('kernel_svc', SVC(kernel='poly', degree=degree, C=C))
    ])

poly_kernel_svc = PolynomiaKernelSVC(degree=3)
poly_kernel_svc.fit(x, y)
# Pipeline(memory=None,
#     steps=[('std_scale', StandardScaler(copy=True, with_mean=True, with_std=True)),
#  ('kernel_svc', SVC(C=1.0, cache_size=200, class_weight=None, coef0=0.0,
#   decision_function_shape='ovr', degree=3, gamma='auto_deprecated',
#   kernel='poly', max_iter=-1, probability=False, random_state=None,
#   shrinking=True, tol=0.001, verbose=False))])

plot_decision_boundary(poly_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()

机器学习(十)——支持向量机_第23张图片
这种方法就是svm中kernel函数。接下来具体说明核函数。


核函数

在现实任务中,原始的样本空间也许并不存在一个能正确划分两类的超平面,对于这样一个问题,可将样本从原始空间映射到一个更高维的特征空间,使得样本在这个特征空间内线性可分。因此核函数的作用就是使得原本线性不可分的数据变得线性可分。下面是一个使用多项式核映射的过程。
机器学习(十)——支持向量机_第24张图片
第一步:向高维空间映射
机器学习(十)——支持向量机_第25张图片
第二步:对偶问题的证明过程
机器学习(十)——支持向量机_第26张图片
第三步:经过对偶问题的求解后即可得到,
机器学习(十)——支持向量机_第27张图片
常用核函数:
机器学习(十)——支持向量机_第28张图片
接下里重点介绍高斯核函数。


高斯核函数

高斯核函数的目的就是将每一个样本点映射到一个无穷维的特征空间。实质上就是把一个m*n维的数据映射成了m*m的数据。由于理论上数据量可以是无穷维,所以说是映射到一个无穷维空间中。
机器学习(十)——支持向量机_第29张图片
接下来通过高斯核函数映射来更加直观地理解整个映射的过程。

import numpy as np
import matplotlib.pyplot as plt

x = np.arange(-4, 5, 1)
# array([-4, -3, -2, -1,  0,  1,  2,  3,  4])
y = np.array((x >= -2) & (x <= 2), dtype='int')
# array([0, 0, 1, 1, 1, 1, 1, 0, 0])
plt.scatter(x[y==0], [0] * len(x[y==0]))
plt.scatter(x[y==1], [0] * len(x[y==1]))
plt.show()

机器学习(十)——支持向量机_第30张图片

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))
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()

机器学习(十)——支持向量机_第31张图片
其实,真正的高斯核函数实现的过程中并不是固定的landmark,而是对于每一个数据点都是landmark。
机器学习(十)——支持向量机_第32张图片
机器学习(十)——支持向量机_第33张图片
接下来,使用sklearn中封装的高斯核函数:

import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets

x, y = datasets.make_moons(noise=0.15, random_state=666)

from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC
from sklearn.pipeline import Pipeline

def RBFKernelSVC(gamma=1.0):
    return Pipeline([
        ('std_scale', StandardScaler()),
        ('svc', SVC(kernel='rbf', gamma=gamma))
    ])

svc = RBFKernelSVC(gamma=1.0)
svc.fit(x, y)

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=[-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()

机器学习(十)——支持向量机_第34张图片

svc_gamma100 = RBFKernelSVC(gamma=100)
svc_gamma100.fit(x, y)

plot_decision_boundary(svc_gamma100, 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()

机器学习(十)——支持向量机_第35张图片

svc_gamma10 = RBFKernelSVC(gamma=10)
svc_gamma10.fit(x, y)

plot_decision_boundary(svc_gamma10, 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()

机器学习(十)——支持向量机_第36张图片

svc_gamma01 = RBFKernelSVC(gamma=0.1)
svc_gamma01.fit(x, y)

plot_decision_boundary(svc_gamma01, 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()

机器学习(十)——支持向量机_第37张图片
gamma相当于是在调节模型的复杂度,gammma越小模型复杂度越低,gamma越高模型复杂度越高。因此需要调节超参数gamma平衡过拟合和欠拟合。


SVM解决回归问题

svm解决回归问题的思路:在margin区域内的点越多越好。
机器学习(十)——支持向量机_第38张图片
在sklearn中实现SVM解决回归问题:

import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets

boston = datasets.load_boston()
x = boston.data
y = boston.target

from sklearn.model_selection import train_test_split

x_train, x_test, y_train, y_test = train_test_split(x, y, random_state=666)

from sklearn.svm import SVR
from sklearn.svm import LinearSVR
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline

def StandardLinearSVR(epsilon=0.1):
    return Pipeline([
        ('std_scale', StandardScaler()),
        # C, kernel, 等超参需要调节
        ('linear_svr', LinearSVR(epsilon=epsilon))
    ])

svr = StandardLinearSVR()
svr.fit(x_train, y_train)
svr.score(x_test, y_test)
# 0.6357154352424855

你可能感兴趣的:(机器学习,支持向量机,机器学习,算法)