【自然言語処理】文章の感情分析をやってみた

# はじめに
Python学習中の大学院生です。

最近、自然言語処理による感情分析について学ぶ機会があったのでそのアウトプットとして自分でもプログラムを作成してみました。


# 本記事の概要
1. プログラムの目的
2. 実行環境
3. プログラムの概要
4. 使用するデータ
5. 実際のプログラム

# 本題
## 1. プログラムの目的

AmazonなどのECサイトで商品を購入する際、その商品のどこがいいのか、どこが懸念点なのかをレビューを見て判断する人も多いと思います。

自分は星の評価が高い順、低い順に並べ替えてレビューを見ていくのですが、評価の高いレビューの中にも批判的なコメントがあったり、低評価のレビューにポジティブなコメントが含まれていたりします。

また、レビュー文は書かれているのに星の評価を記入していない人もたくさんいます。有益なコメントが書かれていたとしても見逃してしまう可能性が高いです。

よって、星の評価順に加えてレビュー文の内容を基準としてレビューを並べ替えられるようになればもっと便利になるのではないかと考えました。

レビュー文の内容で並べ替えを行うには、レビュー文の内容を数値化する必要があります。そこで今回はレビュー文を数値化するプログラムを作成していきたいと思います。


## 2. 実行環境
GoogleColaboratory上で実行しています。

## 3. プログラムの概要

文章の内容を数値化する方法についてですが、自然言語処理による感情分析を利用します。

感情分析とは文章を解析して文章の感情の度合いを数値化するというものです。具体的には感情分析モデルを用いて文章のポジティブ度合い、ネガティブ度合いを感情スコアとして数値化します。

また、感情スコアを元にその文章がネガティブなのかポジティブなのかを分類することもできます。

例として、以下の三つの文章があるとします。

・給料が高くて満足しています。
・給料低すぎるだろ！
・可もなく不可もなく

人間がパッと読むと上から順にポジティブ、ネガティブ、ポジティブとネガティブの間のニュートラルな文章と感覚的に判断できます。これを学習済みの感情分析モデルを用いて数値化して判断するようにします。

```python
import torch
from transformers import pipeline, AutoModelForSequenceClassification, AutoTokenizer

# 事前学習済みの日本語感情分析モデルとそのトークナイザをロード
model = AutoModelForSequenceClassification.from_pretrained('christian-phu/bert-finetuned-japanese-sentiment')
tokenizer = AutoTokenizer.from_pretrained('christian-phu/bert-finetuned-japanese-sentiment', model_max_lentgh=512)

# 感情分析のためのパイプラインを設定
nlp = pipeline('sentiment-analysis', model=model, tokenizer=tokenizer, truncation=True)

# 分析対象となるテキストのリスト
texts = ['給料が高くて満足しています。', '給料低すぎるだろ！', '可もなく不可もなく']

# 各テキストに対して感情分析を実行
for text in texts:
    print('*'*50)
    inputs = tokenizer(text, padding=True, truncation=True, return_tensors='pt', max_length=512)
    outputs = model(**inputs)
    logits = outputs.logits

    # ロジットを確率に変換
    probabilities = torch.softmax(logits, dim=1)[0]

    # 最も高い確率の感情ラベルを取得
    sentiment_label = model.config.id2label[torch.argmax(probabilities).item()]

    print('テキスト：{}'.format(text))
    print('感情：{}'.format(sentiment_label))


    # positiveまたはnegativeの場合はその確率を表示、neutralの場合はpositiveとnegativeの最大値を表示
    if ((sentiment_label == 'positive') or (sentiment_label == 'negative')):  
        print('感情スコア：{}'.format(max(probabilities)))
    else:
        print('感情スコア：{}'.format(max(probabilities[0], probabilities[2])))

# 出力結果
# **************************************************
# テキスト：給料が高くて満足しています。
# 感情：positive
# 感情スコア：0.9992052912712097
# **************************************************
# テキスト：給料低すぎるだろ！
# 感情：negative
# 感情スコア：0.9936193823814392
# **************************************************
# テキスト：可もなく不可もなく
# 感情：neutral
# 感情スコア：0.0014821902150288224
```

無事に文章を数値化し、感情の判定ができています。
このようにして、レビュー文の数値化を行っていきたいと思います。

## 4. 使用するデータ
Amazonなどの実際のレビュー文を使用することは難しいので、Kaggleの[Women's E-Commerce Clothing Reviews](https://www.kaggle.com/datasets/nicapotato/womens-ecommerce-clothing-reviews?resource=download)にあるデータを使って擬似的にレビュー文の感情分析を行ってみようと思います。

データには商品IDやレビュー文、評価（5段階評価、星の評価的な）、商品のカテゴリ名など11カラムあります。

今回はDressesのレビューを感情分析していきたいと思います。

```python
import pandas as pd

# CSVファイルをデータフレームに格納
review = pd.read_csv('/content/Womens Clothing E-Commerce Reviews.csv')
df = pd.DataFrame(review)

# カラム名を使いやすい名前に変更
df = df.rename(columns={"Unnamed: 0":"ID",'Review Text': 'Text'})

# 商品を　Dressesに絞る
df = df[df['Class Name'] == 'Dresses']

# 分析に必要なカラムに絞る
df = df[['ID', 'Title', 'Text', 'Rating', 'Class Name']]

# 欠損値の削除
df = df.dropna()

# シャッフル
df = df.sample(frac=1, random_state=70, ignore_index=True)
```
最終的なデータフレームは以下のようになります。
![スクリーンショット 2023-09-23 12.02.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2741553/c2c5d5f1-0231-eb81-b8eb-a4bf48055060.png)

## 5. 実際のプログラム

### 使用するモデルについて
先ほどの例はテキストが日本語だったのでBERTの日本語の感情分析用に用意されたモデルを使用したのですが、今回のレビュー文は全て英語なので、VADERを用います。

BERTの方は深層学習ベースのモデルで大量のテキストデータで事前に学習されたものですが、VADERはルールベースのモデルで感情の強度を示すスコアの辞書を使用してテキストの感情を評価します。

ソーシャルメディアのような短いテキストの感情分析に特化していて、リアルタイムでの高速な分析が可能になっているのが特徴です。レビュー文も比較的短いということもあってVADERを使ってみます。

### 感情分析の実行

```python
import nltk
from nltk.sentiment.vader import SentimentIntensityAnalyzer

# VADERの感情分析ツールを初期化
sia = SentimentIntensityAnalyzer()

# 結果を保存するためのリスト
sentiment_label_list = []
score_label_list = []

# データセットの各テキストに対して感情分析を実行
for text in df['Text']:

    # VADERを使用して感情スコアを取得
    scores = sia.polarity_scores(text)
    compound_score = scores['compound']

    # compoundスコアを基に感情のラベルを割り当て
    if compound_score >= 0.05:
        sentiment_label = 'POSITIVE'
        score = compound_score
    elif compound_score <= -0.05:
        sentiment_label = 'NEGATIVE'
        score = compound_score
    else:
        sentiment_label = 'NEUTRAL'
        score = compound_score

    # 結果をリストに保存
    sentiment_label_list.append(sentiment_label)
    score_label_list.append(score)
```

それぞれのレビュー文を感情分析し、感情のラベルと感情スコアをリストにまとめました。

### 感情分析の前後の変化
Ratingの1と2をネガティブ、3をニュートラル、4と5をポジティブと仮定します。

感情分析前
```python
# 感情分析前のネガポジの割合
print('negative:', sum(df['Rating'] == 1) + sum(df['Rating'] == 2))
print('neutral:', sum(df['Rating'] == 3))
print('positive:', sum(df['Rating'] == 4) + sum(df['Rating'] == 5))

# 出力結果
# negative: 589
# neutral: 737
# positive: 4045

```

感情分析後
```python
# 感情分析後のネガポジの割合
print('negative:', sentiment_label_list.count('NEGATIVE'))
print('neutral:', sentiment_label_list.count('NEUTRAL'))
print('positive:', sentiment_label_list.count('POSITIVE'))

# 出力結果
# negative: 332
# neutral: 56
# positive: 4983
```

ルールベースのモデルを使っているので、完璧に感情分析できているとは言い難いですが、レビュー文を感情分析することで評価が変化していることがわかります。

低評価にも関わらずレビュー内容はポジティブだったものが多そうです。

### 感情スコア基準でレビューを並び替え

```python
df_result = df.copy()
df_result = df_result[['ID', 'Title', 'Text', 'Rating']]

# 感情ラベルと感情スコアを追加
df_result['Sentiment_Label'] = sentiment_label_list
df_result['Sentiment_Value'] = score_label_list

# Ratingではなく感情スコアの大きい順にレビューを並び替え
df_result = df_result.sort_values(by='Sentiment_Value', ascending=False)
df.head()
```
![スクリーンショット 2023-09-23 12.03.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2741553/bc778729-4d42-e749-6671-8d4346c325c9.png)

```python
df.tail()
```
![スクリーンショット 2023-09-23 11.28.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2741553/aef7f677-8ead-6727-b7cc-6e619990dd20.png)

このように、Rating基準ではなく感情スコア順に再度レビューを並べ替えることができました。


# まとめ
無事にレビューを星の評価基準だけでなくレビュー文の内容で並び替えることができました。

Kaggleのデータを使って擬似的にはなりましたが、現実のレビュー閲覧にもこのような機能ができたらいいなと思って作ってみました。

精度としては完璧ではないものの、人間が感覚的に判断しているものを数値化して感情を分類することができるということを学び、自然言語処理の凄さを実感しました。