TensorFlow 2.0 : 上級 Tutorials : テキスト :- 単語埋め込み (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 11/09/2019
* 本ページは、TensorFlow org サイトの TF 2.0 – Advanced Tutorials – Text の以下のページを翻訳した上で
適宜、補足説明したものです:
* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。
- お住まいの地域に関係なく Web ブラウザからご参加頂けます。事前登録 が必要ですのでご注意ください。
- Windows PC のブラウザからご参加が可能です。スマートデバイスもご利用可能です。
◆ お問合せ : 本件に関するお問い合わせ先は下記までお願いいたします。
株式会社クラスキャット セールス・マーケティング本部 セールス・インフォメーション |
E-Mail:sales-info@classcat.com ; WebSite: https://www.classcat.com/ |
Facebook: https://www.facebook.com/ClassCatJP/ |
テキスト :- 単語埋め込み
このチュートリアルは単語埋め込みを紹介します。それは小さいデータセット上でスクラッチから単語埋め込みを訓練し、そして埋め込みプロジェクター (= 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) です (それは単語間のどのような関係も捕捉しません)。
- 整数エンコーディングはモデルが解釈することは困難な可能性があります。例えば、線形分類器は各特徴について単一の重みを学習します。任意の 2 つの単語の類似性とそれらのエンコーディングの類似性の間に関係はありませんので、この特徴-重みの組み合わせは意味がありません。
単語埋め込み
単語埋め込みは効率的で、密な表現を使用する方法を与えてくれます、そこでは類似した単語は類似したエンコーディングを持ちます。重要なことは、このエンコーディングを手動で指定しなくてもかまわないことです。埋め込みは浮動小数点値の密ベクトルです (ベクトルの長さは貴方が指定するパラメータです)。埋め込みのための値を手動で指定する代わりに、それらは訓練可能なパラメータです (モデルが dense 層のための重みを学習するのと同じ方法で、訓練の間にモデルにより学習される重みです)。(小さいデータセットのための) 8-次元から、巨大なデータセットで作業するときの 1024 次元までの単語埋め込みを見ることは一般的です。高い次元の埋め込みは単語間のきめ細かい関係性を捕捉できますが、学習するためにより多くのデータを取ります。
上は単語埋め込みのための図です。各単語は浮動小数点値の 4-次元ベクトルとして表現されています。埋め込みを考えるもう一つの方法は「検索テーブル (= lookup table)」です。これらの重みが学習された後、各単語をそれがテーブルで対応する密ベクトルを検索することによりエンコードできます。
セットアップ
from __future__ import absolute_import, division, print_function, unicode_literals import tensorflow as tf
from tensorflow import keras from tensorflow.keras import layers import tensorflow_datasets as tfds tfds.disable_progress_bar()
埋め込み層を使用する
Keras は単語埋め込みを使用することを容易にします。Embedding 層を見てみましょう。
Embedding 層は整数インデックス (これは特定の単語を表します) から密ベクトル (それらの埋め込み) へマップする検索テーブルとして理解できます。埋め込みの次元 (or width) は、(Dense 層のニューロン数で実験するのとだいたい同じ方法で) 貴方の問題のために何が上手く動作するかを見るためにそれで実験できるパラメータです。
embedding_layer = layers.Embedding(1000, 5)
埋め込み層を作成するとき、埋め込めのための重みは (丁度任意の他の層のように) ランダムに初期化されます。訓練の間に、それらは逆伝播を通して徐々に調整されます。ひとたび訓練されれば、学習された単語埋め込みは単語間の類似性を大雑把にエンコードします (何故ならばそれらは貴方のモデルがその上で訓練された特定の問題のために学習されたからです)。
整数を埋め込み層に渡せば、結果は各整数を埋め込みテーブルからのベクトルで置き換えます :
If you pass an integer to an embedding layer,
the result replaces each integer with the vector from the embedding table:
result = embedding_layer(tf.constant([1,2,3])) result.numpy()
array([[ 0.03075174, 0.01077418, 0.04825656, -0.02901061, -0.02860381], [-0.01487043, 0.03127805, -0.00348612, 0.00670124, 0.03629971], [ 0.0334234 , -0.02370602, -0.01366466, -0.02364613, -0.00822309]], dtype=float32)
テキストまたはシークエンス問題のために、Embedding 層は shape (samples, sequence_length) の整数の 2D tensor を取ります、そこでは各エントリは整数のシークエンスです。それは可変長のシークエンスを埋め込むことができます。上の埋め込み層に shapes (32, 10) (長さ 10 の 32 シークエンスのバッチ) あるいは (64, 15) (長さ 15 の 64 シークエンスのバッチ) を持つバッチを供給できるでしょう。
返される tensor は入力よりも 1 つ多い軸を持ち、埋め込みベクトルは新しい最後の軸に沿って並べられます。それに (2, 3) 入力バッチを渡すと出力は (2, 3, N) です。
result = embedding_layer(tf.constant([[0,1,2],[3,4,5]])) result.shape
TensorShape([2, 3, 5])
入力としてシークエンスのバッチが与えられたとき、埋め込み層は shape (samples, sequence_length, embedding_dimensionality) の 3D 浮動小数点 tensor を返します。この可変長のシークエンスから固定表現に変換するために、様々な標準的なアプローチがあります。それを Dense 層に渡す前に RNN, Attention あるいは pooling 層を使用できるでしょう。このチュートリアルは pooling を使用します、何故ならばそれが最も単純だからです。Text Classification with an RNN チュートリアルは良い次のステップです。
スクラッチから埋め込みを学習する
このチュートリアルでは IMDB 映画レビュー上でセンチメント分類器を訓練します。この過程で、モデルはスクラッチから埋め込みを学習します。前処理されたデータセットを使用します。
テキストデータセットをスクラッチからロードするためには Loading text チュートリアル を見てください。
(train_data, test_data), info = tfds.load( 'imdb_reviews/subwords8k', split = (tfds.Split.TRAIN, tfds.Split.TEST), with_info=True, as_supervised=True)
Downloading and preparing dataset imdb_reviews (80.23 MiB) to /home/kbuilder/tensorflow_datasets/imdb_reviews/subwords8k/0.1.0... WARNING:tensorflow:From /home/kbuilder/.local/lib/python3.6/site-packages/tensorflow_datasets/core/file_format_adapter.py:209: tf_record_iterator (from tensorflow.python.lib.io.tf_record) is deprecated and will be removed in a future version. Instructions for updating: Use eager execution and: `tf.data.TFRecordDataset(path)` WARNING:tensorflow:From /home/kbuilder/.local/lib/python3.6/site-packages/tensorflow_datasets/core/file_format_adapter.py:209: tf_record_iterator (from tensorflow.python.lib.io.tf_record) is deprecated and will be removed in a future version. Instructions for updating: Use eager execution and: `tf.data.TFRecordDataset(path)` Dataset imdb_reviews downloaded and prepared to /home/kbuilder/tensorflow_datasets/imdb_reviews/subwords8k/0.1.0. Subsequent calls will reuse this data.
エンコーダ ( tfds.features.text.SubwordTextEncoder ) を得て、語彙を素早く見てみます。
語彙の “_” は空白を表します。語彙が (“_” で終わる) 全体単語 (whole words) とより大きい単語を構築するためにそれが使用できる部分単語 (= partial words) をどのように含むかに注意してください :
encoder = info.features['text'].encoder encoder.subwords[:20]
['the_', ', ', '. ', 'a_', 'and_', 'of_', 'to_', 's_', 'is_', 'br', 'in_', 'I_', 'that_', 'this_', 'it_', ' /><', ' />', 'was_', 'The_', 'as_']
映画レビューは異なる長さである可能性があります。レビューの長さを標準化するために padded_batch を使用します。
padded_shapes = ([None],()) train_batches = train_data.shuffle(1000).padded_batch(10, padded_shapes = padded_shapes) test_batches = test_data.shuffle(1000).padded_batch(10, padded_shapes = padded_shapes)
インポートされたとき、レビューのテキストは整数エンコードされています (各整数は語彙内の特定の単語か単語部分 (= word-part) を表します)。trailing ゼロに注意してください、バッチは最大長のサンプルに (合わせて) パッドされているからです。
train_batch, train_labels = next(iter(train_batches)) train_batch.numpy()
array([[ 62, 27, 9, ..., 0, 0, 0], [3923, 34, 1092, ..., 0, 0, 0], [ 12, 180, 31, ..., 0, 0, 0], ..., [ 12, 6981, 64, ..., 0, 0, 0], [ 147, 1, 2639, ..., 0, 0, 0], [ 693, 83, 4, ..., 0, 0, 0]])
単純なモデルを作成する
モデルを定義するために Keras Sequential API を使用します。この場合それは “Continuous bag of words” スタイルモデルです。
- 次に Embedding 層は整数エンコードされた語彙を取り各単語インデックスのための埋め込みベクトルを検索します。これらのベクトルはモデルを訓練するときに学習されます。ベクトルは出力配列に次元を追加します。結果としての次元は : (batch, sequence, embedding) です。
- 次に、GlobalAveragePooling1D 層はシークエンス次元に渡り平均することにより各サンプルのために固定長出力ベクトルを返します。これは可能な最も単純な方法の中で、モデルに可変長の入力を処理することを可能にします。
- この固定長出力ベクトルは 16 隠れユニットを持つ完全結合 (Dense) 層を通してパイプされます。
- 最後の層は単一出力ノードと密に結合されます。sigmoid 活性化関数を使用して、この値は 0 と 1 の間の float で、レビューがポジティブである確率 (or 確信度レベル) を表します。
警告: このモデルはマスキングを使用しませんので、ゼロ・パディングは入力の一部として使用されますので、パディング長は出力に影響を与えます。これを修正するには、masking and padding ガイド を見てください。
embedding_dim=16 model = keras.Sequential([ layers.Embedding(encoder.vocab_size, embedding_dim), layers.GlobalAveragePooling1D(), layers.Dense(1, activation='sigmoid') ]) model.summary()
Model: "sequential" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= embedding_1 (Embedding) (None, None, 16) 130960 _________________________________________________________________ global_average_pooling1d (Gl (None, 16) 0 _________________________________________________________________ dense (Dense) (None, 1) 17 ================================================================= Total params: 130,977 Trainable params: 130,977 Non-trainable params: 0 _________________________________________________________________
モデルをコンパイルして訓練する
model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy']) history = model.fit( train_batches, epochs=10, validation_data=test_batches, validation_steps=20)
Epoch 1/10 2500/2500 [==============================] - 15s 6ms/step - loss: 0.6373 - accuracy: 0.6942 - val_loss: 0.0000e+00 - val_accuracy: 0.0000e+00 Epoch 2/10 2500/2500 [==============================] - 12s 5ms/step - loss: 0.4680 - accuracy: 0.8341 - val_loss: 0.3942 - val_accuracy: 0.8550 Epoch 3/10 2500/2500 [==============================] - 12s 5ms/step - loss: 0.3635 - accuracy: 0.8754 - val_loss: 0.3866 - val_accuracy: 0.8600 Epoch 4/10 2500/2500 [==============================] - 12s 5ms/step - loss: 0.3080 - accuracy: 0.8940 - val_loss: 0.3504 - val_accuracy: 0.8550 Epoch 5/10 2500/2500 [==============================] - 12s 5ms/step - loss: 0.2750 - accuracy: 0.9068 - val_loss: 0.3000 - val_accuracy: 0.8850 Epoch 6/10 2500/2500 [==============================] - 12s 5ms/step - loss: 0.2482 - accuracy: 0.9162 - val_loss: 0.3219 - val_accuracy: 0.8700 Epoch 7/10 2500/2500 [==============================] - 12s 5ms/step - loss: 0.2314 - accuracy: 0.9217 - val_loss: 0.2410 - val_accuracy: 0.9100 Epoch 8/10 2500/2500 [==============================] - 12s 5ms/step - loss: 0.2142 - accuracy: 0.9294 - val_loss: 0.2480 - val_accuracy: 0.9000 Epoch 9/10 2500/2500 [==============================] - 12s 5ms/step - loss: 0.2022 - accuracy: 0.9328 - val_loss: 0.2728 - val_accuracy: 0.9050 Epoch 10/10 2500/2500 [==============================] - 12s 5ms/step - loss: 0.1896 - accuracy: 0.9378 - val_loss: 0.3014 - val_accuracy: 0.8600
このアプローチで私達のモデルはおよそ 88% の検証精度に到達します (モデルは overfitting していることに注意してください、訓練精度はかなり高いです)。
import matplotlib.pyplot as plt history_dict = history.history acc = history_dict['accuracy'] val_acc = history_dict['val_accuracy'] loss = history_dict['loss'] val_loss = history_dict['val_loss'] epochs = range(1, len(acc) + 1) plt.figure(figsize=(12,9)) plt.plot(epochs, loss, 'bo', label='Training loss') plt.plot(epochs, val_loss, 'b', label='Validation loss') plt.title('Training and validation loss') plt.xlabel('Epochs') plt.ylabel('Loss') plt.legend() plt.show() plt.figure(figsize=(12,9)) 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.ylim((0.5,1)) plt.show()
<Figure size 1200x900 with 1 Axes> <Figure size 1200x900 with 1 Axes>
学習された埋め込みを取得する
次に、訓練の間に学習された単語埋め込みを取得しましょう。これは shape (vocab_size,embedding-dimension) の行列です。
e = model.layers[0] weights = e.get_weights()[0] print(weights.shape) # shape: (vocab_size, embedding_dim)
(8185, 16)
今は重みをディスクに書きます。Embedding Projector を使用するには、タブ区切りフォーマットで 2 つのファイルをアップロードします : (埋め込みを含む) ベクトルのファイル、そして (単語を含む) メタデータのファイルです。
encoder = info.features['text'].encoder
import io encoder = info.features['text'].encoder out_v = io.open('vecs.tsv', 'w', encoding='utf-8') out_m = io.open('meta.tsv', 'w', encoding='utf-8') for num, word in enumerate(encoder.subwords): vec = weights[num+1] # skip 0, it's padding. out_m.write(word + "\n") out_v.write('\t'.join([str(x) for x in vec]) + "\n") out_v.close() out_m.close()
このチュートリアルを Colaboratory で実行している場合、これらのファイルを貴方のローカルマシンにダウンロードするために次のスニペットを使用できます (or ファイルブラウザを使用します、View -> Table of contents -> File browser)。
try: from google.colab import files except ImportError: pass else: files.download('vecs.tsv') files.download('meta.tsv')
埋め込みを可視化する
私達の埋め込みを可視化するには、それらを埋め込みプロジェクターにアップロードします。
Embedding Projector をオープンします (これはローカルの TensorBoard インスタンスでも実行できます)。
- “Load data” をクリックする
- 上で作成された 2 つのファイルをアップロードする : vecs.tsv と meta.tsv.
貴方が訓練した埋め込みが今表示されます。単語をそれらに最も近い近傍を見つけるために検索することができます。例えば、”beautiful” を検索してみてください。”wonderful” のような近傍を見るかもしれません。
Note: 埋め込み層を訓練する前に重みがどのようにランダムに初期化されたかに依拠して、貴方の結果は少し異なるかもしれません。
Note: 実験的に、より単純なモデルを使用してより解釈可能な埋め込みを生成できるかもしれません。Dense(16) 層を削除して、モデルを再訓練し、そして埋め込みを再度可視化してみてください。
Next steps
このチュートリアルは小さいデータセット上でスクラッチからどのように単語埋め込みを訓練して可視化するかを示しました。
- リカレント・ニューラルネットワークについて学習するためには、Keras RNN ガイド を見てください。
- テキスト分類について更に学習するためには (全体的なワークフローを含み、そしてもし貴方がいつ埋め込み vs one-hot エンコーディングを使用するかについて関心があれば)、この実践的なテキスト分類 ガイド を勧めます。
以上