目录
3.1、SMO 算法的第一部分——2个变量二次规划的求解方法
1.简介
支持向量机是一种基于分类边界的方法。其基本原理是(以二维数据为例):如果训练数据分布在二维平面上的点,它们按照其分类聚集在不同的区域。基于分类边界的分类算法的目标是,通过训练,找到这些分类之间的边界(直线的――称为线性划分,曲线的――称为非线性划分)。对于多维数据(如 N 维),可以将它们视为 N 维空间中的点,而分类边界就是 N 维空间中的面,称为超面(超面比 N维空间少一维)。线性分类器使用超平面类型的边界,非线性分类器使用超曲面。
支持向量机的原理:是将低维空间的点映射到高维空间,使它们成为线性可分,再使用线性划分的原理来判断分类边界。在高维空间中是一种线性划分,而在原有的数据空间中,是一种非线性划分。SVM 在解决小样本、非线性及高维模式识别问题中表现出许多特有的优势,并能够推广应用到函数拟合等其他机器学习问题中。
2.分类
2.1、线性可分支持向量机(也称为硬间隔支持向量机)
当训练数据集可分时,通过硬间隔最大化,学得一个线性可分支持向量机。
2.2、线性支持向量机(也称为软间隔支持向量机)
当训练数据集近似线性可分时,通过软间隔最大化学得一个线性支持向量机。
2.3、非线性支持向量机
当训练数据集不可分时,通过使用核技巧以及软间隔最大化,学得一个非线性支持向量机。
2.4、函数间隔与几何间隔
2.4.1、函数间隔
2.4.2、几何间隔
2.4.3、函数间隔与几何间隔的关系
2.5、几类支持向量机的算法
2.5.1、线性可分支持向量机学习算法——最大间隔法
2.5.2、线性可分支持向量机学习算法
2.5.3、线性支持向量机学习算法
2.5.4、非线性支持向量机学习算法(核函数)
3.序列最小最优化算法——SMO 算法
3.1、SMO 算法的第一部分——2个变量二次规划的求解方法
3.2、SMO的第二部分——变量的选择方法
SMO 算法在每个子问题中选择2个变量优化,其中至少一个变量是违反KKT条件的。
<1>、第一个变量的选择
SMO 称选择第1个变量的过程中是外层循环;
外层循环在训练样本中选取违反KKT条件最严重的样本点,并将其对应的变量作为第一个变量,
具体地,检验样本点(xi, yi)是否满足KKT条件:
|
|
|
其中,
|
该检验是在允许范围内进行的,在检验过程中,外层循环首先遍历所有满足条件的样本点,即在间隔边界上的支持向量点,检验他们是否满足KKT条件,那么遍历整个训练数据集,检验他们是否满足KKT条件。
<2>、第一个变量的选择
SMO 称选择第2个变量的过程中是内层循环;
假设在外层循环中已经找到了第一个变量,现在要在内层循环中找到第二个变量,第二个变量的选择是变量
必须有足够的变化。
<3>、计算阈值b和差值Ei
3.3、SMO 算法
4、SMO 算法的Python实现
4.1、选择第一个参数并更新
首先,判断选择的第一变量是否满足KKT条件,在判断的过程中计算误差值Ei,当检查完第一个变量后,需要选择第二个变量,对于第二个变量,选择的标准是使得其改变最大。具体规程如下:
# 选择并更新参数
def choose_and_update(svm, alpha_i):
# 计算第一个样本的误差error_i
error_i = cal_error(svm, alpha_i)
# 判断选择出的第一个变量是否违反了 KKT 条件
if (svm.train_y[alpha_i]*error_i < -svm.toler) and (svm.alphas[alpha_i] < svm.C) or (svm.train_y[alpha_i]*error_i >
svm.toler) and (svm.alphas[alpha_i] > 0):
# 1.选择第二个变量
alpha_j, error_j = select_second_sample_j(svm, alpha_i, error_i)
alpha_i_old = svm.alphas[alpha_i].copy()
alpha_j_old = svm.alphas[alpha_j].copy()
# 2.计算上下界
if svm.train_y[alpha_i] != svm.train_y[alpha_j]:
L = max(0, svm.alphas[alpha_j] - svm.alphas[alpha_i])
H = min(svm.C, svm.C + svm.alphas[alpha_j] - svm.alphas[alpha_i])
else:
L = max(0, svm.alphas[alpha_j] + svm.alphas[alpha_i] - svm.C)
H = min(svm.C, svm.alphas[alpha_j] + svm.alphas[alpha_i])
if L == H:
return 0
# 3.计算 eta
eta = 2.0 * svm.kernel_mat[alpha_i, alpha_j] - svm.kernel_mat[alpha_i, alpha_i] - svm.kernel_mat[alpha_j, alpha_j]
if eta > 0:
return 0
# 4.更新 alpha_j
svm.alphas[alpha_j] -= svm.train_y[alpha_j] * (error_i - error_j)/eta
# 5.确定最终的 alpha_j
if svm.alphas[alpha_j] > H:
svm.alphas[alpha_j] = H
if svm.alphas[alpha_j] < L:
svm.alphas[alpha_j] = L
# 6. 判断是否结束
if abs(alpha_j_old - svm.alphas[alpha_j]) < 0.00001:
update_error_tmp(svm, alpha_j)
return 0
# 7.更新 alpha_i
svm.alphas[alpha_i] += svm.train_y[alpha_i] * svm.train_y[alpha_j] * (alpha_j_old - svm.alphas[alpha_j])
# 8.更新 b
b1 = svm.b - error_i - svm.train_y[alpha_i]*(svm.alphas[alpha_i]-alpha_i_old)*svm.kernel_mat[alpha_i, alpha_i]\
-svm.train_y[alpha_j]*(svm.alphas[alpha_j]-alpha_j_old)*svm.kernel_mat[alpha_i, alpha_j]
b2 = svm.b - error_j - svm.train_y[alpha_i]*(svm.alphas[alpha_i]-alpha_i_old)*svm.kernel_mat[alpha_i, alpha_j]\
-svm.train_y[alpha_j]*(svm.alphas[alpha_j]-alpha_j_old)*svm.kernel_mat[alpha_j, alpha_j]
if 0 < svm.alphas[alpha_i] < svm.C:
svm.b = b1
elif 0 < svm.alphas[alpha_j] < svm.C:
svm.b = b2
else:
svm.b = (b1 + b2)/2.0
# 9.更新 error
update_error_tmp(svm, alpha_j)
update_error_tmp(svm, alpha_i)
return 1
else:
return 0
4.2、选择第二个变量
对于第二个变量的选择,选择的标准是使得误差值改变最大。
# 选择第二个变量
def select_second_sample_j(svm, alpha_i, error_i):
svm.error_tmp[alpha_i] = [1, error_i]
candidateAlphaList = np.nonzero(svm.error_tmp[:, 0].A)[0]
maxStep = 0
alpha_j = 0
error_j = 0
if len(candidateAlphaList) > 1:
for alpha_k in candidateAlphaList:
if alpha_k == alpha_i:
continue
error_k = cal_error(svm, alpha_k)
if abs(error_k-error_i) > maxStep:
maxStep = abs(error_k - error_i)
alpha_j = alpha_k
error_j = error_k
else:
alpha_j = alpha_i
while alpha_j == alpha_i:
alpha_j = int(np.random.uniform(0, svm.n_samples))
error_j = cal_error(svm, alpha_j)
return alpha_j, error_j
4.3、误差的计算
# 计算误差
def cal_error(svm, alpha_k):
output_k = float(np.multiply(svm.alphas, svm.train_y).T * svm.kernel_mat[:, alpha_k]+svm.b)
error_k = output_k - float(svm.train_y[alpha_k])
return error_k
5、完整训练代码及数据集链接
5.1、完整代码及训练结果
# -*- coding: utf-8 -*-
# @Time : 2019-1-15 18:58
# @Author : Chaucer_Gxm
# @Email : gxm4167235@163.com
# @File : SVM_Train.py
# @GitHub : https://github.com/Chaucergit/Code-and-Algorithm
# @blog : https://blog.youkuaiyun.com/qq_24819773
# @Software: PyCharm
import numpy as np
import _pickle as pickle
class SVM:
def __init__(self, dataSet, labels, C, toler, kernel_option):
self.train_x = dataSet
self.train_y = labels
self.C = C
self.toler = toler
self.n_samples = np.shape(dataSet)[0]
self.alphas = np.mat(np.zeros((self.n_samples, 1)))
self.b = 0
self.error_tmp = np.mat(np.zeros((self.n_samples, 2)))
self.kernel_opt = kernel_option
self.kernel_mat = calc_kernel(self.train_x, self.kernel_opt)
# 核函数矩阵
def calc_kernel(train_x, kernel_option):
m = np.shape(train_x)[0]
kernel_matrix = np.mat(np.zeros((m, m)))
for i in range(m):
kernel_matrix[:, i] = cal_kernel_value(train_x, train_x[i, :], kernel_option)
return kernel_matrix
# 定义样本之间的核函数的值
def cal_kernel_value(train_x, train_x_i, kernel_option):
kernel_type = kernel_option[0]
m = np.shape(train_x)[0]
kernel_value = np.mat(np.zeros((m, 1)))
if kernel_type == 'rbf':
sigma = kernel_option[1]
if sigma == 0:
sigma = 1.0
for i in range(m):
diff = train_x[i, :] - train_x_i
kernel_value[i] = np.exp(diff * diff.T/(-2.0 * sigma**2))
else:
kernel_value = train_x * train_x_i.T
return kernel_value
def SVM_training(train_x, train_y, C, toler, max_iter, kernel_option=('rbf', 0.431029)):
# 1.初始化 SVM 分类器
svm = SVM(train_x, train_y, C, toler, kernel_option)
# 2.开始训练 SVM 分类器
entireSet = True
alpha_pairs_changed = 0
iteration = 0
while (iteration < max_iter) and((alpha_pairs_changed > 0) or entireSet):
print('\t 迭代次数为:', iteration)
alpha_pairs_changed = 0
if entireSet:
for x in range(svm.n_samples):
alpha_pairs_changed += choose_and_update(svm, x)
iteration += 1
else:
bound_samples = []
for i in range(svm.n_samples):
if 0 < svm.alphas[i, 0] < svm.C:
bound_samples.append(i)
for x in bound_samples:
alpha_pairs_changed += choose_and_update(svm, x)
iteration += 1
if entireSet:
entireSet = False
elif alpha_pairs_changed == 0:
entireSet = True
return svm
# 选择并更新参数
def choose_and_update(svm, alpha_i):
# 计算第一个样本的误差error_i
error_i = cal_error(svm, alpha_i)
# 判断选择出的第一个变量是否违反了 KKT 条件
if (svm.train_y[alpha_i]*error_i < -svm.toler) and (svm.alphas[alpha_i] < svm.C) or (svm.train_y[alpha_i]*error_i >
svm.toler) and (svm.alphas[alpha_i] > 0):
# 1.选择第二个变量
alpha_j, error_j = select_second_sample_j(svm, alpha_i, error_i)
alpha_i_old = svm.alphas[alpha_i].copy()
alpha_j_old = svm.alphas[alpha_j].copy()
# 2.计算上下界
if svm.train_y[alpha_i] != svm.train_y[alpha_j]:
L = max(0, svm.alphas[alpha_j] - svm.alphas[alpha_i])
H = min(svm.C, svm.C + svm.alphas[alpha_j] - svm.alphas[alpha_i])
else:
L = max(0, svm.alphas[alpha_j] + svm.alphas[alpha_i] - svm.C)
H = min(svm.C, svm.alphas[alpha_j] + svm.alphas[alpha_i])
if L == H:
return 0
# 3.计算 eta
eta = 2.0 * svm.kernel_mat[alpha_i, alpha_j] - svm.kernel_mat[alpha_i, alpha_i] - svm.kernel_mat[alpha_j, alpha_j]
if eta > 0:
return 0
# 4.更新 alpha_j
svm.alphas[alpha_j] -= svm.train_y[alpha_j] * (error_i - error_j)/eta
# 5.确定最终的 alpha_j
if svm.alphas[alpha_j] > H:
svm.alphas[alpha_j] = H
if svm.alphas[alpha_j] < L:
svm.alphas[alpha_j] = L
# 6. 判断是否结束
if abs(alpha_j_old - svm.alphas[alpha_j]) < 0.00001:
update_error_tmp(svm, alpha_j)
return 0
# 7.更新 alpha_i
svm.alphas[alpha_i] += svm.train_y[alpha_i] * svm.train_y[alpha_j] * (alpha_j_old - svm.alphas[alpha_j])
# 8.更新 b
b1 = svm.b - error_i - svm.train_y[alpha_i]*(svm.alphas[alpha_i]-alpha_i_old)*svm.kernel_mat[alpha_i, alpha_i]\
-svm.train_y[alpha_j]*(svm.alphas[alpha_j]-alpha_j_old)*svm.kernel_mat[alpha_i, alpha_j]
b2 = svm.b - error_j - svm.train_y[alpha_i]*(svm.alphas[alpha_i]-alpha_i_old)*svm.kernel_mat[alpha_i, alpha_j]\
-svm.train_y[alpha_j]*(svm.alphas[alpha_j]-alpha_j_old)*svm.kernel_mat[alpha_j, alpha_j]
if 0 < svm.alphas[alpha_i] < svm.C:
svm.b = b1
elif 0 < svm.alphas[alpha_j] < svm.C:
svm.b = b2
else:
svm.b = (b1 + b2)/2.0
# 9.更新 error
update_error_tmp(svm, alpha_j)
update_error_tmp(svm, alpha_i)
return 1
else:
return 0
# 计算误差
def cal_error(svm, alpha_k):
output_k = float(np.multiply(svm.alphas, svm.train_y).T * svm.kernel_mat[:, alpha_k]+svm.b)
error_k = output_k - float(svm.train_y[alpha_k])
return error_k
# 选择第二个变量
def select_second_sample_j(svm, alpha_i, error_i):
svm.error_tmp[alpha_i] = [1, error_i]
candidateAlphaList = np.nonzero(svm.error_tmp[:, 0].A)[0]
maxStep = 0
alpha_j = 0
error_j = 0
if len(candidateAlphaList) > 1:
for alpha_k in candidateAlphaList:
if alpha_k == alpha_i:
continue
error_k = cal_error(svm, alpha_k)
if abs(error_k-error_i) > maxStep:
maxStep = abs(error_k - error_i)
alpha_j = alpha_k
error_j = error_k
else:
alpha_j = alpha_i
while alpha_j == alpha_i:
alpha_j = int(np.random.uniform(0, svm.n_samples))
error_j = cal_error(svm, alpha_j)
return alpha_j, error_j
# 重新计算误差值
def update_error_tmp(svm, alpha_k):
error = cal_error(svm, alpha_k)
svm.error_tmp[alpha_k] = [1, error]
def load_data_libsvm(filename):
df = open(filename)
features = []
labels = []
for line in df.readlines():
lines = line.strip().split(' ') # lines = ['+1', '1:0.708333', '2:1', '3:1', '4:-0.320755', '5:-0.105023', '6:-1', '7:1', '8:-0.419847', '9:-1', '10:-0.225806', '12:1', '13:-1']
# print(lines[1][1])
labels.append(float(lines[0]))
index = 0
tmp = []
for i in range(1, len(lines)):
x = lines[i].strip().split(':')
# print(x[1])
if int(x[0])-1 == index:
tmp.append(float(x[1]))
else:
while int(x[0])-1 > index:
tmp.append(0)
index += 1
tmp.append(float(x[1]))
index += 1
while len(tmp) > 13:
tmp.append(0)
features.append(tmp)
df.close()
return np.mat(features), np.mat(labels).T
def cal_accuracy(svm, test_x, test_y):
n_samples = np.shape(test_x)[0]
correct = 0.0
for i in range(n_samples):
predict = svm_predict(svm, test_x[i, :])
if np.sign(predict) == np.sign(test_y[i]):
correct += 1
accuracy = correct / n_samples
return accuracy
def svm_predict(svm, test_sample_x):
kernel_value = cal_kernel_value(svm.train_x, test_sample_x, svm.kernel_opt)
predict = kernel_value.T * np.multiply(svm.train_y, svm.alphas) + svm.b
return predict
# 保存模型
def save_model(svm_model, model_file):
with open(model_file, 'wb') as file:
pickle.dump(svm_model, file)
def main():
# 1.导入数据集
print('********* 导入数据集 **********')
train_data, label_data = load_data_libsvm('heart_scale')
print(train_data.shape, label_data.shape)
# 2.训练 SVM 模型
print('********* 训练 SVM 模型 **********')
C = 0.6
toler = 0.001
maxIter = 500
svm_model = SVM_training(train_data, label_data, C, toler, maxIter)
# 3.计算模型的准确性
print('********* 计算模型的准确性 **********')
accuracy = cal_accuracy(svm_model, train_data, label_data)
print('训练精度为:%.3f%%' % (accuracy*100))
# 4.保存最终的模型
print('********* 保存最终的模型 **********')
save_model(svm_model, "model_file")
if __name__ == '__main__':
main()
训练结果:
********* 导入数据集 **********
(270, 13) (270, 1)
********* 训练 SVM 模型 **********
迭代次数为: 0
迭代次数为: 1
迭代次数为: 2
迭代次数为: 3
迭代次数为: 4
********* 计算模型的准确性 **********
训练精度为:97.037%
********* 保存最终的模型 **********
5.2、全部文件链接(github)
https://github.com/Chaucergit/Code-and-Algorithm/tree/master/ML/4.SVM-SMO
参考书籍及网络资源:
[1].统计方法.李航
[2].Python机器学习算法.赵志勇
[3].利用Python进行数据分析.WesKinney著,唐学韬等译
[4].https://zh.wikipedia.org/wiki/%E6%94%AF%E6%8C%81%E5%90%91%E9%87%8F%E6%9C%BA