NLTK に Sentiment Analysis がやってきた

NLTK 3.1でSentiment analysis関連が追加されたので試してみました。

\* 3.1時点での話だから今後古くなるかも...†

## NLTKって？

[Natural Language Tool Kit](http://www.nltk.org/) という自然言語処理・テキストマイニングの学習や実験に向いてるPython用ライブラリのこと。
品詞タグ付けや構文解析、情報抽出、意味解析などが簡単にできるようになっていて、[オライリーから出ている入門自然言語処理](http://www.amazon.co.jp/gp/product/4873114705/ref=as_li_qf_sp_asin_tl?ie=UTF8&camp=247&creative=1211&creativeASIN=4873114705&linkCode=as2&tag=yukinoi-22)の題材にもなってます。

## Sentiment analysisって？

ざっくりいうと文が positive/negative (快/不快あるいは肯定的/否定的) であるかを判定すること。
日本語だと評判分析と呼ばれてたり。

あくまでも2極だけをみるので喜怒哀楽・恥ずかしい・怖いみたいな違いはわからないけど、そこまで細かく知る必要がないときには使えます。

## nltk.sentiment の中を覗いてみよう

### nltk.sentiment.sentiment_analyzer

[ソース冒頭のコメント](https://github.com/nltk/nltk/blob/3.1/nltk/sentiment/sentiment_analyzer.py#L12) に `especially for teaching and demonstrative
purposes` と書いてある通り、実用性よりも分かりやすさを重視した作りになってる。

#### 使い方の一例
```python
from nltk.classify import NaiveBayesClassifier
from nltk.sentiment import SentimentAnalyzer

# インスタンス生成
sentim_analyzer = SentimentAnalyzer()
# 特徴量抽出関数を登録
# (特徴量抽出関数へのパラメータは後ろにキーワードつけると渡せる)
sentim_analyzer.add_feat_extractor(特徴量抽出関数, パラメ〜タ〜=値)
# 対象データセットに先程登録した特徴量抽出関数を適用
training_set = sentim_analyzer.apply_features(学習用テストデ〜タ)
# 好きな学習アルゴリズムをtrainerに入れる
trainer = NaiveBayesClassifier.train
# 学習させる
sentim_analyzer.train(trainer, training_set)

# 判定 (apply_featuresはしなくてよい)
判定したいものたち = [判定したい文1, 判定したい文2, ...]
predicts = sentim_analyzer.classify(判定したいものたち)
```

特徴量抽出関数は`nltk.sentiment.util`に入ってるやつでも自作のでもなんでもOK.

scikit-learnのクラスで学習させたいときは `SklearnClassifier` で包む必要あり。

```python
from nltk.classify.scikitlearn import SklearnClassifier
from sklearn.svm import LinearSVC

trainer = SklearnClassifier(LinearSVC()).train
```

### nltk.sentiment.util

特徴量抽出の補助関数とデモ用のコードが多いです。

#### `extract_unigram_feats`(document, unigrams, handle_negation=False) [[source](https://github.com/nltk/nltk/blob/3.1/nltk/sentiment/util.py#L95)]

特徴量抽出に使うやつ。 
`document` 内に `unigrams` の語があるかないかを返す。

```python
>>> words = ['ice', 'police', 'riot']
>>> document = 'ice is melting due to global warming'.split()
>>> sorted(extract_unigram_feats(document, words).items())
[('contains(ice)', True), ('contains(police)', False), ('contains(riot)', False)]
```

#### `extract_bigram_feats`(document, bigrams) [[source](https://github.com/nltk/nltk/blob/3.1/nltk/sentiment/util.py#L119)]

特徴量抽出に使うやつ。 
↑の2-gram版で、`document` 内に `bigrams` のものがあるかないかを返す。

#### `mark_negation`(document, double_neg_flip=False, shallow=False) [[source](https://github.com/nltk/nltk/blob/3.1/nltk/sentiment/util.py#L145)]

特徴量抽出に使うやつ。 
否定がかかった語の末尾に '\_NEG' を付ける。

```py
>>> sent = "I didn't like this movie . It was bad .".split()
>>> mark_negation(sent)
['I', "didn't", 'like_NEG', 'this_NEG', 'movie_NEG', '.', 'It', 'was', 'bad', '.']
```

### nltk.sentiment.vader

VADERというルールベースによるsentiment analysisを実装したもの。

こっちは学習させる必要がないので、わずか3行で極性値を出せる

```python
>>> from nltk.sentiment.vader import SentimentIntensityAnalyzer
>>> vader_analyzer = SentimentIntensityAnalyzer()
>>> vader_analyzer.polarity_scores('I am happy')
FileNotFoundError: [Errno 2] No such file or directory: '/work/venv/py3/lib/python3.5/site-packages/nltk/sentiment/vader_lexicon.txt'
```

...はずなんだけどモデルのファイルがなかった...😨

とりあえずGitHubからダウンロー 
ド

```bash
$ curl https://raw.githubusercontent.com/nltk/nltk/develop/nltk/sentiment/vader_lexicon.txt > /work/venv/py3/lib/python3.5/site-packages/nltk/sentiment/vader_lexicon.txt
```

もっかいチャレンジ！

```python
>>> vader_analyzer.polarity_scores('I am happy')
{'compound': 0.5719, 'neg': 0.0, 'neu': 0.213, 'pos': 0.787}
```

ゎーぃ

#### ところでVADERって？

はてなブログの方に説明を書きました。気になる方はどうぞ！
http://yukinoi.hatenablog.com/entry/2018/03/09/020923

## おわりに

というわけでNLTK3.1で追加されたSentiment analysisについて紹介しました。
ガチで使うには物足りないけど、ちょこっと使ってみる程度にはいいかもと思いました。

過去に日本語を対象にした研究をしてたら途中で海外の方も参加することになって
急いでそこで使われてるsentiment analysis的なものを英語化したりして大変だった記憶があるのですが、
そのときにこれがあれば楽だったなーって悔しいです笑