書籍「15Stepで踏破 自然言語処理アプリケーション開発入門」をやってみる - 3章Step11メモ「Word Embeddings」

# 内容
[15stepで踏破 自然言語処理アプリケーション入門](https://www.amazon.co.jp/dp/4865941320) を読み進めていくにあたっての自分用のメモです。
今回は3章Step11で、自分なりのポイントをメモります。
個人的には自然言語処理の中でも特に気になる技術です。

# 準備
- 個人用MacPC：MacOS Mojave バージョン10.14.6
- docker version：Client, Server共にバージョン19.03.2

# 章の概要
これまでの特徴抽出法はBoW（やBoWの亜種）で、語彙数に等しい次元数で表していたのに対し、
word embeddingsでは特定の次元数のベクトル(**単語の分散表現**)で表すことができる。
このベクトルは単語の意味を表すかのように情報を持つ。

# 11.1 Word embeddingsとは？
BoWなどのOne-hot表現との比較

|項目 |One-hot表現 |Word embeddings |
|--- |--- |--- |
|ベクトルの次元数 |・語彙数<br>・数万〜数百万になることも |・設計者が決めた固定値<br>・数百程度 |
|ベクトルの値 |特定の次元のみが1で、その他は0 |全ての次元が実数値をとる |

# 11.2 Word embeddingsに触れてみる
## Analogy task
```bash:analogy_sample.pyを実行してみる
$ docker run -it -v $(pwd):/usr/src/app/ 15step:latest python analogy_sample.py
tokyo - japan + france =  ('paris', 0.9174968004226685)
```

`gensim.downloader.load('<word embeddingsモデル>')`は単語に対応する特徴ベクトルを保持している。ただし、モデルによっては英単語のみの場合もある。

この例では`tokyo`と`japan`と`france`という単語を扱っており、単語の意味を踏まえて擬似計算してみると
`(tokyo - japan) + france = (首都) + france ≒ paris`と表現できていることがわかる。

## 応用課題
他の本で読んだ例をいくつか試してみる。

- king - man + woman ≒ queen
- gone - go + see ≒ seen

```bash
king - man + woman =  [('king', 0.8859834671020508), ('queen', 0.8609581589698792), ('daughter', 0.7684512138366699), ('prince', 0.7640699148178101), ('throne', 0.7634970545768738), ('princess', 0.7512727975845337), ('elizabeth', 0.7506488561630249), ('father', 0.7314497232437134), ('kingdom', 0.7296158075332642), ('mother', 0.7280011177062988)]

gone - go + see =  [('see', 0.8548812866210938), ('seen', 0.8507398366928101), ('still', 0.8384071588516235), ('indeed', 0.8378400206565857), ('fact', 0.835073709487915), ('probably', 0.8323071002960205), ('perhaps', 0.8315557837486267), ('even', 0.8241520524024963), ('thought', 0.8223952054977417), ('much', 0.8205327987670898)]
```

## 類義語
Analogy taskでも扱ったように、`model.wv.similar_by_vector(..)`によって得ることができる。

## Word embeddingsの性質
Word embeddingsによって得られる分散表現には以下の性質がある。

- ベクトルの足し算や引き算で、意味の足し算や引き算を表現できる
- 意味が近い単語の分散表現は、ベクトル空間の中でも近くに分布している

## Word embeddingsの種類
|項目 |内容 |
|--- |--- |
|Word2Vec |文章中の連続する数単語に着目して分散表現を得る |
|Glove |学習データ全体における単語の共起頻度情報を利用して分散表現を得る |
|fastText |文字n-gramの分散表現を得て、それらを足し合わせて単語の分散表現とする |

# 11.3 学習済みモデルの利用と日本語対応
Word embeddingsでは前述のように、すでに学習済みのモデルを利用することができる（アプリケーションに組み込む際は、学習済みのモデルを使う際は転移学習してデータにある程度適当させてから利用した方が良さそう）

あと配布されている学習済みモデルを利用するときには、そのモデルのライセンスや利用規約に注意する。

# 11.4 識別タスクにおけるword embeddings
## 分散表現を特徴量として利用
`simple_we_classification.py`は`sec130_140_cnn_rnn/classification/`下にある。tokenize.pyはこのディレクトリには存在しないので、`sec40_preprocessing/tokenizer.py`を利用した。

前章(Step09)からの追加・変更点

- 特徴抽出変更：TF-IDF → Word2Vec

```python
def calc_text_feature(text):
    """
    単語の分散表現をもとにして、textの特徴量を求める。
    textをtokenizeし、各tokenの分散表現を求めたあと、
    すべての分散表現の合計をtextの特徴量とする。
    """
    tokens = tokenize(text)

    word_vectors = np.empty((0, model.wv.vector_size))
    for token in tokens:
        try:
            word_vector = model[token]
            word_vectors = np.vstack((word_vectors, word_vector))
        except KeyError:
            pass

    if word_vectors.shape[0] == 0:
        return np.zeros(model.wv.vector_size)
    return np.sum(word_vectors, axis=0)
```

```bash:実行結果
$ docker run -it -v $(pwd):/usr/src/app/ 15step:latest python simple_we_classification.py
0.40425531914893614
```

通常実装(Step01)：37.2%
前処理追加(Step02)：43.6%
前処理＋特徴抽出変更(Step04)：58.5%
前処理＋特徴抽出変更(Step11)：40.4%

word embeddingsを単純に足して得られた文レベルの特徴量では性能が低い。

------------------------------------
前処理＋特徴抽出変更＋識別器変更(Step06)：61.7%
前処理＋特徴抽出変更＋識別器変更(Step09)：66.0%

## 形態素解析器・前処理の統一
利用するword embeddingsのモデルが**学習されたときに用いられたわかち書きの方法や前処理となるべく同じものを利用時にも再現する**のが望ましい。