文章目录
第六章 支持向量机
引言
支持向量机(Support (Vector Machines ,SVM)
序列最小优化 ( Sequential Minimal Optimization, SMO)
SVM有很多实现,但是其中最流行的一种实现是序列最小优化
SVM优缺点
优点:泛化错误率低,计算开销不大,结果易解释。
缺点:对参数调节和核函数的选择敏感,原始分类器不加修改仅适用于处理二类问题。
适用数据类型:数值型和标称型数据
SVM的一般流程
- 收集数据 :可以使用任意方法。
- 准备数据:需要数值型数据。
- 分析数据:有助于可视化分隔超平面。
- 训练算法:SVM的大部分时间都源自训练,该过程主要实现两个参数的调优。
- 测试算法:十分简单的计算过程就可以实现。
- 使用算法:几乎所有分类问题都可以使用SVM,值得一提的是,SVM本身是一个二类分类器,对多类问题应用SVM需要对代码做一些修改。
该流程与之学习的算法的流程是一样的。
6.1基于最大间隔分隔数据
超平面:
是分类的决策边界。分布在超平面一侧的所有数据都属于某个类别,而分布在另一侧的所有数据则属于另一个类别。
分类器分类方式:
如果数据点离决策边界越远,那么其最后的预测结果也就越可信。
间隔(margin)
点到分隔面的距离被称为间隔
我们希望间隔尽可能地大,这是因为如果我们犯错或者在有限数据上训练分类器的话,我们希望分类器尽可能健壮。
支持向量(support.vector)
离分隔超平面最近的那些点。
6.2 寻找最大间隔
分隔超平面的形式可以表示为: W T X + b W^TX+b WTX+b
计算点A到到分隔超平面的距 离,就必须给出点到分隔面的 法线或垂线的长度,该值为
∣
W
T
X
+
b
∣
/
∣
W
∣
\mid W^TX+b\mid/\mid W\mid
∣WTX+b∣/∣W∣
常数b类似于Logistic回归中的截距
W
0
W_0
W0。
向量W和常数b一起描述了所给数据的分隔线或超平面。
点A到分隔平面的距离就是该点到分隔面的法线长度
分类器求解的优化问题
分类器输人数据给分类器会输出一个类别标签,这相当于一个类似于的Sigmoid函数在作用。
使用类似海维赛德阶跃函数(即单位阶跃函数)的函数对 W T X + b W^TX+b WTX+b作用得到 f ( W T X + b ) f(W^TX+b) f(WTX+b),其中当 u<0 时 f(u) 输出 -1, 反之则输出+1。
这和前一章的Logistic回归有所不同,那里的类别标签是0或1。
由于 -1 和 +1 仅仅相差一个符号,方便数学上的处理。我们可以通过一个统一公式来表示间隔或者数据点到分隔超平面的距离,同时不必担心数据到底是属于-1还是+1类。
间隔通过 l a b l e ∗ ( W T X + b ) lable *(W^TX +b) lable∗(WTX+b)来计算。
为找出分类器定义中的 w 和 b 。必须找到具有最小间隔的数据点,而这些数据点也就是前面提到的支持向量。一旦找到具有最小间隔的数据点,我们就需要对该间隔最大化。
过程可以表示为:
a r g m a x w , b { m i n n ( l a b e l ∗ ( W T + b ) ) ∗ 1 ∣ ∣ w ∣ ∣ } arg \ max_{w,b} \begin{Bmatrix}min_n(label*(W^T + b))* \frac{1}{\mid |w|\mid}\end{Bmatrix} arg maxw,b{minn(label∗(WT+b))∗∣∣w∣∣1}
直接求解上述问题相当困难,所以引入约束条件
l
a
b
l
e
∗
(
W
T
X
+
b
)
≥
1
lable *(W^TX +b)\geq1
lable∗(WTX+b)≥1
优化目标函数最后可以写成:
m a x α [ ∑ i = 1 m α − 1 2 ∑ i , j = 1 m l a b e l ( i ) ∗ l a b e l ( j ) ∗ a i ∗ a j ⟨ x ( i ) , x ( j ) ⟩ ] max_\alpha[\sum^m_{i =1}\alpha - \frac{1}{2}\sum^m_{i,j=1}label^{(i)}*label^{(j)}*a_i*a_j \langle x^{(i)},x{(j)}\rangle] maxα[i=1∑mα−21i,j=1∑mlabel(i)∗label(j)∗ai∗aj⟨x(i),x(j)⟩]
其约束条件为:
α ≥ 0 ,和 ∑ i − 1 m α i ∗ l a b e l ( i ) = 0 \alpha \geq 0,和 \sum _{i-1}^m\alpha_i*label^(i) = 0 α≥0,和i−1∑mαi∗label(i)=0
上述公式成立有个假设条件:数据必须100%线性可分
但所有数据都不那么“干净”,因此引入所谓的松弛变量(slack,variable),来允许有些数据点可以处于分隔面的错误一侧。
新的约束条件则变为:
C
≥
α
≥
0
,和
∑
i
−
1
m
α
i
∗
l
a
b
e
l
(
i
)
=
0
C \geq \alpha \geq 0,和 \sum _{i-1}^m\alpha_i*label^(i) = 0
C≥α≥0,和i−1∑mαi∗label(i)=0
常数C用于控制 “最大化间隔”和“保证大部分点的函数间隔小于1.0” 这两个目标的权重。
在优化算法的实现代码中,常数C是一个参数,因此我们就可以通过调节该参数得到不同的结果。
一旦求出了所有的alpha,那么分隔超平面就可以通过这些alpha来表达。
SVM中的主要工作就是求解这些alpha。
本书只有结论没有相关推导过程。
6.3 SMO高效优化算法
SMO算法
SMO表示序列最小优化 (Sequential Minimal Optimization )
SMO算法是将大优化问题分解为多个小优化问题来求解的。
SMO算法的目标是求出一系列alpha和b 一旦求出了这些alpha, 就很容易计算出权重向量w并得到分隔超平面。
SMO算法的工作原理是:
每次循环中选择两个alpha进行优化处理。一旦找到一对合适的alpha,那么就增大其中一个同时减小另一个。这里所谓的“合适” 就是指两个alpha必须要符合一定的条件:
- 条件一就是这两个alpha必须要在间隔边界之外。
- 条件二是这两个alpha还没有进行过区间化处理或者不在边界上。
应用简化版SMO算法处理小规模数据集
SMO算法中的辅助函数
from numpy import *
from time import sleep
#加载数据
def loadDataSet(fileName):
dataMat = []; labelMat = []
fr = open(fileName)
for line in fr.readlines():
#分割数据,去掉回车,放入列表
lineArr = line.strip().split('\t')
#转换数据类型,并存到数组中,存储特征数据
dataMat.append([float(lineArr[0]), float(lineArr[1])])
#存储标签数据
labelMat.append(float(lineArr[2]))
return dataMat,labelMat
#随机选择另外一个alpha的下标,
def selectJrand(i,m):
j=i
while (j==i):
#在0-m中选择一个数作为下标
j = int(random.uniform(0,m))
return j
#调整大于H和小于L的值
def clipAlpha(aj,H,L):
if aj > H:
aj = H
if L > aj:
aj = L
return aj
fileName = r"./Ch06/testSet.txt "
datArr,labelArr = loadDataSet(fileName)
print(datArr,end='\n')
print(labelArr)
输出结果如下
上述代码主要功能是将文本文件中的数据存储到数组中,随机选择另外一个alpha的下标,调整大于和小于alpha的值。
SMO函数的伪代码大致如下:
创建一个alpha向量并将其初始化为0向量
当迭代次数小于最大迭代次数时(外循环)
对数据集中的每个数据向量(内循环):
` 如果该数据向量可以被优化:
随机选择另外一个数据向量
同时优化这两个向量
如果两个向量都不能被优化,退出内循环
如果所有向量都没被优化,增加迭代数目,继续下一次循环``
代码实现如下:
```python
#简化版SMO算法
# 5个参数 数据集、类别标签、常数0 、容错率和取消前最大的循环次数
def smoSimple(dataMatIn, classLabels, C, toler, maxIter):
#转换为numpy的mat存储,标签矩阵化后进行转置
dataMatrix = mat(dataMatIn); labelMat = mat(classLabels).transpose()
#初始化b参数,统计dataMatrix的维度
b = 0; m,n = shape(dataMatrix)
#初始化alpha参数,设为0,转为矩阵
alphas = mat(zeros((m,1)))
#初始化迭代次数
iter_num = 0
#最多迭代matIter次
while (iter_num < maxIter):
alphaPairsChanged = 0
for i in range(m):
#步骤1:计算误差Ei
#matrix.T()方法我们可以对维数大于或等于1的任何矩阵进行转置。matrix.T()方法为转置
fXi = float(multiply(alphas,labelMat).T*(dataMatrix*dataMatrix[i,:].T)) + b
#
Ei = fXi - float(labelMat[i])
#优化alpha,更设定一定的容错率。
if ((labelMat[i]*Ei < -toler) and (alphas[i] < C)) or ((labelMat[i]*Ei > toler) and (alphas[i] > 0)):
#随机选择另一个与alpha_i成对优化的alpha_j
j = selectJrand(i,m)
#步骤1:计算误差Ej
fXj = float(multiply(alphas,labelMat).T*(dataMatrix*dataMatrix[j,:].T)) + b
Ej = fXj - float(labelMat[j])
#保存更新前的aplpha值,使用深拷贝
alphaIold = alphas[i].copy(); alphaJold = alphas[j].copy();
#步骤2:计算上下界L和H
if (labelMat[i] != labelMat[j]):
L = max(0, alphas[j] - alphas[i])
H = min(C, C + alphas[j] - alphas[i])
else:
L = max(0, alphas[j] + alphas[i] - C)
H = min(C, alphas[j] + alphas[i])
if L==H: print("L==H"); continue
#步骤3:计算eta
eta = 2.0 * dataMatrix[i,:]*dataMatrix[j,:].T - dataMatrix[i,:]*dataMatrix[i,:].T - \
dataMatrix[j,:]*dataMatrix[j,:].T
if eta >= 0: print("eta>=0"); continue
#步骤4:更新alpha_j
alphas[j] -= labelMat[j]*(Ei - Ej)/eta
#步骤5:修剪alpha_j
alphas[j] = clipAlpha(alphas[j],H,L)
if (abs(alphas[j] - alphaJold) < 0.00001): print("alpha_j变化太小"); continue
#步骤6:更新alpha_i
alphas[i] += labelMat[j]*labelMat[i]*(alphaJold - alphas[j])
#步骤7:更新b_1和b_2
b1 = b - Ei- labelMat[i]*(alphas[i]-alphaIold)*dataMatrix[i,:]*dataMatrix[i,:].T - \
labelMat[j]*(alphas[j]-alphaJold)*dataMatrix[i,:]*dataMatrix[j,:].T
b2 = b - Ej- labelMat[i]*(alphas[i]-alphaIold)*dataMatrix[i,:]*dataMatrix[j,:].T - \
labelMat[j]*(alphas[j]-alphaJold)*dataMatrix[j,:]*dataMatrix[j,:].T
#步骤8:根据b_1和b_2更新b
if (0 < alphas[i]) and (C > alphas[i]): b = b1
elif (0 < alphas[j]) and (C > alphas[j]): b = b2
else: b = (b1 + b2)/2.0
#统计优化次数
alphaPairsChanged += 1
#打印统计信息
print("第%d次迭代 样本:%d, alpha优化次数:%d" % (iter_num,i,alphaPairsChanged))
#更新迭代次数
if (alphaPairsChanged == 0): iter_num += 1
else: iter_num = 0
print("迭代次数: %d" % iter_num)
#更新迭代次数
if (alphaPairsChanged == 0): iter_num += 1
else: iter_num = 0
print("迭代次数: %d" %iter_num)
return b,alphas
fileName = r"./Ch06/testSet.txt "
dataArr,labelArr = loadDataSet(fileName)
b ,alphas = smoSimple(dataArr, labelArr, 0.6, 0.001, 40)
print("b : "+str(b))
输出结果如下:
alpha_j变化太小
迭代次数: 40
迭代次数: 41
b : [[-3.79895745]]
代码篇幅有点长,有点难懂。
这个是求解关键,但是没理解
fXi = float(multiply(alphas,labelMat).T*(dataMatrix*dataMatrix[i,:].T)) + b
Ei = fXi - float(labelMat[i])
打印函数代码如下:
参考这篇文章
def showClassifer(dataMat,labelMat, w, b):
#绘制样本点
#正样本
data_plus = []
#负样本
data_minus = []
for i in range(len(dataMat)):
if labelMat[i] > 0:
data_plus.append(dataMat[i])
else:
data_minus.append(dataMat[i])
data_plus_np = array(data_plus)
data_minus_np = array(data_minus)
#正样本散点图
plt.scatter(transpose(data_plus_np)[0], transpose(data_plus_np)[1], s=30, alpha=0.7)
#负样本散点图
plt.scatter(transpose(data_minus_np)[0], transpose(data_minus_np)[1], s=30, alpha=0.7)
#绘制直线
x1 = max(dataMat)[0]
x2 = min(dataMat)[0]
a1, a2 = w
b = float(b)
a1 = float(a1[0])
a2 = float(a2[0])
y1, y2 = (-b- a1*x1)/a2, (-b - a1*x2)/a2
plt.plot([x1, x2], [y1, y2])
#找出支持向量点
for i, alpha in enumerate(alphas):
if abs(alpha) > 0:
x, y = dataMat[i]
plt.scatter([x], [y], s=150, c='none', alpha=0.7, linewidth=1.5, edgecolor='red')
plt.show()
def get_w(dataMat, labelMat, alphas):
alphas, dataMat, labelMat = array(alphas), array(dataMat), array(labelMat)
w = dot((tile(labelMat.reshape(1, -1).T, (1, 2)) * dataMat).T, alphas)
return w.tolist()
fileName = r"./Ch06/testSet.txt "
dataArr,labelArr = loadDataSet(fileName)
b ,alphas = smoSimple(dataArr, labelArr, 0.6, 0.001, 40)
print("b : "+str(b))
w = get_w(dataArr, labelArr, alphas)
showClassifer(dataArr,labelArr, w, b)
输出结果如下:
上图显示的为分类的数据散点图,直线将两者数据进行了划分,红色圆圈圈出的是支持向量。
6.4 利用完整PIatt SMO 算法加速优化
几百个点组成的小规模数据集上,简化版SMO 算法的运行是没有什么问题的,但是在更大的数据集上的运行速度就会变慢。
Platt SMO算法是通过一个外循环来选择第一个alpha值的,并且其选择过程会在两种方式之间进行交替:
- 一种方式是在所有数据集上进行单遍扫描
- 另一种方式则是在非边界alpha中实现单遍扫描。
非边界alpha指的就是那些不等于边界0或C的alpha值,实现非边界alpha值的扫描时,我们要先建立这些alpha的列表,然后再对这个表进行遍历同时,跳过那些已知的不会改变的alpha值。
在选择第一个alpha值后,算法会通过一个内循环来选择第二个alpha值。在优化过程中,会通过最大化步长的方式来获得第二个alpha值。
完整版版SMO 的支持函数
实现代码如下:
class optStruct:
def __init__(self, dataMatIn, classLabels, C, toler):
#数据矩阵
self.X = dataMatIn
#数据标签
self.labelMat = classLabels
#松弛变量
self.C = C
#容错率
self.tol = toler
self.m = shape(dataMatIn)[0]
#根据矩阵行数初始化alpha参数为0
self.alphas = mat(zeros((self.m,1)))
#初始化b参数为0
self.b = 0
#根据矩阵行数初始化虎误差缓存,第一列为是否有效的标志位,第二列为实际的误差E的值
self.eCache = mat(zeros((self.m,2)))
#计算误差
def calcEk(oS, k):
fXk = float(multiply(oS.alphas,oS.labelMat).T*(oS.X*oS.X[k,:].T) + oS.b)
Ek = fXk - float(oS.labelMat[k])
return Ek
def selectJrand(i, m):
#选择一个不等于i的j
j = i
while (j == i):
j = int(random.uniform(0, m))
return j
#随机选择alpha_j的索引值
def selectJ(i, oS, Ei):
maxK = -1; maxDeltaE = 0; Ej = 0
#根据Ei更新误差缓存
oS.eCache[i] = [1,Ei]
#返回误差不为0的数据的索引值
validEcacheList = nonzero(oS.eCache[:,0].A)[0]
#有不为0的误差
if (len(validEcacheList)) > 1:
#遍历,找到最大的Ek
for k in validEcacheList:
#不计算i,浪费时间
if k == i: continue
#计算Ek
Ek = calcEk(oS, k)
#计算|Ei-Ek|
deltaE = abs(Ei - Ek)
#找到maxDeltaE
if (deltaE > maxDeltaE):
maxK = k; maxDeltaE = deltaE; Ej = Ek
#返回maxK,Ej
return maxK, Ej
#没有不为0的误差
else:
#随机选择alpha_j的索引值
j = selectJrand(i, oS.m)
#计算Ej
Ej = calcEk(oS, j)
return j, Ej
#计算Ek,并更新误差缓存
def updateEk(oS, k):
#计算Ek
Ek = calcEk(oS, k)
#更新误差缓存
oS.eCache[k] = [1,Ek]
#优化的SMO算法
def innerL(i, oS):
#步骤1:计算误差Ei
Ei = calcEk(oS, i)
#优化alpha,设定一定的容错率。
if ((oS.labelMat[i] * Ei < -oS.tol) and (oS.alphas[i] < oS.C)) or \
((oS.labelMat[i] * Ei > oS.tol) and (oS.alphas[i] > 0)):
#使用内循环启发方式2选择alpha_j,并计算Ej
j,Ej = selectJ(i, oS, Ei)
#保存更新前的aplpha值,使用深拷贝
alphaIold = oS.alphas[i].copy(); alphaJold = oS.alphas[j].copy();
#步骤2:计算上下界L和H
if (oS.labelMat[i] != oS.labelMat[j]):
L = max(0, oS.alphas[j] - oS.alphas[i])
H = min(oS.C, oS.C + oS.alphas[j] - oS.alphas[i])
else:
L = max(0, oS.alphas[j] + oS.alphas[i] - oS.C)
H = min(oS.C, oS.alphas[j] + oS.alphas[i])
if L == H:
print("L==H")
return 0
#步骤3:计算eta
eta = 2.0 * oS.X[i,:] * oS.X[j,:].T - oS.X[i,:] * oS.X[i,:].T - oS.X[j,:] * oS.X[j,:].T
if eta >= 0:
print("eta>=0")
return 0
#步骤4:更新alpha_j
oS.alphas[j] -= oS.labelMat[j] * (Ei - Ej)/eta
#步骤5:修剪alpha_j
oS.alphas[j] = clipAlpha(oS.alphas[j],H,L)
#更新Ej至误差缓存
updateEk(oS, j)
if (abs(oS.alphas[j] - alphaJold) < 0.00001):
print("alpha_j变化太小")
return 0
#步骤6:更新alpha_i
oS.alphas[i] += oS.labelMat[j]*oS.labelMat[i]*(alphaJold - oS.alphas[j])
#更新Ei至误差缓存
updateEk(oS, i)
#步骤7:更新b_1和b_2
b1 = oS.b - Ei- oS.labelMat[i]*(oS.alphas[i]-alphaIold)*oS.X[i,:]*oS.X[i,:].T - \
oS.labelMat[j]*(oS.alphas[j]-alphaJold)*oS.X[i,:]*oS.X[j,:].T
b2 = oS.b - Ej- oS.labelMat[i]*(oS.alphas[i]-alphaIold)*oS.X[i,:]*oS.X[j,:].T - \
oS.labelMat[j]*(oS.alphas[j]-alphaJold)*oS.X[j,:]*oS.X[j,:].T
#步骤8:根据b_1和b_2更新b
if (0 < oS.alphas[i]) and (oS.C > oS.alphas[i]): oS.b = b1
elif (0 < oS.alphas[j]) and (oS.C > oS.alphas[j]): oS.b = b2
else: oS.b = (b1 + b2)/2.0
return 1
else:
return 0
# 完整的线性SMO算法
def smoP(dataMatIn, classLabels, C, toler, maxIter):
#初始化数据结构
oS = optStruct(mat(dataMatIn), mat(classLabels).transpose(), C, toler)
#初始化当前迭代次数
iter = 0
entireSet = True; alphaPairsChanged = 0
#遍历整个数据集都alpha也没有更新或者超过最大迭代次数,则退出循环
while (iter < maxIter) and ((alphaPairsChanged > 0) or (entireSet)):
alphaPairsChanged = 0
#遍历整个数据集
if entireSet:
for i in range(oS.m):
#使用优化的SMO算法
alphaPairsChanged += innerL(i,oS)
print("全样本遍历:第%d次迭代 样本:%d, alpha优化次数:%d" % (iter,i,alphaPairsChanged))
iter += 1
#遍历非边界值
else:
#遍历不在边界0和C的alpha
nonBoundIs = nonzero((oS.alphas.A > 0) * (oS.alphas.A < C))[0]
for i in nonBoundIs:
alphaPairsChanged += innerL(i,oS)
print("非边界遍历:第%d次迭代 样本:%d, alpha优化次数:%d" % (iter,i,alphaPairsChanged))
iter += 1
#遍历一次后改为非边界遍历
if entireSet:
entireSet = False
#如果alpha没有更新,计算全样本遍历
elif (alphaPairsChanged == 0):
entireSet = True
print("迭代次数: %d" % iter)
#返回SMO算法计算的b和alphas
return oS.b,oS.alphas
def calcWs(alphas,dataArr,classLabels):
X = mat(dataArr); labelMat = mat(classLabels).transpose()
m,n = shape(X)
w = zeros((n,1))
for i in range(m):
w += multiply(alphas[i]*labelMat[i],X[i,:].T)
return w
fileName = r"./Ch06/testSet.txt "
dataArr,labelArr = loadDataSet(fileName)
# b ,alphas = smoSimple(dataArr, labelArr, 0.6, 0.001, 40)
b, alphas = smoP(dataArr, labelArr, 0.6, 0.001, 40)
w = calcWs(alphas,dataArr, labelArr)
showClassifer(dataArr, labelArr, w, b)
输出结果如下:
输出结果图中,直线将两者数据进行了划分,红色圆圈圈出的是支持向量。
对比简易版算法,支持向量圈是
代码看得头大,后面再仔细分析。现在能跑起来了,๑乛◡乛๑
6.5 在复杂数据上应用核函数
核函数(kemd):
将数据转换成易于分类器理解的形式的工具。
这个数据在二维平面中很难用一条直线分隔,不过很明显,这里存在分隔方形点和圆形点的模式。
利用核函数将数据映射到高维空间
从一个特征空间到另一个特征空间的映射是通过核函数来实现的。
可以把核函数想象成一个包装器(wrapper)或者是接口(interface) , 它能把数据从某个很难处理的形式转换成为另一个较容易处理的形式.。
经过空间转换之后,我们可以在高维空间中解决线性问题,这也就等价于在低维空间中解决非线性问题。
径向基核函数
径向基函数的高斯版本公式为:
k
(
x
,
y
)
=
e
x
p
(
−
∣
∣
x
−
y
∣
∣
2
2
σ
2
)
k(x,y) = exp \left( \frac{-||x-y||^2}{2\sigma^2}\right)
k(x,y)=exp(2σ2−∣∣x−y∣∣2)
σ
\sigma
σ是用户定义的用于确定到达率(reach) 或者说函数值跌落到0的速度参数。
上述高斯核函数将数据从其特征空间映射到更高维的空间,具体来说这里是映射到一个无穷维的空间。
核转换函数实现代码如下:
# 通过核函数将数据转换更高维的空间
def kernelTrans(X, A, kTup):
m,n = np.shape(X)
K = np.mat(np.zeros((m,1)))
#线性核函数,只进行内积。
if kTup[0] == 'lin': K = X * A.T
#高斯核函数,根据高斯核函数公式进行计算
elif kTup[0] == 'rbf':
for j in range(m):
deltaRow = X[j,:] - A
K[j] = deltaRow*deltaRow.T
#计算高斯核K
K = np.exp(K/(-1*kTup[1]**2))
else: raise NameError('核函数无法识别')
return K
在测试中使用核函数
# 测试函数
def testRbf(k1 = 0.14):
#加载训练集
dataArr,labelArr = loadDataSet('./Ch06/testSetRBF.txt')
#根据训练集计算b和alphas
b,alphas = smoP(dataArr, labelArr, 200, 0.0001, 10000, ('rbf', k1))
datMat = mat(dataArr); labelMat = mat(labelArr).transpose()
#获得支持向量
svInd = nonzero(alphas.A > 0)[0]
sVs = datMat[svInd]
labelSV = labelMat[svInd];
print("支持向量个数:%d" % shape(sVs)[0])
m,n = shape(datMat)
errorCount = 0
for i in range(m):
#计算各个点的核
kernelEval = kernelTrans(sVs,datMat[i,:],('rbf', k1))
#根据支持向量的点,计算超平面,返回预测结果
predict = kernelEval.T * multiply(labelSV,alphas[svInd]) + b
#返回数组中各元素的正负符号,用1和-1表示,并统计错误个数
if sign(predict) != sign(labelArr[i]): errorCount += 1
#打印错误率
print("训练集错误率: %.2f%%" % ((float(errorCount)/m)*100))
#加载测试集
dataArr,labelArr = loadDataSet('./Ch06/testSetRBF2.txt')
errorCount = 0
datMat = mat(dataArr); labelMat = mat(labelArr).transpose()
m,n = shape(datMat)
for i in range(m):
#计算各个点的核
kernelEval = kernelTrans(sVs,datMat[i,:],('rbf', k1))
#根据支持向量的点,计算超平面,返回预测结果
predict=kernelEval.T * multiply(labelSV,alphas[svInd]) + b
#返回数组中各元素的正负符号,用1和-1表示,并统计错误个数
if sign(predict) != sign(labelArr[i]): errorCount += 1
print("测试集错误率: %.2f%%" % ((float(errorCount)/m)*100))
testRbf()
输出结果如下;
全样本遍历:第7次迭代 样本:99, alpha优化次数:0
迭代次数: 8
支持向量个数:68
训练集错误率: 4.00%
测试集错误率: 9.00%
使用参数k1=1.3达不到书中的结果,将参数k1修改到0.13时错误率能明显降低。而再往小调整参数时,错误率又开始上升。
代码没有完全理解。
6.6 示例:手写识别问题回顾
使用SVM进行手写体识别。
# 手写数字分类测试
def handwritingClassTest():
#测试集的Labels
hwLabels = []
#返回trainingDigits目录下的文件名
trainingFileList = listdir(u'./Ch06/trainingDigits')
#返回文件夹下文件的个数
m = len(trainingFileList)
#初始化训练的Mat矩阵,测试集
trainingMat = zeros((m, 1024))
#从文件名中解析出训练集的类别
for i in range(m):
#获得文件的名字
fileNameStr = trainingFileList[i]
#获得分类的数字
classNumber = int(fileNameStr.split('_')[0])
#将获得的类别添加到hwLabels中
hwLabels.append(classNumber)
#将每一个文件的1x1024数据存储到trainingMat矩阵中
trainingMat[i,:] = img2vector(u'./Ch06/trainingDigits/%s' %(fileNameStr))
clf = SVC(C=200,kernel='rbf')
clf.fit(trainingMat,hwLabels)
#返回testDigits目录下的文件列表
testFileList = listdir(u'./Ch06/testDigits')
#错误检测计数
errorCount = 0.0
#测试数据的数量
mTest = len(testFileList)
#从文件中解析出测试集的类别并进行分类测试
for i in range(mTest):
#获得文件的名字
fileNameStr = testFileList[i]
#获得分类的数字
classNumber = int(fileNameStr.split('_')[0])
#获得测试集的1x1024向量,用于训练
vectorUnderTest = img2vector('./Ch06/testDigits/%s' %(fileNameStr))
#获得预测结果
# classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3)
classifierResult = clf.predict(vectorUnderTest)
print("分类返回结果为%d\t真实结果为%d" %(classifierResult, classNumber))
if(classifierResult != classNumber):
errorCount += 1.0
print("总共错了%d个数据\n错误率为%f%%" %(errorCount, errorCount/mTest * 100))
handwritingClassTest()
img2vector函数为使用的是第二章KNN中的。
这里使用的是sklearn.svm模块svm.SVC
输出结果如下:
分类返回结果为9 真实结果为9
分类返回结果为9 真实结果为9
总共错了9个数据
错误率为0.951374%
6.7 本章小结
本章学习了新的分类器支持向量机,先是介绍了一个简化版本所实现的SMO优化过程,接着给出了完整的Platt SMO算法。
后面介绍了核函数,核技巧会将数据(有时是非线性数据)从一个低维空间映射到一个高维空间,可以将一个在低维空间中的非线性问题转换成高维空间下的线性问题来求解。
本章代码有点多(非常多),计算时是直接使用矩阵进行的运算,对于矩阵的数据操作不熟练掌握,所以在理解代码时增加了难度。本章代码只做到了实现功能,对于代码的理解基本没有做到。后面要在回头看一下代码实现。此外支持向量机原理部分也没有做到理解,核函数也没有理解。(不知道本章学了个啥::>_<:: ˃̣̣̥᷄⌓˂̣̣̥᷅)