

オンライン機械学習を買ったので、書いてあるアルゴリズムから線形識別器をいくつか試してみた。オンライン学習とは、データを一括処理するバッチ学習に対して、個々のデータを逐次処理する学習手法のこと。オンライン学習だと以下のようなうれしいことがある。

データを一つ一つ処理するので、オンライン学習の計算量はデータ数に対して線形に増加する。バッチ学習のカーネル法などでは、データ数について2~3乗のオーダで計算量が増えていったりして、数万を超える大規模データには使えない。例えば非線形カーネルを使うSVMではかなり速いとされるSMOアルゴリズムでも時間計算量がO(mn^2)だが、線形カーネルに限ればSVMはオンラインに学習することができてO(mn)で済む (m: 入力次元、n: データ数)。

この本で扱っているほとんどの学習手法は線形分離可能を前提とするが、データを高次元化させたりして実用レベルの精度を出せることも多い。例えばTwitterのストリーミングAPIからデータを拾ってきて、単語のあるなしに対応する特徴量を持つ数万次元の疎なベクトルを入力として学習し、リアルタイムに何らかの予測を立てるような応用に適している。この本の後半にはそういった応用に向けて、疎なベクトル向けのベクトル演算の実装の仕方のようなものも解説されている。全体的に実装を指向した本という印象である。

コード quicklispですぐ読み込める形にしたものをgithubに置いておく。

インストールするには、まずシェルから $ cd ~/quicklisp/local-projects/ $ git clone https://github.com/masatoi/cl-online-learning.git 次にLisp処理系からquicklispでロードする。 ( ql:quickload :cl-online-learning )

ベクトル演算は副作用あり ベクトル演算はとりあえず密ベクトルに絞って考えて、CLML（Common Lisp Machine Learning)に付いてたユーティリティの一部を流用する。ベクトルはdouble-float型のsimple-arrayで表現する。ベクトル同士の足し算やスカラー倍などの操作において、結果受け取り用のベクトルを渡して、それを破壊的に変更することでベクトル演算の度にmake-arrayさせないようになっている。 ( let (( v1 ( make-array 3 :element-type ' double-float :initial-contents '( 1d0 2d0 3d0 ))) ( v2 ( make-array 3 :element-type ' double-float :initial-contents '( 10d0 20d0 30d0 ))) ( result ( make-array 3 :element-type ' double-float :initial-element 0d0 ))) ( print ( v+ v1 v2 result )) ( print ( v-scale v1 3d0 result )) ( print ( v+ result v2 result )) ( print result )) ベクトル演算の返り値は結果受け取り用ベクトルと同一オブジェクトなので、返り値を束縛した後に同じ結果受け取り用ベクトルを使って別のベクトル演算をやると値が変わってハマることになる(ハマった)。

予測とテスト部分 線形識別器なので、重みと入力ベクトルの内積を取って、それが正か負かでクラスを分ける。 ( defun sign ( x ) ( if ( > x 0d0 ) 1d0 -1d0 )) ( defun f ( input weight bias ) ( + ( inner-product weight input ) bias )) ( defun predict ( input weight bias ) ( sign ( f input weight bias ))) どうせシーケンシャルアクセスしかしないので、データセットは教師信号と入力ベクトルのドット対のリストということにする。するとテストはこう書ける。 ( defun test ( test-data weight bias ) ( let (( len ( length test-data )) ( n-correct ( count-if ( lambda ( datum ) ( = ( predict ( cdr datum ) weight bias ) ( car datum ))) test-data ))) ( format t "Accuracy: ~f%, Correct: ~A, Total: ~A~%" ( * ( / n-correct len ) 100.0 ) n-correct len )))

訓練部分 とりあえず最も基本となるパーセプトロンと、線形SVMを確率的勾配法で更新する場合を実装してみる。

バイアス(決定境界fの切片)は、入力ベクトルの次元を1つ上げて(常に値が定数となる要素を入力ベクトルに追加する)、普通に重みを学習すると、重みベクトルの余計に1次元増えた要素がバイアスになっている。だが、この本によるとバイアスの更新は他の重みとは分けた方がいいとある。バイアスだけ学習率を変えるなどのヒューリスティクスがあるらしい。 ( defun train-perceptron-1step ( input weight bias training-label ) ( if ( <= ( * training-label ( f input weight bias )) 0d0 ) ( if ( > training-label 0d0 ) ( values ( v+ weight input weight ) ( + bias 1d0 )) ( values ( v- weight input weight ) ( - bias 1d0 ))) ( values weight bias ))) ( defun train-perceptron-all ( training-data weight bias ) ( loop for datum in training-data do ( setf bias ( nth-value 1 ( train-perceptron-1step ( cdr datum ) weight bias ( car datum ))))) ( values weight bias )) ( defun train-perceptron ( training-data ) ( let (( weight ( make-dvec ( length ( cdar training-data )) 0d0 )) ( bias 0d0 )) ( train-perceptron-all training-data weight bias ))) ( defun train-svm-sgd-1step ( input weight bias learning-rate regularization-parameter training-label v-scale-result ) ( let* (( update-p ( <= ( * training-label ( f input weight bias )) 1d0 )) ( tmp-weight ( if update-p ( v+ weight ( v-scale input ( * learning-rate training-label ) v-scale-result ) weight ) weight )) ( tmp-bias ( if update-p ( + bias ( * learning-rate training-label )) bias ))) ( values ( v-scale tmp-weight ( - 1d0 ( * 2d0 learning-rate regularization-parameter )) weight ) ( * tmp-bias ( - 1d0 ( * 2d0 learning-rate regularization-parameter )))))) ( defun train-svm-sgd-all ( training-data weight bias learning-rate regularization-parameter v-scale-result ) ( loop for datum in training-data do ( setf bias ( nth-value 1 ( train-svm-sgd-1step ( cdr datum ) weight bias learning-rate regularization-parameter ( car datum ) v-scale-result )))) ( values weight bias )) ( defun train-svm-sgd ( training-data learning-rate regularization-parameter ) ( let (( weight ( make-dvec ( length ( cdar training-data )) 0d0 )) ( bias 0d0 ) ( v-scale-result ( make-dvec ( length ( cdar training-data )) 0d0 ))) ( train-svm-sgd-all training-data weight bias learning-rate regularization-parameter v-scale-result )))