テキストデータで特徴量選択をする

# この記事で紹介すること

* [特徴量選択するパッケージ作ったので紹介](https://pypi.python.org/pypi/DocumentFeatureSelection)
* テキストデータから特徴量選択することの大切さ
* 代表的な特徴量選択の手法
* かんたんな例


# てかなんで、特徴量選択とか必要なの？

これを読んでいる方は、すでにテキストデータから特徴量（以下、素性）を抽出して何かしろの機械学習を試したことがある方だと思います。たとえば、文書分類とかですね。

Qiitaをサクッと探してみてもいくつか「やってみた系」の記事が見つかります。
[モーニング娘。のブログを自動分類してみました。](http://qiita.com/hello_analytics/items/75004a310ab9c836bc53)
[Rで自然言語処理。ナイーブベイズで文書分類を試みる](http://qiita.com/nezuq/items/b5cee4e38aaca1e4fc54)

文書分類では、単語を素性に利用して、行列データを作成するのがベーシックな手法です。頻度行列と呼ばれる行列ですね。


さてさて、ここで１つ疑問が出てきます。__分類に関係ないような単語も多く含まれているけど、それって大丈夫なの？__

よい疑問です。大丈夫ではないです。
分類に関係ないような素性が多く含まれていると、それはノイズとして働いてしまいます。
ノイズが分類性能の向上を阻むのですねー。こまった。こまった。

じゃあそこで、__「関係する素性だけを残せばいーじゃん」__というアイディアが出てきます。
そう、これが、__特徴量選択__というやつなのです。

特徴量選択には２つのメリットがあります。

1. 機械学習アルゴリズムにぶちこむ前に、特徴量選択をきちっとして、モデルの性能向上（ランダムフォレストみたいに、アルゴリズムそのものに特徴量選択が内包されているケースもありますが、それはまた別の話）
2. データの観察を容易にする

# 特徴量選択を簡単にできるパッケージつくったよー

特徴量選択を真面目にやっていくと結構めんどいです。
そこで、よく使うであろう[特徴量選択の手法をパッケージ化](https://pypi.python.org/pypi/DocumentFeatureSelection/0.9)しました。

Python3.xで動きます。Python2.xはそのうち対応します。

## サポートしてる手法

* [TF-IDF](http://qiita.com/ynakayama/items/300460aa718363abc85c): ベーシックな手法です。実は[Scikit-learnのクラス](http://scikit-learn.org/stable/modules/generated/sklearn.feature_extraction.text.TfidfTransformer.html)を呼んでいるだけです。
* PMI: 文書ラベルと素性の相関性を考慮する手法です。[参考の記事をどーぞ](http://aidiary.hatenablog.com/entry/20100619/1276950312)
* SOA: PMIの弱点を補った手法です。文書ラベルと素性の「非関連度」をマイナスで算出することができます。
* BNS: ２クラス分類にしか使えません。ただし、学習データのラベルに偏りがある場合には、すぐれた結果を出すことが知られています。[参考の記事をどーぞ](http://kensuke-mi.hatenablog.com/entry/2015/04/29/175350)

## パッケージの特徴

### （たぶん）早い

内部処理はすべてscipyの疎行列を利用しています。
また、分散処理できる部分はすべてマルチプロセス化しているため、それなりに早いです。

### 前処理をけっこーテキトーにやってくれる。

形態素分割した状態でdictにして、放り込んでもらえれば、疎行列の構築までやってくれます。

たとえば、入力のdictはこんな感じ

```py3

input_dict = {
    "label_a": [
        ["I", "aa", "aa", "aa", "aa", "aa"],
        ["bb", "aa", "aa", "aa", "aa", "aa"],
        ["I", "aa", "hero", "some", "ok", "aa"]
    ],
    "label_b": [
        ["bb", "bb", "bb"],
        ["bb", "bb", "bb"],
        ["hero", "ok", "bb"],
        ["hero", "cc", "bb"],
    ],
    "label_c": [
        ["cc", "cc", "cc"],
        ["cc", "cc", "bb"],
        ["xx", "xx", "cc"],
        ["aa", "xx", "cc"],
    ]
}
```


# ちょっと遊んでみる


せっかく作ったので、試してみることにします。
試してみたipythonノートは[Gist](https://gist.github.com/Kensuke-Mitsuzawa/f3b09ca59e89ee06cb4e9a1f8d084439)に置いておきました。

ipythonノートではscipyと[形態素解析ラッパーパッケージ](https://pypi.python.org/pypi/JapaneseTokenizer/0.9)と[特徴量選択パッケージ](https://pypi.python.org/pypi/DocumentFeatureSelection/0.9)を使います。

テキストは５つのジャンルを用意しました。
該当しそうなテキストをネットからひろってきて、コピペではっつけて作りました。（~~これぞ集合知~~）

５つのジャンルは

* 航空機に関するwiki記事
* アダルトビデオ業界に関するニュース記事
* コナンの映画に関するニュース記事
* イランの街に関するwiki記事
* テロん関するニュース記事

です。[^1]

PMIとSOAを試してみました。

結果から抜粋してみます。

### PMIの結果

スコアが高い順にこんな結果がみられました。

```
{'label': 'iranian_cities', 'score': 0.67106056632551592, 'word': '人口'},
{'label': 'conan_movies', 'score': 0.34710665998172219, 'word': '登場'},
 {'label': 'av_actress', 'score': 0.30496452198069324, 'word': 'AV女優'},
 {'label': 'av_actress', 'score': 0.26339266409673928, 'word': '出演'},
{'label': 'av_actress', 'score': 0.2313987055319647, 'word': '女性'},
```

「う、うん。そうだよね〜」という単語が並んでいます。

ラベルとの関連性がわかりやすい単語が高い重み付けされているので、特徴量選択という意味では成功でしょう。

データの観察、という意味では特に示唆はなさそうです。

逆にスコアが低い箇所はどうなっているでしょうか？

```
 {'label': 'av_actress', 'score': 5.7340738217327128e-06, 'word': '男'},
 {'label': 'conan_movies', 'score': 5.7340738217327128e-06, 'word': '3'},
 {'label': 'conan_movies', 'score': 5.7340738217327128e-06, 'word': '化'},
 {'label': 'conan_movies', 'score': 5.7340738217327128e-06, 'word': '表記'},
 {'label': 'terror', 'score': 5.7340738217327128e-06, 'word': '型'}
```

？という結果も混じっていますね。
文書の中で機能語的に利用された単語なのだと思われます。
数字の「３」が混じってしまっているのは、形態素解析のミスですね・・・（MecabのNeologd辞書を利用するとこういうことはよく起きます）

機能語的な単語を低いスコアに抑えた。という点で、うまくいっているように見えます。

### SOAの結果

若干、並びが変わりました。
SOAはPMIの式をベースにしているので、こういうことはよくあります（たぶん）

```
[{'label': 'conan_movies', 'score': 5.3625700793847084, 'word': '登場'},
 {'label': 'iranian_cities', 'score': 5.1604646721932461, 'word': '人口'},
 {'label': 'av_actress', 'score': 5.1395513523987937, 'word': 'AV女優'},
 {'label': 'av_actress', 'score': 4.8765169465650002, 'word': 'さ'},
 {'label': 'av_actress', 'score': 4.8765169465650002, 'word': 'ん'},
 {'label': 'av_actress', 'score': 4.8765169465650002, 'word': '女性'},
 {'label': 'terror', 'score': 4.8765169465650002, 'word': 'シリア'},
```


では、逆にスコアが低くなった箇所をみてみましょう。
SOAにおけるスコアが低い部分は、「ラベルとの非関連度」と解釈できます。

```
{'label': 'terror', 'score': -1.4454111483223628, 'word': '人口'},
 {'label': 'iranian_cities', 'score': -1.6468902498643583, 'word': 'ところ'},
 {'label': 'iranian_cities', 'score': -1.6468902498643583, 'word': 'もの'},
 {'label': 'iranian_cities', 'score': -1.6468902498643583, 'word': '中'},
 {'label': 'iranian_cities', 'score': -1.6468902498643583, 'word': '製造'},
 {'label': 'iranian_cities', 'score': -2.009460329249066, 'word': 'こと'},
 {'label': 'airplane', 'score': -3.3923174227787602, 'word': '人'}]
```

なんだか、いまいちパッとしませんね。

文書中での頻度を調べてみると、この単語は出現文書数が１回か２回しかないのです。
つまり、「ラベルとの関連は薄い」と言ってもよく、マイナスの値が大きくなったのは妥当と言えます。


# まとめ

この記事では、特徴量選択の話と、特徴量選択を簡単にできるパッケージの話をしました。

今回は、特徴量選択をした後の文書分類の性能までチェックしませんでした。

が、先行研究で十分に有効性が示されている手法です。
文書分類系のタスクの際には、ぜひ、ご利用ください。

`pip install DocumentFeatureSelection`でインストールできますので。

# 補足

パッケージのバージョン1.0から　入力データが柔軟に設計できるようになりました。

１例で、`(表層語, POS)`をbigramにして、特徴量設計したいときは、こんな感じのタプルの配列を与えることができます。ここでは、`(("he", "N"), ("is", "V"))`が１つの特徴量ですね。

```python
input_dict_tuple_feature = {
    "label_a": [
        [ (("he", "N"), ("is", "V")), (("very", "ADV"), ("good", "ADJ")), (("guy", "N"),) ],
        [ (("you", "N"), ("are", "V")), (("very", "ADV"), ("awesome", "ADJ")), (("guy", "N"),) ],
        [ (("i", "N"), ("am", "V")), (("very", "ADV"), ("good", "ADJ")), (("guy", "N"),) ]
    ],
    "label_b": [
        [ (("she", "N"), ("is", "V")), (("very", "ADV"), ("good", "ADJ")), (("girl", "N"),) ],
        [ (("you", "N"), ("are", "V")), (("very", "ADV"), ("awesome", "ADJ")), (("girl", "N"),) ],
        [ (("she", "N"), ("is", "V")), (("very", "ADV"), ("good", "ADJ")), (("guy", "N"),) ]
    ]
}
```

タプルを入力文の特徴量として与えることができるので、ユーザーが自由に特徴量の設計をできます。[^2]
例えば、こんなタスクに使えます、という例

- `(表層語, 何かタグ)`を特徴量にしたいとき
- 係り受けのエッジラベルを特徴量にしたいとき

- - -

[^1]: よく、「どうしてサンプルテキストにアダルトビデオとペルシア語とかイランが入っているんですか？」と聞かれます。それは、アダルトビデオは私が好きだからです。ペルシア語は、勉強していたので、愛着があるからです。
[^2]: 従来でも`表層語_タグ`をstr型にして、無理くりに特徴量抽出することもできます。が、それって、何かスマートじゃないじゃん？前処理と後処理が必要じゃん？と思ったので、この機能を入れました。