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. 这个优化问题的. 虽然这个优化问题只剩下了α这一个变 ...
随机推荐
- 【luoguP1382】楼房
题目描述 离散化,线段树维护区间修改,发现询问都是单点的\(max\),不妨把标记留在点上,不用下传,查询时取个\(max\)就可以了 #include<algorithm> #inclu ...
- nlp语义理解的一点儿看法
nlp领域里,语义理解仍然是难题! 给你一篇文章或者一个句子,人们在理解这些句子时,头脑中会进行上下文的搜索和知识联想.通常情况下,人在理解语义时头脑中会搜寻与之相关的知识.知识图谱的创始人人为,构成 ...
- vb.net 判断某个文件是否已经打开了
' 判断这个excel文件是否已经打开了: 如果打开了,不能下载 Try Dim fs AsFileStream = NewFileStream(excelFileName, FileMode.O ...
- USB、UART、SPI等总线速率(转)
1. USB总线 USB1.1: ——-低速模式(low speed):1.5Mbps ——-全速模式(full speed): 12Mbps USB2.0:向下兼容.增加了高速模式,最大速率480M ...
- React-native 导航插件React Navigation 4.x的使用
React-native 导航插件React Navigation 4.x的使用 文档 英文水平可以的话,建议直接阅读英文文档 简单使用介绍 安装插件 yarn add react-navigatio ...
- 【Activiti学习之五】BPMN事件
环境 JDK 1.8 MySQL 5.6 Tomcat 7 Eclipse-Luna activiti 6.0 一.事件定义1.定时器事件(1)timeDate:指定时间触发<timerEven ...
- .net core 读取配置文件的值
.net core中的配置文件可以存一些自定义的值,我们需要去读取 在配置中添加json: "name": "sealee", "Connection ...
- xilinx SDK开发 GPIO使用API总结
t_v GPIO常用函数 1.XGpio_Config *XGpio_LookupConfig(u16 DeviceId) 功能:根据输入设备ID查找该设备. 输入:设备ID. 输出:若找到该设备ID ...
- Windows设置国内源阿里云镜像加速与离线安装pip包的方法
Windows设置国内源阿里云镜像加速1.先在 windows “文件资源管理器” 地址栏 输入 %APPDATA% 按回车,打开程序自定义设置文件夹然后,创建名为 pip 的文件夹,用于存放 pip ...
- JDBC链接数据库MySQL 8.0 Public Key Retrieval is not allowed 错误的解决方法
现象 Mybatis和Spring框架整合过程中报 com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: Publ ...