前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >大过年的,一起来用Seq2Seq来作对联吧!

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

作者头像
石晓文
发布2018-04-11 15:33:02
2.4K0
发布2018-04-11 15:33:02
举报
文章被收录于专栏:小小挖掘机小小挖掘机

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

该函数的原型是:

代码语言:javascript
复制
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

该函数的原型是:

代码语言:javascript
复制
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是闭区间。我们来看一个例子大家就明白啦。

代码语言:javascript
复制
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))

输出为:

代码语言:javascript
复制
# 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

该函数的原型是:

代码语言:javascript
复制
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就是张量对应的索引,其他的参数不介绍。看个例子吧:

代码语言:javascript
复制
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)

输出为:

代码语言:javascript
复制
[[ 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是一个构建全链接层的类,如下面的例子:

代码语言:javascript
复制
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的一个类,只能在训练时使用,示例代码如下:

代码语言:javascript
复制
helper = tf.contrib.seq2seq.TrainingHelper(
    input=input_vectors,
    sequence_length=input_lengths)

1.6 tf.contrib.seq2seq.GreedyEmbeddingHelper

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

代码语言:javascript
复制
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,示例代码如下

代码语言:javascript
复制
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). 示例代码如下

代码语言:javascript
复制
outputs, _ = tf.contrib.seq2seq.dynamic_decode(
   decoder=decoder,
   output_time_major=False,
   impute_finished=True,
   maximum_iterations=20)

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

代码语言:javascript
复制
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

该函数的原型是:

代码语言:javascript
复制
tile(
    input,
    multiples,
    name=None
)

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

代码语言:javascript
复制
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))

输出为:

代码语言:javascript
复制
[[ 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

该函数的原型是:

代码语言:javascript
复制
identity(
    input,
    name=None
)

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

1.11 tf.sequence_mask

该函数的原型是:

代码语言:javascript
复制
sequence_mask(
    lengths,
    maxlen=None,
    dtype=tf.bool,
    name=None
)

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

代码语言:javascript
复制
tf.sequence_mask([1, 3, 2], 5)

输出为:

代码语言:javascript
复制
[[True, False, False, False, False],
[True, True, True, False, False],
[True, True, False, False, False]]

1.12 tf.contrib.seq2seq.sequence_loss

该函数的原型是:

代码语言:javascript
复制
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的类,示例如下:

代码语言:javascript
复制
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,示例如下:

代码语言:javascript
复制
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

该函数的原型是:

代码语言:javascript
复制
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。示例如下:

代码语言:javascript
复制
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的输入以及损失计算:

代码语言:javascript
复制
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')

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

代码语言:javascript
复制
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])

输出为:

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

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

代码语言:javascript
复制
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函数,有关该函数的解释我们前文已经介绍过了。

代码语言:javascript
复制
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>标识,告诉模型这代表一个句子的开始。

代码如下:

代码语言:javascript
复制
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。代码如下:

代码语言:javascript
复制
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单元:

代码语言:javascript
复制
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)])

构造全链接的输出层

代码语言:javascript
复制
output_layer = Dense(target_vocab_size,kernel_initializer=tf.truncated_normal_initializer(mean=0.1,stddev=0.1))

构造training decoder

代码语言:javascript
复制
    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

代码语言:javascript
复制
    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代码如下:

代码语言:javascript
复制
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模型:

代码语言:javascript
复制
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。详细的过程我们之前已经介绍过了。

代码语言:javascript
复制
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 的代码如下:

代码语言:javascript
复制
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

训练过程代码如下:

代码语言:javascript
复制
# 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 模型测试

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

代码语言:javascript
复制
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

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-02-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 小小挖掘机 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、代码涉及Tensorflow函数介绍
    • 1.1 tf.contrib.layers.embed_sequence
      • 1.2 tf.strided_slice
        • 1.3 tf.nn.embedding_lookup
          • 1.4 tensorflow.ayers.Dense
            • 1.5 tf.contrib.seq2seq.TrainingHelper
              • 1.6 tf.contrib.seq2seq.GreedyEmbeddingHelper
                • 1.7 tf.contrib.seq2seq.BasicDecoder
                  • 1.8 tf.contrib.seq2seq.dynamic_decode
                    • 1.9 tf.tile
                      • 1.10 tf.identity
                        • 1.11 tf.sequence_mask
                          • 1.12 tf.contrib.seq2seq.sequence_loss
                            • 1.13 tf.train.AdamOptimizer
                              • 1.14 tf.contrib.rnn.LSTMCell
                                • 1.15 tf.contrib.rnn.MultiRNNCell
                                  • 1.16 tf.nn.dynamic_rnn
                                  • 2、代码实现思路
                                  • 2.1 数据处理
                                    • 2.2 模型构建
                                      • 2.2.1Encoder
                                        • 2.2.2 Decoder
                                        • 2.2.3 Seq2Seq模型
                                      • 2.3 定义loss以及optimizer
                                      • 2.4 训练模型
                                        • 2.5 模型测试
                                        • 3、参考文献
                                        相关产品与服务
                                        机器翻译
                                        机器翻译(Tencent Machine Translation,TMT)结合了神经机器翻译和统计机器翻译的优点,从大规模双语语料库自动学习翻译知识,实现从源语言文本到目标语言文本的自动翻译,目前可支持十余种语言的互译。
                                        领券
                                        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档