先日の #hasmoku でやろうとしてたSVM出来た。 https://gist.github.com/msakai/34fcd8d862fadfa1139a #ml
自分で一から書いたのではなく、CourseraのMachine Learningのコース https://www.coursera.org/course/ml の課題で用意されていたMatlab/OctaveコードのHaskellへの翻訳。
以下の文献あたりを読んで、元のSVMのコードが何をやっているかだいたい理解できたので、Haskell + HMatrix に翻訳してみた。
Support vector machine - Wikipedia, the free encyclopedia
https://en.wikipedia.org/wiki/Support_vector_machine
SMO徹底入門 - SVMをちゃんと実装する
http://www.slideshare.net/sleepy_yoshi/smo-svm
Machine Learning のコースを受講したときは、 超平面で分離するという定義と、サンプル間で内積(やカーネル関数)を計算してという定義の関係をちゃんと理解できていなかったけれど、双対形式を導出して、ちゃんと繋がっているというのが理解できて良かった。
ただ、翻訳してみて、元のMatlab/Octaveコードの怪しい箇所を何箇所か発見してしまった。
* バイアス項の更新式がなんか変な気がする (が、ここはまだちゃんと理解できていない)
* RBFカーネルを使う際の予測式でバイアス項を使っていない
* 本質的ではないけど、処理パスの数え方がちょっと変なやり方になっている気がする。
[Programming Exercise 6] svmPredict don't add bias term when gaussian kernel is used
https://class.coursera.org/ml-003/forum/thread?thread_id=5545
[Programming Exercise 6] Bias updating formula in svmTrain.m
https://class.coursera.org/ml-003/forum/thread?thread_id=5546
元のコードは https://www.coursera.org/learn/machine-learning/programming/e4hZk/support-vector-machines からダウンロードできる。
基本的な定義などは https://en.wikipedia.org/wiki/Support_vector_machine にあるのと同様で、若干変数名は違うけれど 大体 http://www.slideshare.net/sleepy_yoshi/smo-svm のような感じでSMOで解いている。
ここで α_i と α_j に着目して、それを α_new_i と α_new_j に更新したとする。
また、話を簡単にするためにカーネルは線形カーネルとする。
w = Σ_i y_i α_i x_i と定義すると、更新後のwの値は
w_new = w + y_i (α_new_i - α_i) x_i + y_j (α_new_j - α_j) x_j … (1)
となる。
また、コード中で E_i = f(x_i) - y_i = w ・ x_i + b - y_i としている。これを変形すると、
w ・ x_i = E_i - b + y_i … (2)
となる。
0 < α_new_i < C の場合に、bの値をb1に更新している。
この条件は x_i サポートベクターであるということだと思うのだけれど、そうすると以下のようになるはず。
b1
= 〜 x_i がサポートベクターであるので 〜
w_new・x_i - y_i
= 〜 w_new を (1) で変形 〜
w・x_i + y_i (α_new_i - α_i) (x_i・x_i) + y_j (α_new_j - α_j) (x_j・x_i) - y_i
= 〜 w・x_i を (2) で変形 〜
(E_i - b + y_i) + y_i (α_new_i - α_i) (x_i・x_i) + y_j (α_new_j - α_j) (x_j・x_i) - y_i
= 〜 y_i をキャンセルした上で項を整理 〜
- b + E_i + y_i (α_new_i - α_i) (x_i・x_i) + y_j (α_new_j - α_j) (x_j・x_i)
が、元のコードでは
b1 = b - E_i - y_i (α_new_i - α_i) (x_i・x_j) - y_j (α_new_j - α_j) (x_j・x_i)
という感じになっていて、最初の内積の箇所は (x_i・x_j) ではなく (x_i・x_i) ではないかと思うが、それを修正しても符号が完全に反転してしまっている。
自分の導出がどこか勘違いしてるのか、それとも Machine Learning の方のコードが間違っている?