機械学習を用いて、アニメの二次小説（SS）の原作を推定する


## tl;dr

Task: SSの本文から原作（10作品）を推定（教師あり学習。Classification）
Feature Engineering: 本文内の単語 + TF-IDF
Classifier: SVM, Logistic Regression, Random Forest
Accuracy: 98.5%
[Jupyter Notebook（GitHub上）](https://github.com/TakayukiSakai/ss_categorize/blob/189562dbd9798ba7592fbf1478714ce4d8430afc/tfidf.ipynb)

## 背景

最近RailsやNode.jsを書くサーバエンジニアから、広告配信を最適化する機械学習エンジニアへと転身しつつありまして、日々Courseraのビデオを見たり論文を読んだりしています。
サーバエンジニア時代も当時のスキルを活かすために趣味でアプリを作ったりしていたので、今回は最近身につけた機械学習スキルを使って、趣味レベルで少し遊んでみることにしました。

といっても、すでにやり尽くされているMNIST（手書き文字の数字）のようなデータで機械学習をやってもあまり面白くありません。
そこで、アニメの二次小説（以下SSと呼ぶ。ショートストーリーの略語。）関係のアプリを昔作ったときに作成したデータを使って、SSの本文から原作を推定するというタスクをやってみることにしました。

## この記事を理解するのに必要な知識

- MeCabによる形態素解析
- scikit-learnでirisデータの分類ができる

## 環境

- Ubuntu14.04
- Python3.5
- Jupyter Notebook
- MeCab 0.996 + mecab-ipadic-NEologd

機械学習モデルの作成・評価は全てJupyter Notebook上でやってGitHubにも上げておいたので、上級者の方は直接こちらを見た方が分かりやすいかもしれません。
[**tfidf.ipynb**](https://github.com/TakayukiSakai/ss_categorize/blob/189562dbd9798ba7592fbf1478714ce4d8430afc/tfidf.ipynb)

## 対象データ

- **article.csv**
	- category_id: 原作を表す一意なID
	- category_name: 原作名
	- html: SSの本文。

![Screen Shot 2016-06-19 at 17.11.55.png](https://qiita-image-store.s3.amazonaws.com/0/43701/4268cae7-c8bd-b47f-c7da-e080a9af0883.png "Screen Shot 2016-06-19 at 17.11.55.png")

## タスク

上のデータには「男女」のような原作のないオリジナル小説や、マイナーなためカテゴリIDの振られていないものも混じっています。
今回はタスクを単純にするためオリジナル作品は省き、かつ十分なサンプルがある以下の10作を対象としました。

- モバマス
- 艦これ
- アイドルマスター
- 俺ガイル
- ラブライブ！
- シュタインズ・ゲート
- 咲
- とある魔術の禁書目録
- ゆるゆり
- ガールズ＆パンツァー

（データ内にサンプル数が多かった順）

本記事における目標は、上記10作のいずれかのSSの「本文」を入力として、正しい「原作ID」を出力する予測モデルを作成することです。

## どのような情報を使って推定するか

（私の経験的に）ほぼ全てのSSは、登場するキャラクターの名前だけで推定することができます。
というのもSSは以下のような台本形式のものが主流で、キャラの名前が多く含まれているからです。

```
提督「い、いきなり何するんですか」（敬語）

大淀「すみません、つい」ｼﾗｰ

提督「技名叫んだよね？　こっちは浮き上がるほどの打撃だったよ！？」

大淀「ふぅ･･･」
```

しかし、実はテキストの中からSSに登場するようなキャラの名前だけを抽出するのは簡単ではありません。これには以下のような理由があります。

- SSのキャラの数は膨大
	- すべての名前を抽出できる独自辞書を作るのは困難
- SSのキャラの名前は現実世界では一般的でないものが多い
	- MeCabなどの形態素解析器では名前として認識してくれない
	- 名前によっては単語としてすら認識されない（穂乃果、加蓮、紅莉栖...）
- 台本形式でないSSも一定割合存在する
	- 「」の前の単語だけ抜き出してもうまくいかない
- 名前以外にも重要な単語は存在する
	- 地名、専門用語など（学園都市、戦車、アイドル、艦娘...）

そこで本記事では、文章内の単語の重要度を示す指標であるTF-IDF値を用いて特徴的な単語を抽出することとします。

## TF-IDFを用いて特徴単語を抽出

TF-IDFの細かい説明は[他の記事](http://tech-blog.fancs.com/entry/site-classification)に譲りますが、大まかに言うと **ある作品のSSでよく出てくるけど、ほかの作品のSSではあまり出てこないと、その単語は重要** という感じです。

この値を用いて、以下の手順で特徴単語を抽出します。
また、前処理でhtmlタグは取り除いてあります。

1. それぞれのSS記事からMecabを用いて一般名詞と固有名詞のみを抽出し、配列として保持。
2. 同じ原作の記事の単語配列を結合。（この時点で非常に長い単語配列が10個できる）
3. 原作ごとにTF-IDFが閾値より高い単語を抽出する

実際に抽出された単語は以下のようになりました。

|原作|TF-IDF Top3|
|---|---|
|モバマス|モバ、幸子、菜々|
|艦これ|提督、加賀、瑞鶴|
|アイドルマスター|伊織、春香、美希|
|俺ガイル|八幡、由比ヶ浜、雪ノ下|
|ラブライブ！|花陽、絵里、果|
|シュタインズゲート|岡部、栖、郁|
|咲|京太郎、竜華、憧|
|とある魔術の禁書目録|ミサカ、美琴、上条|
|ゆるゆり|櫻子、京子、あかり|
|ガールズ&パンツァー|エリカ、優花里、沙織|

おぉ！自前でルールを一切記述しなくても、かなり良い精度で単語が抽出できているようです。
ただ「紅莉栖」「穂乃果」などの、MeCabが単語と認識できない名前の断片が紛れてしまっているのは気になります。

TF-IDFの閾値がハイパーパラメータになってますが、今回のデータの場合は0.001程度の時に過不足なく単語が抽出できているようだったので、それを採用します。

また、この時に抽出された単語の総数は484でした。

## SS記事ごとに特徴量ベクトルを作成

484単語であれば、それぞれの単語が出現した回数を表現するベクトルを特徴量ベクトルとしても計算量的に問題なさそうです。

以下に、特徴単語を5種類のみとした場合の例を示します。

**特徴単語辞書**
["モバ", "ミサカ", "上条", "エリカ", "沙織"]

**単語**
記事1: ["ミサカ", "上条", "ミサカ"]
記事2: ["エリカ", "沙織", "沙織", "沙織"]

**特徴量ベクトル**
記事1: [0, 2, 1, 0, 0]
記事2: [0, 0, 0, 1, 3]

**特徴量ベクトル（n2ノルムによる正規化後）**
記事1: [0, 0.89, 0.45, 0,    0]
記事2: [0, 0,    0,    0.32, 0.95]

実際には単語の出現回数をそのまま使うのではなく、log(単語の出現回数 + 1)を使いました。
これは、特定の単語があまりに多く出現した時にその単語の影響を受けすぎないようにするためです。

さて、これで記事ごとに長さ484のベクトルが生成できました。分類器にかけてみましょう。

## 分類

特徴量が484、データ数が10000程度なので、scikit-learnの関数でも問題なさそうです。
ただ、解析的に解くソルバを使っているモデルだとさすがに終わらないので、反復法を用いているモデルにしました。

以下がクロスバリデーションをした結果です。

|モデル|正答率|
|---|---|
|SVM|0.98536 (+/- 0.00313)|
|SVM+ガウシアンカーネル|0.96746 (+/- 0.00612)|
|Logistic Regression|0.98509 (+/- 0.00215)|
|Random Forest|0.98360 (+/- 0.00454)|

98.5%！かなり良い精度が出ました！
今回の例ではSVMによる分類が最も精度が高いようです。

## 考察

98.5%はかなり良い数字ではあるのですが、正直人の方が精度が高いです。
そこで分類に失敗したSSを見て、理由を検証したところ以下の種類に大別されました。

1. 正解ラベルの方が間違っている
	- 人だって間違える 
2. 正解が曖昧
	- 複数作品のクロスもの
3. マイナーなキャラがメインだった
4. 名前が似ているキャラクター
	- モバマスとラブライブ！に出てくる「凛」など
5. 形態素解析できない名前のキャラ
	- 特にひどいのが「にこ（ラブライブ！）」。副詞と判定される。

1は論外。2は人間でもできないので機械にもできません。

3は特徴量抽出に問題があったために起きた問題です。TF-IDFでは、その作品で滅多に登場しないキャラクターの名前を拾うことはできません。

4は分類器の問題があるためです。「凛 + 卯月」 -> モバマス。「凛 + 花陽」 -> ラブライブ！のようにしてくれればいいのですが、SVMなどの線形分類器では特徴量の組み合わせ情報は考慮されてないので、それはできません。

5は前処理（MeCab）の問題です。これはなかなか難しいです。MeCabの辞書に単語を追加していくしかありません。


## まとめ

独自のルールや辞書などは一切作成せずに、かなりいい精度で原作を推定することができました。
特徴量抽出部分や、分類部分でより良い案があれば是非コメントに書いてもらえればと思います！

また今回用いた元データは公開していないのですが、需要があれば検討してみようと思います。