Word2Vecとか単語の分散表現とかよくわからないので skip gram のモデルから理解を試みてみた

# 参考文献

1. [Word2Vec (skip-gram model): PART 1 - Intuition. | Medium](https://towardsdatascience.com/word2vec-skip-gram-model-part-1-intuition-78614e4d6e0b)
2. [【論文シリーズ】CBOWとSkip-gramについて | Qiita, To_Murakami](https://qiita.com/To_Murakami/items/6bd5638689166ec4821c)

1の記事を全面的に参考にしています。
 
 
# はじめに

自然言語界隈を見ていると Word2Vec のことは良く目にします。見聞きする様々な手法のベースとして参照されているようにも見受けられ、重要そうだというのはわかっていました。

雰囲気でライブラリを調べて、使ってみて...この繰り返しではスキル習得の効率が悪いし、結局目指すモノを作るためにスピードが出ないと考え始めました。そこで、まずは Word2Vec を理解しようと色々調べてみました。...しかし、巷の入門的な記事では少々不足がありました。概要は掴めるものの、結局具体的なイメージはわからない。

自分なりに整理すると、理解するためには以下のような障害があると考えました。

- 用語の理解、およびバリエーションによる混乱
- モデルの具体的な In/Out をイメージできない

参考文献の記事を通読することで理解は格段に進んだように思います。そこで、乱雑ではありますが、学習ログとして書きなぐった内容を（雑に）整形して公開してみることにします。少し前の自分が読みたかった内容、というイメージで加筆修正していますのである程度対象読者は選ぶと思いますが、似たような状況の方に対して何かしらのヒントになれば。

なお、私自身も勉強の真っ最中なので、本記事は**誤った解釈に基づく記述が含まれている**可能性が大いにあります。詳しい方、もし誤りがあればコメント欄などで訂正いただけると助かります。これを読んでくれた方も、ここで述べている内容を100%信用に足るものとして鵜呑みにしないようお願いします。

# 前提

まず、この記事では「単語のベクトル表現（Word2Vec）」に関する一手法である "Skip gram" モデルに関する理解に焦点を当てています。CBOWには特に触れません。

対象読者として以下のような想定をします。

- 数学ガチ勢じゃないけど何か機械学習アプリ作ってみたい、と思っている機械学習勉強中の人
- 行列計算に免疫がある（Webサイト見つつ理解できる・もしくは理解しようとする意思がある程度。できれば内積は知ってて欲しい）
- ニューラルネットワークがどういうものかなんとなく把握している（重みがどうのとか、最適化が云々とか聞いて雰囲気がわかる程度）

行列演算は大昔やったなぁ、くらいの記憶で私自身も割と理解が怪しいです。
ニューラルネットワークに関しては詳細なロジックまで理解できている必要はないです。私もさほど理解していません。トレーニングデータを流して最適化、という流れがわかっていればOKかと。

原文の記事も、そのレベルでおそらく読き切れると思います。

# 本題

Skip gram に関する説明です。ですます調じゃなくなってますが、ベースが個人の勉強メモであるゆえ、ご容赦ください。

## Skip gram モデル

Skip-gramモデルは、1つの中間層（隠れ層）をを持つニューラルネットワークである。

![](https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.amazonaws.com%2F0%2F127228%2F8e02227c-c5f3-4090-85fb-8fd6e5e2f23b.jpeg?ixlib=rb-1.2.2&auto=format&gif-q=60&q=75&s=4079313e34902a858c7b6437204ac512)

※参考文献[2] より引用 ... https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.amazonaws.com%2F0%2F127228%2F8e02227c-c5f3-4090-85fb-8fd6e5e2f23b.jpeg?ixlib=rb-1.2.2&auto=format&gif-q=60&q=75&s=4079313e34902a858c7b6437204ac512

skip gram は「ある単語が入力に与えられたとき、（ボキャブラリー中の）他の単語がその "周辺" である確率」を学習するモデルである。

しかし、ゴールはモデルそのものの構築ではなく、**中間層の重み**を獲得することにある。

1. ドキュメントの単語が与えられたとき、その周辺の単語をランダムに選択する
2. すると、Skip gramネットワークは「（ボキャブラリー中の）任意の単語」を与えたときに「その周辺に来る単語の確率」を教えてくれる
3. この確率は、「入力単語に対して関連のある単語」を知ることができる。（in/outどちらの単語もボキャブラリーに存在すること）

WIndows size = 2 で、入力文章「The quick borwn fox jumps oevr the lazy dog.」をトレーニングの入力に突っ込んでみた図が以下。
注意点は、この文章に見えるテキストはあくまで「単語の列」であり、自然言語としての1文字列ではないこと。これを日本語に置き換えて読むならば、形態素解析するなりして予め「単語の列」の状態になっている状態をイメージする必要がある。

![](https://miro.medium.com/max/1772/0*0m03CiSHWl4f2dae.png)

※参考文献[2] より引用

上図の右側の _word pair_ がトレーニングデータとなる。ペアの左が入力単語、右がその周辺単語（=正解ラベル）となる。

※この表現では「前後何番目の周辺なのか？」という情報が失われている。要は、このモデルは厳密な順序関係の情報を意図的に削ぎ落として、Windows size = 2 の範囲内を「周辺」と定義しモデルを構築しているということ。

実物のモデルでは、「単語」の One Hot 表現を用いる。トレーニングに用いた全ての文章から得られたボキャブラリーを10,000語として図解したのが以下の図。

![](https://miro.medium.com/max/3138/0*FTfdlZ7yDBoQ8c9W.png)

※参考文献[2] より引用

入力ベクトルは単語の One Hot 表現。出力層のノードはボキャブラリーの数だけ存在し、ノードの番号がボキャブラリー中の各単語に対応している。Softmaxとあるので出力値は0.0 - 1.0の値域をとる。これを確率とみなす。つまり、出力層の i 番目の出力は、ボキャブラリー中の i 番目の単語が入力単語の周辺である確率を示している。

※注意：図解の便宜上と思われるが、実際の Input Vector は **行ベクトル** である。でないとその後の行列演算ができない。

## 行列で考える

構成要素|行x列|説明
:---|:---|:---
入力ベクトル | 1 x 10,000 | 10,000次元はボキャブラリー中のそれぞれの単語。One Hot 表現
中間層 |10,000 x 300 | 300は中間層のノード数。ちゃんと読んでないけど根拠にした事例のある数字らしい
中間層の出力 | 1 x 300 | 300次元を持つ特徴ベクトルと考えて良い。出力層に対する input になる
出力層 | 300 x 10,000 | 10,000は最終的に出力する数。ボキャブラリーのサイズと一致させる。
出力ベクトル | 1 x 10,000 | 各単語に対する確率がはいったベクトル。それぞれの値を「単語(i)が入力単語の周辺である確率」と見なしている|

図にするとこんな感じ。中間層と出力層をイメージするために書いてみた。
※実際にはこの図の右側に Softmax を適用する処理が必要なので、モデルの全体像ではない。

![IMG_20200320_224246.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/78417/91cc4c75-2cea-6006-bf98-94278cc3a5dc.jpeg)


このとき、中間層に着目する。中間層は、 10,000行 x 300列 の重み行列である。

これを入力単語（の One Hot 表現に対する）Lookup tableとして使う。ボキャブラリーのi番目の単語（単語[i]とする）が与えられたとき、中間層の[i]行目の列 (1 x 300) が **単語[i]の分散表現** である。

# ハマりポイントを振り返る

自分がハマっていた箇所を振り返ります。

### 用語の理解に関する混乱

いくつか、1つの意味に対して複数の用語のバリエーションがある場合があります。記事によってどの言葉を使っているのかは異なりますので、それをまず意識する必要があります。

当初一番混乱したのが「単語のベクトル表現」でした。これと同様の意味を持つ用語は複数あります。例えば次のような表記です：

- 分散表現（Word Vector）
- 埋め込み表現（Word Embedding）

記事を書く人によって表記がバラけるので、非常に混乱しました。

あとは、「文章」や「単語」に関する記述はそのスコープや単位・形式を意識すると良いかもしれません。

データの粒度に注目するとおおよそ次のような区別があります。

- 学習対象とするデータ全体。「文書」の集合
- ↑に含まれる各々の文書（単語のリストとして表現される）
- 文書に含まれる各単語

今述べているのがどの粒度の話なのかは意識していると良いように思います。

あと、一見「自然言語の文章」のように図示されている文章でも、よく読むとあくまで「単語のリスト」を見やすく並べただけでデータ形式としては文字列のリストを意図している場合があります。そのへんも注意すると良いでしょう。

### 応用タスクにおける利用シーンとの混同

応用タスクをやろうとすると、前処理である形態素解析やストップワードの除去、トレーニング時点で登場しなかった未知の単語への対処方法、モデル更新、などなど考えるべきスコープは広がります。

各種ライブラリを触りつつも、ずっと片隅で 

> _「トレーニング時になかった単語が入ってきたらどうすんの？このモデルってそこに回答出せるの？」_

という引っかかりがありました。これは現実世界での応用（つまりSlack botでの活用シーン）を念頭に置いた疑問だったわけですが、ここをモデルが扱う問題スコープと混同していたフシがあります。

こうした混乱を紐解くには、やはり各要素の入出力を理解するのが手っ取り早いやり方であろうと思います。私もこの記事を書く前は Word2Vec の Input/Output をあまり理解できていませんでした。記事の前置きで行列やベクトルの演算について触れているのはそういう理由からです。

※なお、上述した疑問に対する回答ですが、 Word2Vec の枠組み単体では不可能です。そもそものモデルに対する入力形式が「ボキャブラリーのサイズ分の次元を持った、入力単語のOneHot表現」なので、トレーニングの時点でボキャブラリーに含まれない単語は入力形式として扱うこと自体できません。この問題に対する回答はまだ勉強中なので解説できませんが、モデル更新による対応あたりが正解なのかもしれません。あるいは、単語の One Hot 表現に "Unkonown" という特別な時限を追加して、未知の単語を全てそこに分類するか。


## 補足

参考文献[1] ではこの辺までで前半をクリア、という感じです。

他にも

- ステミングの効果があるよ的な話
- 実際の応用タスクにまつわる課題とその対策アプローチ
	- サブサンプリング
	- ネガティブサンプリング

といったトピックに言及があります。

そのへんはいったん書きませんが、いずれまとめて加筆修正したいと思います。