Seq2Seq 模型次要在 NLP,机器翻译,序列预测等问题上效果显著。
个别状况下能够合成 Seq2Seq 模型为两个子模型:Encoder 和 Decoder。
- Encoder 的输出为原始的序列数据,输入为通过 NN 泛化的表征 Tensor(惯例操作);此 output 便是 Decoder 的 input。通过 Encoder 进行编码的 raw data,再通过 Decoder 进行解码为另外齐全不同的 output(例如:英文到中文的转换)
那么在构建这样一个 Seq2Seq 模型须要哪几步呢?
1. 定义 Encoder 模型中的数据输出参数
encoder_decoder_model_inputs 创立并返回与模型相干的参数(tf 占位符)
def enc_dec_model_inputs():
inputs = tf.placeholder(tf.int32, [None, None], name='input')
targets = tf.placeholder(tf.int32, [None, None], name='targets')
target_sequence_length = tf.placeholder(tf.int32, [None], name='target_sequence_length')
max_target_len = tf.reduce_max(target_sequence_length)
return inputs, targets, target_sequence_length, max_target_len
- inputs 占位符为了接管原始英文句子,shape==(None,None)别离示意 batch size 和句子长度。这里有个小 trick,不同 batch 中句子的长度可能是不同的,所以不能设置为固定长度。通用解决办法是设置每一个 batch 中最长的句子长度为最大长度(必须通过 Padding 补齐)。
- targets 占位符接管原始中文句子
- target_sequence_length 占位符示意每个句子的长度,shape 为 None,是列张量,与批处理大小雷同。该特定值是与前面的 TrainerHelper 的参数,用于构建用于训练的解码器模型。
- max_target_len 是指从所有指标句子(序列)的长度中获取最大值。target_sequence_length 参数中蕴含所有句子的长度。从中获取最大值的办法是应用 tf.reduce_max。
2. 建设 Decoder 模型
编码模型由两个不同局部组成。第一局部是嵌入层;句子中的每个单词都将应用指定为 encoding_embedding_size 来泛化。这一层对文字信息压缩编码表示。第二局部是 RNN 层。在此实际中,在利用 embedding 之后,多个 LSTM 单元被重叠在一起。当然能够应用不同品种的 RNN 单元,例如 GRU。
def encoding_layer(rnn_inputs, rnn_size, num_layers, keep_prob,
source_vocab_size,
encoding_embedding_size):
""":return: tuple (RNN output, RNN state)"""
embed = tf.contrib.layers.embed_sequence(rnn_inputs,
vocab_size=source_vocab_size, embed_dim=encoding_embedding_size)
stacked_cells = tf.contrib.rnn.MultiRNNCell([tf.contrib.rnn.DropoutWrapper(tf.contrib.rnn.LSTMCell(rnn_size), keep_prob) for _ in range(num_layers)])
outputs, state = tf.nn.dynamic_rnn(stacked_cells,
embed, dtype=tf.float32)
return outputs, state
- embedding layer:tf.keras.layers.Embedding
-
RNN layer:
- TF contrib.rnn.LSTMCell 形容存在多少个外在神经元节点
- TF contrib.rnn.DropoutWrapper 退出 dropout 参数
- TF contrib.rnn.MultiRNNCell 连贯多个 RNN cell
- Encoder model:TF nn.dynamic_rnn 组合 embedding 层与 RNN 层
3. 定义 Decoder 模型中的数据输出参数
对于 Decoder 中的训练和推理,须要不同的输出。在训练过程中,输出由 embedded 后的指标 label 提供;在推理阶段,每个工夫步的输入将是下一时间步的输出。它们也须要 embedding,并且 embedded 向量应在两个不同的阶段之间共享。
- 那么训练过程中 如何让模型晓得接下来就是 target label 呢?答案是在 target label 之前退出标识符。如下:
def process_decoder_input(target_data, target_vocab_to_int, batch_size):
# get '<GO>' id
go_id = target_vocab_to_int['<GO>']
after_slice = tf.strided_slice(target_data, [0, 0], [batch_size, -1], [1, 1])
after_concat = tf.concat([tf.fill([batch_size, 1], go_id), after_slice], 1)
return after_concat
4. 建设 Decoder 模型中的训练局部
解码模型能够思考两个独立的过程,即训练和推理。不是它们具备不同的体系结构,而是它们共享雷同的体系结构及其参数。他们有不同的策略来提供共享模型。
尽管编码器应用 tf.contrib.layers.embed_sequence,但它可能不适用于解码器,就算须要 embedded 其输出。那是因为应该通过训练和推断阶段来共享雷同的 embedded 向量。tf.contrib.layers.embed_sequence 只能在运行之前 embedded 筹备好的数据集。推理过程所需的是动静 embedded 性能。在运行模型之前,不可能嵌入推理过程的输入,因为以后工夫步的输入将是下一时间步的输出。
那么到底在推理过程中是如何嵌入的?下一节将会讲到。须要记住的是训练和推理过程共享雷同的嵌入参数。对于培训局部,应提供嵌入的输出。在推断局部,仅传递训练局部中应用的嵌入参数。
def decoding_layer_train(encoder_state, dec_cell, dec_embed_input,
target_sequence_length, max_summary_length,
output_layer, keep_prob):
"""
Create a training process in decoding layer
:return: BasicDecoderOutput containing training logits and sample_id
"""
dec_cell = tf.contrib.rnn.DropoutWrapper(dec_cell,
output_keep_prob=keep_prob)
# for only input layer
helper = tf.contrib.seq2seq.TrainingHelper(dec_embed_input,
target_sequence_length)
decoder = tf.contrib.seq2seq.BasicDecoder(dec_cell,
helper,
encoder_state,
output_layer)
# unrolling the decoder layer
outputs, _, _ = tf.contrib.seq2seq.dynamic_decode(decoder,
impute_finished=True,
maximum_iterations=max_summary_length)
return outputs
- tf.contrib.seq2seq.TrainingHelper:TrainingHelper 是用来传递 embedded 输出参数的。顾名思义,这只是一个 helper 实例。该实例由 BasicDecoder 调用,这就是构建解码器模型的理论流程。
- tf.contrib.seq2seq.BasicDecoder:BasicDecoder 构建解码器模型。这意味着它将解码器端的 RNN 层与 TrainingHelper 筹备的输出连接起来。
- tf.contrib.seq2seq.dynamic_decode:dynamic_decode 开展解码器模型,以便 BasicDecoder 能够针对每个工夫步长检索理论预测。
5. 建设 decoder 模型中的推理局部
- tf.contrib.seq2seq.GreedyEmbeddingHelper::GreedyEmbeddingHelper 动静获取以后步骤的输入,并将其提供给下一个步骤的输出。为了动静地嵌入每个输出后果,应提供嵌入参数(只是一堆权重值)。同时,GreedyEmbeddingHelper 要求提供与批处理大小和 end_of_sequence_id 雷同数量的 start_of_sequence_id。
def decoding_layer_infer(encoder_state, dec_cell, dec_embeddings, start_of_sequence_id,
end_of_sequence_id, max_target_sequence_length,
vocab_size, output_layer, batch_size, keep_prob):
"""
Create a inference process in decoding layer
:return: BasicDecoderOutput containing inference logits and sample_id
"""
dec_cell = tf.contrib.rnn.DropoutWrapper(dec_cell,
output_keep_prob=keep_prob)
helper = tf.contrib.seq2seq.GreedyEmbeddingHelper(dec_embeddings,
tf.fill([batch_size], start_of_sequence_id),
end_of_sequence_id)
decoder = tf.contrib.seq2seq.BasicDecoder(dec_cell,
helper,
encoder_state,
output_layer)
outputs, _, _ = tf.contrib.seq2seq.dynamic_decode(decoder,
impute_finished=True,
maximum_iterations=max_target_sequence_length)
return outputs
6. 建设 Decoder layer
6.1. Embed the target sequences
- tf.contrib.layers.embed_sequence: 创立嵌入参数的外部示意,因而咱们无奈考察或检索它。相同,您须要通过 TF Variable 手动创立嵌入参数。
- 手动创立的嵌入参数用于训练阶段,在运行训练之前通过 TF nn.embedding_lookup 转换提供的指标数据(句子序列)。带有手动创立的嵌入参数的 TF nn.embedding_lookup 返回的后果与 TF contrib.layers.embed_sequence 类似。对于推理过程,每当通过解码器计算以后工夫步的输入时,它将被共享的嵌入参数嵌入,并成为下一个工夫步的输出。您只须要向 GreedyEmbeddingHelper 提供 embedding 参数,将对处理过程有所帮忙。
- tf.nn.embedding_lookup:简而言之,检索出合乎指定行。
- 对于 tf.variable_scope 相干,请查看此链接
6.2 Construct the decoder RNN layer(s)
Decoder 与 Encoder 中 RNN 的层数必须统一
6.3 创立一个输入层以将解码器的输入映射到咱们词汇表的元素
全连贯层以获取每个单词最初呈现的概率。
def decoding_layer(dec_input, encoder_state,
target_sequence_length, max_target_sequence_length,
rnn_size,num_layers, target_vocab_to_int, target_vocab_size,
batch_size, keep_prob, decoding_embedding_size):
"""
Create decoding layer
:return: Tuple of (Training BasicDecoderOutput, Inference BasicDecoderOutput)
"""
target_vocab_size = len(target_vocab_to_int)
dec_embeddings = tf.Variable(tf.random_uniform([target_vocab_size, decoding_embedding_size]))
dec_embed_input = tf.nn.embedding_lookup(dec_embeddings, dec_input)
cells = tf.contrib.rnn.MultiRNNCell([tf.contrib.rnn.LSTMCell(rnn_size) for _ in range(num_layers)])
with tf.variable_scope("decode"):
output_layer = tf.layers.Dense(target_vocab_size)
train_output = decoding_layer_train(encoder_state,
cells,
dec_embed_input,
target_sequence_length,
max_target_sequence_length,
output_layer,
keep_prob)
with tf.variable_scope("decode", reuse=True):
infer_output = decoding_layer_infer(encoder_state,
cells,
dec_embeddings,
target_vocab_to_int['<GO>'],
target_vocab_to_int['<EOS>'],
max_target_sequence_length,
target_vocab_size,
output_layer,
batch_size,
keep_prob)
return (train_output, infer_output)
7. 建设 Seq2Seq 模型
最终,encoding_layer
, process_decoder_input
, and decoding_layer
等函数组合起来建设 Seq2Seq 模型。
def seq2seq_model(input_data, target_data, keep_prob, batch_size,
target_sequence_length,
max_target_sentence_length,
source_vocab_size, target_vocab_size,
enc_embedding_size, dec_embedding_size,
rnn_size, num_layers, target_vocab_to_int):
"""
Build the Sequence-to-Sequence model
:return: Tuple of (Training BasicDecoderOutput, Inference BasicDecoderOutput)
"""
enc_outputs, enc_states = encoding_layer(input_data,
rnn_size,
num_layers,
keep_prob,
source_vocab_size,
enc_embedding_size)
dec_input = process_decoder_input(target_data,
target_vocab_to_int,
batch_size)
train_output, infer_output = decoding_layer(dec_input,
enc_states,
target_sequence_length,
max_target_sentence_length,
rnn_size,
num_layers,
target_vocab_to_int,
target_vocab_size,
batch_size,
keep_prob,
dec_embedding_size)
return train_output, infer_output
8. 建设动态图,loss 函数,优化器,梯度裁剪(RNN 网络必加)
save_path = 'checkpoints/dev'
(source_int_text, target_int_text), (source_vocab_to_int, target_vocab_to_int), _ = load_preprocess()
max_target_sentence_length = max([len(sentence) for sentence in source_int_text])
train_graph = tf.Graph()
with train_graph.as_default():
input_data, targets, target_sequence_length, max_target_sequence_length = enc_dec_model_inputs()
lr, keep_prob = hyperparam_inputs()
train_logits, inference_logits = seq2seq_model(tf.reverse(input_data, [-1]),
targets,
keep_prob,
batch_size,
target_sequence_length,
max_target_sequence_length,
len(source_vocab_to_int),
len(target_vocab_to_int),
encoding_embedding_size,
decoding_embedding_size,
rnn_size,
num_layers,
target_vocab_to_int)
training_logits = tf.identity(train_logits.rnn_output, name='logits')
inference_logits = tf.identity(inference_logits.sample_id, name='predictions')
# https://www.tensorflow.org/api_docs/python/tf/sequence_mask
# - Returns a mask tensor representing the first N positions of each cell.
masks = tf.sequence_mask(target_sequence_length, max_target_sequence_length, dtype=tf.float32, name='masks')
with tf.name_scope("optimization"):
# Loss function - weighted softmax cross entropy
cost = tf.contrib.seq2seq.sequence_loss(
training_logits,
targets,
masks)
# Optimizer
optimizer = tf.train.AdamOptimizer(lr)
# Gradient Clipping
gradients = optimizer.compute_gradients(cost)
capped_gradients = [(tf.clip_by_value(grad, -1., 1.), var) for grad, var in gradients if grad is not None]
train_op = optimizer.apply_gradients(capped_gradients)
8.1 dataloader
(source_int_text, target_int_text) 为输出数据,(source_vocab_to_int, target_vocab_to_int) 为查找每个值对应的索引值的字典
8.2 create inputs
详见代码
def hyperparam_inputs():
lr_rate = tf.placeholder(tf.float32, name='lr_rate')
keep_prob = tf.placeholder(tf.float32, name='keep_prob')
return lr_rate, keep_prob
8.3 建设 seq2seq model
返回训练与推理后果
8.4 损失函数
TF contrib.seq2seq.sequence_loss:Weighted cross-entropy loss for a sequence of logits. 为时序模型专用
8.5 优化器
8.6 梯度裁剪
RNN 根本都会面临的问题就是训练过程中的梯度爆炸。解决办法就是梯度裁剪。罕用的梯度裁剪有两种办法:
- 间接依据参数的梯度值间接进行裁剪
- 由若干参数的梯度组成向量的 L2 正则化进行裁剪
此处应用的是第一种办法:
通过确定阈值以使梯度放弃在某个边界内。具体一点此处是阈值范畴在 - 1 和 1 之间。下面代码中的构建流程为:
- 通过调用 compute_gradients 手动从优化器中获取梯度值,
- 而后应用 clip_by_value 操作梯度值
- 须要通过调用 apply_gradients 将批改后的梯度放回到优化器中
此框架曾经扩大到生理信号分类我的项目,泛化性能最高。