理解SVM
线性可分数据
考虑下面的图像,它具有两种数据类型,红色和蓝色。在kNN中,对于测试数据,用来测量其与所有训练样本的距离,并以最小的距离作为样本。测量所有距离都需要花费大量时间,并且需要大量内存来存储所有训练样本。但是考虑到图像中给出的数据,是否需要那么多?
考虑另一个想法,可以找到一条线
f
(
x
)
=
a
x
1
+
b
x
2
+
c
f(x)=ax_1 + bx_2+c
f(x)=ax1+bx2+c,它将这些数据分为两个区域。当得到一个新的test_data
X
X
X时,只需将其替换为
f
(
X
)
f(X)
f(X)即可;如果
f
(
X
)
>
0
f(X) > 0
f(X)>0,则属于蓝色组,否则属于红色组。可以将此线称为“决策边界”,它非常简单且内存高效。可以将这些数据用直线(或高维超平面)一分为二的数据称为线性可分离数据。
在上图中,可以看到很多这样的行都是可能的。选哪一个?非常直观地,可以说直线应该从所有点尽可能远地经过。为什么?因为传入的数据中可能会有噪音,此噪音数据不应影响分类准确性。因此,走最远的分离线将提供更大的抗干扰能力;因此SVM要做的是找到到训练样本的最小距离最大的直线(或超平面)。如下面图像中穿过中心的粗线。
因此要找到此决策边界,需要训练数据,那么需要全部吗?并不用。仅接近边界的那些就足够了,在图像中,它们是一个蓝色填充的圆圈和两个红色填充的正方形,称其为支撑向量,通过它们的线称为支撑平面。它们足以找到决策边界,而不必关心所有数据,它有助于减少数据量。
接下来,找到了最能代表数据的前两个超平面。例如,蓝色数据由
w
T
x
+
b
0
>
−
1
w^Tx+b_0>-1
wTx+b0>−1表示,红色数据由w^Tx+b_0<-1表示,其中w是权重向量(
w
=
[
w
1
,
w
2
,
.
.
.
,
w
n
]
w=[w_1,w_2,...,w_n]
w=[w1,w2,...,wn]),x是特征向量(
x
=
[
x
1
,
x
2
,
.
.
.
,
x
n
]
x=[x_1,x_2,...,x_n]
x=[x1,x2,...,xn]),
b
0
b_0
b0是偏置。权重矢量确定决策边界的方向,而偏置点确定其位置。现在,将决策边界定义为这些超平面之间的中间,因此表示为
w
T
x
+
b
0
=
0
w^Tx + b_0 = 0
wTx+b0=0。从支撑向量到决策边界的最小距离由
d
i
s
t
a
n
c
e
s
u
p
p
o
r
t
v
e
c
t
o
r
s
=
1
∥
w
∥
distance_{support vectors}=\frac{1}{\|w\|}
distancesupportvectors=∥w∥1给出,间隔是此距离的两倍,因此需要最大化此间隔。也就是说,需要使用一些约束来最小化新函数
L
(
w
,
b
0
)
L(w,b_0)
L(w,b0),这些约束可以表示如下:
min
w
,
b
0
L
(
w
,
b
0
)
=
1
2
∣
∣
w
∣
∣
2
subject to
t
i
(
w
T
x
+
b
0
)
≥
1
∀
i
\min_{w, b_0} L(w, b_0) = \frac{1}{2}||w||^2 \; \text{subject to} \; t_i(w^Tx+b_0) \geq 1 \; \forall i
w,b0minL(w,b0)=21∣∣w∣∣2subject toti(wTx+b0)≥1∀i
其中
t
i
t_i
ti是每类的标签,
t
i
∈
[
−
1
,
1
]
t_i\in[-1,1]
ti∈[−1,1]。
非线性可分数据
考虑一些不能用直线分成两部分的数据。例如,考虑一维数据,其中’X’位于-3和+3,而’O’位于-1和+1。显然,它不是线性可分离的。但是有解决这些问题的方法,可以使用函数
f
(
x
)
=
x
2
f(x) = x^2
f(x)=x2映射此数据集,则在线性可分离的9处获得’X’,在1处获得’O’。
否则,可以将此一维数据转换为二维数据。可以使用
f
(
x
)
=
(
x
,
x
2
)
f(x) = (x, x^2)
f(x)=(x,x2)函数来映射此数据。然后,’X’变成(-3,9)和(3,9),而’O’变成(-1,1)和(1,1),这也是线性可分的。简而言之,低维空间中的非线性可分离数据更有可能在高维空间中变为线性可分离。
通常,可以将d维空间中的点映射到某个D维空间(D > d),以检查线性可分离性的可能性。有一个想法可以通过在低维输入(特征)空间中执行计算来帮助在高维(内核)空间中计算点积。可以用下面的例子来说明。
考虑二维空间中的两个点,
p
=
(
p
1
,
p
2
)
p=(p_1,p_2)
p=(p1,p2)和
q
=
(
q
1
,
q
2
)
q=(q_1,q_2)
q=(q1,q2)令ϕ为映射函数,它将二维点映射到三维空间,如下所示:
ϕ
(
p
)
=
(
p
1
2
,
p
2
2
,
2
p
1
p
2
)
ϕ
(
q
)
=
(
q
1
2
,
q
2
2
,
2
q
1
q
2
)
\phi (p) = (p_{1}^2,p_{2}^2,\sqrt{2} p_1 p_2) \phi (q) = (q_{1}^2,q_{2}^2,\sqrt{2} q_1 q_2)
ϕ(p)=(p12,p22,2p1p2)ϕ(q)=(q12,q22,2q1q2)
定义一个核函数
K
(
p
,
q
)
K(p,q)
K(p,q),该函数在两点之间做一个点积,如下所示:
K
(
p
,
q
)
=
ϕ
(
p
)
.
ϕ
(
q
)
=
ϕ
(
p
)
T
ϕ
(
q
)
=
(
p
1
2
,
p
2
2
,
2
p
1
p
2
)
.
(
q
1
2
,
q
2
2
,
2
q
1
q
2
)
=
p
1
q
1
+
p
2
q
2
+
2
p
1
q
1
p
2
q
2
=
(
p
1
q
1
+
p
2
q
2
)
2
ϕ
(
p
)
.
ϕ
(
q
)
=
(
p
.
q
)
2
\begin{aligned} K(p,q) = \phi(p).\phi(q) &= \phi(p)^T \phi(q) \\ &= (p_{1}^2,p_{2}^2,\sqrt{2} p_1 p_2).(q_{1}^2,q_{2}^2,\sqrt{2} q_1 q_2) \\ &= p_1 q_1 + p_2 q_2 + 2 p_1 q_1 p_2 q_2 \\ &= (p_1 q_1 + p_2 q_2)^2 \\ \phi(p).\phi(q) &= (p.q)^2 \end{aligned}
K(p,q)=ϕ(p).ϕ(q)ϕ(p).ϕ(q)=ϕ(p)Tϕ(q)=(p12,p22,2p1p2).(q12,q22,2q1q2)=p1q1+p2q2+2p1q1p2q2=(p1q1+p2q2)2=(p.q)2
这意味着,可以使用二维空间中的平方点积来实现三维空间中的点积;这可以应用于更高维度的空间。因此,可以从较低尺寸本身计算较高尺寸的特征;一旦将它们映射,将获得更高的空间。
除了所有这些概念之外,还存在分类错误的问题。因此,仅找到具有最大间隔的决策边界是不够的,还需要考虑分类错误的问题。有时,可能会找到间隔较少但分类错误减少的决策边界。无论如何,需要修改模型,以便它可以找到具有最大间隔但分类错误较少的决策边界。最小化标准修改为:\min |w|^2+C(分类错误的样本到其正确区域的距离),下图显示了此概念,对于训练数据的每个样本,定义一个新的参数
ξ
i
ξ_i
ξi,它是从其相应的训练样本到其正确决策区域的距离;对于那些未分类错误的样本,它们落在相应的支撑平面上,因此它们的距离为零。
因此,新的优化函数为:
min
w
,
b
0
L
(
w
,
b
0
)
=
∣
∣
w
∣
∣
2
+
C
∑
i
ξ
i
subject to
y
i
(
w
T
x
i
+
b
0
)
≥
1
−
ξ
i
and
ξ
i
≥
0
∀
i
\min_{w, b_{0}} L(w,b_0) = ||w||^{2} + C \sum_{i} {\xi_{i}} \text{ subject to } y_{i}(w^{T} x_{i} + b_{0}) \geq 1 - \xi_{i} \text{ and } \xi_{i} \geq 0 \text{ } \forall i
w,b0minL(w,b0)=∣∣w∣∣2+Ci∑ξi subject to yi(wTxi+b0)≥1−ξi and ξi≥0 ∀i
如何选择参数C?显然,这个问题的答案取决于训练数据的分布方式。尽管没有一般性的答案,但考虑以下规则是很有用的:
- C的值越大,解决方案的分类错误越少,但宽度也越小;考虑到在这种情况下,错误分类的错误的代价是巨大的。由于优化的目的是最小化参数,因此几乎没有误分类的错误。
- C的值越小,解决方案的宽度就越大,分类误差也越大;在这种情况下,最小化对总和项的考虑不多,因此它更多地集中在寻找具有大间隔的超平面上。
使用OCR手写数据集运行SVM
SVM识别手写数字过程及代码
在kNN中,直接使用像素强度作为特征向量。这次将使用定向梯度直方图(HOG)作为特征向量。
在找到HOG之前,使用其二阶矩对图像进行偏斜校正。因此,首先定义一个函数deskew()
,该函数获取一个数字图像并将其校正。
接下来,必须找到每个单元格的HOG描述符;为此,找到了每个单元在X和Y方向上的Sobel导数;然后在每个像素处找到它们的大小和梯度方向,该梯度被量化为16个整数值。将此图像划分为四个子正方形,对于每个子正方形,计算权重大小方向的直方图(16个bin);因此,每个子正方形为你提供了一个包含16个值的向量;(四个子正方形的)四个这样的向量共同为我们提供了一个包含64个值的特征向量,这是用于训练数据的特征向量。
最后与前面的情况一样,首先将大数据集拆分为单个单元格;对于每个数字,保留250个单元用于训练数据,其余250个数据保留用于测试。完整的代码如svm_digit.py所示
import cv2 as cv
import numpy as np
SZ = 20
bin_n = 16 #bins的数量
affine_flags = cv.WARP_INVERSE_MAP | cv.INTER_LINEAR
def deskew(img):
m = cv.moments(img)
if abs(m['mu02']) < 1e-2:
return img.copy()
skew = m['mu11'] / m['mu02']
M = np.float32([[1, skew, -0.5 * SZ * skew], [0, 1, 0]])
img = cv.warpAffine(img, M, (SZ, SZ), flags=affine_flags)
return img
def hog(img):
gx = cv.Sobel(img, cv.CV_32F, 1, 0)
gy = cv.Sobel(img, cv.CV_32F, 0, 1)
mag, ang = cv.cartToPolar(gx, gy)
bins = np.int32(bin_n * ang / (2 * np.pi)) #量化(0...16)中的bin值
bin_cells = bins[:10, :10], bins[10:, :10], bins[:10, 10:], bins[10:, 10:]
mag_cells = mag[:10, :10], mag[10:, :10], mag[:10, 10:], mag[10:, 10:]
hists = [np.bincount(b.ravel(), m.ravel(), bin_n) for b, m in zip(bin_cells, mag_cells)]
hist = np.hstack(hists)
return hist
img = cv.imread('./OpenCV/data/digits.png', 0)
if img is None:
raise Exception("we need the digits.png images here")
cells = [np.hsplit(row, 100) for row in np.vsplit(img, 50)]
# 前半部分是训练数据,剩余部分是测试数据
train_cells = [i[:50] for i in cells]
test_cells = [i[50:] for i in cells]
deskewed = [list(map(deskew, row)) for row in train_cells]
hogdata = [list(map(hog, row)) for row in deskewed]
trainData = np.float32(hogdata).reshape(-1, 64)
responses = np.repeat(np.arange(10), 250)[:, np.newaxis]
svm = cv.ml.SVM.create()
svm.setKernel(cv.ml.SVM_LINEAR)
svm.setType(cv.ml.SVM_C_SVC)
svm.setC(2.67)
svm.setGamma(5.383)
svm.train(trainData, cv.ml.ROW_SAMPLE, responses)
svm.save('./OpenCV/data/svm_data.dat')
deskewed = [list(map(deskew, row)) for row in test_cells]
hogdata = [list(map(hog, row)) for row in deskewed]
testData = np.float32(hogdata).reshape(-1, bin_n * 4)
result = svm.predict(testData)[1]
mask = result == responses
correct = np.count_nonzero(mask)
print('accuracy: {}'.format(correct * 100.0 / result.size))
accuracy: 93.8
结果显示有93.8%的准确性。可以为SVM的各种参数尝试不同的值,以检查是否可以实现更高的精度。
学习来源:OpenCV-Python中文文档