【後編】元自衛隊のエンジニア１年目がPythonで感情予測をした話 25.5%→33.6%

# はじめに  
本記事は、下記のとおり【前編】から続く内容になっています。
<br>
【流れ】

　[`1.~ 5.【前編】`](https://qiita.com/takahiroaoi/private/bf3decfe6766f90d7fae)
1. 分析の設計
1. データの収集
1. データの整形（1回目：最小限）
1. モデルの構築（ランダムフォレスト）
1. 可視化・考察
[`以下【後編】`]()
1. データの整形（文字化け対策）　**←以下はここからのお話です**
1. データの整形（不要語の削除）
1. データの整形（語幹の抽出・統合）
1. 目的変数修正
1. 上記を実装
1. 結論・今後の展望

### 全体を通して、そもそも何をしようとしているのか？  
本記事ではTwitterのデータを利用して、ツイートのデータを感情分析しています。
感情分析の高精度なモデルが出来れば、ツイートに限らず様々な文章の感情を定量的に判定できるようになると考えます。
全体を通して、精度が高くなるようにデータの前処理やモデルの性能向上を実施しています。
（この感情分析で何をどう分類したいのか？ 具体例は[【前編】のこちら ](https://qiita.com/takahiroaoi/private/bf3decfe6766f90d7fae#%E7%9B%AE%E7%9A%84%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6qa)で挙げております）
結果を先に申し上げると、当初 **24.0%** の精度だった感情予測モデルを **33.6**% に引き上げました。
私は機械学習初心者であるため、試行錯誤の過程も残しておきたいと考えております。
<br>
# 1. 分析の設計～ 5. 可視化・考察
[【前編】元自衛隊のエンジニア1年目がPythonで感情予測をした話 24.0%→25.5%](https://qiita.com/takahiroaoi/private/bf3decfe6766f90d7fae)
上記をご参照ください。

<details><summary>自己紹介、目的、実行環境等は前編と同じなので畳んでおきます</summary>

# 1. 当記事の概要

### 1.1 自己紹介
|期間|経歴|
|:-:|:-:|
|2011～2022|陸上自衛隊|
|2022～現在|IT企業|

私は陸上自衛隊の最前線で11年働いていました。
その中で災害現場やレンジャーなどを経験しています。  
任務の中で、人の感情に強く興味を持つようになりました。
2022年、データ分析やエンジニアリングにとても興味があったためIT業界へ転職しました。
普段はQlik SenseというBIツールでお客様のデータを可視化したり、PythonやVBAを書いています。  

<br>
<br>

### 1.2 目的
感情分析をしようと思ったきっかけは、IT企業で働く中でお客様である「コールセンターの業務改善」に携わったことです。  
コールセンターが受電する**クレーム**を文章化して「感情分析」できたら面白いのではないかと思いました。  
**クレーム**の文章化は音声のテキスト化や個人情報が障害となり、**データ準備は困難**です。
ですが、まずは足掛かりとして余暇の時間を使い**データ準備**と**予測モデルの構築**をしてみようと思います。
もう一つの目的があり、それは私自身の**スキルアップ**です。

###### 目的についてQ&A

|Q.|A.|
|:----|:----|
|結局やりたいことは？|[文章] を与えると [この文章の感情は`●●`です] と高精度で返してくれるような仕組みの作成（`●●`に入るのは`hapiness` `sadness`など13クラス）|
|どのようなデータを使うの？|[[文章] [文章の感情] という2列のデータセット](https://www.kaggle.com/datasets/pashupatigupta/emotion-detection-from-text)|
|具体的に何をやるの？|機械学習モデル:ランダムフォレストなどによる分類器作成|


<br>

### 1.3 環境
- Google Colaboratory
- Python 3.8
- ChatGPT ver.Feb 13


</details>

【前編】で構築したモデルの精度は`25.5%`でした。
【後編】で以下の解決策を検討し、**精度の向上を目指します。**

| No. | 未解決の課題 | 解決策 | 精度 | 実行前との差 |
| --- | --- | --- | --- | ---: |
| 6 | "quot"の多発が気になる | 文字化け対策 | ??% | ??% |
| 7.1 | ストップワード未使用 | ストップワードの削除 | ??% | ??% |
| 7.2 | 不要語未処理 | 自力でNGワードリストを作成 | ??% | ??% |
| 8 | 語幹の抽出をしていない | nltk形態素解析・語幹の抽出 | ??% | ??% |
| 9 | 感情ごとの正答率差が激しい | 目的変数修正 | ??% | ??% |

<br>

# 6. データの整形（文字化け対策）
### 6.1 目的
分析初期の時期にcsvファイルをWindowsエクスプローラから開いたとき、Excelにて開いていました。
当時、文字コードを確認せず閉じていたことに分析途中で気が付いたため、凡ミスですが是正のため修正します。

### 6.2 手段
Kaggleから再度データセットをダウンロードし、Windowsで開かずに文字コードUTF-8で実行環境へ読み込みます。
このパートはそれだけなので**結果だけ簡単に記載いたします。7. ストップワード除去 からが本番です。**

### 6.3 結果
1. 文字化けはなくなりました。
1. モデルの予測精度は期待に反して**約0.5%下がりました。**

【出力】`ランダムフォレスト: 0.24966599866399466`

### 6.4 可視化・考察
何が起きたかを詳しく見るために、差分を見てみます。
修正前の混同行列 － 修正後の混同行列 ＝ 以下の差分の混同行列
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3094088/7ee1bdc2-bbb2-5a08-34a7-9c8041a8a8e4.png)
- 大きな変化は`happiness`と予測する回数が418回増えたことです。
- **文字化けの有無だけでは、予測精度は大きく変化しない**と言えそうです。

| No. | 未解決の課題 | 解決策 | 精度 | 実行前との差 |
| --- | --- | --- | --- | ---: |
| 6 | "quot"の多発が気になる | 文字化け対策 | <font color="red">25.0%</font> | <font color="red">-0.5%</font> |
| 7.1 | ストップワード未使用 | ストップワードの削除 | <font color="red"></font> | <font color="red"></font> |
| 7.2 | 不要語未処理 | 自力でNGワードリストを作成 | <font color="red"></font> | <font color="red"></font> |
| 8 | 語幹の抽出をしていない | nltk形態素解析・語幹の抽出 | <font color="red"></font> | <font color="red"></font> |
| 9 | 感情ごとの正答率差が激しい | 目的変数修正 | <font color="red"></font> | <font color="red"></font> |

<br>
<br>

# 7. データの整形（ストップワード除去）
### 7.1 nltkライブラリのstop_word(English)を使う
###### 7.1.1 目的
- 無意味な情報排除：文章の内容と直接関係のない単語を除外することで、解析精度を向上します
- メモリ使用量低減

###### 7.1.2 手段
- nltkライブラリの英語stopwordが有名です。まずはこれを実行してみます。
- …が、その前にどんなワードが含まれているのか、気になるので確認します。



```Python:
import nltk

# まずは内容確認
nltk.download('averaged_perceptron_tagger') # 品詞タグをダウンロード
def sort_words_by_pos(words):
    pos_tags = nltk.pos_tag(words) # 品詞タグ付け
    sorted_words = {}
    for word, pos_tag in pos_tags:
        if pos_tag not in sorted_words:
            sorted_words[pos_tag] = [word]
        else:
            sorted_words[pos_tag].append(word)
    return sorted_words

sorted_words = sort_words_by_pos(stop_words)
```

```Python:どんな単語が含まれているか品詞ごとにまとめてみた
dictionary = {
    'IN': ['i', 'if', 'because', 'as', 'until', 'while', 'of', 'at', 'by', 'for', 'with', 'against', 'between', 'into', 'through', 'during', 'before', 'after', 'above', 'below', 'from', 'in', 'on', 'off', 'over', 'under', 'than'], 
    'PRP': ['me', 'myself', 'we', 'ours', 'ourselves', 'you', 'yourself', 'he', 'him', 'himself', 'she', 'her', 'it', 'itself', 'they', 'them', 'themselves'], 
    'PRP$': ['my', 'our', 'your', 'his', 'its', 'their'], 
    'VBP': ["you're", 'am', 'are', 'ain', 'didn', "weren't"], 
    'JJ': ["you've", 'theirs', 'further', 'few', 'other', 'such', 'own', 'same', 's', 'll', 'aren', "hadn't", "haven't", 'isn', 'mightn', "mustn't", 'needn', "shan't", "wasn't", "won't"], 
    'NN': ["you'll", 'do', 'about', 't', "don't", 'm', 'o', 're', 've', 'y', "aren't", 'couldn', "couldn't", "didn't", 'doesn', "doesn't", 'hasn', "hasn't", "isn't", "mightn't", "needn't", 'shouldn', "shouldn't", 'wouldn', "wouldn't"], 
    'VB': ["you'd", 'be', 'have', 'don', "should've"], 
    'NNS': ['yours', 'hers', 'weren'], 
    'VBZ': ['yourselves', "it's", "that'll", 'is', 'has', 'does', 'd'], 
    'VBD': ["she's", 'herself', 'was', 'were', 'did', 'hadn', 'haven', 'ma', 'mustn', 'shan', 'wasn', 'won'], 
    'WP': ['what', 'who', 'whom'], 
    'WDT': ['which', 'that'], 
    'DT': ['this', 'these', 'those', 'a', 'an', 'the', 'all', 'any', 'both', 'each', 'some', 'no'], 
    'VBN': ['been', 'had'], 
    'VBG': ['being', 'having', 'doing'], 
    'CC': ['and', 'but', 'or', 'nor'], 
    'TO': ['to'], 
    'RP': ['up', 'out'], 
    'RB': ['down', 'again', 'then', 'once', 'here', 'not', 'only

```
- 前提として、nltkのストップワードとは**検索システムの運用において除外したいワード**だそうです。
- 感情分析用ではないので、鵜呑みにするのは良くないですね。
- i, you, she などの代名詞や、will, must, had などの助動詞が多いです。
- nor, not, only など感情が含まれそうな単語も存在します。

```Python:実行します
# 下記のstopsを単語リストに適用して、削除します
stops = set(stopwords.words("english")) 
```
###### 7.1.3 結果
- 単語数は**150**語減りました。
`登場単語の種類：30933語` → `登場単語の種類：30783語`

- しかし、モデルの予測精度は期待に反して**2.3%下がりました。**
`ランダムフォレスト: 0.23224455611390285`
<br>

###### 7.1.4 可視化・考察

再び、何が起きたかを詳しく見るために、差分を見てみます。
修正前の混同行列 － 修正後の混同行列 ＝ 以下の差分の混同行列
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3094088/b1236c16-d2b5-6f0b-583d-641bc1f8d9de.png)
- `happiness`　`sadness`　`love` の予測件数が目立ちます
- 上記の3つの感情増えた＝平滑化分類しやすくなった
- しかし**nltkのストップワード適用だけでは、予測精度は大きく変化しない**と言えそうです。
- 前述のとおり「検索機能のための単語リスト」なので、他に「感情予測用の単語リスト」が作れないか検討します。

| No. | 未解決の課題 | 解決策 | 精度 | 実行前との差 |
| --- | --- | --- | --- | ---: |
| 6 | "quot"の多発が気になる | 文字化け対策 | <font color="red">25.0%</font> | <font color="red">-0.5%</font> |
| 7.1 | ストップワード未使用 | ストップワードの削除 | <font color="red">23.2%</font> | <font color="red">-2.3%</font> |
| 7.2 | 不要語未処理 | 自力でNGワードリストを作成 | <font color="red"></font> | <font color="red"></font> |
| 8 | 語幹の抽出をしていない | nltk形態素解析・語幹の抽出 | <font color="red"></font> | <font color="red"></font> |
| 9 | 感情ごとの正答率差が激しい | 目的変数修正 | <font color="red"></font> | <font color="red"></font> |
<br>
<br>

### 7.2 自力でNGワードリストを作成

###### 7.2.1 目的
- nltkのストップワードを今回のモデル構築にそのまま使うのは、やや乱暴だと考えます。
- 自分なりの工夫として、より精度の高い**不要語の削除**を狙います。


###### 7.2.1 手段
- 【前編】で「単語ごとの出現回数をカウント」をしていることを活用し、出現数TOP100を確認します。
- 出現数TOP100を形態素解析し、最後は自分の判断で削除するかどうかアノテーションします。

<details><summary>データセット全体の単語の出現割合の概観</summary>

```Python:
!pip install wordcloud
from wordcloud import WordCloud

# ワードクラウドの作成
wordcloud = WordCloud(background_color='white', width=1500, height=700).generate_from_frequencies(dict(zip(counts_df_sorted['word'], counts_df_sorted['count'])))

# ワードクラウドの表示
plt.imshow(wordcloud, interpolation='bilinear')
plt.axis('off')
plt.show()
```

</details>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3094088/9c2c3bf6-587d-1f6e-ea50-59c5f9f5f48b.png)
```Python:概観より具体的に確認するため、形態素解析
import nltk
# 品詞判定用のライブラリをダウンロード
nltk.download('averaged_perceptron_tagger')

# 各単語の品詞を取得
pos_tags = nltk.pos_tag(word100)
```
以下のように品詞ごとに並べることで、**アノテーション（deleteフラグを立てる）しやすくします**
|word|and|but|one|the|a|that|this|no|some|an|in|for|of|on|with|at|like|from|about|if|as|off|m|good|quot|happy|u|new|miss|great|last|more|can|will|i|s|t|day|http|today|work|lol|time|com|im|amp|night|home|oh|ll|re|morning|need|hope|get|thanks|you|it|me|we|they|he|so|just|not|all|now|too|back|really|think|well|still|much|here|my|your|its|up|to|be|out|go|do|know|there|don|see|was|got|had|going|have|are|love|am|want|is|what|how|when|
|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|count|7,468|3,410|1,408|12,986|9,844|4,307|2,281|1,928|1,124|932|5,443|5,291|4,518|4,169|2,968|2,723|1,939|1,431|1,246|1,096|1,015|969|3,443|2,311|1,855|1,485|1,422|1,081|892|877|876|919|2,266|1,298|24,185|5,336|4,629|3,221|1,838|1,664|1,640|1,449|1,390|1,349|1,327|1,159|1,125|1,072|1,009|1,000|888|860|856|844|2,017|1,024|7,790|7,741|4,180|1,543|1,124|963|3,758|3,210|2,659|2,347|2,274|1,633|1,348|1,279|1,044|1,035|982|955|950|8,077|1,676|1,183|2,165|14,387|2,907|2,186|1,823|1,700|1,356|1,294|1,260|1,186|2,661|1,636|1,143|1,574|3,773|1,915|1,641|1,430|1,017|5,740|1,479|1,064|1,020|
|pos|'CC')|'CC')|'CD')|'DT')|'DT')|'DT')|'DT')|'DT')|'DT')|'DT')|'IN')|'IN')|'IN')|'IN')|'IN')|'IN')|'IN')|'IN')|'IN')|'IN')|'IN')|'IN')|'JJ')|'JJ')|'JJ')|'JJ')|'JJ')|'JJ')|'JJ')|'JJ')|'JJ')|JJR')|'MD')|'MD')|'NN')|'NN')|'NN')|'NN')|'NN')|'NN')|'NN')|'NN')|'NN')|'NN')|'NN')|'NN')|'NN')|'NN')|'NN')|'NN')|'NN')|'NN')|'NN')|'NN')|NNS')|NNS')|PRP')|PRP')|PRP')|PRP')|PRP')|PRP')|'RB')|'RB')|'RB')|'RB')|'RB')|'RB')|'RB')|'RB')|'RB')|'RB')|'RB')|'RB')|'RB')|RP$')|RP$')|RP$')|'RP')|'TO')|'VB')|'VB')|'VB')|'VB')|'VB')|'VB')|'VB')|'VB')|VBD')|VBD')|VBD')|VBG')|VBP')|VBP')|VBP')|VBP')|VBP')|VBZ')|'WP')|WRB')|WRB')|
|delete| | |1|1|1|1|1| | |1|1|1|1|1|1|1| |1|1| |1| |1| |1| |1| | | | | | | |1|1|1| |1| | | | | |1| | | | |1| | | | | | |1|1|1|1|1|1| | | | | | | | | | | | | |1|1|1| | |1| | | | | | | |1| | | | |1| |1| |1| | | |

**自力で作った疑似ストップワードリスト**で不要語の削除を行います
```Python:不要語の削除
path = '/content/drive/MyDrive/Data for Colab Notebooks/20230303200335_頻出ワード100.xlsx'
df = pd.read_excel(path)
delete_list = df['word'].tolist()
delete_list
# '@'が含まれる文字とストップワードを削除
stops = set(delete_list)  
meaningful_words = [w for w in words if not w in stops and not re.match("^[@]", w)]
```
###### 7.2.3 結果
- モデルの予測精度は期待に反して**約1.4%下がりました。**
`ランダムフォレスト: 0.24060464339402038`
<br>

###### 7.2.4 可視化・考察  

再び、何が起きたかを詳しく見るために、差分を見てみます。
修正前の混同行列 － 修正後の混同行列 ＝ 以下の差分の混同行列
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3094088/64096126-eef8-c517-0333-75ec803d6a78.png)
- 強いて言えば `neutral` `worry` `happiness`　`sadness`　の予測件数増減があります。
- しかし、私の技術レベルにおいて**自前のストップワード適用だけでは、予測精度は上げられない**と言えそうです。
- 次に同じく形態素解析を活用し、**語幹の抽出による単語の統合により精度向上**できないか検討します。


| No. | 未解決の課題 | 解決策 | 精度 | 実行前との差 |
| --- | --- | --- | --- | ---: |
| 6 | "quot"の多発が気になる | 文字化け対策 | <font color="red">25.0%</font> | <font color="red">-0.5%</font> |
| 7.1 | ストップワード未使用 | ストップワードの削除 | <font color="red">23.2%</font> | <font color="red">-2.3%</font> |
| 7.2 | 不要語未処理 | 自力でNGワードリストを作成  | <font color="red">24.1%</font> | <font color="red">-1.4%</font> |
| 8 | 語幹の抽出をしていない | nltk形態素解析・語幹の抽出 | <font color="red"></font> | <font color="red"></font> |
| 9 | 感情ごとの正答率差が激しい | 目的変数修正 | <font color="red"></font> | <font color="red"></font> |

<br>
<br>

# 8. データの整形（nltk形態素解析、語幹の抽出）
### 8.1 目的
- 同じ語幹を持つ単語をまとめることができ、より正確な感情分析を行う
- 単語の形態変化によって生じる情報量の違いを排除し、感情分析の精度を向上させる

### 8.2 手段
1. まずは使うライブラリを検討
2. 関数を作成し、データを処理
3. 語幹抽出による単語数の削減効果と、モデルの精度を確認

|ライブラリ名|語幹の精度|処理速度|メリット|デメリット|
|:---:|:---:|:---:|:---:|:---:|
|Polyglot|○|◎|多言語処理に特化しており、様々な言語に対応している。|語幹の精度がNLTKなどに比べて低いことがある。|
|NLTK|◎|△|語幹の精度が高い。多くの自然言語処理タスクに対応している。<font color="red">情報量が多い。→初めてなのでこちらを採用</font>|処理速度が遅いことがある。|
|TreeTagger|◎|○|語幹の精度が高い。多くの言語に対応している。|インストールや使用に専門的な知識が必要な場合がある。英語以外の言語では精度が低いことがある。|

<br>

- 語幹の具体的な抽出方法の例として、以下の様に行います。

|単語|語幹の抽出方法|語幹の例|
|:---:|:---:|:---:|
|running|動詞を原型にする|run|
|mice|名詞を単数形にする|mouse|
|biggest|形容詞を原形にする|big|


<details><summary>関数のコードが長くなったため畳みます</summary>

```Python:形態素解析
import nltk
!pip install -U nltk
nltk.download('punkt')
nltk.download('averaged_perceptron_tagger')
nltk.download('omw-1.4')
nltk.download('wordnet')
# 単語の正規化
from nltk.stem import WordNetLemmatizer
lemmatizer = WordNetLemmatizer()

def lemmatize_word(word, tag):
    if tag.startswith('NN'):
        return lemmatizer.lemmatize(word, pos='n')
    elif tag.startswith('VB'):
        return lemmatizer.lemmatize(word, pos='v')
    elif tag.startswith('JJ'):
        return lemmatizer.lemmatize(word, pos='a')
    elif tag.startswith('R'):
        return lemmatizer.lemmatize(word, pos='r')
    else:
        return word

# 形態素解析
from nltk.tokenize import word_tokenize
# from nltk.corpus import stopwords

# stop_words = set(stopwords.words('english'))

def tokenize_text(text):
    tokens = word_tokenize(text)
    tokens = [token.strip() for token in tokens]
    tagged_tokens = nltk.pos_tag(tokens)
    lemmatized_tokens = [lemmatize_word(token, tag) for token, tag in tagged_tokens]
    return [token.lower() for token in lemmatized_tokens if token.isalpha()]

clean_tweet_df['tokens'] = clean_tweet_df['content'].apply(tokenize_text)

# 変換履歴の表示
import itertools

def print_transformation_history(original_token, final_token):
    if original_token != final_token:
        print(f"{original_token} → {final_token}")

def print_top_transformation_history(df, n=10):
    all_tokens = list(itertools.chain.from_iterable(df['tokens']))
    transformed_tokens = list(itertools.chain.from_iterable(df['tokens_transformed']))
    transformed_count = len([t for t in transformed_tokens if t != ''])
    print(f"Total number of transformations: {transformed_count}")
    print("\nTop transformation history:")
    for original_token, final_token in itertools.zip_longest(all_tokens, transformed_tokens):
        if original_token is None or final_token is None:
            break
        print_transformation_history(original_token, final_token)
        n -= 1
        if n == 0:
            break

# 語幹の変換
import inflect
p = inflect.engine()

def stem_transform(token):
    return p.singular_noun(token) or token

clean_tweet_df['tokens_transformed'] = clean_tweet_df['tokens'].apply(lambda x: [stem_transform(token) for token in x])

# 過去形、進行形の変換
from textblob import TextBlob

def verb_transform(token):
    blob = TextBlob(token)
    if blob.tags[0][1] == 'VBD' or blob.tags[0][1] == 'VBN':
        return blob.words[0].lemmatize('v')
    elif blob.tags[0][1] == 'VBG':
        return blob.words[0].lemmatize('v')[:-1]
    else:
        return token

clean_tweet_df['tokens_transformed'] = clean_tweet_df['tokens_transformed'].apply(lambda x: ' '.join([verb_transform(token) for token in x]))

clean_tweet_df
```
</details>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3094088/14e01596-a7c2-e79a-7d1a-b81627ea4140.png)



### 8.3 結果
- 単語数は**4,838**語減りました
`登場単語の種類：30,933語` → `登場単語の種類：26,095語`

- モデルの予測精度は期待に反して**約0.2%下がりました。**
`ランダムフォレスト: 0.25334001336005346`

### 8.4 可視化・考察

再び、何が起きたかを詳しく見るために、差分を見てみます。
修正前の混同行列 － 修正後の混同行列 ＝ 以下の差分の混同行列
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3094088/5d16adb8-d5b9-f564-e196-60110772a7d9.png)
- **目立った差異がありませんでした**
- **語幹の抽出による単語数削減だけでは、予測精度は大きく変化しない**と言えそうです。
- ここで特徴量の調整を一旦離れて、**目的変数の修正で精度向上を図ります。**

| No. | 未解決の課題 | 解決策 | 精度 | 実行前との差 |
| --- | --- | --- | --- | ---: |
| 6 | "quot"の多発が気になる | 文字化け対策 | <font color="red">25.0%</font> | <font color="red">-0.5%</font> |
| 7.1 | ストップワード未使用 | ストップワードの削除 | <font color="red">23.2%</font> | <font color="red">-2.3%</font> |
| 7.2 | 不要語未処理 | 自力でNGワードリストを作成 | <font color="red">24.1%</font> | <font color="red">-1.4%</font> |
| 8 | 語幹の抽出をしていない | nltk形態素解析・語幹の抽出 | <font color="red">25.3%</font> | <font color="red">-0.2%</font> |
| 9 | 感情ごとの正答率差が激しい | 目的変数修正 | <font color="red"></font> | <font color="red"></font> |


<br><br>

# 9. 目的変数修正
### 9.1 目的変数削減（13個→8個）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3094088/14572110-a0a1-9a2d-0fe2-0ebf5562ba46.png)
###### 9.1.1 目的
- 互いに密接に関連するカテゴリを減らすことで、予測性能を向上させることが出来る。
- データの偏りや不均衡をなくして、カテゴリへの過剰な適合を防ぐことが出来る。
- モデルの計算時間を低減することが出来る。

###### 9.1.2 手段
　**①モデルがどの目的変数に分類しているか**  
　**②偏りや不均衡を発生させているのはどこか**
　下図のように混同行列から確認していきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3094088/8093cfbb-b443-6449-768d-c2cb77e78261.png)

###### 中間考察

分類タスクの典型的な例として
**ガンの判定において「ガン発症者をガンでないと看過することを避けたい」** というものがあります。 

感情を分類する時も同じく **「ネガティブな感情を看過することを避けたい」** と考えられます。

以上の検討から、分類を困難にしていると考えらえる`neutral`,`worry`, `empty`, `boredom`, `enthusiasm` を削減します。
```Python:目的変数削除
# 削除したいクラスを書いて下記で~を記述する、あるいは残したいクラスを書く
drop_class = ["neutral","worry", 'empty', 'boredom', 'enthusiasm']
# 削除項目を~で指定して、削除項目以外をすべて取り出す
drop_Tweet = Tweet[~Tweet["sentiment"].isin(drop_class)]
```
　→ `20642 rows × 2 columns`
　　40,000行のデータが約半分になりました。

どんな割合になるか？
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3094088/d403d4c9-d4d2-b6f0-b5ec-204fbe0b6664.png)
 `anger`は特に少ないものの、感情分析タスクにおいて重要な目的変数であると考え、残しています。

###### 9.1.3 結果
【出力】`ランダムフォレスト: 0.282807570977918`

　→ **約28.3**%とします

###### 9.1.4 考察
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3094088/d9ee8515-5c67-c81f-d270-a6ac9ce442f0.png)
対角線で**正解できた割合**を見ていきます。

**① `love`以上の3つは正答率が比較的高い**
**② 下位5つは正答率0に近い**
**③ `happiness` `saddness` とその他の目的変数との混同**が多い
　→ 下位の<font color="lightgreen">③で囲んでいる中</font>において、混同数÷正解ラベル数9割を超えています。ほとんどが`happiness` `saddness` に割り振られてしまっています。


- 一旦目的変数を13個に戻します。
- 次に別の手段として**目的変数の統合**を行います

<br>

### 9.2 目的変数統合（13個→6個）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3094088/8134f9ec-cbba-e1a7-725a-7ab587ee748d.png)
###### 9.2.1 目的
- **データセットの行数を減らすことなく**、目的変数だけを減らすため
- 互いに密接に関連するカテゴリをグループ化して、予測性能を向上させるため
- データの偏りや不均衡をなくして、カテゴリへの過剰な適合を防ぐため
- モデルの計算時間を削減するため

###### 9.2.2 手段

- 言語として意味が近いものを統合する（例：`empty`=空虚 と `boredom`=物足りない）
- `その他`として極端に少ない正解ラベルをまとめる（例：`other`として`empty` `boredum` `relief`=安心 を統合）


具体的には以下の様に、データを操作しました。
```Python:目的変数統合
Tweet['sentiment'] = Tweet['sentiment'].apply(lambda x : x if x in ['happiness', 'sadness', 'worry', 'neutral', 'love'] else "other")
```
　↓　今回は割合の少なかった下位*7*個の目的変数を、`other`と定義しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3094088/92f096a2-5ba0-0a91-6307-b16c65293cef.png)
　　　　　　　　**↓ `other`に**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3094088/55fcaf86-b732-9d07-ef79-edf84314b3a2.png)

###### 9.2.3 結果

【出力】`ランダムフォレスト: 0.25250501002004005`

　→ **約25.3**%とします


###### 9.2.4 考察
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3094088/2a4daf3b-8d5a-b9fd-d536-a213754d5ef2.png)
※ `other` に多くの予測が集まっています。
※ `neutral` が上位3件のうち、やや少なめです。
**① `other` `neutral` `worry`** は正解率が約**30～40**%
**② `happiness` `sadness` `love`** は正解率が約**3～8**%
　→ラベルの**正解数**合計は①が2,000個台に対して②は1,000個台であり、その差は**約2倍**
　→しかし、**正解率**は①と②の間で**3～10倍**の差が開いている  
　　→**つまり <font color="red">`happiness` `sadness` `love` が苦手、</font>** と考えられる  
　　→ <font color="red">**`neutral` が文章の特徴が低いクラスなので、`worry`や`other`に分類されやすい**</font> と考えられる
　　　理由： `neutral` には「特徴的な単語が少ない文章」が分類されると考えられる
　　　　　　 `worry` と `neutral` を比べると、`neutral`の方が単語量が多いと考えられる
<br>

④ 悲しい文章などは特徴的。日常会話で使う種類の多い単語は`neutral`なものが多そう。
　→`worry`や`other`に割り振られる確率は低いと考えられる  
<br>

⑤ `neutral`で使われているのは30,000種類、`worry`は1,000種類のようなイメージ
　→ランダムフォレストの中では、該当の単語が出てきているかどうかで行き先が決められている。
　　→単語が多くても、一個でも`worry`的なものがあれば`worry`に行ってしまうと考えられる。
　　　→アノテーション作業を考える。
　　　　「特徴的な単語がないな」→`neutral`行き
　　　　「少しでも`worry`だな」`worry`となりそう
<br>
※`happiness`以下は1,000個台でデータ量が少ない。ランダムフォレストは単純なので、精度向上のため2,000個台の多いクラスに割り振ろうとする
　 → 結果、予測値が2,000台のクラスになると思われる。もう少しサンプリングするなどして、1,500ぐらいに揃えてみるのも良さそう。

<br>
ここまでのまとめ

| No. | 未解決の課題 | 解決策 | 精度 | 実行前との差 |
| --- | --- | --- | --- | ---: |
| 6 | "quot"の多発が気になる | 文字化け対策 | <font color="red">25.0%</font> | <font color="red">-0.5%</font> |
| 7.1 | ストップワード未使用 | ストップワードの削除 | <font color="red">23.2%</font> | <font color="red">-2.3%</font> |
| 7.2 | 不要語未処理 | 自力でNGワードリストを作成 | <font color="red">24.1%</font> | <font color="red">-1.4%</font> |
| 8 | 語幹の抽出をしていない | nltk形態素解析・語幹の抽出 | <font color="red">25.3%</font> | <font color="red">-0.2%</font> |
| 9 | 感情ごとの正答率差が激しい | 目的変数修正（5個削除） | <font color="red">28.3%</font> | <font color="red">**+2.8%**</font> |

<br>

# 10. 上記を組み合わせて実装
　`6. 文字化け対策`、`7.2 不要語削除`、`8. 形態素解析による語幹の抽出`、`9.1 目的変数の削減`

### 10.1 目的
- 精度30%以上を目指すため
### 10.2 手段
 1. `6. 文字化け対策`、`7.2 不要語削除`、`8. 形態素解析による語幹の抽出`、`9.1 目的変数の削減` を実装
 1. ランダムフォレストのパラメータを調整

### 10.3 結果
1. を実装した結果：`ランダムフォレスト: 0.3305993690851735`
    →  <font color="red">**約33.6**%：目標とする精度に到達しました。</font>
1. ランダムフォレストのパラメータ`n_estimator`を1～501までの間で調整
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3094088/3f91c8d0-5555-b31e-9ced-4c7f0318bcbb.png)
　実装した結果：`ランダムフォレスト: 0.3359621451104101`
    　　→ <font color="red">**約33.6**%</font>

### 10.4 考察
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3094088/a688b989-8b84-d6c1-d001-47a081d2fcc5.png)
**① `happiness` の正解率は46% `sadness` の正解率は<font color="green">69%</font>** 
**② `suprise` より下の感情の正解率は<font color="orange">2%</font>以下**
　 `happiness` と `sadness` に分類されてしまっているのが大きな要因と考えられます。
<br>
<br>


# 11.結論・今後の展望

### 11.1 結論

**現状のモデルだとこれ以上の大幅な精度向上は見込めなそうです。**

###### 11.1.1 精度が上がりづらくなっている理由
① ランダムフォレストは比較的単純なモデルなので限界がありそうです。
 
|内容|詳細|
|:---:|:---|
|ランダムフォレスト<br>の特徴|・複数の決定木を組み合わせて全体的な精度を向上させるアンサンブル学習法<br>・多数の特徴を持つ高次元データを扱える<br>・欠損値を扱え、データセットが不均衡でも精度を維持可能<br>・解釈性が高く、意思決定プロセスを比較的簡単に解釈・可視化可能|
|精度向上できない場合<br>の理由|・単語間の複雑な関係を捉えたり、シーケンシャルなデータを分析するようなタスクには、LSTMやBERTなどのディープラーニングモデルほど効果的ではない<br>・クラスの数が多いタスクや、クラスが不均衡な場合には、うまく機能しない可能性がある|

② データセットの特徴として、ツイートなので以下の理由から綺麗なテキストデータではないです。
- 話し言葉と書き言葉が混在している
- クラス数に偏りがある

    
<br>

### 11.2 今後の展望
###### 11.2.1 他の手法を使った感情分析に挑戦してみます

|手法|メリット|デメリット|
|:---:|:---|:---|
|TF-IDF|高次元データから特徴的な情報を抽出しやすい。 | 意味が似ているが異なる単語を区別しない。|
|勾配ブースティング|高精度、重要度算出、外れ値に強い|過学習に陥りやすい、パラメータ調整が難しい|
|LSTM|高精度、説明変数自動抽出|過学習に陥りやすい、学習に時間がかかる|
|Transformer|高精度、長文処理、並列処理に適している|多くのデータ量、計算資源が必要で、解釈性が低い|


###### 11.2.2 感情予測モデルの汎用性について
- 当初目的のコールセンターのお客様の声だけでなく、「LINEの感情分析」「レビューの感情分析」などに、精度次第では横展開できそうだと感じました。
- 昨今の翻訳ツールや翻訳WebAPIをフル活用すれば、英語でモデルに学習させたとしても、日本語のテキストを予測することは出来るのではないかと考えます。

###### 11.2.3 感情分析の言語の違いについて


今回は英語だったので、今後日本語のデータが準備できれば日本語でもやってみようと思います。


# おわりに

以上となります。
機械学習に初めて挑戦しましたので、まだ入り口に立ったばかりと感じます。
今回の感情予測をきっかけに、データ分析のスキルをどんどん磨いていければと思います。

ここまでお読みいただき、本当にありがとうございました。

***
[【前編】元自衛隊のエンジニア1年目がPythonで感情予測をした話 24.0%→25.5%](https://qiita.com/takahiroaoi/private/bf3decfe6766f90d7fae)