ML-求解 SVM 的SMO 算法
这算是我真正意义上认真去读的第一篇ML论文了, but, 我还是很多地方没有搞懂, 想想, 缓缓吧, 还是先熟练调用API 哈哈
求解w, b
这其实是一个在运筹学里面的经典二次规划 (Quadratic Programming)问题, 可以在多项式时间内求解.
坐标轮转法
Coordinate Descent
- 每次搜索值允许一个变量变化, 其余不变(像求偏导数), 沿坐标轴轮流进行搜索寻求优化
- 把多元变量的优化问题,轮转地转为了单个变量的优化问题, 感觉就是偏导数的思想
- 搜索过程不需要对目标函数求导(不像梯度下降那样来找方向)
将多维的无约束优化问题,转为一系列的最优化问题. 每次搜索值允许一个变量变化(其余不变), 即沿着n个线性无关的方向(通常是坐标方向) 轮流进行搜索.
我感觉最好的对比是梯度下降法, 不清楚可以看我之前的搬运: (有一个小难点是理解向量的方向,及在数学及代码该如何表示)
梯度下降 Python 实现: https://www.cnblogs.com/chenjieyouge/p/11667959.html
每一轮的迭代公式:
\(x_i ^k= x_{(i-1)}^k + \alpha_i ^k d_i ^k\)
坐标方向: \(d_i = e_i\)
轮次: k = 0,1,2....
坐标: i = 1,2...
收敛条件:
\(||x_{now} ^k - x_{before} ^k|| <= \xi\)
即每两次变换后, 收敛达到了 设定的误差, 这样就 break了.
SMO
回顾Daul SVM
\(max_w \ W(a) = \sum \limits _{i=1}^n a_i - \frac {1}{2} \sum \limits_{i=1}^n \sum \limits_{j=1}^n y_i y_j a_i a_j <x_i, x_j> \\ s.t.\)
\(0<= a_i \le C \\ \sum \limits_{i=1}^n a_i y_i = 0\)
其KKT条件为:
\(a_i = 0, \ \rightarrow y_i(w^Tx_i + b) \ge 1\)
\(a_i = C, \ \rightarrow y_i(w^Tx_i + b) \le 1\)
\(0 \le a_i \le C, \ \rightarrow y_i(w^Tx_i + b) \ge 1\)
认识SMO
在前面已经贴出paper了, 就还是有点不太懂, 道阻且跻. 革命尚未成功, 同志仍需努力.
其特点是, 一次必须两个元素 \(a_i, a_j\), 原因在于要满足约束 \(\sum \limits _{i=1}^{n} a_i y_i = 0\)
max_iter , count = n, 0
while True:
count += 1
1. 选择下一步需优化的 \(a_i, a_j\) (选择 使得值 向 最大值发展快的方向, 的 \(a_i, a_j\)
2. 固定其他 a 值, 仅仅通过改变 \(a_i, a_j\) 来优化\(W(a)\)
IF 满足KKT条件收敛:
break
IF count >= max_iter :
break
SMO 算法过程
假设现在要优化 \(a1 和a2\), 根据约束条件 \(\sum \limits _{i=1}^{n} a_i y_i = 0\) , 可展开,移项得:
$a1y1 + a2y2 = 0 - \sum \limits _{i=3}^n a_i y_i $
就类似 已知 1+2+3+4 = 10 必然 1 + 2 = 10 - (3 + 4)
而等式右边必然是固定的, 可以用一个常数 \(\zeta\) 读作(\zeta) 来代替, 则:
\(a1y1 + a2y2 = \zeta\)
则 a1 可以用 a2 的函数, 即:
\(a1 = \frac {(\zeta - a2y2)} {y1} = (\zeta -a2y2)y1\)
因为 y 的值只能去 + - 1, 除和乘是一样的
原优化问题可转化为:
\(W(a1, a2...an) = W((\zeta - a2y2)y1, \ a2, a3....an)\)
将 a3, a4...an 固定, 看作常数, 于是优化函数可以写为:
\(m\ a2^2 + n \ a2 + c\) 的形式
如果忽略 a1, a2 的约束关系, 对该二次函数求导就可以得到最优解.
如果此时 a2' 是 a2 的解, 那么 a2 的新值为:
a1, a2 还受到 \(a1y1 + a2y2 = \zeta\) 的约束, 则必然存在一个上下界 H, L
$a2^{new} = $
IF \(a2^{new} \ge H: \rightarrow H\)
IF \(L \le a2^{new} \ge H: \rightarrow a2^{new}\)
IF \(a2^{new} \le L : \rightarrow L\)
斯坦福smo: http://cs229.stanford.edu/materials/smo.pdf
Python 实现SMO
我目前的半吊子水平, 就大概明白, 还是在 github 搬运一波 jerry 大佬的代码., 也是简化版.
我只是做了详细的注释, 当然, 要真正理解下面的代码, 需要掌握SVM的关键点
凸优化推导及KKT条件
margin 的推导
svm 目标函数推导
svm 的拉格朗日函数 形式 推导
svm 的对偶形式推导
带松弛变量的 svm 推导
核函数的 svm 推导
"""
Author: Lasse Regin Nielsen
"""
from __future__ import division, print_function
import os
import numpy as np
import random as rnd
filepath = os.path.dirname(os.path.abspath(__file__))
class SVM():
"""
Simple implementation of a Support Vector Machine using the
Sequential Minimal Optimization (SMO) algorithm for training.
"""
def __init__(self, max_iter=10000, kernel_type='linear', C=1.0, epsilon=0.001):
self.kernels = {
'linear' : self.kernel_linear,
'quadratic' : self.kernel_quadratic
}
self.max_iter = max_iter
self.kernel_type = kernel_type
self.C = C
self.epsilon = epsilon
def fit(self, X, y):
# Initialization
n, d = X.shape[0], X.shape[1]
alpha = np.zeros((n))
kernel = self.kernels[self.kernel_type]
count = 0
while True:
count += 1
alpha_prev = np.copy(alpha)
for j in range(0, n):
i = self.get_rnd_int(0, n-1, j) # Get random int i~=j
x_i, x_j, y_i, y_j = X[i,:], X[j,:], y[i], y[j]
k_ij = kernel(x_i, x_i) + kernel(x_j, x_j) - 2 * kernel(x_i, x_j)
if k_ij == 0:
continue
alpha_prime_j, alpha_prime_i = alpha[j], alpha[i]
(L, H) = self.compute_L_H(self.C, alpha_prime_j, alpha_prime_i, y_j, y_i)
# Compute model parameters
self.w = self.calc_w(alpha, y, X)
self.b = self.calc_b(X, y, self.w)
# Compute E_i, E_j
E_i = self.E(x_i, y_i, self.w, self.b)
E_j = self.E(x_j, y_j, self.w, self.b)
# Set new alpha values
alpha[j] = alpha_prime_j + float(y_j * (E_i - E_j))/k_ij
alpha[j] = max(alpha[j], L)
alpha[j] = min(alpha[j], H)
alpha[i] = alpha_prime_i + y_i*y_j * (alpha_prime_j - alpha[j])
# Check convergence
diff = np.linalg.norm(alpha - alpha_prev)
if diff < self.epsilon:
break
if count >= self.max_iter:
print("Iteration number exceeded the max of %d iterations" % (self.max_iter))
return
# Compute final model parameters
self.b = self.calc_b(X, y, self.w)
if self.kernel_type == 'linear':
self.w = self.calc_w(alpha, y, X)
# Get support vectors
alpha_idx = np.where(alpha > 0)[0]
support_vectors = X[alpha_idx, :]
return support_vectors, count
def predict(self, X):
return self.h(X, self.w, self.b)
def calc_b(self, X, y, w):
b_tmp = y - np.dot(w.T, X.T)
return np.mean(b_tmp)
def calc_w(self, alpha, y, X):
return np.dot(X.T, np.multiply(alpha,y))
# Prediction
def h(self, X, w, b):
return np.sign(np.dot(w.T, X.T) + b).astype(int)
# Prediction error
def E(self, x_k, y_k, w, b):
return self.h(x_k, w, b) - y_k
def compute_L_H(self, C, alpha_prime_j, alpha_prime_i, y_j, y_i):
if(y_i != y_j):
return (max(0, alpha_prime_j - alpha_prime_i), min(C, C - alpha_prime_i + alpha_prime_j))
else:
return (max(0, alpha_prime_i + alpha_prime_j - C), min(C, alpha_prime_i + alpha_prime_j))
def get_rnd_int(self, a,b,z):
i = z
cnt=0
while i == z and cnt<1000:
i = rnd.randint(a,b)
cnt=cnt+1
return i
# Define kernels
def kernel_linear(self, x1, x2):
return np.dot(x1, x2.T)
def kernel_quadratic(self, x1, x2):
return (np.dot(x1, x2.T) ** 2)
详细注释版
我这里只是做了一个可能无关痛痒的注释, 然后从我平时写代码的习惯顺序, 重写排了下
"""
Author: Lasse Regin Nielsen
"""
import numpy as np
import random as rnd
class SVM():
"""
Simple implementation of a Support Vector Machine using the
Sequential Minimal Optimization (SMO) algorithm for training.
"""
def __init__(self, max_iter=10000, kernel_type='linear', C=1.0, epsilon=0.001):
self.kernels = {
'linear': self.kernel_linear,
'quadratic': self.kernel_quadratic
}
# 最大迭代次数,比如1000次, 在之内没有解也程序结束
self.max_iter = max_iter
# 核函数: 自定义了线性核, 二次项核 两种可选其一
self.kernel_type = kernel_type
# C 表示 alpha 的上界, 受KKT中, 偏导为0的约束
self.C = C
# 误差精度(判断收敛的)
self.epsilon = epsilon
# 定义两个核函数, 多个也行,随意
def kernel_linear(self, x1, x2):
"""线性核(无核)"""
return np.dot(x1, x2.T)
def kernel_quadratic(self, x1, x2):
"""多项式核(2次方)"""
return (np.dot(x1, x2.T) ** 2)
def get_rnd_int(self, a, b, z):
"""随处初始化 [a,b]内的整数,作为下标"""
i = z
cnt = 0
while i == z and cnt < 1000:
i = rnd.randint(a, b)
cnt = cnt + 1
return i
def compute_L_H(self, C, alpha_prime_j, alpha_prime_i, y_j, y_i):
"""计算函数的上下界,之前的 \zeta 约束"""
if (y_i != y_j):
return (max(0, alpha_prime_j - alpha_prime_i), min(C, C - alpha_prime_i + alpha_prime_j))
else:
return (max(0, alpha_prime_i + alpha_prime_j - C), min(C, alpha_prime_i + alpha_prime_j))
def calc_w(self, alpha, y, X):
"""计算w的值"""
return np.dot(X.T, np.multiply(alpha, y))
def calc_b(self, X, y, w):
"""计算偏置量b"""
b_tmp = y - np.dot(w.T, X.T)
return np.mean(b_tmp)
# Prediction
def h(self, X, w, b):
"""根据值的正负号来分类"""
# y = np.sign(*args)
# -1 if y < 0, 0 if y == 0
# 1 if y > 0
return np.sign(np.dot(w.T, X.T) + b).astype(int)
def predict(self, X):
"""输入x,对其做预测是 1 or -1"""
return self.h(X, self.w, self.b)
# Prediction error
def E(self, x_k, y_k, w, b):
"""计算误差"""
return self.h(x_k, w, b) - y_k
def fit(self, X, y):
# Initialization
# n表示样本的个数(行), d表示每个样本的维度(列)
n, d = X.shape[0], X.shape[1]
# 初始化 alpha为 [0,0,0...0], 每个分量对一个样本(1行数据)
alpha = np.zeros((n))
# 选择定义的核函数, kernels是字典, key是名称, value是自定义的方法名
kernel = self.kernels[self.kernel_type]
# 用监测控制最外层,最大迭代次数的, 每迭代一次,则 计数+1
count = 0
while True:
count += 1
# 对alpha 变化前的值,进行深拷贝
alpha_prev = np.copy(alpha)
for j in range(0, n):
# 随机选择[0, n-1]的整数作为下标
i = self.get_rnd_int(0, n - 1, j)
# 选中优化的2个样本xi, xj, yi, yj
x_i, x_j, y_i, y_j = X[i, :], X[j, :], y[i], y[j]
# 计算该样本在核函数映射(R^n->n 下的实数值
k_ij = kernel(x_i, x_i) + kernel(x_j, x_j) - 2 * kernel(x_i, x_j)
# dual是要求max,等于0的就跳过,不用优化了呀
if k_ij == 0:
continue
alpha_prime_j, alpha_prime_i = alpha[j], alpha[i]
# 计算出当前 a_i 的上下界
(L, H) = self.compute_L_H(self.C, alpha_prime_j, alpha_prime_i, y_j, y_i)
# Compute model parameters
self.w = self.calc_w(alpha, y, X)
self.b = self.calc_b(X, y, self.w)
# Compute E_i, E_j
E_i = self.E(x_i, y_i, self.w, self.b)
E_j = self.E(x_j, y_j, self.w, self.b)
# Set new alpha values
alpha[j] = alpha_prime_j + float(y_j * (E_i - E_j)) / k_ij
alpha[j] = max(alpha[j], L)
alpha[j] = min(alpha[j], H)
alpha[i] = alpha_prime_i + y_i * y_j * (alpha_prime_j - alpha[j])
# 计算向量alpha新前后的范数,得到实数值,并判断是否收敛
diff = np.linalg.norm(alpha - alpha_prev)
if diff < self.epsilon:
break
if count >= self.max_iter:
print("Iteration number exceeded the max of %d iterations" % (self.max_iter))
return
# Compute final model parameters
self.b = self.calc_b(X, y, self.w)
if self.kernel_type == 'linear':
self.w = self.calc_w(alpha, y, X)
# Get support vectors
alpha_idx = np.where(alpha > 0)[0]
support_vectors = X[alpha_idx, :]
return support_vectors, count
小结
关于写篇还是很有难度的, 涉及到2篇论文和前面的smv推导是想联系的. 此篇目的除了巩固svm之外, 更多是想回归到代码层面, 毕竟能写出代码,才是我的本职, 与我而言, 理论推导的目的为了成功更加自信的调参侠, 当发现调参搞不定业务需求的时候, 那我就自己写一个, 也不是很难, 毕竟,如果数学原理都整明白了, 代码还很难嘛, 我不相信
其次, 关于代码,正如 jerry 大佬说认为, 跟咱平时写作文是一样的. 一开始都是认识单词, 然后能组句子, 然后能写一个段落, 然后抄满分作文, 增删改一下, 然后开始去大量阅读, 慢慢地才有了自己的思路和想法, 才著文章. 抄和前人的认知,我觉得是最为重要的一点.
钱钟书先生曾认为, "牢骚之人善著文章". 包含两个关键点: 一是阅读量, 一个是"牢骚", 即不安于现状, 善于发现不合理和提出自己的观点. 这岂不就是我写代码的真谛? 有空就多抄写和模仿大佬的代码, 然后有想法,自己去实现, "改变世界"?, 不存在的, 改变自我认识的同时, 顺带解决一些工作问题, 嗯....coding 其乐无穷, 我目标是能做当将其当做作文来写哦
还是重复一些, smo吧, 我也就掌握了70%左右, 代码自己整还是有点难度, 但要理解代码, 嗯, 如下的理论要求,真的是必须的:
凸优化推导及KKT条件
margin 的推导
svm 目标函数推导
svm 的拉格朗日函数 形式 推导
svm 的对偶形式推导
带松弛变量的 svm 推导
核函数的 svm 推导
ML-求解 SVM 的SMO 算法的更多相关文章
- SVM之SMO算法(转)
支持向量机(Support Vector Machine)-----SVM之SMO算法(转) 此文转自两篇博文 有修改 序列最小优化算法(英语:Sequential minimal optimizat ...
- 支持向量机(Support Vector Machine)-----SVM之SMO算法(转)
此文转自两篇博文 有修改 序列最小优化算法(英语:Sequential minimal optimization, SMO)是一种用于解决支持向量机训练过程中所产生优化问题的算法.SMO由微软研究院的 ...
- 统计学习方法c++实现之六 支持向量机(SVM)及SMO算法
前言 支持向量机(SVM)是一种很重要的机器学习分类算法,本身是一种线性分类算法,但是由于加入了核技巧,使得SVM也可以进行非线性数据的分类:SVM本来是一种二分类分类器,但是可以扩展到多分类,本篇不 ...
- <转>SVM实现之SMO算法
转自http://blog.csdn.net/zouxy09/article/details/17292011 终于到SVM的实现部分了.那么神奇和有效的东西还得回归到实现才可以展示其强大的功力.SV ...
- SMO算法--SVM(3)
SMO算法--SVM(3) 利用SMO算法解决这个问题: SMO算法的基本思路: SMO算法是一种启发式的算法(别管启发式这个术语, 感兴趣可了解), 如果所有变量的解都满足最优化的KKT条件, 那么 ...
- 深入理解SVM,详解SMO算法
今天是机器学习专题第35篇文章,我们继续SVM模型的原理,今天我们来讲解的是SMO算法. 公式回顾 在之前的文章当中我们对硬间隔以及软间隔问题都进行了分析和公式推导,我们发现软间隔和硬间隔的形式非常接 ...
- 机器学习——支持向量机(SVM)之Platt SMO算法
Platt SMO算法是通过一个外循环来选择第一个alpha值的,并且其选择过程会在两种方式之间进行交替: 一种方式是在所有数据集上进行单遍扫描,另一种方式则是在非边界alpha中实现单遍扫描. 所谓 ...
- [笔记]关于支持向量机(SVM)中 SMO算法的学习(一)理论总结
1. 前言 最近又重新复习了一遍支持向量机(SVM).其实个人感觉SVM整体可以分成三个部分: 1. SVM理论本身:包括最大间隔超平面(Maximum Margin Classifier),拉格朗日 ...
- 关于SVM数学细节逻辑的个人理解(三) :SMO算法理解
第三部分:SMO算法的个人理解 接下来的这部分我觉得是最难理解的?而且计算也是最难得,就是SMO算法. SMO算法就是帮助我们求解: s.t. 这个优化问题的. 虽然这个优化问题只剩下了α这一个变 ...
随机推荐
- mysql where/having
select * from t1 where id<5;select * from t1 where id<5; where 从t1中筛选内容 而having从*中筛选内容
- python源码解剖
print()本身就是用了多态:不同类型的对象,其实是调用了自身的print()方法 多态:动物 狗1 = new狗() 用公共的部分来指定类型,实则是调用各自的属性 创建对象有两种方式: 通过C A ...
- Linux笔记本合上屏幕不待机
Linux笔记本合上屏幕不待机[]# vim /etc/systemd/logind.conf# This file is part of systemd.## systemd is free sof ...
- Windows远程桌面连接Debian
参考 https://portal.databasemart.com/kb/a457/how-to-install-desktop-environment-and-xrdp-service-in-de ...
- 如何快速将磁盘的MBR分区方式改成GPT分区方式
注:修改分区格式时此硬盘不能是在使用状态(简单说就是不能出现在盘符中),如果在使用中先在计算机的磁盘管理中删除卷. 由于MBR分区表模式的硬盘最大只支持2T的硬盘空间,而现在我们的硬盘越来越大,有时候 ...
- Oracle 层次查询 connect by
oracle 层次查询 语法: SELECT ... FROM [WHERE condition] --过 ...
- [转帖]国产统一操作系统UOS龙芯版正式上线
国产统一操作系统UOS龙芯版正式上线 2019/12/13 12:49:31来源:IT之家作者:骑士责编:骑士评论:446 https://www.ithome.com/0/462/725.htm ...
- AntDesign vue学习笔记(二)axios使用
之前在vue页面中引入axios使用,本篇在mainjs中引入,这样就不用单独在每个页面引入 1.mainjs中引入axios,设置基础url import axios from 'axios' ax ...
- poi根据excel模板导出Excel
/****单元格值对象**/public class Cells { /*** * 行 */ private int row; /** * 列 */ private int column; /** * ...
- 微信小程序路径表达式解析规则
小程序 setData 方法支持路径表达式来设置属性,例如 setData({"x.y.z": 1}). 微信官方没有公布路径表达式的语法规则及解析规则,本文所描述的路径表达式解析 ...