3. Pythonによる自然言語処理　5-1. 感情分析の考え方［AFINN-111］

* ある文書を一括りにして性格づけする手法があります。文書を構成している単語に付与された属性をもとに、好き・嫌い、肯定的・否定的などの判定をするものです。
* それには元となる辞書が必要になりますが、**英単語の感情値辞書 [AFINN-111](https://www.npmjs.com/package/afinn-111)** には収録数 2,477語のそれぞれに感情値として-4から4までの整数がふられています。

#⑴ AFINN-111 の取得
* [AFINN-111](https://raw.githubusercontent.com/fnielsen/afinn/master/afinn/data/AFINN-111.txt)を読み込んでデータフレーム `afn` とします。

```python
import pandas as pd

afn = pd.read_csv(r'https://raw.githubusercontent.com/fnielsen/afinn/master/afinn/data/AFINN-111.txt', 
                  names=['word_score'])
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502109/023c7adb-62b7-85e4-b14b-d57940913d3e.png)

#⑵ AFINN-111 を辞書型に変換
* 「\t」を区切り文字として単語 `word` と感情値 `score` の列に分けます。

```python
afn["split"] = afn["word_score"].str.split("\t")
afn["word"] = afn["split"].str.get(0)
afn["score"] = afn["split"].str.get(1)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502109/469a2190-d6eb-f735-8e9a-c7636c80c315.png)

* `score` の値は文字列なので数値型 (INTEGER) に変換した列 `score_int` を生成します。

```python
import numpy as np
afn_int = afn.assign(score_int = afn['score'].astype(np.int64))
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502109/899f987a-44a6-c775-cf0a-68d4e005716c.png)

* データフレームから対象となる2カラム `word` と `score_int` を抽出し、 `set_index()` にキーを `word` と指定して `to_dict()` で辞書に変換します。

```python
afn_int = afn_int[["word", "score_int"]]

sentiment_dict = afn_int.set_index('word')['score_int'].to_dict()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502109/11b5059d-a1e7-3e34-1b0e-39115541c424.png)

#⑶ AFINN-111 による感情値の計算
* 以下は、宮沢賢治『セロ弾きのゴーシュ』の冒頭3行です。

`ゴーシュは町の活動写真館でセロを弾く係りでした。けれどもあんまり上手でないという評判でした。上手でないどころではなく実は仲間の楽手のなかではいちばん下手でしたから、いつでも楽長にいじめられるのでした。`

* 英語に訳して入力データ `string` とします。
    * Gauche was in charge of playing the cello at the town's activity photo studio.
    * He had a reputation for not being a very good player.
    * Not only was he not a good player, but he was actually the worst player among his fellow musicians, so he was always bullied by the head musician.

```python
string = "Gauche was in charge of playing the cello at the town's activity photo studio. He had a reputation for not being a very good player. Not only was he not a good player, but he was actually the worst player among his fellow musicians, so he was always bullied by the head musician."
```
* Pythonの自然言語処理パッケージ NLTK とそのサブモジュール [nltk.tokenize](https://www.nltk.org/api/nltk.tokenize.html) 一式をインポートし、トークナイザ [punkt](https://www.nltk.org/_modules/nltk/tokenize/punkt.html) をダウンロードします。
* `string` から `sent_tokenize()` で文章ごとに取り出して、以下の処理をくり返します。 `lower()` ですべて小文字にして `word_tokenize()` で単語に分割し、単語単位で `sentiment_dict` から感情値を取得した合計を `score` とします。

```python
import nltk
from nltk.tokenize import *
nltk.download('punkt')

for s in sent_tokenize(string):
    words = word_tokenize(s.lower())
    score = sum(sentiment_dict.get(word, 0) for word in words)
    print(score)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502109/307d2efc-7823-21b4-da7a-f6bb7e369615.png)

* **得点が正の数なら肯定的、逆に負の数なら否定的**な性格をもつ文章であることを表します。
    * 「ゴーシュは町の活動写真館でセロを弾く係りでした」は 0 点で客観的事実です。
    * 「けれどもあんまり上手でないという評判でした」は 3 点ですが、否定的な意味を遠回しに言っている感じです。
    * 「上手でないどころではなく実は仲間の楽手のなかではいちばん下手でしたから、いつでも楽長にいじめられるのでした」は -2 点で、こちらは明らかに否定的な意味内容になっています。
* そこで、**正の感情値の合計と、負の感情値の合計を別々に算出**してみます。

```python
for s in sent_tokenize(string):
    words = word_tokenize(s.lower())
    positive = 0
    negative = 0
    for word in words:
        score = sentiment_dict.get(word, 0)
        if score > 0:
            positive += score
        if score < 0:
            negative += score
    print(s)
    print("positive:", positive)
    print("negative:", negative)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502109/abdc99e9-bcee-f723-1363-8c4dacd9e5c7.png)

* 2番目の文章の「あんまり上手でない」を「下手だ」と言い換えたらどうなるでしょうか。

```python
string_2 = "Gauche was in charge of playing the cello at the town's activity photo studio. But he had a reputation for being terrible at it. Not only was he not a good player, but he was actually the worst player among his fellow musicians, so he was always bullied by the head musician."

for s in sent_tokenize(string_2):
    words = word_tokenize(s.lower())
    positive = 0
    negative = 0
    for word in words:
        score = sentiment_dict.get(word, 0)
        if score > 0:
            positive += score
        if score < 0:
            negative += score
    print(s)
    print("positive:", positive)
    print("negative:", negative)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502109/c36dc37b-f04e-bb41-4190-2eaf739edf23.png)

* 日本語と英語の違いはありますが、言い回しによって結果はかなり違ったものになることがわかります。

#⑷ 問題点と注意点
* 以上が感情分析の原理になりますが、次のようなさまざまな問題点が挙げられます。
    * 辞書にない単語はカウントされない。
    * 先の「**not** being a very **good** player」のように否定修飾する語がある場合でも正の値としてカウントされる。
    * 同じく「not being a **very good** player」のように増幅修飾する語がある場合でも「good」しかカウントされない。
    * 文章が長いほど感情値の和は大きくなる。
    * 文章の前半と後半で感情が異なる場合もあり、一括りに性格づけすることに無理がある。
* ちなみに、日本語の「可愛い」に対応する英語はいくつかありますが、AFINN-111では「pretty : 1」「cute : 2」「lovely : 3」などとなっています。つまり感情値は**順序尺度**に相当すると考えられますが、連続値（-4, -3, -2, -1, 0, 1, 2, 3, 4 ）すなわち**比例尺度**として扱っていることに注意が必要です。