京东6.18大促主会场领京享红包更优惠

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 4873|回复: 0

如何学习SVM(支持向量机)以及改进实现SVM算法程序

[复制链接]

10

主题

0

回帖

10

积分

新手上路

积分
10
发表于 2019-5-8 03:16:31 | 显示全部楼层 |阅读模式 来自 中国
雷锋网 AI 科技评论按,本文为韦易笑在知乎问题如何学习SVM(支持向量机)以及改进实现SVM算法程序下面的回复,雷锋网 AI 科技评论获其授权转载。以下为正文:! b  H6 t! I5 Z+ O' e" j
学习 SVM 的最好方法是实现一个 SVM,可讲理论的很多,讲实现的太少了。5 ~, t2 j2 o, l  u4 \
假设你已经读懂了 SVM 的原理,并了解公式怎么推导出来的,比如到这里:
$ f$ b2 P9 c- F% `; N9 g/ D5 H9 D, Z9 L" L% N! I$ D& R# d
SVM 的问题就变成:求解一系列满足约束的 alpha 值,使得上面那个函数可以取到最小值。然后记录下这些非零的 alpha 值和对应样本中的 x 值和 y 值,就完成学习了,然后预测的时候用:" x0 g& A; W! P! m
7 S2 |1 [! N- z; {3 V) P
上面的公式计算出 f(x) ,如果返回值 > 0 那么是 +1 类别,否则是 -1 类别,先把这一步怎么来的,为什么这么来找篇文章读懂,不然你会做的一头雾水。# F' t% B5 X- J2 ?* q, E
那么剩下的 SVM 实现问题就是如何求解这个函数的极值。方法有很多,我们先找个起点,比如 Platt 的 SMO 算法,它后面有伪代码描述怎么快速求解 SVM 的各个系数。0 u- v2 s8 l, a& F3 t' L+ o& a
第一步:实现传统的 SMO 算法
/ V! P5 w, C/ y. Q: s2 Z$ A现在大部分的 SVM 开源实现,源头都是 platt 的 smo 算法,读完他的文章和推导,然后照着伪代码写就行了,核心代码没几行:0 S' \  w/ b/ X5 {
procedure takeStep(i1,i2): k# ?6 {% t+ n, L& S) ^
if (i1 == i2) return 0
6 h$ d% E1 W3 Z' l( Q/ x  v3 v alph1 = Lagrange multiplier for i1
( t4 p0 ?$ C& P& T9 v9 o5 O0 f" P; @1 L# G& n+ M4 j4 i
y1 = target[i1]
1 [9 n5 @' i( ~& b, j) b
3 ?* d3 J' m* l- [& d6 n9 Y2 D2 AE1 = SVM output on point[i1] – y1 (check in error cache); C( q2 p0 K; d. j/ N5 m# ~! ^

+ {$ M& }) @5 [0 Vs = y1*y23 U/ ]  P% w' H  v: v
* T$ B; H4 B5 V5 e; r  l2 N- y, l0 W
Compute L, H via equations (13) and (14)0 q) @4 x* C( w( {8 e: r
. o! A/ e( P! D! a% d* ?# }
if (L == H)
% o7 m- Y! w. F% f* N4 L6 s
! N/ B  j2 d8 y/ Kreturn 0
3 ^5 {1 v: r; \: `6 S8 S: Y* c6 q2 v5 a
k11 = kernel(point[i1],point[i1])/ y. V, K* C6 ~4 j3 r1 \

9 j% _7 H% F4 H- r4 Hk12 = kernel(point[i1],point[i2])% y! H- L- b  c

+ t5 d; s5 b7 b; J  Y  R5 C8 hk22 = kernel(point[i2],point[i2])/ z! z9 t. u" N; K

+ f' [5 H( I4 y# Neta = k11+k22-2*k124 U0 b* H# e! a1 D
, Z& R" k3 r1 _4 K! E/ F
if (eta > 0)
% ?5 D) M; j1 T
; S; m5 K5 U% c( n/ Q* Z% G; L3 p{- }  T5 g+ h. u4 ]: ?9 B3 Z" O: J

7 h" G( {- B. P; Ea2 = alph2 + y2*(E1-E2)/eta
' h- h$ J/ D: Z) e( Y& q7 c! [' G/ ~  g$ J4 T7 v! Z
if (a2 < L) a2 = L8 B. g* c$ Q: I# O5 u: m
3 }$ A, t% f) G' t6 Z2 W0 l1 Y
else if (a2 > H) a2 = H2 H, K! x. \. j4 b- x/ a. }* R
6 U* ?+ j0 P& _4 ~8 O
}# D2 H6 k% Q1 \, M
+ h) m& ?  w- C/ K4 P
else; e7 G/ o) |+ ?9 ^( Q( C$ w

8 r. Z% b+ K  E0 G1 h$ r- A! [6 y{
- D+ e3 z9 |! s2 x% ]" _% p4 ]' ]2 j. S5 E7 R
Lobj = objective function at a2=L* L( J# L6 H" t4 z" V: e+ a

" E# C" f7 A* @+ c# |0 D$ HHobj = objective function at a2=H: W% o) ^, d$ Q- B5 C# [$ V# \
0 h& M2 y3 i) S5 ~8 R0 v( v
if (Lobj < Hobj-eps)
: T1 F3 ?; f" g' v: s0 ?  [5 C& y. m& q/ n7 M; \2 t" R
a2 = L
+ s& J) g3 k1 m: Z
2 m8 A# A9 a7 c3 ?' B0 z5 delse if (Lobj > Hobj+eps)( m: i9 u5 R' y: V# N& {2 Z$ m

: E0 `+ ~. Y; S- Fa2 = H
  O6 a3 t* I; L1 ]5 e) I! K: _* s* r2 s/ m! j, i$ O0 t
else
/ i2 \5 G9 d) x' h
% M$ i9 I3 R6 Y' w0 _a2 = alph27 j" K$ K6 Z$ i/ v# H7 g% j* t
8 j( T- L, D( l
}' V5 q1 X+ N0 p- a: u

; z6 {+ Q8 T4 I: gif (|a2-alph2| < eps*(a2+alph2+eps))8 R9 e% H2 [0 H: Q7 x

5 {2 U# ?" G" Mreturn 0
: c9 {) u+ s, ?, _, U; ^' U" ?- ^! G
: O3 j7 o4 ?! d* y" H" p9 L* Ga1 = alph1+s*(alph2-a2)
# `- E3 ]8 }$ Z/ Y& t5 o. u3 [, w3 b! a4 |4 H
Update threshold to reflect change in Lagrange multipliers
1 a4 d1 K/ `( w. [+ J, c2 k5 v
0 B8 J+ j# F9 f3 ?: \9 MUpdate weight vector to reflect change in a1 & a2, if SVM is linear
$ t, W% j; y6 d( }! |5 C3 z# _0 z" v% R) @4 {; J
Update error cache using new Lagrange multipliers. T; }/ e8 B; {$ G
# v4 d0 ]$ g" ~6 m7 R3 J3 `
Store a1 in the alpha array
1 ~: S/ ]0 K/ H) P' n: N3 b, ]/ g9 F+ R4 E: H
Store a2 in the alpha array8 n( u' ~, L( {+ {0 H% a
& ^* C" @& E; z, ^0 V( {1 ]
return 1
2 D9 U) K( A5 h$ rendprocedure) h3 v! ^! k! n* i8 a# q2 H0 q2 v
核心代码很紧凑,就是给定两个 ai, aj,然后迭代出新的 ai, aj 出来,还有一层循环会不停的选择最需要被优化的系数 ai, aj,然后调用这个函数。如何更新权重和 b 变量(threshold)文章里面都有说,再多调试一下,可以用 python 先调试,再换成 C/C++,保证得到一个正确可用的 SVM 程序,这是后面的基础。
, n: d& r2 p& X) y/ R. Q2 @) B第二步:实现核函数缓存; `# l5 s% h+ i% Q
观察下上面的伪代码,开销最大的就是计算核函数 K(xi, xj),有些计算又反复用到,一个 100 个样本的数据集求解,假设总共要调用核函数 20 万次,但是 xi, xj 的组和只有 100x100=1 万种,有缓存的话你的效率可以提升 20 倍。' S) C/ A" Y; q; r
样本太大时,如果你想存储所有核函数的组和,需要 N*N * sizeof(double) 的空间,如果训练集有 10 万个样本,那么需要 76 GB 的内存,显然是不可能实现的,所以核函数缓存是一个有限空间的 LRU 缓存,SVM 的 SMO 求解过程中其实会反复用到特定的几个有限的核函数求解,所以命中率不用担心。
) n9 O* K* a7 G有了这个核函数缓存,你的 SVM 求解程序能瞬间快几十倍。. [+ S5 _' s1 p) |
第三步:优化误差值求解
# G* k* v# h2 |0 }/ p注意看上面的伪代码,里面需要计算一个估计值和真实值的误差 Ei 和 Ej,他们的求解方法是:
) Z$ f" {+ c3 M. ^- E* `7 l! i  k* xE(i) = f(xi) - yi: J, b4 X+ M. Y" [8 k4 h
这就是目前为止 SMO 这段为代码里代价最高的函数,因为回顾下上面的公式,计算一遍 f(x) 需要 for 循环做乘法加法。# l- ?5 C5 d5 s9 }6 I5 T: C
platt 的文章建议是做一个 E 函数的缓存,方便后面选择 i, j 时比较,我看到很多入门版本 SVM 实现都是这么做。其实这是有问题的,后面我们会说到。最好的方式是定义一个 g(x) 令其等于:
  w! F  ]; B! A' b3 l
- f3 d  B7 N6 O( [# p4 z. G3 T* C' ]也就是 f(x) 公式除了 b 以外前面的最费时的计算,那么我们随时可以计算误差:9 a8 W( h2 z" \; `8 F6 ^: f  i; r
E(j) = g(xj) + b - yj
$ V9 c' e8 G4 N所以最好的办法是对 g(x) 进行缓存,platt 的方法里因为所有 alpha 值初始化成了 0,所以 g(x) 一开始就可以全部设置成 0,稍微观察一下 g(x) 的公式,你就会发现,因为去掉了 b 的干扰,而每次 SMO 迭代更新 ai, aj 参数时,这两个值都是线性变化的,所以我们可以给 g(x) 求关于 a 的偏导,假设 ai,aj 变化了步长 delta,那么所有样本对应的 g(x) 加上 delta 乘以针对 ai, aj 的偏导数就行了,具体代码类似:- w$ E5 p- K/ _
double Kik = kernel(i, k);
7 v, t$ _- v$ e. G6 Tdouble Kjk = kernel(j, k);, R7 g1 D$ m/ T# X
G[k] += delta_alpha_i * Kik * y + delta_alpha_j * Kjk * y[j];  P9 \' @! O# P7 i5 r' T- L% F+ {8 g" l
把这段代码放在 takeStep 后面,每次成功更新一对 ai, aj 以后,更新所有样本对应的 g(x) 缓存,这样通过每次迭代更新 g(x) 避免了大量的重复计算。
7 B' ^/ H) p) y8 U& e这其实是很直白的一种优化方式,我查了一下,有人专门发论文就讲了个类似的方法。
" X0 }2 K5 v4 C/ j0 b* _第四步:实现冷热数据分离4 I. x/ e0 V6 o. g; L* S
Platt 的文章里也证明过一旦某个 alpha 出于边界(0 或者 C)的时候,就很不容易变动,而且伪代码也是优先在工作集里寻找 > 0 and < C 的 alpha 值进行优化,找不到了,再对工作集整体的 alpha 值进行迭代。
( m* N& [! D2 q0 v" G那么我们势必就可以把工作集分成两个部分,热数据在前(大于 0 小于 C 的 alpha 值),冷数据在后(小于等于 0 或者大于等于 C 的 alpha)。% }8 A: C+ x  ?/ ]* m
随着迭代加深,会发现大部分时候只需要在热数据里求解,并且热数据的大小会逐步不停的收缩,所以区分了冷热以后 SVM 大部分都在针对有限的热数据迭代,偶尔不行了,再全部迭代一次,然后又回到冷热迭代,性能又能提高不少。6 ^' ]! k# S4 W  E4 I- I/ @
第五步:支持 Ensemble# M+ }; t1 b& c5 ]
大家都知道,通过 Ensemble 可以让多个不同的弱模型组和成一个强模型,而传统 SVM 实现并不能适应一些类似 AdaBoost 的集成方法,所以我们需要做一些改动。可以让外面针对某一个分类传入一个“权重”过来,修正 SVM 的识别结果。) W+ n* H! _7 p# @% B+ v
最传统的修改方式就是将不等式约束 C 分为 Cp 和 Cn 两个针对 +1 分类的 C 及针对 -1 分类的 C。修改方式是直接用原始的 C 乘以各自分类的权重,得到 Cp 和 Cn,然后迭代时,不同的样本根据它的 y 值符号,用不同的 C 值带入计算。
: t# }5 ^  ?& l4 a( O$ i5 C这样 SVM 就能用各种集成方法同其他模型一起组成更为强大精准的模型了。
) J& `$ X5 Q& X$ K$ M* c8 H9 L- N实现到这一步你就得到了功能上和性能上同 libsvm 类似的东西,接下来我们继续优化。
8 o& Z9 T# _# g! w第六步:继续优化核函数计算# y# Y' X& D" w! {5 O7 T
核函数缓存非常消耗内存,libsvm 数学上已经没得挑了,但是工程方面还有很大改进余地,比如它的核缓存实现。$ Z  z4 ~$ b# O' T$ u9 r% k
由于标准 SVM 核函数用的是两个高维矢量的内积,根据内积的几个条件,SVM 的核函数又是一个正定核,即 K(xi, xj) = K(xj, xi),那么我们同样的内存还能再多存一倍的核函数,性能又能有所提升。+ J& t( _5 ?7 n: `& t
针对核函数的计算和存储有很多优化方式,比如有人对 NxN 的核函数矩阵进行采样,只计算有限的几个核函数,然后通过插值的方式求解出中间的值。还有人用 float 存储核函数值,又降低了一倍空间需求。
* P* F3 p( \  w; `7 s, \/ z第七步:支持稀疏向量和非稀疏向量) U) |. N; \7 U  C0 d5 a# g& F2 p
对于高维样本,比如文字这些,可能有上千维,每个样本的非零特征可能就那么几个,所以稀疏向量会比较高效,libsvm 也是用的稀疏向量。
; s' e1 G: s1 z! U2 B但是还有很多时候样本是密集向量,比如一共 200 个特征,大部分样本都有 100个以上的非零特征,用稀疏向量存储的话就非常低效了,openCV 的 SVM 实现就是非稀疏向量。' {* q3 E! F$ p
非稀疏向量直接是用数组保存样本每个特征的值,在工程方面就有很多优化方式了,比如用的最多的求核函数的时候,直接上 SIMD 指令或者 CUDA,就能获得更好的计算性能。" ]% Q/ i* K! C5 i
所以最好的方式是同时支持稀疏和非稀疏,兼顾时间和空间效率,对不同的数据选择最适合的方式。
: h$ N' N4 E0 [/ L- y, G- r  T第八步:针对线性核进行优化
8 p4 ?: y  P- N  Q; v传统的 SMO 方法,是 SVM 的通用求解方法,然而针对线性核,就是:) A& z- t2 i; y; G6 t3 T) ?: I
K(xi, xj) = xi . xj1 V0 ]: x  z, {+ ?
还有很多更高效的求解思路,比如 Pegasos 算法就用了一种类似随机梯度下降的方法,快速求 SVM 的解权重 w,如果你的样本适合线性核,使用一些针对性的非 SMO 算法可以极大的优化 SVM 求解,并且能处理更加庞大的数据集,LIBLINEAR 就是做这件事情的。; f; U$ \3 n% B! D5 q
同时这类算法也适合 online 训练和并行训练,可以逐步更新增量训练新的样本,还可以用到多核和分布式计算来训练模型,这是 SMO 算法做不到的地方。1 `# b+ A4 ?' D0 _' \; b
但是如果碰到非线性核,权重 w 处于高维核空间里(有可能无限维),你没法梯度下降迭代 w,并且 pegasos 的 pdf 里面也没有提到如何用到非线性核上,LIBLINEAR 也没有办法处理非线性核。1 f5 S& @8 e" u0 ~/ t1 {3 T
或许哪天出个数学家又找到一种更好的方法,可以用类似 pegasos 的方式求解非线性核,那么 SVM 就能有比较大的进展了。
# R3 C6 Z( q! ]- h* A( J% J后话, E8 M) t% p, L, |6 R- U
上面八条,你如果实现前三条,基本就能深入理解 SVM 的原理了,如果实现一大半,就可以得到一个类似 libsvm 的东西,全部实现,你就能得到一个比 libsvm 更好用的 SVM 库了。5 W0 a& I5 n& z- U2 e
上面就是如何实现一个相对成熟的 SVM 模型的思路,以及配套优化方法,再往后还有兴趣,可以接着实现支持向量回归,也是一个很有用的东西。* ^1 J8 g# P; z* s

; p8 r* |. J1 g; K. D! ~; T& c9 @来源:http://www.yidianzixun.com/article/0Lv0UIiC4 z/ O4 r0 F# z: g4 a2 x
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×

帖子地址: 

梦想之都-俊月星空 优酷自频道欢迎您 http://i.youku.com/zhaojun917
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|手机版|小黑屋|梦想之都-俊月星空 ( 粤ICP备18056059号 )|网站地图

GMT+8, 2026-1-17 20:18 , Processed in 0.036299 second(s), 24 queries .

Powered by Mxzdjyxk! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表