【Java初心者】Wekaによる機械学習 02-コマンドラインでテキスト分類

# さくっと機械学習するために

WekaのUIの使い方を覚えるのが面倒なので、これまで、JavaでWekaのAPIを呼び出すプログラムを書いていました。幸いなことに、Weka Wiki( https://waikato.github.io/weka-wiki/use_weka_in_your_java_code/ )には、参考になるサンプルが丁寧に書かれているので、ふんだんに参考にさせていただきました。

また、世界中のエンジニアが、いろいろなブログで、APIの呼び出し方をまとめてくださっているのも助かりますね。

ただ、手っ取り早く機械学習するには、コマンドラインで答えが出るのがありがたい。もちろんその方法も様々なブログに書かれているけれど、テキスト分類の際の「ストップワード」の指定方法が見つからず苦労しました。

試行錯誤の結果「多分これだろう」という方法が見つかりましたので、自分用のメモのつもりでまとめてみます。

# 元のデータを形態素解析

たとえば迷惑メールの自動分類をやってみるとします。

こちら( https://sleepy-yoshi.hatenablog.com/entry/20080218/p2 )の方が、まとめてくださっていますが、分類したいメール本文をspam,hamというディレクトリに保存し、weka.core.converters.TextDirectoryLoader と処理すると、arffデータに整えてくれます。ただ、日本語の文章を分類には、事前に形態素解析をしておかなければなりません。

このため、簡単なRubyスクリプトを書いて処理してみました。(他にいい方法があるかもしれません)

```ruby
#!/usr/bin/ruby -Ku

# dirディレクトリで
# ./mecab.rb spam/*txt などとして利用する
for files in ARGV do
  a = `mecab -Owakati #{files}`
  File.open(files , "w") do |ofile|
    ofile.puts(a)
  end
end
```

spam,hamディレクトリのどちらも処理できたら
```
java -cp mini-weka-3.9.15955.jar \
weka.core.converters.TextDirectoryLoader -dir dir > spam.arff
```
とすると、arffにまとめてくれる。(spam.arffの内容)

```
@relation XXX_dir

@attribute text string
@attribute @@class@@ {ham,spam}

@data

'ETC ザービス を ご 利用 いただき ありがとう ござい ます 。 フィッシングメール が 汇濫 し 、 ユーザー の 財産 に 被害 が 出 て いる こと から 、 シス\
テム の リスク 管理 を 強化 し 、 警察 や 銀行 と 協力 し て 、 ユーザー の 財産 が 被害 を 受け ない よう に し て いる 。 システム の 指示 を 受け \
た 后 、 口座 の 使用 を 一時 停止 し 、 警視庁 特殊 詐欺 担当 に 通知 し 、 銀行 に クレジットカード と カード の 凍結 を 通知 し ます 。 警視庁 の \
許 欺 部門 が 捜査 に 入り ます メール を 受け取っ た ユーザー は 、 あなた の アカウント に リスク が ある こと を 示す メール を 受信 し て から 12\
 時間 以内 に ログイン し て アカウント を 確認 し て ください 。 そう し ない と ETC アカウント 、 クレジットカード 、 銀行 を 利用 でき なく なり \\
ます 。 これ を クリック し て 検証 ご 不便 や ご 心配 を おかけ し た こと を 深く お 诧 び 申し上げ ます 。 ご 理解 ありがとう ござい ます 。 \n',s\
pam
'年会 費 は 永年 無料 。 1 年間 最大 1 万 円 分 の ケータイ 補償 や ETC カード 初年度 無料 など 様々 な 特典 が あり ます 。 \n',ham
'Amazon プライム を ご 利用 いただき ありがとう ござい ます 。 Amazon プライム の 会費 の お 支払い に ご 指定 いただい た お客様 の お 支払い 方法 \\
が 承認 さ れ ない ため 、 Amazon プライム の 会費 （ 税込 500 円 ） を ご 請求 する こと が でき ませ ん でし た 。 現在 、 Amazon プライム 会員 の \
特典 は ご 利用 いただけ ませ ん 。 6 日 以内 に お 支払 方法 を 更新 いただけ ない 場合 は 、 お客様 の Amazon プライム 会員 資格 は キャンセル さ \\
れ ます 。 引き続き Amazon プライム の 特典 を ご 利用 さ れ たい 場合 、 お 支払い 方法 を 更新 する に は 、 以下 の リンク を クリック し て くだ\\
さい 。 \n',spam
:
:
```

# ストップワードをファイルにまとめる

ストップワードは、自然言語処理を行うために除外する語句群です。助詞(は,が など)や数字、記号などは、機械学習のためには重要な語句ではないため、形態素解析をしたあとで、それを取り除きます。

以下のようなテキストファイルを stopwords.txt などのファイル名で保存します。コードはUTF-8にします。
```
お
に
の
を
おいて
が
に
、
こと
でき
ない
は
ませ
ん
:
:
```

# StringToWordVectorのオプションでストップワードを指定

コマンドラインで、以下のように実行します
```
java -cp mini-weka-3.9.15955.jar \
weka.filters.unsupervised.attribute.StringToWordVector \
 -stopwords-handler "weka.core.stopwords.WordsFromFile -stopwords stopwords.txt" -i spam.arff 
```

ストップワード指定のオプションは、ダブルクオーテーションでくくり、その中にストップワードをまとめたテキストファイルを指定するところにたどり着くのに苦労しました。

結果は次のようになります。リダイレクトで別のarffファイルに保存します。
```
@relation 'XX_dir-weka.filters.unsupervised.attribute.StringToWordVector-R1-W1000-prune-rate-1.0-N0-stemmerweka.core.stemmers.NullStemmer-stopwords-h\
andlerweka.core.stopwords.WordsFromFile -stopwords stopwords.txt-M1-tokenizerweka.core.tokenizers.WordTokenizer -delimiters \" \\r\\n\\t.,;:\\\'\\\"(\
)?!\"'                                                                      

@attribute @@class@@ {ham,spam}
@attribute あう numeric
@attribute あっ numeric
@attribute あり numeric
@attribute ありがとう numeric
@attribute ありま numeric
@attribute ある numeric
@attribute い numeric
@attribute いたし numeric
@attribute いただい numeric
:
:
```

ストップワードをもっと増やしたほうが良さそうですね。

# 分類

上記arffを見ると、一番最初の項目が、spam,ham という目的変数になっているので、weka.filters.unsupervised.attribute.Reorder で末尾に入れ替えます。-Rオプションで、2-lastを最初に持っていき、その後firstが続くようにするという意味です。

```
java -cp mini-weka-3.9.15955.jar \
weka.filters.unsupervised.attribute.Reorder -R 2-last,first \
-i spam1.arff > spam2.arff
```
その後、機械学習させます。(J48アルゴリズムによる決定木の例)
データが少ない場合は、交差検証オプション(-x)を2などとします。
```
java -cp mini-weka-3.9.15955.jar \
weka.classifiers.trees.J48 -x 2 -t spam2.arff -k -d spam.model
```

J48アルゴリズムは、学習結果がテキストでわかりやすく表示されますので、それを見ると、ストップワードを追加するべきかがわかります。ストップワードを育てていくのも面白いですね。

問題なければ、作成したモデル(spam.model)を使って、テストデータを分類します。