LIBLINEARで分類したい!(1)


2012年のゴールドラッシュに続き、2013年は彗星ラッシュということでテンション上がりまくりの東です。

さて、クラス分類(classification)ライブラリの雄LIBLINEARを試してみました。コマンドを2回叩くだけで簡単にクラス分類ができて素適なのですが、学習データによってはうまく分類できない場合があります。また、オプションもたくさんあるので、何をどう設定すればどういう効果があるのかピンとこないなど、なかなか難しいものがあります。
ここでは簡単な例を手始めに、いろいろ試して分かったことをお伝えできればと思います。

■ まずはインストールから。

Ubuntu等にはパッケージも用意されているのですが、最新のものを使いたかったので今回はソースからインストールします。
こちらからソースファイルをダウンロードします。今回使用したのはバージョン1.93です。以前のバージョンはこちらから入手できます。
ダウンロードしたファイルを展開した後は、makeコマンド一発で実行ファイルができあがります。
trainコマンドで学習をして、predictコマンドで分類をします。

Ubuntu 12.04.2 LTSでは下記のコマンドでパッケージがインストールできます。

$ sudo apt-get install liblinear-tools

このパッケージはバージョン1.8相当のものですので、若干機能が異なります。変更履歴はこちらで確認できます。
またパッケージの場合、コマンド名がliblinear-train、liblinear-predictとなります。適宜読み替えてください。

■ 早速学習してみます。

学習用のファイルを作成します。
学習データのフォーマットは「[クラス番号] [素性番号:素性の値]+」。
第1素性をf_1、第2素性をf_2とした場合に「直線f_2=-f_1がクラス分類の境界線になる」ということを期待して、データを作成してみました。横軸に第1素性、縦軸に第2素性をプロットしたグラフも描いておきます。

1 1:1.0  2:-0.9
1 1:0.6  2:-0.5
1 1:0.2  2:-0.1
1 1:0.0  2:0.1
1 1:-0.4 2:0.5
1 1:-0.8 2:0.9
1 1:0.5  2:0.5
2 1:0.8  2:-0.9
2 1:0.4  2:-0.5
2 1:0.0  2:-0.1
2 1:-0.2 2:0.1
2 1:-0.6 2:0.5
2 1:-1.0 2:0.9
2 1:-0.5 2:-0.5
test01.data

このデータに対し、trainコマンドで学習をしてモデル(test01.model)を作成します。

$ ./train test01.data test01.model

次にpredictコマンドで分類を行います。ここでは学習に使ったデータをそのまま分類してみます。

$ ./predict test01.data test01.model test01.predict
Accuracy = 100% (14/14)

predictコマンドの出力にある「Accuracy = 100%」が分類の正解率です。
出力ファイル(test01.predict)には結果(どのクラスに分類されたか)が入っています。test01.predictの各々の行はtest01.dataの行に対応しています。
グラフ中の記号はそれぞれ以下を表しています。
 緑の□「Class 1-o」: クラス1のデータが、正しくクラス1と分類されたもの
 赤の□「Class 1-x」: クラス1のデータが、間違ってクラス2と分類されたもの
 緑の△「Class 2-o」: クラス2のデータが、正しくクラス2と分類されたもの
 赤の△「Class 2-x」: クラス2のデータが、間違ってクラス1と分類されたもの

1
1
1
1
1
1
1
2
2
2
2
2
2
2
test01.data_predict

すべて緑、つまり全部正しく分類できました。

■ 違うデータで学習してみます。

では次に、先ほどのデータを少し右側にずらしたデータを作ります。

1 1:1.2  2:-0.9
1 1:0.8  2:-0.5
1 1:0.4  2:-0.1
1 1:0.2  2:0.1
1 1:-0.2 2:0.5
1 1:-0.6 2:0.9
1 1:0.7  2:0.5
2 1:1.0  2:-0.9
2 1:0.6  2:-0.5
2 1:0.2  2:-0.1
2 1:0.0  2:0.1
2 1:-0.4 2:0.5
2 1:-0.8 2:0.9
2 1:-0.3 2:-0.5
test02.data

先ほどと同じように学習と分類を行います。

$ ./train test02.data test02.model
$ ./predict test02.data test02.model test02.predict
Accuracy = 57.1429% (8/14)
1
1
1
1
1
1
1
1
1
1
1
1
1
2
test02.data_predict

正解率が下がってしまいました。
これはどういうことでしょうか。学習で作成されたモデルファイルの中身を見ることで、この仕掛けがわかります。

■ モデルファイルを見てみます。

LIBLINEARが出力するモデルファイルはテキスト形式ですので、そのまま中身を見ることができます。

solver_type L2R_L2LOSS_SVC_DUAL
nr_class 2
label 1 2
nr_feature 2
bias -1
w
1.11874174709748 
0.9946123753569761 
solver_type L2R_L2LOSS_SVC_DUAL
nr_class 2
label 1 2
nr_feature 2
bias -1
w
0.931413592877283 
0.8418136387423126 

左が最初の正解率100%のモデル、右が二番目の正解率57%ぐらいのモデルです。7行目と8行目に差異がありますが、ここが学習した結果(w)を表しています。
分類の際にはこのwを使って計算を行います。その詳細がLIBLINEARの論文の2章に述べられています。

In the testing phase, we predict a data point x as positive if w^T x > 0, and negative otherwise.

学習結果のwと分類したい事例xの内積を計算して、その正負でクラスの判別を行っています。
具体的に内積計算の式を展開すると、

w^T x=(w_1,w_2)\left(\begin{array}{c}f_1\\f_2\end{array}\right)=w_1f_1+w_2f_2>0


さらに変形して、

\begin{align}f_2>-\frac{w_1}{w_2}f_1\end{align}


式(1)は直線で領域を分割することを表しています。この直線がクラス1とクラス2の境界線となります。境界線を分類結果のグラフに描き加えると、次のようになります。

test01.predict test02.predict
test01 test02

式(1)からわかるように、境界線は必ず原点を通るものになります。そのため、test02のような偏ったデータはうまく分類することができません。
もし、境界線を右に動かすことができれば、うまく分類することができそうです。

■ 境界線を動かします。

先ほどのLIBLINEARの論文の2章には、バイアス項に関する記述があります。このバイアス項によって、境界線を平行移動させることができます。

In some cases, the discriminant function of the classifier includes a bias term, b. LIBLINEAR handles this term by augmenting the vector w and each instance x_i with an additional dimension: w^T \leftarrow [w^T, b],  x_i^T \leftarrow [x_i^T, B], where B is a constant specified by the user.

識別関数にはバイアス項が含まれることがある。LIBLINEARはwと事例x_iの次元を増やすことで、この項に対処する。

早速バイアスを指定して、学習と分類を行いましょう。バイアス(上記論文中のB)にはどんな数値を指定すれば良いのか分かりませんので、ここではとりあえず1.0を指定しておきます。

$ ./train -B 1.0 test02.data test02.B1.model
$ ./predict test02.data test02.B1.model test02.B1.predict
Accuracy = 100% (14/14)

分類の正解率が100%となりました。うまく分類できるようになっています。

■ バイアスを指定したときのモデルファイルを見てみます。

バイアス指定のありなしで、学習したモデルファイルを比較します。

solver_type L2R_L2LOSS_SVC_DUAL
nr_class 2
label 1 2
nr_feature 2
bias -1
w
0.931413592877283 
0.8418136387423126 
solver_type L2R_L2LOSS_SVC_DUAL
nr_class 2
label 1 2
nr_feature 2
bias 1
w
1.091430672426507 
0.9838581679805346 
-0.2225436690102433 
バイアス指定なし バイアス指定あり

オプションで指定したバイアスの値が入っている(5行目)ことと、wの次元が一つ増えている(9行目)ことがわかります。
分類の際の計算方法は、先ほどのLIBLINEARの論文の2章のバイアス項に関する数式部分に記述されています。

w^T \leftarrow [w^T, b], x_i^T \leftarrow [x_i^T, B], where B is a constant specified by the user.

このbがモデルファイル中のwの最後の次元に相当し、Bが学習時に-Bオプションで指定したバイアスに相当します。分類の際にはバイアス指定なしのときと同様に内積の計算を行います。

w^T x=(w_1,w_2,b)\left(\begin{array}{c}f_1\\f_2\\B\end{array}\right)=w_1f_1+w_2f_2+bB>0


\begin{align}f_2>-\frac{w_1}{w_2}f_1-\frac{bB}{w_2}\end{align}


式(2)には切片の項(-\frac{bB}{w_2})が現れました。これにより境界線の平行移動が可能となります。
分類結果のグラフに境界線を描き加えると、うまく分類ができていることが良くわかります。

test02.predict test02.B1.predict
バイアス指定なし バイアス指定あり

■ まとめ

LIBLINEARの簡単な使い方を説明しました。
データの分布によっては、うまく分類できないこともあります。分類境界線をずらすことでうまく分類できるようなデータの場合、学習時に-Bオプションでバイアスを指定すれば対応できることを示しました。

次回はもう少しバイアスに迫ります。


This entry was posted in 分類器, 機械学習 and tagged , . Bookmark the permalink.