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

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 4932|回复: 0

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

[复制链接]

10

主题

0

回帖

10

积分

新手上路

积分
10
发表于 2019-5-8 03:16:31 | 显示全部楼层 |阅读模式 来自 中国
雷锋网 AI 科技评论按,本文为韦易笑在知乎问题如何学习SVM(支持向量机)以及改进实现SVM算法程序下面的回复,雷锋网 AI 科技评论获其授权转载。以下为正文:& ^8 b2 T; w/ p" T" G' S
学习 SVM 的最好方法是实现一个 SVM,可讲理论的很多,讲实现的太少了。7 o# I' M8 z2 E
假设你已经读懂了 SVM 的原理,并了解公式怎么推导出来的,比如到这里:
- Y9 \7 q. r' j. L6 v
5 F# c- A/ a# E$ @. ]' u4 ISVM 的问题就变成:求解一系列满足约束的 alpha 值,使得上面那个函数可以取到最小值。然后记录下这些非零的 alpha 值和对应样本中的 x 值和 y 值,就完成学习了,然后预测的时候用:8 N( @* {& c8 D2 \% Q% k( l
! w4 W) T" [, O$ V7 g% U
上面的公式计算出 f(x) ,如果返回值 > 0 那么是 +1 类别,否则是 -1 类别,先把这一步怎么来的,为什么这么来找篇文章读懂,不然你会做的一头雾水。1 G) d# r. M# o+ J5 _5 g
那么剩下的 SVM 实现问题就是如何求解这个函数的极值。方法有很多,我们先找个起点,比如 Platt 的 SMO 算法,它后面有伪代码描述怎么快速求解 SVM 的各个系数。
2 L: H, G$ i' B* H8 P4 d' `3 [第一步:实现传统的 SMO 算法0 z' _! W1 r& k% {7 N- ]
现在大部分的 SVM 开源实现,源头都是 platt 的 smo 算法,读完他的文章和推导,然后照着伪代码写就行了,核心代码没几行:
0 w8 \2 _1 @: n8 _# D& Tprocedure takeStep(i1,i2)
( m; a: R  J3 T$ Q7 W( L' f  b( K if (i1 == i2) return 02 T. {; d1 q; Q6 m1 c! R
alph1 = Lagrange multiplier for i1; K/ u; r6 N' o0 X

0 `! G; Z: Z& ~5 Iy1 = target[i1]$ n- C8 E1 q* ?) W) x7 ]9 n( G
9 J5 }, z: s% {0 b
E1 = SVM output on point[i1] – y1 (check in error cache)' M  G+ Y+ q7 ]) h

/ N' c" e4 X* h3 X* S9 `s = y1*y2# |2 a4 E* _$ t& I, K

- ]2 n- O' L$ R) \Compute L, H via equations (13) and (14)
) G3 Z, A4 u) R9 Y1 D2 O
% s1 E+ [# b% {( e" v. g; }if (L == H)
5 H$ T. X5 ~7 s8 M" o) f) f
" ^" B, q% ~: Y, O4 v! f7 }return 0, d# [: {+ j3 y0 w

! V$ U3 l5 v) L& L2 ?k11 = kernel(point[i1],point[i1])% `0 @4 M% f' k1 o% Q

1 |- b9 g8 ^0 ~5 f1 T/ g! w+ ck12 = kernel(point[i1],point[i2])+ D" V5 R0 e. f% q7 I4 q# y
& u. B( P  K, g( ]' L
k22 = kernel(point[i2],point[i2])
$ C) F$ l9 J7 w) v. J! s* _/ d+ A3 ^$ ^& ]( j7 f* r7 Y
eta = k11+k22-2*k12
  i9 X, q* W. q2 {3 r/ |5 j; x4 c- @9 K; ]8 {8 J! b: D
if (eta > 0)" T" y6 d8 a& I, N; Q8 k
* C, c$ Q% s, K8 x7 E3 `& M( {
{' E* ^0 M4 f9 b
9 z$ t5 S$ P2 v4 A/ P0 {6 I
a2 = alph2 + y2*(E1-E2)/eta% }- t! m6 s4 a
3 @. K6 e$ h# w; q6 F# U3 X
if (a2 < L) a2 = L
- F- v$ [* n: g5 `' J# @& [" E3 x* X- E& p5 W
else if (a2 > H) a2 = H; P* p! `3 D, W7 ~
9 n8 s/ A& K" \/ j
}6 e% r! z6 H8 g( E' @
& z4 X$ z/ J5 m% }9 D, P
else
) @* p. v+ u' x0 `8 w6 l, t, T- _1 d: x: B" f! ]. }5 U
{: |/ V# N1 @. G5 J

2 X# t2 ^. T7 y0 ?  C0 ~$ qLobj = objective function at a2=L
$ U/ q1 m" {. H- R# R5 D2 V; M2 \$ M+ c6 y' @# }" r+ Z# G* ^
Hobj = objective function at a2=H
7 B9 S  Y" J8 `) X+ w6 |: ]
* S( j6 [* }7 j; M( ~/ qif (Lobj < Hobj-eps)
; p. t. v# L8 p# Z, O- S* [1 ~/ A3 F0 @
a2 = L
7 d  y" v. [! s8 u" `4 j0 Z  P
* c0 k# v# U% n  qelse if (Lobj > Hobj+eps)2 v" v$ F7 z2 i( Q* z
. E, v3 k3 _0 Y( p+ }% k3 \* X  g
a2 = H
& E! D! M4 ^1 B5 o+ h: x
2 g; ~: b5 a5 O# J3 V! \* \else& k+ m5 B2 Z% }/ h- e

4 E# m6 {7 ]+ h5 ga2 = alph2
$ E- {5 G: l$ c0 r: H* Q; d# d6 R' z' Z& b! ?
}* p5 `4 A% f/ s7 }

2 \& Q! u8 _9 A! Eif (|a2-alph2| < eps*(a2+alph2+eps))
7 o/ @' w1 J$ m+ }9 X' T0 m* _6 o. Z$ u. o( n% l" u8 M" i
return 0& z& A% D( |! _1 o
9 \+ ~- ], {9 [5 C2 Y! L& J
a1 = alph1+s*(alph2-a2); g' ^2 U( J. |6 ?7 w/ S( d

; n" T- N. n# e0 j' W' k( V' CUpdate threshold to reflect change in Lagrange multipliers- w% e; l& B$ N/ {4 L! N# b4 l

  r+ K1 f+ O1 M! jUpdate weight vector to reflect change in a1 & a2, if SVM is linear
9 Y- c+ B& f/ K! I2 K0 W3 |+ Z* t; }4 ]
Update error cache using new Lagrange multipliers
) `1 K- Z% d' y/ Y
* b4 G: Y+ Z; j3 }) v# P3 ZStore a1 in the alpha array: v8 C% ]3 ]' d5 B% y9 @

7 v8 L0 i; [' R0 J/ \4 O$ UStore a2 in the alpha array  m: ?; u) q  t* O$ Z
% ^4 j! c# C9 [) a
return 1; k" \$ K/ O3 e2 y' {4 Y
endprocedure( R4 Z) R6 c. L& ^& o  Q: P; I' l
核心代码很紧凑,就是给定两个 ai, aj,然后迭代出新的 ai, aj 出来,还有一层循环会不停的选择最需要被优化的系数 ai, aj,然后调用这个函数。如何更新权重和 b 变量(threshold)文章里面都有说,再多调试一下,可以用 python 先调试,再换成 C/C++,保证得到一个正确可用的 SVM 程序,这是后面的基础。* C3 t( M  S8 e, a5 i- _
第二步:实现核函数缓存' s0 t* Z# T. Q) ?' i, G
观察下上面的伪代码,开销最大的就是计算核函数 K(xi, xj),有些计算又反复用到,一个 100 个样本的数据集求解,假设总共要调用核函数 20 万次,但是 xi, xj 的组和只有 100x100=1 万种,有缓存的话你的效率可以提升 20 倍。
) @8 Y. `# Q2 y样本太大时,如果你想存储所有核函数的组和,需要 N*N * sizeof(double) 的空间,如果训练集有 10 万个样本,那么需要 76 GB 的内存,显然是不可能实现的,所以核函数缓存是一个有限空间的 LRU 缓存,SVM 的 SMO 求解过程中其实会反复用到特定的几个有限的核函数求解,所以命中率不用担心。
* l* ]# X: v" H& [, `1 B有了这个核函数缓存,你的 SVM 求解程序能瞬间快几十倍。
( ^+ z/ X" A. K8 @* H4 x8 E# P第三步:优化误差值求解
1 W: [: P' J! z4 N注意看上面的伪代码,里面需要计算一个估计值和真实值的误差 Ei 和 Ej,他们的求解方法是:; c5 S+ n! f- z5 F
E(i) = f(xi) - yi( S4 u2 j9 s+ e8 ~2 |4 R
这就是目前为止 SMO 这段为代码里代价最高的函数,因为回顾下上面的公式,计算一遍 f(x) 需要 for 循环做乘法加法。
9 l0 B' @: p- D8 w$ gplatt 的文章建议是做一个 E 函数的缓存,方便后面选择 i, j 时比较,我看到很多入门版本 SVM 实现都是这么做。其实这是有问题的,后面我们会说到。最好的方式是定义一个 g(x) 令其等于:
3 h9 I7 }. C, Q! Z$ L: e, B% J' c! Q0 S# E3 n
也就是 f(x) 公式除了 b 以外前面的最费时的计算,那么我们随时可以计算误差:, Z2 C$ ~$ X, H  |, g5 S5 l% }
E(j) = g(xj) + b - yj
# N" e' D: z( }$ Y4 r/ S! I+ \所以最好的办法是对 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 的偏导数就行了,具体代码类似:
3 Z! b2 H6 }/ f3 H. Z3 F  edouble Kik = kernel(i, k);# x* ]9 ~3 U0 e/ l3 {1 ]) {! b
double Kjk = kernel(j, k);
7 B  w2 |7 }& }, a+ {G[k] += delta_alpha_i * Kik * y + delta_alpha_j * Kjk * y[j];/ F  N# w' X0 B  r8 d( X
把这段代码放在 takeStep 后面,每次成功更新一对 ai, aj 以后,更新所有样本对应的 g(x) 缓存,这样通过每次迭代更新 g(x) 避免了大量的重复计算。
% N9 r! E& X. F" M! ~这其实是很直白的一种优化方式,我查了一下,有人专门发论文就讲了个类似的方法。
  i: f9 y7 X+ T8 U+ K4 e, z第四步:实现冷热数据分离
4 L2 y8 A1 K( f4 B; m$ B% n' OPlatt 的文章里也证明过一旦某个 alpha 出于边界(0 或者 C)的时候,就很不容易变动,而且伪代码也是优先在工作集里寻找 > 0 and < C 的 alpha 值进行优化,找不到了,再对工作集整体的 alpha 值进行迭代。0 [  W! d1 w# [) K$ Y8 b. e
那么我们势必就可以把工作集分成两个部分,热数据在前(大于 0 小于 C 的 alpha 值),冷数据在后(小于等于 0 或者大于等于 C 的 alpha)。
$ u/ k/ M; A' f# M5 G3 M随着迭代加深,会发现大部分时候只需要在热数据里求解,并且热数据的大小会逐步不停的收缩,所以区分了冷热以后 SVM 大部分都在针对有限的热数据迭代,偶尔不行了,再全部迭代一次,然后又回到冷热迭代,性能又能提高不少。* x% A4 ]+ ]" o: {, T! f
第五步:支持 Ensemble
6 Z/ E7 a* c3 a4 ?: Q大家都知道,通过 Ensemble 可以让多个不同的弱模型组和成一个强模型,而传统 SVM 实现并不能适应一些类似 AdaBoost 的集成方法,所以我们需要做一些改动。可以让外面针对某一个分类传入一个“权重”过来,修正 SVM 的识别结果。7 C1 H  d* i3 w2 n' t  Q
最传统的修改方式就是将不等式约束 C 分为 Cp 和 Cn 两个针对 +1 分类的 C 及针对 -1 分类的 C。修改方式是直接用原始的 C 乘以各自分类的权重,得到 Cp 和 Cn,然后迭代时,不同的样本根据它的 y 值符号,用不同的 C 值带入计算。8 z% U( t4 o: I
这样 SVM 就能用各种集成方法同其他模型一起组成更为强大精准的模型了。5 i9 L' J: m7 ]$ o) q* D
实现到这一步你就得到了功能上和性能上同 libsvm 类似的东西,接下来我们继续优化。
* W7 x7 ^& u" Z8 m9 V/ v3 r8 \第六步:继续优化核函数计算( |. s( i% r# i7 f; O" C2 q. J
核函数缓存非常消耗内存,libsvm 数学上已经没得挑了,但是工程方面还有很大改进余地,比如它的核缓存实现。; v4 ^7 D$ i, P+ {
由于标准 SVM 核函数用的是两个高维矢量的内积,根据内积的几个条件,SVM 的核函数又是一个正定核,即 K(xi, xj) = K(xj, xi),那么我们同样的内存还能再多存一倍的核函数,性能又能有所提升。! p3 E2 o+ r% p5 G. l
针对核函数的计算和存储有很多优化方式,比如有人对 NxN 的核函数矩阵进行采样,只计算有限的几个核函数,然后通过插值的方式求解出中间的值。还有人用 float 存储核函数值,又降低了一倍空间需求。5 z+ T# z. C/ D9 v) v
第七步:支持稀疏向量和非稀疏向量9 z% N9 I" k9 A
对于高维样本,比如文字这些,可能有上千维,每个样本的非零特征可能就那么几个,所以稀疏向量会比较高效,libsvm 也是用的稀疏向量。
! M$ R) v2 A) O但是还有很多时候样本是密集向量,比如一共 200 个特征,大部分样本都有 100个以上的非零特征,用稀疏向量存储的话就非常低效了,openCV 的 SVM 实现就是非稀疏向量。
5 A6 D4 w7 X" a8 q& N非稀疏向量直接是用数组保存样本每个特征的值,在工程方面就有很多优化方式了,比如用的最多的求核函数的时候,直接上 SIMD 指令或者 CUDA,就能获得更好的计算性能。6 t- Z0 L* b# \% k% P
所以最好的方式是同时支持稀疏和非稀疏,兼顾时间和空间效率,对不同的数据选择最适合的方式。
0 o/ |% {, S! C8 I  p3 t第八步:针对线性核进行优化2 }3 K* `4 s# {- l- C. o# }! @
传统的 SMO 方法,是 SVM 的通用求解方法,然而针对线性核,就是:& J; P" u1 Y! q$ S2 _+ p, W- _
K(xi, xj) = xi . xj
: v  n8 @* ?: Y% p还有很多更高效的求解思路,比如 Pegasos 算法就用了一种类似随机梯度下降的方法,快速求 SVM 的解权重 w,如果你的样本适合线性核,使用一些针对性的非 SMO 算法可以极大的优化 SVM 求解,并且能处理更加庞大的数据集,LIBLINEAR 就是做这件事情的。
: [* ^) x( Z; |8 Z( a同时这类算法也适合 online 训练和并行训练,可以逐步更新增量训练新的样本,还可以用到多核和分布式计算来训练模型,这是 SMO 算法做不到的地方。
, ?8 r  r4 w. R  d! s5 ]但是如果碰到非线性核,权重 w 处于高维核空间里(有可能无限维),你没法梯度下降迭代 w,并且 pegasos 的 pdf 里面也没有提到如何用到非线性核上,LIBLINEAR 也没有办法处理非线性核。) D7 H5 B# G  B  v$ B! o8 n
或许哪天出个数学家又找到一种更好的方法,可以用类似 pegasos 的方式求解非线性核,那么 SVM 就能有比较大的进展了。8 U# z" o, ?+ Z  P# u6 I
后话% y# x) U& v# y) ]4 j
上面八条,你如果实现前三条,基本就能深入理解 SVM 的原理了,如果实现一大半,就可以得到一个类似 libsvm 的东西,全部实现,你就能得到一个比 libsvm 更好用的 SVM 库了。( F. f- {+ I4 x9 O3 a' a( v$ g
上面就是如何实现一个相对成熟的 SVM 模型的思路,以及配套优化方法,再往后还有兴趣,可以接着实现支持向量回归,也是一个很有用的东西。0 G2 @7 p9 n6 a0 t9 `8 I
6 B( G1 Q0 X9 r; U, W& k) C+ h$ O& Y$ e
来源:http://www.yidianzixun.com/article/0Lv0UIiC, A5 M2 d! z2 H& a6 D$ [5 S6 A
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

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

×

帖子地址: 

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

使用道具 举报

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

本版积分规则

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

GMT+8, 2026-4-20 14:38 , Processed in 0.045124 second(s), 24 queries .

Powered by Mxzdjyxk! X3.5

© 2001-2026 Discuz! Team.

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