Word2Vecで言葉を演算して遊ぶ

## はじめに

こんにちは！[MYJLab Advent Calendar 2022](https://qiita.com/advent-calendar/2022/myjlab)の13日目です。ギリギリです。

昨日は、@Hiiro_Tanio くんの[Pythonで問題を解いてみる](https://qiita.com/Hiiro_Tanio/items/a9805db5a04c3a680f91)って記事でした！
頑張って解いてみようと思ったのですが、問題見てすぐ諦めて考察読みました。中学生に数学を教えてる塾講師のプライドなんて微塵もありませんでした。まだ読んでない人はぜひ考えてから考察読んでみてください！

さて、今日担当の私は、学習済みWord2Vecモデルを使って言葉の演算で遊びたいと思います。機械学習演習の授業でWord2Vecを教わって楽しかったっていう単純な理由です。もっと応用的なこととか、Word2Vecより新しい手法を使ってみようと思っていたのですが、私のおつむでそれをやろうとするとクリスマスを過ぎてしまいそうだったのでまた今度挑戦します、。

## Word2Vecとは
まず、軽くWord2Vecについて説明しておくと、単語をベクトルで表現できる自然言語処理の手法です。コンピュータは単語の意味は理解できないので、似ている文脈で使われる単語は意味が似ていると考えます。
Word2Vecについて大事なことはほとんど[Word2Vecを理解する](https://qiita.com/g-k/items/69afa87c73654af49d36)という記事にまとまっていたのでぜひこちらを読んでください。

## 実装
環境はGoogle colaboratoryです。

```py:
# Googleドライブをマウント
from google.colab import drive
drive.mount('/content/drive')
# gensimをインストール
!pip install -q gensim==4.2.0
```
##### 学習済みモデルの読み込み
今回はFacebookが公開している分散表現を使います。あらかじめドライブに入れてあります。
```py:
import gensim
model=gensim.models.KeyedVectors.load_word2vec_format('/content/drive/MyDrive/cc.ja.300.vec.gz', binary=False)
```
### 似ている言葉探し
私の名字「北川」と似ている言葉トップ10を表示します

```py:
model.most_similar('北川', topn=10)
```
```text:
[('柏木', 0.6539483070373535),
 ('黒木', 0.6474124789237976),
 ('田中', 0.6462180614471436),
 ('山本', 0.6429767608642578),
 ('高橋', 0.6357751488685608),
 ('原田', 0.6354154348373413),
 ('中村', 0.6315122246742249),
 ('岸本', 0.6311200261116028),
 ('佐藤', 0.6300588250160217),
 ('加藤', 0.6266388297080994)]
```
「北川」と同じように名字が10個出てきました。ちなみに加藤さん以外の名字は全員知り合いにいます。

### 似ているか判定

これまでスポーツ観戦は野球しか興味なかったのですが、W杯が始まってからサッカーにも興味が出てきたミーハーなので、野球とサッカーの類似度を調べてみました。
```py:
model.similarity('野球', 'サッカー')
```
```text:
0.67518353
```
まあまあ似てる、って感じですかね。ルールやボールの大きさなどは全然違いますが、部活とかでは2大人気なイメージがありますし、どっちもイケメン多いですし。~~私は野球部派です。~~


あと派閥でいえば犬派か猫派ってありますよね。
残念ながら私はどちら派でもなくて、強いて言うなら実家で飼っている亀派です。どちらの派閥に入ればいいか迷うので、亀がどっちに似ているのか調べてみます。
```py:
model.similarity('犬', '亀')
```
```text:
0.26716912
```
...全然似てませんね。
```py:
model.similarity('猫', '亀')
```
```text:
0.3017495
```
若干ですが猫の方に似てるようなので、これからは猫派を名乗ろうと思います。
ちなみに犬と猫の類似度を調べると
```py:
model.similarity('犬', '猫')
```
```text:
0.65053856
```
亀よりも圧倒的に似てますね。こんなに似てるならもう犬かキャットかで死ぬまで喧嘩する必要はないんじゃないでしょうか。

### 足し算・引き算

さて、本題の演算をやってみます！
Word2Vecで有名な演算をまずは。
　**王様 - 男 + 女**という式です
```py:
model.most_similar(positive=['王様', '女'], negative=['男'])
```
**女王**になるはずです。
```
[('女王', 0.4983246922492981),
 ('ラジオキッズ', 0.4979418218135834),
 ('王さま', 0.49087509512901306),
 ('熟', 0.4839142858982086),
 ('王妃', 0.4745190143585205),
 ('アンナ・レオノーウェンズ', 0.4703938066959381),
 ('タプチム', 0.45472589135169983),
 ('ゲムケン', 0.4503451883792877),
 ('裸', 0.4499165415763855),
 ('お姫様', 0.4497036933898926)]
```
無事、なりました。
これは論理的にすごく正しいですね。
意味を理解せずにこの演算ができていることがとっても面白いですね！

じゃあ感覚的な言葉の演算をしてみようと思います！ただただ私の感覚でやっていきます。


じゃあまずさっき出した野球とサッカーを使います。野球に何かを足し算・引き算してサッカーをトップに出したいと思います。
とりあえずサッカーって手を使ってはいけないスポーツということで、**野球**から**手**を引いてみます。
```py:
model.most_similar(positive=['野球'], negative=['手'])
```
```
[('サッカー', 0.47943028807640076),
 ('ベースボール', 0.4328802824020386),
 ('ラグビー', 0.413484662771225),
 ('ヤクルトスワローズ', 0.3915332853794098),
 ('ファイターズ', 0.38967710733413696),
 ('MLB', 0.3874797224998474),
 ('ホークス', 0.38718676567077637),
 ('阪神タイガース', 0.3868173360824585),
 ('Ｊリーグ', 0.3863668143749237),
 ('球技', 0.3771236836910248)]
```

...できてしまいました。まさかこの段階でトップにサッカーが来るとは思いませんでした。

他にもスポーツをいくつかやってみます。
```py:
model.most_similar(positive=['バスケットボール','アタック'], negative=['シュート'])
```
```
[('バレーボール', 0.5214961767196655),
 ('ハンドボール', 0.4445866644382477),
 ('バドミントン', 0.4291817247867584),
 ('ホッケー', 0.42893993854522705),
 ('バスケ', 0.41954880952835083),
 ('ソフトボール', 0.41509339213371277),
 ('アメリカンフットボール', 0.40720057487487793),
 ('ラグビー', 0.3954552710056305),
 ('マラソン', 0.3911930024623871),
 ('チアリーディング', 0.39098966121673584)]
```
狙い通り、**バスケットボール - シュート + アタック = バレーボール**となりました。

```py:
model.most_similar(positive=['テニス','台'], negative=['外'])
```
```
[('卓球', 0.5025442242622375),
 ('ラケット', 0.4774833619594574),
 ('バレーボール', 0.4445890486240387),
 ('バドミントン', 0.43084633350372314),
 ('ビリヤード', 0.42351725697517395),
 ('ダブルス', 0.423374742269516),
 ('ゴルフ', 0.4143419563770294),
 ('サーブ', 0.4079762101173401),
 ('台数', 0.4044944643974304),
 ('バスケ', 0.3993244469165802)]
```
こちらも狙い通り**テニス**から**外**を引いて**台**を足すと**卓球**になりました。
これを書きながら、外を引くより**コート**を引く方がなんか良さそうって思ったので試すと、
```
[('卓球', 0.4684402644634247),
 ('台数', 0.4397422671318054),
 ('瞻星', 0.4388388991355896),
 ('堡灯', 0.42698201537132263),
 ('北鈴蘭', 0.40984904766082764),
 ('ビリヤード', 0.3996897041797638),
 ('灣人', 0.39569970965385437),
 ('ロットリングミリペントレース', 0.39085444808006287),
 ('馮寄', 0.3892287611961365),
 ('灣大', 0.3856922388076782)]
```
若干スコアが下がって、しかもトップ10の中に読めない単語が急に増えました、、。難しい。

スポーツには飽きてきたので方向性を変えます。
今日ラジオに出演したので「ラジオ」って出したいですね。相模原のコミュニティFMに時々出たりディレクターしたりしてるので良かったら聴いてください(宣伝)。ついでに、部活でYouTubeにラジオあげてたりするので[チャンネル登録](https://www.youtube.com/@user-mw1kc3br5x)してもらえると喜びます(めちゃくちゃ宣伝)。

```py:
model.most_similar(positive=['テレビ', '音楽'], negative=['映像'])
```
```
[('ラジオ', 0.5526846647262573),
 ('TV', 0.5316915512084961),
 ('ＴＶ', 0.5207523703575134),
 ('番組', 0.5064358711242676),
 ('ドラマ', 0.4719764292240143),
 ('アニメ', 0.47106385231018066),
 ('クラシック', 0.46768683195114136),
 ('民放', 0.46599945425987244),
 ('テレビ局', 0.4621303975582123),
 ('ジャズ', 0.45537909865379333)]
```
無事**ラジオ**出せました！


あと今週はM-1グランプリがありますね！毎年敗者復活戦からテレビにかじりついて見てます。
**漫才**を出してみます。
```py:
model.most_similar(positive=['コント', 'マイク'], negative=['小道具'])
```
```
[('漫才', 0.4539731442928314),
 ('ザ・パンチ', 0.426846444606781),
 ('漫談', 0.4087786078453064),
 ('マイクオフ', 0.4075491428375244),
 ('マイクロホン', 0.39903736114501953),
 ('オフマイク', 0.39698871970176697),
 ('スピーカー', 0.3960576057434082),
 ('グダグダトーク', 0.39351633191108704),
 ('オンマイク', 0.38938379287719727),
 ('ボケツッコミ', 0.3892975151538849)]
```
**コント - 小道具 + マイク = 漫才**の式で出せました！マヂラブは漫才なのか論争もこの定義なら決着つきそうですね。8番目にグダグダトーク、10番目にボケツッコミがありますが、これが漫才の本質なのかもしれないですね。

## おわり
言葉の演算にもようやく飽きてきました。もっとスコアが上がるような式や、面白い式があれば教えてください！

## 参考文献

https://qiita.com/g-k/items/69afa87c73654af49d36