Y's note

Web技術・プロダクトマネジメント・そして経営について

本ブログの更新を停止しており、今後は下記Noteに記載していきます。
https://note.com/yutakikuchi/

Mahoutを使ったNaiveBayesによる機械学習

入門 ソーシャルデータ ―データマイニング、分析、可視化のテクニック

入門 ソーシャルデータ ―データマイニング、分析、可視化のテクニック

BigDataでの機械学習

膨大なデータに対して機械学習を行いたい時にlocalの端末一台では処理の時間が掛かりすぎてしまいます。学習、モデル作成、予測のそれぞれの処理を高速で行うための一つのSolutionがHadoop上で機械学習をしてしまうことだと思います。Hadoop上で機械学習をするための便利なライブラリとしてJAVAベースのMahoutがあります。この記事ではMahoutによるNaiveBayes分類学習を中心としたMahoutデータの生成と使い方について紹介します。
Machine Learning With Hadoop - Yuta.Kikuchiの日記 はてなブックマーク - Machine Learning With Hadoop - Yuta.Kikuchiの日記

NLTKによる分かち書き

NLTK Install

Python自然言語処理ライブラリのnltkを利用して分かち書きを行います。nltkのセットアップは非常に簡単で以下のコマンドを実行するだけです。Installing NLTK ― NLTK 2.0 documentation はてなブックマーク - Installing NLTK ― NLTK 2.0 documentation

$ python -V
Python 2.6.6
$ wget "http://pypi.python.org/packages/2.6/s/setuptools/setuptools-0.6c11-py2.6.egg#md5=bfa92100bd772d5a213eedd356d64086"
$ sudo sh setuptools-0.6c11-py2.6.egg --prefix=/usr/
$ sudo easy_install pip
$ sudo pip install -U numpy
$ sudo pip install -U pyyaml nltk
Mecab Install

形態素解析器で有名なMecabPythonから利用できるようにします。Mecab本体、Mecab-ipadic、mecab-pythonの3つをinstallします。mecab-pythonのinstallの時に"mecab-config: コマンドが見つかりません"のように怒られたらsetup.pyのmecab-configをmecabをinstallした時のlocalディレクトリを指定するように修正するとinstallできます。

// mecab本体
$ wget http://mecab.googlecode.com/files/mecab-0.99.tar.gz
$ tar -xzf mecab-0.99.tar.gz
$ cd mecab-0.99
$ ./configure --with-charset=utf8
$ make && sudo make install

// mecab-ipadic
$ wget http://sourceforge.net/projects/mecab/files/mecab-ipadic/2.7.0-20070801/mecab-ipadic-2.7.0-20070801.tar.gz/download
$ tar -xzf mecab-ipadic-2.7.0-20070801.tar.gz
$ cd mecab-ipadic-2.7.0-20070801
$ ./configure --with-charset=utf8
$ make && sudo make install

// mecab-python
$ wget http://mecab.googlecode.com/files/mecab-python-0.993.tar.gz
$ tar -xzf mecab-python-0.993.tar.gz
$ cd mecab-python-0.993
$ python setup.py build
$ sudo python setup.py install
libmecab.so.2の読み込み

上の設定が完了しただけではまだPythonからMeCabが読み出せません。libmecab.so.2のエラーが出てしまうのでそれを回避するために設定ファイルに/usr/local/libを追記してldconfigの再読み込みを行います。これでmecab-pythonが起動できる準備が整いました。

$ sudo vim /etc/ld.so.conf.d/lib.conf
/usr/local/lib  //追記

// 再読み込み
$ sudo ldconfig

// mecab-pythonの起動
$ python
Python 2.6.6 (r266:84292, Sep 11 2012, 08:34:23) 
[GCC 4.4.6 20120305 (Red Hat 4.4.6-4)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import MeCab
データの抽出と分かち書き

Apache Mahout 機械学習Libraryを使って「魔法少女まどか☆マギカ」の台詞をテキストマイニングしてみた - Yuta.Kikuchiの日記 はてなブックマーク - Apache Mahout 機械学習Libraryを使って「魔法少女まどか☆マギカ」の台詞をテキストマイニングしてみた - Yuta.Kikuchiの日記 ここでも書いたようにまどマギの台詞を抽出してテキストファイルに落とします。更にテキストファイルに落としたデータに対して分かち書きを行います。分かち書きをした結果をディレクトリ名とファイル名をディレクトリ人物としたパスに保存します。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os,sys,re,urllib,urllib2

if( os.path.exists( "./data" ) != True ) :
   os.mkdir( "./data" )

urls = { 'http://www22.atwiki.jp/madoka-magica/pages/131.html' : 'madoka.txt', 
         'http://www22.atwiki.jp/madoka-magica/pages/57.html'  : 'homura.txt',
         'http://www22.atwiki.jp/madoka-magica/pages/123.html' : 'sayaka.txt',
         'http://www22.atwiki.jp/madoka-magica/pages/130.html' : 'mami.txt',
         'http://www22.atwiki.jp/madoka-magica/pages/132.html' : 'kyoko.txt',
         'http://www22.atwiki.jp/madoka-magica/pages/56.html'  : 'kyube.txt'
        }
opener = urllib2.build_opener()
ua = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/534.51.22 (KHTML, like Gecko) Version/5.1.1 Safari/    534.51.22'
referer = 'http://www22.atwiki.jp/madoka-magica/'
opener.addheaders = [( 'User-Agent', ua ),( 'Referer', referer )]
for k,v in urls.iteritems():
    f = open( './data/' + v , 'w' )
    content = opener.open( k ).read()
    if re.compile( r'^「(.*?)」$', re.M ).search( content ) is not None: 
        lines = re.compile( r'^「(.*?)」$', re.M ).findall( content )
        for line in lines:
            f.write( line + "\n" )
f.close()
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys,os
reload(sys)
sys.setdefaultencoding('utf-8')

import MeCab
mecab = MeCab.Tagger('-Ochasen')
names = [ 'homura', 'kyoko', 'kyube', 'madoka', 'mami', 'sayaka' ]

for name in names :
    if( os.path.exists( name ) != True ) :
        os.mkdir( name )
    file = './data/' + name + '.txt'
    f = open( name + '/' + name + '.txt', 'w' )
    for line in open( file ):
        line = line.strip().rstrip()
        p = mecab.parseToNode( line )
        phrases = p.next
        while phrases:
            try:
                k = p.surface
                f.write( k + " " )
                p = p.next
            except AttributeError:
                break 
        f.write( "\n" )
    f.close()
$ vi madoka/madoka.txt
 あっ … ?  
 ひどい …   
 そんな … あんまり だ よ 、 こんな の って ない よ  
 本当 な の ?  
 私 なんか でも 、 本当に 何 か できる の ? こんな 結末 を 変え られる の ?  
 夢 オチ … ?  
 おはよう 、 パパ  
 ママ は ?  
 はぁ い  
 おっき ろ 〜!  

Mahout用のデータ生成

HDFSへデータのPUT

Hadoopでデータを扱えるように上で生成した形態素ファイルをHDFSに全てputします。putするデータは形態素ファイルをsplitして1行ずつのファイルに落とします。アップする前に適当なディレクトリを一つ作っておくと良いと思います。/usr/lib/hadoop-0.20/bin/hadoop fsとタイピングするのが面倒なのでalias hdfs="/usr/lib/hadoop-0.20/bin/hadoop fs"としています。

$ cd homura
$ split -l 1 homura.txt
$ hdfs -mkdir madmagi
$ hdfs -put homura madmagi/
$ hdfs -put kyoko madmagi/
$ hdfs -put kyube madmagi/
$ hdfs -put madka madmagi/
$ hdfs -put madoka madmagi/
$ hdfs -put mami madmagi/
$ hdfs -put sayaka madmagi/
$ hdfs -lsr /user/yuta/madmagi
(略)
-rw-r--r--   1 yuta supergroup        128 2012-11-12 23:57 /user/yuta/madmagi/sayaka/xld
-rw-r--r--   1 yuta supergroup        339 2012-11-12 23:57 /user/yuta/madmagi/sayaka/xle
-rw-r--r--   1 yuta supergroup         58 2012-11-12 23:57 /user/yuta/madmagi/sayaka/xlf
-rw-r--r--   1 yuta supergroup         40 2012-11-12 23:57 /user/yuta/madmagi/sayaka/xlg
-rw-r--r--   1 yuta supergroup        228 2012-11-12 23:57 /user/yuta/madmagi/sayaka/xlh
-rw-r--r--   1 yuta supergroup         98 2012-11-12 23:57 /user/yuta/madmagi/sayaka/xli
-rw-r--r--   1 yuta supergroup        216 2012-11-12 23:57 /user/yuta/madmagi/sayaka/xlj
-rw-r--r--   1 yuta supergroup         10 2012-11-12 23:57 /user/yuta/madmagi/sayaka/xlk
SequenceFile生成

Hadoopでデータを扱えるようにHDFSにputしたデータをSequenceFileに変換します。変換にはmahoutコマンドのseqdirectoryを利用します。文字コードUTF-8を指定します。生成したファイルを確認するためにはseqdumperを利用します。seqdumperにて確認するとそれぞれのラベルが付けられたファイルの先頭指定文字を表示してくれます。

$ mahout seqdirectory  --input madmagi --output madmagi_seq -c UTF-8
$ mahout seqdumper --input madmagi_seq --substring 10
Key: /sayaka/xlc: Value:  あの さあ 、 キ
Key: /sayaka/xld: Value:  まさか あんた 、
Key: /sayaka/xle: Value:  はあ 、 どっち 
Key: /sayaka/xlf: Value:  … 何 か 、 手
Key: /sayaka/xlg: Value:  … うん 。 これ
Key: /sayaka/xlh: Value:  そう だ よ 。 
Key: /sayaka/xli: Value:  それ を 思い出せ
Key: /sayaka/xlj: Value:  まあ 、 そりゃ 
Key: /sayaka/xlk: Value:  うん  
VectorData生成

SeqenceFileが生成された後にMahoutでのinputファイルとして利用できるようにVectorデータに変換する必要があります。変換にはseq2sparseを利用します。オプションとして最大のDF値とNGramのサイズを指定しています。生成されたVectorDataを確認するためには先ほどと同様にseqdumperを利用します。形態素のデータがIDベースのベクトルデータに変換されている事が確認できます。

$ mahout seq2sparse --input madmagi_seq --output madmagi_vector --maxDFPercent 40 --maxNGramSize 8 --sequentialAccessVector --namedVector
$ mahout seqdumper --input madmagi_vector/tf-vectors/part-r-00000 --substring 100

Key: /sayaka/xla: Value: /sayaka/xla:{5:2.0,7:2.0,11:2.0,26:1.0,27:1.0,29:1.0,31:2.0,32:2.0,34:3.0,37:1.0,41:1.0,42:1.0,44:1.
Key: /sayaka/xlb: Value: /sayaka/xlb:{3:1.0,11:1.0,23:1.0,31:1.0,34:1.0,37:1.0,47:1.0,118:1.0,841:1.0,842:1.0,844:1.0,845:1.0
Key: /sayaka/xlc: Value: /sayaka/xlc:{3:3.0,5:2.0,9:1.0,12:2.0,13:1.0,17:1.0,21:2.0,27:1.0,29:1.0,31:1.0,35:1.0,37:1.0,38:2.0
Key: /sayaka/xld: Value: /sayaka/xld:{3:2.0,5:1.0,7:2.0,11:2.0,21:1.0,23:1.0,24:1.0,31:1.0,38:1.0,39:2.0,41:2.0,44:1.0,45:1.0
Key: /sayaka/xle: Value: /sayaka/xle:{3:1.0,5:2.0,7:2.0,11:4.0,13:2.0,17:1.0,19:1.0,21:1.0,23:3.0,29:1.0,31:1.0,32:4.0,33:2.0
Key: /sayaka/xlf: Value: /sayaka/xlf:{11:2.0,17:1.0,21:1.0,27:1.0,31:1.0,33:1.0,34:1.0,44:1.0,63:1.0,170:1.0,410:1.0,752:1.0,
Key: /sayaka/xlg: Value: /sayaka/xlg:{5:2.0,7:1.0,19:1.0,38:1.0,67:1.0,71:1.0,75:1.0,978:1.0,992:1.0,1274:1.0,1634:1.0,1635:1
Key: /sayaka/xlh: Value: /sayaka/xlh:{3:1.0,5:1.0,7:2.0,11:2.0,12:1.0,13:1.0,17:1.0,23:1.0,29:1.0,31:4.0,32:4.0,34:4.0,37:1.0
Key: /sayaka/xli: Value: /sayaka/xli:{5:2.0,7:1.0,17:1.0,27:1.0,29:1.0,31:1.0,32:2.0,38:1.0,41:1.0,45:1.0,62:2.0,67:1.0,71:1.
Key: /sayaka/xlj: Value: /sayaka/xlj:{3:1.0,5:7.0,15:2.0,17:1.0,21:1.0,23:2.0,24:1.0,27:1.0,29:1.0,31:1.0,32:1.0,33:1.0,34:3.
Key: /sayaka/xlk: Value: /sayaka/xlk:{7:1.0,75:1.0,1634:1.0}

$ mahout seqdumper --input madmagi_vector/tfidf-vectors/part-r-00000 --substring 100
Key: /homura/homura.txt: Value: /homura/homura.txt:{4:3.386294364929199,76:4.1972246170043945,95:1.6931471824645996,98:1.69314718246
Key: /kyoko/kyoko.txt: Value: /kyoko/kyoko.txt:{0:1.6931471824645996,28:2.932616949081421,78:11.104812622070312,79:11.301372528076
Key: /kyube/kyube.txt: Value: /kyube/kyube.txt:{0:1.6931471824645996,28:2.932616949081421,80:1.6931471824645996,81:2.9326169490814
Key: /madoka/madoka.txt: Value: /madoka/madoka.txt:{4:2.3944716453552246,81:1.6931471824645996,95:2.3944716453552246,97:2.3944716453
Key: /mami/mami.txt: Value: /mami/mami.txt:{88:2.967885971069336,97:2.3944716453552246,105:1.6931471824645996,107:3.634903192520
Key: /sayaka/sayaka.txt: Value: /sayaka/sayaka.txt:{57:3.6349031925201416,90:2.967885971069336,91:2.967885971069336,93:4.69263982772

NaiveBayesによる分類学習

NaiveBayes

ようやくNaiveBayesの話ができるようになりました。NaiveBayesは教師あり学習の一つで単純ベイズ分類器とも呼ばれ独立過程とベイズの定理に寄って算出される確率ベースの分類器です。
単純ベイズ分類器 - Wikipedia はてなブックマーク - 単純ベイズ分類器 - Wikipedia
MahoutでNaiveBayesを利用するにはtrainnbとtestnbを使用します。まずはtrainnbにて予測Modelを作成、testnbにてModelからの予測を行い評価します。今回のinputはまどマギの台詞ですが、ラベル済みのデータを学習させて未ラベルの評価データから台詞の人物を予測する事を目的としています。

予測Model作成

inputはtfidfのデータを利用します。生成したデータのoutput先指定とtrainComplementaryというオプションで精度を高めることが出来るようです。生成されたModelはバイナリファイルのようでseqdumperでも中身を確認する事が出来ませんでした。labelindexは登場人物に対して正解ラベルとなる整数値を割り当てています。

$ mahout trainnb --input madmagi_vector/tfidf-vectors/part-r-00000 --output madmagi_model --extractLabels --labelIndex madmagi_labelindex --trainComplementary

$ mahout seqdumper --input /user/yuta/madmagi_model
Exception in thread "main" java.io.IOException: hdfs://localhost:8020/user/yuta/madmagi_model/naiveBayesModel.bin not a SequenceFile

$ mahout seqdumper --input /user/yuta/madmagi_labelindex
Key: homura: Value: 0
Key: kyoko: Value: 1
Key: kyube: Value: 2
Key: madoka: Value: 3
Key: mami: Value: 4
Key: sayaka: Value: 5
予測の実行

生成したModelを利用してデータの予測を行います。ここでのinputはModel生成に利用した学習データと全く同一のものを利用します。学習データと評価データが一致しているので正解率が高くなるのは当然の事で、ここでは1880行中1686行のデータを当てていて、正解率は89.6809%となっています。

$ mahout testnb --input madmagi_vector/tfidf-vectors/part-r-00000 --output madmagi_test --model madmagi_model --labelIndex madmagi_labelindex
Standard NB Results: =======================================================
Summary
-------------------------------------------------------
Correctly Classified Instances          :       1686	   89.6809%
Incorrectly Classified Instances        :        194	   10.3191%
Total Classified Instances              :       1880

=======================================================
Confusion Matrix
-------------------------------------------------------
a    	b    	c    	d    	e    	f    	<--Classified as
238  	8    	7    	8    	5    	8    	 |  274   	a     = homura
3    	209  	0    	8    	1    	4    	 |  225   	b     = kyoko
5    	3    	312  	5    	3    	3    	 |  331   	c     = kyube
21   	13   	2    	498  	28   	24   	 |  586   	d     = madoka
2    	0    	2    	1    	158  	4    	 |  167   	e     = mami
5    	2    	1    	11   	7    	271  	 |  297   	f     = sayaka
学習データと評価データの分離

予測Modelの正確な精度を確認するために学習データと評価データを分けてtestnbしてみます。データを分けるコマンドとしてsplitがあります。下では50%ずつデータを分割しています。分割したデータに対して予測Modelを作成、評価を行ってみると正解度は50.4634%となりました。以前SVMでも似たような事を試していてその時の精度がイマイチだったので、今回の結果ももっと低いと予測していたんですが、まぁそれなりの結果が出たと言えると思います。
Support Vector Machinesを用いた「魔法少女まどか☆マギカ」人物予測モデル - Yuta.Kikuchiの日記 はてなブックマーク - Support Vector Machinesを用いた「魔法少女まどか☆マギカ」人物予測モデル - Yuta.Kikuchiの日記

$ mahout split --input madmagi_vector/tfidf-vectors --trainingOutput madmagi_train --testOutput madmagi_output --randomSelectionPct 50 --method sequential --sequenceFiles --overwrite

$ mahout trainnb --input /user/yuta/madmagi_train/part-r-00000 --output madmagi_model --extractLabels --labelIndex madmagi_labelindex --trainComplementary

$ mahout testnb --input /user/yuta/madmagi_output/part-r-00000 --output madmagi_test_output --model madmagi_model --labelIndex madmagi_labelindex

Standard NB Results: =======================================================
Summary
-------------------------------------------------------
Correctly Classified Instances          :        490	   50.4634%
Incorrectly Classified Instances        :        481	   49.5366%
Total Classified Instances              :        971

=======================================================
Confusion Matrix
-------------------------------------------------------
a    	b    	c    	d    	e    	f    	<--Classified as
46   	20   	15   	13   	29   	14   	 |  137   	a     = homura
8    	61   	5    	9    	12   	10   	 |  105   	b     = kyoko
12   	9    	110  	12   	25   	11   	 |  179   	c     = kyube
36   	25   	4    	165  	31   	45   	 |  306   	d     = madoka
15   	1    	4    	9    	46   	12   	 |  87    	e     = mami
22   	11   	3    	39   	20   	62   	 |  157   	f     = sayaka

Machine Learning With Hadoop

Machine Learning: A Probabilistic Perspective (Adaptive Computation and Machine Learning series)

Machine Learning: A Probabilistic Perspective (Adaptive Computation and Machine Learning series)

Big DataのMachine Learning

Daily数百ギガバイトのAccessLogからDataMiningに必要なFeatureをかき集めるのにスタンドアロンの端末で処理を行うには時間が掛かりすぎます。バッチ処理で1日以内にUserのAccessLogを整形、必要な部分を取り出してDataMining/Machine Learningに掛けて、Userが利用するSystemに反映して行こうと考えると最初のバッチ処理で利用できる時間はそれほど多くありません。処理時間改善のためにHadoopを使い複数台のマシンに大量のログデータとバッチ処理を分散させる仕組みはBig Dataを扱う人の中では常識として利用されています。今日はBigDataをMachineLearningさせたいときの方法について調べた内容を載せます。Apache Mahout、PigのUDF、独自MapReduceの3つのうちどれかを使う事になりそうです。

Apache Mahout
  • Hadoop上で実行可能な機械学習ライブラリ。ファイルをDownloadして展開するだけで利用可能。一番お手軽だが未実装のAlgorithmもあり、公開されているPatchファイルを当てるなどの対応が必要になる場合がある。
  • Apache Mahout: Scalable machine learning and data mining はてなブックマーク - Apache Mahout: Scalable machine learning and data mining
  • Algorithms はてなブックマーク - Algorithms
    • Classification
    • Clustering
    • Pattern Mining
    • Regression
    • Dimension reduction
    • Evolutionary Algorithms
    • Recommenders / Collaborative Filtering
    • Vector Similarity
Pig
training = load `training.txt'
using SVMLightStorage()
as (target: int, features: map[]);
store training into `model/'
using FeaturesLRClassifierBuilder();
独自MapReduce
その他

Pythonのscikit-learnでRandomForest vs SVMを比較してみた

Random Forest

Random Forest

Random Forest

Random Forestとは

機械学習の方法論の一つで決定木ベースの集団学習アルゴリズムを取り入れたものです。説明変数の依存が少ないことや学習が高速であることが特徴として挙げられています。英語サイトの方で特徴として紹介されているRFの内容について記述します。

Features
  • 大きなデータに対して効率よく処理される。
  • 変数の削除をすることなく入力した数千の変数を扱う事ができる。
  • どの変数が分類に対して重要なのかを計算して与えてくれる。
  • 木の構築処理中に一般的なエラーの偏りの無い計算を生成する。
  • 高い割合でデータが誤っている時に誤りのデータを計算し、精度を保つ効果的な手法を持っている。
  • アンバランスなデータが与えられたクラス群の中でエラーのバランスに対する手法を持っている。
  • 生成された木は今後他のデータに適用させるために保存する事ができる。
  • 変数とクラスタリング間の関係性に関する情報を計算する。
  • クラスタリングに利用される隣接するケースを計算する。
  • 上の特性はラベリングやクラスタリングされていないデータやはずれ値に対しても拡張する事ができる。
  • 変数の相互作用を発見するための実験的な方法を推薦する。
Remarks
  • RFではoverfitは存在しない。
  • RFは複数の木として処理できるし、それは処理速度が速い。
  • 5万のデータと100の変数を持ったデータに対して、100個の木に割り当て、800Mhzのマシンで11分で処理が終わる。
  • RFではCross-Validationをする必要がない。out-of-bag (oob) エラー計算がその代わりとなる。
テキストデータ分類器の比較

このPFD-PaperによるとRFがマクロ平均のF値において最も精度が高いと言われていますが、どんなデータに対しても精度が高いとは言えないと思います。画像引用 : 図6 : 10のテーマにおけるマクロ平均のF1 値の平均プロット

Mac × Pythonで利用する

env

pythonは2.7を利用します。元々設定されていたPythonは2.6.7だったのですが、install時に色々と問題がでてきたので2.7に変えます。複数Pythonを使い分けるにはpython_selectというコマンドがあったのですが、現在は使えなくなっているようです。その代わりにport select --setで切り替えます。

$ sudo port select --set python python27
$ python -V
Python 2.7.2
Package

MacOSX 10.6.8とPythonでRandomForestを利用するには以下のパッケージが必要になります。
※scikit-learnにはRandomForest以外のアルゴリズムも含まれているので、その他の機械学習ツールを使いたい場合も以下を参考にすると設定できると思います。

設定手順

※easy_installで上のpackageをinstallを試みましたが、scipyのinstallで失敗します。UMFPACKというパッケージが存在しないことが原因のようですが、直接的な解決方法が分かりませんでした。もし直接的な解決方法をご存知の方いましたら教えていただけると助かります。

$ sudo easy_install-2.7 scipy 
Searching for scipy
Reading http://pypi.python.org/simple/scipy/
Reading http://www.scipy.org
Download error: [Errno 61] Connection refused -- Some packages may not be found!
Reading http://sourceforge.net/project/showfiles.php?group_id=27747&package_id=19531
Reading http://new.scipy.org/Wiki/Download
Best match: scipy 0.11.0
Downloading http://pypi.python.org/packages/source/s/scipy/scipy-0.11.0.zip#md5=40b700ddde9ddab643b640fff7a9d753
Processing scipy-0.11.0.zip
Running scipy-0.11.0/setup.py -q bdist_egg --dist-dir /tmp/easy_install-7aUbZx/scipy-0.11.0/egg-dist-tmp-phUwYf
Running from scipy source directory.
/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/numpy-1.6.2-py2.7-macosx-10.6-x86_64.egg/numpy/distutils/system_info.py:470: UserWarning: 
    UMFPACK sparse solver (http://www.cise.ufl.edu/research/sparse/umfpack/)
    not found. Directories to search for the libraries can be specified in the
    numpy/distutils/site.cfg file (section [umfpack]) or by setting
    the UMFPACK environment variable.
  warnings.warn(self.notfounderror.__doc__)
error: None

間接的な解決方法として、ぐぐってみたら以下のようなサイトを見つけました。ScipySuperpackというものを利用してnumpy,matplotlib,scipyをinstallします。

ScipySuperpackのinstall.shを実行します。実行時に質問を聞かれますが、nで答えます。

$ curl -o install_superpack.sh "https://raw.github.com/fonnesbeck/ScipySuperpack/master/install_superpack.sh"
$ sh install_superpack.sh
Are you installing from a repository cloned to this machine (if unsure, answer no)? (y/n)
n 
Cloning Scipy Superpack
Cloning into ScipySuperpack...
remote: Counting objects: 452, done.
remote: Compressing objects: 100% (226/226), done.
remote: Total 452 (delta 240), reused 433 (delta 221)
Receiving objects: 100% (452/452), 332.41 MiB | 1.11 MiB/s, done.
Resolving deltas: 100% (240/240), done.
(略)

Reading http://pypi.python.org/simple/patsy/
Reading https://github.com/pydata/patsy
Best match: patsy 0.1.0
Downloading http://pypi.python.org/packages/source/p/patsy/patsy-0.1.0.zip#md5=4be1210fb5050fb83b6859fe7706b339
Processing patsy-0.1.0.zip
Running patsy-0.1.0/setup.py -q bdist_egg --dist-dir /tmp/easy_install-u6TbHU/patsy-0.1.0/egg-dist-tmp-CXu4_o
no previously-included directories found matching 'doc/_build'
zip_safe flag not set; analyzing archive contents...
Adding patsy 0.1.0 to easy-install.pth file

Installed /opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/patsy-0.1.0-py2.7.egg
Cleaning up
Done
RandomForestの実行

RFの実行にlibsvmのサンプルデータでもあるirisを使います。LIBSVM Data: Classification, Regression, and Multi-label はてなブックマーク - LIBSVM Data: Classification, Regression, and Multi-label データに付けられたlabelとfeatureをそれぞれPythonのRandomForestClassifierに渡せるように分解し、学習データと評価データを用意します。irisは150個のサンプルデータなので75個ずつに振り分けられます。学習データをfitさせ、評価データでpredictさせます。実行してみた結果としては71/75を正確にpredictできていたので、Accuracyは94.6%でした。処理時間は0.36s user 0.17s system 99% cpu 0.536 totalと出ました。以下はテスト用のコードです。

$ wget http://www.csie.ntu.edu.tw/~cjlin/libsvmtools/datasets/multiclass/iris.scale
$ time python rf.py
predict ok id = 0
predict ok id = 1
predict ok id = 2
predict ok id = 3
predict ok id = 4
predict ok id = 5
predict ok id = 6
predict ok id = 7
predict ok id = 8
predict ok id = 9
predict ok id = 10
predict ok id = 11
predict ok id = 12
(略)
predict miss id = 60
predict miss id = 61
predict ok id = 62
predict miss id = 63
predict ok id = 64
(略)
python rf.py  0.36s user 0.17s system 99% cpu 0.536 total
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import re
from sklearn.ensemble import RandomForestClassifier

training_label = []
training_data = []
predict_label = []
predict_data = []
num = 0

file = open( './iris.scale' , 'r' )
for line in file :
    line = line.rstrip()
    node = []
    label = re.search( r'^(.*?)\s', line ).group(1)
    for i in range(1,5) :
        try : 
            pattern = r'%s' % ( str( i ) + ':(.*?\s)' )
            match = re.search( pattern, line ).group(1)
            if match is None :
                node.append(0) 
            else:
                node.append(match)
        except AttributeError :
            node.append(0)
            continue
    if num % 2 == 0 :
        training_data.append( node )
        training_label.append( label )
    else :
        predict_data.append( node )
        predict_label.append( label )
    num = num + 1

predict_data = training_data
model = RandomForestClassifier()
model.fit(training_data, training_label)
output = model.predict(predict_data)

for i in range( 0,len( output ) ) :
    str = "ok" if( int( predict_label[i] ) == int( output[i] ) ) else "miss"
    print "predict %s id = %d" % ( str, i )
SVMの実行

同じ要領でSVMも実行してみました。kernelは線形を利用しています。結果としては68 / 75が正確にpredictされていてAccuracyは90.6%でした。処理時間は0.38s user 0.20s system 97% cpu 0.599 totalとなりました。以下にPythonのテストコードを載せます。

$ time python svm.py
predict ok id = 0
predict ok id = 1
predict ok id = 2
predict ok id = 3
predict ok id = 4
predict ok id = 5
predict ok id = 6
predict ok id = 7
predict ok id = 8
predict ok id = 9
predict ok id = 10
predict ok id = 11
predict ok id = 12
(略)
predict ok id = 37
predict miss id = 38
predict ok id = 39
predict ok id = 40
predict miss id = 41
predict ok id = 42
(略)
python svm.py  0.38s user 0.20s system 97% cpu 0.599 total
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import re,numpy as np
from sklearn import svm

training_label = []
training_data = []
predict_label = []
predict_data = []
num = 0
file = open( './iris.scale' , 'r' )
for line in file :
    line = line.rstrip()
    node = []
    label = re.search( r'^(.*?)\s', line ).group(1)
    for i in range(1,5) :
        try : 
            pattern = r'%s' % ( str( i ) + ':(.*?\s)' )
            match = re.search( pattern, line ).group(1)
            if match is None :
                node.append(0) 
            else:
                node.append( float( match ) )
        except AttributeError :
            node.append(0)
            continue
    if num % 2 == 0 :
        training_data.append( node )
        training_label.append( label )
    else :
        predict_data.append( node )
        predict_label.append( label )
    num = num + 1

model = svm.libsvm.fit( np.array( training_data ), np.float64( np.array( training_label ) ), kernel='linear' )
output = svm.libsvm.predict( np.array( predict_data ), *model,  **{'kernel' : 'linear'} )

for i in range( 0, len(output) ) :
    str = "ok" if( int( predict_label[i] ) == int( output[i] ) ) else "miss"
    print "predict %s id = %d" % ( str, i )

比較のまとめ

  • irisのデータに関してはRandomForestの方がAccuraryが良い結果が出た。
  • 75件の学習/評価データに対する処理速度は若干ながらRandomForestの方が速い事が分かった。

線形予測の機械学習ツールliblinearで効果最大化のための最適な定数Cを探る

Machine Learning for Hackers

Machine Learning for Hackers

  • 作者: Drew Conway,John Myles White
  • 出版社/メーカー: Oreilly & Associates Inc
  • 発売日: 2012/02/28
  • メディア: ペーパーバック
  • クリック: 63回
  • この商品を含むブログを見る

liblinear

今日はliblinearを用いた機会学習の話です。今まではSVMを利用するときはkernelオプション付きのR言語SVM/libsvm/svm-lightを利用していましたが、学習データが多い時に計算時間が何時間も掛かる事に不便を感じていました。そこでSVMツールについて色々と調べてみたところ、線形予測に特化したliblinearの存在を知りました。公式のDocumentにもlibsvmとliblinearでの線形予測での処理時間が桁違いにliblinearの方が優れていることが記述されています。以下にliblinearの特徴を記述します。

  • liblinearはinstanceや特徴が100万桁のデータを線形分離するためのtoolであり以下をサポートしています。
    • L2正則化の分類
      • L2-loss linear SVM, L1-loss linear SVM, and logistic regression (LR)
    • L1正則化の分類
      • L2-loss linear SVM and logistic regression (LR)
    • Support Vechtor RegressionのL2正則化
      • L2-loss linear SVR and L1-loss linear SVR

正則化とはOverfittingを回避するために罰則項を与える事です。種類としてはL1,L2,L1L2の3つが良く利用されるもので精度とスパース性によって異なります。L1は精度が低くスパース性が高い、L2は精度が高くスパース性が低い、L1L2は両方を取り入れ精度を高く保ちながらスパース性を高くすることです。

  • 以下の特徴も含んでいます。
    • LIBSVMのデータformatをサポートし、一般的なSVMツールと同じ目的で、使い方も似ています。
    • Multi-Classへの分類が可能。1 ) 2値分類、2 ) Crammer and Singer
    • モデル選択に対するCross-Validation。
    • 確率推定(logistic regression限定で)が可能。
    • アンバランスなデータに対しての重み付け。
    • Matlab/OctaveJAVAPythonRubyのinterfaceがある。

liblinear vs libsvm CrossValidation

liblinear、libsvmでの線形予測で統一したCrossValidationの精度と実行時間を比較します。データセットに対して5fold-cross-validationを行います。cross-validationのデータとしてはgisetteのscaleデータを利用しました。LIBSVM Data: Classification (Binary Class) はてなブックマーク - LIBSVM Data: Classification (Binary Class) 結果としてはliblinearがAccurary、処理速度ともにlibsvmを上回る結果となりました。

Tool Accurary 処理時間
liblinear 96.2119% 4.89s
libsvm 96.1417% 96.22s
  • liblinear
$time /home/yuta/work/liblinear/liblinear-1.91/train -v 5 gisette_scale   
....*
optimization finished, #iter = 45
Objective value = -0.281585
nSV = 657
....*
optimization finished, #iter = 44
Objective value = -0.268779
nSV = 677
....*
optimization finished, #iter = 45
Objective value = -0.266992
nSV = 658
....*
optimization finished, #iter = 48
Objective value = -0.270758
nSV = 663
....*
optimization finished, #iter = 48
Objective value = -0.282720
nSV = 677
Cross Validation Accuracy = 96.2119%
/home/yuta/work/liblinear/liblinear-1.91/train -v 5 gisette_scale  4.89s user 1.68s system 48% cpu 13.452 total
/home/yuta/work/libsvm/libsvm-3.12/svm-train -v 5 -t 0 gisette_scale           
.*.*
optimization finished, #iter = 2876
nu = 0.000235
obj = -0.268630, rho = 0.708937
nSV = 638, nBSV = 0
Total nSV = 638
.*.*
optimization finished, #iter = 2921
nu = 0.000246
obj = -0.280181, rho = 0.300795
nSV = 660, nBSV = 0
Total nSV = 660
.*..*
optimization finished, #iter = 3122
nu = 0.000249
obj = -0.283893, rho = 0.352812
nSV = 674, nBSV = 0
Total nSV = 674
.*.*
optimization finished, #iter = 2791
nu = 0.000239
obj = -0.272718, rho = 0.532717
nSV = 623, nBSV = 0
Total nSV = 623
.*.*
optimization finished, #iter = 2837
nu = 0.000236
obj = -0.268811, rho = 0.831119
nSV = 642, nBSV = 0
Total nSV = 642
Cross Validation Accuracy = 96.1417%
Positive (+1) class:
  precision = 0.960545
     recall = 0.960545
   F1 value = 0.960545

Negative (-1) class:
  precision = 0.962251
     recall = 0.962251
   F1 value = 0.962251

/home/yuta/work/libsvm/libsvm-3.12/svm-train -v 5 -t 0 gisette_scale  96.22s user 24.09s system 52% cpu 3:49.32 total

liblinearのPrecision、recall、F値を求める

libsvmでもdefaultのソースではCross-Validation時にPrecision、recall、F値を求めるコードは書かれていませんが、Patchにより差分を加える事でそれらの値を求める事が可能でした。libsvmのCross-ValidationでPrecision、Recall、F値を求めるPatch はてなブックマーク - 同じようにliblinearでもpatchが無いか調べたところ、親切な人がgithubに上げてくれていました。LIBLINEARのcross validationオプションでprecision/recallを出力する ― Gist はてなブックマーク - LIBLINEARのcross validationオプションでprecision/recallを出力する ― Gistこのpatchをdownloadして元のソースに当ててみます。

$ wget https://raw.github.com/gist/3341000/723cad7b81d28164bb7def833767b93a591cdca0/train.patch
$ patch < train.patch
$ gmake 
$ home/yuta/work/liblinear/liblinear-1.91/train -v 5 gisette_scale
....*
optimization finished, #iter = 45
Objective value = -0.281585
nSV = 657
....*
optimization finished, #iter = 44
Objective value = -0.268779
nSV = 677
....*
optimization finished, #iter = 45
Objective value = -0.266992
nSV = 658
....*
optimization finished, #iter = 48
Objective value = -0.270758
nSV = 663
....*
optimization finished, #iter = 48
Objective value = -0.282720
nSV = 677
Cross Validation Accuracy = 96.2119%
Positive (+1) class:
  precision = 0.962697
     recall = 0.959943
   F1 value = 0.961318

Negative (-1) class:
  precision = 0.961565
     recall = 0.964212
   F1 value = 0.962887

効果最大なCを探る

SVMは多次元にPlotされた座標のマージン最大化を行う分離線を求める手法です。データが奇麗に分けられるような境界線を常に求められれば問題はありませんが、ノイズやデータのオーバーラップなどの影響を受けて正確に分離が行えない事もあります。データの誤りを許さないような方法はハードマージン、ある程度データの誤りを許すがペナルティを与えるのがソフトマージンと呼ばれるものです。ソフトマージンで利用するパラメータにはスラック変数のζ(ゼータ)と外部変数のCがあります。Cパラメータは正則化と同じように誤りに対するペナルティ項です。SVMではスラック変数のゼータと外部パラメータのCの関係はCを大きくする = ゼータを小さくする、Cを小さくする = ゼータを大きくするという調整になっています。
目的関数 :  \frac{1}{2} ||W^2|| + C\Sigma_{i=1}^n + \zeta_{i}
制約条件 :  y_{i}(W^Tx_{i} + b ) \geq 1 - \zeta_{i}, \zeta_{i} \geq 0
Cはペナルティ項なのでCを大きくするのは制約を大きくすることを意味するのでCを無限に近づければハードマージン化させることを意味し、逆にCを小さくするとソフトマージン化させることになります。それで最適なCを求めるにはどうしたら良いか?これは総当たりの実験で求めていくしか手段が無いようです。以下のPythonコードでC=0.1〜1、1〜10の5fold-cross-validationを実行してみます。結果としてはC=0.4,0.6,0.9,3,4の時にValidation Accuracy = 96.282%という最大数を得る事が分かりました。Cを掛けた時のAccuraryが線形に変化すると予測していたのですが、どうもバラバラの結果となってしまいました。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os,sys
filename = sys.argv[1]

for i in range( 1, 11 ) :
    c = i
    print "5fold-cross-validation C=" + str( c )
    os.system( '/home/yuta/work/liblinear/liblinear-1.91/train -v 5 -c ' + str( c ) + ' ' + filename )
    print "\n"
$ python cross-validation.py /home/yuta/work/data/gisette_scale
5fold-cross-validation C=0.1 Cross Validation Accuracy = 96.2469%
5fold-cross-validation C=0.2 Cross Validation Accuracy = 96.1417%
5fold-cross-validation C=0.3 Cross Validation Accuracy = 96.1768%
5fold-cross-validation C=0.4 Cross Validation Accuracy = 96.282%
5fold-cross-validation C=0.5 Cross Validation Accuracy = 96.2469%
5fold-cross-validation C=0.6 Cross Validation Accuracy = 96.282%
5fold-cross-validation C=0.7 Cross Validation Accuracy = 96.1768%
5fold-cross-validation C=0.8 Cross Validation Accuracy = 96.2469%
5fold-cross-validation C=0.9 Cross Validation Accuracy = 96.282%
5fold-cross-validation C=1 Cross Validation Accuracy = 96.2119%
5fold-cross-validation C=2 Cross Validation Accuracy = 96.2119%
5fold-cross-validation C=3 Cross Validation Accuracy = 96.282%
5fold-cross-validation C=4 Cross Validation Accuracy = 96.282%
5fold-cross-validation C=5 Cross Validation Accuracy = 96.1768%
5fold-cross-validation C=6 Cross Validation Accuracy = 96.2119%
5fold-cross-validation C=7 Cross Validation Accuracy = 96.2119%
5fold-cross-validation C=8 Cross Validation Accuracy = 96.2119%
5fold-cross-validation C=9 Cross Validation Accuracy = 96.2119%
5fold-cross-validation C=10 Cross Validation Accuracy = 96.2119%

まとめ

  • liblinearは様々な正則化に対応している。
  • liblinearはSVMの線形予測を行うツールだがlibsvmより処理速度が速い。
  • SVMの線形データの分離の制限はC定数にて行う。C定数を小さくすれば条件は緩く、大きくすれば条件を厳しくすることになる。

そろそろ本気で機械学習の評価方法について学習するよ

Machine Learning for Hackers

Machine Learning for Hackers

  • 作者: Drew Conway,John Myles White
  • 出版社/メーカー: Oreilly & Associates Inc
  • 発売日: 2012/02/28
  • メディア: ペーパーバック
  • クリック: 63回
  • この商品を含むブログを見る

機械学習の評価方法について学習

機械学習初心者ですが最近業務で本格的に触り始めています。少し前までSmartPhoneのWebAppliを作ることを専門職としていたので機械学習の領域は未知な事が非常に多く、用語の意味ですら十分に理解できていません。今日は機械学習の評価方法を中心に学習(勉強)した内容を記録して行きます。例えばPrecision/Accuracy/Recallの言葉の違いやROC曲線,AUC評価などの技法といったものが話の中心になります。初心者視点で書いていますので専門性がありません。間違い等ありましたらご指摘ください。また以前にもはじめての機械学習という本のサマリーを書いたのでそちらも参照していただけると嬉しいです。
初めての機械学習理論 - Yuta.Kikuchiの日記 はてなブックマーク - 初めての機械学習理論 - Yuta.Kikuchiの日記

用語定義

初めに慣れない用語が多いので、その意味を定義します。

用語 意味
K-Fold Cross-Validation 標本をK子に分割してK-1個を学習データ、1個を評価データとして扱い、K回検定を行い推定の平均を得る
Leave-One-Out Cross-Validation 標本から1つの事例を取り出して評価データとし、残りを学習データとする。全事例が1回は評価となるように検定を繰り返す。
Accuracy 正解率のこと。予測結果全体と、答えがどれぐらい一致しているかを判断する指標。計算式は下記を参照。
Precision 適合率のこと。予測を正と判断した中で、答えも正のもの。計算式は下記を参照。
Recall 再現率のこと。答えが正の中で、予測が正とされたもの。計算式は下記を参照。
F-measure F値のこと。予測精度の評価指標。PresicionとRecallの調和平均。計算式は下記を参照。
ROC曲線 Receiver Operating Characteristicのこと。縦軸にTrue Positive、横軸にFalse Positiveの割合を2次元プロットして点を線で連結した曲線
AUC Area Under the Curveのこと。ROC曲線の曲線よりしたの面積。分類器の精度評価に使う。
マイクロ平均 Nセットのテストをする場合、テストを合計してから評価値を計算。計算式は下記を参照。
マクロ平均 Nセットのテストをする場合、各セットを計算してからそれらを平均する計算。計算式は下記を参照。
True Positive 正しくPositiveと判断。予測が正解しているのでOK
False Positive 誤ってPositiveと判断。予測が不正解なのでNG
False Negative 誤ってNegativeと判断。予測が不正解なのでNG
True Negative 正しくNegativeと判断。予測が正解しているのでOK
- 事実が1 事実が-1
予測が1 True Positive(TP) False Positive(FP)
予測が-1 False Negative(FN) True Negative(TN)

Accuracy = \frac{TP+TN}{TP+FP+FN+TN}
Precision = \frac{TP}{TP+FP}
Recall = \frac{TP}{TP+FN}
F-measure = \frac{2Recall * Precision }{Recall+Precision}
Micro-average = \frac{ \sum_{i=0}^Nx_{i} }{ \sum_{i=0}^Nn_{i} }
Macro-average = \frac{1}{N} \sum_{i=0}^N\frac{x_{i}}{n_{i}}
AccuracyとPrecisionは相関関係になり、PresicionとRecallは逆相関の関係になるのが一般的です。PresicionとRecallともに予測が正しい事の指標ですが、数式で表すと分母が予測ベースか事実ベースかが異なります。どっちだっけという判断が非常に難しいです。
True Positive,False Positive,False Negative,True Negativeの覚え方ですが、True/Falseは正しく予測できたかPositive/Negativeは予測が正負のどちらに判断されたかを示す内容です。False Positive,False Negativeが結果と予測が一致していないので駄目な予測ということになります。正/負という言葉が入ると少しややこしい感じがしますね。上の表では正を1、負を-1と置き換えました。

Cross-Validation

10秒で設定可能なlibsvmで機械学習を行う - Yuta.Kikuchiの日記 はてなブックマーク - 10秒で設定可能なlibsvmで機械学習を行う - Yuta.Kikuchiの日記
以前にlibsvmにてK-Fold-Cross-Validationを行いました。記事を書いた時は自分で学習データ,評価データを分割してK回学習/評価を行っていましたが、実はsvm-trainコマンドにはvオプションでK-Foldを指定でき、平均Accuracyを表示してくれるようです。libsvmと同じようなツールsvm-lightというものがありますが、xオプションを指定するとLeave-One-Out-Cross-Validationが実現できるようです。
libsvmを用いて再度news20-binaryをsvmの線形予測を行ってみます。news20-binaryはデータ量が多く学習に時間がかかりそうだったので半分のデータを利用しました。今回はCross-Validationをvオプションで行う事とPrecision,Recall,F値を求めます。通常のlibsvmではこれらを計算できないのでsleepy_yoshiさんが作られているpatchを当てます。LibSVMのcross validationオプションでprecision/recallを出力する - 睡眠不足?! はてなブックマーク - LibSVMのcross validationオプションでprecision/recallを出力する - 睡眠不足?! 注しなければいけないことはCross-Validationを実行するとmodelは作成されません。K-Foldを5と指定し、Cross Validation Accuracy = 84.21%を得る事ができました。

$ wget wget http://sleepyheads.jp/software/svm-train.patch

$ patch < svm-train.patch

$ gmake clean

$ gmake

$ wget http://www.csie.ntu.edu.tw/~cjlin/libsvmtools/datasets/binary/news20.binary.bz2

$ bzip2 -d news20.binary.bz2

$ cat news20.binary
+1 103:0.250000 279:0.250000 291:0.250000 5757:0.250000 16311:0.250000 16314:0.250000 30408:0.250000 85418:0.250000
-1 40:0.041065 75:0.041065 89:0.041065 97:0.041065 103:0.041065 108:0.041065 114:0.041065 149:0.041065 170:0.041065 171:0.041065 174:0.041065

$ grep '^\-1' news20.binary | head -n 5000 > train.txt
$ grep '^\+1' news20.binary | head -n 5000 >> train.txt

$ svm-train -v 5 train.txt
Cross Validation Accuracy = 84.21%
Positive (+1) class:
  precision = 0.7466
     recall = 0.922868
   F1 value = 0.825428

Negative (-1) class:
  precision = 0.9376
     recall = 0.787238
   F1 value = 0.855865

svmlightでも同じようなことをやってみます。libsvmとsvmlightの入力データ形式は互換性があるようです。まずはinstallするところから進めます。libsvmと同様にsvmlightも圧縮ファイルを解凍してgmakeするだけでsvm_learn,svm_classifyコマンドが生成されます。上で利用したtrain.txtとpredict.txtをそれぞれ用意してAccuracy/Preciseion/Recallの値を見てみます。svm_learnに少し時間がかかりましたが、Accuracy: 69.25% Precision:91.67% Recall:42.35%となりました。

$ wget http://download.joachims.org/svm_light/current/svm_light.tar.gz

$ tar -xzf svm_light.tar.gz

$ gmake

$ grep '^\-1' news20.binary | tail -n 4000 > predict.txt
$ grep '^\+1' news20.binary | tail -n 4000 >> predict.txt

$ svm_learn -x 1 train.txt model

$ svm_classify predict.txt model output
(略)
Leave-one-out estimate of the error: error=1.89%
Leave-one-out estimate of the recall: recall=98.28%
Leave-one-out estimate of the precision: precision=97.95%
Actual leave-one-outs computed:  561 (rho=1.00)
Runtime for leave-one-out in cpu-seconds: 2652.56

$ svm_classify predict.txt model output
Reading model...OK. (4120 support vectors read)
Classifying test examples..100..200..300..400..500..600..700..800..900..1000..1100..1200..1300..1400..1500..1600..1700..1800..1900..2000..2100..2200..2300..2400..2500..2600..2700..2800..2900..3000..3100..3200..3300..3400..3500..3600..3700..3800..3900..4000..4100..4200..4300..4400..4500..4600..4700..4800..4900..5000..5100..5200..5300..5400..5500..5600..5700..5800..5900..6000..6100..6200..6300..6400..6500..6600..6700..6800..6900..7000..7100..7200..7300..7400..7500..7600..7700..7800..7900..8000..done
Runtime (without IO) in cpu-seconds: 0.15
Accuracy on test set: 69.25% (5540 correct, 2460 incorrect, 8000 total)
Precision/recall on test set: 91.67%/42.35%

ROC曲線、AUC

ROC曲線は2値分類問題を解く場合に利用するもので、TruePositiveとFalsePositiveの割合をplotし、分類器の精度評価を行う目的で利用します。通常は縦軸にTruePositive、横軸にFalsePositiveを定義します。精度が高いことを証明するために理想の曲線としては横軸の値が小さい段階で縦軸の値が高く示し、そこから水平に伸びて行く状態です。縦軸には\frac{TP}{TP+FN} 横軸には\frac{FP}{FP+TN}をplotします。AUCとはROC曲線より下に示される面積であるのでROCが縦軸に高く引っ張られているほど面積も大きくなります。ランダムの場合ROC曲線が[0,0],[1,1]への直線となり、AUCは1*1/2 = 0.5となります。
LIBSVM Tools はてなブックマーク - LIBSVM Tools ROC Curve for Binary SVMという項目にlibsvm向けのMatlabPython用のコマンドスクリプトが用意されています。これを利用してROC曲線を描きAUCを算出してみます。残念ながら手元にはMatlabが無いのでPythonで試してみます。 http://www.csie.ntu.edu.tw/~cjlin/libsvmtools/roc/plotroc.py このplotroc.pyを取得します。この実行にはlibsvmを解凍した時に生成されるpythonディレクトリ以下のsvm.py、svmutil.pyというファイルを必要とします。python scriptを通してもlibsvmのオプションは基本的にそのまま利用できます。

$ wget http://www.csie.ntu.edu.tw/~cjlin/libsvmtools/roc/plotroc.py

$ python plotroc.py

$ python plotroc.py -v 5 train.txt

上を一度実行したのですが、実行環境のmemory不足で以下のようなエラーがでました。解決方法は現在調査中ですがスマートな対応はできないかもしれないです。ここではtrain.txtのデータ量を半分にする事で再度plotをしました。今度は正常に画像が出力され、AUC=0.7222という値を得る事ができました。ランダムが0.5なのでそれよりは良い結果を得る事ができました。

optimization finished, #iter = 3973
nu = 0.993250
obj = -7945.807458, rho = 0.999930
nSV = 7946, nBSV = 7946
Total nSV = 7946
Accuracy = 48.65% (973/2000) (classification)
Traceback (most recent call last):
  File "plotroc.py", line 219, in <module>
    main()
  File "plotroc.py", line 216, in main
    plot_roc(deci, train_y, output_file, output_title)
  File "plotroc.py", line 167, in plot_roc
    g = gnuplot(output)
  File "plotroc.py", line 63, in __init__
    self.__dict__['iface'] = popen(cmdline,'w')
OSError: [Errno 12] Cannot allocate memory
Exception AttributeError: "gnuplot instance has no attribute 'iface'" in  ignored

-----データ量を半分にしてplot
$ head -n 2500 train.txt > train2.txt
$ tail -n 2500 train.txt >> train2.txt
$ mv train2.txt train.txt 

$ python plotroc.py -v 5 train.txt

plotされた座標のデータを取得したい場合はplotroc.pyに対して以下の行を追加する事でできます。またAUCも画像で出力するのではなく標準でも出力するように修正しました。

$ diff -u plotroc.py plotroc2.py
--- plotroc.py	2010-11-11 18:45:28.000000000 +0900
+++ plotroc2.py	2012-09-09 17:58:31.000000000 +0900
@@ -150,6 +150,7 @@
 			tp+=1
 		else:
 			fp+=1
+		print fp/neg,tp/pos
 		xy_arr.append([fp/neg,tp/pos])
 
 	#area under curve
@@ -159,6 +160,7 @@
 		if x != prev_x:
 			aoc += (x - prev_x) * y
 			prev_x = x
+	print "AUC = %0.4f" % aoc 
 
 	#begin gnuplot
 	if title == None:

0.996 1.0
0.9964 1.0
0.9968 1.0
0.9972 1.0
0.9976 1.0
0.998 1.0
0.9984 1.0
0.9988 1.0
0.9992 1.0
0.9996 1.0
1.0 1.0

AUC = 0.7222

ここで気づいたのですが、libsvmはデフォルトでRBFを選択しているのでtオプションで線形を選択して再度実行してみます。結果的にはAUCが0.9973という恐ろしい値を得る事ができました。やり方が間違っていないか逆に心配です。

$ python plotroc.py -t 0 -v 5 -h 0 train.txt


Support Vector Machinesを用いた「魔法少女まどか☆マギカ」人物予測モデル

言語処理のための機械学習入門 (自然言語処理シリーズ)

言語処理のための機械学習入門 (自然言語処理シリーズ)

人物予測モデル

記事のタイトルがだいぶ固い内容になっていまいましたがやりたい事はとても簡単です。過去に発せられたまど☆マギ台詞の形態素を学習し、予測モデルを作成します。その後に未分類の形態素のデータセットを与えた時にどれだけ人物のラベル付けが正しく行われたかを評価します。予測モデルの対象となる人物は鹿目まどか/暁美ほむら/美樹さやか/キュゥべえ/佐倉杏子/巴マミの合計6名です。機械学習にはSVMを利用します。先に実験の結果をお伝えしておくと、台詞の形態素ベクトルでは十分なマルチラベリングができていません。それでもこの記事が気になる方は読み進めてください。処理手順の詳細は以下の通りです。

  • まど☆マギ台詞の収集。
  • 発言者の1行台詞を形態素解析し、形態素IDと形態素出現回数をベクトル化。
  • TrainデータとPredictデータを分離する。
  • SVMを利用したTrainデータの学習。Model作成。
  • Kfold-Cross-Validation実施。Modelの評価。

まど☆マギ台詞の収集

魔法少女まどか☆マギカ WIKI - ネタバレ考察/台詞集 はてなブックマーク - 魔法少女まどか☆マギカ WIKI - ネタバレ考察/台詞集
上のWIKIの台詞を利用させてもらっています。※承諾は得ていません。
Pythonコードで各登場人物の台詞を取得します。Webページのスクレイピングによる抽出です。実行すると各登場人物ファイルにデータを落とし込みます。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys,re,urllib,urllib2
urls = { 'http://www22.atwiki.jp/madoka-magica/pages/131.html' : 'madoka.txt', 
         'http://www22.atwiki.jp/madoka-magica/pages/57.html'  : 'homura.txt',
         'http://www22.atwiki.jp/madoka-magica/pages/123.html' : 'sayaka.txt',
         'http://www22.atwiki.jp/madoka-magica/pages/130.html' : 'mami.txt',
         'http://www22.atwiki.jp/madoka-magica/pages/132.html' : 'kyoko.txt',
         'http://www22.atwiki.jp/madoka-magica/pages/56.html'  : 'kyube.txt'
        }
opener = urllib2.build_opener()
ua = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/534.51.22 (KHTML, like Gecko) Version/5.1.1 Safari/    534.51.22'
referer = 'http://www22.atwiki.jp/madoka-magica/'
opener.addheaders = [( 'User-Agent', ua ),( 'Referer', referer )]
for k,v in urls.iteritems():
    f = open( './data/' + v , 'w' )
    content = opener.open( k ).read()
    if re.compile( r'^「(.*?)」$', re.M ).search( content ) is not None: 
        lines = re.compile( r'^「(.*?)」$', re.M ).findall( content )
        for line in lines:
            f.write( line + "\n" )
f.close()

形態素解析形態素ベクトル化

Mecabによる形態素解析

MeCab: Yet Another Part-of-Speech and Morphological Analyzer はてなブックマーク - MeCab: Yet Another Part-of-Speech and Morphological Analyzer
形態素解析にはMecabを利用します。Mecabの設定完了後、import Mecabmecab.parseToNodeを指定すると単語の形態素分解が出来ます。試しに文書をスペース区切りの分かち書きします。上で収集した台詞をdata/all.txtというファイルにまとめます。そして以下のPythonコードを実行します。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
reload(sys)
sys.setdefaultencoding('utf-8')

import MeCab
mecab = MeCab.Tagger('-Ochasen')

allfile = 'data/all.txt'
data = open( allfile ).read()
node = mecab.parseToNode( data )
phrases = node.next

while phrases:
    try:
        print node.surface + " ",
        node = node.next
    except AttributeError:
       break
  暁美  ほ  むら  です  。  よろしく  お願い  し  ます  東京  の  、  ミッション  系  の  学校  よ  やっ  て  無かっ  た  わ  ごめんなさい  。  何だか  緊張  し  すぎ  た  みたい  で  、  ちょっと  、  気分  が  。  保健  室  に  行か  せ  て  貰える  かしら  いえ  、  おか  まい  なく  。  係  の  人  に  お願い  し  ます  から  鹿目  まどか  さん  。 
形態素ID付け/ベクトル化

SVMはベクトル空間でのマージン最大化を行うので、形態素そのままだと機械学習ができません。そこで形態素に対して整数のIDを割り当て、1行の台詞で何回出現したかを記録します。libsvmに最終的に渡したいデータは
正解ラベル<スペース>形態素ID:出現回数<スペース>形態素ID:出現回数
という形式にします。正解ラベルは人物ID、形態素IDは全形態素でユニークなIDを示します。以下のコードを実行するとlibsvmへの入力データを生成します。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
reload(sys)
sys.setdefaultencoding('utf-8')

import MeCab
mecab = MeCab.Tagger('-Ochasen')
names = { 'homura' : 1,
          'kyoko' : 2,
          'kyube' : 3,
          'madoka' : 4,
          'mami' : 5,
          'sayaka' : 6 }

allfile = './data/all.txt'
data = open( allfile ).read()
node = mecab.parseToNode( data )
words = {}
num = 0;
phrases = node.next
while phrases:
    try:
        k = node.surface
        k = k.strip().rstrip()
        if k in words:
            pass
        else:
            words[k] = num;
            num = num + 1 
        node = node.next
    except AttributeError:
       break

for i in names.keys():
    file = './data/' + i + '.txt'
    for line in open( file, 'r' ):
        line = line.strip().rstrip()
        n = mecab.parseToNode( line )
        p = n.next
        attrs = {}
        while p:
            try:
                k = n.surface
                if k not in words:
                   break 
                id = words[k]
                if id in attrs:
                    attrs[id] = attrs[id] + 1
                else:
                    attrs[id] = 1
                n = n.next
            except AttributeError:
               break

        print names[i],
        for ak in sorted( attrs.keys() ):
            print str(ak) + ":" + str(attrs[ak]), 
        print 
6 0:2 12:1 46:1 199:1 2188:1 2452:1
6 0:2 54:1 757:1 1992:1 2453:1
6 0:2 5:1 8:2 11:1 46:1 60:1 62:1 84:2 668:1 1329:1 1974:1 2005:1 2454:1 2455:1
6 0:2 84:1 872:1 2456:1 2457:1
6 0:2 2:1 4:1 5:1 8:1 26:1 33:1 44:1 46:1 54:2 66:1 83:1 100:1 144:3 353:1 530:1 1673:1 1992:1 2188:1 2458:1 2459:1 2460:1 2461:1 2462:1 2463:1 2464:1
6 0:2 12:1 20:1 44:2 54:1 62:1 71:1 84:1 110:1 144:2 189:1 292:1 406:1 418:1 489:2 572:1 1974:1 2140:1 2188:2 2465:1 2466:1 2467:1 2468:1 2469:1 2470:1
6 0:2 11:2 33:2 46:1 62:1 69:1 84:1 85:1 93:1 107:1 132:1 189:1 201:1 209:1 489:3 969:1 2188:2 2378:1 2453:1 2466:1 2471:1 2472:1

機械学習

2Fold-Cross-Validation

形態素をベクトル化したデータを3つに分割して、それぞれを学習/評価データとして利用します。今回はデータも少ないので2つに分けています。上で出力したデータをvector.txtとして保存した場合、以下のスクリプトでデータを分離する事が出来ます。shuffle分割したファイルを別名で保存し直して、libsvmsvm-trainコマンドに掛けます。その結果、予測Modelのファイルが生成されます。

$ perl -MList::Util=shuffle -e 'print shuffle(<>)' < vector.txt | split -l 945
$ mv xaa data1.txt
$ mv xab data2.txt
$ svm-train data1.txt 
*
optimization finished, #iter = 136
nu = 0.739927
obj = -200.401126, rho = 0.908119
nSV = 214, nBSV = 189
*
optimization finished, #iter = 187
nu = 0.521964
obj = -201.006920, rho = 0.935477
nSV = 221, nBSV = 180
*
optimization finished, #iter = 141
nu = 0.824490
obj = -200.481473, rho = 0.917041
nSV = 214, nBSV = 189

$ svm-train data2.txt 
*
optimization finished, #iter = 178
nu = 0.414698
obj = -157.645809, rho = 0.977467
nSV = 196, nBSV = 128
*
optimization finished, #iter = 121
nu = 0.774510
obj = -157.307372, rho = 0.989609
nSV = 172, nBSV = 142
*
optimization finished, #iter = 135
nu = 0.661088
obj = -157.307427, rho = 0.963986
nSV = 181, nBSV = 141
*

$ ls
合計 492
drwxr-xr-x 2 yuta yuta   4096  9月  2 00:27 .
drwxr-xr-x 5 yuta yuta   4096  9月  2 00:00 ..
-rw-r--r-- 1 yuta yuta  69615  9月  2 00:19 data1.txt
-rw-r--r-- 1 yuta yuta  84900  9月  2 00:27 data1.txt.model
-rw-r--r-- 1 yuta yuta  66225  9月  2 00:19 data2.txt
-rw-r--r-- 1 yuta yuta  81545  9月  2 00:27 data2.txt.model
-rw-r--r-- 1 yuta yuta 135840  9月  1 23:52 vector.txt
prediction
$ svm-predict data2.txt data1.txt.model output > accuracy1.txt
Accuracy = 31.9915% (302/944) (classification)
$ svm-predict data1.txt data2.txt.model output2 > accuracy2.txt
Accuracy = 30.2646% (286/945) (classification)

大きな問題として学習データが少な過ぎて評価がしづらい状態になりました。またoutputファイルの中身を見てみると全て4(鹿目まどか)というラベル付けがされてしまっていて評価に失敗しています。これは学習データ量に4のラベルが偏っていたためと考えます。全体的な話だとAccuracyが32%、30%となっており登場人物6名のマルチラベル問題なので1/6= 16%と比較すると倍以上の正解度と考えられますが、前述したように学習自体が正しく行われていないので再度やり直します。

学習データを整えて再度prediction

各登場人物の台詞ベクトルデータを100件ずつ学習データ、評価データに使います。巴マミの台詞データは残念ながら200件データが無かったのでラベリングの対象から外しました。よって5人分のラベリングになります。predictの結果は以下の通りで、Accuracyが53.8%、32.2%という結果でどちらとも1/5=20%の数値は超えています。Model1,Model2で予測されたラベルの個数を見てみました。Model1はそれなりにデータが分散していますが、Model2では佐倉杏子に偏ってしまい、学習が失敗していることが分かります。

$ svm-predict data2.txt data1.txt.model output 
Accuracy = 53.8% (269/500) (classification)
$ svm-predict data1.txt data2.txt.model output2
Accuracy = 32.2% (161/500) (classification)
人物 Model1 Model2
暁美ほむら 109 22
佐倉杏子 29 437
キュゥべえ 53 15
鹿目まどか 273 12
美樹さやか 36 14
形態素の評価
  • 登場人物の名前(鹿目まどか/暁美ほむら等)は一人称/二人称のどちらでも判断されるケースがあり、有効な形態素になりませんでした。
  • ワルプルギスや魔法/少女などの単語も特定の人物が発している形態素ではないので、様々な登場人物にラベリングされてしまっています。
  • その他有効な形態素を探し中ですがこれと言った材料が無く、予測Model作成の成功とは言えない状態になっています。

10秒で設定可能なlibsvmで機械学習を行う

Support Vector Machines (Information Science and Statistics)

Support Vector Machines (Information Science and Statistics)

libsvm

前回RでのSVMを簡単に紹介しましたが、今日はlibsvmを利用したirisの分類学習を行いたいと思います。libsvmは導入がめちゃくちゃ簡単なところが売りだと思います。zipをlibsvmサイトからdownloadして展開してgmakeで設定完了です。

設定
$ wget "http://www.csie.ntu.edu.tw/~cjlin/cgi-bin/libsvm.cgi?+http://www.csie.ntu.edu.tw/~cjlin/libsvm+zip"
$ unzip libsvm-3.12.zip
$ cd libsvm-3.12
$ gmake
$ ls
-rw-r--r-- 1 yuta yuta  1497  1月 31  2012 COPYRIGHT
-rw-r--r-- 1 yuta yuta 72186  4月  1 07:17 FAQ.html
-rw-r--r-- 1 yuta yuta   732  1月  2  2012 Makefile
-rw-r--r-- 1 yuta yuta  1087  9月 12  2010 Makefile.win
-rw-r--r-- 1 yuta yuta 27332  2月  3  2012 README
-rw-r--r-- 1 yuta yuta 27670  7月 12  2003 heart_scale
drwxr-xr-x 3 yuta yuta  4096  4月  1 07:18 java
drwxr-xr-x 2 yuta yuta  4096 10月 30  2011 matlab
drwxr-xr-x 2 yuta yuta  4096  3月 22 12:25 python
-rwxr-xr-x 1 yuta yuta 67413  8月 29 07:53 svm-predict
-rw-r--r-- 1 yuta yuta  5381  2月  5  2011 svm-predict.c
-rwxr-xr-x 1 yuta yuta 15650  8月 29 07:53 svm-scale
-rw-r--r-- 1 yuta yuta  7042  5月 28  2011 svm-scale.c
drwxr-xr-x 5 yuta yuta  4096  2月  3  2012 svm-toy
-rwxr-xr-x 1 yuta yuta 71912  8月 29 07:53 svm-train
-rw-r--r-- 1 yuta yuta  8891  5月 27  2011 svm-train.c
-rw-r--r-- 1 yuta yuta 63412 12月 26  2011 svm.cpp
-rw-r--r-- 1 yuta yuta   434  9月 12  2010 svm.def
-rw-r--r-- 1 yuta yuta  3129  2月  3  2012 svm.h
-rw-r--r-- 1 yuta yuta 93208  8月 29 07:53 svm.o
drwxr-xr-x 2 yuta yuta  4096  2月 24  2012 tools
drwxr-xr-x 2 yuta yuta  4096  3月 16 00:44 windows
scale,train,predictコマンド

gmakeを行うと以下のコマンドが生成されます。これらのコマンドに対して学習データ,学習Model,評価データを与える事によりSVMによる機械学習が実現できます。各種コマンドの使い方については以下のサイトが詳しいと思います。またコマンドに対してaliasを貼っておきます。

  • svm-scale : データの正規化を行うコマンド
  • svm-train : 学習データからModelを生成するコマンド
  • svm-predict:評価データとModelから分類とaccuracyを導きだすコマンド
alias svm-predict=/home/yuta/work/libsvm/libsvm-3.12/svm-predict
alias svm-scale=/home/yuta/work/libsvm/libsvm-3.12/svm-scale
alias svm-train=/home/yuta/work/libsvm/libsvm-3.12/svm-train
irisデータの学習

R言語には標準でirisのデータが備わっていましたが、libsvmのdataformatに従ったサンプルも以下にあります。libsvmに対しての学習にはiris.scaleのデータはiris.scaleを利用します。Iris Setosa、Iris Versicolour 、Iris Virginicaといった3つのアヤメ種別(class)と蕚片の長さ/幅、花びらの長さ/幅といった4つの特徴(attribute)を持つものです。種別に対しては1から3のラベルを貼り、各種特徴にも特徴番号を付け、-1〜1の正規化した特徴量を持たせています。以下にデータ取得から学習までのコマンドを記載します。svm-trainコマンドを実行するとiris.scale.modelというmodelファイルが生成されます。

$ wget "http://www.csie.ntu.edu.tw/~cjlin/libsvmtools/datasets/multiclass/iris.scale"

$ less iris.scale
1 1:-0.555556 2:0.25 3:-0.864407 4:-0.916667 
1 1:-0.666667 2:-0.166667 3:-0.864407 4:-0.916667 
1 1:-0.777778 3:-0.898305 4:-0.916667 
1 1:-0.833333 2:-0.0833334 3:-0.830508 4:-0.916667 
1 1:-0.611111 2:0.333333 3:-0.864407 4:-0.916667 
1 1:-0.388889 2:0.583333 3:-0.762712 4:-0.75 
1 1:-0.833333 2:0.166667 3:-0.864407 4:-0.833333 
1 1:-0.611111 2:0.166667 3:-0.830508 4:-0.916667 
1 1:-0.944444 2:-0.25 3:-0.864407 4:-0.916667 
1 1:-0.666667 2:-0.0833334 3:-0.830508 4:-1 
1 1:-0.388889 2:0.416667 3:-0.830508 4:-0.916667 
1 1:-0.722222 2:0.166667 3:-0.79661 4:-0.916667

$ svm-train iris.scale 
*
optimization finished, #iter = 12
nu = 0.092569
obj = -5.138435, rho = -0.062041
nSV = 11, nBSV = 8
*
optimization finished, #iter = 25
nu = 0.048240
obj = -2.538201, rho = 0.016007
nSV = 8, nBSV = 2
*
optimization finished, #iter = 36
nu = 0.447449
obj = -32.467419, rho = 0.105572
nSV = 46, nBSV = 42
Total nSV = 58

$ ls
drwxr-xr-x 2 yuta yuta 4096  8月 29 09:03 .
drwxr-xr-x 4 yuta yuta 4096  8月 29 08:57 ..
-rw-r--r-- 1 yuta yuta 6954  6月  8  2005 iris.scale
-rw-r--r-- 1 yuta yuta 3226  8月 29 09:03 iris.scale.model
Modelによる評価

予測はsvm-predictコマンドで行います。ここでは学習データで使ったデータをそのまま予測用データとして利用します。svm-predictの第一引数に評価データ、第二引数に学習で生成したmodel、第三引数にラベル付けの結果ファイルを指定します。標準出力でaccuracyが出てくるのでそれをファイルにリダイレクトしておきます。個々の結果ではaccuracyが97%と出ました。

$ svm-predict iris.scale iris.scale.model iris.scale.output > accuracy.txt
$ less accuracy.txt
Accuracy = 97.3333% (146/150) (classification)
K-分割交差検定

上の評価ではaccuracyが97%と出ていますが、学習データと評価データが同じでは本来のModel精度が分からないので、まずは学習データと評価データを分離します。ここでは一般的なCross Validationにしたがって150個のirisサンプルを50個ずつに分割します。50個のデータセットをA,B,Cというファイルで定義した場合、学習と評価を3パターンで試します。交差検定を"K-fold cross-validation"と呼びますが、ここではK=3となります。

  • 学習(A,B) 評価(C)
  • 学習(A,C) 評価(B)
  • 学習(B,C) 評価(A)

行を完全にrandomでshuffleして100行の学習データ/50行の評価データを作成します。

$ perl -MList::Util=shuffle -e 'print shuffle(<>)' < iris.scale | split -l 50
$ cat xaa xab | wc -l
$ cat xaa xab > 1.train.txt
$ cat xaa xac > 2.train.txt
$ cat xab xac > 3.train.txt
$ mv xac 1.predict.txt
$ mv xab 2.predict.txt
$ mv xaa 3.predict.txt

続いて学習と予測、accuracy算出までをやります。下の結果から分かるようにAccuracyの算出が94%、96%、98%といずれも高い数値が出ました。Cross Validationでもかなり高い確率で予測が出来ていると言えます。

$ svm-train 1.train.txt
$ svm-train 2.train.txt
$ svm-train 3.train.txt
*
optimization finished, #iter = 21
nu = 0.065188
obj = -2.237066, rho = 0.057124
nSV = 7, nBSV = 1
*
optimization finished, #iter = 30
nu = 0.558458
obj = -26.839488, rho = -0.084482
nSV = 39, nBSV = 34
*
optimization finished, #iter = 8
nu = 0.119403
obj = -4.403712, rho = 0.039139
nSV = 10, nBSV = 7
Total nSV = 49
*
optimization finished, #iter = 22
nu = 0.070101
obj = -2.437480, rho = 0.018715
nSV = 8, nBSV = 2
*
optimization finished, #iter = 17
nu = 0.443524
obj = -20.533308, rho = -0.040417
nSV = 30, nBSV = 28
*
optimization finished, #iter = 12
nu = 0.128258
obj = -4.819891, rho = -0.074851
nSV = 11, nBSV = 8
Total nSV = 44
*
optimization finished, #iter = 8
nu = 0.129629
obj = -4.619123, rho = 0.001523
nSV = 11, nBSV = 8
*
optimization finished, #iter = 9
nu = 0.067031
obj = -2.349066, rho = 0.175743
nSV = 9, nBSV = 3
*
optimization finished, #iter = 39
nu = 0.506626
obj = -27.356803, rho = 0.120588
nSV = 38, nBSV = 33
Total nSV = 50

$ svm-predict 1.predict.txt 1.train.txt.model 1.output.txt > 1.accuracy.txt
$ svm-predict 2.predict.txt 2.train.txt.model 2.output.txt > 2.accuracy.txt
$ svm-predict 3.predict.txt 3.train.txt.model 3.output.txt > 3.accuracy.txt

$ less 1.accuracy.txt 
Accuracy = 94% (47/50) (classification)
$ less 2.accuracy.txt 
Accuracy = 96% (48/50) (classification)
$ less 3.accuracy.txt 
Accuracy = 98% (49/50) (classification)