2019年4月17日水曜日

Pythonで童話のネガティブ・ポジティブ感情を判定する

テキストの感情を分析したいときに、単語ごとのネガティブ・ポジティブ感情の度合いを数値化した表を使う方法がある。そういった表のひとつが単語感情極性対応表で、同ページでは感情極性について以下のように説明されている。

感情極性とは、その語が一般的に良い印象を持つか(positive) 悪い印象を持つか(negative)を表した二値属性です。 例えば、「良い」、「美しい」などはpositiveな極性、 「悪い」、「汚い」などはnegativeな極性を持ちます。

文章を形態素解析して、単語ごとの極性値の平均を出したらその文章全体のネガティブ・ポジティブ感情の度合いがわかるのでは? という考え。実際には、対応表ではすべての単語をカバーできないし、否定形の文もあるので正確な判定はできないかもしれないが、とりあえずやってみる。感情を判定するのは『赤ずきんちゃん』『白雪姫』『ヘンゼルとグレーテル』『ラプンツェル』の4作のグリム童話。


環境


Windows10のWSL(Ubuntu 18.04)。



単語感情極性対応表の確認


まずは単語感情極性対応表から日本語版をダウンロード。ダウンロードしたファイルpn_ja.dicの中身を確認してみる。1行に見出し語、読み、品詞、感情極性実数値が「:」区切りで並んでいる。


中身をもう少し確認してみる。以下のスクリプトを、対応表と同じディレクトリに置いて実行する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import codecs
import pandas as pd
 
def import_pndic():
    #単語感情極性対応表の読み込み
 
    with codecs.open('./pn_ja.dic', 'r', 'shift_jis') as f:
        df = pd.read_csv(f, sep=':', header=None, names=['word', 'kana', 'category', 'pn_value'])
 
    print('単語数:', len(df.index))
    print('品詞一覧:', df['category'].unique())
 
    print('ポジティブ語:', len(df[df['pn_value']>0].index))
    print('中立語:', len(df[df['pn_value']==0].index))
    print('ネガティブ語:', len(df[df['pn_value']<0].index))
 
    # 読みと品詞で重複している語
    print()
    print('読みと品詞で重複している単語数:', len(df[df.duplicated(keep=False, subset=['word', 'category'])].index))
    print(df[df.duplicated(keep=False, subset=['word', 'category'])].sort_values(by=['word']))
 
if __name__ == '__main__':
    import_pndic()

以下は結果の通りで、対応表にある品詞は「動詞」「形容詞」「名詞」「副詞」「助動詞」。ネガティブよりの単語がかなり多く、かつ見出し語と品詞でのダブりがけっこうある。


こういう対応表であることをふまえた上で進める。


ネガティブ・ポジティブ感情を判定する童話


青空文庫にある4作のグリム童話『赤ずきんちゃん』『白雪姫』『ヘンゼルとグレーテル』『ラプンツェル』を対象に極性値を求める。それぞれのテキストデータは、Pythonで青空文庫の作品テキストからルビなどを取り除くの方法でルビなどを取り除いたもの。それぞれ、akazukin.txt、shirayukihime.txt、hansel_gretel.txt、rapunzel.txtというファイル名で保存。


童話ごとに感情極性値を求める


形態素解析した語の原型および品詞をもとに、対応表から対象の極性値を求める。対応表に極性値がない場合は極性値0とする。対応表で読みと品詞で重複する語については、最初の単語を採用することにした。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
import codecs
from collections import OrderedDict
import MeCab
import pandas as pd
import numpy as np
import pprint
 
# 対象とする品詞
MAIN_CAT_LIST = ['名詞', '動詞', '形容詞', '副詞', '助動詞']
# 除外する詳細分類1
SUB_CAT_LIST = ['数', '代名詞', '接尾', '非自立', '特殊', '固有名詞', '接続詞的', '動詞非自立的']
# ストップワード
# 辞書に定義されていない語はアスタリスクに置き換えらるため
STOP_LIST = ['*']
 
def match_word(row, pn_df):
    # 原型、品詞が一致する極性値を取得
    # 対応表に一致する単語がない場合は極性値0とする
 
    # 原型、品詞が一致する極性値を取得
    res = pn_df[(pn_df['word'] == row['base']) & (pn_df['category'] == row['category'])]['pn_value']
 
    if len(res.index) == 1:
        return res.values[0]
    else:
        # 極性値がない語は0
        return 0.0
 
def get_pn_value(x_df, pn_df):
    # 童話ごとの極性値を求める
    pn_value_df = x_df.apply(lambda row: match_word(row, pn_df), axis=1)
    pn_list = pn_value_df.values
 
    return np.sum(pn_list) / pn_list.size
 
def to_word_dic(text):
    # テキストを形態素解析して、対象品詞の原型と品詞をdictionary形式で返す
 
    m = MeCab.Tagger('-Ochasen')
    m.parse('')
    node = m.parseToNode(text)
 
    base_list = []
    cat_list = []
 
    while node:
        feature_split = node.feature.split(',')
 
        base_form = feature_split[6]
        main_cat = feature_split[0]
        sub_cat = feature_split[1]
 
        if main_cat in MAIN_CAT_LIST and sub_cat not in SUB_CAT_LIST and base_form not in STOP_LIST:
            base_list.append(base_form)
            cat_list.append(main_cat)
 
        node = node.next
 
    return {'base': base_list, 'category': cat_list}
 
def import_pndic():
    #単語感情極性対応表の読み込み
    with codecs.open('./pn_ja.dic', 'r', 'shift_jis') as f:
        df = pd.read_csv(f, sep=':', header=None, names=['word', 'kana', 'category', 'pn_value'])
 
    # 重複するものは、はじめのものを使用するように重複を省く
    df.drop_duplicates(subset=['word', 'category'], keep='first', inplace=True)
 
    return df
 
def main():
    pn_table_df = import_pndic()
 
    story_dic = {'赤ずきんちゃん': 'akazukin.txt', '白雪姫': 'shirayukihime.txt', 'ヘンゼルとグレーテル': 'hansel_gretel.txt', 'ラプンツェル': 'rapunzel.txt'}
 
    pn_dic = OrderedDict()
 
    for title, filename in story_dic.items():
        with codecs.open(filename, 'r', 'utf-8') as f:
            text = f.read()
 
        df = pd.DataFrame(to_word_dic(text))
        pn_value = get_pn_value(df, pn_table_df)
        pn_dic.update({title: pn_value})
 
    pprint.pprint(pn_dic)
 
if __name__ == '__main__':
    main()

結果は以下の通り。


極性値を出したあとで気づいたのだが、童話はひらがなが多いので、対応表との対応がとれない単語が多い。そういう点ではこの分析に不向きな対象だったかもしれない。

とりあえず結果をみると、どの童話もハッピーエンドなのだが、『ラプンツェル』が他の3童話に比べてネガティブ感情が高い結果となった。4童話ともハッピーな箇所は物語の最後だけなので、長い話ほどネガティブな箇所が多いとも考えられる。『赤ずきんちゃん』のネガティブ感情が低いのはそのためかもしれないが、同じくらいの長さの『ラプンツェル』が最もネガティブなのは説明できない。極性値がない単語が多いし、否定形も考慮していないし、なかなか正確な判定は難しいか?


0 件のコメント:

コメントを投稿