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

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 4904|回复: 0

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

[复制链接]

10

主题

0

回帖

10

积分

新手上路

积分
10
发表于 2019-5-8 03:16:31 | 显示全部楼层 |阅读模式 来自 中国
雷锋网 AI 科技评论按,本文为韦易笑在知乎问题如何学习SVM(支持向量机)以及改进实现SVM算法程序下面的回复,雷锋网 AI 科技评论获其授权转载。以下为正文:
: m. f6 U9 B1 H. g0 e5 o& P学习 SVM 的最好方法是实现一个 SVM,可讲理论的很多,讲实现的太少了。
. M* q! x6 E0 X+ z假设你已经读懂了 SVM 的原理,并了解公式怎么推导出来的,比如到这里:
) K8 N/ w. p( V/ P/ y. Q+ X+ i+ ~! E) Y0 I& G$ H1 Y1 ]
SVM 的问题就变成:求解一系列满足约束的 alpha 值,使得上面那个函数可以取到最小值。然后记录下这些非零的 alpha 值和对应样本中的 x 值和 y 值,就完成学习了,然后预测的时候用:
% B% G$ C, S+ x2 {
! E4 k3 C+ Q# y/ E! C/ K* f/ E+ E上面的公式计算出 f(x) ,如果返回值 > 0 那么是 +1 类别,否则是 -1 类别,先把这一步怎么来的,为什么这么来找篇文章读懂,不然你会做的一头雾水。
% D9 d: b. [7 W+ m9 k, G. A那么剩下的 SVM 实现问题就是如何求解这个函数的极值。方法有很多,我们先找个起点,比如 Platt 的 SMO 算法,它后面有伪代码描述怎么快速求解 SVM 的各个系数。
( t: B/ R% v/ r+ ?/ A  h: x1 C第一步:实现传统的 SMO 算法
1 Q% i6 O- o3 g/ c现在大部分的 SVM 开源实现,源头都是 platt 的 smo 算法,读完他的文章和推导,然后照着伪代码写就行了,核心代码没几行:
6 p# S$ {) i3 ?, o3 Q+ A! kprocedure takeStep(i1,i2)' s. d; U. |; J$ @
if (i1 == i2) return 0
. q+ ^+ B3 G& U7 x alph1 = Lagrange multiplier for i1
+ M3 D# h% V# K( A7 [
5 E  N" R$ @* \% \y1 = target[i1]2 N/ D; o% N* f$ C! `

0 F" U. a2 _$ B% E; L3 kE1 = SVM output on point[i1] – y1 (check in error cache)& _7 U$ n; f; x8 x) Z9 D$ P! ^
2 w; w0 z; ]" \$ _
s = y1*y26 N5 v" [& s* u( M

% Y8 I! s  m; F, ACompute L, H via equations (13) and (14)1 O, y( \! A+ }3 _, L3 B. z3 P
) a+ c  ^; k6 C, R  a
if (L == H), [  e, P9 I7 W, _5 K7 N7 E
3 {- [% E0 P3 S3 M2 b
return 0
6 ?& B+ E6 p' M% H
  `) G0 H$ v+ Dk11 = kernel(point[i1],point[i1])
9 y2 s* e, r4 h3 B  s4 v8 d
5 X; W5 a  ~: Y, Ok12 = kernel(point[i1],point[i2]), t) ]- F" u3 w$ l1 P# Y* u

9 L+ w! ~) u% b  Mk22 = kernel(point[i2],point[i2])$ J/ o9 t3 x' p/ ~- w

& ]& d4 H$ X8 O% [eta = k11+k22-2*k12
; U; T! V8 D0 z3 x& X" B8 E3 S3 V* m
if (eta > 0)
3 |) G- W1 d: N, B
1 n  ?' S: X. b# p; Y5 n, X, X{( k) j& [' R" r1 [7 I
- W; F' _2 n5 z$ M. i% s
a2 = alph2 + y2*(E1-E2)/eta5 I% H8 f5 W8 P1 z2 N
8 [8 g# x0 |  l  v" S
if (a2 < L) a2 = L
% @* E$ O6 H6 q  ~6 P4 l
* w$ t  u* Y- u, n% yelse if (a2 > H) a2 = H, F& u) B5 D% B" O! ~
( z7 Z4 e+ J  k5 {0 T7 b
}; w3 g* h. N- y  v) r; N
2 ]) E+ x1 A/ ?9 m3 p- R6 a
else
  B7 u; T) V- s/ V& m: A! @8 L& C9 v7 r2 T; @
{
, v7 Q6 R, W# ?; o' t1 W; b+ O
. d% W# w. G& F0 T5 _; e6 Z2 {6 h2 L+ HLobj = objective function at a2=L
: H& W- \/ @  n* m
. F" f7 {( e3 ]4 c0 ~+ a8 U3 _Hobj = objective function at a2=H
# l7 ~) ?7 p) Y0 y7 c0 S5 U
1 h% P$ p6 x; u+ Lif (Lobj < Hobj-eps): ^9 y0 ?0 g. V7 s: {- g/ v& A
: t3 [( J" U7 W5 j6 B; ^' {% C0 o" m
a2 = L$ |: p! N2 z( @* ^: x9 h& V! r% S
  v# b$ X' u; p! h
else if (Lobj > Hobj+eps)
2 d$ F6 t( O, X2 I% J" R: J" X1 q8 y( B
a2 = H7 L* l) d) M. [; a$ M) W, |1 F$ m

/ J- S! s4 m& ~8 t: Q, D  [" felse; q" F) R5 U7 U" N2 F7 W

6 u: m/ ]# P8 G1 ~a2 = alph2
8 C  Y+ K& f. A. z6 ?. X
, Q" {7 l1 ?8 d- p}
5 o6 l3 K) B7 G1 H) u7 V( c" B/ Y0 T4 D* H6 E
if (|a2-alph2| < eps*(a2+alph2+eps))
; `! c" N0 m  o. A& D# y9 J8 `3 `2 S& }  b
return 00 ~' s& M7 }; [  O/ Q  d0 F

8 r3 n' [; ~! W8 x. r2 q% c5 xa1 = alph1+s*(alph2-a2)+ t4 D3 L) X% ?0 Y

' u; }+ _  d9 }Update threshold to reflect change in Lagrange multipliers
7 b% F# E6 z! h3 q; P9 Y/ ~7 x6 C
- {1 I$ M2 R( a. xUpdate weight vector to reflect change in a1 & a2, if SVM is linear
' D: h# {, j8 K; a: U% A- h" f6 Y* `" _3 Q' p
Update error cache using new Lagrange multipliers
' W" B& q& K4 d! S* k2 _* k" X: {; Y
. y  H2 _8 @$ aStore a1 in the alpha array8 v& u: S! F, G( P" z6 S: Q( k
, k/ b* q, }' F& F
Store a2 in the alpha array6 a* ^: g8 N( R
9 c3 F8 j& g0 g8 ?% h
return 1
, ]% |; C  {& o2 v$ Y( |. i( Tendprocedure
! ^0 A! e- e9 Y  o& q. R; M. B+ C! ~核心代码很紧凑,就是给定两个 ai, aj,然后迭代出新的 ai, aj 出来,还有一层循环会不停的选择最需要被优化的系数 ai, aj,然后调用这个函数。如何更新权重和 b 变量(threshold)文章里面都有说,再多调试一下,可以用 python 先调试,再换成 C/C++,保证得到一个正确可用的 SVM 程序,这是后面的基础。
0 e, P- u: Y) @第二步:实现核函数缓存1 ?% w! o% b9 J1 Y" I
观察下上面的伪代码,开销最大的就是计算核函数 K(xi, xj),有些计算又反复用到,一个 100 个样本的数据集求解,假设总共要调用核函数 20 万次,但是 xi, xj 的组和只有 100x100=1 万种,有缓存的话你的效率可以提升 20 倍。
. H$ o- a+ z% t7 O样本太大时,如果你想存储所有核函数的组和,需要 N*N * sizeof(double) 的空间,如果训练集有 10 万个样本,那么需要 76 GB 的内存,显然是不可能实现的,所以核函数缓存是一个有限空间的 LRU 缓存,SVM 的 SMO 求解过程中其实会反复用到特定的几个有限的核函数求解,所以命中率不用担心。
/ \- Z$ i, O: Q' z有了这个核函数缓存,你的 SVM 求解程序能瞬间快几十倍。
4 ^4 Y5 d% ^+ ]  c. t第三步:优化误差值求解
6 A; E! N# s7 z; Q0 h) c1 z) N) D注意看上面的伪代码,里面需要计算一个估计值和真实值的误差 Ei 和 Ej,他们的求解方法是:9 [( F5 X8 g  H/ B5 d+ |" r3 I/ J
E(i) = f(xi) - yi9 O/ B6 b* |8 T% p
这就是目前为止 SMO 这段为代码里代价最高的函数,因为回顾下上面的公式,计算一遍 f(x) 需要 for 循环做乘法加法。8 I4 h  q  ^9 F
platt 的文章建议是做一个 E 函数的缓存,方便后面选择 i, j 时比较,我看到很多入门版本 SVM 实现都是这么做。其实这是有问题的,后面我们会说到。最好的方式是定义一个 g(x) 令其等于:
$ e: ?6 i! Q, e6 e- [' s& e: b. e/ \% m/ q, d/ J6 ^: K
也就是 f(x) 公式除了 b 以外前面的最费时的计算,那么我们随时可以计算误差:
4 f! @5 C# ?8 X" J' U9 [( i" fE(j) = g(xj) + b - yj! C& P& N3 ~% G( _
所以最好的办法是对 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 的偏导数就行了,具体代码类似:) \. Y6 O1 Q  W. H1 w
double Kik = kernel(i, k);
. c' b3 m1 \9 x! U+ H2 [! t2 j) Hdouble Kjk = kernel(j, k);; i+ p& H4 S" r  [/ W
G[k] += delta_alpha_i * Kik * y + delta_alpha_j * Kjk * y[j];5 P% X3 P) B, s
把这段代码放在 takeStep 后面,每次成功更新一对 ai, aj 以后,更新所有样本对应的 g(x) 缓存,这样通过每次迭代更新 g(x) 避免了大量的重复计算。
# V+ W& ~! @3 n* _2 D这其实是很直白的一种优化方式,我查了一下,有人专门发论文就讲了个类似的方法。; i+ B$ O* _, D" Q
第四步:实现冷热数据分离/ T% e* c# j$ O. X
Platt 的文章里也证明过一旦某个 alpha 出于边界(0 或者 C)的时候,就很不容易变动,而且伪代码也是优先在工作集里寻找 > 0 and < C 的 alpha 值进行优化,找不到了,再对工作集整体的 alpha 值进行迭代。
; L& E7 G3 G; u4 M: w6 l! a那么我们势必就可以把工作集分成两个部分,热数据在前(大于 0 小于 C 的 alpha 值),冷数据在后(小于等于 0 或者大于等于 C 的 alpha)。
  H$ {: D( M! e1 M, ]+ @随着迭代加深,会发现大部分时候只需要在热数据里求解,并且热数据的大小会逐步不停的收缩,所以区分了冷热以后 SVM 大部分都在针对有限的热数据迭代,偶尔不行了,再全部迭代一次,然后又回到冷热迭代,性能又能提高不少。+ B+ d7 q, I' J9 {8 G9 F6 `8 g
第五步:支持 Ensemble4 U, W; o' g9 U7 {
大家都知道,通过 Ensemble 可以让多个不同的弱模型组和成一个强模型,而传统 SVM 实现并不能适应一些类似 AdaBoost 的集成方法,所以我们需要做一些改动。可以让外面针对某一个分类传入一个“权重”过来,修正 SVM 的识别结果。
% @, a1 d/ ~$ ]" q  \, z最传统的修改方式就是将不等式约束 C 分为 Cp 和 Cn 两个针对 +1 分类的 C 及针对 -1 分类的 C。修改方式是直接用原始的 C 乘以各自分类的权重,得到 Cp 和 Cn,然后迭代时,不同的样本根据它的 y 值符号,用不同的 C 值带入计算。; d$ }9 W' H/ h" _  B7 @& |
这样 SVM 就能用各种集成方法同其他模型一起组成更为强大精准的模型了。
1 D+ P/ X9 M4 R9 ^9 W实现到这一步你就得到了功能上和性能上同 libsvm 类似的东西,接下来我们继续优化。
9 f. G$ ~6 B9 d' g第六步:继续优化核函数计算; B* \& T: {6 D% e5 _  @; F9 @
核函数缓存非常消耗内存,libsvm 数学上已经没得挑了,但是工程方面还有很大改进余地,比如它的核缓存实现。5 e& A$ O7 A& o' m
由于标准 SVM 核函数用的是两个高维矢量的内积,根据内积的几个条件,SVM 的核函数又是一个正定核,即 K(xi, xj) = K(xj, xi),那么我们同样的内存还能再多存一倍的核函数,性能又能有所提升。; P# o% p4 a$ @' a
针对核函数的计算和存储有很多优化方式,比如有人对 NxN 的核函数矩阵进行采样,只计算有限的几个核函数,然后通过插值的方式求解出中间的值。还有人用 float 存储核函数值,又降低了一倍空间需求。" h2 F" h$ {$ Y) `. d* b$ n
第七步:支持稀疏向量和非稀疏向量
& s: @" y: R$ n: o0 t对于高维样本,比如文字这些,可能有上千维,每个样本的非零特征可能就那么几个,所以稀疏向量会比较高效,libsvm 也是用的稀疏向量。
! b4 y3 `7 p' x) j0 U4 }# E但是还有很多时候样本是密集向量,比如一共 200 个特征,大部分样本都有 100个以上的非零特征,用稀疏向量存储的话就非常低效了,openCV 的 SVM 实现就是非稀疏向量。
; s& J% c2 S' G2 v非稀疏向量直接是用数组保存样本每个特征的值,在工程方面就有很多优化方式了,比如用的最多的求核函数的时候,直接上 SIMD 指令或者 CUDA,就能获得更好的计算性能。; f1 E( m6 A) {' ^$ x
所以最好的方式是同时支持稀疏和非稀疏,兼顾时间和空间效率,对不同的数据选择最适合的方式。
0 X' R- Q3 D- @第八步:针对线性核进行优化
' W/ ?4 @+ ^) r  |! x4 P2 l传统的 SMO 方法,是 SVM 的通用求解方法,然而针对线性核,就是:
1 `2 R6 J' h7 E! j4 vK(xi, xj) = xi . xj. b3 B$ V- D+ E( w( E
还有很多更高效的求解思路,比如 Pegasos 算法就用了一种类似随机梯度下降的方法,快速求 SVM 的解权重 w,如果你的样本适合线性核,使用一些针对性的非 SMO 算法可以极大的优化 SVM 求解,并且能处理更加庞大的数据集,LIBLINEAR 就是做这件事情的。
  k0 a0 m$ v: ?! W1 {# \同时这类算法也适合 online 训练和并行训练,可以逐步更新增量训练新的样本,还可以用到多核和分布式计算来训练模型,这是 SMO 算法做不到的地方。
# n3 v) I- g5 ~9 _+ ]. R* V但是如果碰到非线性核,权重 w 处于高维核空间里(有可能无限维),你没法梯度下降迭代 w,并且 pegasos 的 pdf 里面也没有提到如何用到非线性核上,LIBLINEAR 也没有办法处理非线性核。
9 g7 \2 E  d$ F7 C或许哪天出个数学家又找到一种更好的方法,可以用类似 pegasos 的方式求解非线性核,那么 SVM 就能有比较大的进展了。" I8 l- _% j# c$ v% v
后话
2 Z1 V- V' U/ b& d9 Y1 h8 V6 d上面八条,你如果实现前三条,基本就能深入理解 SVM 的原理了,如果实现一大半,就可以得到一个类似 libsvm 的东西,全部实现,你就能得到一个比 libsvm 更好用的 SVM 库了。6 a% q  j5 v" {5 e5 h
上面就是如何实现一个相对成熟的 SVM 模型的思路,以及配套优化方法,再往后还有兴趣,可以接着实现支持向量回归,也是一个很有用的东西。" p! p* z4 }- Y8 i2 w/ I- |' ~, z

6 A2 z6 e3 O. Z% g' k$ h/ X/ A来源:http://www.yidianzixun.com/article/0Lv0UIiC, |. w; P: ]3 @5 u5 F: s8 }& Y  t" |
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

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

×

帖子地址: 

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

使用道具 举报

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

本版积分规则

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

GMT+8, 2026-3-19 08:10 , Processed in 0.037063 second(s), 24 queries .

Powered by Mxzdjyxk! X3.5

© 2001-2026 Discuz! Team.

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