【機械学習】Word2Vecで単語間の類似度を測定する

##1.はじめに
この記事では、自然言語処理の最も基本的？なことである単語間の類似度を測定するということを行います。
というのも、私が次に作ろうと思っているwebアプリケーションで自然言語処理の要素を少し入れようと思っているからです。
そのため、学んだことをまとめる、他の人に自然言語処理について分かりやすく伝えるために、この記事を書きます。


##2.Word2Vecとは
Word2Vecとは自然言語処理でよく用いられる、単語の意味をベクトル化するツールのことを言います。
単語の意味をベクトル化するとは、どういうことでしょうか？
例えば、「小学校」、「中学校」、「コンビニエンスストア」という３つの単語があったとします。
これら３つの単語を３次元のベクトルとして、次のように表されるとします。
「小学校」 = [0.9, 0.8, 0.2]
「中学校」 = [0.9, 0.7, 0.3]
「コンビニエンスストア」 = [0.1, 0.3, 0.8]
ベクトルの要素はそれぞれ、「教育」、「大きさ」、「利便性」を表しているとします。(意味も数字も適当です)
このとき、「小学校」と「中学校」のベクトルの数値が非常に似ているということに気づきます。
これは、「小学校」と「中学校」は似たもの同士であるという私たちの常識から考えても正しいと思われます。
「小学校」と「コンビニエンスストア」はどうでしょうか？
建造物であるということに共通点はありますが、２つが似ているとはいえないですよね。
そのため、「小学校」と「コンビニエンスストア」をベクトルで表すと、似た値にはなりません。
このように、単語をベクトル表現することにより、数値的に単語同士が似ているかどうかを判断することができます。
これをさらに高次元化(200次元など)にすることで、より精密に単語間の類似度を算出することが可能です。
また、単語をベクトル化することにより、単語の足し引きもできるようになります。
例えば、「フランス」 - 「パリ」　＝　「日本」 - 「東京」
といった計算を行うことができます。

##3.学習済みモデルのダウンロード,単語間の類似度測定
Word2Vecのモデルを作成するためには、大量の文章を取得して、それを使いやすいように処理して、学習して、と大きな手間と時間が必要になります。
なので、今回はネット上で学習済みのWord2Vecモデルをダウンロードして使用します。
今回使用するのは、白ヤギコーポレーションさんが作成した学習済みWord2Vecモデルです。
この学習済みモデルでは、登録されている単語数が100万語と非常に多いため、使い勝手が良いです。
(登録単語数が5万語のものも試したのですが、「くじ引き」などの日常で使う可能性のある単語も登録されていないということがありました。)
→白ヤギコーポレーションが提供している学習済みWord2Vecモデルのダウンロードは<a href='https://aial.shiroyagi.co.jp/2017/02/japanese-word2vec-model-builder/'>こちら</a>のページから
学習済みモデルをダウンロードして、保存したら、以下のようなコードを記述することで学習済みWord2Vecモデルを使用することができます。

```python:word2vec.py
import gensim

# PATHは学習済みファイルの保存場所
model = gensim.models.Word2Vec.load(PATH)
```
次にこのモデルを使用して、簡単にできることを紹介していきます。

```python:word2vec2.py
# 登録されている単語のベクトルを表示する
a = model.wv['日本']
print(type(a))
print(a.shape)
print(a)

'''
<class 'numpy.ndarray'>
(50,)
[-0.08019581 -0.10632093  0.14115061  0.08310905 -0.00913872  0.04414612
 -0.11600631 -0.35167393  0.04924094  0.07659911  0.07906264  0.02693196
  0.03977293  0.19168071 -0.16376053 -0.01040754 -0.13442372  0.20567688
 -0.04850557 -0.22208223  0.03354846 -0.18884456 -0.00623878 -0.12064678
 -0.01325125  0.02779626  0.02936541  0.06843039  0.11489733  0.07094222
  0.14459857 -0.12487161  0.03558656  0.41905314 -0.15021588 -0.12890314
  0.04594489 -0.28921965  0.15588439 -0.01789199 -0.2866291  -0.1415011
  0.057166   -0.19496612  0.09121747  0.01317389 -0.0359673   0.16124834
 -0.03846167 -0.06273678]
'''

# 単語の類似度の高いものを上から１０個表示する
print(model.wv.most_similar('新島襄'))

'''
[('同志社', 0.865658700466156),
 ('内村鑑三', 0.8353294730186462),
 ('女学校', 0.8297003507614136),
 ('青山学院', 0.8282883763313293),
 ('同志社英学校', 0.8270008563995361),
 ('数学教師', 0.8200982809066772),
 ('植村正久', 0.818874180316925),
 ('英学', 0.8070827722549438),
 ('英語教師', 0.8043112754821777),
 ('明治学院', 0.8034539222717285)]
'''

# 複数の単語で類似度の高いものを上から１０個表示する
print(model.wv.most_similar(['滋賀県', 'スーパー']))
'''
[('イトーヨーカドー', 0.7843093872070312),
 ('ゆめタウン', 0.7779777646064758),
 ('パチンコホール', 0.7674244046211243),
 ('JA全農', 0.764684796333313),
 ('栃木', 0.7576559782028198),
 ('グルメシティ', 0.7515535354614258),
 ('農産物直売所', 0.7511919736862183),
 ('三井アウトレットパーク', 0.7510249018669128),
 ('埼玉', 0.7485976815223694),
 ('平和堂', 0.7462714910507202)]
'''

# 単語の意味を足し引きして、類似度の高いものを上から１０個表示する
print(model2.wv.most_similar(positive=['エンジニア'], negative=['プログラミング']))
'''
[('工場長', 0.5950774550437927),
 ('不動産王', 0.5934329032897949),
 ('大物', 0.5812276005744934),
 ('総支配人', 0.5657695531845093),
 ('重役', 0.5622079968452454),
 ('日系', 0.5560007095336914),
 ('ショウ・ブラザーズ', 0.5554418563842773),
 ('大富豪', 0.5457643270492554),
 ('エンツォ・フェラーリ', 0.5454198718070984),
 ('フランク・コステロ', 0.5440135598182678)]
'''

# ２つの単語の類似度を算出する
print(model2.wv.similarity('小学校', '中学校'))
'''
0.92630494
'''
```

この中で出てくる類似度というのは、cos類似度のことを言っています。
cos類似度とは簡単にいうと、２つのベクトルがどれだけ同じ方向を向いているか(類似しているか)を数値化したものです。cos類似度が0のときには、類似度が低く、1のときには類似度が低いことを示しています。
cos類似度は以下のような計算式で表されます。

<img width="477" alt="スクリーンショット 2020-01-20 21.41.29.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/473300/e116635a-c368-01f0-9a26-00f00dc22e0d.png">

この式の分母は、ベクトルqとdの大きさ(ノルム)をそれぞれ掛けたものであり、
分子はベクトルqとdの内積になっています。
そのため、上のソースコードの類似度を算出するsimilarityは次のようなコードで表すことができます。

```python:cos-similarity.py
import numpy as np

def cal_cos_similarity(q, d):
    return np.dot(q, d) / np.linalg.norm(q) * np.linalg.norm(d)
```

##4.終わりに
今回はword2Vecを用いた自然言語処理の簡単な説明と実装を行いました。
学習済みモデルを使用したため、数行の簡単なコードで単語間の類似度を算出することができました。
これからはwebアプリケーションに実装するに当たって、自然言語処理の学習部分や自然言語処理でできることの限界について学びたいと思います。

##参考文献
<a href='http://www.cse.kyoto-su.ac.jp/~g0846020/keywords/cosinSimilarity.html'>・cos類似度について</a>
<a href='https://qiita.com/yoppe/items/512c7c072d08c64afa7e'>・Word2Vecを使った文章間の類似度算出（簡易版）</a>
<a href='https://www.slideshare.net/okamoto-laboratory/word2vec-70898693'>・数式からみるWord2Vec</a>
<a href='https://qiita.com/omuram/items/6570973c090c6f0cb060'>・Word2Vecの学習済み日本語モデルを読み込んで使う</a>
<a href='https://www.randpy.tokyo/entry/word2vec_skip_gram_model'>・word2vec（Skip-Gram Model）の仕組みを恐らく日本一簡潔にまとめてみたつもり</a>