TensorFlow 实战(五)(4)https://developer.aliyun.com/article/1522833
让我们看看如何在 TensorFlow 中实现这一点。首先,我们将加载刚保存的模型:
model = tf.keras.models.load_model(save_path)
很容易获得编码器模型,因为我们将编码器封装为最终模型中的嵌套模型。可以通过调用 get_layer()函数来取出它。
en_model = model.get_layer("encoder")
之后,我们定义两个输入来表示解码器的两个输入:
d_inp = tf.keras.Input(shape=(1,), dtype=tf.string, name='d_infer_input') d_state_inp = tf.keras.Input(shape=(256,), name='d_infer_state')
正如我们之前讨论的那样,我们定义了两个输入:一个表示解码器的输入(d_inp),另一个表示解码器 GRU 层的状态输入(d_state_inp)。分析形状,d_inp 接受一堆字符串的数组,就像之前一样。d_state_inp 表示 GRU 模型的状态向量,具有 256 个特征维度。
在定义输入后,我们将重现我们在训练模型中构建解码器时遵循的所有步骤。但是,我们将从训练模型中获取层,而不是创建新的随机初始化层。特别是,我们将有以下层来流输输入:
- 解码器的向量化层(产生 d_vectorized_out)
- 解码器的嵌入层(产生 d_emb_out)
- 解码器的 GRU 层(产生 d_gru_out)
- 解码器的完全连接的隐藏层(产生 d_dense1_out)
- 最后的预测层(产生 d_final_out)
我们引入了一个重要的改变来介绍 GRU 模型。请注意,我们将 return_sequences 设置为 False,以确保解码器 GRU 仅返回最后一个输出(即不返回输出序列)。换句话说,GRU 层的输出是一个[None,256]大小的张量。这有助于我们将输出形状匹配到先前定义的 d_state_inp,使递归模型更容易构建。此外,GRU 层以 d_state_inp 作为模型中的 initial_state。这样,我们就可以将输出状态向量作为递归输入馈送到解码器:
# Generate the vectorized output of inp d_vectorizer = model.get_layer('d_vectorizer') d_vectorized_out = d_vectorizer(d_inp) # Generate the embeddings from the vectorized input d_emb_out = model.get_layer('d_embedding')(d_vectorized_out) # Get the GRU layer d_gru_layer = model.get_layer("d_gru") # Since we generate one word at a time, we will not need the return_sequences d_gru_layer.return_sequences = False # Get the GRU out while using d_state_inp from earlier, as the initial state d_gru_out = d_gru_layer(d_emb_out, initial_state=d_state_inp) # Get the dense output d_dense1_out = model.get_layer("d_dense_1")(d_gru_out) # Get the final output d_final_out = model.get_layer("d_dense_final")(d_dense1_out)
是时候定义最终的解码器模型了:
de_model = tf.keras.models.Model( inputs=[d_inp, d_state_inp], outputs=[d_final_out, d_gru_out] )
模型接受 d_inp 和 d_state_inp 作为输入,并产生 d_final_out(即最终预测)和 d_gru_out(即 GRU 输出状态)作为输出。最后,让我们退后一步,并将我们所做的工作封装在一个名为 get_inference_model()的单个函数中,如下面的示例所示。
列出 11.11 号栏目中的机器翻译模型递归推理模型定义
import tensorflow.keras.backend as K K.clear_session() def get_inference_model(save_path): """ Load the saved model and create an inference model from that """ model = tf.keras.models.load_model(save_path) ❶ en_model = model.get_layer("encoder") ❷ d_inp = tf.keras.Input( shape=(1,), dtype=tf.string, name='d_infer_input' ) ❸ d_state_inp = tf.keras.Input(shape=(256,), name='d_infer_state') ❹ d_vectorizer = model.get_layer('d_vectorizer') ❺ d_vectorized_out = d_vectorizer(d_inp) ❺ d_emb_out = model.get_layer('d_embedding')(d_vectorized_out) ❻ d_gru_layer = model.get_layer("d_gru") ❼ d_gru_layer.return_sequences = False ❽ d_gru_out = d_gru_layer(d_emb_out, initial_state=d_state_inp) ❾ d_dense1_out = model.get_layer("d_dense_1")(d_gru_out) ❿ d_final_out = model.get_layer("d_dense_final")(d_dense1_out) ⓫ de_model = tf.keras.models.Model( inputs=[d_inp, d_state_inp], outputs=[d_final_out, d_gru_out] ⓬ ) return en_model, de_model
❶ 加载已保存的训练模型。
❷ 通过调用 get_layer()函数从加载的模型中获取编码器模型。
❸ 定义新推理解码器的第一个输入,一个输入层,以一批字符串为输入。
❹ 定义新推理解码器的第二个输入,一个输入层,以初始状态作为解码器 GRU 的输入状态传递。
❺ 生成解码器的字符串输入的向量化输出。
❻ 从矢量化输入生成嵌入。
❼ 获得解码器的 GRU 层。
❽ 由于我们一次生成一个单词,我们将不需要 return_sequences
。
❾ 在使用先前的 d_state_inp
作为初始状态时获得 GRU 输出。
❿ 获得密集输出。
⓫ 获得最终输出。
⓬ 定义最终的解码器。
然后,我们将定义一个函数来加载我们刚刚保存的词汇表。我们使用 JSON 格式保存了词汇表,加载词汇表所需的所有操作就是调用打开的词汇文件的 json.load()
:
def get_vocabularies(save_dir): """ Load the vocabulary files from a given path""" with open(os.path.join(save_dir, 'en_vocab.json'), 'r') as f: en_vocabulary = json.load(f) with open(os.path.join(save_dir, 'de_vocab.json'), 'r') as f: de_vocabulary = json.load(f) return en_vocabulary, de_vocabulary print("Loading vocabularies") en_vocabulary, de_vocabulary = get_vocabularies( os.path.join('models', 'seq2seq_vocab') ) print("Loading weights and generating the inference model") en_model, de_model = get_inference_model(os.path.join('models', 'seq2seq')) print("\tDone")
接下来,我们已经拥有了生成新翻译所需的一切。如前所述,我们将创建一个输入英文句子(用 sample_en_text
表示)到编码器生成上下文向量的过程/函数。接下来,使用 SOS 标记和上下文向量进行预测,以获得第一个德语标记预测和下一个状态输出。最后,递归地将解码器的输出作为解码器的输入,直到预测的标记为 EOS。以下清单使用我们刚刚构建的推断模型描述了这一功能。
清单 11.12 使用新推断模型生成翻译
def generate_new_translation(en_model, de_model, de_vocabulary, sample_en_text): """ Generate a new translation """ start_token = 'sos' print("Input: {}".format(sample_en_text)) ❶ d_state = en_model.predict(np.array([sample_en_text])) ❷ de_word = start_token ❸ de_translation = [] ❹ while de_word != end_token: ❺ de_pred, d_state = de_model.predict([np.array([de_word]), d_state])❻ de_word = de_vocabulary[np.argmax(de_pred[0])] ❼ de_translation.append(de_word) ❽ print("Translation: {}\n".format(' '.join(de_translation)))
❶ 打印输入。
❷ 获取解码器的初始状态。
❸ 解码器的第一个输入词将始终是起始标记(即其值为 sos)。
❹ 我们在这个列表中收集翻译。
❺ 一直预测,直到我们获得结束标记(即它的值为 eos)。
❻ 使用新状态覆盖先前的状态输入。
❼ 从预测的标记 ID 中获取实际的单词。
❽ 将其添加到翻译中。
让我们在数据集中对几个测试输入运行这个,看看我们得到了什么:
for i in range(5): sample_en_text = test_df["EN"].iloc[i] generate_new_translation(en_model, de_model, de_vocabulary, sample_en_text)
这将输出
Input: The pleasure's all mine. Translation: die [UNK] [UNK] mir eos Input: Tom was asking for it. Translation: tom sprach es zu tun eos Input: He denied having been involved in the affair. Translation: er [UNK] sich auf das [UNK] [UNK] eos Input: Is there something in particular that you want to drink? Translation: gibt es etwas [UNK] wenn du etwas [UNK] eos Input: Don't run. Walk slowly. Translation: [UNK] nicht zu fuß eos
你可以将这些英文短语/句子与谷歌翻译进行比较,看看我们的模型是如何接近它们的。对于在相对简单和小的数据集上训练的模型,我们的模型表现非常好。翻译中存在 [UNK] 标记,因为在语料库中所有不太频繁的词都被替换为 [UNK]。因此,当模型不确定应该在某个位置填充哪个单词时,它可能会输出 [UNK]。
练习 4
您决定使用 LSTM 模型而不是 GRU 模型。正如您所知,LSTM 模型有两个状态:细胞状态和输出状态。您已经构建了编码器,现在正在构建解码器。您计划调整以下代码以使用 LSTM 模型:
d_inp = tf.keras.Input(shape=(1,), dtype=tf.string) d_state_inp = tf.keras.Input(shape=(256,)) d_vectorized_out = de_vectorizer(d_inp) d_emb_out = tf.keras.layers.Embedding(de_vocab+2, 128, mask_zero=True)(d_vectorized_out) d_gru_out = tf.keras.layers.GRU(256)(d_emb_out, initial_state=d_state_inp) d_final_out = tf.keras.layers.Dense( de_vocab+2, activation='softmax' )(d_gru_out) de_model = tf.keras.models.Model( inputs=[d_inp, d_state_inp], outputs=[d_final_out, d_gru_out] )
如果你在 LSTM 层中设置 return_state=True
并在某些兼容输入 x
上调用它,则输出如下
lstm_out, state_h, state_c = tf.keras.layers.LSTM(256, return_state=True)(x)
state_h
和 state_c
分别代表输出状态和细胞状态。
我们已经使用序列到序列架构训练了一个机器翻译模型。在下一章中,我们将探讨如何使用一种称为注意力的技术进一步改进这个模型。
摘要
- 编码器-解码器模式在序列到序列任务中很常见,比如机器翻译。
- 编码器接收源语言输入并生成上下文向量。
- 上下文向量由解码器用于生成目标语言输出(即翻译)。
- tf.keras.layers.experimental.preprocessing.TextVectorization 层允许您将标记化(即将字符串转换为标记列表,然后转换为标记 ID)集成到模型中。这使得模型可以接受字符串而不是数值。
- 在序列到序列任务上训练模型时,可以使用教师强制训练:
- 在教师强制训练中,编码器像往常一样接收源语言输入并生成上下文向量。然后,解码器消耗并预测翻译中的单词。换句话说,解码器在训练时以这样的方式进行,以便在给定翻译中的前一个单词(或多个单词)的情况下预测下一个单词。
- 机器翻译模型产生的翻译质量使用 BLEU 分数进行衡量:
- BLEU 分数使用修改后的精确度指标以及测量翻译的不同 n-gram 上的精确度来计算得分。
- 使用教师强制训练模型后,需要定义一个单独的推理模型,使用训练后的权重:
- 此推理模型具有相同的编码器,但解码器将以先前预测的单词作为下一步的输入,并递归预测单词,直到满足预定义的结束条件为止。
练习答案
练习 1
def vocab_size(ser): cnt = Counter(ser.sum()) return len(cnt)
练习 2
# The decoder en_repeat_out = tf.keras.layers.RepeatVector(de_seq_length)(en_gru_out) d_gru_layer = tf.keras.layers.GRU(256, return_sequences=True, name='d_gru') d_gru_out = d_gru_layer(en_repeat_out, initial_state=gru_out) d_dense_layer_1 = tf.keras.layers.Dense(512, activation='relu', name='d_dense_1') d_dense1_out = d_dense_layer_1(d_gru_out) d_dense_layer_final = tf.keras.layers.Dense( de_vocab+2, activation='softmax', name='d_dense_final' ) d_final_out = d_dense_layer_final(d_dense1_out) # Define the full model model = tf.keras.models.Model( inputs=inp, outputs=d_final_out, name='final_seq2seq' )
练习 3
prev_bleu = None for epoch in range(epochs): bleu_log = [] n_train_batches = en_inputs_raw.shape[0]//batch_size for i in range(n_train_batches): print("Training batch {}/{}".format(i+1, n_train_batches), end='\r') x = [ en_inputs_raw[i*batch_size:(i+1)*batch_size], de_inputs_raw[i*batch_size:(i+1)*batch_size] ] y = vectorizer(de_labels_raw[i*batch_size:(i+1)*batch_size]) model.train_on_batch(x, y) pred_y = model.predict(x) bleu_log.append(bleu_metric.calculate_bleu_from_predictions(y, pred_y)) mean_bleu = np.mean(bleu_log) # The termination criteria if prev_bleu and prev_bleu > mean_bleu: break prev_bleu = mean_bleu
练习 4
d_inp = tf.keras.Input(shape=(1,), dtype=tf.string) d_state_h_inp = tf.keras.Input(shape=(256,)) d_state_c_inp = tf.keras.Input(shape=(256,)) d_vectorized_out = de_vectorizer(d_inp) d_emb_out = tf.keras.layers.Embedding( de_vocab+2, 128, mask_zero=True )(d_vectorized_out) d_lstm_out, d_state_h, d_state_c = tf.keras.layers.LSTM( 256, return_state=True )(d_emb_out, initial_state=[d_state_h_inp, d_state_c_inp]) d_final_out = tf.keras.layers.Dense( de_vocab+2, activation='softmax' )(d_lstm_out) de_model = tf.keras.models.Model( inputs=[d_inp, d_state_h_inp, d_state_c_inp], outputs=[d_final_out, d_state_h, d_state_c] ) de_model.summary()