ノンプログラミングで機械学習サービスが作りたい! テキスト分類編

# はじめに
「機械学習部分のプログラムは作りたくないが、機械学習を使ったサービスは作りたい」という記事です。**機械学習に関しては**ほとんどノンプログラミングで、精度はそれなり、「分類してみた」だけではなく実際にサービスに組み込むまでの解説記事を目指します。

ソースはgithubにあります。また、サンプルの学習済みモデルを同梱してありますので記事の手順を進めれば即座に試せます。
https://github.com/shuukei-imas-cg/imas_cg_words

## この記事で読者ができるようになること
筆者が制作した「[シンデレラガールズ台詞判定](http://www.shuukei.info/imas_cg_words/)」のサブセット的なサービスを段階を踏んで制作していきます。これはテキスト分類の応用として、「アイドルマスターシンデレラガールズ」に登場する183人のアイドルの台詞を学習させ、任意の文章を入力すると「誰が発した台詞らしいか」を判定するサービスです。

今回は、ローカルPCで標準入力からテキストを入力すると判定結果が表示される、というところまでを扱います。


# 対象
## 想定する読者
- Python初心者～中級者
- 機械学習にこれから触れてみたい人
- 機械学習やディープラーニングのコードなんか書きたくない! 面倒!! という人

## 動作環境
- CentOS or Ubuntu
- Python 2.7以上
- Jubatus
- MeCab
- mecab-ipadic-NEologd


# 使用するフレームワーク、ソフトウェアについて
## Jubatusについて
Jubatus(ユバタス)はPFN & NTTが開発したオンライン機械学習向け分散処理フレームワークです。高速性とスケーラビリティを両立しており、オンラインで(サービスを動かしながら)学習もできます。そんなJubatusの使い方は、極端にいえば以下の2つだけです。

- 学習( 正解ラベル, 正解データ )
- 分類( 分類したいデータ ) -> 推定されたラベルを返す

このJubatus(正確にはJubatusにいくつもある機能のうちのClassifier)を使っていきます。

## MeCabについて
MeCab(和布蕪:めかぶ)は自然言語処理分野では定番中の定番といえる形態素解析エンジンです。非常に高速に動作し、各種言語からのバインディングも用意されています。
「形態素解析」は、入力された文章を「形態素」という、言語において、単語より小さい、それ以上分割できない、意味を持つ最小の単位へ分割し、形態素の品詞などを推定する処理です。

## mecab-ipadic-NEologdについて
mecab-ipadic-NEologdは、Web上のテキストから得た新語を追加したMeCab用のシステム辞書です。常にWebの言語資源をクロールして週2回(月曜、木曜)更新されており、「新語を変に分割せずにひとかたまりで扱いたい」場合に非常に有効です。
また、単純にMeCab添付のIPA辞書にないエントリが多く採録されています。

MeCabとmecab-ipadic-NEologdを組み合わせて利用するには、MeCabのシステム辞書としてmecab-ipadic-NEologdのディレクトリを指定します。
例えば、コマンドラインから以下のように入力します。

```
$ mecab -d /usr/local/lib/mecab/dic/mecab-ipadic-neologd/
Nintendo Switchソフト新作発表
Nintendo Switch 名詞,固有名詞,一般,*,*,*,Nintendo Switch,ニンテンドースイッチ,ニンテンドースイッチ
ソフト  名詞,一般,*,*,*,*,ソフト,ソフト,ソフト
新作    名詞,サ変接続,*,*,*,*,新作,シンサク,シンサク
発表    名詞,サ変接続,*,*,*,*,発表,ハッピョウ,ハッピョー
EOS
```

Nintendo Switchを一つのかたまりとして扱っていることがわかります。


# インストール手順
まずサンプルプログラムを動かしてみましょう。以下の手順はCentOS 6.9を想定したものですが、Ubuntu用のインストール手順もそれぞれのミドルウェアの説明に日本語で解説してありますので、適宜読み替えてください。

## Jubatusのインストール
```
# Jubatus の Yum リポジトリをシステムに登録
sudo rpm -Uvh http://download.jubat.us/yum/rhel/6/stable/x86_64/jubatus-release-6-2.el6.x86_64.rpm

# jubatus と jubatus-client のパッケージをインストール
sudo yum install jubatus jubatus-client

# JubatusのMeCabプラグインをインストール
sudo yum install jubatus-plugin-mecab

# JubatusのPythonクライアントライブラリをインストール
sudo pip install jubatus
# virtualenv環境を使用している場合は、その環境に入ってからpip install jubatusを実行
```

## MeCab, mecab-ipadic-NEologdのインストール
mecab-ipadic-NEologd標準のインストール手順で、MeCabも一緒にインストールします。

```
# MeCabのインストール
sudo rpm -ivh http://packages.groonga.org/centos/groonga-release-1.1.0-1.noarch.rpm
sudo yum install mecab mecab-devel mecab-ipadic git make curl xz

cd (適当なフォルダ)
git clone --depth 1 https://github.com/neologd/mecab-ipadic-neologd.git
cd mecab-ipadic-neologd
./bin/install-mecab-ipadic-neologd -n
```
最後のコマンドが正常に進むと、NEologdの辞書をインストールするか否かのプロンプトが表示されますので、Yesを入力してください。辞書のインストール先が表示されますので、それを控えておいてください。

## ソースファイルのcloneと準備

```
git clone https://github.com/shuukei-imas-cg/imas_cg_words.git
cd imas_cg_words/jubatus
```

ここで、NEologdの辞書のインストール先が /usr/local/lib/mecab/dic/mecab-ipadic-neologd 以外であれば、serif.jsonのパスを修正してください。

```serif.json(の一部)
修正前 "arg": "-d /usr/local/lib/mecab/dic/mecab-ipadic-neologd",
修正例 "arg": "-d /usr/lib64/mecab/dic/mecab-ipadic-neologd",
```

パスを修正または確認したら、以下のように続けます。
(2017-04-21追記: モデルファイルを-mオプションでロードする場合、設定ファイルとの設定の食い違いを避けるため、モデル学習時の設定が優先されてしまうようです。serif.jsonでNEologdのパスを指定しても反映されません。
従って、添付のモデルファイルを使用する際は、/usr/local/lib/…にシンボリックリンクを張るか、近日リリース予定の[jubakit](https://github.com/jubatus/jubakit) 0.4.3以降に添付のツールを使ってモデルファイル中の設定を書き換えてください)

```
# Jubatus分類器を起動、常駐
# -f で設定ファイルをロード、-m で学習済みのモデルファイルをロードする
jubaclassifier -f serif.json -m model/sample.jubatus &
cd ..
cd localservice/
python classify.py
```

classify.pyが起動したら、適当に単語や台詞を入力してみてください。添付のモデルファイルは、アイドルマスターシンデレラガールズに登場する喜多日菜子、棟方愛海、浅利七海の3人のアイドルの台詞290個ずつ、合計870個の台詞から学習した特徴が保存されています(元の台詞を復元できる情報ではありません)。
この3人の誰の台詞らしいかがスコア降順で表示されます。

![151s-3.png](https://qiita-image-store.s3.amazonaws.com/0/175910/674a208a-3ff9-d1fb-de83-f27466da5078.png)

ここでは上記の学習データには含まれない台詞を入力しています。(原作知識がないと判断できないかもしれませんが)おおむね正しいようですね。


# 解説

## 性能評価
![score.png](https://qiita-image-store.s3.amazonaws.com/0/175910/96a711fe-415f-45af-3e93-d09664d699a6.png)

用意したデータを10分割し、9割で学習、残りの1割でテスト…を10回繰り返して平均を取る交差検定を行います。
用語の詳しい解説は他の記事を参照していただくとして、正答率(Accuracy)が約93%、F値の平均が0.94となりました。


## 実装について
ここまで動かすために書いたコードはあわせて65行、設定ファイルは1つだけです。

### train.py(学習)の解説
CSVファイルをロードし、1行ずつ処理していきます。ここで、line[0]には正解ラベル(アイドル名)が、line[1]にはテキスト(台詞)が入っています。
正解ラベルと、学習すべきテキスト(台詞)をJubatusが扱えるようににDatum形式にしつつ、共にリストに代入します。Datumはkey-valueデータ形式になっていて、keyが特徴名、valueが特徴量に相当します。keyである'serif'の説明はあとで行います。

全件をリストに追加したらシャッフルし、client.trainで学習します。

```train.py(抜粋)
    train_data = []
    for line in reader:
        train_data.append((line[0], Datum({'serif': line[1]})))
    random.shuffle(train_data)
    for data in train_data:
        client.train([data])
```

### serif.json(Jubatusの設定ファイル)の解説
機械学習(テキスト分類)に関する処理はすべてこの設定ファイルで指定しています。

```serif.json
{
  "method": "CW",
  "converter": {
    "string_filter_types": {},
    "string_filter_rules": [],
    "string_types": {
      "mecab": {
          "method": "dynamic",
          "path": "libmecab_splitter.so",
          "function": "create",
          "arg": "-d /usr/local/lib/mecab/dic/mecab-ipadic-neologd",
          "ngram": "1",
          "base": "false",
          "include_features": "*",
          "exclude_features": ""
       }
    },
    "string_rules": [
      {"key": "serif", "type": "mecab", "sample_weight": "bin", "global_weight": "bin"}
    ]
  },
  "parameter": {
    "regularization_weight" : 1.0
  }
}
```
string_rulesのエントリで、先程登場した"serif"という特徴に対し、"mecab"という前処理をすることを指定しています。その"mecab"処理の中身はstring_typesで定義されていて、JubatusのMeCabプラグインを使って分かち書きすること、mecab-ipadic-NEologd辞書の指定、分かち書きされた形態素1個単位をそのまま特徴として使うこと("ngram": "1", "base": "false", など)が指定されています。

なお、今回は先頭の"method"で、Jubatusに実装されているアルゴリズムの中からCWというアルゴリズムを指定していますが、タスクによっては他のアルゴリズムに変えたほうが精度がよくなる可能性があります。
また、この設定ファイルでは、他にも様々な前処理(HTMLタグの除去など)を指定することができます。[^1]

[^1]: Jubatus - [データ変換](http://jubat.us/ja/fv_convert.html)

### classify.pyの解説
ほんの20行ほどのコードです。

```classify.py
# -*- coding:utf-8 -*-
from __future__ import absolute_import, print_function, unicode_literals

import jubatus
from config import host, port, name
from jubatus.common import Datum


def predict():
    while True:
        client = jubatus.Classifier(host, port, name)
        words = raw_input().decode("utf-8")
        datum = Datum({'serif': words})
        res = client.classify([datum])
        sorted_res = sorted(res[0], key=lambda x: -x.score)
        for result in sorted_res:
            print("label:{0} score:{1}".format(result.label, result.score))


if __name__ == '__main__':
    predict()
```

'serif'という特徴名をkeyに、raw_input()で取得した、コンソールから入力したテキストを特徴量としてvalueにし、Datum形式にします。
client.classifyで分類処理を行います。結果はリストのリストとして返るので、sortedとlambda式でscore降順にソートし、表示しています。


## 内部で何が行われているのか
中身を知らずに利用することもできるのですが、一応、最低限の説明を試みます。

### 学習時
あらかじめ人力で、870(290*3)個の台詞(テキスト)とその正解ラベルとなるアイドル名を用意しました。これをCSV形式にしてtrain.pyで学習します。テキストはMeCabによって形態素単位に分かち書きされ、それぞれの形態素が正解ラベルの「特徴」として入力、学習されます。[^2]

[^2]: 今回はConfidence Weighted Learning(CW)というアルゴリズムを使用しています。詳細はJubatus - [アルゴリズム](http://jubat.us/ja/method.html)

学習の結果、形態素ごとに「アイドルらしさ」に相当する「重み」が設定されます(大まかには、形態素の数×正解ラベルの数だけ実数値が設定されます)。

今回は学習済みのモデルファイル(model/sample.jubatus)をjubaclassifierの起動時に指定し、ロードして利用しています。

### 分類時
分類時も、入力されたテキストが形態素分かち書きされます。分割された、各形態素の「重み」を合計したものがスコアになります。

通常、機械学習でテキスト分類を行う場合は頻度が多すぎて意味のない文字や単語を「ストップワード」として事前に除外する処理を入れることが多いですが、今回は台詞を分類するので助詞などの細かなニュアンスが必要なことと、Jubatusはデフォルトの設定でもわりとよろしくやってくれるので、そのまま入力しています(タスクによります)。

### 形態素分かち書きの例

- 入力: 妄想しすぎました…頭を休めないと…
- 出力: 妄想 し すぎ まし た … 頭 を 休め ない と …

この場合、妄想, し, すぎ, まし, た, …, 頭, を, 休め, ない, と, … の12個の形態素が特徴になります。


# 自分で学習データを用意する場合
以下のようなCSV形式でデータを用意すれば、リポジトリに用意してあるtrain.pyを使って学習できます。

~~~
Name, Serif
ラベルa,テキスト1
ラベルa,テキスト2
ラベルb,テキスト1
ラベルb,テキスト2
...以下同様
~~~
~~~
# 上で起動したjubaclassifierを終了
pkill jubaclassifier

jubaclassifier -f serif.json &
python train.py (上記ファイルを指定)
~~~

# 次回
これで機械学習サービスのコアとなる学習と分類の機能が出来上がりました。次回は、分類機能をWebサーバに組み込んで外部からWebAPIとして利用できるようにします。


# 最後に(重要)
もしこの記事を気に入っていただけたら、あなたが「いい」と思った分だけ、[アイドルマスターシンデレラガールズ](http://www.mbga.jp/_game_intro?game_id=12008305)で2018年4月頃に開催されると思われる「第7回シンデレラガール総選挙」で喜多日菜子に投票してください。