目录
一、前言
支持向量机(Support Vector Machine :SVM):
二分类算法模型,数据集较小时,分类效果甚至优于神经网络。
其最大的特点在于:能够造出最大间距的决策边界,从而提高分类算法的鲁棒性。
主要用于解决模式识别领域中的数据分类问题,属于有监督学习算法的一种。
二、基于最大间隔分割数据
2.1线性模型
在二维空间上,两类点被一条直线完全分开叫做线性可分。如下图,在二维坐标下,样本空间中找到直线, 将不同类别的样本分开
上述将数据集分隔开来的直线称为分隔超平面,即。
2.2超平面
由于数据点都在二维平面上,所以此时分隔超平面就只是一条直线。但是,如果所给的数据 集是三维的,那么此时用来分隔数据的就是一个平面。显而易见,更高维的情况可以依此类推。如果数据集是1000维的,那么就需要一个999维的某某对象来对数据进行分隔。当数据集是N维时,需要一个N-1维的某某对象来对数据进行分隔。N-1维的该对象被称为超平面(hyperplane),也就是分类的决策边界。 分布在超平面一侧的所有数据都属于某个类别,而分布在另一侧的所有数据则属于另一个类别。
问题:将训练样本分开的超平面可能有很多, 哪一个好?
从二维扩展到多维空间中时,将 和 完全正确地划分开的 就成了一个超平面。为了使这个超平面更具鲁棒性,我们会去找最佳超平面,以最大间隔把两类样本分开的超平面,也称之为最大间隔超平面。
如上图,有五条直线,它们都能将数据分隔开,但是其中哪一条最好呢?我们希望找到离分隔超平面最近的点,确保它们离分隔面的距离尽可能远。(通俗来说就是:使得距离这条直线最近的点到这条直线的距离最短)这里点到分隔面的距离被称为间隔(margin)。我们需要的是间隔尽可能地大,这是因为如果犯错或者在有限数据上训练分类器的话,分类器尽可能健壮。所以,应选择”正中间”的那条直线 , 容忍性好, 鲁棒性高, 泛化能力最强,选择最大化决策边界的边缘。
2.3支持向量
如下图,支持向量(support vector)就是离分隔超平面最近的那些点。
三、算法原理
3.1线性可分支持向量机
支持向量机最简单的就是线性可分支持向量机,解决线性可分问题(能由一条线完全分为两类)。当训练数据线性可分时,通过硬间隔最大化,学习一个线性的分类器,也称为硬间隔支持向量机,可以表示为凸二次规划问题。
3.1.1算法描述
1.给定训练样本集D = ( x 1 , y 1 ) , ( x 2 , y 2 ) , . . . , ( x m , y m ) , 标签:y ∈{-1, +1}
2.假设训练样本的数据集是线性可分的。
3.学习得到分离差超平面: 。
4.对应分类决策函数:,其中sign( )为符号函数。
目标:“硬间隔”最大化
注:(w,b)是带定参数,其中w是向量,b是常数;w = ( w 1 , w 2 , . . . , w n )^T ,x1=(x1,x2,...,xn)T
(1)硬间隔、硬间隔最大化
样本空间中任意点x到超平面的距离可以写为
其中||W||为超平面的范数:常数b类似于直线方程中的截距。
二维空间中点(x, y)到直线的距离:
三维空间中点(x, y, z)到平面的距离:
支持向量:离超平面最近的几个训练样本点,使得成立
间隔:两个异类支持向量到超平面的距离和: d
硬间隔:满足所有样本都划分正确。
图中 A 点即为支持向量,由其所在直线表达式可知:
-
最优分隔超平面由支持向量完全决定。
-
因此,支持向量到分割超平面距离d为:,因此,离超平面最近的点到超平面的距离越远越好。
- 对于形状为“x”的点,必定满足的约束条件
- 对于形状为“·”的点,必定满足的约束条件。
-
相当于使用+1,-1当作类别标签,类似于逻辑回归中的0,1都是为了简化数学表达。
- 我们要求的就是距离d的最大值,于是可以转化为其等价形式:最小化
其中1/2是为了便于求导运算加上的,可简化运算。
这是一个凸二次规划问题。
(2)间隔最大化求解----拉格朗日乘子法
一般的极值优化问题的三种情况:
1.无约束条件:求导找到极值点、梯度下降: min f(x)
2.等式约束条件:拉格朗日乘子法、消元法转化问题:
min f(x)
h(x)=0
3.不等式约束条件:拉格朗日乘子法 + KKT条件:
min f(x)
g(x)<=0
h(x)=0
该约束最优化问题也就是凸优化问题。
其中目标函数 f ( x ) 和约束函数 g ( x ) 目标函数f(x)和约束函数g(x)目标函数f(x)和约束函数g(x)都是连续可微凸函数,约束函数h ( x ) h(x)h(x)是仿射函数。
当目标函数是二次函数,且约束函数g(x)是仿射函数时,上述凸最优化问题成为凸二次规划问题。
在约束最优化问题中,常常利用拉格朗日对偶性将原始问题转换为对偶问题
通过对对偶问题求解得到原问题的解:
第一步:利用拉格朗日乘子法,得到原问题的对偶问题;
第二步:再利用KKT条件,解得最优参数w,b。
二次规划的对偶问题:
优点,是对偶问题往往更容易求解,二,自然引入核函数,进而推广到非线性分类问题
首先将目标函数与约束条件构建成拉格朗日函数。为此,对每一个不等式约束引入拉格朗日乘子α。
根据拉格朗日的对偶性,原始问题极大极小值问题转换为对偶性极大极小值问题:
将拉格朗日函数L ( w , b , α ) 分别对w, b, α求偏导并令其为0(会涉及矩阵求导)。
得到:
将结果带入拉格朗日函数中,消去w,b:
求对α的极大,即得到对偶问题:
将其转化为求最小之后,便得到:
KKT条件:
由上可得,原问题的最优参数解:
3.1.2算法流程总结
1.输入:线性可分训练集
,标签:
2.输出:分离超平面和分类决策函数
①、构造并求解带约束的最优化问题:
求得最优解(对偶问题的解)
②、计算:
并选择的一个正分量,计算
③、由此求得分离超平面:,以及分类决策函数:
注:
- 只要D线性可分,那就一定可以求出一个(w,b),存在且唯一存在;若线性不可分,则无法求出。
- 一句话总结:求解SVM算法,就是在约束条件,求解的最小值
- 在决定超平面时只有支持向量起作用,其他样本点并不起作用,所以这种分类模型称为支持向量机。
- 向量的个数一般很少,所以支持向量机是由“重要的”少量的训练样本确定的
3.2、线性支持向量机
3.2.1软间隔,松弛因子
前面的讨论中,我们一直假定训练样本在样本空间中是线性可分的,
即存在一个超平面能将不同类的样本完全划分开,也就是硬间隔。
然而现实任务中往往不太可能;
因此,我们引入软间隔,允许支持向量机在某些样本上出错。
这样的线性支持向量机也成为软间隔支持向量机
硬间隔:所有样本都必须划分正确
软间隔:允许一些样本出错,也就是允许一些样本不满足
松弛系数:为了解决无法找到最大间距的分割超平面问题,引入了一个参数ε,称为松弛系数。
此时,为确保在最大化间隔,同时误分类点尽量少。对每个松弛标量付出一定的代价,所以目标优化函数变为:
其中m为数据集的个数,R为算法参数,其对应的约束条件(s.t.)也变成:
如何理解松弛系数?
我们可以把εi 理解为样本数据违反最大间距规则的程度,
对于正常样本,即满足约束条件ε=0;而对于部分违反最大间距规则的ε>0。
参数R则为惩罚参数,R值大,对误分类的惩罚增大,反之减小(以一定的步伐变化,找出最好的那个)。
可以看出,引入松弛系数类似于逻辑回归里成本函数引入正则项,目的都是为了纠正过拟合问题,让支持向量机对噪声数据有更强的适应性。
求参数也是采用拉格朗日乘子法,和上述方法步骤相同(仅多了一个惩罚因子),算法流程也基本相同。
对偶问题:
- 根据目标优化函数,通过拉格朗日乘子法得到拉格朗日函数:
- 分别对参数求偏导并令其为0:
- 将结果带入,得到对偶问题:
等价于:
3.2.2算法流程总结
1.输入:线性可分训练集
,标签:
2.输出:分离超平面和分类决策函数
①、构造并求解带约束的最优化问题:
求得最优解(对偶问题的解)
②、计算:
并选择 的一个正分量,计算
③、由此求得分离超平面:,以及分类决策函数:
注:此处的解唯一,但不一定。
3.3、非线性支持向量机与核函数
在现实任务中,原始的样本空间可能并不存在一个能正确划分两类样本的超平面,
因此,需要利用非线性模型才能很好地进行分类。
这时可以使用非线性支持向量机,主要特点就是利用核技巧(kernel trick)。
3.3.1核技巧
非线性问题往往不好求解,所以我们希望能用解线性分类问题的方法来解决该问题。
所以需要采取非线性变换,将非线性问题转变为线性问题,进而求解原来非线性问题。
核技巧就属于这样的方法:高维映射:首先使用一个变换将原空间的数据映射到新空间(更高的维度);定义从原样本空间到新空间的变换(映射)函数为φ ( x )
然后在新空间里用线性分类学习方法从训练数据中学习分类模型
注:φ ( x ) 是无限维。
也就是将样本映射到一个更高维度的特征空间中,使得其线性可分。如:
令φ(x)为将x映射后的特征向量,于是在新的特征空间中划分超平面可以表示为:
使用软间隔最大化,目标函数为:
其中w,b是模型参数。
对偶问题:
由于特征维数可能很高,直接计算ϕ ( x i )ϕ(x j)通常会很困难,因此引入了核函数。于是,我们将对偶问题重写
核函数带入之后进行求解,可得分类决策函数为:
3.3.2核函数
根据以上可知,
核函数(Kernel Function)允许SVM在原始特征空间中无法线性分割的情况下,通过映射数据到高维空间来实现线性分割。核函数的基本思想是在新的高维空间中找到一个更容易线性分割的超平面。
在SVM中,核函数可以将输入特征映射到一个更高维度的空间,从而使得在原始特征空间中非线性可分的问题在新的空间中变得线性可分。这样,SVM就能够通过一个超平面来分割数据。
常见的核函数:
1.线性核Linear:
两向量的内积,相当于没有用核。
2.多项式核Ploy:
其中d大于等于1,为多项式的次数;
d值越大,维度越高。
当d=1时,退化为线性核。
3.高斯核Rbf(常用):
其中σ 为高斯核的带宽(width);
例如,如果我们输入的特征是一维的标量,高斯核函数对应的形状是一个反钟形的曲线,该参数就是控制其宽度的。该核对应的ϕ ( x ) 是无限维的:函数可以将输入特征映射到无限多维。公式的推导会用到泰勒公式。
4.sigmoid核(常用):
其中tanh为双曲正切函数:
5.拉普拉斯核:
非线性支持向量机实现:
创建数据:
from sklearn.datasets import make_moons
X, y = make_moons(n_samples=100, noise=0.15, random_state=42)
def plot_dataset(X, y, axes):
plt.plot(X[:, 0][y==0], X[:, 1][y==0], "bs")
plt.plot(X[:, 0][y==1], X[:, 1][y==1], "g^")
plt.axis(axes)
plt.grid(True, which='both')
plt.xlabel(r"$x_1$", fontsize=20)
plt.ylabel(r"$x_2$", fontsize=20, rotation=0)
plot_dataset(X, y, [-1.5, 2.5, -1, 1.5])
plt.show()
def plot_predictions(clf,axes):
x0s = np.linspace(axes[0],axes[1],100)
x1s = np.linspace(axes[2],axes[3],100)
x0,x1 = np.meshgrid(x0s,x1s)
X = np.c_[x0.ravel(),x1.ravel()]
y_pred = clf.predict(X).reshape(x0.shape)
plt.contourf(x0,x1,y_pred,cmap=plt.cm.brg,alpha=0.2)
plot_predictions(polynomial_svm_clf,[-1.5,2.5,-1,1.5])
plot_dataset(X,y,[-1.5,2.5,-1,1.5])
from sklearn.svm import SVC
poly_kernel_svm_clf = Pipeline([
("scaler", StandardScaler()),
("svm_clf", SVC(kernel="poly", degree=3, coef0=1, C=5))
])
poly_kernel_svm_clf.fit(X, y)
plt.figure(figsize=(11, 4))
plt.subplot(121)
plot_predictions(poly_kernel_svm_clf, [-1.5, 2.5, -1, 1.5])
plot_dataset(X, y, [-1.5, 2.5, -1, 1.5])
plt.title(r"$d=3, r=1, C=5$", fontsize=18)
plt.subplot(122)
plot_predictions(poly100_kernel_svm_clf, [-1.5, 2.5, -1, 1.5])
plot_dataset(X, y, [-1.5, 2.5, -1, 1.5])
plt.title(r"$d=10, r=100, C=5$", fontsize=18)
plt.show()
添加高斯函数:
def gaussian_rbf(x, landmark, gamma):
return np.exp(-gamma * np.linalg.norm(x - landmark, axis=1)**2)
gamma = 0.3
x1s = np.linspace(-4.5, 4.5, 200).reshape(-1, 1)
x2s = gaussian_rbf(x1s, -2, gamma)
x3s = gaussian_rbf(x1s, 1, gamma)
XK = np.c_[gaussian_rbf(X1D, -2, gamma), gaussian_rbf(X1D, 1, gamma)]
yk = np.array([0, 0, 1, 1, 1, 1, 1, 0, 0])
plt.figure(figsize=(11, 4))
plt.subplot(121)
plt.grid(True, which='both')
plt.axhline(y=0, color='k')
plt.scatter(x=[-2, 1], y=[0, 0], s=150, alpha=0.5, c="red")
plt.plot(X1D[:, 0][yk==0], np.zeros(4), "bs")
plt.plot(X1D[:, 0][yk==1], np.zeros(5), "g^")
plt.plot(x1s, x2s, "g--")
plt.plot(x1s, x3s, "b:")
plt.gca().get_yaxis().set_ticks([0, 0.25, 0.5, 0.75, 1])
plt.xlabel(r"$x_1$", fontsize=20)
plt.ylabel(r"Similarity", fontsize=14)
plt.annotate(r'$\mathbf{x}$',
xy=(X1D[3, 0], 0),
xytext=(-0.5, 0.20),
ha="center",
arrowprops=dict(facecolor='black', shrink=0.1),
fontsize=18,
)
plt.text(-2, 0.9, "$x_2$", ha="center", fontsize=20)
plt.text(1, 0.9, "$x_3$", ha="center", fontsize=20)
plt.axis([-4.5, 4.5, -0.1, 1.1])
plt.subplot(122)
plt.grid(True, which='both')
plt.axhline(y=0, color='k')
plt.axvline(x=0, color='k')
plt.plot(XK[:, 0][yk==0], XK[:, 1][yk==0], "bs")
plt.plot(XK[:, 0][yk==1], XK[:, 1][yk==1], "g^")
plt.xlabel(r"$x_2$", fontsize=20)
plt.ylabel(r"$x_3$ ", fontsize=20, rotation=0)
plt.annotate(r'$\phi\left(\mathbf{x}\right)$',
xy=(XK[3, 0], XK[3, 1]),
xytext=(0.65, 0.50),
ha="center",
arrowprops=dict(facecolor='black', shrink=0.1),
fontsize=18,
)
plt.plot([-0.1, 1.1], [0.57, -0.1], "r--", linewidth=3)
plt.axis([-0.1, 1.1, -0.1, 1.1])
plt.subplots_adjust(right=1)
plt.show()
理论情况下会得到怎么维特征呢?可以对每一个实例(样本数据点)创建一个地标,此时会将m*n的训练集转换成m*m的训练集。
使用了不同的核函数参数来创建多个支持向量机分类器
- 增加 gamma γ 使高斯曲线变窄,因此每个实例的影响范围都较小:决策边界最终变得更不规则,在个别实例周围摆动。
- 减少 gamma γ 使高斯曲线变宽,因此实例具有更大的影响范围,并且决策边界更加平滑。
rbf_kernel_svm_clf = Pipeline((
("scaler",StandardScaler()),
("svm_clf",SVC(kernel="rbf",gamma=5,C=0.001))
))
rbf_kernel_svm_clf.fit(X,y)
from sklearn.svm import SVC
gamma1, gamma2 = 0.1, 5
C1, C2 = 0.001, 1000
hyperparams = (gamma1, C1), (gamma1, C2), (gamma2, C1), (gamma2, C2)
svm_clfs = []
for gamma, C in hyperparams:
rbf_kernel_svm_clf = Pipeline([
("scaler", StandardScaler()),
("svm_clf", SVC(kernel="rbf", gamma=gamma, C=C))
])
rbf_kernel_svm_clf.fit(X, y)
svm_clfs.append(rbf_kernel_svm_clf)
plt.figure(figsize=(11, 7))
for i, svm_clf in enumerate(svm_clfs):
plt.subplot(221 + i)
plot_predictions(svm_clf, [-1.5, 2.5, -1, 1.5])
plot_dataset(X, y, [-1.5, 2.5, -1, 1.5])
gamma, C = hyperparams[i]
plt.title(r"$\gamma = {}, C = {}$".format(gamma, C), fontsize=16)
plt.show()
四、实验之SVM手写体识别
4.1实验步骤
示例:基于SVM的数字识别流程步骤
(1) 收集数据:提供的文本文件。
(2) 准备数据:基于二值图像构造向量。
(3) 分析数据:对图像向量进行目测。
(4) 训练算法:采用两种不同的核函数,并对径向基核函数采用不同的设置来运行SMO算法。
(5) 测试算法:编写一个函数来测试不同的核函数并计算错误率。
4.2数据集准备
4.3代码实现
from numpy import *
# 随机选择alpha
def selectJrand(i, m):
j = i # 选择一个不等于i的j
while (j == i): # 只要函数值不等于输入值i,函数就会进行随机选择
j = int(random.uniform(0, m))
return j
# 修剪alpha
def clipAlpha(aj, H, L): # 用于调整大于H或小于L的alpha值
if aj > H:
aj = H
if L > aj:
aj = L
return aj
# 类
class optStruct:
def __init__(self, dataMatIn, classLabels, C, toler, kTup): # 使用参数初始化结构
self.X = dataMatIn # 数据矩阵
self.labelMat = classLabels # 数据标签
self.C = C # 松弛变量
self.tol = toler # 容错率
self.m = shape(dataMatIn)[0] # 数据矩阵行数m
self.alphas = mat(zeros((self.m, 1))) # 根据矩阵行数初始化alpha参数为0
self.b = 0 # 初始化b参数为0
self.eCache = mat(zeros((self.m, 2))) # 第一列是有效标志
self.K = mat(zeros((self.m,self.m))) # 初始化核K
for i in range(self.m): # 计算所有数据的核K
self.K[:,i] = kernelTrans(self.X, self.X[i,:], kTup)
# 通过核函数将数据转换更高维的空间
def kernelTrans(X, A, kTup):
m,n = shape(X)
K = mat(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 = exp(K/(-1*kTup[1]**2)) #计算高斯核K
else: raise NameError('核函数无法识别')
return K
# 计算误差
def calcEk(oS, k):
fXk = float(multiply(oS.alphas, oS.labelMat).T*oS.K[:,k] + oS.b)
Ek = fXk - float(oS.labelMat[k])
return Ek
# 内循环启发方式
def selectJ(i, oS, Ei):
maxK = -1; maxDeltaE = 0; Ej = 0 # 初始化
oS.eCache[i] = [1, Ei] # 选择给出最大增量E的alpha
validEcacheList = nonzero(oS.eCache[:, 0].A)[0]
if (len(validEcacheList)) > 1:
for k in validEcacheList: # 循环使用有效的Ecache值并找到使delta E最大化的值
if k == i: continue # 如果k对于i,不计算i
Ek = calcEk(oS, k) # 计算Ek的值
deltaE = abs(Ei - Ek) # 计算|Ei-Ek|
if (deltaE > maxDeltaE): # 找到maxDeltaE
maxK = k; maxDeltaE = deltaE; Ej = Ek
return maxK, Ej
else: # 在这种情况下(第一次),没有任何有效的eCache值
j = selectJrand(i, oS.m) # 随机选择alpha_j的索引值
Ej = calcEk(oS, j)
return j, Ej
# 计算Ek并更新误差缓存
def updateEk(oS, k): # 任何alpha更改后,更新缓存中的新值
Ek = calcEk(oS, k)
oS.eCache[k] = [1, Ek]
# 优化的SMO算法
def innerL(i, oS):
Ei = calcEk(oS, i) # 计算误差Ei
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)):
# 使用内循环启发方式选择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("j not moving enough"); 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.K[i, i] - oS.labelMat[j] * (oS.alphas[j] - alphaJold) * oS.K[i, j]
b2 = oS.b - Ej - oS.labelMat[i] * (oS.alphas[i] - alphaIold) * oS.K[i, j] - oS.labelMat[j] * (oS.alphas[j] - alphaJold) * oS.K[j, j]
# 步骤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,kTup=('lin', 0)):
oS = optStruct(mat(dataMatIn),mat(classLabels).transpose(), C, toler, kTup)# 初始化
iter = 0 # 初始化迭代次数为0
entireSet = True; alphaPairsChanged = 0
while (iter < maxIter) and ((alphaPairsChanged > 0) or (entireSet)): # 超过最大迭代次数或者遍历整个数据集都alpha也没有更新,则退出循环
alphaPairsChanged = 0
if entireSet:
for i in range(oS.m): # 遍历整个数据集
alphaPairsChanged += innerL(i, oS) # 使用优化的SMO算法
print("全样本遍历,第%d次迭代 样本:%d, alpha优化次数:%d" % (iter, i, alphaPairsChanged))
iter += 1
else: # 遍历非边界值
nonBoundIs = nonzero((oS.alphas.A > 0) * (oS.alphas.A < C))[0] # 遍历不在边界0和C的alpha
for i in nonBoundIs:
alphaPairsChanged += innerL(i, oS)
print("非边界遍历,第%d次迭代 样本:%d, alpha优化次数:%d" % (iter, i, alphaPairsChanged))
iter += 1
if entireSet:
entireSet = False # 切换整个集合循环
elif (alphaPairsChanged == 0):
entireSet = True
print("迭代次数: %d" % iter)
return oS.b, oS.alphas
# 图像转换为向量
def img2vector(filename):
returnVect = zeros((1, 1024))
fr = open(filename)
for i in range(32):
lineStr = fr.readline()
for j in range(32):
returnVect[0, 32 * i + j] = int(lineStr[j])
return returnVect
# 加载图像数据
def loadImages(dirName):
from os import listdir
hwLabels = []
trainingFileList = listdir(dirName) # 加载训练集
m = len(trainingFileList)
trainingMat = zeros((m, 1024))
for i in range(m):
fileNameStr = trainingFileList[i]
fileStr = fileNameStr.split('.')[0]
classNumStr = int(fileStr.split('_')[0])
if classNumStr == 9:
hwLabels.append(-1)
else:
hwLabels.append(1)
trainingMat[i, :] = img2vector('%s/%s' % (dirName, fileNameStr))
return trainingMat, hwLabels
# 测试
def testDigits(kTup=('rbf', 10)):
dataArr, labelArr = loadImages('trainingDigits')
b, alphas = smoP(dataArr, labelArr, 200, 0.0001, 10000, kTup)
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, :], kTup)
predict = kernelEval.T * multiply(labelSV, alphas[svInd]) + b
if sign(predict) != sign(labelArr[i]): errorCount += 1
print("训练集错误率: %f" % (float(errorCount) / m))
dataArr, labelArr = loadImages('testDigits')
errorCount = 0
datMat = mat(dataArr);
labelMat = mat(labelArr).transpose()
m, n = shape(datMat)
for i in range(m):
kernelEval = kernelTrans(sVs, datMat[i, :], kTup)
predict = kernelEval.T * multiply(labelSV, alphas[svInd]) + b
if sign(predict) != sign(labelArr[i]): errorCount += 1
print("测试错误率: %f" % (float(errorCount) / m))
五、SVM算法总结
优点:
• 使用核函数可以向高维空间进行映射,解决非线性的分类;
• 分类思想很简单:将样本与决策面的间隔最大化;
• 分类效果较好,计算开销不大;
• SVM 是一种有坚实理论基础的小样本学习方法
缺点:
• 对缺失数据敏感,对参数调节和核函数的选择敏感;
• 对大规模数据训练比较困难
对偶问题以及KKT条件
对偶问题就是使求解更加高效且目标函数值不变,通过先消去w,b,
得到关于α的函数,然后单独计算 α,通过得到的α反求w,b,最后获得超平面的参数
相比于先对α的不等式约束进行计算,对偶的方式使得计算更加便捷。
另外KKT条件就是在约束下求得目标函数极值时αi满足的条件,只有满足了kkt条件,才算是满足了目标函数和约束函数,因此下面描述的计算迭代算法也是基于KKT条件,通过不断修改不满足KKT条件的α,使其满足KKT条件,从而求出目标函数的最优值。
SVM算法和Logstic回归的区别与联系
区别:
模型形式:
SVM是一种非概率的分类模型,它寻找一个超平面,使得不同类别的数据点在超平面两侧,并最大化它们到超平面的间隔。
逻辑回归是一种概率模型,它利用Logistic函数(Sigmoid函数)将输入映射到0到1之间的概率,然后根据阈值进行分类。
决策边界:
SVM的决策边界是由支持向量决定的,即离超平面最近的数据点。
逻辑回归的决策边界是根据概率模型计算得出的,通常是一个线性决策边界。
损失函数:
SVM使用了Hinge Loss作为损失函数,旨在最大化分类间隔。
逻辑回归使用交叉熵损失函数,通过最大似然估计来优化模型。
处理离群值:
SVM对离群值比较敏感,因为它们可能成为支持向量影响决策边界。
逻辑回归相对较为鲁棒,离群值的影响通常较小。
应用领域:
SVM在维度较高的数据和非线性问题上表现较好,特别适用于图像分类、文本分类等。
逻辑回归通常用于概率建模和二分类问题,也可用于多分类
联系:
二分类问题:
SVM和逻辑回归都可以用于解决二分类问题。
优化算法:
SVM和逻辑回归都使用梯度下降等优化算法来最小化损失函数。
正则化:
二者都可以通过正则化项来控制模型的复杂度,防止过拟合。
(SVM引入松弛系数类似于逻辑回归里成本函数引入正则项)
可解释性:
逻辑回归的输出可以解释为概率,而SVM的输出是一个距离超平面的函数,解释相对较为复杂。
扩展:
支持向量机可以通过核函数处理非线性问题,逻辑回归在非线性问题上需要额外的特征工程。