自然言語処理におけるEmbeddingの方法一覧とサンプルコード

概要

自然言語処理における単語や文章のEmbeddingの方法を勉強したので概要を記載しました。
また、学習済みモデルからEmbeddingベクトルを取得するサンプルソースコードも一部記載しました。

Word2vec

似た意味の単語の周りには同じような単語が出現するとして、ある単語の周辺に出現する単語を予測するNNの隠れ層の重みを、ある単語のベクトルとしたもの。Doc2vecはWord2vecを文章に拡張したもの。

NNには以下のようなSkip-Gramのモデルが使われる。
f:id:YukoIshizaki:20200103023036p:plain
Word2vecの元論文 : [1310.4546] Distributed Representations of Words and Phrases and their Compositionality
Doc2vecの元論文 : [1405.4053] Distributed Representations of Sentences and Documents
参考 1 : 絵で理解するWord2vecの仕組み - Qiita
参考 2 : [1411.2738] word2vec Parameter Learning Explained


サンプルコード
gensimを使います。ここから日本語のWikipediaの学習済みモデルをダウンロードしてきます。
学習済みモデル : GitHub - Kyubyong/wordvectors: Pre-trained word vectors of 30+ languages

import gensim
model = gensim.models.Word2Vec.load('ja/ja.bin')
print(model.wv['三日月'])

 >> array([-1.61277249e-01, -3.04615557e-01,  2.59203255e-01,  2.29006037e-01, .....  
5.58053315e-01, -3.36245120e-01], dtype=float32)

fastText

Word2vecの単語の活用形 (subword) を考慮したもの。

元論文 :[1607.04606] Enriching Word Vectors with Subword Information

サンプルコード
facebook research のリポジトリにfasttextのソースコードと使い方があるのですが、今回は学習済みモデルから生成されたベクトル一覧ファイルから、ベクトルを取得しました。

ソースコード : GitHub - facebookresearch/fastText: Library for fast text representation and classification.
ワードベクトル : English word vectors · fastText

import io

def load_vectors(target_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:]]

print(load_vectors('sun'))

  >> [0.1882, 0.0284, -0.1026, 0.0115, -0.0426, -0.1592, 0.0543, 0.1111, -0.0036, -0.0481, 0.0463, 0.0837, ....  
-0.0851, 0.1371, 0.1049, 0.0401, 0.0375, 0.0062, -0.0197, 0.0295, -0.0276]

GloVe

GloVe (Global Vectors for Word Representation) は、文書全体における単語と単語の共起行列を使って表される、ある単語の文脈単語が現れる確率(に対数をとった)値と、ある単語ベクトルと文脈単語ベクトルの内積が等しいものとモデル化して、最小二乗法で解くことで得られるものを、ある単語のベクトルとしたもの。

元論文 : https://nlp.stanford.edu/pubs/glove.pdf
参考 : 論文メモ: GloVe: Global Vectors for Word Representation - け日記

サンプルコード
スタンフォードのサイトにある学習済みモデルから生成した単語ベクトルの一覧ファイルから、ベクトルを取得します。

ソースコード : GitHub - stanfordnlp/GloVe: GloVe model for distributed word representation
ワードベクトル : GloVe: Global Vectors for Word Representation

import numpy as np

embeddings_dict = {}
with open("glove.6B/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

embeddings_dict['water']

  >> array([ 0.53507 ,  0.5761  , -0.054351, -0.208   , -0.7882  , -0.17592 ,..... 
0.61563 , -0.95478 ], dtype=float32)

参考 : Basics of Using Pre-trained GloVe Vectors in Python | by Sebastian Theiler | Analytics Vidhya | Medium

Skip-thought

Skip-thought は、ある文章 (単語をone-hot) をエンコーダーの入力とし、その文章の前の文章と、後の文章をそれぞれデコーダーの出力として学習させたNNにおいて、エンコーダーの入力となるある文章の最後の単語が入力された次の時点の隠れ層の出力値が、文章ベクトルとして得られる。エンコーダーデコーダーには GRU ベースの RNN モデルを使用。
下の図で言うところの、点線で囲われている部分が得られる文章ベクトル。
f:id:YukoIshizaki:20200103020938p:plain

元論文 : [1506.06726] Skip-Thought Vectors
参考 : Skip-thoughtを用いたテキストの数値ベクトル化 - Platinum Data Blog by BrainPad

SCDV

SCDV (Sparse Composite Document Vectors) は、以下のように文章のベクトルを得る。

1. Word2vecなどで、単語ベクトルを得る
2. GMMでK個のクラスタに分ける
3. 1.の単語ベクトルと2.の単語がクラスタに属する確率から、単語クラスタ表現を得る
4. 単語クラスタ表現に単語のidfと単語クラスタ表現を掛け合わせて、単語トピックベクトルを得る
5. 文章内の単語トピックベクトルを足し合わせる
6. ベクトル内の絶対値がゼロに近い要素をゼロとする

元論文 : [1612.06778] SCDV : Sparse Composite Document Vectors using soft clustering over distributional representations
参考 1 : 文章の埋め込みモデル: Sparse Composite Document Vectors を読んで実装してみた - nykergoto’s blog
参考 2 : [論文メモ] SCDV : Sparse Composite Document Vectors using soft clustering over distributional representations - Qiita

USE

USE (Universal Sentence Encoder) は、エンコーダーにTransformerを用いたNNで、前後文の予測や文書分類などの複数のタスクを解くことで得られる文章ベクトル。以下の図のようなNNで、グレーの部分は共通のエンコーダーレイヤーになっている。TransformerについてはBERTの説明欄を参照。

f:id:YukoIshizaki:20200103030254p:plain:w500

(TransformerではなくてDeep Averaging Network のものもある)

元論文 : [1803.11175] Universal Sentence Encoder
参考 1 : Universal Sentence Encoder · Issue #4 · hakubishin3/papers · GitHub
参考 2 : Google AI Blog: Advances in Semantic Textual Similarity

サンプルコード
TensorFlow Hub に事前学習済みUSEがあるので、それを使います。
https://tfhub.dev/google/universal-sentence-encoder/4

import tensorflow_hub as hub
embed = hub.load("https://tfhub.dev/google/universal-sentence-encoder/4")
embeddings = embed(['It takes a great bravery to stand up to out enemies, but just as much to stand up to our friends.'])
print(embeddings)

  >> tf.Tensor([[-6.44176304e-02 -3.21280882e-02 -2.36084983e-02  5.24843968e-02  ...... 
1.38827525e-02 1.35980593e-03 -6.22187331e-02 2.80580819e-02]], shape=(1, 512), dtype=float32)

ELMo

bi-LSTM(双方向LSTM)を複数層重ねたモデルで、各隠れ層の重みづけ線形和を Embedding ベクトルとして得る。前の単語列から1つ先の単語出現率を条件付き確率で表した時の対数尤度と、先の単語列から1つ前の単語出現率を条件付き確率で表した時の対数尤度を最大にするように学習する。利用するときは、入力の埋め込みベクトルと結合する必要がある。

f:id:YukoIshizaki:20200103102212p:plain:w500
元論文 : [1802.05365] Deep contextualized word representations
参考 : 論文メモ:Deep contextualized word representations – きままにNLP – A Technical Blog about NLP and ML

サンプルコード
AllenNLP を使います。
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.0577, 0.8521, -0.3685, ..., 0.0323, -0.1151, 0.2783]]], grad_fn= CopySlices )]

参考 : 固有表現認識器に言語モデルを組み込んで、性能を向上させる - Ahogrammer

BERT

双方向のTransformerを複数層重ねたモデルで、マスク予測と文脈の関連予測のタスクで学習させたもの。エンコーダーとして使うときは双方向Transformerを使い、言語生成をするときなどデコーダーとして使うときは単方向Transformerでデコードする。

f:id:YukoIshizaki:20200103102156p:plain:w250

元論文 : [1810.04805] BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding
参考 : 作って理解する Transformer / Attention - Qiita

Transformerは、エンコーダーをマルチヘッドSelf-AttentionとFFNのブロックを複数重ねたもので、デコーダーをマルチヘッドSelf-AttentionとマルチヘッドSource-Target-AttentionとFFNのブロックを複数重ねたもので構成されたもの。

f:id:YukoIshizaki:20200103114207p:plain:w400

Self-Attentionは前の隠れ層をquery, key, value として(全て同じ)、queryとkeyの内積をsoftmaxに通したものとvalueの行列積をとったもの。Source-Target-Attentionは、queryがデーコーダーの隠れ層で、key, valueがエンコーダの隠れ層としたもので、同じくqueryとkeyの内積をsoftmaxに通したものとvalueの行列積をとったもの。

マルチヘッドにするには、query, key, value をそれぞれヘッドの数に分割して、それぞれで Attention を計算し、結果を結合する。

f:id:YukoIshizaki:20200103115944p:plain:w500

[1706.03762] Attention Is All You Need
論文解説 Attention Is All You Need (Transformer) - ディープラーニングブログ

サンプルコード
Transformersを使います。
Transformers — transformers 4.1.1 documentation

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.6182], .....
[ 0.7766, 0.1315, -0.1458, ..., 0.1757, -0.4855, -0.3783]]], grad_fn=NativeLayerNormBackward)

おわり

2ヶ月前ぐらいからNLPの勉強を始めたのですが、今までBERTとWord2vecぐらいしか知らなかったので、色々知れて良かったです。最近のNLPだとBERTに続き、ARBERTやDistil-BERT、XLNetが主要なモデルといったところでしょうか。この辺も引き続き調べていきたいです。

今回調べたどの方法も、とてもわかりやすいブログや記事が日本語で公開されていたため、理解するのにとても助かりました。それらを公開してくださった方々には感謝です!
また、誤りがありましたら指摘いただけたら嬉しいです。

宣伝

弊社にて毎週木曜日18:30から勉強会をやっているので、興味がある方は遊びに来ていただけたら嬉しいです !
github.com

また、データサイエンティスト職も募集中で、話だけでも聞きたいなどカジュアル面談も受け付けてます !
www.wantedly.com

Pythonサプリ プログラミング学習