CUBE SUGAR CONTAINER

技術系のこと書きます。

Python: gensim を使った Word Embedding の内省的評価について

以下の書籍では、Word Embedding の評価方法として内省的評価 (intrinsic evaluation) と外省的評価 (extrinsic evaluation) という 2 つのやり方が紹介されている。 内省的評価では、人間が判断した単語間の類似度や、単語の持つ意味を使ったアナロジーを、Word Embedding が適切に表現できているかを評価する。 それに対し、外省的評価では Word Embedding を応用する先となる最終的な目的を表したタスクを使って評価する。

今回は、gensim に用意されている内省的評価の仕組みがどんなことをやっているのか気になって調べた内容を書いてみる。

使った環境は次のとおり。

$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.14.6
BuildVersion:   18G4032
$ python -V
Python 3.7.7

下準備

はじめに、gensim をインストールしておく。

$ pip install gensim

続いて、評価したい Pretrained Word Embeddings を用意する。 今回は Facebook の公開している fastText を選んだ。 なお、評価用データとして gensim に組み込みで用意されている英語のコーパスを使うので、ひとまず英語を使ったものにする。 このファイルはサイズが 6GB ほどあるので結構な時間がかかる。

$ wget https://dl.fbaipublicfiles.com/fasttext/vectors-wiki/wiki.en.vec

ダウンロードできたら Python のインタプリタを起動しておく。

$ python

先ほどダウンロードした Pretrained Word Embeddings を gensim から読み込む。

>>> import gensim
>>> model = gensim.models.KeyedVectors.load_word2vec_format('wiki.en.vec', binary=False)

ちなみに、この操作にもかなりの時間 (数分以上) がかかるので気長に待つ。

内省的評価に使うデータのフォーマットについて

gensim には、内省的評価をするための API として単語間類似度を使ったものとアナロジータスクがサポートされている。 そして、英語のコーパスに関してはラベル付きデータも組み込みで提供されている。 ひとまず、どういったフォーマットになっているか紹介しておく。

単語間類似度

はじめに単語間類似度について。 単語間類似度は、2 つの単語について、どれだけ類似しているかを人間の主観で評価したもの。 単語間類似度を評価するためのデータとして、gensim では wordsim353.tsv というデータが組み込みで提供されている。

ファイルの場所は次のようにして gensim.test.utils.datapath() を使って得られる。

$ python -c "from gensim.test.utils import datapath; print(datapath('wordsim353.tsv'))" 2>/dev/null
/Users/amedama/.virtualenvs/py37/lib/python3.7/site-packages/gensim/test/test_data/wordsim353.tsv

先頭について確認すると、こんな感じ。 タブ区切りで、ある単語と別の単語の類似度が浮動小数点で記述されている。 シャープ (#) からはじまる行は、単なるコメントなので読み飛ばして構わない。

$ head $(python -c "from gensim.test.utils import datapath; print(datapath('wordsim353.tsv'))" 2>/dev/null)
# The WordSimilarity-353 Test Collection (http://www.cs.technion.ac.il/~gabr/resources/data/wordsim353/)
# Word 1   Word 2  Human (mean)
love    sex 6.77
tiger   cat 7.35
tiger   tiger   10.00
book    paper   7.46
computer    keyboard    7.62
computer    internet    7.58
plane   car 5.77
train   car 6.31

アナロジータスク

もうひとつのアナロジータスクでは、特定の単語の分散表現に別の単語を足したり引いたりして目当ての単語となるかどうかを評価する。 アナロジータスクを評価するためのデータとして、gensim では questions-words.txt というデータが組み込みで提供されている。

先頭について確認すると以下のとおり。 基本的に、スペース区切りで 4 つの単語が並んでいる。 1 列目の単語から 2 列目の単語を引いて、3 列目の単語を足したとき 4 列目の単語になるかを評価することになる。 先頭のコロン (:) からはじまる行については、タスクのジャンルを表している。 ちなみに、タスクのジャンルのことはセクションと呼ぶようだ。

$ head $(python -c "from gensim.test.utils import datapath; print(datapath('questions-words.txt'))" 2>/dev/null)
: capital-common-countries
Athens Greece Baghdad Iraq
Athens Greece Bangkok Thailand
Athens Greece Beijing China
Athens Greece Berlin Germany
Athens Greece Bern Switzerland
Athens Greece Cairo Egypt
Athens Greece Canberra Australia
Athens Greece Hanoi Vietnam
Athens Greece Havana Cuba

単語間類似度を使った評価

それでは、実際に単語間類似度を使った評価を試してみよう。 単語間類似度は、WordEmbeddingsKeyedVectors#evaluate_word_pairs() を使って評価する。

>>> from gensim.test.utils import datapath
>>> similarities = model.evaluate_word_pairs(datapath('wordsim353.tsv'))

得られる結果はタプルで、最初の要素がピアソンの相関係数になっている。 中身もまたタプルになってるけど、先頭が相関係数で二番目は「相関がないこと」を帰無仮説とした仮説検定の p-value らしい。

>>> similarities[0]
(0.6987675497727828, 5.237592231656601e-53)

次の要素はスピアマンの相関係数で、実装には scipy を使っているようだ。

>>> similarities[1]
SpearmanrResult(correlation=0.7388081960366618, pvalue=3.98104844873057e-62)

評価用のデータに記述された単語間の類似度を表す点数と、Word Embedding が出した単語ベクトル間のコサイン類似度の相関が高いほど、より優れていると評価することになる。

アナロジータスクを使った評価

続いてアナロジータスクを使った評価を試してみる。 アナロジータスクは WordEmbeddingsKeyedVectors#evaluate_word_analogies() を使って評価する。

>>> analogy_scores = model.evaluate_word_analogies(datapath('questions-words.txt'))

上記では、前述したとおり 1 列目の単語から 2 列目の単語を引いて、3 列目の単語を出した結果が 4 列目の単語になるか評価している。 ただし、ピンポイントで一致しなくともコーパスの中でベクトルが最も似ている TOP5 の中にさえ入っていれば正解としているようだ。

メソッドの返り値として得られるのは、こちらもタプルとなっている。 最初の要素は、正解ラベルが TOP5 に入ったか否かの二値で評価した Accuracy となっている。 この値が高いほど、より優れた Word Embedding と捉えることになる。

>>> analogy_scores[0]
0.7492042304138001

次の要素はリストで、これはアナロジータスクをセクションごとに正解したデータと不正解したデータで分けたもの。 questions-words.txt は 15 のセクションに分かれているらしい。

>>> type(analogy_scores[1])
<class 'list'>
>>> len(analogy_scores[1])
15

それぞれの要素は辞書になっている。

>>> analogy_scores[1][0].keys()
dict_keys(['section', 'correct', 'incorrect'])

内容を確認すると、セクションの名前や正解したタスク、正解できなかったタスクが入っている。

>>> analogy_scores[1][0]['section']
'capital-common-countries'
>>> from pprint import pprint
>>> pprint(analogy_scores[1][0]['correct'][:10])
[('ATHENS', 'GREECE', 'BAGHDAD', 'IRAQ'),
 ('ATHENS', 'GREECE', 'BANGKOK', 'THAILAND'),
 ('ATHENS', 'GREECE', 'BEIJING', 'CHINA'),
 ('ATHENS', 'GREECE', 'BERLIN', 'GERMANY'),
 ('ATHENS', 'GREECE', 'BERN', 'SWITZERLAND'),
 ('ATHENS', 'GREECE', 'CAIRO', 'EGYPT'),
 ('ATHENS', 'GREECE', 'CANBERRA', 'AUSTRALIA'),
 ('ATHENS', 'GREECE', 'HANOI', 'VIETNAM'),
 ('ATHENS', 'GREECE', 'HAVANA', 'CUBA'),
 ('ATHENS', 'GREECE', 'HELSINKI', 'FINLAND')]
>>> pprint(analogy_scores[1][0]['incorrect'][:10])
[('ATHENS', 'GREECE', 'LONDON', 'ENGLAND'),
 ('BAGHDAD', 'IRAQ', 'CANBERRA', 'AUSTRALIA'),
 ('BAGHDAD', 'IRAQ', 'LONDON', 'ENGLAND'),
 ('BANGKOK', 'THAILAND', 'LONDON', 'ENGLAND'),
 ('BEIJING', 'CHINA', 'LONDON', 'ENGLAND'),
 ('BERN', 'SWITZERLAND', 'LONDON', 'ENGLAND'),
 ('CAIRO', 'EGYPT', 'LONDON', 'ENGLAND'),
 ('CANBERRA', 'AUSTRALIA', 'LONDON', 'ENGLAND'),
 ('HANOI', 'VIETNAM', 'LONDON', 'ENGLAND'),
 ('HANOI', 'VIETNAM', 'BERLIN', 'GERMANY')]

最初の要素で示されている Accuracy の値を、次の要素を使って検算してみよう。

>>> from itertools import chain
>>> correct = len(list(chain.from_iterable(s['correct'] for s in analogy_scores[1])))
>>> incorrect = len(list(chain.from_iterable(s['incorrect'] for s in analogy_scores[1])))
>>> correct / (correct + incorrect)
0.7492042304138001

上記のとおり、ちゃんと値が一致した。

いじょう。