tensorflow实现seq2seq模型细节(5):如何实现带attention的beam search,tensorflow构建独立的计算图(子图),推理图加载训练图的参数达到参数共享
为什么会有这样的问题,在对encoder构建attention时,训练时传入到AttentionWrapper的参数和解码时是不一样的。
构造attention的方法:
def build_rnn_layer_attention(self,encoder_output,decoder_layer,decoder_num_units,src_seq_len):
#scaled_luong normed bahdanau
attention = None
if self.attention_mechanism == 'scaled_luong':
attention = tf.contrib.seq2seq.LuongAttention(
decoder_num_units, encoder_output,
memory_sequence_length=src_seq_len,scale=True)
else:
attention = tf.contrib.seq2seq.BahdanauAttention(decoder_num_units,
encoder_output, src_seq_len, normalize = True)
decoder_cell = tf.contrib.seq2seq.AttentionWrapper(
decoder_layer, attention,
attention_layer_size=decoder_num_units,name='attention')
return decoder_cell
构造训练时decoder的attention参数:
self.decoder_rnn_layer = self.get_rnn_layer(decoder_rnn_layer_size, decoder_num_units, decoder_cell_type)
#加入attention
self.decoder_rnn_layer_attention = self.build_rnn_layer_attention(encoder_outputs, self.decoder_rnn_layer, decoder_num_units,self.src_batch_seq_len)
构造预测/推理阶段attention时,需要对encoder_outputs,encoder_state,src_seq_len(源序列长度)做tile_batch。
memory = tf.contrib.seq2seq.tile_batch(
encoder_outputs, multiplier=beam_width)
src_seq_len = tf.contrib.seq2seq.tile_batch(
self.src_batch_seq_len, multiplier=beam_width)
encoder_state = tf.contrib.seq2seq.tile_batch(
encoder_states, multiplier=beam_width)
batch_size = self.inference_batch_size * beam_width
inference_rnn_layer_attention = self.build_rnn_layer_attention(memory, rnn_layer, decoder_num_units,src_seq_len)
这导致了不能直接使用variable_scope来重用attention的参数,因为无论如何你需要在推理/预测阶段重新构建一个attention,传入不同的参数。接下来看看我找到的解决方法。
在nmt的教程中,有attention的模型使用beam search我看到实现中又声明了新的attention,但是令人疑惑的就是声明新的attention如何使用到训练过的参数,因为训练过程和推理过程传入attention的参数不一样,无论如何不可能使用同一个声明的attention,这个问题困扰了我很久。
Google后我终于找到了几乎一样的问题。
Stackoverflow上:https://stackoverflow.com/questions/46021216/implementing-attention-with-beam-search-in-tensorflow
GitHub nmt的issue:
https://github.com/tensorflow/nmt/issues/93
最后讨论出的解决方案是,训练和推理构建独立的计算图,推理阶段使用训练图中的参数,具体实现待求证。
- 方法一
被AttentionWrapper包装的rnn cell有一个方法set_weights和get_weights
我尝试在训练完成后将训练的attention的weights传递给推理的attention
但是weights的个数竟然不一样,传递了shape相同的2个weight后,测试发现
似乎是有效果的,但还是有问题,
- 方法二 之前提到stackoverflow探讨的方法
https://github.com/piyank22/Blog/blob/master/TheJourneyOfNLP.ipynb
关于这个问题的解决在clever way to implement two seperate graphs with the same trainable variables
这位老兄的建议如下:
我去看了代码,做法也大概是上面这样的:
训练和推理构建两个不同的图,先对训练图进行训练然后保存,
恢复时使用推理图从训练图中恢复参数。
这里nlp_nmt和nlp_nmt-2应该是一样的,估计是他写的时候忘记改了。
按照这个方法,这个问题被完美解决了。需要注意一个问题就是构建不同子图时需要保证他们的相同部分的name_scope或者variable_scope一样,不然会出现找不到tensor的问题。
大概解决思路,训练和推理生成2个不同的子图,保存空的推理子图和训练好参数的训练图,加载模型时使用推理图但是恢复参数时从训练图中恢复。
#加载模型
def load_model(self):
self.inference_graph = tf.Graph()
self.sess = tf.InteractiveSession(graph=self.inference_graph)
dir,file = os.path.split(self.model_path)
# 导入的计算图是inference的
loader = tf.train.import_meta_graph(dir+'/inference/'+file+".inference"+ '.meta')
#导入的数据是训练图的
loader.restore(self.sess, self.model_path)
我觉得这是一个很有用的方法可以实现任何参数共享的问题。