本篇博客主要介绍支持向量机中的SMO算法,相较于上篇博客中利用二次规划工具求解的方法,SMO算法更为高效,能够处理更大规模的数据集。在这篇博客中,我们将会讲解SMO算法的原理、主要流程以及代码实现。SMO算法是一种基于启发式的方法,能够高效地求解支持向量机模型参数。我们将会讨论SMO算法的主要思想以及每一步的具体实现过程,并且提供Python代码实现,帮助读者更好地理解算法的实现细节。在下一节中,我们将会开始介绍SMO算法的原理和实现过程。
SMO算法原理
SMO算法是一种基于启发式的方法,用于求解支持向量机模型的参数。其核心思想是将大优化问题分解为多个小优化问题来求解。在这个过程中,每次选择两个变量进行优化,而将其他变量视为常数。这种方法可以减小每次优化的规模,从而提高算法的效率。
具体来说,SMO算法的每一步都会选取两个变量进行优化。这两个变量分别为违反KKT条件最严重的样本点,即第一个变量需要满足0<α< C,而第二个变量需要满足- C<α<0。通过求解这两个变量的最优值,可以更新对应的模型参数。
SMO算法流程
SMO算法的具体流程如下:
- 初始化α、b为0向量和0,选择第一个变量;
- 选择第二个变量,并通过内循环选择优化的变量;
- 通过解析公式计算新的α2;
- 对α2进行剪辑;
- 更新α1,α2,b;
- 如果满足停机条件,则退出循环;否则,继续进行优化。
上述流程中,第2步和第3步的内循环是SMO算法的关键部分,它可以通过启发式方法快速地选择需要优化的变量。
SMO算法代码实现
import numpy as np
from sklearn.datasets import make_blobs
import matplotlib.pyplot as plt
%matplotlib inline
from sklearn.datasets import make_gaussian_quantiles
X, y = make_gaussian_quantiles(n_samples=200, n_features=2, n_classes=2, mean=[1,2],cov=2,random_state=222)
plt.scatter(X[:, 0], X[:, 1], marker='o', c=y)
class SVM_SMO:
def __init__(self,C=1,sigma=1,max_iter=100000):
self.C = C
self.sigma = sigma
self.max_iter = max_iter
def _gaussian(self,x,z):
return np.exp(-0.5*np.sum((x-z)**2)/(sigma**2))
def _g(self,i):
return np.sum(self.alpha*self.y*self.K[i])+self.b
def _E(self,i):
return self._g(i) - self.y[i]
def _KKT(self,i):
y_g = self._g(i)*self.y[i]
if appro_equal(self.alpha[i],0):
return y_g >= 1
elif 0 < self.alpha[i] < self.C:
return appro_equal(y_g,1)
else:
return y_g <= 1
def fit(self,X,y):
self.alpha = np.random.rand(len(y))
self.b = 0
self.X = X
self.y = y
n_samples,n_features = X.shape
self.K = np.zeros([n_samples,n_samples])
for i in range(n_samples):
for j in range(i,n_samples):
K_ij = gaussian(X[i],X[j])
self.K[i][j] = K_ij
self.K[j][i] = K_ij
iter_ = 0
while iter_ <= self.max_iter:
for i in range(len(self.alpha)):
for j in range(i,len(self.alpha)):
self.update(i,j)
iter_ += 1
def predict(self,X):
preds = []
for x in tqdm(X):
Ks = np.array([self._gaussian(x,self.X[i]) for i in range(len(self.X))])
pred = np.sum(self.alpha*Ks*self.y)+self.b
pred = 1 if pred >= 0 else -1
preds.append(pred)
return np.array(preds)
def score(self,X,y):
return np.sum(self.predict(X)==y) / len(y)
def update(self,i,j):
# 如果i==j,直接返回
if i == j:
return
# 保存旧值
a_i,a_j = self.alpha[i],self.alpha[j]
# 计算a_j的上下界
L = max(0,a_j-a_i) if self.y[i]!=self.y[j] else max(0,a_j+a_i-self.C)
H = min(self.C,self.C+a_j-a_i) if self.y[i]!=self.y[j] else min(self.C,a_i+a_j)
# 计算a_j未经剪辑的值
eta = self.K[i][j]+self.K[j][j]-2*self.K[i][j]
if eta <= 0:
return
a_j_unc = a_j + y[j]*(self._E(i)-self._E(j))/eta
# 计算a_j_new,a_i_new
if a_j_unc < L:
a_j_new = L
elif a_j_unc > H:
a_j_new = H
else:
a_j_new = a_j_unc
a_i_new = a_i + y[i]*y[j]*(a_j-a_j_new)
# 更新a_i和a_j
self.alpha[i] = a_i_new
self.alpha[j] = a_j_new
# 更新b_new
b_i_new = self.y[i] - np.sum(self.alpha*self.y*self.K[i])
b_j_new = self.y[j] - np.sum(self.alpha*self.y*self.K[j])
## 不管 a_i 和 a_j 怎样,b_new 更新为 b_i_new和b_j_new的一半都是合理的
self.b = 0.5*(b_i_new + b_j_new)
svc = SVM_SMO()
svc.fit(X,y)
y[y==0] = -1
svc.score(X,y)
上面的代码实现了使用SMO算法训练支持向量机的过程,以下是每个函数的主要含义和对应的代码:
-
_gaussian
: 用于计算高斯核函数,其中x和z是输入的向量,sigma是高斯核函数的参数。代码实现如下:
def _gaussian(self,x,z):
return np.exp(-0.5*np.sum((x-z)**2)/(sigma**2))
-
_g
: 用于计算决策函数的值,其中i是输入的样本点的索引。代码实现如下:
def _g(self,i):
return np.sum(self.alpha*self.y*self.K[i])+self.b
-
_E
: 用于计算样本点i的预测误差,其中i是输入的样本点的索引。代码实现如下:
def _E(self,i):
return self._g(i) - self.y[i]
在_KKT
函数中,我们通过计算样本点的决策函数值和预测值的乘积来判断其是否满足KKT条件。如果满足KKT条件,则函数返回True
;否则,返回False
。具体来说,如果,则判断是否大于等于1;如果,则判断是否等于1;如果,则判断是否小于等于1。如果上述三个条件都不满足,则样本点违反了KKT条件,函数返回False
。
def _KKT(self,i):
y_g = self._g(i)*self.y[i]
if appro_equal(self.alpha[i],0):
return y_g >= 1
elif 0 < self.alpha[i] < self.C:
return appro_equal(y_g,1)
else:
return y_g <= 1
其中,appro_equal
是一个辅助函数,用于判断两个浮点数是否相等。在实际计算中,由于浮点数的精度问题,两个看似相等的浮点数可能存在微小的差异。因此,在判断两个浮点数是否相等时,我们需要引入一定的误差范围。
def _KKT(self,i):
y_g = self._g(i)*self.y[i]
if appro_equal(self.alpha[i],0):
return y_g >= 1
elif 0 < self.alpha[i] < self.C:
return appro_equal(y_g,1)
else:
return y_g <= 1
fit
: 用于训练支持向量机模型,其中X和y分别是输入的训练数据和标签。我们首先对参数进行初始化。接下来,我们通过计算核函数矩阵K,以及选择需要优化的变量,来不断更新模型参数。在每次迭代中,我们需要计算每个样本点的预测误差,并根据其是否满足KKT条件来判断是否需要进行优化。如果所有样本点都满足KKT条件,则停止迭代。
-
alpha
和b
是模型参数,分别表示样本点的权重和偏置。 -
X
和y
是训练数据和标签。 -
K
是核函数矩阵,用于计算样本点之间的相似度。 -
iter_
表示当前迭代的次数。
while iter_ <= self.max_iter:
for i in range(len(self.alpha)):
for j in range(i,len(self.alpha)):
self.update(i,j)
iter_ += 1
在内循环中,我们首先选择需要优化的变量,并计算其未经剪辑的值。接下来,我们根据上下界和未经剪辑的值,计算出新的值,并更新模型参数。最后,我们计算新的偏置b。具体来说,我们可以更新偏置b的值为两个样本点的预测误差之间的平均值。
def update(self,i,j):
# 如果i==j,直接返回
if i == j:
return
# 保存旧值
a_i,a_j = self.alpha[i],self.alpha[j]
# 计算a_j的上下界
L = max(0,a_j-a_i) if self.y[i]!=self.y[j] else max(0,a_j+a_i-self.C)
H = min(self.C,self.C+a_j-a_i) if self.y[i]!=self.y[j] else min(self.C,a_i+a_j)
# 计算a_j未经剪辑的值
eta = self.K[i][j]+self.K[j][j]-2*self.K[i][j]
if eta <= 0:
return
a_j_unc = a_j + y[j]*(self._E(i)-self._E(j))/eta
# 计算a_j_new,a_i_new
if a_j_unc < L:
a_j_new = L
elif a_j_unc > H:
a_j_new = H
else:
a_j_new = a_j_unc
a_i_new = a_i + y[i]*y[j]*(a_j-a_j_new)
# 更新a_i和a_j
self.alpha[i] = a_i_new
self.alpha[j] = a_j_new
# 更新b_new
b_i_new = self.y[i] - np.sum(self.alpha*self.y*self.K[i])
b_j_new = self.y[j] - np.sum(self.alpha*self.y*self.K[j])
## 不管 a_i 和 a_j 怎样,b_new 更新为 b_i_new和b_j_new的一半都是合理的
self.b = 0.5*(b_i_new + b_j_new)
-
predict
: 用于对输入的测试数据进行预测,其中X是输入的测试数据。代码实现如下:
def predict(self,X):
preds = []
for x in tqdm(X):
Ks = np.array([self._gaussian(x,self.X[i]) for i in range(len(self.X))])
pred = np.sum(self.alpha*Ks*self.y)+self.b
pred = 1 if pred >= 0 else -1
preds.append(pred)
return np.array(preds)
-
score
: 用于计算模型在输入的数据上的准确率,其中X和y分别是输入的数据和标签。代码实现如下:
def score(self,X,y):
return np.sum(self.predict(X)==y) / len(y)
总结
本文介绍了使用SMO算法实现支持向量机的过程。我们首先介绍了高斯核函数的计算方法,然后讨论了决策函数、预测误差和KKT条件的计算方法。接下来,我们介绍了SMO算法的详细实现过程,包括参数的初始化、核函数矩阵的计算、优化变量的选择和更新、以及偏置的更新。最后,我们通过计算模型在输入数据上的准确率来评估模型的性能。
支持向量机是一种强大的分类器,它可以在高维空间中找到最优的超平面来分隔不同类别的样本点。使用SMO算法实现支持向量机可以有效地解决大规模数据集的分类问题。