TensorFlow 2.0 Alpha : Beginner Tutorials : テキストとシークエンス :- 単語埋め込み (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 04/01/2019
* 本ページは、TensorFlow の本家サイトの TF 2.0 Alpha – Beginner Tutorials – Text and sequences の以下のページを翻訳した上で適宜、補足説明したものです:
* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。
テキストとシークエンス :- 単語埋め込み
このチュートリアルは単語埋め込みを紹介します。それは小さいデータセット上でスクラッチから単語埋め込みを訓練して埋め込みプロジェクター (= Embedding Projector) (下の画像で示されます) を使用してこれらの埋め込みを可視化する完全なコードを含みます。
テキストを数字として表現する
機械学習モデルは入力としてベクトル (数字の配列) を取ります。テキストで作業するとき、行わなければならない最初のことはそれをモデルに供給する前の文字列を数字に変換する (or テキストを「ベクトル化」する) ストラテジーとともに提示されます。このセクションでは、それを行なうための 3 つのストラテジーを見ます。
One-hot エンコーディング
最初のアイデアとして、語彙の各単語を “one-hot” エンコードできるかもしれません。センテンス “The cat sat on the mat” を考えます。このセンテンスの語彙 (or 一意の単語) は (cat, mat, on, sat, the) です。各単語を表わすために、語彙に等しい長さを持つゼロベクトルを作成してから、単語に対応するインデックス内に 1 を置きます。このアプローチは次の図で示されます。
センテンスのエンコーディングを含むベクトルを作成するために、それから各単語のための one-hot ベクトルを連結できるでしょう。
Key point: このアプローチは不十分です。one-hot エンコード・ベクトルは疎です (つまり、殆どのインデックスがゼロです)。語彙に 10,000 単語を持つことを想像してください。各単語を one-hot エンコードするには、要素の 99.99% はゼロであるベクトルを作成するでしょう。
一意の数字を持つ各単語をエンコードする
私達が試すかもしれない 2 番目のアプローチは一意の数字を使用して各単語をエンコードすることです。上の例を続けるのであれば、1 を “cat” に、2 を “mat” 等に割り当てることができるでしょう。それからセンテンス “The cat sat on the mat” を [5, 1, 4, 3, 5, 2] のような密ベクトルとしてエンコードできるでしょう。このアプローチは効率的です。疎ベクトルの代わりに、今では密なひとつ (そこでは総ての要素は満ちています) を持ちます。
けれども、このアプローチには 2 つの欠点があります :
- 整数エンコーディングは恣意的 (= arbitrary) です (それは単語間のどのような関係も捕捉しません)。
- 整数エンコーディングはモデルが解釈することは挑戦的です。例えば、線形分類器は各特徴について単一の重みを学習します。異なる単語は同様のエンコーディングを持つかもしれませんので、この特徴-重みの組み合わせは意味がありません。
単語埋め込み
単語埋め込みは効率的で、密な表現を使用する方法を与えてくれます、そこでは類似した単語は類似したエンコーディングを持ちます。重要なことは、このエンコーディングを手動で指定しなくてかまわないことです。埋め込みは浮動小数点値の密ベクトルです (ベクトルの長さは貴方が指定するパラメータです)。埋め込みのための値を手動で指定する代わりに、それらは訓練可能なパラメータです (モデルが dense 層のための重みを学習するのと同じ方法で、訓練の間にモデルにより学習される重みです)。(小さいデータセットのための) 8-次元から、巨大なデータセットで作業するとき 1024 次元までの単語埋め込みを見ることは一般的です。高い次元の埋め込みは単語間のきめ細かい関係性を捕捉できますが、学習するためにより多くのデータを取ります。
上は単語埋め込みのための図です。各単語は浮動小数点値の 4-次元ベクトルとして表現されます。埋め込みを考えるもう一つの方法は「検索テーブル (= lookup table)」です。これらの重みが学習された後、各単語をそれがテーブルで対応する密ベクトルを検索することでエンコードできます。
埋め込み層を使用する
Keras は単語埋め込みを使用することを容易にします。Embedding 層を見てみましょう。
from __future__ import absolute_import, division, print_function !pip install -q tensorflow==2.0.0-alpha0 import tensorflow as tf from tensorflow import keras from tensorflow.keras import layers # The Embedding layer takes at least two arguments: # the number of possible words in the vocabulary, here 1000 (1 + maximum word index), # and the dimensionality of the embeddings, here 32. embedding_layer = layers.Embedding(1000, 32)
Embedding 層は整数インデックス (これは特定の単語を表します) から密ベクトル (それらの埋め込み) へマップする検索テーブルとして理解できます。埋め込みの次元は、(Dense 層のニューロン数で実験するのとだいたい同じ方法で) それで貴方の問題のために何が上手く動作するかを見るための実験可能なパラメータです。
埋め込み層を作成するとき、その埋め込めのための重みは (丁度任意の他の層のように) ランダムに初期化されます。訓練の間に、それらは徐々に逆伝播で調整されます。ひとたび訓練されれば、学習された単語埋め込みは単語間の類似性を大雑把にエンコードします (何故ならばそれらは貴方のモデルがその上で訓練された特定の問題のために学習されたからです)。
入力として、Embedding 層は shape (samples, sequence_length) の整数の 2D tensor を取ります、そこでは各エントリは整数のシークエンスです。それは可変長のシークエンスを埋め込むことができます。上の埋め込み層に shapes (32, 10) (長さ 10 の 32 シークエンスのバッチ) あるいは (64, 15) (長さ 15 の 64 シークエンスのバッチ) を持つバッチを供給できるでしょう。バッチの総てのシークエンスは同じ長さを持たなければなりませんので、他よりも短いシークエンスはゼロでパディングされるべきで、より長いシークエンスは切り縮められるべきです。
出力として、埋め込み層は shape (samples, sequence_length, embedding_dimensionality) の 3D 浮動小数点 tensor を返します、そのような 3D tensor はそれから RNN で処理できますし、あるいは単純に平坦化されるかプールされるかして Dense 層で処理できます。このチュートリアルでは後者のアプローチを示します、そして前者を学習するために Text Classification with an RNN を参照できます。
スクラッチから埋め込みを学習する
IMDB 映画レビュー上でセンチメント分類器を訓練します。この過程で、スクラッチから埋め込みを学習します。データセットをダウンロードして前処理するコードを通して迅速に進めます (より詳細はこの チュートリアル を見てください)。
vocab_size = 10000 imdb = keras.datasets.imdb (train_data, train_labels), (test_data, test_labels) = imdb.load_data(num_words=vocab_size)
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/imdb.npz 17465344/17464789 [==============================] - 0s 0us/step
インポートされたとき、レビューのテキストは整数エンコードされています (各整数は辞書の特定の単語を表します)。
print(train_data[0])
[1, 14, 22, 16, 43, 530, 973, 1622, 1385, 65, 458, 4468, 66, 3941, 4, 173, 36, 256, 5, 25, 100, 43, 838, 112, 50, 670, 2, 9, 35, 480, 284, 5, 150, 4, 172, 112, 167, 2, 336, 385, 39, 4, 172, 4536, 1111, 17, 546, 38, 13, 447, 4, 192, 50, 16, 6, 147, 2025, 19, 14, 22, 4, 1920, 4613, 469, 4, 22, 71, 87, 12, 16, 43, 530, 38, 76, 15, 13, 1247, 4, 22, 17, 515, 17, 12, 16, 626, 18, 2, 5, 62, 386, 12, 8, 316, 8, 106, 5, 4, 2223, 5244, 16, 480, 66, 3785, 33, 4, 130, 12, 16, 38, 619, 5, 25, 124, 51, 36, 135, 48, 25, 1415, 33, 6, 22, 12, 215, 28, 77, 52, 5, 14, 407, 16, 82, 2, 8, 4, 107, 117, 5952, 15, 256, 4, 2, 7, 3766, 5, 723, 36, 71, 43, 530, 476, 26, 400, 317, 46, 7, 4, 2, 1029, 13, 104, 88, 4, 381, 15, 297, 98, 32, 2071, 56, 26, 141, 6, 194, 7486, 18, 4, 226, 22, 21, 134, 476, 26, 480, 5, 144, 30, 5535, 18, 51, 36, 28, 224, 92, 25, 104, 4, 226, 65, 16, 38, 1334, 88, 12, 16, 283, 5, 16, 4472, 113, 103, 32, 15, 16, 5345, 19, 178, 32]
整数を単語に変換し戻す
整数群をどのようにテキストに変換し戻すかを知ることは有用かもしれません。ここで、整数から文字列へのマッピングを含む辞書オブジェクトに問い合わせるためのヘルパー関数を作成します :
# A dictionary mapping words to an integer index word_index = imdb.get_word_index() # The first indices are reserved word_index = {k:(v+3) for k,v in word_index.items()} word_index[""] = 0 word_index[" "] = 1 word_index[" "] = 2 # unknown word_index[" "] = 3 reverse_word_index = dict([(value, key) for (key, value) in word_index.items()]) def decode_review(text): return ' '.join([reverse_word_index.get(i, '?') for i in text]) decode_review(train_data[0])
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/imdb_word_index.json 1646592/1641221 [==============================] - 0s 0us/step "<START> this film was just brilliant casting location scenery story direction everyone's really suited the part they played and you could just imagine being there robert <UNK> is an amazing actor and now the same being director <UNK> father came from the same scottish island as myself so i loved the fact there was a real connection with this film the witty remarks throughout the film were great it was just brilliant so much that i bought the film as soon as it was released for <UNK> and would recommend it to everyone to watch and the fly fishing was amazing really cried at the end it was so sad and you know what they say if you cry at a film it must have been good and this definitely was also <UNK> to the two little boy's that played the <UNK> of norman and paul they were just brilliant children are often left out of the <UNK> list i think because the stars that play them all grown up are such a big profile for the whole film but these children are amazing and should be praised for what they have done don't you think the whole story was so lovely because it was true and was someone's life after all that was shared with us all"
映画レビューは異なる長さであり得ます。レビューの長さを標準化するために pad_sequences 関数を使用します。
maxlen = 500 train_data = keras.preprocessing.sequence.pad_sequences(train_data, value=word_index[""], padding='post', maxlen=maxlen) test_data = keras.preprocessing.sequence.pad_sequences(test_data, value=word_index[" "], padding='post', maxlen=maxlen)
最初のパッドされたレビューを調べましょう。
print(train_data[0])
[ 1 14 22 16 43 530 973 1622 1385 65 458 4468 66 3941 4 173 36 256 5 25 100 43 838 112 50 670 2 9 35 480 284 5 150 4 172 112 167 2 336 385 39 4 172 4536 1111 17 546 38 13 447 4 192 50 16 6 147 2025 19 14 22 4 1920 4613 469 4 22 71 87 12 16 43 530 38 76 15 13 1247 4 22 17 515 17 12 16 626 18 2 5 62 386 12 8 316 8 106 5 4 2223 5244 16 480 66 3785 33 4 130 12 16 38 619 5 25 124 51 36 135 48 25 1415 33 6 22 12 215 28 77 52 5 14 407 16 82 2 8 4 107 117 5952 15 256 4 2 7 3766 5 723 36 71 43 530 476 26 400 317 46 7 4 2 1029 13 104 88 4 381 15 297 98 32 2071 56 26 141 6 194 7486 18 4 226 22 21 134 476 26 480 5 144 30 5535 18 51 36 28 224 92 25 104 4 226 65 16 38 1334 88 12 16 283 5 16 4472 113 103 32 15 16 5345 19 178 32 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
単純なモデルを作成する
モデルを定義するために Keras Sequential API を使用します。
- 最初の層は Embedding 層です。この層は整数エンコードされた語彙を取り各単語インデックスのための埋め込みベクトルを検索します。これらのベクトルはモデルを訓練するときに学習されます。ベクトルは出力配列に次元を追加します。結果としての次元は : (batch, sequence, embedding) です。
- 次に、GlobalAveragePooling1D 層はシークエンス次元に渡り平均することにより各サンプルのために固定長出力ベクトルを返します。これは可能な最も単純な方法の中で、モデルに可変長の入力を処理することを可能にします。
- この固定長出力ベクトルが 16 隠れユニットを持つ完全結合 (Dense) 層を通してパイプされます。
- 最後の層は単一出力ノードと密に結合されます。sigmoid 活性化関数を使用して、この値は 0 と 1 の間の float で、レビューがポジティブである確率 (or 確信度レベル) を表します。
embedding_dim=16 model = keras.Sequential([ layers.Embedding(vocab_size, embedding_dim, input_length=maxlen), layers.GlobalAveragePooling1D(), layers.Dense(16, activation='relu'), layers.Dense(1, activation='sigmoid') ]) model.summary()
Model: "sequential" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= embedding_1 (Embedding) (None, 500, 16) 160000 _________________________________________________________________ global_average_pooling1d (Gl (None, 16) 0 _________________________________________________________________ dense (Dense) (None, 16) 272 _________________________________________________________________ dense_1 (Dense) (None, 1) 17 ================================================================= Total params: 160,289 Trainable params: 160,289 Non-trainable params: 0 _________________________________________________________________
モデルをコンパイルして訓練する
model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy']) history = model.fit( train_data, train_labels, epochs=30, batch_size=512, validation_split=0.2)
Train on 20000 samples, validate on 5000 samples Epoch 1/30 20000/20000 [==============================] - 2s 97us/sample - loss: 0.6927 - accuracy: 0.5265 - val_loss: 0.6921 - val_accuracy: 0.5212 Epoch 2/30 20000/20000 [==============================] - 2s 84us/sample - loss: 0.6904 - accuracy: 0.5512 - val_loss: 0.6877 - val_accuracy: 0.5928 Epoch 3/30 20000/20000 [==============================] - 2s 84us/sample - loss: 0.6833 - accuracy: 0.6811 - val_loss: 0.6766 - val_accuracy: 0.7204 Epoch 4/30 20000/20000 [==============================] - 2s 84us/sample - loss: 0.6668 - accuracy: 0.7427 - val_loss: 0.6528 - val_accuracy: 0.7448 Epoch 5/30 20000/20000 [==============================] - 2s 83us/sample - loss: 0.6362 - accuracy: 0.7778 - val_loss: 0.6148 - val_accuracy: 0.7808 Epoch 6/30 20000/20000 [==============================] - 2s 83us/sample - loss: 0.5908 - accuracy: 0.8000 - val_loss: 0.5655 - val_accuracy: 0.7986 Epoch 7/30 20000/20000 [==============================] - 2s 82us/sample - loss: 0.5377 - accuracy: 0.8174 - val_loss: 0.5157 - val_accuracy: 0.8146 Epoch 8/30 20000/20000 [==============================] - 2s 83us/sample - loss: 0.4838 - accuracy: 0.8405 - val_loss: 0.4699 - val_accuracy: 0.8278 Epoch 9/30 20000/20000 [==============================] - 2s 83us/sample - loss: 0.4359 - accuracy: 0.8578 - val_loss: 0.4306 - val_accuracy: 0.8492 Epoch 10/30 20000/20000 [==============================] - 2s 82us/sample - loss: 0.3950 - accuracy: 0.8717 - val_loss: 0.4005 - val_accuracy: 0.8586 Epoch 11/30 20000/20000 [==============================] - 2s 83us/sample - loss: 0.3621 - accuracy: 0.8792 - val_loss: 0.3767 - val_accuracy: 0.8646 Epoch 12/30 20000/20000 [==============================] - 2s 81us/sample - loss: 0.3358 - accuracy: 0.8856 - val_loss: 0.3590 - val_accuracy: 0.8682 Epoch 13/30 20000/20000 [==============================] - 2s 82us/sample - loss: 0.3138 - accuracy: 0.8929 - val_loss: 0.3447 - val_accuracy: 0.8712 Epoch 14/30 20000/20000 [==============================] - 2s 82us/sample - loss: 0.2952 - accuracy: 0.8989 - val_loss: 0.3335 - val_accuracy: 0.8724 Epoch 15/30 20000/20000 [==============================] - 2s 83us/sample - loss: 0.2808 - accuracy: 0.9036 - val_loss: 0.3245 - val_accuracy: 0.8766 Epoch 16/30 20000/20000 [==============================] - 2s 83us/sample - loss: 0.2667 - accuracy: 0.9077 - val_loss: 0.3235 - val_accuracy: 0.8736 Epoch 17/30 20000/20000 [==============================] - 2s 82us/sample - loss: 0.2546 - accuracy: 0.9114 - val_loss: 0.3115 - val_accuracy: 0.8808 Epoch 18/30 20000/20000 [==============================] - 2s 82us/sample - loss: 0.2431 - accuracy: 0.9160 - val_loss: 0.3052 - val_accuracy: 0.8822 Epoch 19/30 20000/20000 [==============================] - 2s 83us/sample - loss: 0.2332 - accuracy: 0.9191 - val_loss: 0.3012 - val_accuracy: 0.8846 Epoch 20/30 20000/20000 [==============================] - 2s 83us/sample - loss: 0.2240 - accuracy: 0.9219 - val_loss: 0.2976 - val_accuracy: 0.8868 Epoch 21/30 20000/20000 [==============================] - 2s 83us/sample - loss: 0.2163 - accuracy: 0.9247 - val_loss: 0.2944 - val_accuracy: 0.8884 Epoch 22/30 20000/20000 [==============================] - 2s 83us/sample - loss: 0.2101 - accuracy: 0.9267 - val_loss: 0.2919 - val_accuracy: 0.8888 Epoch 23/30 20000/20000 [==============================] - 2s 84us/sample - loss: 0.2018 - accuracy: 0.9304 - val_loss: 0.2915 - val_accuracy: 0.8882 Epoch 24/30 20000/20000 [==============================] - 2s 83us/sample - loss: 0.1949 - accuracy: 0.9323 - val_loss: 0.2886 - val_accuracy: 0.8910 Epoch 25/30 20000/20000 [==============================] - 2s 84us/sample - loss: 0.1884 - accuracy: 0.9342 - val_loss: 0.2886 - val_accuracy: 0.8904 Epoch 26/30 20000/20000 [==============================] - 2s 83us/sample - loss: 0.1835 - accuracy: 0.9360 - val_loss: 0.2867 - val_accuracy: 0.8918 Epoch 27/30 20000/20000 [==============================] - 2s 82us/sample - loss: 0.1778 - accuracy: 0.9395 - val_loss: 0.2870 - val_accuracy: 0.8910 Epoch 28/30 20000/20000 [==============================] - 2s 82us/sample - loss: 0.1720 - accuracy: 0.9413 - val_loss: 0.2851 - val_accuracy: 0.8904 Epoch 29/30 20000/20000 [==============================] - 2s 82us/sample - loss: 0.1665 - accuracy: 0.9439 - val_loss: 0.2845 - val_accuracy: 0.8930 Epoch 30/30 20000/20000 [==============================] - 2s 81us/sample - loss: 0.1618 - accuracy: 0.9453 - val_loss: 0.2869 - val_accuracy: 0.8930
このアプローチで私達のモデルは 88% まわりの検証精度に到達します (モデルは overfitting し、訓練精度はかなり高いです)。
import matplotlib.pyplot as plt acc = history.history['accuracy'] val_acc = history.history['val_accuracy'] epochs = range(1, len(acc) + 1) plt.plot(epochs, acc, 'bo', label='Training acc') plt.plot(epochs, val_acc, 'b', label='Validation acc') plt.title('Training and validation accuracy') plt.xlabel('Epochs') plt.ylabel('Accuracy') plt.legend(loc='lower right') plt.figure(figsize=(16,9)) plt.show()
<Figure size 640x480 with 1 Axes> <Figure size 1600x900 with 0 Axes>
学習された埋め込みを取得する
次に、訓練の間に学習された単語埋め込みを取得しましょう。これは shape (vocab_size,embedding-dimension) の行列です。
e = model.layers[0] weights = e.get_weights()[0] print(weights.shape) # shape: (vocab_size, embedding_dim)
(10000, 16)
さて重みをディスクに書きます。Embedding Projector を使用するには、タブ区切りフォーマットで 2 つのファイルをアップロードします : (埋め込みを含む) ベクトルのファイル、そして (単語を含む) メタデータのファイルです。
out_v = open('vecs.tsv', 'w') out_m = open('meta.tsv', 'w') for word_num in range(vocab_size): word = reverse_word_index[word_num] embeddings = weights[word_num] out_m.write(word + "\n") out_v.write('\t'.join([str(x) for x in embeddings]) + "\n") out_v.close() out_m.close()
このチュートリアルを Colaboratory で実行している場合、これらのファイルを貴方のローカルマシンにダウンロードするために次のスニペットを使用できます (or ファイルブラウザを使用してください、View -> Table of contents -> File browser)。
# from google.colab import files # files.download('vecs.tsv') # files.download('meta.tsv')
埋め込みを可視化する
私達の埋め込みを可視化するには、それらを embedding projector にアップロードします。
Embedding Projector をオープンします。
- “Load data” をクリックする
- 上で作成された 2 つのファイルをアップロードする : vecs.tsv と meta.tsv.
貴方が訓練した埋め込みが今表示されます。単語をそれらに最も近い近傍を見つけるために検索することができます。例えば、”beautiful” を検索してみてください。”wonderful” のような近傍を見るかもしれません。Note: 埋め込み層を訓練する前に重みがどのようにランダムに初期化されたかに依拠して、貴方の結果は少し異なるかもしれません。
Note: 実験的に、より単純なモデルを使用してより解釈可能な埋め込みを生成できるかもしれません。Dense(16) 層を削除して、モデルを再訓練し、そして埋め込みを再度可視化してみてください。
Next steps
このチュートリアルは小さいデータセット上でスクラッチからどのように単語埋め込みを訓練して可視化するかを示しました。
- Kreas で埋め込みについて更に学習するためには、François Chollet のこれらの ノートブック を勧めます。
- テキスト分類について更に学習するためには (全体的なワークフローを含み、そしてもし貴方がいつ埋め込み vs one-hot エンコーディングを使用するかについて関心があれば)、この実践的なテキスト分類 ガイド を勧めます。
以上