机器学习系列笔记十一: 支持向量机SVM
文章目录
Hard margin SVM
在分类问题中,决策边界决定了模型的预测结果,为了提高模型的泛化能力,我们通常期望决策边界不仅能将特征空间中不同类别的数据进行划分,还期望这条边界与与两方甚至多方数据达到一个距离上的平等,从而避免过拟合,提高模型的泛化能力。
从而问题就变为,我们要找到一条决策边界,这条边界离各个数据(特征)都要尽可能地远。
SVM尝试寻找到一个最优的决策边界,距离两个类别的样本最远,而最优边界的寻找是依赖于支持向量的
因此,我们希望能找到最大的margin–>d,在解析几何中,(x,y)点到(Ax+By+C=0)直线的距离为: ∣ A x + B y + C ∣ A 2 + B 2 \frac{|Ax+By+C|}{\sqrt{A^2+B^2}} A2+B2∣Ax+By+C∣
拓展到n维空间"线"的表达式为: θ T x b = 0 \theta^Tx_b=0 θTxb=0 --> w T x + b = 0 w^Tx+b=0 wTx+b=0
而点x到该线的距离为 w T x + n ∣ ∣ w ∣ ∣ , ∣ ∣ w ∣ ∣ = w 1 2 + w 2 2 + . . . + w n 2 \frac{w^Tx+n}{||w||},||w||=\sqrt{w_1^2+w_2^2+...+w_n^2} ∣∣w∣∣wTx+n,∣∣w∣∣=w12+w22+...+wn2
因而对于前面描述的SVM距离d 有如下关系式:
由于其中 ∣ ∣ w ∣ ∣ d ||w||d ∣∣w∣∣d 为常数,所以,可以令 w d T = w T ∣ ∣ w ∣ ∣ d w^T_d=\frac{w^T}{||w||d} wdT=∣∣w∣∣dwT , b d = b ∣ ∣ w ∣ ∣ d b_d = \frac{b}{||w||d} bd=∣∣w∣∣db
从而推导出了与决策边界平行的两条直线表达式:如下
最后,为方便表示,令
w
=
w
d
,
b
=
b
d
w=w_d,b=b_d
w=wd,b=bd
可以得到距离关系式为:
{
w
T
x
(
i
)
+
b
≥
1
∀
y
(
i
)
=
1
w
T
x
(
i
)
+
b
≤
1
∀
y
(
i
)
=
−
1
\left\{ \begin{array}{l} w^Tx^{\left( i \right)}+b\ge 1\ \ \ \forall y^{\left( i \right)}=1\\ w^Tx^{\left( i \right)}+b\le 1\ \ \ \forall y^{\left( i \right)}=-1\\ \end{array} \right.
{wTx(i)+b≥1 ∀y(i)=1wTx(i)+b≤1 ∀y(i)=−1
即 : y ( i ) ( w T x ( i ) + b ) ≥ 1 即:y^{\left( i \right)}\left( w^Tx^{\left( i \right)}+b \right) \ge 1 即:y(i)(wTx(i)+b)≥1
可以得到距离的表达式为:
d
≤
∣
∣
w
T
x
+
b
∣
∣
w
∣
∣
∣
∣
d\le|| \frac{w^Tx+b}{||w||}||
d≤∣∣∣∣w∣∣wTx+b∣∣
从而,我们的目标就转换为了:
min
1
2
∣
∣
w
∣
∣
2
s
t
.
y
(
i
)
(
w
T
x
(
i
)
+
b
)
≥
1
\begin{array}{c} \min \frac{1}{2}||w||^2\\ st.\ \ y^{\left( i \right)}\left( w^Tx^{\left( i \right)}+b \right) \ge 1\\ \text{ }\\ \end{array}
min21∣∣w∣∣2st. y(i)(wTx(i)+b)≥1
这是一个有条件的优化问题。
Soft Margin和SVM的正则化
在很多时候我们希望牺牲比较小的模型精度从而提高模型的泛化能力,比如下图:
因此我们希望引入一个机制能使得模型拥有容错能力,在训练过程中可以容忍小部分的错误来使得模型泛化能力更强,或者说不同数据间的Margin更大。
而这种SVM就叫做Soft Margin SVM。在Hard Margin SVM的限制条件中稍作改动即可转化为Soft Margin,
min
1
2
∣
∣
w
∣
∣
2
+
C
∑
i
=
1
m
ζ
i
s
t
.
y
(
i
)
(
w
T
x
(
i
)
+
b
)
≥
1
−
ζ
i
ζ
i
≥
0
\begin{array}{c} \min \frac{1}{2}||w||^2+C\sum_{i=1}^{m}\zeta_i\\ st.\ \ y^{\left( i \right)}\left( w^Tx^{\left( i \right)}+b \right) \ge 1-\zeta_i\\ \zeta_i \ge 0 \text{ }\\ \end{array}
min21∣∣w∣∣2+C∑i=1mζist. y(i)(wTx(i)+b)≥1−ζiζi≥0
其中的
ζ
i
≥
0
\zeta_i\ge0
ζi≥0 通常被视作L1正则项。同理也存在L2正则化的表达式
min
1
2
∣
∣
w
∣
∣
2
+
C
∑
i
=
1
m
ζ
i
2
s
t
.
y
(
i
)
(
w
T
x
(
i
)
+
b
)
≥
1
−
ζ
i
ζ
i
≥
0
\begin{array}{c} \min \frac{1}{2}||w||^2+C\sum_{i=1}^{m}\zeta_i^2\\ st.\ \ y^{\left( i \right)}\left( w^Tx^{\left( i \right)}+b \right) \ge 1-\zeta_i\\ \zeta_i \ge 0 \text{ }\\ \end{array}
min21∣∣w∣∣2+C∑i=1mζi2st. y(i)(wTx(i)+b)≥1−ζiζi≥0
SVM的使用
首先,因为SVM是设计到对距离的度量的,所以和KNN算法一样需要考虑将数据标准化。
下面就scikit-learn提供的SVM来进行一些使用。
scikit-learn中的SVM
加载鸢尾花的数据集
import numpy as np
import matplotlib.pyplot as plt
import warnings
from sklearn import datasets
warnings.filterwarnings("ignore")
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],color='r')
plt.scatter(X[y==1,0],X[y==1,1],color='b')
plt.show()
对数据集进行标准化工作
from sklearn.preprocessing import StandardScaler
std_scaler = StandardScaler()
std_scaler.fit(X)
X_std = std_scaler.transform(X)
调用SVM算法
from sklearn.svm import LinearSVC
svc = LinearSVC(C=1e9) # C取得越大,对精度要求越高,对泛化能力要求越低
svc.fit(X_std,y)
LinearSVC(C=1000000000.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)
绘制决策边界
- hard margin
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_std[y==0,0],X_std[y==0,1],color='r')
plt.scatter(X_std[y==1,0],X_std[y==1,1],color='b')
plt.show()
- soft margin
svc2 = LinearSVC(C=0.01) # SoftMargin
svc2.fit(X_std,y)
LinearSVC(C=0.01, 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)
可以看到一些比较重要的默认参数:
- multi_class=‘ovr’:解决多分类问题所采用的方式默认为ovr
- penalty=‘l2’:默认使用L2正则项
plot_decision_boundary(svc2,axis=[-3,3,-3,3])
plt.scatter(X_std[y==0,0],X_std[y==0,1],color='r')
plt.scatter(X_std[y==1,0],X_std[y==1,1],color='b')
plt.show()
查看w
svc.coef_ # 二维数组
array([[ 4.03237732, -2.50700312]])
查看b
svc.intercept_ # 一维数组
array([0.9273623])
改造可视化函数,使之绘制出margin
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*x0/w1 -b/w1
# w0*x0 + w1*x1 + b = +-1 => x1 = -w0*x0/w1 -b/w1 +- 1/w1
plot_x = np.linspace(axis[0],axis[1],200)
up_y = -w[0]*plot_x/w[1] -b/w[1] + 1/w[1]
down_y = -w[0]*plot_x/w[1] -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')
绘制hard margin
plot_svc_decision_boundary(svc,axis=[-3,3,-3,3])
plt.scatter(X_std[y==0,0],X_std[y==0,1],color='r')
plt.scatter(X_std[y==1,0],X_std[y==1,1],color='b')
plt.show()
绘制soft margin
plot_svc_decision_boundary(svc2,axis=[-3,3,-3,3])
plt.scatter(X_std[y==0,0],X_std[y==0,1],color='r')
plt.scatter(X_std[y==1,0],X_std[y==1,1],color='b')
plt.show()
SVM中使用多项式特征
多项式特征的应用是依靠升维使得原本线性不可分的数据再高维空间中变得线性可分。
使用多项式特征可以使得线性分类算法拟合出非线性的决策边界,从而提高模型的精确度,对于SVM也同样适用。
生成非线性分布的数据
X,y = datasets.make_moons()
plt.scatter(X[y==0,0],X[y==0,1])
plt.scatter(X[y==1,0],X[y==1,1])
plt.show()
为数据集添加噪音
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()
from sklearn.preprocessing import PolynomialFeatures,StandardScaler
from sklearn.svm import LinearSVC
from sklearn.pipeline import Pipeline
def PolynomialSVC(degree,C=1.0):
return Pipeline([
('poly',PolynomialFeatures(degree=degree)),
('std_scaler',StandardScaler()),
('SVM',LinearSVC(C=C))
])
poly_svc = PolynomialSVC(degree=3)
poly_svc.fit(X,y)
Pipeline(memory=None,
steps=[('poly',
PolynomialFeatures(degree=3, include_bias=True,
interaction_only=False, order='C')),
('std_scaler',
StandardScaler(copy=True, with_mean=True, with_std=True)),
('SVM',
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))],
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()
变化C的值使之为hard margin
poly_svc2 = PolynomialSVC(degree=3,C=1e9)
poly_svc2.fit(X,y)
plot_decision_boundary(poly_svc2,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()
可以看到边界可以把所有的数据分隔开,但是泛化能力就不是那么好了
使用多项式核函数的SVM
from sklearn.svm import SVC
def PolynomialKernelSVC(degree,C=1.0):
return Pipeline([
("std_scaler",StandardScaler()),
("kernelSVC",SVC(kernel="poly",degree=degree,C=C))
])
poly_kernel_svc = PolynomialKernelSVC(degree=3)
poly_kernel_svc.fit(X,y)
Pipeline(memory=None,
steps=[('std_scaler',
StandardScaler(copy=True, with_mean=True, with_std=True)),
('kernelSVC',
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))],
verbose=False)
plot_decision_boundary(poly_kernel_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()
可以看到通过kernel也可以直接实现SVM的多项式,但是在同样的参数C,degree下与前面通过pipline得到的决策边界大不相同,这是由于计算方式不同造成的。
核函数Kernel
先回顾一下SVM算法的本质就是求解下列的最优化问题:
min
1
2
∣
∣
w
∣
∣
2
+
C
∑
i
=
1
m
ζ
i
2
s
t
.
y
(
i
)
(
w
T
x
(
i
)
+
b
)
≥
1
−
ζ
i
ζ
i
≥
0
\begin{array}{c} \min \frac{1}{2}||w||^2+C\sum_{i=1}^{m}\zeta_i^2\\ st.\ \ y^{\left( i \right)}\left( w^Tx^{\left( i \right)}+b \right) \ge 1-\zeta_i\\ \zeta_i \ge 0 \text{ }\\ \end{array}
min21∣∣w∣∣2+C∑i=1mζi2st. y(i)(wTx(i)+b)≥1−ζiζi≥0
在求解这个最优化问题前,我们可以通过合理的转化使之更好求解:
可以看到,如果我们希望使得SVM支持多项式特征,其中的
x
i
,
x
j
x_i,x_j
xi,xj就会被升维成
x
i
′
,
x
j
′
x_i',x_j'
xi′,xj′ ,在这里期望找到一个函数能够通过
x
i
,
x
j
x_i,x_j
xi,xj 直接计算出
x
i
′
,
x
j
′
x_i',x_j'
xi′,xj′ 即:
K
(
x
i
,
x
j
)
=
x
i
′
,
x
j
′
K(x_i,x_j)=x_i',x_j'
K(xi,xj)=xi′,xj′
这样我们就无需对原来的样本进行变形,使得优化目标变为如下形式:
max
∑
i
=
1
m
α
i
−
1
2
∑
i
−
1
m
∑
j
=
1
m
α
i
α
j
y
i
y
j
K
(
x
i
,
x
j
)
s
t
.
0
≤
α
i
≤
C
∑
i
=
1
m
α
i
y
i
=
0
\begin{array}{c} \max \sum_{i=1}^m{\alpha _i}-\frac{1}{2}\sum_{i-1}^m{\sum_{j=1}^m{\alpha _i\alpha _jy_iy_jK\left( x_i,x_j \right)}}\\ st.\,\,\,\,0\le \alpha _i\le C\\ \ \ \ \ \ \sum_{i=1}^m{\alpha _iy_i=}0\,\,\\ \end{array}
max∑i=1mαi−21∑i−1m∑j=1mαiαjyiyjK(xi,xj)st.0≤αi≤C ∑i=1mαiyi=0
这样即省去了存储升维后的高维变量的空间,又减少了计算的步骤,大大降低了计算复杂度。
核函数只是用来计算映射到高维空间之后的内积的一种简便方法。
这里引用一个知乎回答,我看完之后确实茅舍顿开:
机器学习有很多关于核函数的说法,核函数的定义和作用是什么? - SleepyBag的回答 - 知乎 https://www.zhihu.com/question/24627666/answer/1085861632
其实是一个非常简单的概念。首先给你两个向量 x,z 。
在一般的机器学习方法,比如 SVM 里面,这里一个向量是一个实体。比如一个向量代表一个人。每个向量有两个维度,身高和体重。比如可以有
x
=
(
180
,
70
)
x=(180,70)
x=(180,70)
y = ( 160 , 50 ) y=(160,50) y=(160,50)
现在要求两个人的相似度,最简单的方法是计算它们的内积<x,z>
。这很简单,只要按照维度相乘求和就可以了。
<
x
,
z
>
=
180
∗
160
+
70
∗
50
=
32300
<x,z> = 180*160+70*50=32300
<x,z>=180∗160+70∗50=32300
但是有的时候(比如 SVM 的数据线性不可分的时候),我们可能会想对数据做一些操作。我们可能认为体重的二次方,身高的二次方,或者身高体重的乘积是更重要的特征,我们把这个操作记为过程
ϕ
\phi
ϕ,比如可能有
ϕ
(
x
)
=
(
x
1
2
,
x
2
2
,
2
x
1
x
2
)
\phi(x)=(x_1^2,x_2^2,\sqrt{2}x1x2)
ϕ(x)=(x12,x22,2x1x2)
我们认为
ϕ
(
x
)
\phi(x)
ϕ(x) 比
x
x
x 更能表示一个人的特征。我们再计算两个人的相似度时,使用
ϕ
(
x
)
\phi(x)
ϕ(x)与
ϕ
(
z
)
\phi(z)
ϕ(z) 的内积:
<
ϕ
(
x
)
,
ϕ
(
z
)
>
=
<
(
18
0
2
,
7
0
2
,
2
∗
180
∗
70
)
,
(
16
0
2
,
5
0
2
,
2
∗
160
∗
50
)
>
=
18
0
2
∗
16
0
2
+
7
0
2
∗
5
0
2
+
2
∗
180
∗
70
∗
2
∗
160
∗
50
=
1043290000
<\phi(x),\phi(z)>=<(180^2,70^2,\sqrt{2}*180*70),(160^2,50^2,\sqrt{2}*160*50)>\\ =180^2*160^2+70^2*50^2+\sqrt{2}*180*70*\sqrt{2}*160*50\\ =1043290000
<ϕ(x),ϕ(z)>=<(1802,702,2∗180∗70),(1602,502,2∗160∗50)>=1802∗1602+702∗502+2∗180∗70∗2∗160∗50=1043290000
在上面的操作中,我们总共要计算 11 次乘法,2 次加法。但是如果我们定义核函数
K
(
x
,
z
)
=
(
<
x
,
z
>
)
2
K(x,z)=(<x,z>)^2
K(x,z)=(<x,z>)2
那么有
K
(
x
,
z
)
=
(
<
x
,
z
>
)
2
=
(
x
1
z
1
+
x
2
z
2
)
2
=
(
180
∗
160
+
70
∗
50
)
2
=
1043290000
K(x,z)=(<x,z>)^2\\ =(x_1z_1+x_2z_2)^2\\ =(180*160+70*50)^2\\ =1043290000
K(x,z)=(<x,z>)2=(x1z1+x2z2)2=(180∗160+70∗50)2=1043290000
可以看到 。但是这次我们只计算了 3 次乘法,1 次加法。所以其实核函数就是这么一回事:当我们需要先对数据做转换,然后求内积的时候,这样的一系列操作往往成本过高(有时候根本不可能,因为我们可能想要升到无穷维)。因此我们可以直接定义一个核函数 K 直接求出做转换后求内积的结果,从而降低运算量。
多项式核函数
首先看针对二项式的核函数:
按照数学的假设证明思想,我们可以得到一个更为general的核函数定义:
K
(
x
,
y
)
=
(
x
⋅
y
+
c
)
d
K(x,y)=(x\cdot y +c)^d
K(x,y)=(x⋅y+c)d
这就是多项式核函数,其中
-
c作为一个模型超参数,对应SVC构造方法中的参数
coef0
-
d表示多项式的最高次幂,对应SVC构造方法中的参数
degree
class sklearn.svm.SVC
(*, C=1.0, kernel=‘rbf’, degree=3, gamma=‘scale’, coef0=0.0, shrinking=True, probability=False, tol=0.001, cache_size=200, class_weight=None, verbose=False, max_iter=-1, decision_function_shape=‘ovr’, break_ties=False, random_state=None)[source]
C: float, default=1.0
Regularization parameter. The strength of the regularization is inversely proportional to C. Must be strictly positive. The penalty is a squared l2 penalty.
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)
.
degree: int,default=3
Degree of the polynomial kernel function (‘poly’). Ignored by all other kernels.
coef0: float, default=0.0
Independent term in kernel function. It is only significant in ‘poly’ and ‘sigmoid’.
高斯核函数RBF kernel
高斯核函数又称Radial basis function(径向基函数),
定义:
K
(
r
)
=
e
−
r
2
2
σ
2
K(r)=e^{-\frac{r^2}{2\sigma^2}}\\
K(r)=e−2σ2r2
式中,
r
=
∣
∣
X
1
,
X
2
∣
∣
r=||X_1,X_2||
r=∣∣X1,X2∣∣ ,
σ
\sigma
σ为RBF核的超参数,定义了学习样本间相似性的特征长度尺度(characteristic length-scale),即权重空间视角下特征空间映射前后样本间距离的比例.
令
γ
=
1
2
σ
2
\gamma = \frac{1}{2\sigma^2}
γ=2σ21 ,令x与y为两个独立样本,上式可以改写为如下形式:
K
(
x
,
y
)
=
e
−
γ
∣
∣
x
−
y
∣
∣
2
K(x,y)=e^{-\gamma||x-y||^2}
K(x,y)=e−γ∣∣x−y∣∣2
其中根据高斯函数的分布可以得知
- γ \gamma γ 越大,高斯分布越窄
- γ \gamma γ 越小,高斯分布越宽
通过高斯核函数,我们可以把每一个样本点映射到一个无穷维的空间。
那么“把维度映射到无穷多维”,是如何理解的?如何看出是“无穷多维”的?
直观理解高斯核函数
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]))
plt.scatter(x[y==1],[0]*len(x[y==1]))
plt.show()
可以看到对于这些样本点是线性不可分的。
接下来我们取y为坐标L1,L2,实现高斯核函数对原始数据的升维:
K
(
x
,
y
)
=
e
−
γ
∣
∣
x
−
y
∣
∣
2
K(x,y)=e^{-\gamma||x-y||^2}
K(x,y)=e−γ∣∣x−y∣∣2
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变为二维的X_new
X_new[i,0] = gaussian(data,L1) # X_new^(i)的第0个特征
X_new[i,1] = gaussian(data,L2) # X_new^(i)的第1个特征
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()
可以看到通过高斯核函数将原本一维的数据映射为了二维的数据,原本一维的数据在二维的平面中变得线性可分了
此外,我们可以通过对上面的计算过程得出对于高斯核函数,每一个其他类型Y的数据样本点都可以作为当前类型数据X的landmark L
,所以有多少个样本就能对数据提升多少个维度。从而实现所谓无穷多维
。
scikit-learn中的RBF核函数
scikit-learn为开发者提供了SVC
类,其内部实现了高斯核函数,我们可以通过对其构造方法钟传入参数kernel="rbf"
来使用核函数。通过传入参数gamma=n
来指定
γ
\gamma
γ 的值。通常对
γ
\gamma
γ 的取值越大,越容易出现过拟合,对
γ
\gamma
γ 的取值越小,越容易出现欠拟合。
import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
import warnings
warnings.filterwarnings("ignore")
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()
scikit-learn在SVC类中提供了高斯核函数的实现
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(gamma=1.0)
svc.fit(X,y)
Pipeline(memory=None,
steps=[('std_scaler',
StandardScaler(copy=True, with_mean=True, with_std=True)),
('svc',
SVC(C=1.0, cache_size=200, class_weight=None, coef0=0.0,
decision_function_shape='ovr', degree=3, gamma=1.0,
kernel='rbf', max_iter=-1, probability=False,
random_state=None, shrinking=True, tol=0.001,
verbose=False))],
verbose=False)
看看经过高斯核函数改进后的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=[-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()
可以看到似乎与前面通过多项式得到的SVM决策边界非常像,我们不妨调节一下gamma的取值
svc2 = RBFKernelSVC(gamma=100.0)
svc2.fit(X,y)
plot_decision_boundary(svc2,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()
假设我们站在一个俯视的角度来看这个图像,其中粉色的包裹视作为一个个高斯分布
验证了前面gamma越大高斯分布越窄的判断。显然此时的模型出现了过拟合
继续调整gamma为10
svc3 = RBFKernelSVC(gamma=10.0)
svc3.fit(X,y)
plot_decision_boundary(svc3,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()
这个图可以理解为对于每一个蓝色的点来说,它相应的那个高斯钟型图案变得宽了一些,最终形成了这样的决策边界.
SVM思想解决回归问题
与SVM解决分类问题时,决策边界的上下是存在一个margin的,这使得分类器的泛化能力得到质的飞跃;同样,在SVM解决回归问题同样是存在一个margin的,只不过在这种情况下,我们期望的是在这个margin范围里,我们可以包含进的样本数据点越多越好。
可以直观地想象为拟合出来的这条线变得很粗
这是与SVM解决分类问题的一个反向思路。
在scikit-learn的SVR
中提供了一个通过SVM实现的回归器,
具体使用
使用波士顿数据集
import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.model_selection import train_test_split
import warnings
warnings.filterwarnings("ignore")
boston = datasets.load_boston()
X = boston.data
y = boston.target
X_train,X_test,y_train,y_test = train_test_split(X,y,random_state=666)
使用LinearSVR实现对波士顿房价的回归拟合
from sklearn.svm import LinearSVR
from sklearn.svm import SVR
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
def StandardLinearSVR(epsilon=0.1):
return Pipeline([
("std_scaler",StandardScaler()),
("linearSVR",LinearSVR(epsilon=epsilon))
])
svr = StandardLinearSVR()
svr.fit(X_train,y_train)
Pipeline(memory=None,
steps=[('std_scaler',
StandardScaler(copy=True, with_mean=True, with_std=True)),
('linearSVR',
LinearSVR(C=1.0, dual=True, epsilon=0.1, fit_intercept=True,
intercept_scaling=1.0, loss='epsilon_insensitive',
max_iter=1000, random_state=None, tol=0.0001,
verbose=0))],
verbose=False)
其中的episilon参数代表的是margin的大小,与前面画的那个图是一一对应的,其余的参数C也是可以调节的超参数
svr.score(X_test,y_test)
0.6355952602172072
使用SVR实现对波士顿房价的回归拟合
def StandardSVR(C=1,epsilon=0.1,degree=3):
return Pipeline([
("std_scaler",StandardScaler()),
("SVR",SVR(epsilon=epsilon,C=C,degree=degree))
])
svr = StandardSVR()
svr.fit(X_train,y_train)
Pipeline(memory=None,
steps=[('std_scaler',
StandardScaler(copy=True, with_mean=True, with_std=True)),
('SVR',
SVR(C=1, cache_size=200, coef0=0.0, degree=3, epsilon=0.1,
gamma='auto_deprecated', kernel='rbf', max_iter=-1,
shrinking=True, tol=0.001, verbose=False))],
verbose=False)
svr.score(X_test,y_test)
0.6835114955961085
发现改变C之后能够提高模型的精度,但是其实该泛化能力是在下降的
def plot_learning_curve(algo, X_train, X_test, y_train, y_test):
from sklearn.metrics import mean_squared_error
train_score = []
test_score = []
for i in range(1, len(X_train)+1):
algo.fit(X_train[:i], y_train[:i])
y_train_predict = algo.predict(X_train[:i])
train_score.append(mean_squared_error(y_train[:i], y_train_predict))
y_test_predict = algo.predict(X_test)
test_score.append(mean_squared_error(y_test, y_test_predict))
plt.plot([i for i in range(1, len(X_train)+1)],
np.sqrt(train_score), label="train")
plt.plot([i for i in range(1, len(X_train)+1)],
np.sqrt(test_score), label="test")
plt.legend()
# plt.axis([0, len(X_train)+1, 0, 4])
plt.show()
plot_learning_curve(svr, X_train, X_test, y_train, y_test)
svr2 = StandardSVR(C=10)
svr2.fit(X_train,y_train)
plot_learning_curve(svr2, X_train, X_test, y_train, y_test)
svr2.score(X_test,y_test)
0.8000666894200117
svr3 = StandardSVR(C=100)
svr3.fit(X_train,y_train)
plot_learning_curve(svr3, X_train, X_test, y_train, y_test)
svr3.score(X_test,y_test)
0.8256298418557101
同样,我们试着改变epsilon来看其对模型精度与泛化能力的影响
svr4 = StandardSVR(epsilon=0.00000001)
svr4.fit(X_train,y_train)
plot_learning_curve(svr4, X_train, X_test, y_train, y_test)
svr4.score(X_test,y_test)
0.6823543018512488
svr5 = StandardSVR(epsilon=1)
svr5.fit(X_train,y_train)
plot_learning_curve(svr5, X_train, X_test, y_train, y_test)
svr5.score(X_test,y_test)
0.6885019791112185
svr6 = StandardSVR(epsilon=10)
svr6.fit(X_train,y_train)
plot_learning_curve(svr6, X_train, X_test, y_train, y_test)
svr6.score(X_test,y_test)
0.22528483765477603
可以看到epsilon越大,模型泛化能力越好,相应的精度越低
总结
参考致谢
liuyubobo:https://github.com/liuyubobobo/Play-with-Machine-Learning-Algorithms
liuyubobo:https://coding.imooc.com/class/169.html
莫烦Python:https://www.bilibili.com/video/BV1xW411Y7Qd?from=search&seid=11773783205368546023