🐷

感情分析のやり方が7割わかるようになる記事(初心者向け)(ソースコードあり)(GiNZA)

2023/01/14に公開

こんにちにゃんです。
水色桜(みずいろさくら)です。
GiNZAに関する記事4本目です。
この記事では感情分析について書いていきます。
まず感情分析とは何かということから説明していきます。
感情分析は、AIがテキストや音声から人間の気持ちや意図を読み取ることを言います。
たとえば、「やったー。テストでいい点が取れた!」という文があったときに、プラスの感情もしくは喜びの感情を出力するような感じです。

感情分析の手法は大きく分けて2つあります。
1つ目はルールベースのアプローチです。
ルールベースのアプローチは前持って定義されたルールを用いて感情分析をする手法です。例えば、あらかじめ単語ごとに何の感情が含まれているかという「辞書」を作製しておいて、その辞書を用いて感情分析を行います。機械学習が不要のため、手軽に感情分析が行え、処理が軽いというメリットがありますが、「辞書」を用いて行う手法ゆえ文脈を考慮するのが苦手です。皮肉などがある場合もうまく分析を行うことができません。

2つ目は機械学習によるアプローチです。
機械学習による感情分析は文脈を考慮したり、皮肉などの直接的でない表現を分析することに長けています。しかし大量のデータが必要であり、学習には時間もかかるため、気軽には行えない手法です。しかし、現在は様々な機関からコーパスが配布されていて、データは入手しやすくなっているため、大分敷居は下がりました。

今回は1つ目のルールベースのアプローチを用いて感情分析を行っていきます。(機械学習は私のマシンだと重すぎるため)
感情分析を行う時に辞書(ルール)を用いますが、現在日本語で公表されている主な辞書は次の4つです。

1⃣東京工業大学 高村先生が公表している単語感情極性対応表
http://www.lr.pi.titech.ac.jp/~takamura/pndic_ja.html
各単語にー1~1の間の特定の極性値が振られている辞書。
2⃣東北大学 乾・岡崎研究室が公表している日本語評価極性辞書
http://www.cl.ecei.tohoku.ac.jp/Open_Resources-Japanese_Sentiment_Polarity_Dictionary.html
各単語をネガティブ・ニュートラル・ポジティブの3つに分類している。
3⃣長岡技術科学大学 言語商会 山本先生が公表している日本語感情表現辞書
https://www.jnlp.org/GengoHouse/snow/d18
48の感情のうち特定の感情が各単語に付加されている。
4⃣愛媛大学 梶原先生の公表しているWRIME: 主観と客観の感情分析データセット
https://github.com/ids-cv/wrime
三人の被験者が各単語に8つの感情の内何を感じたかが記されている。完全に主観という点がほかの辞書とは異なる。

一つ一つの辞書には長所と短所がそれぞれありますが、今回は1⃣の辞書を用います。(すべての辞書を使ってみた結果、一番妥当な結果が得られたと思われるため)

https://twitter.com/Mizuiro__sakura/status/1567042420256415745?s=20&t=-eFur9NoqPn8ijHMBQX65Q

このような感じで感情分析が行えます。
それでは、ソースコードを見てみましょう。

sent_anal.py
# 必要なライブラリをインポート
import spacy
import tkinter as tk 
import re
import os

# tkinterを用いてGUIを作成
root = tk.Tk() # ウィンドウを作成
root.title(u'東京工業大学高村先生の作成した単語感情極性対応表を用いたGiNZAによる感情分析プログラム')
root.geometry('670x450') # ウィンドウのサイズを設定
img=tk.PhotoImage(file='star_oran.png') # イメージを読み込み
cvs=tk.Canvas(width=670,height=400) # イメージを載せるキャンバスのサイズ
cvs.pack()
cvs.create_image(330,200,image=img) # イメージのサイズ設定
txt='分析したい文章を入力してください。感情スコア(0~100点)を表示します。'
msg=tk.Label(text=txt,fg='black',bg='green yellow') # テキスト(ラベル)を設定
msg.place(x=10,y=10) # テキスト(ラベル)を設置

path=os.getcwd() # パスを取得

nlp = spacy.load('ja_ginza_electra') # GiNZAのロード
t=1

def feel(): # ボタンを押したらこの関数を実行
    global t # ラベルの位置を決めるための変数
    s=textF.get() # 入力したテキストを取得
    msg=tk.Label(text=s,fg='black',bg='white') # テキスト(ラベル)を設定
    msg.place(x=10,y=10+t*30) # テキスト(ラベル)を設置
    t=t+1
    textF.delete(0,'end') # テキストボックスの文字を削除
    doc=nlp(s) # GiNZAによる入力したテキストの解析
    words=[] # 分割した単語を保持する配列
    score=0
    for sent in doc.sents: # 文章群の中から文章を一つずつ抽出
        for token in sent: # 文章の中から形態素を一つずつ抽出
            if token.pos_=='PROPN' or token.pos_=='NOUN' : # 名詞を抽出
                 words.append(token.lemma_) # 原形をリストに追加
    with open('toukou_pn.txt',encoding='ANSI') as f: # コーパスを読み込み
        text=f.read() # 文字を読み込み
        dic=re.split('\n|:',text) # 改行と:で分割
    for word in words: 
        for i in range(int(len(dic)/4)):
            if dic[4*i+2]=='名詞': # 属性が名詞の場合
                if dic[4*i]==word or dic[4*i+1]==word:
                    score=score+float(dic[4*i+3]) # 単語の極性を足し合わせる
    a=len(words) # 文章に含まれていた名詞の数を保持
    for sent in doc.sents: # 文章群の中から文章を一つずつ抽出
        for token in sent: # 文章の中から形態素を一つずつ抽出
            if token.pos_=='VERB' or token.pos_=='ADJ' or token.pos_=='ADV': # 用言を抽出
                 words.append(token.lemma_) # 原形をリストに追加
    with open('toukou_pn.txt',encoding='ANSI') as f: # コーパスを読み込み
        text=f.read() # 文字を読み込み
        dic=re.split('\n|:',text) # 改行と:で分割
    for word in words:
        for i in range(int(len(dic)/4)):
            if dic[4*i+2]=='動詞' or dic[4*i+2]=='形容詞' or dic[4*i+2]=='副詞': # 属性が動詞、形容詞、副詞の場合
                if dic[4*i]==word or dic[4*i+1]==word:
                    score=score+float(dic[4*i+3]) # 単語の極性を足し合わせる
    b=len(words) # 文章に含まれていた用言の数を保持

    c=(((score/(a+b))+1)*50) # 得られた極性の和を単語数で割る
    msg=tk.Label(text='文章中の感情スコアは'+str(c)+'点)',fg='black',bg='green yellow') # 感情スコアを表示
    msg.place(x=10,y=10+t*30)
    # 感情スコアに応じて音声を再生
    if c>75:
        os.system('ずんだもん喜.wav')
    elif c>=50 and c<=75:
        os.system('ずんだもんえーと.wav')
    elif c<50:
        os.system('ずんだもん落.wav')
    t=t+1
    
# ボタンとテキストボックスの定義
btn=tk.Button(root,text='送信',font=('utf-8_sig',10),bg='cyan',command=feel)
btn.pack(side='right')
textF=tk.Entry(root,font=('utf-8_sig',15),width=64)
textF.pack(side='right')
# 画面の保持
root.mainloop()

流れとしては、GiNZAで形態素解析をして単語に分解し、その単語を辞書に通すというイメージです。

div.py
for sent in doc.sents: # 文章群の中から文章を一つずつ抽出
        for token in sent: # 文章の中から形態素を一つずつ抽出
            if token.pos_=='PROPN' or token.pos_=='NOUN' : # 名詞を抽出
                 words.append(token.lemma_) # 原形をリストに追加

まずこのようにGiNZAで文章を単語に分け、原形をリスト化します。

sent_equ.py
with open('toukou_pn.txt',encoding='ANSI') as f: # コーパスを読み込み
        text=f.read() # 文字を読み込み
        dic=re.split('\n|:',text) # 改行と:で分割
    for word in words: 
        for i in range(int(len(dic)/4)):
            if dic[4*i+2]=='名詞': # 属性が名詞の場合
                if dic[4*i]==word or dic[4*i+1]==word:
                    score=score+float(dic[4*i+3]) # 単語の極性を足し合わせる

そのあと、辞書にその単語がないか検索し、あった場合はその単語の極性値を取得します。
そうすることで、その文章に含まれていた単語の極性値の和を求めることができます。最後に得られた極性値の和を単語数で割ることで、文の極性値を算出します。

sent_calc.py
c=(((score/(a+b))+1)*50) # 得られた極性の和を単語数で割る
    msg=tk.Label(text='文章中の感情スコアは'+str(c)+'点)',fg='black',bg='green yellow') # 感情スコアを表示
    msg.place(x=10,y=10+t*30)

以上が感情分析の流れです。意外と簡単だと思われたのではないでしょうか?ぜひ皆さんも感情分析を行ってみてください。まずは東京工業大学高村先生のコーパスをダウンロードしてみてください。
では、ばいにゃん~

参考にさせていただいたもの
(感情分析の手法について参考にさせていただきました2022/9/16)
https://www.cloud-contactcenter.jp/blog/sentiment-analysis-in-business.html

Discussion