1. SVM原理
SVM算法的本质就是最大化离超平面最近点(支持向量)到该平面的距离。如图所示(以二分类为例):
转化为数学问题就是:
m
a
x
w
,
b
(
m
i
n
x
i
y
i
(
w
T
x
i
+
b
)
∣
w
∣
)
max_{w,b}(min_{x_i}\frac{y_i(w^Tx_i+b)}{|w|})
maxw,b(minxi∣w∣yi(wTxi+b))
注意:
- 这里的 y i y_i yi 就是标签(这里以二分类为例),其值是 1 和 -1,其保证了不论样本属于哪一类 y i ( w T x i + b ) y_i(w^Tx_i+b) yi(wTxi+b) 的值都是大于 0 的
-
y
i
(
w
T
x
i
+
b
)
y_i(w^Tx_i+b)
yi(wTxi+b) 称为函数距离,
y
i
(
w
T
x
i
+
b
)
∣
w
∣
)
\frac{y_i(w^Tx_i+b)}{|w|})
∣w∣yi(wTxi+b)) 称为几何距离,这里之所以要使用几何距离是因为:当
w
,
b
w, b
w,b 成倍增加时,函数距离也会相应的成倍的增加,而几何距离则不会。
由于这里涉及到求两个最值问题,比较麻烦,为简化问题,这里不妨就将最近点到超平面的函数距离设为 1,自然其他非最近点的函数距离便大于 1,于是以上问题转化为:
而这明显时一个在有不等式约束下最小值优化的问题,因此,这里可以使用 KKT 条件。
1. 求解最小值的优化问题
(1)若约束条件为等式–拉格朗日乘子法
假设给定最小值优化问题为:
m
i
n
f
(
x
)
minf(x)
minf(x),满足的约束条件为:
g
(
x
)
=
0
g(x)=0
g(x)=0。(为方便分析,假设
f
(
x
)
f(x)
f(x) 与
g
(
x
)
g(x)
g(x) 都是连续可导函数)
求解步骤为:
①写出其拉格朗日函数:
L
(
x
,
λ
)
=
f
(
x
)
+
λ
g
(
x
)
L(x,\lambda)=f(x)+\lambda g(x)
L(x,λ)=f(x)+λg(x),其中
λ
≢
0
\lambda\not≡0
λ≡0
②根据最优解的必要条件可得如下方程:
{
∇
x
L
=
∂
L
∂
x
=
∇
f
+
λ
∇
g
=
0
∇
λ
L
=
∂
L
∂
λ
=
g
(
x
)
=
0
\begin{cases} \nabla_xL = \frac{\partial L}{\partial x}=\nabla f+\lambda\nabla g=0 \\ \nabla_{\lambda}L = \frac{\partial L}{\partial \lambda}=g(x)=0 \end{cases}
{∇xL=∂x∂L=∇f+λ∇g=0∇λL=∂λ∂L=g(x)=0
③解上述方程便可得到
λ
\lambda
λ 的值,并可求解
m
i
n
f
(
x
)
minf(x)
minf(x)。
(2)若约束条件为不等式–KKT
假设给定最小值优化问题为:
m
i
n
f
(
x
)
minf(x)
minf(x),满足的约束条件为:
g
(
x
)
≤
0
g(x)\leq0
g(x)≤0(称为原始可行性)。
求解步骤为:
①写出其拉格朗日函数:
L
(
x
,
λ
)
=
f
(
x
)
+
λ
g
(
x
)
L(x,\lambda)=f(x)+\lambda g(x)
L(x,λ)=f(x)+λg(x),其中
λ
≢
0
\lambda\not≡0
λ≡0
②据此我们定义可行域
K
=
x
∈
R
n
∣
g
(
x
)
≤
0
K=x\in R^n|g(x)\leq0
K=x∈Rn∣g(x)≤0。假设
x
∗
x^*
x∗ 为满足约束条件的最优解,分开两种情况讨论:
1)
g
(
x
∗
)
<
0
g(x^*)<0
g(x∗)<0,最佳解位于
K
K
K 的内部,称为内部解,这是约束条件是无效的。
2)
g
(
x
∗
)
=
0
g(x^*)=0
g(x∗)=0,最佳解落在
K
K
K 的边界,称为边界解,此时约束条件是有效的。
这两种情况的最佳解具有不同的必要条件。
1)内部解:在约束条件无效的情形下,
g
(
x
)
g(x)
g(x) 不起作用,约束优化问题退化为无约束优化问题,因此驻点
x
∗
x^*
x∗ 满足
∇
f
=
0
\nabla f=0
∇f=0 且
λ
=
0
\lambda=0
λ=0
2)边界解:在约束条件有效的情形下,约束不等式变成等式
g
(
x
)
=
0
g(x)=0
g(x)=0,这与前述拉格朗日乘子法的情况相同,我们可以证明驻点
x
∗
x^*
x∗ 发生于
∇
f
∈
s
p
a
n
∇
g
\nabla f\in span\nabla g
∇f∈span∇g,换句话说,存在
λ
\lambda
λ 使得
∇
f
=
−
λ
∇
g
\nabla f=-\lambda\nabla g
∇f=−λ∇g,但这里
λ
\lambda
λ 的正负号是有其意义的。因为我们希望最小化
f
f
f,梯度
∇
f
\nabla f
∇f(函数
f
f
f 在点
x
x
x 的最陡上升方向)应该指向可行域
K
K
K 的内部(因为你的最优解最小值是在边界取得的),但
∇
g
\nabla g
∇g 指向
K
K
K 的外部(即
g
(
x
)
>
0
g(x)>0
g(x)>0 的区域,因为你的约束是小于等于0),因此
λ
≥
0
\lambda\geq0
λ≥0,称为对偶可行性。
因此,不论是内部解或边界解,
λ
g
(
x
)
=
0
\lambda g(x)=0
λg(x)=0 恒成立,称为互补松弛性。整合上述两种情况,最佳解的必要条件包括拉格朗日函数
L
(
x
,
λ
)
L(x,\lambda)
L(x,λ) 的定常方程式、原始可行性、对偶可行性以及互补松弛性:
{
∇
x
L
=
∂
L
∂
x
=
∇
f
+
λ
∇
g
=
0
g
(
x
)
≤
0
λ
≥
0
λ
g
(
x
)
=
0
\begin{cases} \nabla_xL = \frac{\partial L}{\partial x}=\nabla f+\lambda\nabla g=0 \\ g(x)\leq0 \\ \lambda \geq 0 \\ \lambda g(x) = 0 \end{cases}
⎩⎪⎪⎪⎨⎪⎪⎪⎧∇xL=∂x∂L=∇f+λ∇g=0g(x)≤0λ≥0λg(x)=0
这些条件合称为Karush-Kuhn-Tucker (KKT)条件。如果我们要最大化
f
(
x
)
f(x)
f(x) 且受限于
g
(
x
)
≤
0
g(x)\leq0
g(x)≤0,那么对偶可行性要改成
λ
≤
0
\lambda\leq0
λ≤0。上面结果可推广至多个约束等式与约束不等式的情况。考虑标准约束优化问题(或称非线性规划):
定义Lagrangian 函数
其中
λ
i
\lambda_i
λi 是对应
g
i
(
x
)
=
0
g_i(x)=0
gi(x)=0 的Lagrange乘数,
μ
k
\mu_k
μk是对应
h
k
(
x
)
≤
0
h_k(x)\leq0
hk(x)≤0 的Lagrange乘数(或称KKT乘数)。KKT条件包括
2. 松弛因子
实际上很多样本数据都不能够用一个超平面把数据完全分开。如果数据集中存在离群点的话,那么在求超平面时会出现很大问题。从下图中可以看出,其中一个蓝点偏差太大,如果把它作为支持向量的话所求出来的
m
a
r
g
i
n
margin
margin 就会比不算入它时要小得多。更糟糕的情况是如果这个蓝点落在了红点之间那么就找不出超平面了。
因此引入一个松弛变量
ξ
\xi
ξ 来允许一些数据可以处于分隔面错误的一侧。这时新的约束条件变为:
y
i
(
w
T
x
i
+
b
)
≥
1
−
ξ
i
,
i
=
1
,
2
,
.
.
.
,
m
y_i(w^Tx_i+b)\geq1-\xi_i,i=1,2,...,m
yi(wTxi+b)≥1−ξi,i=1,2,...,m
式子中
ξ
i
\xi_i
ξi 的含义为允许第
i
i
i 个数据点偏离的间隔。如果让
ξ
\xi
ξ 任意大的话,那么任意的超平面都是符合条件的了。所以在原有目标的基础之上,我们也尽可能的让
ξ
\xi
ξ 的总量小。所以新的目标函数变为:
m
i
n
1
2
∣
∣
w
∣
∣
2
+
C
∑
i
=
1
m
ξ
i
s
.
t
.
∑
i
=
1
m
α
i
y
i
=
0
a
n
d
0
≤
α
≤
C
min\frac{1}{2}||w||^2+C\sum_{i=1}^m{\xi_i}{\,}{\,}{\,}{\,}{\,} s.t.{\,}{\,}{\,} \sum_{i=1}^m{\alpha_iy_i}=0 {\,}{\,}and {\,}{\,}0\leq\alpha\leq C
min21∣∣w∣∣2+Ci=1∑mξis.t.i=1∑mαiyi=0and0≤α≤C
其中的
C
C
C 是用于控制“最大化间隔”和“保证大部分的点的函数间隔都小于1”这两个目标的权重。将上述模型完整的写下来就是:
m
i
n
1
2
∣
∣
w
∣
∣
2
+
C
∑
i
=
1
m
ξ
i
s
.
t
.
ξ
i
≥
0
a
n
d
y
i
(
w
T
x
i
+
b
)
≥
1
−
ξ
i
,
i
=
1
,
2
,
.
.
.
,
m
min\frac{1}{2}||w||^2+C\sum_{i=1}^m{\xi_i}{\,}{\,}{\,}{\,}{\,} s.t.{\,}{\,}{\,}\xi_i\geq0{\,}{\,}and{\,}{\,}y_i(w^Tx_i+b)\geq1-\xi_i,i=1,2,...,m
min21∣∣w∣∣2+Ci=1∑mξis.t.ξi≥0andyi(wTxi+b)≥1−ξi,i=1,2,...,m
新的拉格朗日函数变为:
L
(
w
,
b
,
ξ
,
α
,
τ
)
=
1
2
∣
∣
w
∣
∣
2
+
C
∑
i
=
1
m
ξ
i
−
∑
i
=
1
m
α
i
(
y
i
(
w
T
x
i
+
b
)
−
1
+
ξ
i
)
−
∑
i
=
1
m
τ
i
ξ
i
L(w,b,\xi,\alpha,\tau)=\frac{1}{2}||w||^2+C\sum_{i=1}^m{\xi_i}-\sum_{i=1}^m{\alpha_i(y_i(w^Tx_i+b)-1+\xi_i)}-\sum_{i=1}^m{\tau_i\xi_i}
L(w,b,ξ,α,τ)=21∣∣w∣∣2+Ci=1∑mξi−i=1∑mαi(yi(wTxi+b)−1+ξi)−i=1∑mτiξi
接下来将拉格朗日函数转化为其对偶函数,首先对
L
L
L 分别求
w
,
b
,
ξ
w,b,\xi
w,b,ξ 的偏导,并令其为0,结果如下:
{
∂
L
∂
w
=
0
⇒
w
=
∑
i
=
1
m
α
i
y
i
x
i
∂
L
∂
b
=
0
⇒
∑
i
=
1
m
α
i
y
i
=
0
∂
L
∂
ξ
i
=
0
⇒
C
−
α
i
−
τ
i
=
0
\begin{cases} \frac{\partial L}{\partial w} = 0 \Rightarrow w=\sum_{i=1}^m{\alpha_iy_ix_i} \\ \frac{\partial L}{\partial b} = 0 \Rightarrow \sum_{i=1}^m{\alpha_iy_i}=0 \\ \frac{\partial L}{\partial \xi_i} = 0 \Rightarrow C-\alpha_i-\tau_i=0 \end{cases}
⎩⎪⎨⎪⎧∂w∂L=0⇒w=∑i=1mαiyixi∂b∂L=0⇒∑i=1mαiyi=0∂ξi∂L=0⇒C−αi−τi=0
代入原式化简之后得到和原来一样的目标函数:
m
a
x
L
(
α
)
=
∑
i
=
1
m
α
i
−
1
2
∑
i
,
j
=
1
m
α
i
α
j
y
i
y
j
x
i
T
x
j
max L(\alpha)=\sum_{i=1}^m{\alpha_i}-\frac{1}{2}\sum_{i,j=1}^m{\alpha_i\alpha_jy_iy_jx_i^Tx_j}
maxL(α)=i=1∑mαi−21i,j=1∑mαiαjyiyjxiTxj
但是由于我们得到
C
−
α
i
−
τ
i
=
0
C-\alpha_i-\tau_i=0
C−αi−τi=0,而
τ
i
≥
0
\tau_i\geq0
τi≥0,因此有
α
i
≤
C
\alpha_i\leq C
αi≤C,所以对偶问题写成:
L
(
α
)
=
1
2
∑
i
,
j
=
1
m
α
i
α
j
y
i
y
j
x
i
T
x
j
−
∑
i
,
j
=
1
m
α
i
α
j
y
i
y
j
x
i
T
x
j
−
b
∑
i
,
j
=
1
m
α
i
y
i
+
∑
i
=
1
m
α
i
=
∑
i
=
1
m
α
i
−
1
2
∑
i
,
j
=
1
m
α
i
α
j
y
i
y
j
x
i
T
x
j
L(\alpha)=\frac{1}{2}\sum_{i,j=1}^m{\alpha_i\alpha_jy_iy_jx_i^Tx_j}-\sum_{i,j=1}^m{\alpha_i\alpha_jy_iy_jx_i^Tx_j}-b\sum_{i,j=1}^m{\alpha_iy_i}+\sum_{i=1}^m{\alpha_i}=\sum_{i=1}^m{\alpha_i}-\frac{1}{2}\sum_{i,j=1}^m{\alpha_i\alpha_jy_iy_jx_i^Tx_j}
L(α)=21i,j=1∑mαiαjyiyjxiTxj−i,j=1∑mαiαjyiyjxiTxj−bi,j=1∑mαiyi+i=1∑mαi=i=1∑mαi−21i,j=1∑mαiαjyiyjxiTxj
经过添加松弛变量的方法,我们现在能够解决数据更加混乱的问题。通过修改参数
C
C
C,我们可以得到不同的结果而
C
C
C 的大小到底取多少比较合适,需要根据实际问题进行调节。
3. 核函数
以上讨论的都是在线性可分的情况下进行讨论的,但是实际问题中给出的数据并不是都是线性可分的,比如有些数据可能是如下图样子:
那么这种非线性可分的数据是否就不能用 SVM 算法来求解呢?答案是否定的。事实上,对于低维平面内不可分的数据,放在一个高维空间中去就有可能变得可分。以二维平面的数据为例,我们可以通过找到一个映射将二维平面的点放到三维平面之中。理论上任意的数据样本都能够找到一个合适的映射使得这些在低维空间不能划分的样本到高维空间中之后能够线性可分。我们再来看一下之前的目标函数:
m
a
x
L
(
α
)
=
∑
i
=
1
m
α
i
−
1
2
∑
i
,
j
=
1
m
α
i
α
j
y
i
y
j
x
i
T
x
j
s
.
t
.
∑
i
=
1
m
α
i
y
i
=
0
a
n
d
α
i
>
0
,
i
=
1
,
2
,
.
.
.
,
m
max{\,}{\,}L(\alpha)=\sum_{i=1}^m{\alpha_i}-\frac{1}{2}\sum_{i,j=1}^m{\alpha_i\alpha_jy_iy_jx_i^Tx_j}{\,}{\,}{\,}{\,}s.t.{\,}{\,}{\,}{\,}\sum_{i=1}^m{\alpha_iy_i}=0{\,}{\,}and{\,}{\,}\alpha_i>0,i=1,2,...,m
maxL(α)=i=1∑mαi−21i,j=1∑mαiαjyiyjxiTxjs.t.i=1∑mαiyi=0andαi>0,i=1,2,...,m
定义一个映射使得将所有映射到更高维空间之后等价于求解上述问题的对偶问题:
m
a
x
L
(
α
)
=
∑
i
=
1
m
α
i
−
1
2
∑
i
,
j
=
1
m
α
i
α
j
y
i
y
j
<
ϕ
(
x
i
)
,
ϕ
(
x
j
)
>
s
.
t
.
∑
i
=
1
m
α
i
y
i
=
0
a
n
d
0
≤
α
i
≤
C
,
i
=
1
,
2
,
.
.
.
,
m
max{\,}{\,}L(\alpha)=\sum_{i=1}^m{\alpha_i}-\frac{1}{2}\sum_{i,j=1}^m{\alpha_i\alpha_jy_iy_j<\phi(x_i),\phi(x_j)>}{\,}{\,}{\,}{\,}s.t.{\,}{\,}{\,}{\,}\sum_{i=1}^m{\alpha_iy_i}=0{\,}{\,}and{\,}{\,}0\leq\alpha_i\leq C,i=1,2,...,m
maxL(α)=i=1∑mαi−21i,j=1∑mαiαjyiyj<ϕ(xi),ϕ(xj)>s.t.i=1∑mαiyi=0and0≤αi≤C,i=1,2,...,m
这样对于线性不可分的问题就解决了,现在只需要找出一个合适的映射即可。当特征变量非常多的时候,在高维空间中计算内积的运算量是非常庞大的。考虑到我们的目的并不是为找到这样一个映射而是为了计算其在高维空间的内积,因此如果我们能够找到计算高维空间下内积的公式,那么就能够避免这样庞大的计算量,我们的问题也就解决了。实际上这就是我们要找的和函数
K
(
x
i
,
x
j
)
K(x_i,x_j)
K(xi,xj),即两个向量在隐式映射后的空间中的内积。
那么怎样的函数才可以作为核函数呢?下面的一个定理可以帮助我们判断。
Mercer定理:任何半正定的函数都可以作为核函数。其中所谓半正定函数
f
(
x
i
,
x
j
)
f(x_i, x_j)
f(xi,xj) 是指拥有训练集数据集合,我们定义一个矩阵的元素
α
i
j
=
f
(
x
i
,
x
j
)
\alpha_{ij}=f(x_i,x_j)
αij=f(xi,xj),这个矩阵是
n
×
n
n\times n
n×n 的矩阵,如果这个矩阵是半正定的,那么
f
(
x
i
,
x
j
)
f(x_i,x_j)
f(xi,xj) 就称为半正定函数。
值得注意的是,上述定理中所给出的条件是充分条件而非充要条件。因为有些非正定函数也可以作为核函数。
下面是一些常用的核函数:
现在我们已经了解了一些支持向量机的理论基础,我们通过对偶问题的的转化将最开始求
w
,
b
w,b
w,b 的问题转化为求
α
\alpha
α 的对偶问题。只要找到所有的
α
\alpha
α (即找出所有支持向量),我们就能够确定
w
,
b
w,b
w,b。然后就可以通过计算数据点到这个超平面的距离从而判断出该数据点的类别。
SMO算法是支持向量机学习的一种快速算法,其特点是不断地将原二次规划问题分解为只有两个变量的二次规划子问题,并对子问题进行解析求解,直到所有变量满足KKT条件为止。这样通过启发式的方法得到原二次规划问题的最优解。因为子问题有解析解,所以每次计算子问题都很快,虽然计算子问题次数很多,但在总体上还是高效的。
2. SVM算法实现
1. 线性SVM
①导入各种第三方库
import numpy as np
import pandas as pd
import sklearn.svm
import seaborn as sns
import scipy.io as sio
import matplotlib.pyplot as plt
②加载并构建数据集
mat = sio.loadmat('../data/ex6data1.mat')
data = pd.DataFrame(mat.get('X'), columns=['X1', 'X2'])
data['y'] = mat.get('y')
data.head()
③可视化数据
plt.style.use('seaborn')
fig, ax = plt.subplots(figsize = (8, 6))
# 参数c:数字在[0,1]之间,代表颜色由浅入深
ax.scatter(data['X1'], data['X2'], s=50, c=data['y'], cmap='Reds')
ax.set_title('Raw data')
ax.set_xlabel('X1')
ax.set_ylabel('X2')
plt.show()
④当C=1时,模型的分数
class sklearn.svm.LinearSVC(penalty='l2', loss='squared_hinge', dual=True, tol=0.0001, 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 有
loss:损失函数,有 ‘hinge’ 和 ‘squared_hinge’ 两种可选,前者又称L1损失,后者称为L2损失,默认是是 ’squared_hinge’,其中 hinge 是 SVM 的标准损失,squared_hinge 是 hinge 的平方。
dual:是否转化为对偶问题求解,默认是 True。
tol:残差收敛条件,默认是0.0001,与线性回归中的一致。
C:惩罚系数,用来控制损失函数的惩罚系数,类似于线性回归中的正则化系数。
multi_class:负责多分类问题中分类策略制定,有 ‘ovr’ 和 ‘crammer_singer’ 两种参数值可选,默认值是 ‘ovr’,‘ovr’ 的分类原则是将待分类中的某一类当作正类,其他全部归为负类,通过这样求取得每个类别作为正类时的正确率,取正确率最高的那个类别作为正类;'crammer_singer‘ 是直接针对目标函数设置多个参数值,最后进行优化,得到不同类别的参数值大小。
fit_intercept:是否计算截距,与线性回归模型中的意思一致。
class_weight:与其他模型中参数含义一样,也是用来处理不平衡样本数据的,可以直接以字典的形式指定不同类别的权重,也可以使用 balanced 参数值。
verbose:是否冗余,默认是 False。
random_state:随机种子的大小。
max_iter:最大迭代次数,默认是1000。
返回值:
coef_:各个特征之前的系数。
intercept_:截距。
方法:
decision_function(self, X):预测样本的信心分。
densify(self):将系数矩阵转换为稠密数组格式。
fit(self, X, y, sample_weight):根据训练数据集训练模型。
get_params(self, deep):得到模型的参数。
predict(self, X):在样本数据中预测类别的标签。
score(self, X):返回模型的平均准确率。
set_params(self, params):设置模型的参数。
sparsify(self):将系数矩阵转换为稀疏格式。
svc1 = sklearn.svm.LinearSVC(C=1, loss='hinge')
svc1.fit(data[['X1', 'X2']], data['y']) # 训练模型
svc1.score(data[['X1', 'X2']], data['y'])
此时,模型的分数为:0.9803921568627451
⑤根据模型自信度将样本数据进行可视化展示
data['SVM1 Confidence'] = svc1.decision_function(data[['X1', 'X2']])
fig, ax = plt.subplots(figsize=(8, 6))
ax.scatter(data['X1'], data['X2'], s=50, c=data['SVM1 Confidence'], cmap='RdBu')
ax.set_title('SVM (C=1) Decision Confidence')
plt.show()
⑥当C=100时,模型的分数
当C很大时,可能会出现过拟合的情况
svc100 = sklearn.svm.LinearSVC(C=100, loss='hinge')
svc100.fit(data[['X1', 'X2']], data['y'])
svc100.score(data[['X1', 'X2']], data['y'])
此时,模型的分数为:1.0
⑦根据模型自信度将样本数据进行可视化展示
data['SVM100 Confidence'] = svc100.decision_function(data[['X1', 'X2']])
fig, ax = plt.subplots(figsize=(8, 6))
ax.scatter(data['X1'], data['X2'], s=50, c=data['SVM100 Confidence'], cmap='RdBu')
ax.set_title('SVM (C=100) Decision Confidence')
plt.show()
⑧查看两种C时的模型自信度
data.head()
2. 高斯核函数
①导入各种第三方库
import matplotlib.pyplot as plt
from sklearn import svm
import numpy as np
import pandas as pd
import seaborn as sns
import scipy.io as sio
②定义高斯核
高斯核函数为:
K
(
x
i
,
x
j
)
=
e
−
(
x
i
−
x
j
)
2
2
σ
2
K(x_i, x_j)=e^{-\frac{(x_i-x_j)^2}{2\sigma^2}}
K(xi,xj)=e−2σ2(xi−xj)2
def gaussian_kernel(x1, x2, sigma):
return np.exp(-np.power(x1-x2, 2).sum() / (2 * (sigma ** 2)))
③示例:计算x1与x2的高斯核函数值
x1 = np.array([1,2,1])
x2 = np.array([0,4,-1])
sigma = 2
gaussian_kernel(x1, x2, sigma) # 0.32465246735834974
④加载数据
mat = sio.loadmat('../data/ex6data2.mat')
data = pd.DataFrame(mat.get('X'), columns=['X1', 'X2'])
data['y'] = mat.get('y')
data.head() # 863条数据
⑤数据可视化展示
sns.set(context='notebook',style='darkgrid',palette='deep',font='sans-serif',font_scale=1,color_codes=True)
context:控制默认的画幅大小,分别有{paper, notebook, talk, poster}四个值。其中,poster > talk > notebook > paper。
style:控制默认样式,分别有{darkgrid, whitegrid, dark, white, ticks},你可以自行更改查看它们的不同。默认是darkgrid。
palette:预设的调色板。分别有{deep, muted, bright, pastel, dark, colorblind}等,你可以自行更改查看它们的不同。
font:设置字体。
font_scale:设置字体大小。
color_codes:不使用调色板而采用先前的 ‘r’ 等色彩缩写。
seaborn.lmplot(x, y, data, hue=None, col=None, row=None, palette=None, col_wrap=None, height=5, aspect=1, markers=’o’, sharex=True, sharey=True, hue_order=None, col_order=None, row_order=None, legend=True, legend_out=True, x_estimator=None, x_bins=None, x_ci=’ci’, scatter=True, fit_reg=True, ci=95, n_boot=1000, units=None, seed=None, order=1, logistic=False, lowess=False, robust=False, logx=False, x_partial=None, y_partial=None, truncate=True, x_jitter=None, y_jitter=None, scatter_kws=None, line_kws=None, size=None)
X, y:此参数是数据中的列名。
hue, col, row:数据的定义子集,这些子集将绘制在网格的不同面上。
data:此参数是DataFrame。
fit_reg:若为True,则估计并绘制与x和y变量相关的回归模型。
scatter_kws:词典。
sns.set(context='notebook', style='white', palette=sns.diverging_palette(240,10,n=2)) # 设置图的背景与格式
sns.lmplot('X1', 'X2', hue='y', data=data, size=5, fit_reg=False, scatter_kws={"s":10}) # 将散点图绘制到FacetGrid上
plt.show()
⑥设置SVM参数并训练模型
sklearn.svm.SVC(C=1.0, kernel='rbf', degree=3, gamma='auto', coef0=0.0, shrinking=True, probability=False,
tol=0.001, cache_size=200, class_weight=None, verbose=False, max_iter=-1,
decision_function_shape=None,random_state=None)
参数:
C:惩罚参数,C越大,相当于惩罚松弛变量,希望松弛变量接近0,即对误分类的惩罚增大,趋向于对训练集全分对的情况,这样对训练集测试时准确率很高,但泛化能力弱。C值小,对误分类的惩罚减小,允许容错,将他们当成噪声点,泛化能力较强。
kernel:核函数,默认是rbf,可以是‘linear’, ‘poly’, ‘rbf’, ‘sigmoid’, ‘precomputed’
degree:多项式poly函数的维度,默认是3,选择其他核函数时会被忽略。
gamma: ‘rbf’,‘poly’ 和‘sigmoid’的核函数参数。默认是’auto’,则会选择1/n_features。
cef0:核函数的常数项。对于‘poly’和 ‘sigmoid’有用。
probability:是否采用概率估计.默认为False。
shrinking:是否采用shrinking heuristic方法,默认为true
tol:停止训练的误差值大小,默认为1e-3。
cache_size:核函数cache缓存大小,默认为200。
class_weight:类别的权重,字典形式传递。设置第几类的参数C为weight*C(C-SVC中的C)。
verbose:是否允许冗余输出。
max_iter:最大迭代次数。-1为无限制。
decision_function_shape:‘ovo’, ‘ovr’ or None, default=None3。
random_state:数据洗牌时的种子值,int值。
svc = svm.SVC(C=100, kernel='rbf', gamma=10, probability=True)
svc.fit(data[['X1', 'X2']], data['y'])
svc.score(data[['X1', 'X2']], data['y']) # 0.9698725376593279
⑦结果可视化
# svc.predict_proba二分类则得到分别表示预测为0的概率和预测为1的概率
predict_prob = svc.predict_proba(data[['X1', 'X2']])[:, 0]
fig, ax = plt.subplots(figsize=(8, 6))
ax.scatter(data['X1'], data['X2'], s=30, c=predict_prob, cmap='Reds')
plt.show()
3. 寻找最优参数
①导入第三方库
from sklearn import svm
from sklearn.model_selection import GridSearchCV
from sklearn import metrics
import numpy as np
import pandas as pd
import scipy.io as sio
②加载数据
mat = sio.loadmat('../data/ex6data3.mat')
training = pd.DataFrame(mat.get('X'), columns=['X1', 'X2'])
training['y'] = mat.get('y')
cv = pd.DataFrame(mat.get('Xval'), columns=['X1', 'X2'])
cv['y'] = mat.get('yval')
training.head()
③设置候选C和gamma的参数组合
candidate = [0.01, 0.03, 0.1, 0.3, 1, 3, 10, 30, 100]
# 设置gamma遵守sklearn参数名
combination = [(C, gamma) for C in candidate for gamma in candidate]
len(combination) # 81
④手动寻找最优参数组合
search = []
for C, gamma in combination:
svc = svm.SVC(C=C, gamma=gamma)
svc.fit(training[['X1', 'X2']], training['y'])
search.append(svc.score(cv[['X1', 'X2']], cv['y']))
best_score = search[np.argmax(search)]
best_param = combination[np.argmax(search)]
best_score, best_param # 0.965, (0.3, 100)
⑤展示准确率、召回率和f1值
best_svc = svm.SVC(C=100, gamma=0.3)
best_svc.fit(training[['X1', 'X2']], training['y'])
ypred = best_svc.predict(cv[['X1', 'X2']])
print(metrics.classification_report(cv['y'], ypred))
⑥利用GridSearch寻找最优参数
# 介绍GridSearchCV
sklearn.model_selection.GridSearchCV(estimator, param_grid, *, scoring=None, n_jobs=None, iid='deprecated',
refit=True, cv=None, verbose=0, pre_dispatch='2*n_jobs', error_score=nan,
return_train_score=False)
参数:
estimator:选择使用的分类器,并且传入除需要确定最佳的参数之外的其他参数。每一个分类器都需要一个scoring参数,或者score方法
estimator = RandomForestClassifier(min_sample_split=100,min_samples_leaf = 20,max_depth = 8,max_features = 'sqrt' , random_state =10)
param_grid:需要优化的参数的取值,值为字典或者列表
scoring=None:模型评价标准,默认为None,这时需要使用score函数;或者如scoring = ‘roc_auc’,根据所选模型不同,评价准则不同,字符串(函数名),或是可调用对象,需要其函数签名,形如:scorer(estimator,X,y);如果是None,则使用estimator的误差估计函数。
n_jobs = 1: n_jobs:并行数,默认为1,当n_jobs = -1:表示使用所有处理器(建议)。
refit=True:默认为True,程序将会以交叉验证训练集得到的最佳参数,重新对所有可能的训练集与开发集进行,作为最终用于性能评估的最佳模型参数。即在搜索参数结束后,用最佳参数结果再次fit一遍全部数据集(不用管即可)。
cv=None:交叉验证参数,默认None,使用五折交叉验证。指定fold数量,默认为5(之前版本为3),也可以是yield训练/测试数据的生成器。
属性:
cv_results_:具有键作为列标题和值作为列的dict,可以导入到DataFrame中。注意,“params”键用于存储所有参数候选项的参数设置列表。
best_estimator_:通过搜索选择的估计器,即在左侧数据上给出最高分数(或指定的最小损失)的估计器,估计器括号里包括选中的参数。如果refit = False,则不可用。
best_score_:best_estimator的最高分数
best_params_:在保存数据上给出最佳结果的参数设置
best_index_:对应于最佳候选参数设置的索引(cv_results_数组)
parameters = {'C':candidate, 'gamma':candidate}
svc = svm.SVC()
# GridSearchCV:自动调参,把参数输进去,输出最优化结果和参数(适合于小数据集)
clf = GridSearchCV(svc, parameters, n_jobs=-1)
clf.fit(training[['X1', 'X2']], training['y'])
clf.best_params_ # {'C': 30, 'gamma': 3}
clf.best_score_ # 0.9194905869324475
ypred = clf.predict(cv[['X1', 'X2']])
print(metrics.classification_report(cv['y'], ypred))
奇怪的是,它们的结果并不相同?
事实证明,GridSearch 会将部分数据作为简历,并使用它来找到最佳候选人。因此,不同结果的原因只是 GridSearch 在这里只是使用部分训练数据进行训练,因为它需要部分数据作为验证集。