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

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 4810|回复: 0

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

[复制链接]

10

主题

0

回帖

10

积分

新手上路

积分
10
发表于 2019-5-8 03:16:31 | 显示全部楼层 |阅读模式 来自 中国
雷锋网 AI 科技评论按,本文为韦易笑在知乎问题如何学习SVM(支持向量机)以及改进实现SVM算法程序下面的回复,雷锋网 AI 科技评论获其授权转载。以下为正文:3 c6 L$ P7 ^4 }' `. r$ U
学习 SVM 的最好方法是实现一个 SVM,可讲理论的很多,讲实现的太少了。5 v0 C. i; g4 T" O3 Z
假设你已经读懂了 SVM 的原理,并了解公式怎么推导出来的,比如到这里:
, ]# c% v' d. x6 w% L: }0 w
7 m+ k3 k5 O: p1 b! jSVM 的问题就变成:求解一系列满足约束的 alpha 值,使得上面那个函数可以取到最小值。然后记录下这些非零的 alpha 值和对应样本中的 x 值和 y 值,就完成学习了,然后预测的时候用:+ c1 C; l/ _# V0 R- \( ^2 ^: Z1 D
7 d! X& R+ L5 Y8 ^# ^
上面的公式计算出 f(x) ,如果返回值 > 0 那么是 +1 类别,否则是 -1 类别,先把这一步怎么来的,为什么这么来找篇文章读懂,不然你会做的一头雾水。# w+ B3 f( |9 }5 k
那么剩下的 SVM 实现问题就是如何求解这个函数的极值。方法有很多,我们先找个起点,比如 Platt 的 SMO 算法,它后面有伪代码描述怎么快速求解 SVM 的各个系数。
5 p2 |' G+ V! z$ ?1 E* Z! \第一步:实现传统的 SMO 算法
, Z+ w' Z  k4 I# r9 M现在大部分的 SVM 开源实现,源头都是 platt 的 smo 算法,读完他的文章和推导,然后照着伪代码写就行了,核心代码没几行:
  F, O+ Y5 F' _7 `procedure takeStep(i1,i2)
$ i4 ?; l/ F0 V) u9 G# ]6 _ if (i1 == i2) return 0
" ]+ V; m9 j% l8 l  C alph1 = Lagrange multiplier for i1& w* P- X" E) c

5 j$ m; _/ A8 Py1 = target[i1]
6 v; L; `( S2 b& y3 D/ V0 Q$ N# \& ?
E1 = SVM output on point[i1] – y1 (check in error cache)) H- E  f6 k) `! y( R% h8 p, I4 a' \6 L

! I; M7 T) E9 H! c# I$ M4 E: ks = y1*y2. `) P$ E2 S1 _! R. N( ]

2 A2 b; d6 Q6 B2 v! @: OCompute L, H via equations (13) and (14)
- N' s" o/ {( H+ M8 V) o- N/ ~& i4 x- n6 s2 d/ K
if (L == H)- _$ H5 C' G% I4 x+ j/ C

8 Y6 _$ w' k% K- T1 ereturn 0( B4 R$ ~  g( f% ]/ c
* N; L5 f) i# Q( i3 i: C
k11 = kernel(point[i1],point[i1])  W: r5 S9 P0 K

) N" D- K, h; A( I2 f# k) qk12 = kernel(point[i1],point[i2])9 }! W1 N5 P  F0 P* ^: u9 j

0 {) i$ ~/ U3 u1 D8 |/ |5 Mk22 = kernel(point[i2],point[i2])
' O- P6 u$ f0 Z& s) X! A% v& v# f" O2 o) g1 x! G' N
eta = k11+k22-2*k12
( F1 ~/ l8 S" h' j* {2 M2 Y! F: o4 D7 \. [5 M, v* N
if (eta > 0)' X) l; V* W) N/ {

" X3 w6 q$ p* A+ I{
& `2 w  g' L- w+ u2 V1 A* ^* `) V+ G% v, E/ t# e
a2 = alph2 + y2*(E1-E2)/eta, @% u7 M/ S4 i; A4 ]2 [, ^3 M

9 V9 @, p* m* nif (a2 < L) a2 = L
  K4 r; s" S) l, R8 R' Y& l) z- I  _' c; |+ }/ e
else if (a2 > H) a2 = H, s& Y# j- ~" R; m

1 Q0 \1 ~: S' G/ y& H& o. g2 k" {}
( e3 p  `3 e1 U3 G! ~6 Z
( D, L/ j' |& @. f9 u% d; C4 J9 z# ielse5 ^& p( |6 p7 e/ A. \5 k2 M
" r/ y# I* }6 v( {: ?
{
9 o7 G8 e- `) r5 y6 [9 d
  A# f8 L6 d# YLobj = objective function at a2=L$ f7 \* W+ ?$ V3 i% {

- _* h1 j& u3 I7 i- MHobj = objective function at a2=H$ s7 p" F* \& B% U
+ {8 v5 ?& D5 J$ H: I/ X
if (Lobj < Hobj-eps)
7 |; W) U7 O5 B, n
+ z8 `5 Q4 A. v7 ~a2 = L/ }6 r4 m3 i) R1 d. s8 V! d/ k
0 \+ }3 Z/ T6 ~  N% A) l# M
else if (Lobj > Hobj+eps)
9 G2 C( \: ]$ {, X
; z- n5 M2 {$ e/ j4 M- v$ Ua2 = H
5 n% N- i/ |3 j+ j
. t, @! s: W5 Yelse
' O' S% X( m4 s0 k% q( G* M: T; P: ?# N( l* V4 `' V1 }6 Q
a2 = alph2
) C( B7 ]- I3 m: {4 W3 m. D8 L
8 i( }+ I$ |" F2 ?}
( H0 k4 e6 Y( I" x9 ?3 n- Q( b1 g- Y9 `
if (|a2-alph2| < eps*(a2+alph2+eps))5 G- j: {' i! g- p5 Y0 U2 v) j6 ?) e
1 e- t) |/ i6 ]/ m( l. H4 T% I3 T
return 01 {; V1 A; v0 E

- j, I: ?2 o6 s! W9 G, Y% va1 = alph1+s*(alph2-a2)% P0 n  F7 g8 N) l7 W2 Y& i. L( s

# P( A( `' G, m' LUpdate threshold to reflect change in Lagrange multipliers4 B7 y2 x+ {6 G
% H1 n3 I) n6 r
Update weight vector to reflect change in a1 & a2, if SVM is linear: z) Y  r  Z( @9 m6 f

) f: ?8 k9 I5 D/ EUpdate error cache using new Lagrange multipliers9 |$ C1 w0 m8 e1 }

; Z( E( O" Z6 l2 B3 t& oStore a1 in the alpha array  L6 Q5 b5 j* Y/ N5 `6 y
" ~, y6 O/ j2 d* U# x
Store a2 in the alpha array
7 |3 l; V; ^: k
9 W- I% h( ~7 \5 @! areturn 1' O9 j4 |6 o2 r: v
endprocedure9 y) p4 [+ a* B& A4 B! L8 Z
核心代码很紧凑,就是给定两个 ai, aj,然后迭代出新的 ai, aj 出来,还有一层循环会不停的选择最需要被优化的系数 ai, aj,然后调用这个函数。如何更新权重和 b 变量(threshold)文章里面都有说,再多调试一下,可以用 python 先调试,再换成 C/C++,保证得到一个正确可用的 SVM 程序,这是后面的基础。
- j( p* C: o. Q% i& ]第二步:实现核函数缓存! {; X& k& j: \5 G. s/ j. d) K/ G
观察下上面的伪代码,开销最大的就是计算核函数 K(xi, xj),有些计算又反复用到,一个 100 个样本的数据集求解,假设总共要调用核函数 20 万次,但是 xi, xj 的组和只有 100x100=1 万种,有缓存的话你的效率可以提升 20 倍。" g$ X9 U% }0 t0 g4 E
样本太大时,如果你想存储所有核函数的组和,需要 N*N * sizeof(double) 的空间,如果训练集有 10 万个样本,那么需要 76 GB 的内存,显然是不可能实现的,所以核函数缓存是一个有限空间的 LRU 缓存,SVM 的 SMO 求解过程中其实会反复用到特定的几个有限的核函数求解,所以命中率不用担心。
8 P- Q6 q) W. v' A( t$ C  M2 b) j有了这个核函数缓存,你的 SVM 求解程序能瞬间快几十倍。5 Q: \: y; i" C' q8 w3 D) b+ M1 b
第三步:优化误差值求解
% v: m+ B! Y  J; V! i注意看上面的伪代码,里面需要计算一个估计值和真实值的误差 Ei 和 Ej,他们的求解方法是:
: S# i1 m( g- o- V$ }% aE(i) = f(xi) - yi
, E) P" [% g' ~8 ]0 R这就是目前为止 SMO 这段为代码里代价最高的函数,因为回顾下上面的公式,计算一遍 f(x) 需要 for 循环做乘法加法。- l5 ~- h: h# f7 }
platt 的文章建议是做一个 E 函数的缓存,方便后面选择 i, j 时比较,我看到很多入门版本 SVM 实现都是这么做。其实这是有问题的,后面我们会说到。最好的方式是定义一个 g(x) 令其等于:
% Z4 K. h! m) D9 V$ @5 I
$ `5 P; v6 Z$ r: S7 e/ W$ q也就是 f(x) 公式除了 b 以外前面的最费时的计算,那么我们随时可以计算误差:
1 Y7 c" J" A# r$ z; T* D& WE(j) = g(xj) + b - yj
! C$ B- V/ H2 G5 ]& N- o  O所以最好的办法是对 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 的偏导数就行了,具体代码类似:. c' E) Y5 L0 d6 d$ @/ o. s6 q- Z: E
double Kik = kernel(i, k);
4 t# K/ [. K$ Adouble Kjk = kernel(j, k);
% j) X, H4 o0 n* f2 a0 r+ OG[k] += delta_alpha_i * Kik * y + delta_alpha_j * Kjk * y[j];. J1 P% ]  x- y
把这段代码放在 takeStep 后面,每次成功更新一对 ai, aj 以后,更新所有样本对应的 g(x) 缓存,这样通过每次迭代更新 g(x) 避免了大量的重复计算。
' b' @8 ^' n5 S. q$ F/ M这其实是很直白的一种优化方式,我查了一下,有人专门发论文就讲了个类似的方法。, p, o# j) T1 `9 f" @
第四步:实现冷热数据分离; E" ]3 O2 o5 \! Q- ^9 Q
Platt 的文章里也证明过一旦某个 alpha 出于边界(0 或者 C)的时候,就很不容易变动,而且伪代码也是优先在工作集里寻找 > 0 and < C 的 alpha 值进行优化,找不到了,再对工作集整体的 alpha 值进行迭代。6 m# I$ y" X$ i
那么我们势必就可以把工作集分成两个部分,热数据在前(大于 0 小于 C 的 alpha 值),冷数据在后(小于等于 0 或者大于等于 C 的 alpha)。
& W% |' Z) z% o8 l0 [/ z2 U7 D随着迭代加深,会发现大部分时候只需要在热数据里求解,并且热数据的大小会逐步不停的收缩,所以区分了冷热以后 SVM 大部分都在针对有限的热数据迭代,偶尔不行了,再全部迭代一次,然后又回到冷热迭代,性能又能提高不少。/ W* K, r: s6 x5 q; u' z
第五步:支持 Ensemble
, U7 {  M- ~& u; p: B大家都知道,通过 Ensemble 可以让多个不同的弱模型组和成一个强模型,而传统 SVM 实现并不能适应一些类似 AdaBoost 的集成方法,所以我们需要做一些改动。可以让外面针对某一个分类传入一个“权重”过来,修正 SVM 的识别结果。* f/ C/ K' j; j: l6 d. T
最传统的修改方式就是将不等式约束 C 分为 Cp 和 Cn 两个针对 +1 分类的 C 及针对 -1 分类的 C。修改方式是直接用原始的 C 乘以各自分类的权重,得到 Cp 和 Cn,然后迭代时,不同的样本根据它的 y 值符号,用不同的 C 值带入计算。  {  s- L9 }+ g0 P6 D; ?
这样 SVM 就能用各种集成方法同其他模型一起组成更为强大精准的模型了。' t5 G) k) u+ d, N' y
实现到这一步你就得到了功能上和性能上同 libsvm 类似的东西,接下来我们继续优化。
, M+ D  @4 w& w& r. N第六步:继续优化核函数计算( B5 y7 S; C$ [) C% g7 {
核函数缓存非常消耗内存,libsvm 数学上已经没得挑了,但是工程方面还有很大改进余地,比如它的核缓存实现。& w( X. x& A7 X3 {% ?
由于标准 SVM 核函数用的是两个高维矢量的内积,根据内积的几个条件,SVM 的核函数又是一个正定核,即 K(xi, xj) = K(xj, xi),那么我们同样的内存还能再多存一倍的核函数,性能又能有所提升。8 ?6 w  ?3 C' ^8 T  ?0 R
针对核函数的计算和存储有很多优化方式,比如有人对 NxN 的核函数矩阵进行采样,只计算有限的几个核函数,然后通过插值的方式求解出中间的值。还有人用 float 存储核函数值,又降低了一倍空间需求。
: O6 K7 \4 v1 O! p8 L第七步:支持稀疏向量和非稀疏向量" |. w& j& x5 s$ I
对于高维样本,比如文字这些,可能有上千维,每个样本的非零特征可能就那么几个,所以稀疏向量会比较高效,libsvm 也是用的稀疏向量。
0 Q: P! c7 U' f6 V0 j8 F; p但是还有很多时候样本是密集向量,比如一共 200 个特征,大部分样本都有 100个以上的非零特征,用稀疏向量存储的话就非常低效了,openCV 的 SVM 实现就是非稀疏向量。1 @+ g) f' V- u$ x: w* e& Y
非稀疏向量直接是用数组保存样本每个特征的值,在工程方面就有很多优化方式了,比如用的最多的求核函数的时候,直接上 SIMD 指令或者 CUDA,就能获得更好的计算性能。
; A. b- \/ W* E: ~6 t所以最好的方式是同时支持稀疏和非稀疏,兼顾时间和空间效率,对不同的数据选择最适合的方式。2 y+ `& P! L* B9 v$ L) c
第八步:针对线性核进行优化
- y9 b; f! i0 t: g/ L* K5 w6 X传统的 SMO 方法,是 SVM 的通用求解方法,然而针对线性核,就是:7 i0 f; g& `9 t9 h
K(xi, xj) = xi . xj! r7 U: r9 f! J; `! P( ~$ P  m/ c
还有很多更高效的求解思路,比如 Pegasos 算法就用了一种类似随机梯度下降的方法,快速求 SVM 的解权重 w,如果你的样本适合线性核,使用一些针对性的非 SMO 算法可以极大的优化 SVM 求解,并且能处理更加庞大的数据集,LIBLINEAR 就是做这件事情的。. C, t0 }, i$ o0 P4 g9 a- H
同时这类算法也适合 online 训练和并行训练,可以逐步更新增量训练新的样本,还可以用到多核和分布式计算来训练模型,这是 SMO 算法做不到的地方。
& O! J, b0 @& _" q; w0 h; v; U但是如果碰到非线性核,权重 w 处于高维核空间里(有可能无限维),你没法梯度下降迭代 w,并且 pegasos 的 pdf 里面也没有提到如何用到非线性核上,LIBLINEAR 也没有办法处理非线性核。" x8 u! y0 ^: X  \3 r
或许哪天出个数学家又找到一种更好的方法,可以用类似 pegasos 的方式求解非线性核,那么 SVM 就能有比较大的进展了。9 g' `: Q6 q: y8 Z
后话
8 r' H9 V9 X% L上面八条,你如果实现前三条,基本就能深入理解 SVM 的原理了,如果实现一大半,就可以得到一个类似 libsvm 的东西,全部实现,你就能得到一个比 libsvm 更好用的 SVM 库了。$ b" W4 g) [5 n( M# W3 N5 _
上面就是如何实现一个相对成熟的 SVM 模型的思路,以及配套优化方法,再往后还有兴趣,可以接着实现支持向量回归,也是一个很有用的东西。+ ~) q1 P$ j& ^6 ~

3 ^6 P/ H+ \* Y* Z% N) s来源:http://www.yidianzixun.com/article/0Lv0UIiC1 p6 T1 U8 F- `& M& [5 W" l( t) |7 Q
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

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

×

帖子地址: 

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

使用道具 举报

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

本版积分规则

关闭

站长推荐上一条 /6 下一条

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

GMT+8, 2025-7-21 21:27 , Processed in 0.037487 second(s), 24 queries .

Powered by Mxzdjyxk! X3.5

© 2001-2025 Discuz! Team.

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