アプリとサービスのすすめ

アプリやIT系のサービスを中心に書いていきます。たまに副業やビジネス関係の情報なども気ままにつづります

BERTで6感情の感情分析モデルを作ってみた【機械学習、自然言語処理】

画像と違って文章から感情を予測すること(emotion prediction from text)は未だ自然言語処理NLP)界隈では、うまくいった事例が少ない。

特に、単純なネガポジ判定ではなく、6感情(怒り、驚き、幸せ、嫌悪、恐れ、悲しみ)を分析する感情分析は、研究が頻繁に行われてる。

今回はBERTでなるべく精度の高い感情分析モデルを作ってみた。

f:id:trafalbad:20190901145030j:plain

目次
・感情分析について
1.twitterからスクレイピングしてデータセット作成したcase
2.スクレイピングした映画レビューからデータセットを作ったcase
3.気づいたこと
4.まとめ

感情分析について

感情分析は英語でも日本語でも未だにうまくいってなくて、論文が頻繁にでてる分野。


難しい理由の一因は「データセットの作成が難しい」とか「ノイズの多い日本語のような難解な言語での感情判定が困難」だから。

比較的処理しやい英語でも、kaggleのIMDBの5段階ネガポジ判定で精度68%くらいだった。

なのでノイズ表現(” ~したいンゴ ”、 “~みが強い”、” インスタ蠅 ”)みたいな意味不な言葉が増えた、かつ難解な日本語の6感情の感情分析ならなおさらむずい。





極性分析な主なデータセットの作り方

①極性分析(主にネガポジ判定)では公開用の極性辞書を使い、ラベルをつけて作成。

②EkmanみたいなAPIで文章にラベルづけして作成

③極性辞書を自作してラベルをつけて作成

④どっからからスクレイピングして、感情ラベルの代わりにする(iPhoneスタンプとか)

⑤人手で一からしっかりデータセット作る

①②は極性辞書やAPI作成者の「どのように感情判定するか」の基準が如実に反映されてるので、個々のタスクごとに最良の結果が出るとは言えない。
なので③~⑤が各タスクのメインな手法な気がする。

Microsoftの例
f:id:trafalbad:20190901145342j:plain




今回は

・感情スタンプ付きのツイートをtwitterからスクレイピング(④)

・映画レビューをスクレイピングして自分で簡単なデータセットを作る(⑤)

の2つを試した。

twitterは6感情でよく使うiPhoneスタンプを含んだツイート、映画レビューは6感情をよく表す映画のレビューから自分でラベルをつけて、データセットを作った。






1.twitterからスクレイピングしてデータセット作成したcase

今回はなるべくいいネットワークを使うため、BERTを選択。よく理解した上で使った。
trafalbad.hatenadiary.jp



友達にアンケートとって6感情でよく使うiPhoneスタンプを教えてもらって、そのスタンプ含んだツイートをスクレイピング

run.sh

#!/bin/bash
# angry
twitterscraper 😠 --lang ja -o angry.json &
twitterscraper 😡 --lang ja -o angry2.json &
twitterscraper 😤 --lang ja -o angry3.json &
# disgust
twitterscraper 🤮 --lang ja -o disgust.json &
twitterscraper 😣  --lang ja -o disgust2.json &
# fear
twitterscraper 😨 --lang ja -o fear.json &
twitterscraper 😰 --lang ja -o fear2.json &
twitterscraper 😱 --lang ja -o fear3.json &
# happy
twitterscraper 😄 --lang ja -o happy.json &
twitterscraper 😆 --lang ja -o happy2.json &
twitterscraper 😂 --lang ja -o happy3.json &

# 以下略
wait;

echo "Done!:twitterscraper"

スクレイピング実行コマンド

$ chmod +x run.sh
$ ./run.sh &


EC2インスタンスGPUでも一日かかった。

データセット作成

# get tweet text and emotion label
emotions = [["angry", "angry1", "angry2"], ["disgust", "disgust2"], ["fear", "fear2", "fear3"], ["happy", "happy2", "happy3"],
            "sad", ["surprise", "surprise2", "surprise3"]]
dir_path = "sentiment_sh"

size = 60000
df = []
for i, es in enumerate(emotions):
    if isinstance(es, list):
        for e in es:
            try:
                data = shuffle(pd.read_json(join(dir_path, "{}.json".format(e)))).iloc[:int(size/len(es))]
                data['label'] = i
                df.append(data)
            except ValueError:
                continue
                
    else:
        data = shuffle(pd.read_json(join(dir_path, "{}.json".format(es)))).iloc[:int(size)]
        data['label'] = i
        df.append(data)
        
df = pd.concat(df)
df = shuffle(df)
text_df = df['text']
label_df = df['label']

dff=pd.concat([text_df, label_df], axis=1)
# save to csv
dff.to_csv('tweet.csv')

とりあえず、アルファベット、絵文字や顔文字とか日本語に関係ない文字が多すぎて、ほぼ文章じゃなかった。

なので、正規化して出来るだけまともな形にした後、BERTで転移学習。

正規化してもほぼ日本語じゃない形で、しかも感情を表す要因が、文章に反映されてない(嬉しい系のツイートでも悲しいスタンプ😢があったり)。

結果、データセットとしてかなり質が悪く、BERTでも精度は43%。





2.スクレイピングした映画レビューからデータセットを作ったcase

映画サイトから、Beautifulsoupでレビューをスクレイピング & 自分の直感でラベル付与して、データセット作った。

映画サイトはURLの形式がパターン化されてるのでスクレイピングしやすい。

ジブリ系(悲しい、幸せ)、ハングオーバー(笑い)、ランペイジ-巨獣大戦争(嫌悪)など6感情を愚直に反映してる映画の7このレビュー文をスクレイピング

rating = []
reviews =[]
first_url = 'https://******/movies/82210'
next_urls = 'https://******/movies/82210?page='
for i in range(1,200):
  if i==1:
    next_url = first_url
  else:
    next_url = next_urls+str(i)
    
  result = requests.get(next_url)
  c = result.content
  # HTMLを元に、オブジェクトを作る
  soup = BeautifulSoup(c, "lxml")
  # リストの部分を切り出し
  sums = soup.find("div",{'class':'l-main'})
  com = sums.find_all('div', {'class':'p-mark'})

  # get review
  for rev in com:
    reviews.append(rev.text)
  # get rating
  for crate in com:
    for rate in crate.find_all('div', {'class':'c-rating__score'}):
      rating.append(rate.text)
  # print(i)

# save review data as DataFrame
rev_list = Series(reviews)
rate_list = Series(rating)
print(len(rev_list), len(rate_list))

movie_df = pd.concat([rev_list, rate_list],axis=1)
movie_df.columns=['review','rating']
movie_df.to_csv('movie_review.csv', sep = '\t',encoding='utf-16')

レビュー文は割と長めのしっかりしたレビューを選択。

・trainデータ:15000
・testデータ:1000

trainデータはスピード重視で、特に感情判定にルール設定はせずに、6感情のうち当てはまりそうな感情のラベルを直感でつけた。

testデータは映画「天気の子」からスクレイピングして、mecabで正規化時に「名詞、形容詞、動詞」のいずれかを10個以上含むしっかりとした長さの文章をランダムに取り出した。


精度は72%。直感で感情判定したが精度はtwitterと比べるとかなり高め。データセットの質が精度に影響してるのがよくわかる。

あとこの精度は「映画のレビュー & 割としっかりした長さの文章」という条件下での精度なので、他のドメインの文章(医療、経済、メディア.etc)に同じ精度は出ない可能性は高い。

やっぱり、特定のドメインの文章には特定のドメインに特化したモデルを作るのがベストだと思う。





3.気づいたこと

・日本語は「2チャンネルやtwitterのような崩壊寸前の文章」、「メディア系のお堅い文章」の最低2つのドメインは確実に存在する。


・なぜこの文章が「怒り、驚き、幸せ、嫌悪、恐れ、悲しみ」の感情に分類されるか、ちゃんとルールを設けること(ルールベース)

ルールの例
ex1.「びっくり」の名詞を含む=驚き

ex2.「びびり」の表現=嫌悪
とか


・「お堅い文章」、「2ちゃんやtwitterのような崩壊した日本語の文章」で大きく2つにドメインが分かれるので、一つで完璧な感情分析言語モデルを作るのは難しい


・完璧な感情分析モデルは作成困難なので、ドメイン別にモデルを作るのがベター


・感情判定にルールを設けた上で、人手できちんとデータセットを作るべし(人間が理解できないものは機械学習でもできないし)


・人手で作るなら質の高いクラウドソーシングのAmazon Mechanical Turkを利用するのがおすすめ


4.まとめ

ラベルをつけるとき、「なぜこの文章はこの感情になるか」のルールを決めるとさらに精度は上がることは間違いない。

2ちゃんや、twitterみたいな日本語が崩壊してるレベルの文章(仲間同士でしか使わない隠語、最近の意味不な表現が多数ある文章)は、映画のレビュー文とは全然違う。
かといって映画とかのレビュー文も、メディアみたいにお堅い文章とは違った。

ともあれ日本語には最低でも上の、2ドメインは確実に存在するので、タスクごとのドメインに特化したモデル作るのが得策だと思う。



メモ:感情分析モデルの活用方法


・患者の診断応答から感情予測して、感情が症状回復に関係ある場合に使える。
→この場合のベストプラクティスは医療ドメイン用文章のデータセットを作り、モデルを作るべきかなと思う



・エンタメで感情ごとに似たエンタメをレコメンドする。
→映画とかで悲しい映画がみたいとき、悲しいを表す似た映画をレコメンドする(作るの難しいし、実用性低いだろうけど)



参考site


日本語ツイートをEkmanの基本6感情で評価

Emotion Detection and Recognition from Text Using Deep Learning

・感情分析に関する情報
https://qr.ae/TWyb8i