TensorFlow : Tutorials : 生成モデル : RNN でテキスト生成 (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 09/02/2018
* TensorFlow 1.10 で更に改訂されています。
* TensorFlow 1.9 でドキュメント構成が変更され、数篇が新規に追加されましたので再翻訳しました。
* 本ページは、TensorFlow の本家サイトの Tutorials – Generative Models の以下のページを翻訳した上で
適宜、補足説明したものです:
* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。
- お住まいの地域に関係なく Web ブラウザからご参加頂けます。事前登録 が必要ですのでご注意ください。
- Windows PC のブラウザからご参加が可能です。スマートデバイスもご利用可能です。
◆ お問合せ : 本件に関するお問い合わせ先は下記までお願いいたします。
株式会社クラスキャット セールス・マーケティング本部 セールス・インフォメーション |
E-Mail:sales-info@classcat.com ; WebSite: https://www.classcat.com/ |
Facebook: https://www.facebook.com/ClassCatJP/ |
RNN を使用するテキスト生成
このノートブックは tf.keras と eager execution を使用して RNN でテキストをどのように生成するかを示します。もし良ければ、より少ないコードを使用して同様の モデル を書くことができます。ここでは、より低いレベルでの実装を示します、これは Neural Machine Translation with Attention のようなより深いサンプルに取り組む前に事前作業として理解するために有用です。
このノートブックは end-to-end なサンプルです。それを実行するとき、Shakespeare の著作のデータセットをダウンロードします。Andrej Karpathy の優れた The Unreasonable Effectiveness of Recurrent Neural Networks から借りた、戯曲のコレクションを使用します。このノートブックはモデルを訓練し、そしてサンプル出力を生成するためにそれを使用します。
下のデフォルト設定で 30 エポックの間の単一層 GRU の訓練後の (string=’w’ で開始した) 出力がここにあります :
were to the death of him
And nothing of the field in the view of hell,
When I said, banish him, I will not burn thee that would live.HENRY BOLINGBROKE:
My gracious uncle–DUKE OF YORK:
As much disgraced to the court, the gods them speak,
And now in peace himself excuse thee in the world.HORTENSIO:
Madam, ‘tis not the cause of the counterfeit of the earth,
And leave me to the sun that set them on the earth
And leave the world and are revenged for thee.GLOUCESTER:
I would they were talking with the very name of means
To make a puppet of a guest, and therefore, good Grumio,
Nor arm’d to prison, o’ the clouds, of the whole field,
With the admire
With the feeding of thy chair, and we have heard it so,
I thank you, sir, he is a visor friendship with your silly your bed.SAMPSON:
I do desire to live, I pray: some stand of the minds, make thee remedies
With the enemies of my soul.MENENIUS:
I’ll keep the cause of my mistress.POLIXENES:
My brother Marcius!Second Servant:
Will’t ple
もちろん、センテンスの幾つかが文法的に正しい一方で、多くは意味を成しません。しかし、以下を考えてください :
- 私達のモデルは文字ベースです (訓練を始めたとき、それは妥当な英語単語をどのようにスペルするか、あるいは単語がテキストのユニットであることさえまだ知りません)。
- 出力の構造は演劇に類似しています (ブロックは話者名で始まり、元のテキストと同様に総て大文字です)。センテンスは一般にピリオドで終わります。テキストを遠くから見れば (あるいは個々の単語を近づいて読まなければ)、それは (演劇の) 脚本からの抜粋であるかのように見えます。
次のステップとして、異なるデータセット上でモデルを訓練する実験ができます – どのような巨大なテキストファイル (ASCII) でもかまいません、そしてその変更を行なうために下のコードの単一の行を修正できます。Have fun!
unidecode ライブラリをインストールする
unicode を ASCII に変換するための役立つライブラリ。
!pip install unidecode
tensorflow をインポートして eager execution を有効にする
# Import TensorFlow >= 1.9 and enable eager execution import tensorflow as tf # Note: Once you enable eager execution, it cannot be disabled. tf.enable_eager_execution() import numpy as np import re import random import unidecode import time
データセットをダウンロードする
このサンプルでは、shakespeare データセット を使用します。貴方の好む任意の他のデータセットも使用できます。
path_to_file = tf.keras.utils.get_file('shakespeare.txt', 'https://storage.googleapis.com/yashkatariya/shakespeare.txt')
データセットを読む
text = unidecode.unidecode(open(path_to_file).read()) # length of text is the number of characters in it print (len(text))
文字からそれらのインデックス、そしてその逆へとマップする辞書を作成します、これは入力をベクトル化するために使用されます。
# unique contains all the unique characters in the file unique = sorted(set(text)) # creating a mapping from unique characters to indices char2idx = {u:i for i, u in enumerate(unique)} idx2char = {i:u for i, u in enumerate(unique)}
# setting the maximum length sentence we want for a single input in characters max_length = 100 # length of the vocabulary in chars vocab_size = len(unique) # the embedding dimension embedding_dim = 256 # number of RNN (here GRU) units units = 1024 # batch size BATCH_SIZE = 64 # buffer size to shuffle our dataset BUFFER_SIZE = 10000
入力と出力 tensor を作成する
入力とターゲット・テキストをベクトル化します、何故ならば私達のモデルは文字列を理解できないからです、数字だけです。
しかしまず、入力と出力ベクトルを作成する必要があります。上で設定した max_length を思い出してください、ここではそれを使用します。入力の max_length チャンクを作成しています、そこでは各入力ベクトルはそのチャンクの最後を除く総ての文字でそしてターゲット・ベクトルはそのチャンクの最初を除く総ての文字です。
例えば、string = ‘tensorflow’ と max_length is 9 を考えます。
すると、input = ‘tensorflo’ そして output = ‘ensorflow’ です。
ベクトル作成後、上で作成した char2idx 辞書を使用して各文字を数字に変換します。
input_text = [] target_text = [] for f in range(0, len(text)-max_length, max_length): inps = text[f:f+max_length] targ = text[f+1:f+1+max_length] input_text.append([char2idx[i] for i in inps]) target_text.append([char2idx[t] for t in targ]) print (np.array(input_text).shape) print (np.array(target_text).shape)
tf.data を使用してバッチを作成してそれらをシャッフルする
dataset = tf.data.Dataset.from_tensor_slices((input_text, target_text)).shuffle(BUFFER_SIZE) dataset = dataset.apply(tf.contrib.data.batch_and_drop_remainder(BATCH_SIZE))
モデルを作成する
Model Subclassing API を使用します、これはモデルを作成してそれを好きなように変更するための完全な柔軟性を与えます。モデルを定義するために 3 つの層を使用します。
- 埋め込み層
- GRU 層 (ここで LSTM 層を使用することができます)
- 完全結合層
class Model(tf.keras.Model): def __init__(self, vocab_size, embedding_dim, units, batch_size): super(Model, self).__init__() self.units = units self.batch_sz = batch_size self.embedding = tf.keras.layers.Embedding(vocab_size, embedding_dim) if tf.test.is_gpu_available(): self.gru = tf.keras.layers.CuDNNGRU(self.units, return_sequences=True, return_state=True, recurrent_initializer='glorot_uniform') else: self.gru = tf.keras.layers.GRU(self.units, return_sequences=True, return_state=True, recurrent_activation='sigmoid', recurrent_initializer='glorot_uniform') self.fc = tf.keras.layers.Dense(vocab_size) def call(self, x, hidden): x = self.embedding(x) # output shape == (batch_size, max_length, hidden_size) # states shape == (batch_size, hidden_size) # states variable to preserve the state of the model # this will be used to pass at every step to the model while training output, states = self.gru(x, initial_state=hidden) # reshaping the output so that we can pass it to the Dense layer # after reshaping the shape is (batch_size * max_length, hidden_size) output = tf.reshape(output, (-1, output.shape[2])) # The dense layer will output predictions for every time_steps(max_length) # output shape after the dense layer == (max_length * batch_size, vocab_size) x = self.fc(output) return x, states
モデルを呼び出して optimizer と損失関数を設定する
model = Model(vocab_size, embedding_dim, units, BATCH_SIZE)
optimizer = tf.train.AdamOptimizer() # using sparse_softmax_cross_entropy so that we don't have to create one-hot vectors def loss_function(real, preds): return tf.losses.sparse_softmax_cross_entropy(labels=real, logits=preds)
モデルを訓練する
ここで GradientTape() の助けを借りてカスタム訓練ループを使用します。
- モデルの隠れ状態をゼロと shape == (batch_size, number of rnn units) で初期化します。モデルを作成する間に定義された関数を呼び出してこれを行ないます。
- 次に、データセットに渡り (バッチ毎に) 反復してその入力に関連して予測と隠れ状態を計算します。
- ここで発生する多くの興味深いことがあります。
- モデルは (0 で初期化された) 隠れ状態を得ます、それを H2 と呼びそして入力の最初のバッチ、それを I0 と呼びましょう。
- それからモデルは予測 P1 と H1 を返します。
- 入力の次のバッチのために、モデルは I1 と H1 を受け取ります。
- ここで興味深いことはモデルに I1 とともに H1 を渡すことです、これがモデルがどのように学習するかです。バッチからバッチへと学習されたコンテキストはこの隠れ状態に含まれます。
- データセットが使い尽くされるまでこれを行ない続けます、そしてそれから新しいエポックを開始してこれを繰り返します。
- 予測を計算した後、上で定義された損失関数を使用して損失を計算します。それからモデル変数 (入力) に関する損失の勾配を計算します。
- 最後に、apply_gradients 関数を使用して optimizer の助けを借りてその方向にステップを踏みます。
Note:- このノートブックを Tesla K80 GPU を持つ Colab で実行する場合、それはエポック毎に 23 秒かかります。
# Training step EPOCHS = 30 for epoch in range(EPOCHS): start = time.time() # initializing the hidden state at the start of every epoch hidden = model.reset_states() for (batch, (inp, target)) in enumerate(dataset): with tf.GradientTape() as tape: # feeding the hidden state back into the model # This is the interesting step predictions, hidden = model(inp, hidden) # reshaping the target because that's how the # loss function expects it target = tf.reshape(target, (-1,)) loss = loss_function(target, predictions) grads = tape.gradient(loss, model.variables) optimizer.apply_gradients(zip(grads, model.variables), global_step=tf.train.get_or_create_global_step()) if batch % 100 == 0: print ('Epoch {} Batch {} Loss {:.4f}'.format(epoch+1, batch, loss)) print ('Epoch {} Loss {:.4f}'.format(epoch+1, loss)) print('Time taken for 1 epoch {} sec\n'.format(time.time() - start))
訓練されたモデルを使用して予測する
下のコードブロックはテキストを生成するために使用されます。
- 開始文字列を選択して隠れ状態を初期化して生成することを望む文字数を設定します。
- start_string と隠れ除隊を使用して予測を得ます。
- それから予測された単語のインデックスを計算するために多項分布を使用します。この予測された単語をモデルへの次の入力として使用します。
- モデルから返される隠れ状態はモデルに供給し返されます、そのためそれは今では単なる一つの単語ではなくより多くのコンテキストを持ちます。次の単語を予測した後、変更された隠れ状態は再度モデルに供給し返され、これはそれが前に予測された単語からのより多くのコンテキストを取得したときにどのように学習するかです。
- 予測を見れば、モデルはいつ大文字にして、パラグラフを作成するかを知りそしてテキストは著作の shakespeare スタイルに従います。これは非常に素晴らしいです!
# Evaluation step(generating text using the model learned) # number of characters to generate num_generate = 1000 # You can change the start string to experiment start_string = 'Q' # converting our start string to numbers(vectorizing!) input_eval = [char2idx[s] for s in start_string] input_eval = tf.expand_dims(input_eval, 0) # empty string to store our results text_generated = '' # low temperatures results in more predictable text. # higher temperatures results in more surprising text # experiment to find the best setting temperature = 1.0 # hidden state shape == (batch_size, number of rnn units); here batch size == 1 hidden = [tf.zeros((1, units))] for i in range(num_generate): predictions, hidden = model(input_eval, hidden) # using a multinomial distribution to predict the word returned by the model predictions = predictions / temperature predicted_id = tf.multinomial(tf.exp(predictions), num_samples=1)[0][0].numpy() # We pass the predicted word as the next input to the model # along with the previous hidden state input_eval = tf.expand_dims([predicted_id], 0) text_generated += idx2char[predicted_id] print (start_string + text_generated)
Next steps
- 開始文字列を異なる文字に、あるいはセンテンスの開始を変更する、
- 異なる (データセット) 上、あるいは異なるパラメータによる訓練で実験する。例えば、Gutenberg プロジェクト は本の巨大なコレクションを含みます。
- 温度パラメータ (= temperature parameter) で実験する。
- もう一つの RNN 層を追加する。
以上