Traffine I/O

日本語

2023-02-03

単語埋め込み

単語埋め込みとは

自然言語をコンピュータに認識させるためには、単語や文書の持つ性質や意味を反映しつつそれらを数値化する必要があります。単語の数値化として、単語を固定長のベクトルで表現することを単語埋め込み(Word Embedding)と言います。

単語をベクトルで表現することで、例えば、意味的に類似した単語(「boat」と「ship」など)や意味的に関連した単語(「boat」と「water」など)がベクトル空間上でより近くなります。意味的な関連性を捉えるには文書を文脈として用い、意味的な類似性を捉えるには単語を文脈として用います。

単語をベクトルで表現する方法として以下のような手法が提案されてきました。

  • One-hotベクトル
  • Word2Vec
  • fastText
  • GloVe
  • ELMo
  • BERT

One-hot ベクトル

単語をベクトル化する手法の一つとして、One-hotベクトルがあります。One-hotベクトルは、ベクトルの全ての要素のうち一つだけが1であり、残りは全て0であるベクトルを意味します。

例えば、以下のように単語ごとにベクトルを割り当てます。

単語 One-hot ベクトル
I (1,0,0)
play (0,1,0)
basketball (0,0,1)

この手法では、機械的にベクトルを割り当てることができる一方、以下の課題があります。

  • 次元が大きすぎる
    100万の単語があるとすると、次元は100万となり計算負荷が大きいです。
  • 意味をエンコードできない
    同一単語であるかどうかの判定はできますが、単語の意味を含んだベクトルにはなっていません。

単語埋め込み

ベクトルの次元が膨大になるという課題を解決するために提案された手法が単語埋め込みです。単語をベクトル空間に埋め込み、その空間上のひとつの点として捉えることを単語埋め込みと言います。単語埋め込みにより、単語の意味を含めたベクトルとして表現できるようになったことで、意味の異なる単語同士でのベクトルの計算ができるようになります。

単語埋め込みの方法として、以下のような手法が提案されてきました。

  • Word2Vec
  • fastText
  • GloVe
  • ELMo
  • BERT

Word2vec

One-hotベクトルの課題である次元が大きすぎる点、意味をエンコードできない点を克服する手法がword2vecです。Word2vecはGoogleの研究チームによって2013年に公開されました。

https://arxiv.org/abs/1310.4546

Word2vecはSkip-gramとCBOW(Continuous Bag-Of-Words)の2つのアーキテクチャの集合です。

CBOW and skip-gram
Efficient Estimation of Word Representations in Vector Space

アーキテクチャ 内容
Skip-gram ある単語から周辺の単語を予測
CBOW 周辺の単語からある単語を予測

Word2vecはgensimというライブラリを使うと簡単に実装することができます。

https://radimrehurek.com/gensim/

word2vec.ipynb
import gensim
import gensim.downloader as gendl

corpus = gendl.load("text8")
model = gensim.models.Word2Vec(corpus, size=200, window=5, iter=10, min_count=1)

「tea」という単語は以下の200次元のベクトルで表現されています。

word2vec.ipynb
model.wv['tea']

array([ 1.0338242 , -0.5497605 ,  0.85095   , -1.9308733 ,  0.02577365,
       -0.04760092, -0.11763089, -0.88888925,  1.0477808 , -0.8593776 ,
       -0.18352145,  0.44907862,  0.7292549 , -0.989956  , -0.31382865,
       -0.326568  ,  1.0326288 , -1.015825  ,  1.1364412 ,  2.2554684 ,
       -0.46170548, -0.87449044, -0.92171615,  1.3384876 ,  0.71479833,
        1.5579652 , -0.79532343, -0.01963346, -0.09595346,  0.2980923 ,
       -0.77367973, -0.21674974, -0.16363882,  0.7561723 , -0.9837275 ,
        0.7700488 ,  0.31943208,  1.1382285 ,  0.7920231 ,  0.4009208 ,
       -0.12014329,  0.45739177, -0.8750017 ,  0.9846294 ,  1.0761734 ,
        0.9916462 , -1.9893979 ,  0.63205105,  1.6679561 ,  0.23037305,
       -0.09124516,  0.20652458,  2.5330052 ,  0.46803576, -0.52581394,
       -0.04794846, -0.8805762 , -0.19912305,  0.4005117 , -0.7323035 ,
        1.1342882 , -2.2171109 ,  2.2963653 ,  0.30365032,  1.0434904 ,
       -0.04669679,  0.4062761 ,  0.5528872 ,  0.47095624, -1.6239247 ,
        1.557434  , -0.9619251 ,  0.9666863 , -0.2241764 , -0.75970024,
        1.8893499 , -1.0962995 , -1.0411807 ,  0.4480955 ,  0.5210397 ,
        1.6454376 ,  0.65203476, -0.04012801, -0.40056226,  2.852509  ,
       -0.32553425, -0.20229222,  0.7245843 , -1.9557822 , -0.12104818,
        0.28175735, -0.895713  ,  0.58786476,  1.3742826 , -0.41480052,
        0.07302658,  0.09337772, -0.34411722, -0.00724911, -0.9056035 ,
        0.12258726, -0.15003532,  0.04658122, -2.2854378 ,  0.28233293,
        0.77172595,  1.3786261 , -1.2632967 , -1.426814  , -0.51365477,
       -1.2197368 ,  0.04790526,  0.62031317,  1.1224078 , -0.1804243 ,
        0.49400517, -0.02745727, -1.0031232 , -0.10298221,  0.91482383,
        0.98645395,  0.2342329 ,  0.02064842, -0.33413368, -1.1859212 ,
        1.1475669 , -1.0501987 , -0.7069197 , -0.12736289,  1.7058631 ,
       -0.74710023,  0.48769948, -0.7129323 ,  0.49225873, -1.3105804 ,
       -2.1176062 ,  0.5835398 , -0.01676613,  0.40714362,  1.6942219 ,
        0.8474393 , -0.7914968 ,  1.8470286 ,  0.4587502 , -1.2789383 ,
        0.48545036, -0.50352156, -0.04223507, -0.35754788, -0.60754126,
        0.05735195,  0.32261458, -0.09268601,  0.702604  ,  1.1815772 ,
        0.17023656, -0.46019134, -1.0920937 ,  0.26714015, -0.06735314,
       -0.16602936,  0.6498549 , -0.35616133,  0.20689702,  0.7797428 ,
        0.14901382, -0.71886814,  1.2617997 ,  0.43995163,  1.0300183 ,
       -0.81545556, -0.06593582, -0.23527797, -1.3182799 ,  0.41896763,
        1.8256154 , -0.04706338, -1.106722  , -0.47043508, -0.30877873,
        0.27309516, -0.5845974 ,  0.12854019,  0.44951373,  0.46647298,
       -0.30743253, -0.7417909 ,  1.0234478 , -1.4167138 , -0.6474553 ,
        0.9093568 ,  0.17825471, -1.3186976 , -1.7007768 , -1.4912218 ,
        0.02761938, -1.5523437 , -0.30878946, -0.5883677 ,  0.35952488],
      dtype=float32)
len(model.wv['tea'])

200

このように、Word2vecはある単語のベクトル表現を数百万次元から200次元に削減することができています。

「tea」という単語に類似している単語は以下のようになります。

word2vec.ipynb
model.wv.most_similar("tea")

[('coffee', 0.7221519947052002),
 ('beef', 0.6720874309539795),
 ('roast', 0.6600600481033325),
 ('spices', 0.653677225112915),
 ('drinks', 0.6486484408378601),
 ('sweets', 0.6478144526481628),
 ('cheese', 0.6444395780563354),
 ('pork', 0.6424828767776489),
 ('sausage', 0.6399528980255127),
 ('vegetables', 0.6373381614685059)]

このように、Word2vecは単語の意味を理解することができています。

以下の単語を組み合わせるとどのようになるのかを確認します。

queen + man - woman


```python
model.wv.most_similar(positive=['queen', 'man'], negative=['woman'])

[('king', 0.5715309977531433),
 ('lord', 0.5021377801895142),
 ('crown', 0.4470388889312744),
 ('wight', 0.43937280774116516),
 ('prince', 0.43743711709976196),
 ('duke', 0.43583834171295166),
 ('regent', 0.4239676296710968),
 ('valdemar', 0.4121330976486206),
 ('scotland', 0.4113471210002899),
 ('vii', 0.3981912136077881)]

「king」という結果が返ってきました。

GloVe

Word2Vecのアーキテクチャはいずれも予測型であり、ある文脈の単語が他の文脈より多く出現するという事実を無視しています。また、局所的な文脈のみを考慮しているため、グローバルな文脈を捉えることができません。

GloVe(Global Vectors for Word Representation)は単語の共起性に関するグローバルな統計量を利用して、単語のベクトル表現を学習します。

以下はGloVeの実装のサンプルコードです。

glove.ipynb
!wget http://nlp.stanford.edu/data/glove.6B.zip
!unzip glove*.zip

import numpy as np

embeddings_dict = {}
with open("glove.6B.50d.txt", 'r') as f:
    for line in f:
        values = line.split()
        word = values[0]
        vector = np.asarray(values[1:], "float32")
        embeddings_dict[word] = vector
glove.ipynb
embeddings_dict['water']

array([ 0.53507 ,  0.5761  , -0.054351, -0.208   , -0.7882  , -0.17592 ,
       -0.21255 , -0.14388 ,  1.0344  , -0.079253,  0.27696 ,  0.37951 ,
        1.2139  , -0.34032 , -0.18118 ,  0.72968 ,  0.89373 ,  0.82912 ,
       -0.88932 , -1.4071  ,  0.55571 , -0.017453,  1.2524  , -0.57916 ,
        0.43    , -0.77935 ,  0.4977  ,  1.2746  ,  1.0448  ,  0.36433 ,
        3.7921  ,  0.083653, -0.45044 , -0.063996, -0.19866 ,  0.75252 ,
       -0.27811 ,  0.42783 ,  1.4755  ,  0.37735 ,  0.079519,  0.024462,
        0.5013  ,  0.33565 ,  0.051406,  0.39879 , -0.35603 , -0.78654 ,
        0.61563 , -0.95478 ], dtype=float32)

https://nlp.stanford.edu/pubs/glove.pdf
https://github.com/stanfordnlp/GloVe

fastText

Word2VecやGloVeでは、未知の単語や語彙のない単語を符号化できないという課題がありました。この課題に対処するために、FacebookはFastTextというモデルを提案しました。

https://arxiv.org/abs/1607.01759

Word2Vecが個々の単語のベクトルを学習するのに対し、fastTextでは単語をいくつかのサブワード(n-gram)に分割し学習されます。

例えば、「what」という単語を考えた場合、tri-gram(n=3)を用いると、単語は以下のような文字n-gramで表現されることになります。

"wh", "wha", "hat", "at"

この概念に基づき、FastTextは学習テキストに含まれない単語の埋め込みベクトルを、その文字埋め込みを用いて生成することができます。例えば、「googling」という未知語があっても、「google」と「ing」というように分割されるようになり、ベクトル表現が可能となります。

以下はfastTextの実装のサンプルコードです。

fasttext.ipynb
!wget https://dl.fbaipublicfiles.com/fasttext/vectors-english/wiki-news-300d-1M.vec.zip
!unzip wiki-news-300d-1M.vec.zip

import io

def load_vectors(word):
    fin = io.open('wiki-news-300d-1M.vec', 'r', encoding='utf-8', newline='\n', errors='ignore')
    for line in fin:
        tokens = line.rstrip().split(' ')
        if target_word == tokens[0]:
            return [float(s) for s in  tokens[1:]]
fasttext.ipynb
load_vectors('googling')

[-0.1151,
  0.2652,
  -0.13,
  .
  .
  .
  0.123,
  0.2461,
  0.0373]

https://fasttext.cc/docs/en/english-vectors.html
https://github.com/facebookresearch/fastText

ELMo

Word2vec、GloVe、fastTextはいづれも1単語を1ベクトルに変換するという性質があります。そのため、多義語を表現することができません。例えば「bank」という言葉は、金融機関だけでなく、川沿いの土手も指すこともあります。

そこで、ELMoというある単語の意味が文脈に依存することに対処するモデルが開発されました。

https://arxiv.org/abs/1802.05365

ELMoは1つの単語に対して固定的なベクトル表現を生成しません。その代わりに、ELMoは文中の各単語の埋め込みを生成する前に、文全体を考慮します。この文脈依存の埋め込みフレームワークは、単語が実際に使用される文脈を加味した単語のベクトル表現を生成します。

ELMoはディープ双方向LSTM言語モデルを用いており、次の単語だけでなく、前の単語もよりよく理解することができます。

以下はELMoの実装のサンプルコードです。

elmo.ipynb
!pip install allennlp

from allennlp.modules.elmo import Elmo, batch_to_ids

options_file = "https://s3-us-west-2.amazonaws.com/allennlp/models/elmo/2x4096_512_2048cnn_2xhighway/elmo_2x4096_512_2048cnn_2xhighway_options.json"
weight_file = "https://s3-us-west-2.amazonaws.com/allennlp/models/elmo/2x4096_512_2048cnn_2xhighway/elmo_2x4096_512_2048cnn_2xhighway_weights.hdf5"
elmo = Elmo(options_file, weight_file, 2, dropout=0)

sentences = ['By working faithfully eight hours a day, you may eventually get to be a boss and work twelve hours a day.'.split(' ')]
character_ids = batch_to_ids(sentences)
embeddings = elmo(character_ids)
print(embeddings['elmo_representations'])

[tensor([[[ 0.2935,  0.2494, -0.4810,  ..., -0.2546, -0.2394,  0.2540],
         [ 0.2875,  0.2179, -0.2757,  ...,  0.0597, -0.1851, -0.2132],
         [-0.4081,  0.1770,  0.5752,  ..., -0.3343,  0.4417,  0.6988],
         ...,
         [ 0.2734,  0.0159, -0.1510,  ..., -0.0459, -0.0769,  0.8115],
         [ 0.0628, -0.0689, -0.3339,  ..., -0.1212, -0.0101,  0.0247],
         [-0.0577,  0.8521, -0.3685,  ...,  0.0323, -0.1151,  0.2783]]],
       grad_fn=<CopySlices>), tensor([[[ 0.2935,  0.2494, -0.4810,  ..., -0.2546, -0.2394,  0.2540],
         [ 0.2875,  0.2179, -0.2757,  ...,  0.0597, -0.1851, -0.2132],
         [-0.4081,  0.1770,  0.5752,  ..., -0.3343,  0.4417,  0.6988],
         ...,
         [ 0.2734,  0.0159, -0.1510,  ..., -0.0459, -0.0769,  0.8115],
         [ 0.0628, -0.0689, -0.3339,  ..., -0.1212, -0.0101,  0.0247],
         [-0.0577,  0.8521, -0.3685,  ...,  0.0323, -0.1151,  0.2783]]],
       grad_fn=<CopySlices>)]

https://allenai.org/allennlp/software/elmo

BERT

BERT は2018年10月11日にGoogleが発表したMLPモデルです。BERTは翻訳や分類問題を解くためのアルゴリズムとしてだけではなく、単語の分散表現としても利用することができます。具体的には、BERTの中ではMasked Language Model(MLM))というマスクされた単語を推定するモデルがあり、その中で単語の分散表現が学習されます。

ELMoは単語を推定する際に順方向と逆方向でそれぞれ独立に学習するのに対して、BERTのMLMは順方向と逆方向を同時に学習します。そのため、理論上はELMoよりもBERTの分散表現の方が精度は高いです。

以下はBERTの実装のサンプルコードです。

bert.ipynb
!pip install transformers

import torch
from transformers import BertTokenizer, BertModel

model = BertModel.from_pretrained('bert-base-uncased')
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
input_ids = torch.tensor(tokenizer.encode('The optimist sees the doughnut, the pessimist sees the hole.', add_special_tokens=True)).unsqueeze(0)
outputs = model(input_ids)
last_hidden_states = outputs[0]

print(last_hidden_states)

tensor([[[-0.4030,  0.3356, -0.0636,  ..., -0.6573,  0.6247,  0.6181],
         [-0.6247,  0.1251, -0.3543,  ...,  0.0818,  1.1095,  0.0013],
         [-0.1742, -0.0781,  0.4559,  ..., -0.3268,  0.2127,  0.1130],
         ...,
         [ 0.3077,  0.2755, -0.1591,  ...,  0.3503,  0.3849,  0.6388],
         [ 0.6037,  0.0777, -0.2810,  ...,  0.1952, -0.3510, -0.3897],
         [ 0.7766,  0.1315, -0.1458,  ...,  0.1757, -0.4855, -0.3783]]],
       grad_fn=<NativeLayerNormBackward0>)

まとめ

単語のベクトル表現の手法をまとめると以下の表のようになります。

手法(大分類) 手法(小分類) 意味理解 文脈理解 次元数
One-hot 表現 One-hot ベクトル × × 1M
文脈を加味しない単語埋め込み Word2vec
Glove
fastText
× 100~300
文脈を加味する単語埋め込み ELMo
BERT
100~1000

参考

https://arxiv.org/abs/1802.05365
https://mccormickml.com/2019/05/14/BERT-word-embeddings-tutorial/
https://towardsdatascience.com/nlp-extract-contextualized-word-embeddings-from-bert-keras-tf-67ef29f60a7b
https://medium.com/@kashyapkathrani/all-about-embeddings-829c8ff0bf5b
https://www.alpha-quantum.com/blog/word-embeddings/introduction-to-word-embeddings-word2vec-glove-fasttext-and-elmo/
https://towardsdatascience.com/word-embedding-techniques-word2vec-and-tf-idf-explained-c5d02e34d08
https://towardsdatascience.com/nlp-embedding-techniques-51b7e6ec9f92
https://rbouadjenek.github.io/papers/wordembed_v2.0.pdf
https://fasttext.cc/docs/en/english-vectors.html
https://github.com/facebookresearch/fastText
https://github.com/stanfordnlp/GloVe
https://nlp.stanford.edu/pubs/glove.pdf
https://nlp.stanford.edu/projects/glove/
https://towardsdatascience.com/learn-word2vec-by-implementing-it-in-tensorflow-45641adaf2ac
https://radimrehurek.com/gensim/

Ryusei Kakujo

researchgatelinkedingithub

Focusing on data science for mobility

Bench Press 100kg!