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

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 5071|回复: 0

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

[复制链接]

10

主题

0

回帖

10

积分

新手上路

积分
10
发表于 2019-5-8 03:16:31 | 显示全部楼层 |阅读模式 来自 中国
雷锋网 AI 科技评论按,本文为韦易笑在知乎问题如何学习SVM(支持向量机)以及改进实现SVM算法程序下面的回复,雷锋网 AI 科技评论获其授权转载。以下为正文:
$ I1 c' \0 }2 Q8 N8 J# D9 J2 J学习 SVM 的最好方法是实现一个 SVM,可讲理论的很多,讲实现的太少了。
2 q5 v# ~+ q6 _# b5 b' w假设你已经读懂了 SVM 的原理,并了解公式怎么推导出来的,比如到这里:
( |( Z4 {6 K+ U. X* ~
" A: n$ N8 |# c7 L2 ]" ]4 e1 TSVM 的问题就变成:求解一系列满足约束的 alpha 值,使得上面那个函数可以取到最小值。然后记录下这些非零的 alpha 值和对应样本中的 x 值和 y 值,就完成学习了,然后预测的时候用:
% e6 ]7 R/ d/ C( m" }: A) v7 C+ Y
上面的公式计算出 f(x) ,如果返回值 > 0 那么是 +1 类别,否则是 -1 类别,先把这一步怎么来的,为什么这么来找篇文章读懂,不然你会做的一头雾水。% ?- W; e* `8 R3 i2 j1 f
那么剩下的 SVM 实现问题就是如何求解这个函数的极值。方法有很多,我们先找个起点,比如 Platt 的 SMO 算法,它后面有伪代码描述怎么快速求解 SVM 的各个系数。  t- T; A+ \( T! T3 j
第一步:实现传统的 SMO 算法$ i: m2 f$ y! N/ R
现在大部分的 SVM 开源实现,源头都是 platt 的 smo 算法,读完他的文章和推导,然后照着伪代码写就行了,核心代码没几行:
1 [# e3 W& v0 l1 D5 Bprocedure takeStep(i1,i2)
) a- c0 H, X0 ?# P( @' z3 o9 H$ V* }/ s if (i1 == i2) return 0/ k. I8 R6 a4 T# L- A2 I
alph1 = Lagrange multiplier for i1+ c# o" l3 M* Y
. l8 f4 N' B7 T! |# R0 b: x
y1 = target[i1]7 t! R2 e# h( {1 T5 g, i
5 W$ A1 t4 Q/ X; O
E1 = SVM output on point[i1] – y1 (check in error cache)( h  Q! z' A* ~: p

: ~" m  I# E& @* vs = y1*y2
6 Z& D/ D: {% O9 R+ w( h4 R! v; _6 `6 X, u" \
Compute L, H via equations (13) and (14)& w7 H: U2 `( a& ?; e- a
+ \2 h9 c. E7 l8 I( O/ `4 r) z
if (L == H)) m3 y0 I5 y- ^; H' m! x
' f4 I% L! @' \# `& g
return 0
' e6 c, ^" S1 p# Q; _
! D8 p8 ?9 }2 @2 A" rk11 = kernel(point[i1],point[i1])
) }' f9 b- m8 Q, Z( w& o6 Z% u. l8 r0 b" [; F$ ?
k12 = kernel(point[i1],point[i2])
6 m9 t$ x+ H( ?
& b/ ]& }* Z6 Tk22 = kernel(point[i2],point[i2])
) O6 [% t, C0 r, x: K7 E5 u" o, F# R4 G; _1 ~1 C
eta = k11+k22-2*k12
" e, _- @7 L  [9 }( B; V' z
& }6 X* Y- A) ^+ Z8 c& \) Q$ U) O6 ~if (eta > 0)
) G& A' X' |7 N4 A1 f  h* q8 M! u. t& m6 {7 u- X4 h# T# V
{# Y5 r% |0 t# V3 y/ o' A( y
9 L/ L7 ?1 z+ Z$ I. O+ X. O7 U7 L
a2 = alph2 + y2*(E1-E2)/eta
' f: x( q( B" @2 f( u
0 f1 K" B$ @7 r7 Kif (a2 < L) a2 = L
+ R+ Z  Y* [0 @
3 b  a3 S( q: U1 A8 b# `else if (a2 > H) a2 = H
7 g: J4 k- [/ j" G# F0 V# R- Q: {/ [
}$ Y9 S# |3 H; c0 Z& b

7 Z+ A2 V. z  Delse0 g) q5 M* d3 I2 j9 g  a

: \( i# S% d- e% ?/ [{
2 t. q! T+ g! H) _
- V" a( d( ]/ zLobj = objective function at a2=L
. N) N8 _' L! t" K* j6 B8 |3 q9 S+ r. N  E# U7 R- }3 _0 Q& I3 {
Hobj = objective function at a2=H
+ H# Q' F, _+ A+ J) l5 O+ z
5 l  H' |' H- m9 }: T  H) C7 gif (Lobj < Hobj-eps)9 @+ L( K  r! X. |

+ u% m2 h- o  C" h0 b& Q; O% ?a2 = L& v+ R2 K  b7 q, W

! H4 L  W% X) |else if (Lobj > Hobj+eps)
$ E9 m! h4 ~4 `( x/ E" [
7 }- i! `9 J* N/ T3 pa2 = H2 J  B* T; q! `" _( y0 C
! Z- F1 W  D8 |' r/ }, d8 H
else
7 `* K0 Z& U+ f9 q
; {& Z& y2 ?/ n2 S/ W3 w2 @7 ca2 = alph2
" W" W. a" x: ~7 a) A! B; ^4 q4 k
}8 j' f) M2 Q- ]( k8 A

+ Z. @) }( W/ j  I4 t% [if (|a2-alph2| < eps*(a2+alph2+eps))
) ?* }2 I/ W5 g) c9 H5 X& x' i+ O: W' w
return 0
- g( f8 A* \. P+ x' j# ~2 p1 F/ w1 x# I! Y5 c1 y4 o, V
a1 = alph1+s*(alph2-a2)5 N4 z+ U' P' x0 D+ C/ l

3 l4 Z3 F( j0 y4 k7 S; JUpdate threshold to reflect change in Lagrange multipliers8 o- ]* K" w% C
# G# M% D; h3 }; t
Update weight vector to reflect change in a1 & a2, if SVM is linear* y7 O* [; r! E2 q
' z5 E5 L; ?8 p4 a
Update error cache using new Lagrange multipliers
9 O# x* a7 H* [; ~$ I$ @/ I8 G2 t8 P) X# ?4 o$ @9 ?' a
Store a1 in the alpha array8 E  x* \2 `8 k( d' u+ ^
. o' s9 R  ~" p( u
Store a2 in the alpha array
3 D' u( C$ y9 r+ ~$ t6 G
* Y: }9 b' J" p: D4 |# }4 Xreturn 1
$ v3 i4 w' V: v8 S6 z4 p3 F1 dendprocedure
3 _0 e% \/ v! C核心代码很紧凑,就是给定两个 ai, aj,然后迭代出新的 ai, aj 出来,还有一层循环会不停的选择最需要被优化的系数 ai, aj,然后调用这个函数。如何更新权重和 b 变量(threshold)文章里面都有说,再多调试一下,可以用 python 先调试,再换成 C/C++,保证得到一个正确可用的 SVM 程序,这是后面的基础。
" o/ B' n- ]" u; Y- x第二步:实现核函数缓存8 T' h6 d- p( E
观察下上面的伪代码,开销最大的就是计算核函数 K(xi, xj),有些计算又反复用到,一个 100 个样本的数据集求解,假设总共要调用核函数 20 万次,但是 xi, xj 的组和只有 100x100=1 万种,有缓存的话你的效率可以提升 20 倍。6 S' R- {& a$ v0 Y7 Q& p
样本太大时,如果你想存储所有核函数的组和,需要 N*N * sizeof(double) 的空间,如果训练集有 10 万个样本,那么需要 76 GB 的内存,显然是不可能实现的,所以核函数缓存是一个有限空间的 LRU 缓存,SVM 的 SMO 求解过程中其实会反复用到特定的几个有限的核函数求解,所以命中率不用担心。" x3 P' i8 @! y% r" h  m* ^- u
有了这个核函数缓存,你的 SVM 求解程序能瞬间快几十倍。- M4 R( m. w+ O
第三步:优化误差值求解
( R7 k; \/ X0 |8 l0 @% p6 p* |0 ~注意看上面的伪代码,里面需要计算一个估计值和真实值的误差 Ei 和 Ej,他们的求解方法是:5 J4 j% Z. ^7 S% F4 n% U
E(i) = f(xi) - yi4 B: d, D! Z1 g! d7 \( j0 y& y" O+ T
这就是目前为止 SMO 这段为代码里代价最高的函数,因为回顾下上面的公式,计算一遍 f(x) 需要 for 循环做乘法加法。6 N/ w& A/ P( G; O( M
platt 的文章建议是做一个 E 函数的缓存,方便后面选择 i, j 时比较,我看到很多入门版本 SVM 实现都是这么做。其实这是有问题的,后面我们会说到。最好的方式是定义一个 g(x) 令其等于:
' H5 D# @0 G2 D  D1 I+ s1 g; q$ x, E: y$ b; e
也就是 f(x) 公式除了 b 以外前面的最费时的计算,那么我们随时可以计算误差:: h& o) ~# q8 b8 v
E(j) = g(xj) + b - yj9 O$ I/ v& z4 Q! ?5 Q$ D2 J
所以最好的办法是对 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 的偏导数就行了,具体代码类似:
- v/ t7 }2 j/ ndouble Kik = kernel(i, k);/ \! x: a5 E  Z$ s
double Kjk = kernel(j, k);
7 D! d" s. S- m" kG[k] += delta_alpha_i * Kik * y + delta_alpha_j * Kjk * y[j];9 @1 D% j7 `( o- j4 @' R
把这段代码放在 takeStep 后面,每次成功更新一对 ai, aj 以后,更新所有样本对应的 g(x) 缓存,这样通过每次迭代更新 g(x) 避免了大量的重复计算。
! P7 h! u: q7 \' v这其实是很直白的一种优化方式,我查了一下,有人专门发论文就讲了个类似的方法。. d' S; j* S- B' p5 E
第四步:实现冷热数据分离
4 I/ S9 t% x0 q% Q* j+ GPlatt 的文章里也证明过一旦某个 alpha 出于边界(0 或者 C)的时候,就很不容易变动,而且伪代码也是优先在工作集里寻找 > 0 and < C 的 alpha 值进行优化,找不到了,再对工作集整体的 alpha 值进行迭代。) U9 O! }5 {/ `) V- Y* a& e
那么我们势必就可以把工作集分成两个部分,热数据在前(大于 0 小于 C 的 alpha 值),冷数据在后(小于等于 0 或者大于等于 C 的 alpha)。
1 @1 B" G1 z: ]; e随着迭代加深,会发现大部分时候只需要在热数据里求解,并且热数据的大小会逐步不停的收缩,所以区分了冷热以后 SVM 大部分都在针对有限的热数据迭代,偶尔不行了,再全部迭代一次,然后又回到冷热迭代,性能又能提高不少。
& ]6 H* A6 R3 C" s5 j9 ~第五步:支持 Ensemble
! [( a) v, |0 l; N3 I# U5 l大家都知道,通过 Ensemble 可以让多个不同的弱模型组和成一个强模型,而传统 SVM 实现并不能适应一些类似 AdaBoost 的集成方法,所以我们需要做一些改动。可以让外面针对某一个分类传入一个“权重”过来,修正 SVM 的识别结果。* P  o0 d- L# C1 y- J9 u( n4 i- S
最传统的修改方式就是将不等式约束 C 分为 Cp 和 Cn 两个针对 +1 分类的 C 及针对 -1 分类的 C。修改方式是直接用原始的 C 乘以各自分类的权重,得到 Cp 和 Cn,然后迭代时,不同的样本根据它的 y 值符号,用不同的 C 值带入计算。2 c6 V1 j9 [$ U( p6 s
这样 SVM 就能用各种集成方法同其他模型一起组成更为强大精准的模型了。
. w9 B& {4 @0 |实现到这一步你就得到了功能上和性能上同 libsvm 类似的东西,接下来我们继续优化。
$ j8 L9 w* T" b# U. k9 w6 j第六步:继续优化核函数计算$ F5 F: k6 T) G/ D, P
核函数缓存非常消耗内存,libsvm 数学上已经没得挑了,但是工程方面还有很大改进余地,比如它的核缓存实现。/ c* {8 Q8 n3 z
由于标准 SVM 核函数用的是两个高维矢量的内积,根据内积的几个条件,SVM 的核函数又是一个正定核,即 K(xi, xj) = K(xj, xi),那么我们同样的内存还能再多存一倍的核函数,性能又能有所提升。+ v, {# e/ d. |) S! c  k- I
针对核函数的计算和存储有很多优化方式,比如有人对 NxN 的核函数矩阵进行采样,只计算有限的几个核函数,然后通过插值的方式求解出中间的值。还有人用 float 存储核函数值,又降低了一倍空间需求。
! J% e; {; L4 Z$ A: \第七步:支持稀疏向量和非稀疏向量. m3 N, _0 S5 F6 W; b
对于高维样本,比如文字这些,可能有上千维,每个样本的非零特征可能就那么几个,所以稀疏向量会比较高效,libsvm 也是用的稀疏向量。
+ z; G9 }1 ^) e2 m但是还有很多时候样本是密集向量,比如一共 200 个特征,大部分样本都有 100个以上的非零特征,用稀疏向量存储的话就非常低效了,openCV 的 SVM 实现就是非稀疏向量。
# n! L$ U! |) b8 A; B. I非稀疏向量直接是用数组保存样本每个特征的值,在工程方面就有很多优化方式了,比如用的最多的求核函数的时候,直接上 SIMD 指令或者 CUDA,就能获得更好的计算性能。+ f7 W' s. w9 v; c5 B0 L4 g. [
所以最好的方式是同时支持稀疏和非稀疏,兼顾时间和空间效率,对不同的数据选择最适合的方式。* S& Z+ V# w. u5 X! d; s/ O
第八步:针对线性核进行优化
# N8 J  F; T3 V* v传统的 SMO 方法,是 SVM 的通用求解方法,然而针对线性核,就是:2 X4 P0 r6 J7 N
K(xi, xj) = xi . xj
* B$ t. a' r1 y0 N9 A9 G5 t还有很多更高效的求解思路,比如 Pegasos 算法就用了一种类似随机梯度下降的方法,快速求 SVM 的解权重 w,如果你的样本适合线性核,使用一些针对性的非 SMO 算法可以极大的优化 SVM 求解,并且能处理更加庞大的数据集,LIBLINEAR 就是做这件事情的。
( \. t" B2 W, M: \* S同时这类算法也适合 online 训练和并行训练,可以逐步更新增量训练新的样本,还可以用到多核和分布式计算来训练模型,这是 SMO 算法做不到的地方。
9 j. x' B4 |( [; e* f1 j但是如果碰到非线性核,权重 w 处于高维核空间里(有可能无限维),你没法梯度下降迭代 w,并且 pegasos 的 pdf 里面也没有提到如何用到非线性核上,LIBLINEAR 也没有办法处理非线性核。
8 h1 f9 |/ v6 ~; r5 y或许哪天出个数学家又找到一种更好的方法,可以用类似 pegasos 的方式求解非线性核,那么 SVM 就能有比较大的进展了。: J: f" t9 q! o8 f
后话/ D$ c& V: }% X5 M
上面八条,你如果实现前三条,基本就能深入理解 SVM 的原理了,如果实现一大半,就可以得到一个类似 libsvm 的东西,全部实现,你就能得到一个比 libsvm 更好用的 SVM 库了。
0 j. f1 p4 c$ Z; W& \0 n; d上面就是如何实现一个相对成熟的 SVM 模型的思路,以及配套优化方法,再往后还有兴趣,可以接着实现支持向量回归,也是一个很有用的东西。  Z5 {' N) s) v/ P5 k9 S% I
! V0 n& e- B9 n- D0 R$ S) S
来源:http://www.yidianzixun.com/article/0Lv0UIiC: o5 t% }2 b8 l/ ^( m' ?
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

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

×

帖子地址: 

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

使用道具 举报

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

本版积分规则

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

GMT+8, 2026-10-15 04:57 , Processed in 0.039570 second(s), 25 queries .

Powered by Mxzdjyxk! X3.5

© 2001-2026 Discuz! Team.

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