查看原文
其他

一起来用Seq2Seq来作对联吧!

文文 Python爱好者社区 2019-04-07

作者:石晓文   中国人民大学信息学院在读研究生

个人公众号:小小挖掘机(ID:wAIsjwj)


Seq2Seq全称Sequence to Sequence,在机器翻译、文章摘要等领域有着广泛的应用。其本身很简单,是一个如下图所示的Encoder-Decoder框架。

本文不纠结于Seq2Seq的原理介绍,而是着重介绍代码实战。本文基于python3和tensorflow1.4 实现。

本文代码参照github链接:https://github.com/NELSONZHAO/zhihu 以及 知乎专栏文章:https://zhuanlan.zhihu.com/p/27608348,感谢大神的引路!

1、代码涉及Tensorflow函数介绍

1.1 tf.contrib.layers.embed_sequence

该函数的原型是:

embed_sequence(
   ids,
   vocab_size=None,
   embed_dim=None,
   unique=False,
   initializer=None,
   regularizer=None,
   trainable=True,
   scope=None,
   reuse=None
)

该函数将输入的ids,转换成embeddings,输入的id是整型的维数为[batch_size, doc_length] 的张量,返回维数为[batch_size, doc_length, embed_dim]的张量。

1.2 tf.strided_slice

该函数的原型是:

strided_slice(
   input_,
   begin,
   end,
   strides=None,
   begin_mask=0,
   end_mask=0,
   ellipsis_mask=0,
   new_axis_mask=0,
   shrink_axis_mask=0,
   var=None,
   name=None
)

我们主要有四个参数,input,begin,end和strides。begin和input以及strides和input的维数要一致。begin,end和strides决定了input的每一维要如何剪切。注意,这里end是闭区间。我们来看一个例子大家就明白啦。

data = [[[1, 1, 1], [2, 2, 2]],
           [[3, 3, 3], [4, 4, 4]],
           [[5, 5, 5], [6, 6, 6]]]
x = tf.strided_slice(data,[0,0,0],[1,1,1])
y = tf.strided_slice(data,[0,0,0],[2,2,2],[1,1,1])
z = tf.strided_slice(data,[0,0,0],[2,2,2],[1,2,1])

with tf.Session() as sess:
   print(sess.run(x))
   print(sess.run(y))
   print(sess.run(z))

输出为:

# x
[[[1]]]
# y
[[[1 1]
 [2 2]]

[[3 3]
 [4 4]]]
# z
[[[1 1]]

[[3 3]]]

x的输出为[[[1]]],因为在每一维我们只截取[0,1),因此只保留了[0,0,0]这里的元素,即1。y在每一位截取[0,2),且步长为1,因此剩了8个元素。而z在第二维的步长是2,因此保留[0,0,0],[1,0,0],[1,0,1],[0,0,1]四个元素。

1.3 tf.nn.embedding_lookup

该函数的原型是:

embedding_lookup(
   params,
   ids,
   partition_strategy='mod',
   name=None,
   validate_indices=True,
   max_norm=None
)

tf.nn.embedding_lookup函数的用法主要是选取一个张量里面索引对应的元素。tf.nn.embedding_lookup(params, id):params就是输入张量,id就是张量对应的索引,其他的参数不介绍。看个例子吧:

c = np.random.random([10, 1])
b = tf.nn.embedding_lookup(c, [1, 3])

with tf.Session() as sess:
   sess.run(tf.initialize_all_variables())
   print(sess.run(b))
   print(c)

输出为:

[[ 0.94285588]
[ 0.75749925]]
[[ 0.69653103]
[ 0.94285588]
[ 0.23237639]
[ 0.75749925]
[ 0.53966384]
[ 0.05784376]
[ 0.80573055]
[ 0.90221424]
[ 0.34374387]
[ 0.51868458]]

可以看到,embedding_lookup选择了c中的索引为1和3的元素返回。

1.4 tensorflow.ayers.Dense

Dense是一个构建全链接层的类,如下面的例子:

output_layer = Dense(target_vocab_size,kernel_initializer=tf.truncated_normal_initializer(mean=0.1,stddev=0.1))

target_vocab_size定义了神经元的个数。

1.5 tf.contrib.seq2seq.TrainingHelper

这是用于seq2seq中帮助建立Decoder的一个类,只能在训练时使用,示例代码如下:

helper = tf.contrib.seq2seq.TrainingHelper(
   input=input_vectors,
   sequence_length=input_lengths)

1.6 tf.contrib.seq2seq.GreedyEmbeddingHelper

这是用于seq2seq中帮助建立Decoder的一个类,在预测时使用,示例代码如下:

helper = tf.contrib.seq2seq.GreedyEmbeddingHelper(
     embedding=embedding,
     start_tokens=tf.tile([GO_SYMBOL], [batch_size]),
     end_token=END_SYMBOL)

start_tokens是预测时每个输入的开头的一个标志。

1.7 tf.contrib.seq2seq.BasicDecoder

用于构造一个decoder,示例代码如下

decoder = tf.contrib.seq2seq.BasicDecoder(
   cell=cell,
   helper=helper,
   initial_state=cell.zero_state(batch_size, tf.float32))

1.8 tf.contrib.seq2seq.dynamic_decode

用于构造一个动态的decoder,返回的内容是:
(final_outputs, final_state, final_sequence_lengths).
示例代码如下

outputs, _ = tf.contrib.seq2seq.dynamic_decode(
  decoder=decoder,
  output_time_major=False,
  impute_finished=True,
  maximum_iterations=20)

上面的1.5-1.8结合使用,可以构造一个完整的Decoder:

cell = # instance of RNNCell

if mode == "train":
 helper = tf.contrib.seq2seq.TrainingHelper(
   input=input_vectors,
   sequence_length=input_lengths)
elif mode == "infer":
 helper = tf.contrib.seq2seq.GreedyEmbeddingHelper(
     embedding=embedding,
     start_tokens=tf.tile([GO_SYMBOL], [batch_size]),
     end_token=END_SYMBOL)

decoder = tf.contrib.seq2seq.BasicDecoder(
   cell=cell,
   helper=helper,
   initial_state=cell.zero_state(batch_size, tf.float32))
outputs, _ = tf.contrib.seq2seq.dynamic_decode(
  decoder=decoder,
  output_time_major=False,
  impute_finished=True,
  maximum_iterations=20)

1.9 tf.tile

该函数的原型是:

tile(
   input,
   multiples,
   name=None
)

tf.tile主要的功能就是在tensorflow中对矩阵进行自身进行复制的功能,比如按行进行复制,或是按列进行复制,如下面的例子:

a = tf.constant([[1, 2],[2, 3],[3, 4]], dtype=tf.float32)
tile_a_1 = tf.tile(a, [1,2])
tile_a_2 = tf.tile(a,[2,1])
tile_a_3 = tf.tile(a,[2,2])

with tf.Session() as sess:
   print(sess.run(tile_a_1))
   print(sess.run(tile_a_2))
   print(sess.run(tile_a_3))

输出为:

[[ 1.  2.  1.  2.]
[ 2.  3.  2.  3.]
[ 3.  4.  3.  4.]]
[[ 1.  2.]
[ 2.  3.]
[ 3.  4.]
[ 1.  2.]
[ 2.  3.]
[ 3.  4.]]
[[ 1.  2.  1.  2.]
[ 2.  3.  2.  3.]
[ 3.  4.  3.  4.]
[ 1.  2.  1.  2.]
[ 2.  3.  2.  3.]
[ 3.  4.  3.  4.]]

第一次我们按第二维复制了两倍,即列数变成了两倍,第二次是第一维复制了两倍,所以行数变成了两倍。第三次是两个维度都复制两倍,因此横向纵向都变成了两倍长。

1.10 tf.identity

该函数的原型是:

identity(
   input,
   name=None
)

该函数用于返回一个跟input一样维度和内容的张量。

1.11 tf.sequence_mask

该函数的原型是:

sequence_mask(
   lengths,
   maxlen=None,
   dtype=tf.bool,
   name=None
)

lengths代表的是一个一维数组,代表每一个sequence的长度,那么该函数返回的是一个mask的张量,张量的维数是:(lengths.shape,maxlen):
例如:

tf.sequence_mask([1, 3, 2], 5)

输出为:

[[True, False, False, False, False],
[True, True, True, False, False],
[True, True, False, False, False]]

1.12 tf.contrib.seq2seq.sequence_loss

该函数的原型是:

sequence_loss(
   logits,
   targets,
   weights,
   average_across_timesteps=True,
   average_across_batch=True,
   softmax_loss_function=None,
   name=None
)

用于计算seq2seq中的loss。当我们的输入是不定长的时候,weights参数常常使用我们1.11中得到的mask。

1.13 tf.train.AdamOptimizer

我们都知道,我们进行训练需要使用一个优化器,这里我并不是想讲AdamOptimizer,你当然可以使用其他的优化器。这里我们想要介绍的是在使用优化器之后,我们想要对什么进行优化,在之前的代码中,我们可能用tf.train.Optimizer.minimize更多,这个函数用于最小化loss,并更新var_list。这个函数其实可以拆解成两个函数来实现同样的功能:

tf.train.Optimizer.compute_gradients(loss,var_list=None, gate_gradients=1,
aggregation_method=None,
colocate_gradients_with_ops=False, grad_loss=None),该函数对var_list中的变量计算loss的梯度
该函数为函数minimize()的第一部分,返回一个以元组(gradient, variable)组成的列表。

tf.train.Optimizer.apply_gradients(grads_and_vars, global_step=None, name=None) ,该函数将计算出的梯度应用到变量上,是函数minimize()的第二部分,返回一个应用指定的梯度的操作Operation,对global_step做自增操作。

本文中,将使用以上两个函数来对loss进行优化。

1.14 tf.contrib.rnn.LSTMCell

用于构建一个LSTMCell的类,示例如下:

lstm_cell = tf.contrib.rnn.LSTMCell(rnn_size,initializer=tf.random_uniform_initializer(-0.1,0.1,seed=2))

rnn_size就是我们隐藏层的神经元的数量。

1.15 tf.contrib.rnn.MultiRNNCell

用于构建多层RNN的类,需要传入一个cell的list,示例如下:

def get_decoder_cell(rnn_size):
       decoder_cell = tf.contrib.rnn.LSTMCell(rnn_size,initializer=tf.random_uniform_initializer(-0.1,0.1,seed=2))
       return decoder_cell

cell = tf.contrib.rnn.MultiRNNCell([get_decoder_cell(rnn_size) for _ in range(num_layers)])

1.16 tf.nn.dynamic_rnn

该函数的原型是:

dynamic_rnn(
   cell,
   inputs,
   sequence_length=None,
   initial_state=None,
   dtype=None,
   parallel_iterations=None,
   swap_memory=False,
   time_major=False,
   scope=None
)

用于构造一个动态的rnn模型,返回模型的输出,以及state的值,如果是lstm,那么state是一个tuple,有短时记忆c和长时记忆h。示例如下:

rnn_layers = [tf.nn.rnn_cell.LSTMCell(size) for size in [128, 256]]

# create a RNN cell composed sequentially of a number of RNNCells
multi_rnn_cell = tf.nn.rnn_cell.MultiRNNCell(rnn_layers)

# 'outputs' is a tensor of shape [batch_size, max_time, 256]
# 'state' is a N-tuple where N is the number of LSTMCells containing a
# tf.contrib.rnn.LSTMStateTuple for each cell
outputs, state = tf.nn.dynamic_rnn(cell=multi_rnn_cell,
                                  inputs=data,
                                  dtype=tf.float32)

2、代码实现思路

这里的代码并不是所有的代码,完整的代码参照github(https://github.com/princewen/tensorflow_practice/blob/master/basic_seq2seq.py)。

2.1 数据处理

首先,我们将我们的对联分为上联和下联,上联用于encoder的输入,下联用于decoder的输入以及损失计算:

source = open("data/source.txt",'w')
target = open("data/target.txt",'w')

with open("data/对联.txt",'r') as f:
   lines = f.readlines()
   for line in lines:
       line = line.strip().split(" ")
       print(line)
       source.write(line[0]+'\n')
       target.write(line[1]+'\n')

读入我们的数据,并进行打印:

with open('data/source.txt','r',encoding='utf-8') as f:
   source_data = f.read()

with open('data/target.txt','r',encoding='utf-8') as f:
   target_data = f.read()

print(source_data.split('\n')[:10])
print(target_data.split('\n')[:10])

输出为:

['承上下求索志', '除旧岁破旧俗', '处处春光济美', '处处欢歌遍地', '处处明山秀水', '窗外红梅最艳', '创造万千气象', '春到碧桃树上', '为江山添秀色', '爆竹一声除旧']
['绘春秋振兴图', '迎新年树新风', '年年人物风流', '家家喜笑连天', '家家笑语欢歌', '心头美景尤佳', '建设两个文明', '莺歌绿柳楼前', '与日月争光辉', '桃符万户更新']

在seq2seq中,我们输入的不能是中文字符,必须是整数数字,因此我们要建立一个中文到整数数字的双向转换的字典,同时需要添加以下的几个特殊字符:<PAD>主要用来进行字符补全,<EOS>和<GO>都是用在Decoder端的序列中,告诉解码器句子的起始与结束,<UNK>则用来替代一些未出现过的词或者低频词。。

def extract_character_vocab(data):
   """
   :param data:
   :return: 字符映射表
   """
   special_words = ['<PAD>','<UNK>','<GO>','<EOS>']
   set_words = list(set([character for line in data.split('\n') for character in line]))
   int_to_vocab = {idx:word for idx,word in enumerate(special_words + set_words)}
   vocab_to_int = {word:idx for idx,word in int_to_vocab.items()}

   return int_to_vocab,vocab_to_int

# 得到输入和输出的字符映射表
source_int_to_letter,source_letter_to_int = extract_character_vocab(source_data+target_data)
target_int_to_letter,target_letter_to_int = extract_character_vocab(source_data+target_data)

# 将每一行转换成字符id的list
source_int = [[source_letter_to_int.get(letter,source_letter_to_int['<UNK>'])
              for letter in line] for line in source_data.split('\n')]

target_int = [[target_letter_to_int.get(letter,target_letter_to_int['<UNK>'])
              for letter in line] for line in target_data.split('\n')]

2.2 模型构建

这里,我们构建模型按以下三步,构建Encoder,构建Decoder,二者进行连接建立Seq2Seq模型。

2.2.1Encoder

在Encoder层,我们首先需要对定义输入的tensor,同时要对字母进行Embedding,再输入到LSTM层,这里构建Embedding我们使用的是 embed_sequence函数,有关该函数的解释我们前文已经介绍过了。

def get_encoder_layer(input_data,rnn_size,num_layers,source_sequence_length,source_vocab_size,encoding_embedding_size):
   """
   构造Encoder层

   参数说明:
   - input_data: 输入tensor
   - rnn_size: rnn隐层结点数量
   - num_layers: 堆叠的rnn cell数量
   - source_sequence_length: 源数据的序列长度
   - source_vocab_size: 源数据的词典大小
   - encoding_embedding_size: embedding的大小
   """
   # https://www.tensorflow.org/versions/r1.4/api_docs/python/tf/contrib/layers/embed_sequence
 
   encoder_embed_input = tf.contrib.layers.embed_sequence(input_data,source_vocab_size,encoding_embedding_size)

   def get_lstm_cell(rnn_size):
       lstm_cell = tf.contrib.rnn.LSTMCell(rnn_size,initializer=tf.random_uniform_initializer(-0.1,0.1,seed=2))
       return lstm_cell

   cell = tf.contrib.rnn.MultiRNNCell([get_lstm_cell(rnn_size) for _ in range(num_layers)])

   encoder_output , encoder_state = tf.nn.dynamic_rnn(cell,encoder_embed_input,sequence_length=source_sequence_length,dtype=tf.float32)

   return encoder_output,encoder_state

2.2.2 Decoder

在Decoder端,我们主要要完成以下几件事情:
1、对target数据进行处理
2、构造Decoder
2.1Embedding
2.2构造Decoder层
2.3构造输出层,输出层会告诉我们每个时间序列的RNN输出结果
2.4Training Decoder
2.5Predicting Decoder

我们这里将decoder分为了training和predicting,这两个encoder实际上是共享参数的,也就是通过training decoder学得的参数,predicting会拿来进行预测。那么为什么我们要分两个呢,这里主要考虑模型的robust。
在training阶段,为了能够让模型更加准确,我们并不会把t-1的预测输出作为t阶段的输入,而是直接使用target data中序列的元素输入到Encoder中。而在predict阶段,我们没有target data,有的只是t-1阶段的输出和隐层状态。

而在predict阶段我们没有target data,这个时候前一阶段的预测结果就会作为下一阶段的输入。

下面我们会对这每个部分进行一一介绍。

对target数据进行处理
我们的target数据有两个作用:
1)在训练过程中,我们需要将我们的target序列作为输入传给Decoder端RNN的每个阶段,而不是使用前一阶段预测输出,这样会使得模型更加准确。
2)需要用target数据来计算模型的loss。

我们首先需要对target端的数据进行一步预处理。在我们将target中的序列作为输入给Decoder端的RNN时,序列中的最后一个字母(或单词)其实是没有用的。看下图:

我们此时只看右边的Decoder端,可以看到我们的target序列是[<go>, W, X, Y, Z, <eos>],其中<go>,W,X,Y,Z是每个时间序列上输入给RNN的内容,我们发现,<eos>并没有作为输入传递给RNN。因此我们需要将target中的最后一个字符去掉,同时还需要在前面添加<go>标识,告诉模型这代表一个句子的开始。

代码如下:

def process_decoder_input(data,vocab_to_int,batch_size):

   ending = tf.strided_slice(data,[0,0],[batch_size,-1],[1,1])
   decoder_input = tf.concat([tf.fill([batch_size,1],vocab_to_int['<GO>']),ending],1)

   return decoder_input

我们使用strided_slice进行裁剪,由于是闭区间的缘故,我们在第二维使用-1,即可裁剪掉每一个序列的最后一个输入。随后,使用tail复制了batch_size个'<GO>'的标记,使用concat在axis=1拼接,从而给每一个序列加入了开始标记。

对target数据进行embedding
我们使用了与Encoder不同的embedding方式,通过变量以及embedding_lookup相结合的方式对对target数据进行embedding。代码如下:

target_vocab_size = len(target_letter_to_int)
decoder_embeddings = tf.Variable(tf.random_uniform([target_vocab_size,decoding_embedding_size]))
decoder_embed_input = tf.nn.embedding_lookup(decoder_embeddings,decoder_input)

构造Decoder层
我们构建一个多层的LSTM单元:

def get_decoder_cell(rnn_size):
    decoder_cell = tf.contrib.rnn.LSTMCell(rnn_size,initializer=tf.random_uniform_initializer(-0.1,0.1,seed=2))
    return decoder_cell

cell = tf.contrib.rnn.MultiRNNCell([get_decoder_cell(rnn_size) for _ in range(num_layers)])

构造全链接的输出层

output_layer = Dense(target_vocab_size,kernel_initializer=tf.truncated_normal_initializer(mean=0.1,stddev=0.1))

构造training decoder

   with tf.variable_scope("decode"):
       training_helper = tf.contrib.seq2seq.TrainingHelper(inputs = decoder_embed_input,
                                                           sequence_length = target_sequence_length,
                                                           time_major = False)


       training_decoder = tf.contrib.seq2seq.BasicDecoder(cell,training_helper,encoder_state,output_layer)
       training_decoder_output,_,_ = tf.contrib.seq2seq.dynamic_decode(training_decoder,impute_finished=True,
                                                                       maximum_iterations = max_target_sequence_length)

构造predicting decoder

   with tf.variable_scope("decode",reuse=True):
       # 创建一个常量tensor并复制为batch_size的大小
       start_tokens = tf.tile(tf.constant([target_letter_to_int['<GO>']],dtype=tf.int32),[batch_size],name='start_token')
       predicting_helper = tf.contrib.seq2seq.GreedyEmbeddingHelper(decoder_embeddings,start_tokens,target_letter_to_int['<EOS>'])

       predicting_decoder = tf.contrib.seq2seq.BasicDecoder(cell,
                                                            predicting_helper,
                                                            encoder_state,
                                                            output_layer)
       predicting_decoder_output,_,_ = tf.contrib.seq2seq.dynamic_decode(predicting_decoder,impute_finished = True,
                                                                         maximum_iterations = max_target_sequence_length)

完整的Decoder代码如下:

def decoding_layer(target_letter_to_int,decoding_embedding_size,num_layers,rnn_size,
                  target_sequence_length,max_target_sequence_length,encoder_state,decoder_input):
   '''
   构造Decoder层

   参数:
   - target_letter_to_int: target数据的映射表
   - decoding_embedding_size: embed向量大小
   - num_layers: 堆叠的RNN单元数量
   - rnn_size: RNN单元的隐层结点数量
   - target_sequence_length: target数据序列长度
   - max_target_sequence_length: target数据序列最大长度
   - encoder_state: encoder端编码的状态向量
   - decoder_input: decoder端输入
   '''

   # 1. Embedding
   target_vocab_size = len(target_letter_to_int)
   decoder_embeddings = tf.Variable(tf.random_uniform([target_vocab_size,decoding_embedding_size]))
   decoder_embed_input = tf.nn.embedding_lookup(decoder_embeddings,decoder_input)

   # 构造Decoder中的RNN单元
   def get_decoder_cell(rnn_size):
       decoder_cell = tf.contrib.rnn.LSTMCell(rnn_size,initializer=tf.random_uniform_initializer(-0.1,0.1,seed=2))
       return decoder_cell

   cell = tf.contrib.rnn.MultiRNNCell([get_decoder_cell(rnn_size) for _ in range(num_layers)])

   # Output全连接层
   # target_vocab_size定义了输出层的大小
   output_layer = Dense(target_vocab_size,kernel_initializer=tf.truncated_normal_initializer(mean=0.1,stddev=0.1))


   # 4. Training decoder
   with tf.variable_scope("decode"):
       training_helper = tf.contrib.seq2seq.TrainingHelper(inputs = decoder_embed_input,
                                                           sequence_length = target_sequence_length,
                                                           time_major = False)


       training_decoder = tf.contrib.seq2seq.BasicDecoder(cell,training_helper,encoder_state,output_layer)
       training_decoder_output,_,_ = tf.contrib.seq2seq.dynamic_decode(training_decoder,impute_finished=True,
                                                                       maximum_iterations = max_target_sequence_length)


   # 5. Predicting decoder
   # 与training共享参数

   with tf.variable_scope("decode",reuse=True):
       # 创建一个常量tensor并复制为batch_size的大小
       start_tokens = tf.tile(tf.constant([target_letter_to_int['<GO>']],dtype=tf.int32),[batch_size],name='start_token')
       predicting_helper = tf.contrib.seq2seq.GreedyEmbeddingHelper(decoder_embeddings,start_tokens,target_letter_to_int['<EOS>'])

       predicting_decoder = tf.contrib.seq2seq.BasicDecoder(cell,
                                                            predicting_helper,
                                                            encoder_state,
                                                            output_layer)
       predicting_decoder_output,_,_ = tf.contrib.seq2seq.dynamic_decode(predicting_decoder,impute_finished = True,
                                                                         maximum_iterations = max_target_sequence_length)


   return training_decoder_output,predicting_decoder_output

2.2.3 Seq2Seq模型

上面已经构建完成Encoder和Decoder,下面将这两部分连接起来,构建seq2seq模型:

def seq2seq_model(input_data,targets,lr,target_sequence_length,max_target_sequence_length,
                 source_sequence_length,source_vocab_size,target_vocab_size,encoder_embedding_size,
                 decoder_embedding_size,rnn_size,num_layers):

   _,encoder_state = get_encoder_layer(input_data,
                                       rnn_size,
                                       num_layers,
                                       source_sequence_length,
                                       source_vocab_size,
                                       encoding_embedding_size)

   decoder_input = process_decoder_input(targets,target_letter_to_int,batch_size)

   training_decoder_output,predicting_decoder_output = decoding_layer(target_letter_to_int,
                                                                      decoding_embedding_size,
                                                                      num_layers,
                                                                      rnn_size,
                                                                      target_sequence_length,
                                                                      max_target_sequence_length,
                                                                      encoder_state,
                                                                      decoder_input)

   return training_decoder_output,predicting_decoder_output

2.3 定义loss以及optimizer

接下来,我们定义我们的loss和optimizer。loss的计算使用sequence_loss函数,同时使用AdamOptimizer去最小化这个loss。我们这里没有直接使用minimize方法,而是使用了两步compute_gradients和apply_gradients。详细的过程我们之前已经介绍过了。

train_graph = tf.Graph()

with train_graph.as_default():
   input_data, targets, lr, target_sequence_length, max_target_sequence_length, source_sequence_length = get_inputs()

   training_decoder_output, predicting_decoder_output = seq2seq_model(input_data,
                                                                      targets,
                                                                      lr,
                                                                      target_sequence_length,
                                                                      max_target_sequence_length,
                                                                      source_sequence_length,
                                                                      len(source_letter_to_int),
                                                                      len(target_letter_to_int),
                                                                      encoding_embedding_size,
                                                                      decoding_embedding_size,
                                                                      rnn_size,
                                                                      num_layers)

   training_logits = tf.identity(training_decoder_output.rnn_output,'logits')
   predicting_logits = tf.identity(predicting_decoder_output.sample_id,name='predictions')

   #mask是权重的意思
   #tf.sequence_mask([1, 3, 2], 5)  # [[True, False, False, False, False],
                               #  [True, True, True, False, False],
                               #  [True, True, False, False, False]]
   masks = tf.sequence_mask(target_sequence_length,max_target_sequence_length,dtype=tf.float32,name="masks")

   with tf.name_scope("optimization"):
       cost = tf.contrib.seq2seq.sequence_loss(
           training_logits,
           targets,
           masks
       )

       optimizer = tf.train.AdamOptimizer(lr)

       # minimize函数用于添加操作节点,用于最小化loss,并更新var_list.
       # 该函数是简单的合并了compute_gradients()与apply_gradients()函数返回为一个优化更新后的var_list,
       # 如果global_step非None,该操作还会为global_step做自增操作

       #这里将minimize拆解为了以下两个部分:

       # 对var_list中的变量计算loss的梯度 该函数为函数minimize()的第一部分,返回一个以元组(gradient, variable)组成的列表
       gradients = optimizer.compute_gradients(cost)
       capped_gradients = [(tf.clip_by_value(grad, -5., 5.), var) for grad, var in gradients if grad is not None]
       # 将计算出的梯度应用到变量上,是函数minimize()的第二部分,返回一个应用指定的梯度的操作Operation,对global_step做自增操作
       train_op = optimizer.apply_gradients(capped_gradients)

2.4 训练模型

我们拿出一个batch的数据做为验证集,其余的数据做训练,每次得到一个batch的数据,同时,在训练完成时,对模型进行了保存。
获取batch 的代码如下:

def pad_sentence_batch(sentence_batch,pad_int):
   '''
   对batch中的序列进行补全,保证batch中的每行都有相同的sequence_length

   参数:
   - sentence batch
   - pad_int: <PAD>对应索引号
   '''
   max_sentence = max([len(sentence) for sentence in sentence_batch])
   return [sentence + [pad_int] * (max_sentence - len(sentence)) for sentence in sentence_batch]


def get_batches(targets,sources,batch_size,source_pad_int,target_pad_int):

   for batch_i in range(0,len(sources)//batch_size):
       start_i = batch_i * batch_size
       sources_batch = sources[start_i : start_i + batch_size]
       targets_batch = targets[start_i : start_i + batch_size]

       pad_sources_batch = np.array(pad_sentence_batch(sources_batch,source_pad_int))
       pad_targets_batch = np.array(pad_sentence_batch(targets_batch,target_pad_int))

       targets_lengths = []
       for target in targets_batch:
           targets_lengths.append(len(target))

       source_lengths = []
       for source in sources_batch:
           source_lengths.append(len(source))

       yield pad_targets_batch,pad_sources_batch,targets_lengths,source_lengths

训练过程代码如下:

# Train
train_source = source_int[batch_size:]
train_target = source_int[batch_size:]

# 留出一个batch进行验证
valid_source = source_int[:batch_size]
valid_target = target_int[:batch_size]

(valid_targets_batch, valid_sources_batch, valid_targets_lengths, valid_sources_lengths) = next(get_batches(valid_target, valid_source, batch_size,
                          source_letter_to_int['<PAD>'],
                          target_letter_to_int['<PAD>']))

display_step = 50

checkpoint = "data/trained_model.ckpt"

with tf.Session(graph=train_graph) as sess:
   sess.run(tf.global_variables_initializer())

   for epoch_i in range(1,epochs+1):
       for batch_i,(targets_batch, sources_batch, targets_lengths, sources_lengths) in enumerate(get_batches(
           train_target,train_source,batch_size,source_letter_to_int['<PAD>'],
                          target_letter_to_int['<PAD>']
       )):
           _,loss = sess.run([train_op,cost],feed_dict={
               input_data:sources_batch,
               targets:targets_batch,
               lr:learning_rate,
               target_sequence_length:targets_lengths,
               source_sequence_length:sources_lengths
           })

           if batch_i % display_step == 0:
               # 计算validation loss
               validation_loss = sess.run(
                   [cost],
                   {input_data: valid_sources_batch,
                    targets: valid_targets_batch,
                    lr: learning_rate,
                    target_sequence_length: valid_targets_lengths,
                    source_sequence_length: valid_sources_lengths})

               print('Epoch {:>3}/{} Batch {:>4}/{} - Training Loss: {:>6.3f}  - Validation loss: {:>6.3f}'
                     .format(epoch_i,
                             epochs,
                             batch_i,
                             len(train_source) // batch_size,
                             loss,
                             validation_loss[0]))

   saver = tf.train.Saver()
   saver.save(sess, checkpoint)
   print('Model Trained and Saved')

训练过程:

2.5 模型测试

在训练阶段,我们将模型进行了保存,那么在测试阶段,我们首先将保存的模型进行恢复,再进行预测:

def source_to_seq(text):
   sequence_length = 7
   return [source_letter_to_int.get(word,source_letter_to_int['<UNK>']) for word in text] + [source_letter_to_int['<PAD>']] * (sequence_length - len(text))


input_word = '日丽风和人乐'
text = source_to_seq(input_word)

checkpoint = "data/trained_model.ckpt"
loaded_graph = tf.Graph()

with tf.Session(graph=loaded_graph) as sess:
   loader = tf.train.import_meta_graph(checkpoint+'.meta')
   loader.restore(sess,checkpoint)

   input_data = loaded_graph.get_tensor_by_name('inputs:0')
   logits = loaded_graph.get_tensor_by_name('predictions:0')
   source_sequence_length = loaded_graph.get_tensor_by_name('source_sequence_length:0')
   target_sequence_length = loaded_graph.get_tensor_by_name('target_sequence_length:0')

   answer_logits = sess.run(logits, {input_data: [text] * batch_size,
                                     target_sequence_length: [len(input_word)] * batch_size,
                                     source_sequence_length: [len(input_word)] * batch_size})[0]

   pad = source_letter_to_int["<PAD>"]

   print('原始输入:', input_word)

   print('\nSource')
   print('  Word 编号:    {}'.format([i for i in text]))
   print('  Input Words: {}'.format(" ".join([source_int_to_letter[i] for i in text])))

   print('\nTarget')
   print('  Word 编号:       {}'.format([i for i in answer_logits if i != pad]))
   print('  Response Words: {}'.format(" ".join([target_int_to_letter[i] for i in answer_logits if i != pad])))

我们预测一个五字对联:

我们再预测一个七字对联:

哈哈,感觉对的还不错呀,起码字数能够统一起来,嘻嘻!

3、参考文献

参考文献
1、seq2seq学习笔记:http://blog.csdn.net/jerr__y/article/details/53749693
2、从Encoder到Decoder实现Seq2Seq模型:https://zhuanlan.zhihu.com/p/27608348
3、Tensorflow一些常用基本概念与函数(4):
http://blog.csdn.net/lenbow/article/details/52218551
4、tf.strided_slice使用简介:https://www.jianshu.com/p/a1a9e44708f6
5、tf.nn.embedding_lookup函数的用法“
http://blog.csdn.net/uestc_c2_403/article/details/72779417
6、http://blog.csdn.net/zj360202/article/details/78872076
7、tf.identity的意义以及用例:http://blog.csdn.net/zj360202/article/details/78872076

github连接
https://github.com/princewen/tensorflow_practice/blob/master/basic_seq2seq.py

Python爱好者社区历史文章大合集

Python爱好者社区历史文章列表(每周append更新一次)

福利:文末扫码立刻关注公众号,“Python爱好者社区”,开始学习Python课程:

关注后在公众号内回复“课程”即可获取:

小编的Python入门免费视频课程!!!

【最新免费微课】小编的Python快速上手matplotlib可视化库!!!

崔老师爬虫实战案例免费学习视频。

陈老师数据分析报告制作免费学习视频。

玩转大数据分析!Spark2.X+Python 精华实战课程免费学习视频。


    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存