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


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

⑴ AFINN-111 の取得

  • AFINN-111を読み込んでデータフレーム afn とします。
import pandas as pd

afn = pd.read_csv(r'https://raw.githubusercontent.com/fnielsen/afinn/master/afinn/data/AFINN-111.txt', 
                  names=['word_score'])

⑵ AFINN-111 を辞書型に変換

  • 「\t」を区切り文字として単語 word と感情値 score の列に分けます。
afn["split"] = afn["word_score"].str.split("\t")
afn["word"] = afn["split"].str.get(0)
afn["score"] = afn["split"].str.get(1)

  • score の値は文字列なので数値型 (INTEGER) に変換した列 score_int を生成します。
import numpy as np
afn_int = afn.assign(score_int = afn['score'].astype(np.int64))

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

sentiment_dict = afn_int.set_index('word')['score_int'].to_dict()

⑶ 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.
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 一式をインポートし、トークナイザ punkt をダウンロードします。
  • string から sent_tokenize() で文章ごとに取り出して、以下の処理をくり返します。 lower() ですべて小文字にして word_tokenize() で単語に分割し、単語単位で sentiment_dict から感情値を取得した合計を score とします。
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)

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

  • 2番目の文章の「あんまり上手でない」を「下手だ」と言い換えたらどうなるでしょうか。
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)

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

⑷ 問題点と注意点

  • 以上が感情分析の原理になりますが、次のようなさまざまな問題点が挙げられます。
    • 辞書にない単語はカウントされない。
    • 先の「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 )すなわち比例尺度として扱っていることに注意が必要です。