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

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 4998|回复: 0

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

[复制链接]

10

主题

0

回帖

10

积分

新手上路

积分
10
发表于 2019-5-8 03:16:31 | 显示全部楼层 |阅读模式 来自 中国
雷锋网 AI 科技评论按,本文为韦易笑在知乎问题如何学习SVM(支持向量机)以及改进实现SVM算法程序下面的回复,雷锋网 AI 科技评论获其授权转载。以下为正文:8 }0 f0 [7 F! J
学习 SVM 的最好方法是实现一个 SVM,可讲理论的很多,讲实现的太少了。7 _' U  g3 P' \
假设你已经读懂了 SVM 的原理,并了解公式怎么推导出来的,比如到这里:0 q! F! Q+ a' P( s5 f6 P$ H

+ s8 ^  t7 K8 c; b/ R& I' _, vSVM 的问题就变成:求解一系列满足约束的 alpha 值,使得上面那个函数可以取到最小值。然后记录下这些非零的 alpha 值和对应样本中的 x 值和 y 值,就完成学习了,然后预测的时候用:! R* X: Y( X2 k$ \

4 m, K$ E/ j+ o, ~  c上面的公式计算出 f(x) ,如果返回值 > 0 那么是 +1 类别,否则是 -1 类别,先把这一步怎么来的,为什么这么来找篇文章读懂,不然你会做的一头雾水。0 p% E3 n+ c) }" M* v
那么剩下的 SVM 实现问题就是如何求解这个函数的极值。方法有很多,我们先找个起点,比如 Platt 的 SMO 算法,它后面有伪代码描述怎么快速求解 SVM 的各个系数。$ U0 P; p  C$ s' ?
第一步:实现传统的 SMO 算法: `) p1 {9 Y& [- I
现在大部分的 SVM 开源实现,源头都是 platt 的 smo 算法,读完他的文章和推导,然后照着伪代码写就行了,核心代码没几行:" V* v2 H! z3 W; n
procedure takeStep(i1,i2)
# G: z) D7 q! [8 E( \/ X$ c* P if (i1 == i2) return 0( r1 G, ~9 J1 T' V
alph1 = Lagrange multiplier for i1
3 K4 O, d; ^, G  ~
9 g  Y8 f4 G$ _8 i0 v. dy1 = target[i1]. w" q9 D$ O* n  U

9 E' C. [/ j- {0 z+ vE1 = SVM output on point[i1] – y1 (check in error cache)
" w  @8 i' c& i$ l
, h1 H9 w7 @+ m8 J# x# s7 qs = y1*y2
5 a! |# e$ w$ t, J1 K& s
1 s+ Z# f2 g6 Z6 A8 I* G  R& XCompute L, H via equations (13) and (14)
5 G! e3 H/ K3 t% X2 g
" H% ^, C6 A% S: c7 ~) b, Sif (L == H)
/ `6 _. R; u1 U
& \2 O# T" R; U" ereturn 0
2 c2 v* f" \1 |
, i7 U& Y! K5 N. T1 ik11 = kernel(point[i1],point[i1])
# @5 Q! }% O2 G- S8 g6 d7 \
# F$ g" M: R8 _4 k' Ak12 = kernel(point[i1],point[i2])
) G$ {8 W5 |9 y4 j( {* V" y. h. u
( N9 l& {4 Y. j  t  Bk22 = kernel(point[i2],point[i2])6 `: s- u) o, e2 a
7 l* D/ O( X) ~" u
eta = k11+k22-2*k124 f2 F4 z: u7 {8 l

) k3 B) l4 _! y  `7 ~+ r, j6 [if (eta > 0)
6 u* I/ o; X7 p4 Y- ?* {0 G& h- c
7 h1 H6 w/ k+ A& V0 W{
- F- P) @* F! I' I. Q4 D
5 k6 l3 f. O) ^+ L9 d$ Za2 = alph2 + y2*(E1-E2)/eta: U# ^0 `# }8 Q

, `& B1 q: V- ^: V6 Sif (a2 < L) a2 = L8 Y7 T4 f7 t, X3 M* z
3 k. y) e. b+ X" N
else if (a2 > H) a2 = H
5 q/ b! P" F3 e3 p5 `( W& {3 Z
, t4 o# g" K0 n1 u& s, ~% A}# A2 D6 X+ e  H
( ]2 @6 C9 i/ f0 t
else- @2 `2 [: t3 Z" ~3 e0 z$ j& N2 e/ {
/ E3 y$ v' x( @- e# X7 ~. @
{, q( {  b) ~2 x6 @% v/ Q$ e1 C% c
0 h* ]* Z) N3 \$ t% g) `
Lobj = objective function at a2=L
& W% _1 d: P+ Y9 S! l, Q5 ^. l  {! Q- w
Hobj = objective function at a2=H" ^3 {* c3 r8 s5 |1 [; ?( b! g
% j) i' T: d" M  R, u1 W* m1 a
if (Lobj < Hobj-eps)
5 U) N5 i: h0 E7 f' d- `5 G9 N
7 O5 }" f) R) S- k) \' \a2 = L
' |. v3 X  x0 |6 r+ b+ B3 \/ v2 \9 l
else if (Lobj > Hobj+eps)
9 d* C$ [. j2 d/ o: _' i: P; L6 J8 k3 t0 @$ q3 s2 g3 Q& S* D; t5 E
a2 = H( ?/ h0 [/ {0 D' j: s; [1 m. Q4 W
7 Z3 g5 }! s. Z- l+ o* y
else
; F  }$ y4 {3 e+ X; c" ]8 E/ `% {2 a2 n+ u* j. I( F% o
a2 = alph2# n7 w" X! c7 H# A5 u1 g9 p
) y# ^. F* {- q- p; s
}
1 h. s  D) W2 \6 G' V9 u% c+ c- |7 ]/ @$ Y* B* h
if (|a2-alph2| < eps*(a2+alph2+eps)), W1 E" H- T" `$ g) N0 r6 q2 D# s
. G- ~5 c* t' {4 S+ R+ D3 Z" W& @
return 0
0 Z; U' X+ m2 F4 X: y
9 ^8 V$ y, w$ ~: Fa1 = alph1+s*(alph2-a2)1 [+ K6 X3 v% G( A

0 {! ~) h. d% T. A, x1 h" gUpdate threshold to reflect change in Lagrange multipliers
6 b- B2 w5 h" x1 z: D* o  t7 v
! s; j4 i* Y7 ]+ f" t0 V; N5 q5 TUpdate weight vector to reflect change in a1 & a2, if SVM is linear% ?) h) Z" B/ S$ f; q# t
1 L* p2 n7 o% l. H
Update error cache using new Lagrange multipliers: B* I- ^) `2 k; [2 `7 _# t9 Y

- H1 ?$ l1 q  v% T" X" W& @Store a1 in the alpha array6 @. a# p7 m2 p0 G3 E4 M, w
  {3 ~2 [6 l6 w, m- e5 w/ C* p8 |: v
Store a2 in the alpha array
* t7 a) y$ L: U. u& j& U4 Z1 e8 [8 m6 ]" C8 p# W# S* ?: H* w
return 1; G0 J5 P) p! t& [) t0 C% v2 Z7 w
endprocedure# \2 _) j3 ?4 G7 d8 ]8 K
核心代码很紧凑,就是给定两个 ai, aj,然后迭代出新的 ai, aj 出来,还有一层循环会不停的选择最需要被优化的系数 ai, aj,然后调用这个函数。如何更新权重和 b 变量(threshold)文章里面都有说,再多调试一下,可以用 python 先调试,再换成 C/C++,保证得到一个正确可用的 SVM 程序,这是后面的基础。
# u6 o( h' x+ R3 ^, K7 k第二步:实现核函数缓存
2 N. \1 g5 e! a4 Y, E4 O. N& b+ P: x观察下上面的伪代码,开销最大的就是计算核函数 K(xi, xj),有些计算又反复用到,一个 100 个样本的数据集求解,假设总共要调用核函数 20 万次,但是 xi, xj 的组和只有 100x100=1 万种,有缓存的话你的效率可以提升 20 倍。# b3 e2 c2 A: i5 Q# N
样本太大时,如果你想存储所有核函数的组和,需要 N*N * sizeof(double) 的空间,如果训练集有 10 万个样本,那么需要 76 GB 的内存,显然是不可能实现的,所以核函数缓存是一个有限空间的 LRU 缓存,SVM 的 SMO 求解过程中其实会反复用到特定的几个有限的核函数求解,所以命中率不用担心。& N; O$ @7 T  [2 @1 \6 E
有了这个核函数缓存,你的 SVM 求解程序能瞬间快几十倍。2 c. O9 g, r$ G1 x; y4 }: o, k. S
第三步:优化误差值求解
$ \; c& i. K4 n. s2 Z注意看上面的伪代码,里面需要计算一个估计值和真实值的误差 Ei 和 Ej,他们的求解方法是:
$ _1 t) W7 {0 e7 O$ NE(i) = f(xi) - yi; ]" l$ x# R4 x, Y, i" k5 `! v
这就是目前为止 SMO 这段为代码里代价最高的函数,因为回顾下上面的公式,计算一遍 f(x) 需要 for 循环做乘法加法。
" @, c4 a" s3 [# a8 {platt 的文章建议是做一个 E 函数的缓存,方便后面选择 i, j 时比较,我看到很多入门版本 SVM 实现都是这么做。其实这是有问题的,后面我们会说到。最好的方式是定义一个 g(x) 令其等于:
+ c0 }7 q2 Y* s( q/ W$ ?6 h
, m9 p; J9 [# Y, t( D, V也就是 f(x) 公式除了 b 以外前面的最费时的计算,那么我们随时可以计算误差:/ p6 Q8 u6 J/ I% }4 Z( j' {
E(j) = g(xj) + b - yj
& x- p$ m8 [) V& 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 的偏导数就行了,具体代码类似:) w0 U4 n0 @' O0 p- f/ k5 y
double Kik = kernel(i, k);
, n% S+ B* C6 K7 j9 p7 J1 k  ~, [double Kjk = kernel(j, k);
" {. u: \( L4 C* s# e3 J+ {" gG[k] += delta_alpha_i * Kik * y + delta_alpha_j * Kjk * y[j];; J/ n* k7 C" H, _( m1 C
把这段代码放在 takeStep 后面,每次成功更新一对 ai, aj 以后,更新所有样本对应的 g(x) 缓存,这样通过每次迭代更新 g(x) 避免了大量的重复计算。
% ?9 z) W& w6 x. A# ]- ^$ U, X这其实是很直白的一种优化方式,我查了一下,有人专门发论文就讲了个类似的方法。
. p: {1 q9 L9 b& m  ^/ ?第四步:实现冷热数据分离
/ ^& n0 x6 R' D% H& y6 fPlatt 的文章里也证明过一旦某个 alpha 出于边界(0 或者 C)的时候,就很不容易变动,而且伪代码也是优先在工作集里寻找 > 0 and < C 的 alpha 值进行优化,找不到了,再对工作集整体的 alpha 值进行迭代。
$ r5 D% D2 ~; d4 N  H9 r' h那么我们势必就可以把工作集分成两个部分,热数据在前(大于 0 小于 C 的 alpha 值),冷数据在后(小于等于 0 或者大于等于 C 的 alpha)。. J2 X0 K) ?& k+ b# v2 W
随着迭代加深,会发现大部分时候只需要在热数据里求解,并且热数据的大小会逐步不停的收缩,所以区分了冷热以后 SVM 大部分都在针对有限的热数据迭代,偶尔不行了,再全部迭代一次,然后又回到冷热迭代,性能又能提高不少。
8 h4 e/ ?! ~4 S' B第五步:支持 Ensemble, m! q% {# E$ n
大家都知道,通过 Ensemble 可以让多个不同的弱模型组和成一个强模型,而传统 SVM 实现并不能适应一些类似 AdaBoost 的集成方法,所以我们需要做一些改动。可以让外面针对某一个分类传入一个“权重”过来,修正 SVM 的识别结果。
# A. Y$ h$ g- f+ ?9 J+ P9 }最传统的修改方式就是将不等式约束 C 分为 Cp 和 Cn 两个针对 +1 分类的 C 及针对 -1 分类的 C。修改方式是直接用原始的 C 乘以各自分类的权重,得到 Cp 和 Cn,然后迭代时,不同的样本根据它的 y 值符号,用不同的 C 值带入计算。
; v$ m  x- _" `, D这样 SVM 就能用各种集成方法同其他模型一起组成更为强大精准的模型了。3 i6 W4 x" S+ Z3 U" o1 y+ \
实现到这一步你就得到了功能上和性能上同 libsvm 类似的东西,接下来我们继续优化。1 m/ [0 h+ Q8 P4 A) s1 w! X
第六步:继续优化核函数计算
% |& E0 K! c" [9 b5 S  q核函数缓存非常消耗内存,libsvm 数学上已经没得挑了,但是工程方面还有很大改进余地,比如它的核缓存实现。
) x) i" z3 _! D6 x/ G( G8 B- \2 q由于标准 SVM 核函数用的是两个高维矢量的内积,根据内积的几个条件,SVM 的核函数又是一个正定核,即 K(xi, xj) = K(xj, xi),那么我们同样的内存还能再多存一倍的核函数,性能又能有所提升。  o/ V  [9 F8 K" p  g
针对核函数的计算和存储有很多优化方式,比如有人对 NxN 的核函数矩阵进行采样,只计算有限的几个核函数,然后通过插值的方式求解出中间的值。还有人用 float 存储核函数值,又降低了一倍空间需求。1 ]5 L) [0 N* i, r7 ^
第七步:支持稀疏向量和非稀疏向量( B& p7 B% J. i/ {
对于高维样本,比如文字这些,可能有上千维,每个样本的非零特征可能就那么几个,所以稀疏向量会比较高效,libsvm 也是用的稀疏向量。! B. Z* I, E% m" t+ m4 }  z# a
但是还有很多时候样本是密集向量,比如一共 200 个特征,大部分样本都有 100个以上的非零特征,用稀疏向量存储的话就非常低效了,openCV 的 SVM 实现就是非稀疏向量。
# M4 C: v. R; x; y1 k$ B* E非稀疏向量直接是用数组保存样本每个特征的值,在工程方面就有很多优化方式了,比如用的最多的求核函数的时候,直接上 SIMD 指令或者 CUDA,就能获得更好的计算性能。
! J. [; w/ Q: o4 Q* {所以最好的方式是同时支持稀疏和非稀疏,兼顾时间和空间效率,对不同的数据选择最适合的方式。  Q6 C1 V4 l  J: |
第八步:针对线性核进行优化
3 V5 Q% g# |  }$ w" A" T7 x传统的 SMO 方法,是 SVM 的通用求解方法,然而针对线性核,就是:; O5 z2 X3 t2 B9 r( Y9 Y
K(xi, xj) = xi . xj
2 h5 p4 b3 X3 A3 C7 B, Y& f还有很多更高效的求解思路,比如 Pegasos 算法就用了一种类似随机梯度下降的方法,快速求 SVM 的解权重 w,如果你的样本适合线性核,使用一些针对性的非 SMO 算法可以极大的优化 SVM 求解,并且能处理更加庞大的数据集,LIBLINEAR 就是做这件事情的。
$ y; i& |9 [+ N& `同时这类算法也适合 online 训练和并行训练,可以逐步更新增量训练新的样本,还可以用到多核和分布式计算来训练模型,这是 SMO 算法做不到的地方。
( _- a' X& K4 Q/ A4 `! A但是如果碰到非线性核,权重 w 处于高维核空间里(有可能无限维),你没法梯度下降迭代 w,并且 pegasos 的 pdf 里面也没有提到如何用到非线性核上,LIBLINEAR 也没有办法处理非线性核。# |# k% E0 O% m" a6 _
或许哪天出个数学家又找到一种更好的方法,可以用类似 pegasos 的方式求解非线性核,那么 SVM 就能有比较大的进展了。0 s6 \- }; i# ?- [( v$ A, V3 Q
后话
" w! H' X5 u; i: @上面八条,你如果实现前三条,基本就能深入理解 SVM 的原理了,如果实现一大半,就可以得到一个类似 libsvm 的东西,全部实现,你就能得到一个比 libsvm 更好用的 SVM 库了。+ D7 l7 ?2 m9 F. c' N2 \
上面就是如何实现一个相对成熟的 SVM 模型的思路,以及配套优化方法,再往后还有兴趣,可以接着实现支持向量回归,也是一个很有用的东西。
8 W$ g  i, V8 T
# @, i1 i4 k; U. A来源:http://www.yidianzixun.com/article/0Lv0UIiC
( A: g# ?5 W0 m+ j! N  E免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

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

×

帖子地址: 

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

使用道具 举报

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

本版积分规则

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

GMT+8, 2026-7-17 10:13 , Processed in 0.039172 second(s), 24 queries .

Powered by Mxzdjyxk! X3.5

© 2001-2026 Discuz! Team.

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