単語の分散表現を使った教師なし単語翻訳

論文)Word Translation Without Parallel Data
https://arxiv.org/abs/1710.04087

実装)facebookresearch/MUSE
https://github.com/facebookresearch/MUSE


単語に実ベクトルを対応させるword embeddingsは、単語の分散表現の工学的な実現のように思われている。多分、本来は、distributed representation/分散表現は、脳内で外界の事象を符号化する方法として、局所表現と対をなす形で考えられた(NLP分野に限定されない)概念で、一方、word embeddingsは、単語をベクトル空間の点で表す工学的な手法を限定して指すと思うのだけど、日本語では、word embeddingsを指して、単に分散表現と呼ばれることが、しばしばあるっぽい。以下では、細かいことは気にせず、word embeddingsのことを、分散表現と呼ぶことにする。

2013年に公表されたword2vecでは、加法構成性が成立していて、意味や概念を獲得していると解釈できることで、話題になった(king-man+woman≒queenなどの結果が成立する)。尤も、類似度を内積で測る場合、x-yと類似度が高くなるには、xと類似度が高くて、yと類似度が低ければよいので、別段、大したことではないのかもしれないけれど。その後も、word2vecの改良版が、沢山出ていて、GloveやfacebookのfastTextは有名っぽい(word2vec論文のfirst authorだったMikolovは、fastText論文でも、last authorになっている)

cf)Word Embeddingモデル再訪
http://www.orsj.or.jp/archive2/or62-11/or62_11_717.pdf


加法構成性が成立する分散表現が、異なる言語間で、どれくらい似ているかは、早い段階から調べられていて、機械翻訳への応用が想定されていたらしい(word2vecの開発者Mikolovは、2013年に、arXiv:1309.416のような論文を出している)

加法構成性のある分散表現で成立する、単語間の線形関係式は、言語に依存せず成立して欲しい。加法構成性を保つという条件は、写像fに関する方程式f(x+y)=f(x)+f(y)の形で書け、これが、任意の点x,yで成り立つというのは、コーシーの関数方程式の高次元版になっている。数学的なことに突っ込んでもしょうがないけど、病的な解を除けば、線形写像のみが、この方程式の解となる。

cf)Cauchy's functional equation for Rn
https://math.stackexchange.com/questions/186350/cauchys-functional-equation-for-mathbb-rn

また、分散表現では、単語の類似度を内積で測るので、よい分散表現間のマッピングでは、内積が保存されていてほしい。従って、十分によい分散表現の間では、内積を保つ線形変換=直交変換の中に、単語翻訳を近似する写像があると期待される(分散表現で、単語ベクトルの長さは、特に規格化されていないので、個別の単語ベクトルを定数倍しても、類似度は変わらないけど、次元数が単語数より小さい時は、特定の方向をスケールする自由度は、あんまり残らなそうに、直感的には思う)


まぁ、もう実装があるので、とりあえず、冒頭のMUSEを使ってみる。fastTextの開発グループによるものっぽいので、データは、fastTextで作成したものを想定しているらしい。自分でデータを用意してもいいけど、以下に、多くの言語に対するデータが公開されている
Wiki word vectors
https://fasttext.cc/docs/en/pretrained-vectors.html

Word vectors for 157 languages
https://fasttext.cc/docs/en/crawl-vectors.html


一応、気付いたことなど。

・Faissは、Windowsには対応してない(README.mdには、recommendedって書いてあるので、MUSEを動かすには必須ではない?)

・そこそこ、メモリが必要かもしれない。計算中のメモリを減らすには、--max_vocabを200000より小さい値に設定すると有効(計算も速く終わる。但し、fastTextバイナリファイルを使うと、全部読み込むんでしまうのでダメっぽい)。教師なしの場合は、max_vocabが75000以下の場合、--dis_most_frequentをそれより小さくしてやらないといけないっぽい(多分)

・教師あり/教師なし共に、最後のtrainer.exportで辞書を出力するっぽいけど、一旦、全データを読み込むっぽいので、メモリ8GBで足りない場合もあるよう。wiki.en.vecは単語数が250万以上あるせいかもしれない。但し、必要なのは、ベクトル空間の間の直交写像Wで、dumped/debug/(ランダム文字列)/best_mapping.pthに出力される(デフォルトでは、300x300の行列なので、大したデータ量ではない)から、trainer.exportはコメントアウトしてもOK(後から、自分で、個々の単語ベクトルに直交写像Wを適用した結果を計算すればいい。normalize_embeddingsを指定した場合は、それに相当する計算も自分でやる必要があるが、デフォルトではOFF)

・src/evaluation/evaluator.pyの"self.word_translation(to_log)"で、Ground-truth bilingual dictionariesを見にいくっぽい。README.mdに、沢山のペアについて、リンクを貼ってくれているけど、リストにない場合は、どうしようもない。教師なし学習の場合、この処理は不要ぽいので、コメントアウトすればいい(例えば、日本語単語ベクトルを、中国語単語ベクトル空間に埋め込みたいという時は、データが用意されてない)

・README.mdの例にあるen vs esのケースを、CPUのみで計算した場合、max_vocabを50000にして、教師なしで半日弱で終わった。教師ありは、もっと早い。速度的には、まぁ悪くない


【テスト1】実際に、README.mdにある、英語->スペイン語を計算してみる。以下のようなコマンドで計算した

python supervised.py --cuda False --src_lang en --tgt_lang es --src_emb data/wiki.en.vec --tgt_emb data/wiki.es.vec --n_refinement 5 --dico_train default 

python unsupervised.py --cuda False --src_lang en --tgt_lang es --src_emb data/wiki.en.vec --tgt_emb data/wiki.es.vec --n_refinement 5

README.mdを見ると、11万強の類似単語対が書かれたデータ(en-es.txt)へのリンクが貼ってあるので、教師なしで学習したWで変換した単語ベクトルを使って、英単語とスペイン語単語の類似度を測る。

生のwiki.en.vecやwiki.es.vecは、単語数が多すぎて、gensimで読むと、遅い(メモリも使う)ので、検証用に使った辞書は、適当に、4万〜6万単語で、足切りした(別に、gensimで読む必要はないけど)。単語ベクトルが得られた場合は、そこそこの類似度になっていた。計測結果は、以下の通りで、教師データの有無で差が殆どない

教師なしの場合:類似度の中央値0.652,平均値0.628
教師ありの場合:類似度の中央値0.652,平均値0.628

計測に使ったコードは、以下のようなもの(以下は、教師なしの場合の類似度の中央値と平均値を測定する例)

import gensim 
import torch
import numpy as np

src_model = gensim.models.KeyedVectors.load_word2vec_format("data/wiki.en.40k.vec")
tgt_model = gensim.models.KeyedVectors.load_word2vec_format("data/wiki.es.60k.vec")
W = torch.load("en_es_unsupervised_mapping.pth")
similarities = []
##-- https://dl.fbaipublicfiles.com/arrival/dictionaries/en-es.txt
for n,line in enumerate(open("data/crosslingual/dictionaries/en-es.txt")):
  src,tgt = line.strip().split()
  try:
     v0 = src_model[src]
     v = np.dot(W, v0)
     w = tgt_model[tgt]
     sim = np.dot(v,w)/np.sqrt(np.dot(v,v)*np.dot(w,w))
     similarities.append( sim )
  except:
     pass


similarities.sort()
print(similarities[len(similarities)//2] , sum(similarities)/len(similarities))


平均値と中央値が、ほぼ同一なので、少し違う手段で、直交行列を比較する。同一の単語ベクトルを、それぞれの直交写像で変換した結果が、最も"離れる"のは、いつかというのを見ればいいだろうと思われる。ここでの"離れる"の意味は、Euclid距離ではなく、コサイン類似度を意味する。従って、(Wx , W'x)/(x,x)が最小になるようなベクトルxを発見すればいい。=(Wx,W'x)/(x,x) = (W'^{T}Wx,x)/(x,x)であるから、W'^{T}W固有値を見ればよさそうに思える。実際には、固有値は、殆ど複素数になるけど、単語ベクトルは、実ベクトルだと考えられているので、固有ベクトルが、欲しいxになるというわけではない。が、理想的には、固有値は全部+1なので、そこから離れている固有値が多いと、問題がある(ワーストケースでは、-1が固有値として出てくる)

実際に

W = torch.load("en_es_unsupervised_mapping.pth")
W2 = torch.load("en_es_supervised_mapping.pth")
print( np.linalg.eig(np.dot(W2.T,W))[0] )

とかやると、固有値-1が、ひとつ存在するけど、大部分の固有値は、1に近いので、それほど悪くない。流石に、例として記載されてるだけあって、悪くない結果のように見える



【テスト2】異なるコーパスで学習した、同一言語間のマッピングを見る。具体的には、Common Crawlで学習したデータと、Wikipediaから学習したデータを使った

教師ありの実験をするために、en-es.0-5000.txtに含まれる英単語から、en-en.0-5000.txtを作った(対応する単語は、同一単語を指定)

結果、教師ありの場合、類似度中央値:0.728 , 類似度平均値:0.702を得た。

CommonCrawlで学習したベクトルを変換して、Wikipediaで学習したデータで最も類似度の高い単語が同一になる割合は、93%だった。コーパスに依存しない情報を、それなりに抽出出来ているらしい。

教師なしでやると、何故か、類似度中央値:0.086,類似度平均値:0.087で、計算に失敗しているらしかった。理由は不明


【テスト3】CommonCrawlコーパスから学習した、英語→スペイン語マッピングを見てみる
python supervised.py --cuda False --src_lang en --tgt_lang es --src_emb data/cc.en.300.vec --tgt_emb data/cc.es.300.vec --n_refinement 5 --max_vocab 100000

python unsupervised.py --cuda False --src_lang en --tgt_lang es --src_emb data/cc.en.300.vec --tgt_emb data/cc.es.300.vec --n_refinement 5

教師あり学習の場合、類似度中央値:0.591,類似度平均値:0.574
教師なし学習の場合、類似度中央値:0.562,類似度平均値:0.549

Wikipediaコーパスの時より、少し下がっているものの、それほど悪くはない数値


【テスト4】テスト1と同様の計測を、英語→日本語でやってみた。

データは、Wikipediaから作ったらしいwiki.en.vecとwiki.ja.vecを使用した場合、
教師ありの場合:類似度の平均値:0.429 , 中央値:0.430
教師なしの場合:類似度の平均値:0.252, 中央値:0.240

単語対データen-ja.txtとen-es.txtは同じものではないので、単純に比較するわけにはいかないけれども、英語→スペイン語に比べると、やや低い値になっている。それ以外に、理由がよくわからないこととして、教師なしと教師ありの場合の差が大きくなっている

教師なしの方は、数値以上に悪い。単語対データの英単語を、日本語単語ベクトル空間にマッピングして、最も類似度の高い単語ベスト3に、対になる単語が含まれている割合を、教師ありと教師なしで比較したところ、教師ありの方は、12%だったが、教師なしの方は、一つもなかった。これは、何も学習できていないといって差し支えない結果である。引っかかる単語の大部分が、japanese_national_route_sign_のような単語ですらないゴミ。教師ありの方も、アルファベットで構成される単語と近くなる傾向が見られ、英語のpearlやclassに近い単語として、日本語側でも、pearlやclassが出てくる(ある意味、間違ってはいないが...)

アルファベットのみの単語を除去してマッピングしても、うまくいかなかったので、単語ベクトルの作り方が悪いのかもしれない。wiki.ja.vecには、japanese_national_route_signだの「アメリカ合衆国国勢調査局に拠れば」だの、単語でないものも含まれているし

cc.en.vec->cc.ja.vecだと
教師ありの場合:類似度の平均値:0.494 , 類似度の中央値:0.510
教師なしの場合:類似度の平均値:0.108 , 類似度の中央値:0.105
で、教師なしでは、うまくいかない。

教師ありの方は、そこそこ。類似単語トップ3に、対になる単語が入っている割合は、64%。トップ3に入ってないものの中には、june->8月、april->5月みたいな形で惜しい物、history->歴史(en-ja.txtには、履歴、由緒、沿革などがある)やhigh->高い(en-ja.txtには、ハイ)のように正解であるものも含まれる。

他に、aspirin->錠剤やfructose->糖分みたいなspecificityが高すぎて特定できてないものなどもあるけど、人間でも、アスピリンを「何か薬だよね」とか、フルクトースを、「なんか糖だよね」とかくらいしか理解してない人がいそうなことを思うと、許容範囲内の結果になってる。言語の違いにも関わらず、良好な結果を与えている

日本語に関しては、wiki.ja.vecには、問題があり、cc.ja.vecの方が、いいっぽい



【テスト5】Gloveで学習した単語ベクトルを使って、英語→英語を試してみる

GloVe: Global Vectors for Word Representation
https://nlp.stanford.edu/projects/glove/
に、異なるコーパスから学習したらしいデータが、いくつか公開されている。Wikipediaから作成されたらしいglove.6B.300d.txtと、Common Crawlから作ったらしいglove.42B.300d.txtを使って、今までと同様の計算をやる。これらのファイルは、fastTextと微妙にフォーマットが違っていて、ヘッダに、単語数と単語ベクトルの次元を追加する必要がある

教師ありの場合:類似度の中央値:0.61, 平均値:0.60, 最も類似度の高い単語が同一である割合:86.2%
教師なしの場合:類似度の中央値:0.62, 平均値:0.60, 最も類似度の高い単語が同一である割合:86.3%

Gloveで作ったデータでも、それなりの結果を得られた。この結果を、テスト2と比較すると、fastTextの方が、よい結果を与えるようにも見える。


異なるコーパスで学習した作った分散表現間でも、類似度が0.6〜0.7にしかならないのは、もうちょっと何とかなってほしい


【おまけ】
ついでに、ヴォイニッチ手稿に、この方法を適用すると、どうなるか試したけど、全ての単語が、","や"a","the","I"などの出現頻度が高い単語との類似度が高くなるように、直交写像Wが学習されてしまい、全然ダメだった(ヴォイニッチ手稿の分散表現は、fasttextで作った)。類似度は、殆どの場合、0.9以上あるので、Wの学習そのものが失敗してるわけではなく、むしろ、最適化を見つけてはいると言える。

この結果だけだと、ヴォイニッチ手稿の単語ベクトルの分布が、偏ってるのか、単語数が少ない(約1000単語)せいで、こうなってるのか分からないけど、聖書一冊(新約+旧約、約5400単語)でやっても、うまくいかなかったので、単語数が少ない時は、もう少し工夫がいるっぽい。そもそも、300次元はでかすぎるのかもしれないという可能性もあるけど、本一冊程度から学習できる程度では、単語ベクトルの点群がなす分布形状全体を網羅しないという方がありそうではある。


ヴォイニッチ手稿を選んだ理由は、思い入れがあるわけではなく、他の未解読文字でもよかったけど
・電子化されたテキストがあること
分かち書きされた言語で書かれてること
という条件で扱いやすそうだったので、ヴォイニッチ手稿を使った。

ヴォイニッチ手稿の画像データは、以下で閲覧できる。
The Voynich Manuscript
https://archive.org/details/TheVoynichManuscript

ちょっと見れば分かる通り、素人目には判読しがたい。画像から(普通、手作業で?)テキストデータに変換することを、transcriptionと読ぶらしい。transcriptionには、行った人によって、複数のバージョンが存在し、統一的な見解というのは、ないようである。

以下の2つのページに、異なるtranscriptionが存在し、似てはいるものの、同一ではない。musyoku/voynich-transcriptionは、大体、20万文字ちょっとになるよう。
musyoku/voynich-transcription
https://github.com/musyoku/voynich-transcription

Voynich Manuscript-transcription
http://www.voynich.com/pages/index.htm

他に、以下のページでも、複数のtranscriptionが入手できる
Reeds/Landini's interlinear file in EVA, version 1.6e6
http://www.ic.unicamp.br/~stolfi/voynich/98-12-28-interln16e6/

未知の文字のOCRは、不可能でないにしても、そこで、間違いが混在する可能性が高いので、先人がテキストに起こしてくれていることが望ましい。あと、個々の文字の判読は難しいけど、なんか、分かち書きされてることは分かる。日本語や中国語のように、分かち書きされてない言語だと、分かち書きするとこから始めないといけない。無教師で分かち書きも、現在の技術で出来ないこともないかもしれないけど

使用した聖書のデータは、Project Gutenbergから引っ張ってきた英語版。

The King James Version of the Bible
http://www.gutenberg.org/ebooks/10