テキスト文書のワード(語)をベクトル化して可視化

>2022/08/22：
**t-SNE**のコードにperplexity未反映、**UMAP**のコードにn_neighborsが未反映でした。
これらを反映し、値を可変することである程度のクラスタリングができました。
**PCA**は以前の記事のままとしていますが、**t-SNE**と**UMAP**は上記をコードに追加するとともにグラフをmatplotlib⇒Plotlyに変更しました。


>- **t-SNE**と**UMAP**に適用したデータ ⇒ [**再エネに関するtweet**](https://raw.githubusercontent.com/hima2b4/Natural-language-processing/main/TwExport_20220806_152219.csv)
>- **PCA**に適用したデータ ⇒ [**「学問ノススメ(本文抽出+ノイズ除去済)」**](https://raw.githubusercontent.com/hima2b4/Natural-language-processing/main/sample.txt)


![newplot (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1275001/c07c1250-894c-cf07-eb68-fa4f19875958.png)



# はじめに

自然言語を扱うのは、前々々回から数えて4回目になります。

[**前々々回**](https://qiita.com/hima2b4/items/824566897de593d882d7)は「ワードクラウド」、「TF-IDFでもワードクラウド」が[**前々回**](https://qiita.com/hima2b4/items/bc9c0fac30b6437c5737)、[**前回**](https://qiita.com/hima2b4/items/46688daebfc4c98d729f)は「自然言語が可視化できるnlplot」というライブラリを使用しました。

今回は word2vec を実行してみました。
word2vec の word は単語、vec はベクトルということで、なんと、単語をベクトルで表現することができるんですね。これ。
よく**『「王様」から「男性」を引いて「女性」を足すと「女王」になる』**と喩えられるようですが、これぞ単語のベクトル化のなせる技（足し算・引き算ができる）なのでしょう。

また、単語同士のベクトルが近ければ類似語と解釈できることにもなるので、「似てる」、「ちょっと似てる」、「ぜんぜん似てない」。。。等、グループわけもできることになります。
グループわけは 2次元がわかりやすいので、文章から抽出した単語のベクトルを計算、「主成分分析（PCA）」と「T-SNE」で 2次元化し、グラフにプロットしたというのが今回の記事の内容になります。

文章を読み込み、前処理、特定の品詞の抽出。。。という過程は、前回とまったく同じなので、今回実行した word2vec のコードは、前回のコードに後付けしただけです。（なので、コード記述は後付け分のみとしています。）



## 実行条件など
>- **Google colab**で実行
- **青空文庫の「学問ノススメ」**で実行
※コードは任意データでの実行を意図していますので、「こころ」限定ではありません。
- [**前回の記事**](https://qiita.com/hima2b4/items/46688daebfc4c98d729f)の「ライブラリのインストール」、「テキストファイル & ストップワード指定」、「モジュールの準備」をそのまま使用しています。この記事では[**前回の記事**](https://qiita.com/hima2b4/items/46688daebfc4c98d729f)のコードは掲載していませんのでご注意ください。（※[**前回の記事**](https://qiita.com/hima2b4/items/46688daebfc4c98d729f)のコードを丸ごと使っても問題ありません）

 
# word2vec実行

```:word2vec実行
from gensim.models import word2vec

# size : 中間層のニューロン数・数値に応じて配列の大きさが変わる。数値が多いほど精度が良くなりやすいが、処理が重くなる。
# min_count : この値以下の出現回数の単語を無視
# window : 対象単語を中心とした前後の単語数
# iter : epochs数
# sg : skip-gramを使うかどうか 0:CBOW 1:skip-gram

model = word2vec.Word2Vec(df_text['words2'],
                          size=200,
                          min_count=10,
                          window=5,
                          iter=20,
                          sg = 1)    # sg=1:skip-gram使用

#　学習結果
print(model.wv.vectors.shape)  # 分散表現の形状
print(model.wv.vectors)  #分散表現の配列
```
>(335, 200)
[[-0.15606833  0.09154884  0.1427529  ...  0.03172395 -0.12487664
   0.06667794]
 [-0.219026    0.09349723  0.13353091 ...  0.04806438 -0.14577825
  -0.01028913]
 [-0.15505351  0.08061756  0.13742137 ...  0.03023052 -0.14424255
   0.05791197]
 ...
 [-0.15994348  0.08921173  0.1391619  ...  0.03586242 -0.14906307
   0.04165793]
 [-0.13841385  0.07792909  0.13102876 ...  0.02942118 -0.15503067
   0.04932646]
 [-0.15625252  0.07768033  0.13904253 ...  0.02566543 -0.15500116
   0.0419891 ]]

```:ベクトル化したテキストの各語彙確認
#ベクトル化したテキストの各語彙確認
model.wv.index2word
```

<details><summary>出力</summary><div>
['人',
 '政府',
 '言う',
 'もつ',
 '人民',
 'なす',
 '見る',
 '得る',
 '知る',
 '国',
 'いう',
 '至る',
 '身',
 '理',
 '心',
 '法',
 '有様',
 '働き',
 '文明',
 '人間',
 'うる',
 '行なう',
 '思う',
 '世',
 '力',
 '学者',
 '他人',
 'かる',
 '多い',
 '西洋',
 '事',
 '世間',
 'たる',
 'なり',
 '天下',
 '外国',
 '名',
 'しめる',
 '用いる',
 '家',
 '尽くす',
 '人物',
 '職分',
 '論ずる',
 '国民',
 'よる',
 '間',
 'くる',
 '取る',
 'つく',
 '輩',
 '趣意',
 '及ぶ',
 '一身',
 '右',
 '学校',
 'はなはだしい',
 '主人',
 '自分',
 '足る',
 '民',
 '出る',
 'あり',
 '守る',
 '殺す',
 '難い',
 '者',
 'むる',
 '言',
 '聞く',
 '金',
 '失う',
 '説',
 '大',
 '譬',
 '罪',
 '義',
 'ざる',
 '用',
 '百姓',
 '子',
 '従う',
 '心事',
 '賤',
 'よい',
 '異なる',
 'わが輩',
 '事物',
 '怨望',
 '世界',
 '作る',
 '進む',
 '他',
 '向かう',
 '生ずる',
 '実',
 '町人',
 '貴い',
 '読む',
 'しかる',
 '書',
 '意',
 '銭',
 '気力',
 '世の中',
 '道理',
 'わが国',
 '示す',
 '開く',
 '所以',
 '起こす',
 '当たる',
 'かく',
 '父母',
 '学ぶ',
 'もと',
 '諸国',
 '道',
 '政',
 '風',
 '施す',
 '疑う',
 '言える',
 '達す',
 '少ない',
 '求める',
 '国法',
 '形',
 '誤る',
 '日本人',
 '居る',
 '疑い',
 '似る',
 '役人',
 '経済',
 '風俗',
 '恐れる',
 '命',
 '人々',
 '地位',
 '生じる',
 '子供',
 '外',
 '取り扱う',
 '勢い',
 '全国',
 '一事',
 '気風',
 '働く',
 '入る',
 '名分',
 'とる',
 '一家',
 '教え',
 '従く',
 '計る',
 '立つ',
 '愚',
 '記す',
 '私立',
 '養う',
 '欲する',
 'ただ',
 '信じる',
 '原因',
 '称す',
 '信ずる',
 '天',
 '身分',
 'ぬる',
 '交わる',
 '成す',
 '好む',
 '洋学',
 '可',
 '旦那',
 '例',
 '趣',
 '広い',
 '別',
 '尋',
 '分限',
 '費やす',
 '我',
 '石',
 '破る',
 '言葉',
 '論',
 '害する',
 '害',
 '患う',
 '悪事',
 '官',
 '教師',
 '求む',
 'ずる',
 '試み',
 '制する',
 '人心',
 '人望',
 '字',
 '唱える',
 '争う',
 '親',
 '物',
 '制す',
 'つる',
 '過ぎる',
 '能う',
 '士君子',
 '買う',
 '精神',
 '手',
 '親子',
 '婦人',
 'さる',
 'できる',
 '定める',
 '古来',
 'ためる',
 '天然',
 '述べる',
 '陥る',
 'こ',
 '威',
 '国内',
 '顧みる',
 '悪',
 '能',
 '功',
 '文',
 '問う',
 'だす',
 '禍',
 'ぶる',
 '習慣',
 '去る',
 '術',
 '志',
 '咎',
 '類',
 '信',
 '社中',
 '塾',
 '義士',
 '朋友',
 '雇う',
 'やすい',
 'いわく',
 '物事',
 '世上',
 '文字',
 '古今',
 'ひとり',
 '衣食',
 '不平',
 '恐る',
 '保つ',
 '生まれる',
 'ぐる',
 '軽重',
 '罪人',
 '米',
 '増す',
 '蒙る',
 '事務',
 '事業',
 'よろしい',
 '古人',
 '与える',
 '世帯',
 '恥づ',
 '時代',
 '人情',
 '妨',
 '強い',
 '設ける',
 '恩',
 '起こる',
 '酒',
 '心身',
 '考',
 '改める',
 '弊害',
 '任',
 '先生',
 '評す',
 '目的',
 '方向',
 '工業',
 '接す',
 '入れる',
 '商社',
 '人類',
 'とく',
 '憂う',
 '天地',
 '諺',
 '身代',
 '箇条',
 '男',
 '女',
 '結ぶ',
 '誇る',
 '内',
 '路',
 'いにしえ',
 '敵',
 '愚民',
 '徳',
 '不徳',
 '受ける',
 '一般',
 '等しい',
 '強弱',
 '御殿',
 'つかえる',
 '口',
 '腕',
 '双方',
 '目',
 'すべ',
 '賊',
 '高い',
 '世界中',
 '待つ',
 '法律',
 '挙げる',
 '書生',
 '是',
 '顔色',
 '業',
 'ける',
 '食',
 '場所']
</div></details>



```:matplotlib日本語化ライブラリのインストール
#matplotlib日本語化
!pip install japanize-matplotlib
```

## PCA
```:PCA実行
#　PCA実行
from sklearn.decomposition import PCA

pca = PCA(n_components=2)
values = pca.fit_transform(model.wv.vectors)
#print(values.shape)
#print(values)
```

```:PCA可視化
#PCA可視化
import matplotlib.pyplot as plt
import japanize_matplotlib
import seaborn as sns

sns.set(font="IPAexGothic")
plt.rcParams["font.size"] = 24
plt.tight_layout()
fig = plt.figure(figsize=(30,18))
for value, word in zip(values, model.wv.index2word):
    plt.plot(value[0], value[1], marker='')
    plt.annotate(word, (value[0], value[1]))
    plt.title('PCA on word2vec embeddings', fontsize=30)
    #plt.xlim(**,**)
    #plt.ylim(**,**)
    plt.xticks(fontsize= 20)
    plt.yticks(fontsize= 20)
plt.show()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1275001/7802f1c2-1422-af9f-bf8f-5f0c45f427ca.png)


## t-SNE
```:Python
#@title t-SNE
#@markdown  **<font color= "Crimson">ガイド</font>：perplenxity によってクラスタリングの状況が変化します。例えば 2, 5, 10, 30, 50, 100 など振ってみてください。**

perplexity = 2 #@param {type:"slider", min:2, max:100, step:1}
tSNE_text_font_size = 10 #@param {type:"slider", min:8, max:24, step:1}

from sklearn.manifold import TSNE

#実行
tsne = TSNE(n_components=2, perplexity=perplexity,random_state=0)
np.set_printoptions(suppress=True)
values2 = tsne.fit_transform(model.wv.vectors)

#可視化
fig2 = go.Figure()
for value, word in zip(values2, model.wv.index2word):
  fig2.add_trace(
      go.Scatter(
          x = pd.Series(value[0]),
          y = pd.Series(value[1]),
          mode = 'markers+text',
          text = word,
          textposition="top center"
          )
)
fig2.update_layout(title=dict(text='<b>t-SNE on word2vec embeddings',
                             font=dict(size=18,
                                       color='grey'),
                             xref='paper', # container or paper
                             x=0.5,
                             y=0.9,
                             xanchor='center'
                            ),
                  showlegend=False,
                  font = dict(size = 10),
                  width=900,
                  height=750
                   )
fig2.show()
```
![newplot (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1275001/c4528a29-1aa7-271d-e5dc-79996b8265b9.png)



## UMAP

```:Python
!pip install umap-learn 
```

```:Python
#@title UMAP
#@markdown  **<font color= "Crimson">ガイド</font>：n_neighbors によってクラスタリングの状況が変化します。例えば 2, 5, 10, 30, 50, 100 など振ってみてください。**

n_neighbors = 2 #@param {type:"slider", min:2, max:100, step:1}
UMAP_text_font_size = 10 #@param {type:"slider", min:8, max:24, step:1}

import umap.umap_ as umap
from scipy.sparse.csgraph import connected_components
import plotly.graph_objs as go
import plotly.express as px
import os

os.environ['OMP_NUM_THREADS'] = '1'
os.environ['PYTHONHASHSEED'] = '0'

np.set_printoptions(suppress=True)
values1 = umap.UMAP(n_components=2,n_neighbors=n_neighbors).fit_transform(model.wv.vectors)

#可視化
fig1 = go.Figure()
for value, word in zip(values1, model.wv.index2word):
  fig1.add_trace(
      go.Scatter(
          x = pd.Series(value[0]),
          y = pd.Series(value[1]),
          mode = 'markers+text',
          text = word,
          textposition="top center"
          )
)
fig1.update_layout(title=dict(text='<b>UMAP on word2vec embeddings',
                             font=dict(size=18,
                                       color='grey'),
                             xref='paper', # container or paper
                             x=0.5,
                             y=0.9,
                             xanchor='center'
                            ),
                  showlegend=False,
                  font = dict(size = 10),
                  width=900,
                  height=700
                   )
fig1.show()
```
![newplot.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1275001/467913b5-9da2-3499-97d9-be931a31232e.png)




# 最後に
テキスト文書から抽出したワードをword2vecでベクトル化し、**PCA**、**t-SNE**、**UMAP**に2次元平面上に可視化することができました。
「学問ノススメ」で実行した**PCA**では、プロットがギュッと固まってしまい、うまくクラスタリングできませんでしたが、「tweetデータ」で実行した**t-SNE**、**UMAP**では、ある程度クラスタリングすることができました。

**t-SNE**、**UMAP**はなかなかすごいですね。

Pythonでの**PCA**・**t-SNE**・**UMAP**の実行コードはほとんど同じなので、同時比較が簡単にできます。

2次元平面をクラスタリング単位で確認し、類似ワードを把握 ⇒ 原文確認を行う・・・等、テキスト分析の手掛かりとして使えるのではないかと思います。


# 参考サイト

https://axross-recipe.com/recipes/34

https://data-analytics.fun/2020/03/12/word2vec/

https://qiita.com/hima2b4/items/ca1a4fa440acdedeb3dd

https://analysis-navi.com/?p=3267