【NLP入門】「フランス」－「パリ」＋「東京」＝「日本」が出来ました♪

「フランス」－「パリ」＋「東京」＝「日本」ってできるのが凄いって記事はたくさんみるけど、実はコードの実物を見たことがないので、一度やってみたかった。
ということで、参考の①がベクトルの計算を示していて、さらにリンク先の参考②にそのものズバリありました。
【参考】
①[word2vecでベクトルから単語を出力する](https://qiita.com/iss-f/items/aec567ee5c79464413dc)
②[How to find the closest word to a vector using word2vec](https://stackoverflow.com/questions/32759712/how-to-find-the-closest-word-to-a-vector-using-word2vec)

```
>>> model.most_similar(positive=['woman', 'king'], negative=['man'])
[('queen', 0.50882536), ...]
```
###問題意識
しかし、ここで普通は疑問持ちますよね。
つまり、上記の参考②のコメントにもありますが、ベクトル計算なので、

```
「woman」+「king」-「man」?=「woman」-「man」+「king」
```
なのか。。。
ということで、**ここをコードを記載しながら確認していきたいと思います**
※おまけにベクトルの基礎を書いています
###簡単なコード
上記のコマンドラインのコードをコードで書くと以下のようになります。
ここで、model.most_similar(positive=['フランス', '東京'], negative=['パリ'])の引数については、おまけに書いた通りです。

```py
from gensim.models import FastText
model=FastText.load_fasttext_format('model300.bin')
print("model.most_similar(positive=['フランス', '東京'], negative=['パリ'])",model.most_similar(positive=['フランス', '東京'], negative=['パリ']))
```
上記のコードで以下の出力が得られました。
つまり、これでとりあえず、
「フランス」＋「東京」－「パリ」＝「日本」
となったといえます。
※もっともスコアは次点の東京北というわけわからないものと僅差ですが、。。

```
$ python3 wv_calc.py
model.most_similar(positive=['フランス', '東京'], negative=['パリ'])
 [('日本', 0.5296447277069092), 
('東京北', 0.5231943130493164), 
('東京窯業', 0.5120759010314941), 
('東京産業', 0.5057594776153564), 
('東京板橋区', 0.5037075877189636), 
('東京青果', 0.500629723072052), 
('東京鐵鋼', 0.49898386001586914), 
('東京夜曲', 0.4987906217575073), 
('東京多摩', 0.48534345626831055), 
('東京短資', 0.48452967405319214)]
```
昨夜のまとめでは、
**「男性－女性」を「おじ」や「王」に足すと、「おば」や「女王」になる**
と書いたけど、これ嘘でした<(_ _)>
以下のとおり、
「女性」＋「王」－「男性」＝「王妃」
でした。
「男性だと王だけど、女性だとなんて呼ぶんだっけ？」
てな意味ですね。

```
$ python3 wv_calc.py
model.most_similar(positive=['女性','王'], negative=['男性']) 
[('王妃', 0.6322572231292725), 
('国王', 0.5768866539001465), 
('王位', 0.5637850165367126), 
('王家', 0.5609192252159119), 
('王女', 0.5540141463279724), 
('妃', 0.55034339427948), 
('グンヒルド', 0.5477126836776733), 
('タワナアンナ', 0.5475058555603027), 
('王テ', 0.5465204119682312), 
('王達', 0.5429999232292175)]
```
ちなみに、
「男性」＋「王」－「女性」＝「王位」
でした。これもなるほどですね。

```
$ python3 wv_calc.py
model.most_similar(positive=['男性','王'], negative=['女性']) 
[('王位', 0.6317461729049683), 
('国王', 0.5961508750915527), 
('先王', 0.562886655330658), 
('大王', 0.5559142827987671), 
('王家', 0.5353712439537048), 
('シセナンド', 0.5245059728622437), 
('王シ', 0.5229213237762451), 
('トゥクルティ・ニヌルタ', 0.5202610492706299), 
('王国', 0.5185250043869019), 
('ア ッシュール・ニラリ', 0.516586184501648)]
```
同じように、
「男性」＋「おば」－「女性」＝「おじ」
「女性だとおばだけど、男性だとなんていうんだっけ？」
という感じですね。

```
$ python3 wv_calc.py
model.most_similar(positive=['男性','おば'], negative=['女性']) 
[('おじ', 0.5055488348007202), 
('おおがい', 0.4829249680042267), 
('おつき', 0.4783822298049927), 
('おおかわら', 0.47714635729789734), 
('ねだ', 0.47638678550720215), 
('たかべ', 0.47350090742111206), 
('のり一', 0.46550655364990234), 
('しらがやま', 0.45829230546951294), 
('みやもり', 0.45756596326828003), 
('ひさの', 0.4558151364326477)]
```
###ベクトル演算でやってみる
おまけのとおり、これは以下のように計算できる。
例えば、フランス、東京、そしてパリのそれぞれをベクトルに変換して計算して、vectorに代入する。そして、このベクトルに対応する類似語をmodel.most_similar( [ vector ], [], 10)で抽出している。

```py
vector=model.wv['フランス']+model.wv['東京']-model.wv['パリ'] 
word = model.most_similar( [ vector ], [], 10)
```
コード全体は以下の通り。

```py
from gensim.models import FastText
model=FastText.load_fasttext_format('model300.bin')
print('フランス', '東京', 'パリ')
vector=model.wv['フランス']+model.wv['東京']-model.wv['パリ'] 
word = model.most_similar( [ vector ], [], 10)
print(word)
```
結果は上記と同じような結果が得られた。
ただし、一つ目に入力として使った東京が入っている。
そして、この方法だと、演算の順番を変えられて文字通り
「フランス」－「パリ」＋「東京」
`vector=model.wv['フランス']-model.wv['パリ'] +model.wv['東京']`
として、結果として「日本」が得られる。

```
$ python3 wv_calc.py
フランス 東京 パリ
[('東京', 0.6973214149475098), 
('日本', 0.5208398699760437), 
('東京北', 0.5097677707672119), 
('東京窯業', 0.49487781524658203), 
('東京産業', 0.4906602203845978), 
('東京板橋区', 0.4869083762168884), 
('東京青果', 0.48689910769462585), 
('東京 鐵鋼', 0.48525524139404297), 
('東京夜曲', 0.4844823479652405), 
('東京短資', 0.4729866087436676)]
```
もう一つ、同じような計算だが、上記のベクトルを使って、類似語は以下のようにも求められる。
`word = model.wv.similar_by_vector(vector, topn=10, restrict_vocab=None)`
結果も以下のとおり、全く同じものが得られました。

```
[('東京', 0.6973214149475098), 
('日本', 0.5208399295806885), 
('東京北', 0.5097677707672119), 
('東京窯業', 0.4948778450489044), 
('東京産業', 0.4906602203845978), 
('東京板橋区', 0.4869083762168884), 
('東京青果', 0.48689913749694824), 
('東京鐵鋼', 0.48525527119636536), 
('東京夜曲', 0.4844823479652405), 
('東京短資', 0.4729866087436676)]
```
【参考】
[gensimでword2vec](https://blog.imind.jp/entry/2019/05/13/235035)
上記の参考にはdoc2vecで文章と単語と演算してベクトル求めて、類似語を抽出しています。
fasttextでは文章のベクトルはこのままでは関数を持っていないようです。
ということで、以下で実施してみました。
`vector = model.wv['現代の地球の支配者は人間です'] - model.wv['人間'] + model.wv['コンピュータ']`
**未来は無停止コンピュータが支配者なんだな。。**

```
[('無停止コンピュータ', 0.7509822845458984), 
('日経コンピュータ', 0.7296304106712341), 
('ザ・コンピュータ', 0.7294954061508179), 
('福井コンピュータ', 0.7269031405448914), 
('クリーンコンピュータ', 0.721671462059021), 
('コンピュータ', 0.7210285663604736), 
('アナログ・コンピュータ', 0.7169326543807983), 
('スーパコンピュータ', 0.7160074710845947), 
('コンカレント・ コンピュータ', 0.7148932814598083), 
('アポロコンピュータ', 0.7119718790054321)]
```
`vector = model.wv['現代の地球の支配者は人間です'] - model.wv['人間'] + model.wv['恐竜']`
**昔は、ゴジラサウルスが地球を支配していたんだな。。。**

```
[('恐竜', 0.8012865781784058), 
('ゴジラサウルス', 0.6655558943748474), 
('ウラガサウルス', 0.6608412861824036), 
('ウルト ラサウルス', 0.6453453302383423), 
('フタバサウルス', 0.6437132358551025), 
('オウラノサウルス', 0.6432296633720398), 
('フクイサウルス', 0.6426962614059448), 
('メガプノサウルス', 0.6411823630332947), 
('アトラサウルス', 0.6398862600326538), 
('カロノサウルス', 0.6397913098335266)]
```
###まとめ
・「フランス」－「パリ」＋「東京」＝「日本」が出来た
・ワードのベクトル計算ができた

・文書のベクトル計算もやってみようと思う
・学習する単語を精製しようと思う
###おまけ
代数的には以下で証明できるけど。。。
【参考】
・[ベクトルの公式一覧（計算・内積・三角形の面積・共線条件）](https://rikeilabo.com/vector-formula-list)

【交換法則

```math 
\overrightarrow{a} + \overrightarrow{b}= \overrightarrow{b} + \overrightarrow{a}
```

【結合法則】

```math 
　(\overrightarrow{a} + \overrightarrow{b})+\overrightarrow{c} =\overrightarrow{a} +(\overrightarrow{b} +\overrightarrow{c} )
```
###gensimでベクトル類似度計算
[class gensim.models.fasttext.FastText(...)](https://radimrehurek.com/gensim/models/fasttext.html#gensim.models.fasttext.FastText)
[most_similar(**kwargs)](https://radimrehurek.com/gensim/models/fasttext.html#gensim.models.fasttext.FastText.most_similar)
[similar_by_vector(**kwargs)](https://radimrehurek.com/gensim/models/fasttext.html#gensim.models.fasttext.FastText.similar_by_vector)

gensimでのwords類似度計算が以下にあります。
[most_similar(positive=None, negative=None, topn=10, restrict_vocab=None, indexer=None)](https://radimrehurek.com/gensim/models/keyedvectors.html#gensim.models.keyedvectors.WordEmbeddingsKeyedVectors.most_similar)

```
Find the top-N most similar words. Positive words contribute positively towards the similarity, negative words negatively.

This method computes cosine similarity between a simple mean of the projection weight vectors of the given words and the vectors for each word in the model. The method corresponds to the word-analogy and distance scripts in the original word2vec implementation.

Parameters:	
positive (list of str, optional) – List of words that contribute positively.
negative (list of str, optional) – List of words that contribute negatively.
topn (int or None, optional) – Number of top-N similar words to return, when topn is int. When topn is None, then similarities for all words are returned.
restrict_vocab (int, optional) – Optional integer which limits the range of vectors which are searched for most-similar values. For example, restrict_vocab=10000 would only check the first 10000 word vectors in the vocabulary order. (This may be meaningful if you’ve sorted the vocabulary by descending frequency.)
Returns:	
When topn is int, a sequence of (word, similarity) is returned. When topn is None, then similarities for all words are returned as a one-dimensional numpy array with the size of the vocabulary.

Return type:	
list of (str, float) or numpy.array
```
そもそも、gensimでのベクトル類似度計算が以下にあります。
[model.wv.similar_by_vector(vector, topn=10, restrict_vocab=None)](https://radimrehurek.com/gensim/models/keyedvectors.html#gensim.models.keyedvectors.WordEmbeddingsKeyedVectors.similar_by_vector)

```
Find the top-N most similar words by vector.
Parameters:
vector (numpy.array) – Vector from which similarities are to be computed.
topn ({int, False}, optional) – Number of top-N similar words to return. If topn is False, similar_by_vector returns the vector of similarity scores.
restrict_vocab (int, optional) – Optional integer which limits the range of vectors which are searched for most-similar values. For example, restrict_vocab=10000 would only check the first 10000 word vectors in the vocabulary order. (This may be meaningful if you’ve sorted the vocabulary by descending frequency.)
Returns: Sequence of (word, similarity).
Return type: list of (str, float)
```