TensorFlow中Sequence-to-Sequence样例代码详解

更新时间:2023-07-10 00:58:16 阅读: 评论:0

TensorFlow 中Sequence-to-Sequence 样例代码详解
在NLP领域,quence to quence模型有很多应⽤,⽐如机器翻译、⾃动应答机器⼈等。在看懂了相关的论⽂后,我开始研读
TensorFlow提供的源代码,刚开始看时感觉⾮常晦涩,现在基本都弄懂了,我在这⾥主要介绍Sequence-to-Sequence Models⽤到的理论,然后对源代码进⾏详解,也算是对⾃⼰这两周的学习进⾏⼀下总结,如果也能够对您有所帮助的话,那就再好不过了~quence-to-quence 模型
在NLP中最为常见的模型是language model,它的研究对象是单⼀序列,⽽本⽂中的quence to quence模型同时研究两个序列。经典的quence-to-quence模型由两个RNN⽹络构成,⼀个被称为“encoder”,另⼀个则称为“decoder”,前者负责把variable-length序列编码成fixed-length向量表⽰,后者负责把fixed_length向量表⽰解码成variable-length输出,它的基本⽹络结构如下,其中每⼀个⼩圆圈代表⼀个cell,⽐如GRUcell、LSTMcell、multi-layer-GRUcell、multi-layer-GRUcell等。这⾥⽐较直观的解释就是,encoder的最终隐状态c包含了输⼊序列的所有信息,因此可以使⽤c进⾏解码输出。尽管“encoder”或者“decoder”内部存在权值共享,但encoder和decoder之间⼀般具有不同的⼀套参数。在训练quence-to-quence模型时,类似于有监督学习模型,最⼤化⽬标函数
  其中  其中称作输出投影,称作输出偏置,标准化常数计算式为
  Dzmitry Bahdanau⼤⽜考虑到fixed-length向量表⽰会限制encoder-decoder架构的性能,于是进⾏了改进,使得模型在输出单⼀word时,能够⾃动查找到有贡献的输⼊sub-quence,新的模型架构如下图所⽰,
  这⾥的编码器为双向RNN架构,定义条件概率 ,其中隐状态 计算公式为 ,上下⽂向量计算公式为
  其中,权值参数
  是⼀个“alignment model”,⽤于表征输⼊序列的第j个位置和输出序列的第i个位置的匹配程度, 表⽰双向RNN隐状态的合并,即 ,根据RNN序列的特点, 中包含了更多的邻域窗序列内的信息,那么显然 是对 标准化后的形式, 的计算公式的⼏何意义就是,对输⼊序列中所有位置的信息进⾏加权求和,从⽽达到了在输出序列的任⼀time step,都能够从θ=∗arg
logP (y ∣y ,x )θmax n =∑N t =1∑T n t n <t n
n p (y ∣y ,..,y ,c )=t 1t −1g (y ,s ,c )=t −1t exp (w ϕ(y ,z ,c )+Z 1t T t −1t t b )
t w t b t Z =exp (w ϕ(y ,z ,c )+k :y ∈V k ∑k T
t −1t t b )k p (y ∣y ,..,y ,x )=i 1i −1g (y ,s ,c )i −1i i s i s =i f (s ,y ,c )i −1i −1i c =i αh j =1∑T x ij j
α=ij exp (e )
∑k =1T x ik exp (e )
ij e =ij activation (s ,h )i −1j h j h =j [h ;h ]j L j R h j αij e ij c i
输⼊序列中动态获取最为相关的⼦序列信息的效果,在作者⽂章中,这种效果被称作为"attention mechanism"。TensorFlow中q2q库函数
尽算上述算法看起来⽐较复杂,但TensorFlow已经把它们封装成了可以直接调⽤的函数,官⽅教程已经对这些库函数做了⼤体介绍,但我感觉讲的还是不够透彻,故在这⾥重新叙述⼀下(还有⼀些其他的函数,但考虑到它们的接⼝参数都是相似的,就不做太多介绍了~)。
(1)outputs, states = basic_rnn_q2q(encoder_inputs, decoder_inputs, cell)
输⼊参数 :
   encoder_inputs: 它是⼀个⼆维tensor构成的列表对象,其中每⼀个⼆维tensor代表某⼀时刻的输⼊,其尺⼨为[batch_size x input_size],这⾥的            batch_size具体指某⼀时刻输⼊的单词个数,input_size指encoder的长度;
   decoder_inputs: 它是⼀个⼆维tensor构成的列表对象,其中每⼀个⼆维tensor代表某⼀时刻的输⼊,其尺⼨为[batch_size x output_size],这⾥的            batch_size具体指某⼀时刻输⼊的单词个数,output_size指decoder的长度;
   cell: 它是⼀个rnn_cell.RNNCell或者multi-layer-RNNCell对象,其中定义了cell函数和hidden units的个数;
  输出参数 :
   outputs: 它是⼀个⼆维tensor构成的列表对象,其中每⼀个⼆维tensor代表某⼀时刻输出,其尺⼨为[batch_size x
output_size],这⾥的
      batch_size具体指某⼀时刻输⼊的单词个数,output_size指decoder的长度;
   state: 它是⼀个⼆维tensor,表⽰每⼀个decoder cell在最后的time-step的状态,其尺⼨为[batch_size x cell.state_size],这⾥的
      cell.state_size可以表⽰⼀个或者多个⼦cell的状态,视输⼊参数cell⽽定;
(2)outputs, states = embedding_attention_q2q(encoder_inputs, decoder_inputs, …)
输⼊参数 :
   encoder_inputs: 与上⾯的基本函数,它是⼀个⼀维tensor构成的列表对象,其中每⼀个⼀维tensor的尺⼨为[batch_size],代表某⼀时刻的输⼊;
   decoder_inputs: 与encoder_inputs的解释类似;
   cell: 它是⼀个rnn_cell.RNNCell或者multi-layer-RNNCell对象,其中定义了cell函数和hidden units的个数;
   num_encoder_symbols: 具体指输⼊词库的⼤⼩,也即输⼊单词one-hot表⽰后的向量长度;
   num_decoder_symbols: 具体指输出词库的⼤⼩;
   embedding_size: 词库中每⼀个单词“嵌套”后向量的长度;
   num_heads: 默认为1(具体的意义我还没弄明⽩);
   output_projection: 为None或者 (W, B) 元组对象,其中W的尺⼨为[output_size x num_decoder_s
ymbols],B的尺⼨为[num_decoder_symbols],
            显然,解码器每⼀时刻的输出仅共享偏置参数B,权值参数不共享;
   feed_previous: 为True时⽤于模型测试阶段,基于贪婪算法⽣成输出序列,为Fal时⽤于训练模型参数;
αij
   initial_state_attention: 设置初始attention的状态,也即上图中的取值;
  输出参数 :
   outputs: 它是⼀个⼆维tensor构成的列表对象,其中每⼀个⼆维tensor代表某⼀时刻输出,其尺⼨为[batch_size x
num_decoder_symbols];
   state: 它是⼀个⼆维tensor,表⽰每⼀个decoder cell在最后的time-step的状态,其尺⼨为[batch_size x cell.state_size],这⾥的
开根号计算      cell.state_size可以表⽰⼀个或者多个⼦cell的状态,视输⼊参数cell⽽定;
quence-to-quence模型实现中的技巧
做理论和做⼯程还是有区别的,在对quence-to-quence模型进⾏实现时,Google的⼯程师们使⽤了sample softmax策略和bucketing策略,下⾯我们分别对其进⾏讲解。
sample softmax策略
解码器RNN序列在每⼀时刻的输出层为softmax分类器,在对上⾯的⽬标函数求梯度时,表达式中会出现对整个target vocabulary的求和项,显然这样做的计算量是⾮常⼤的,于是⼤⽜们想到了⽤target vocabulary中的⼀个⼦集,来近似对整个词库的求和,⼦集中word的选取采⽤的是均匀采样的策略,从⽽降低了每次梯度更新步骤的计算复杂度,在tensorflow中可以采⽤tf.nn.sampled_softmax_loss函数。bucketing策略
bucketing策略可以⽤于处理不同长度的训练样例,如果我们把训练样例的输⼊和输出长度固定,那么在训练整个⽹络的时候,必然会引⼊很多的PAD辅助单词,⽽这些单词却包含了⽆⽤信息;如果不引⼊PAD辅助单词,每⼀个样例作为⼀个graph的话,因为每⼀个样例的输⼊尺⼨和输出尺⼨⼀般是不⼀样的,所以每⼀个样例定义出的graph也是不⼀样的,因此就会定义出⾮常多的graph,尽管这些grap
包饺子的照片h有相似的sub-graph,但是在训练的时候不能够进⾏并⾏计算,势必会⼤⼤降低模型的训练效率。所以,⼀个折中的⽅法就是,可以设置若⼲个buckets,每个bucket指定⼀个输⼊和输出长度,⽐如教程给的例⼦buckets = [(5, 10), (10, 15), (20, 25), (40, 50)],这样的话,经过bucketing策略处理后,会把所有的训练样例分成4份,其中每⼀份的输⼊序列和输出序列的长度分别相同。为了更好地理解源代码中bucketing的使⽤,我们这⾥补充讲述⼀下。TensorFlow是先定义出Graph,模型的训练过程就是对Graph中参数进⾏更新。对于本例中的Graph⽽⾔,Graph中encoder部分的长度为40,decoder部分的长度为50,在每次采⽤梯度下降法更新模型参数时,会随机地从4个buckets中选择⼀个,并从中随机选取batch个训练样例,此时相当于对当前Graph中的参数进⾏优化,但考虑到4个graph之间存
在“weight share”,因此每个batch中样例的长度不⼀样也是可以的。
装神弄鬼Github源代码解析
整个⼯程主要使⽤了四个源⽂件,q2q.py⽂件是⼀个⽤于创建quence-to-quence模型的库,data_utils.py中包含了对原始数据进⾏预处理的⼀些操作,q2q_model.py⽤于定义machine translation模型,translate.py⽤于训练和测试所定义的翻译模型。因为源代码较长,下⾯仅针对每个.py⽂件,对理解起来可能有困难的代码块进⾏解析。
q2q.py⽂件
这个⽂件中⽐较重要的两个库函数basic_rnn_q2q和embedding_attention_q2q已经在上⼀部分作了介绍,这⾥主要介绍其它的⼏个功能函数。
(1)quence_loss_by_example(logits, targets, weights)
这个函数⽤于计算所有examples的加权交叉熵损失,logits参数是⼀个2D Tensor构成的列表对象,每⼀个2D Tensor的尺⼨为
[batch_size x num_decoder_symbols],函数的返回值是⼀个1D float类型的Tensor,尺⼨为batch_size,其中的每⼀个元素代表当前输⼊序列example的交叉熵。另外,还有⼀个与之类似的函数quence_loss,它对quence_loss_by_example函数返回的结果进⾏了⼀个tf.reduce_sum运算,因此返回的是⼀个标称型float Tensor。
(2)model_with_buckets(encoder_inputs, decoder_inputs, targets, weights, buckets, q2q)
for j, bucket in enumerate(buckets):
with variable_scope.variable_scope(_variable_scope(),
reu=True if j >0el None):
# 函数q2q有两个返回值,因为tf.bedding_attention_q2q函数有两个返回值
bucket_outputs, _ = q2q(encoder_inputs[:bucket[0]],
decoder_inputs[:bucket[1]])
outputs.append(bucket_outputs)
if per_example_loss:
loss.append(quence_loss_by_example(
outputs[-1], targets[:bucket[1]], weights[:bucket[1]],
softmax_loss_function=softmax_loss_function))
el:
loss.append(quence_loss(
outputs[-1], targets[:bucket[1]], weights[:bucket[1]],
softmax_loss_function=softmax_loss_function))
这个函数创建了⼀个⽀持bucketing策略的quence-to-quence模型,它仍然属于Graph的定义阶段。具体来说,这段程序定义了length(buckets)个graph,每个graph的输⼊为总模型的输⼊“占位符”的⼀部分,但这些graphs共享模型参数,函数的返回值outputs 和loss均为列表对象,尺⼨为[length(buckets)],其中每⼀个元素为当前graph的bucket_outputs和bucket_loss。
data_utils.py⽂件
(1)create_vocabulary(vocabulary_path, data_path, max_vocabulary_size)
这个函数⽤于根据输⼊⽂件创建词库,在这⾥data_path参数表⽰输⼊源⽂件的路径,vocabulary_path表⽰输出⽂件的路不识庐山
径,vocabulary_path⽂件中每⼀⾏代表⼀个单词,且按照其在data_path中的出现频数从⼤到⼩排列,⽐如第1⾏为r"_EOS",第2⾏为r"_UNK",第3⾏为r’I’,第4⾏为r"have",第5⾏为r’dream’,…
(2)def data_to_token_ids(data_path, target_path, vocabulary_path)
这个函数⽤于把字符串为元素的数据⽂件转换为以int索引为元素的⽂件,在这⾥data_path表⽰输⼊源
数据⽂件的路径,target_path表⽰输出索引数据⽂件的路径,vocabulary_path表⽰词库⽂件的路径。整个函数把数据⽂件中的每⼀⾏转换为在词库⽂件中的索引值,两单词的索引值之间⽤空格隔开,⽐如返回值⽂件的第⼀⾏为’1 123 235’,第⼆⾏为‘3 1 234 554 879 355’,…
q2q_model.py⽂件
机器学习模型的定义过程,⼀般包括输⼊变量定义、输⼊信息的forward propagation和误差信息的backward propagation三个部分,这三个部分在这个程序⽂件中都得到了很好的体现,下⾯我们结合代码分别进⾏介绍。
(1)输⼊变量的定义
# Feeds for inputs.
lf.decoder_inputs =[]
lf.target_weights =[]
for i in xrange(buckets[-1][0]):# Last bucket is the biggest one.
茼蒿菜的做法
name="encoder{0}".format(i)))
for i in xrange(buckets[-1][1]+1):
lf.decoder_inputs.append(tf.placeholder(tf.int32, shape=[None],
name="decoder{0}".format(i)))
lf.target_weights.append(tf.placeholder(dtype, shape=[None],
name="weight{0}".format(i)))
# Our targets are decoder inputs shifted by one.
targets =[lf.decoder_inputs[i +1]
for i in xrange(len(lf.decoder_inputs)-1)]
与前⾯的⼏个样例不同,这⾥输⼊数据采⽤的是最常见的“占位符”格式,以lf.encoder_inputs为例,
这个列表对象中的每⼀个元素表⽰⼀个占位符,其名字分别为encoder0, encoder1,…,encoder39,encoder{i}的⼏何意义是编码器在时刻i的输⼊。这⾥需要注意的是,在训练阶段执⾏ss.run()函数时会再次⽤到这些变量名字。另外,跟language model类似,targets变量是decoder inputs平移⼀个单位的结果,读者可以结合当前模型的损失函数进⾏理解。
(2)输⼊信息的forward propagation
# Training outputs and loss.
if forward_only:
# 返回每⼀个bucket⼦图模型对应的output和loss
lf.outputs, lf.loss = tf.del_with_buckets(
lf.target_weights, buckets,lambda x, y: q2q_f(x, y,True),
softmax_loss_function=softmax_loss_function)
# If we u output projection, we need to project outputs for decoding.
if output_projection is not None:
for b in xrange(len(buckets)):
lf.outputs[b]=[
tf.matmul(output, output_projection[0])+ output_projection[1]
for output in lf.outputs[b]
]
el:
lf.outputs, lf.loss = tf.del_with_buckets(
lf.target_weights, buckets,
消失的下雨天
lambda x, y: q2q_f(x, y,Fal),
softmax_loss_function=softmax_loss_function)
从代码中可以看到,输⼊信息的forward popagation分成了两种情况,这是因为整个quence to quence模型在训练阶段和测试阶段信息的流向是不⼀样的,这⼀点可以从q2qf函数的do_decode参数值体现出来,⽽do_decoder取值对应的就是
(3)误差信息的backward propagation
 # 返回所有bucket⼦graph的梯度和SGD更新操作,这些⼦graph共享输⼊占位符变量encoder_inputs,区别在于,
# 对于每⼀个bucket⼦图,其输⼊为该⼦图对应的长度。
params = tf.trainable_variables()
if not forward_only:
lf.updates =[]
opt = tf.train.GradientDescentOptimizer(lf.learning_rate)
别让猴子跳回背上for b in xrange(len(buckets)):
gradients = tf.gradients(lf.loss[b], params)
clipped_gradients, norm = tf.clip_by_global_norm(gradients,
max_gradient_norm)
lf.updates.append(opt.apply_gradients(
zip(clipped_gradients, params), global_step=lf.global_step))
这⼀段代码主要⽤于计算损失函数关于参数的梯度。因为只有训练阶段才需要计算梯度和参数更新,所以这⾥有个if判断语句。并且,由于当前定义除了length(buckets)个graph,故返回值lf.updates是⼀个列表对象,尺⼨为length(buckets),列表中第i个元素表⽰
graph{i}的梯度更新操作。
# Input feed: encoder inputs, decoder inputs, target_weights, as provided.
input_feed ={}
for l in xrange(encoder_size):
input_der_inputs[l].name]= encoder_inputs[l]
for l in xrange(decoder_size):
input_feed[lf.decoder_inputs[l].name]= decoder_inputs[l]
input_feed[lf.target_weights[l].name]= target_weights[l]
......
if not forward_only:
output_feed =[lf.updates[bucket_id],# Update Op that does SGD.
lf.loss[bucket_id]]# Loss for this batch.
el:
消积食的蔬菜
output_feed =[lf.loss[bucket_id]]# Loss for this batch.
for l in xrange(decoder_size):# Output logits.
output_feed.append(lf.outputs[bucket_id][l])
outputs = ssion.run(output_feed, input_feed)
模型已经定义完成了,这⾥便开始进⾏模型训练了。上⾯的两个for循环⽤于为之前定义的输⼊占位符赋予具体的数值,这些具体的数值源⾃于get_batch函数的返回值。当ssion.run函数开始执⾏时,当前ssion会对第bucket_id个graph进⾏参数更新操作。

本文发布于:2023-07-10 00:58:16,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/fanwen/fan/89/1075040.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:模型   输出   序列   函数   训练   参数   定义
相关文章
留言与评论(共有 0 条评论)
   
验证码:
推荐文章
排行榜
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图