大过年的,一起来用Seq2Seq来作对联吧!

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

原文发布于微信公众号 - 小小挖掘机(wAIsjwj)

原文发表时间:2018-02-23

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏崔庆才的专栏

TensorFlow layers模块用法

TensorFlow 中的 layers 模块提供用于深度学习的更高层次封装的 API,利用它我们可以轻松地构建模型,这一节我们就来看下这个模块的 API 的具...

1.2K80
来自专栏Python小屋

Python符号计算入门及隐函数图像绘制

感谢国防科大刘万伟老师提供了隐函数图像绘制的原始问题以及完美答案,我又补充了一点符号计算的基础知识。 >>> from sympy import * #定义符号...

42050
来自专栏小鹏的专栏

tf API 研读2:math

TF API数学计算 tf...... :math (1)刚开始先给一个运行实例。         tf是基于图(Graph)的计算系统。而图的节点则是由操作(...

81450
来自专栏ACM算法日常

将树围起来(几何凸包)- HDU 1392

在二维欧几里得空间中,凸包可想象为一条刚好包著所有点的橡皮圈。如下图所示。计算凸包也就是求得外围(蓝线上)的那些点。

10220
来自专栏和蔼的张星的图像处理专栏

730. 所有子集的和递归

给一整数 n, 我们需要求前n个自然数形成的集合的所有可能子集中所有元素的和 样例

14620
来自专栏前端新视界

使用 JS 输出螺旋矩阵

这是我曾经遇到过的面试题,在 LeetCode 上找到了题目的原型,难度中等。题目描述如下:

13820
来自专栏小樱的经验随笔

1283 最小周长

1283 最小周长 题目来源: Codility 基准时间限制:1 秒 空间限制:131072 KB 分值: 5 难度:1级算法题 一个矩形的面积为S,已知该矩...

30160
来自专栏机器之心

资源 | 从数组到矩阵的迹,NumPy常见使用大总结

43490
来自专栏calmound

hust 1607 Triangles(经典好题)

题意:给出一定的点,然后再给出哪些点相连,问最后这些相连点的组成多少个三角形,hash的应用 分析:转载牛人的思想 题意:给一些点的坐标和里面的点构成的一些线段...

30180
来自专栏Python小屋

Python+numpy实现函数向量化

Python本身对向量操作的支持并不是很好,需要借助列表推导式或函数式编程来实现,例如: >>> import random # 生成随机测试数据 >>> x...

85750

扫码关注云+社区

领取腾讯云代金券