目录
基于最大间隔分隔数据
寻找最大间隔
分类器求解的优化问题
SVM应用的一般框架
SMO高效优化算法
Platt的SMO算法
应用简化版SMO算法处理小规模数据集
有些人认为,SVM是最好的现成的分类器,这里说的“现成”是指分类器不加修改即可直接使用。同时没这就意味着在数据上应用基本形式的SVM就可以得到抵错误率的结果。SVM能够对训练集以外的数据点做出很好的分类决策。
SVM有很多实现,这里只关注其中最流行的一种实现,即序列最小优化(Sequential Minimal Optimaization, SMO)算法
支持向量机
优点:泛化错误率低,计算开销不大,结果易解释。
缺点:对参数调节和核函数的选择敏感,原始分类器不加修改仅适用于处理二类问题。
使用数据类型:数值型和标称型数据。
分割超平面:将数据集分隔开来的直线
间隔:点到分割面的距离
我们希望间隔尽可能大,这是因为如果我们犯错或者在有限数据上训练分类器的话,我们希望分类器尽可能健壮。
支持向量就是离分割超平面最近的那些点。
分隔超平面的形式可以写成,要计算点A到分隔超平面的距离,就必须给出点到分割线的法线的长度,该值为
这里的类别标签设置为1和-1。当计算数据点到分割面的距离并确定好分割面的放置位置时,间隔通过来计算。如果数据点处于正方面(即+1类)并且离分割超平面很远的位置时,
会是一个很大的正数,同时
也会是一个很大的正数。而如果数据点位于负方向(-1类)并且距离超平面很远的位置时,此时由于是类别标签为-1,
仍然是一个很大的正数。
现在的目标是找出分类器定义中的w和b,要满足以下两个目标:
这就可以写作:
直接求解上述问题相当困难,所以我们将它转换成为另一种更容易求解的形式。
由于对乘积进行优化是一件很讨厌的事情,因此我们要做的是固定其中一个因子而最大化其他因子。如果令所有向量的都为1,那么就可以通过求
的最大值来得到最终解。但是并非所有数据点的
都等于1,只有那些离分割超平面最近的点得到的值才为1.而离超平面越远的数据点,其
的值也就越大。
在上述优化问题中,给定了一些约束条件然后求最优值,因此该问题是一个带约束条件的优化问题。这里的约束条件就是。 对于这类优化问题,有一个非常著名的求解方法,即拉格朗日乘子法。通过引入拉格朗日乘子,我们就可以基于约束条件来表达原来的问题。由于这里的约束条件都是基于数据点的,因此我们就可以将超平面写成数据点的形式。于是,目标优化函数最后可以写成:
其约束条件为:
但是这里有个假设:数据必须100%线性可分。目前为止,我们知道几乎所有数据都不那么“干净”。这时我们引入所谓的松弛变量(slack variable)来允许有些数据点可以处于分割面的错误一侧。这样我们的优化目标就能保持仍然不变,但是此时的约束条件则变为:
这里的常数C用于控制“最大化间隔”和“保证大部分点的函数间隔小于1.0”这两个目标的权重。在优化算法的实现代码中,常数C是一个参数,因此我们可以通过调节改成该参数得到不同的结果。一旦求除了所有的,那么超平面就可以通过这些
来表达,SVM中的主要工作就是求解这些
。
SVM的一般流程
- 收集数据:可以使用任意方法
- 准备数据:需要数值型数据
- 分析数据:有助于可视化分隔超平面
- 训练算法:SVM的大部分时间都源自训练,该过程主要实现两个参数的调优
- 测试算法:十分简单的计算过程就可以实现
- 使用算法:几乎所有分类问题都可以使用SVM,值得一提的是,SVM本身是一个而分类器,对多分类问题应用SVM需要对代码做一些修改。
1996年,John Platt发布了一个称为SMO的强大算法,用于训练SVM。SMO表示序列最小优化(Sequential Minimal Optimization)。
Platt的SMO算法是将大优化问题分解为多个小优化问题求解的。这些小优化问题往往很容易求解,并且对它们进行序列求解的结果于将它们作为整体来求解的结果是完全一致的。
SMO的工作原理是:
每次循环中选择两个alpha进行优化处理。一旦找到一对合适的alpha,那么就增大其中一个同时减小另一个。这里的“合适”需要满足两个条件:
- 这两个alpha必须要在间隔边界之外
- 这两个alpha还没有进行区间化处理或者不在边界上
Platt SMO算法中的外循环确定要优化的alpha对。而简化版将会i熬过这一部分,首先在数据集上遍历每一个alpha,然后在生下的alpha集合中随机选择另一个alpha对。这里有一点非常重要。就是要同时改变两个alpha。
SMO函数的第一个版本伪代码为:
- 创建一个alpha向量并将其初始化为0向量
- 当迭代次数小于最大迭代次数式(外循环)
- 对数据集中的每个数据向量(内循环):
- 如果该数据向量各一杯优化:
- 随机选择另外一个数据向量
- 同时优化这两个向量
- 如果两个向量都不能被优化,退出内循环
- 如果所有向量都没有被优化,增加迭代数目,继续下一次循环
import numpy as np
# SMO算法中的辅助函数
def load_data_set(file_name):
data_mat = []
label_mat = []
file = open(file_name)
for line in file.readlines():
line_array = line.strip().split('\t')
data_mat.append([float(line_array[0]), float(line_array[1])])
label_mat.append(float(line_array[2]))
return data_mat, label_mat
def select_rand_j(i, m):
j = i
while j == i:
j = int(np.random.uniform(0, m))
return j
def clip_alpha(aj, high, low):
if aj > high:
aj = high
if aj < low:
aj = low
return aj
def smo_simple(data_matrix_in, class_labels, c, toler, max_interation):
data_matrix = np.mat(data_matrix_in)
label_mat = np.mat(class_labels).transpose()
b = 0
m, n = np.shape(data_matrix)
alphas = np.mat(np.zeros((m, 1)))
iteration = 0
while iteration < max_interation:
alpha_pairs_changed = 0
for i in range(m):
fxi = float(np.multiply(alphas, label_mat).T * (data_matrix * data_matrix[i, :].T)) + b
ei = fxi - float(label_mat[i])
if (label_mat[i] * ei < -toler and alphas[i] < c) or (label_mat[i] * ei > toler and alphas[i] > 0):
j = select_rand_j(i, m)
fxj = float(np.multiply(alphas, label_mat).T * (data_matrix * data_matrix[j, :].T)) + b
ej = fxj - float(label_mat[j])
alpha_i_old = alphas[i].copy()
alpha_j_old = alphas[j].copy()
if label_mat[i] != label_mat[j]:
low = max(0, alphas[j] - alphas[i])
high = min(c, c + alphas[j] - alphas[i])
else:
low = max(0, alphas[j] + alphas[i] - c)
high = min(c, alphas[j] + alphas[i])
if low == high:
print("low == high")
continue
eta = 2.0 * data_matrix[i, :] * data_matrix[j, :].T - data_matrix[i, :] * data_matrix[i, :].T \
- data_matrix[j, :] * data_matrix[j, :].T
if eta >= 0:
print("eta >= 0")
continue
alphas[j] -= label_mat[j] * (ei - ej) / eta
alphas[j] = clip_alpha(alphas[j], high, low)
if abs(alphas[j] - alpha_j_old) < 0.00001:
print("j not moving enough")
continue
alphas[i] += label_mat[j] * label_mat[i] * (alpha_j_old - alphas[j])
b1 = b - ei - label_mat[i] * (alphas[i] - alpha_i_old) * data_matrix[i, :] * data_matrix[i, :].T \
- label_mat[j] * (alphas[j] - alpha_j_old) * data_matrix[i, :] * data_matrix[j, :].T
b2 = b - ej - label_mat[i] * (alphas[i] - alpha_j_old) * data_matrix[i, :] * data_matrix[j, :].T \
- label_mat[j] * (alphas[j] - alpha_j_old) * data_matrix[j, :] * data_matrix[j, :].T
if alphas[i] > 0 and alphas[i] < c:
b = b1
elif alphas[j] > 0 and alphas[j] < c:
b = b2
else:
b = (b1 + b2) / 2.0
alpha_pairs_changed += 1
print("iteration: %d i: %d, pairs changed: %d" % (iteration, i, alpha_pairs_changed))
if alpha_pairs_changed == 0:
iteration += 1
else:
iteration = 0
print("iteration number: %d" % iteration)
return b, alphas
if __name__ == '__main__':
data_mat, label_mat = load_data_set('testSet.txt')
b, alphas = smo_simple(data_mat, label_mat, 0.6, 0.001, 40)
print("b:", b)
print("alphas(>0): ", alphas[alphas > 0])
结果:
b: [[-3.79464184]]
alphas(>0): [[1.27942323e-01 2.38433967e-01 6.93889390e-18 3.66376290e-01]]