简单的Seq2Seq实现作对联

浏览: 4551

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

微信公众号

推荐 1
本文由 石晓文 创作,采用 知识共享署名-相同方式共享 3.0 中国大陆许可协议 进行许可。
转载、引用前需联系作者,并署名作者且注明文章出处。
本站文章版权归原作者及原出处所有 。内容为作者个人观点, 并不代表本站赞同其观点和对其真实性负责。本站是一个个人学习交流的平台,并不用于任何商业目的,如果有任何问题,请及时联系我们,我们将根据著作权人的要求,立即更正或者删除有关内容。本站拥有对此声明的最终解释权。

0 个评论

要回复文章请先登录注册