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

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 4861|回复: 0

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

[复制链接]

10

主题

0

回帖

10

积分

新手上路

积分
10
发表于 2019-5-8 03:16:31 | 显示全部楼层 |阅读模式 来自 中国
雷锋网 AI 科技评论按,本文为韦易笑在知乎问题如何学习SVM(支持向量机)以及改进实现SVM算法程序下面的回复,雷锋网 AI 科技评论获其授权转载。以下为正文:. N$ O6 V; g7 z& m  N# `
学习 SVM 的最好方法是实现一个 SVM,可讲理论的很多,讲实现的太少了。: F) k% U/ c4 R4 _( H
假设你已经读懂了 SVM 的原理,并了解公式怎么推导出来的,比如到这里:8 L/ M+ O5 Z5 _  p9 {$ Y

$ }, C4 ^0 X" R0 ~. \SVM 的问题就变成:求解一系列满足约束的 alpha 值,使得上面那个函数可以取到最小值。然后记录下这些非零的 alpha 值和对应样本中的 x 值和 y 值,就完成学习了,然后预测的时候用:, x- d. Z" I4 L! U1 L* t" E
" i% u; D1 r+ j; L, V$ H
上面的公式计算出 f(x) ,如果返回值 > 0 那么是 +1 类别,否则是 -1 类别,先把这一步怎么来的,为什么这么来找篇文章读懂,不然你会做的一头雾水。) q% K2 U6 H" u' Y5 W; u# t, ?
那么剩下的 SVM 实现问题就是如何求解这个函数的极值。方法有很多,我们先找个起点,比如 Platt 的 SMO 算法,它后面有伪代码描述怎么快速求解 SVM 的各个系数。3 w* _: L( d! h
第一步:实现传统的 SMO 算法* k& a' j8 P) t- f+ m3 a0 |, S$ g8 m
现在大部分的 SVM 开源实现,源头都是 platt 的 smo 算法,读完他的文章和推导,然后照着伪代码写就行了,核心代码没几行:
( D: H$ I3 |/ q4 f# p' I% A7 Y5 `procedure takeStep(i1,i2)! E% A' R( D) K; l( \5 s# N! J
if (i1 == i2) return 0
+ v1 Q5 X) |) K) G alph1 = Lagrange multiplier for i1/ a/ }5 f' u1 q; p

5 s3 F, T/ ~% N1 [; {+ f2 fy1 = target[i1]: u0 ?0 M/ u1 |0 J' u7 y  i

1 z% P' t- Q# B, I+ e4 pE1 = SVM output on point[i1] – y1 (check in error cache): e2 a7 I) w' G0 O. c
/ R' ^1 u9 K1 ?8 o
s = y1*y2
& o- v3 M, E) [2 _0 c. I& B# P, ^1 J* X" K' f
Compute L, H via equations (13) and (14)
% ^( F4 G% w/ I' A; U
1 |  Z* e5 M  {if (L == H)% m, B; y5 C7 |3 l

1 f3 \2 a4 v* C' lreturn 0
+ k# v2 |8 j* X3 v2 {0 a/ Q! K% s( \3 f7 O4 A+ [5 c
k11 = kernel(point[i1],point[i1])
5 n5 i& I- b: h4 }" h) T% Q
' k) {% X' ]- q* t  C6 c7 kk12 = kernel(point[i1],point[i2])0 @2 w1 I5 a" c! g. O
& v9 s/ L) P. n$ L) t7 W
k22 = kernel(point[i2],point[i2])
" _5 k) a0 |0 I- J1 j+ f* K% I* M* E5 A7 l' P
eta = k11+k22-2*k12# K* T* d( |1 s
1 F& `& i( F" T
if (eta > 0)
/ \; I  T3 z* n
  ?' b5 B$ t9 M5 W{
% ^1 D( F  }2 S0 Y1 ?$ _3 g4 j% p
a2 = alph2 + y2*(E1-E2)/eta
. u8 I: w$ |5 D0 a) k, p) ?. n% N% M6 I; U1 u
if (a2 < L) a2 = L
" p7 ]4 X* {# Z+ s8 Q
( l- M  B, ~! Z- Velse if (a2 > H) a2 = H7 ^# Z. N. u) ~3 X5 ~' {+ V4 h
% k4 t- p9 A+ H% {2 q! h; U4 P
}
% Q4 n0 g0 Z- g" T5 v; G7 T8 c
  K1 ^* K: K. oelse
: q. v/ Y* K3 c3 Y4 M( o
$ [8 _0 o$ ?  N3 j9 y8 K, z: E{
! @- ]! k! y) y2 c- l, g) o8 [8 k4 {! o' d0 s: _8 ?# N( b2 b
Lobj = objective function at a2=L
! t- i0 h) ]  e, t# O, f
8 V2 ?3 e) }0 O2 S) k  {2 w7 bHobj = objective function at a2=H0 n/ e* [% y. y$ K0 q: z5 G5 _! O

0 p& g, c2 y# s* s. wif (Lobj < Hobj-eps)
& m% a" d- P. t) I0 \, w" {
8 |3 \; @; g( S# Ta2 = L7 o& Y$ a5 z$ H: ~3 X

6 ~, Y. h4 t/ l7 r, Belse if (Lobj > Hobj+eps)4 U; s9 T! C- N& x, Q, q
# K' s. ^- D, C. R
a2 = H! I8 ]- N7 [: O+ J' j  r6 |
4 n6 u% ]: `, o7 F* l1 G  t  W# {
else( Z3 a9 O' Y* i* M. {4 F% [# H* Z! T
5 r2 Y6 u. a5 R* u' ~+ I: |0 U
a2 = alph2" O& ]* m* M% ~9 g& Q1 P
0 W3 _: e! k3 u! g5 `1 R
}' c! d4 h& m( t
5 u+ V- z" j/ {8 a+ R
if (|a2-alph2| < eps*(a2+alph2+eps))$ P# r  q1 r. S% G' T6 c: _' g4 P5 R

1 T, H# o+ M; x  A# w! o# ^return 0: k4 |, ~! ~3 R) ]( f  ^
7 l. I! ^6 A- b( L: ?2 `9 e; e. y1 I
a1 = alph1+s*(alph2-a2)9 L/ G  k- @# I8 J

! q# k6 X) ^2 E4 N0 VUpdate threshold to reflect change in Lagrange multipliers5 v. j4 b0 R6 D- V; K( m1 }
1 K$ L$ _5 Z% v1 ?2 t9 U, X" |
Update weight vector to reflect change in a1 & a2, if SVM is linear
* V( `* G! x4 M0 n3 A: E* L9 ^% @
Update error cache using new Lagrange multipliers
" `( \! W  r- E: ?, `3 b& f
: V; O, @( a* [5 C# QStore a1 in the alpha array
! L$ |2 G" |" d: z4 ^; H" Q, z5 y  b3 v, |, k1 u
Store a2 in the alpha array
" a; s8 v! C( T0 W: ?' H8 p- G2 n& u0 }5 c- i
return 1
; g8 d6 Y0 W- @9 ?$ N: Bendprocedure+ d0 `& m' f% E/ w/ G4 V2 T
核心代码很紧凑,就是给定两个 ai, aj,然后迭代出新的 ai, aj 出来,还有一层循环会不停的选择最需要被优化的系数 ai, aj,然后调用这个函数。如何更新权重和 b 变量(threshold)文章里面都有说,再多调试一下,可以用 python 先调试,再换成 C/C++,保证得到一个正确可用的 SVM 程序,这是后面的基础。' r9 H0 n' q2 X& {5 E
第二步:实现核函数缓存
$ }( n( j! a2 a$ _; D观察下上面的伪代码,开销最大的就是计算核函数 K(xi, xj),有些计算又反复用到,一个 100 个样本的数据集求解,假设总共要调用核函数 20 万次,但是 xi, xj 的组和只有 100x100=1 万种,有缓存的话你的效率可以提升 20 倍。, E6 z2 P* S1 o" y
样本太大时,如果你想存储所有核函数的组和,需要 N*N * sizeof(double) 的空间,如果训练集有 10 万个样本,那么需要 76 GB 的内存,显然是不可能实现的,所以核函数缓存是一个有限空间的 LRU 缓存,SVM 的 SMO 求解过程中其实会反复用到特定的几个有限的核函数求解,所以命中率不用担心。
  u9 k# i( B* w, k有了这个核函数缓存,你的 SVM 求解程序能瞬间快几十倍。
) ?% c, m; p* m9 U第三步:优化误差值求解
; F- v) y9 [3 z- s注意看上面的伪代码,里面需要计算一个估计值和真实值的误差 Ei 和 Ej,他们的求解方法是:4 c* {2 O. o1 j8 p
E(i) = f(xi) - yi  v! y6 D$ q9 V' S) B, T' I
这就是目前为止 SMO 这段为代码里代价最高的函数,因为回顾下上面的公式,计算一遍 f(x) 需要 for 循环做乘法加法。- V/ V; V. @" O' Y7 P4 B
platt 的文章建议是做一个 E 函数的缓存,方便后面选择 i, j 时比较,我看到很多入门版本 SVM 实现都是这么做。其实这是有问题的,后面我们会说到。最好的方式是定义一个 g(x) 令其等于:$ H' f" {  w. q+ d' {, i( [
. v8 F$ _8 l* i* o* i3 _
也就是 f(x) 公式除了 b 以外前面的最费时的计算,那么我们随时可以计算误差:
# E* k3 M' d9 m% f0 {" B! M$ JE(j) = g(xj) + b - yj
0 S3 p1 o* y& S$ G+ I/ D所以最好的办法是对 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 的偏导数就行了,具体代码类似:
6 M1 f3 ~, Z% X: F- ]* H4 ndouble Kik = kernel(i, k);
, V; Z2 l- A1 idouble Kjk = kernel(j, k);
- ~: s2 H& f) {5 p7 |G[k] += delta_alpha_i * Kik * y + delta_alpha_j * Kjk * y[j];6 G) ]! h$ ?3 G- z+ I
把这段代码放在 takeStep 后面,每次成功更新一对 ai, aj 以后,更新所有样本对应的 g(x) 缓存,这样通过每次迭代更新 g(x) 避免了大量的重复计算。
4 z1 B' T" [$ v0 I! A- n; B6 q$ L2 H" J这其实是很直白的一种优化方式,我查了一下,有人专门发论文就讲了个类似的方法。
% k7 i8 Q9 d7 d+ g第四步:实现冷热数据分离
0 _; R" Y/ b8 f8 n+ N7 hPlatt 的文章里也证明过一旦某个 alpha 出于边界(0 或者 C)的时候,就很不容易变动,而且伪代码也是优先在工作集里寻找 > 0 and < C 的 alpha 值进行优化,找不到了,再对工作集整体的 alpha 值进行迭代。
1 |' O' {5 E- o! P( h! U那么我们势必就可以把工作集分成两个部分,热数据在前(大于 0 小于 C 的 alpha 值),冷数据在后(小于等于 0 或者大于等于 C 的 alpha)。) A" G+ @+ N& N# V3 W1 X/ Y
随着迭代加深,会发现大部分时候只需要在热数据里求解,并且热数据的大小会逐步不停的收缩,所以区分了冷热以后 SVM 大部分都在针对有限的热数据迭代,偶尔不行了,再全部迭代一次,然后又回到冷热迭代,性能又能提高不少。/ \) r* q- K# y3 X* w: a" n/ \* E
第五步:支持 Ensemble
+ }; L. M7 c" v/ o' ?( z大家都知道,通过 Ensemble 可以让多个不同的弱模型组和成一个强模型,而传统 SVM 实现并不能适应一些类似 AdaBoost 的集成方法,所以我们需要做一些改动。可以让外面针对某一个分类传入一个“权重”过来,修正 SVM 的识别结果。( U! C  ]" H6 [4 }9 e1 S5 B
最传统的修改方式就是将不等式约束 C 分为 Cp 和 Cn 两个针对 +1 分类的 C 及针对 -1 分类的 C。修改方式是直接用原始的 C 乘以各自分类的权重,得到 Cp 和 Cn,然后迭代时,不同的样本根据它的 y 值符号,用不同的 C 值带入计算。
4 Y# ~/ o8 \) X+ V# }8 M这样 SVM 就能用各种集成方法同其他模型一起组成更为强大精准的模型了。
8 o- l1 k# _- c' c: l实现到这一步你就得到了功能上和性能上同 libsvm 类似的东西,接下来我们继续优化。
1 t$ u) |3 L6 w  x第六步:继续优化核函数计算
: {4 Z9 U" y* j2 Z6 p; V核函数缓存非常消耗内存,libsvm 数学上已经没得挑了,但是工程方面还有很大改进余地,比如它的核缓存实现。2 L) R/ a: j" ?- U9 w0 Q  |
由于标准 SVM 核函数用的是两个高维矢量的内积,根据内积的几个条件,SVM 的核函数又是一个正定核,即 K(xi, xj) = K(xj, xi),那么我们同样的内存还能再多存一倍的核函数,性能又能有所提升。, j! q0 R" g2 j0 L/ o
针对核函数的计算和存储有很多优化方式,比如有人对 NxN 的核函数矩阵进行采样,只计算有限的几个核函数,然后通过插值的方式求解出中间的值。还有人用 float 存储核函数值,又降低了一倍空间需求。
. R. C) m, z0 z0 W* g第七步:支持稀疏向量和非稀疏向量
# |7 y3 g& o: l, W& y6 J3 u对于高维样本,比如文字这些,可能有上千维,每个样本的非零特征可能就那么几个,所以稀疏向量会比较高效,libsvm 也是用的稀疏向量。) n2 q7 _. w  }* X; k
但是还有很多时候样本是密集向量,比如一共 200 个特征,大部分样本都有 100个以上的非零特征,用稀疏向量存储的话就非常低效了,openCV 的 SVM 实现就是非稀疏向量。) {0 q  [: [& U7 g- ~: a
非稀疏向量直接是用数组保存样本每个特征的值,在工程方面就有很多优化方式了,比如用的最多的求核函数的时候,直接上 SIMD 指令或者 CUDA,就能获得更好的计算性能。3 y7 W' n8 E8 T
所以最好的方式是同时支持稀疏和非稀疏,兼顾时间和空间效率,对不同的数据选择最适合的方式。
0 a8 U6 I6 i& Q' D( I第八步:针对线性核进行优化
  K3 K3 a. O+ g. F传统的 SMO 方法,是 SVM 的通用求解方法,然而针对线性核,就是:
, T5 `: @3 }6 t- AK(xi, xj) = xi . xj. P& I& i# I% @$ z$ A& a
还有很多更高效的求解思路,比如 Pegasos 算法就用了一种类似随机梯度下降的方法,快速求 SVM 的解权重 w,如果你的样本适合线性核,使用一些针对性的非 SMO 算法可以极大的优化 SVM 求解,并且能处理更加庞大的数据集,LIBLINEAR 就是做这件事情的。
+ f9 f& Z' _6 F2 u( h5 {. @/ X- n同时这类算法也适合 online 训练和并行训练,可以逐步更新增量训练新的样本,还可以用到多核和分布式计算来训练模型,这是 SMO 算法做不到的地方。5 Q$ c: V) J4 t2 ?7 U5 {# H# \
但是如果碰到非线性核,权重 w 处于高维核空间里(有可能无限维),你没法梯度下降迭代 w,并且 pegasos 的 pdf 里面也没有提到如何用到非线性核上,LIBLINEAR 也没有办法处理非线性核。
. r* f3 F) S: l" }3 s5 F5 V或许哪天出个数学家又找到一种更好的方法,可以用类似 pegasos 的方式求解非线性核,那么 SVM 就能有比较大的进展了。
; t" H0 ~7 p. W) y后话
; I9 a- V& e. g+ {6 x- E9 S上面八条,你如果实现前三条,基本就能深入理解 SVM 的原理了,如果实现一大半,就可以得到一个类似 libsvm 的东西,全部实现,你就能得到一个比 libsvm 更好用的 SVM 库了。) f* d/ m; E$ `" {; r% A
上面就是如何实现一个相对成熟的 SVM 模型的思路,以及配套优化方法,再往后还有兴趣,可以接着实现支持向量回归,也是一个很有用的东西。
) t5 D3 E" t. p, q, ]3 Z
4 Y- E  J4 a9 G/ t! w来源:http://www.yidianzixun.com/article/0Lv0UIiC0 M- u. ]% J( a$ I
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

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

×

帖子地址: 

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

使用道具 举报

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

本版积分规则

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

GMT+8, 2025-11-6 16:38 , Processed in 0.039545 second(s), 23 queries .

Powered by Mxzdjyxk! X3.5

© 2001-2025 Discuz! Team.

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