下面使用了双向LSTM和注意力机制
一.数据集准备
我用的是 小黄鸡问答语料库,分为分词前,分词后,我用的是分词后
二.数据预处理
1.生成词汇表(这里选取的前2500个常用词)
import collections
import codecs
from operator import itemgetter
counter=collections.Counter()
with codecs.open('C://RNN//Chat//小黄鸡.conv',encoding='utf-8') as f:
for line in f:
for word in line.strip().split():
for w in word:
if w!='M' and w!='E':
counter[w]+=1
sorted_word_to_cnt=sorted(counter.items(),key=itemgetter(1),reverse=True)
sorted_words=[x[0] for x in sorted_word_to_cnt]
sorted_words=["","",""]+sorted_words
sorted_words=sorted_words[:2500]
with codecs.open('C://RNN//Chat//vocab.txt','w',encoding='utf-8') as f:
for word in sorted_words:
f.write(word+"\n")
这里可以看出,从零开始计数的话,偶数的M 后面跟的是问题,奇数M后面跟的是回答,所以按照如下分开
import codecs
import numpy as np
que=[]
ans=[]
index=0
with codecs.open('C://RNN//Chat//train.txt','r','utf-8') as f:
for line in f.readlines():
if line[0]=='M':
if index % 2 == 0:
que.append(line[2:])
index += 1
elif index %2==1:
index+=1
ans.append(line[2:])
np.save('C://RNN//Chat//question.npy',que)
np.save('C://RNN//Chat//answer.npy',ans)
3.把问题与答案转化为数字(回答 同下)
ques=np.load("C://RNN//Chat//question.npy")
convert_qs=[]
for line in ques:
for word in line:
convert_qs.append(get_id(word))
convert_qs.append('\n')
np.savetxt('convert_qs.txt',convert_qs,fmt ='%s')
4.接下来还是数据的处理(这里处理后每一个batch的每一个例子是
((src_input,src_len),(trg_input,trg_label,trg_len))
解码器需要两种格式的目标句子: # 1.解码器的输入(trg_input),形式如同"X Y Z" # 2.解码器的目标输出(trg_label),形式如同"X Y Z " # 上面从文件中读到的目标句子是"X Y Z "的形式,我们需要从中生成" X Y Z" # 形式并加入到Dataset中。
import tensorflow as tf
question_path='C://RNN//Chat//convert_qs.txt'
answer_path='C://RNN//Chat//convert_as.txt'
MAX_LEN=30
SOS_ID=1
HIDDEN_SIZE=1024
NUM_LAYERS=2
VOCAB_SIZE=2500
SHARE_EMB_AND_SOFTMAX=True
KEEP_PROB=0.8
MAX_GRAD_NORM=5
LR=1
CHECKPOINT_PATH='C://RNN//Chat//save'
BATCH_SIZE=64
NUM_EPOCH=20
def MakeDataset(file_path):
dataset=tf.data.TextLineDataset(file_path)
dataset=dataset.map(lambda string:tf.string_split([string]).values)
dataset=dataset.map(lambda string:tf.string_to_number(string,tf.int32))
dataset=dataset.map(lambda x:(x,tf.size(x)))
return dataset
def MakeSrcTrgDataset(src_path,trg_path,batch_size):
src_data=MakeDataset(src_path)
trg_data=MakeDataset(trg_path)
dataset=tf.data.Dataset.zip((src_data,trg_data))
def FilterLength(src_tuple,trg_tuple):
((src_input,src_len),(trg_label,trg_len))=(src_tuple,trg_tuple)
src_len_ok=tf.logical_and(
tf.greater(src_len,1),tf.less_equal(src_len,MAX_LEN))
trg_len_ok=tf.logical_and(
tf.greater(trg_len,1),tf.less_equal(trg_len,MAX_LEN))
return tf.logical_and(src_len_ok,trg_len_ok)
dataset=dataset.filter(FilterLength)
def MakeTrgInput(src_tuple,trg_tuple):
((src_input,src_len),(trg_label,trg_len))=(src_tuple,trg_tuple)
trg_input=tf.concat([[SOS_ID],trg_label[:-1]],axis=0)
return ((src_input,src_len),(trg_input,trg_label,trg_len))
dataset=dataset.map(MakeTrgInput)
padded_shapes=(
(tf.TensorShape([None]),
tf.TensorShape([])),
(tf.TensorShape([None]),
tf.TensorShape([None]),
tf.TensorShape([])))
batched_dataset=dataset.padded_batch(batch_size,padded_shapes)
return batched_dataset
5.搭建模型
class NMTModel(object):
# 在模型的初始化函数中定义模型要用到的变量。
def __init__(self):
# 定义编码器和解码器所使用的LSTM结构。
self.dec_cell = tf.nn.rnn_cell.MultiRNNCell(
[tf.nn.rnn_cell.BasicLSTMCell(HIDDEN_SIZE)
for _ in range(NUM_LAYERS)])
self.enc_cell_fw=tf.nn.rnn_cell.BasicLSTMCell(HIDDEN_SIZE)
self.enc_cell_bw=tf.nn.rnn_cell.BasicLSTMCell(HIDDEN_SIZE)
# 为源语言和目标语言分别定义词向量。
self.src_embedding = tf.get_variable(
"src_emb", [SRC_VOCAB_SIZE, HIDDEN_SIZE])
self.trg_embedding = tf.get_variable(
"trg_emb", [TRG_VOCAB_SIZE, HIDDEN_SIZE])
# 定义softmax层的变量
if SHARE_EMB_AND_SOFTMAX:
self.softmax_weight = tf.transpose(self.trg_embedding)
else:
self.softmax_weight = tf.get_variable(
"weight", [HIDDEN_SIZE, TRG_VOCAB_SIZE])
self.softmax_bias = tf.get_variable(
"softmax_bias", [TRG_VOCAB_SIZE])
# 在forward函数中定义模型的前向计算图。
# src_input, src_size, trg_input, trg_label, trg_size分别是上面
# MakeSrcTrgDataset函数产生的五种张量。
def forward(self, src_input, src_size, trg_input, trg_label, trg_size):
batch_size = tf.shape(src_input)[0]
# 将输入和输出单词编号转为词向量。
src_emb = tf.nn.embedding_lookup(self.src_embedding, src_input)
trg_emb = tf.nn.embedding_lookup(self.trg_embedding, trg_input)
# 在词向量上进行dropout。
src_emb = tf.nn.dropout(src_emb, KEEP_PROB)
trg_emb = tf.nn.dropout(trg_emb, KEEP_PROB)
# 使用dynamic_rnn构造编码器。
# 编码器读取源句子每个位置的词向量,输出最后一步的隐藏状态enc_state。
# 因为编码器是一个双层LSTM,因此enc_state是一个包含两个LSTMStateTuple类
# 张量的tuple,每个LSTMStateTuple对应编码器中的一层。
# enc_outputs是顶层LSTM在每一步的输出,它的维度是[batch_size,
# max_time, HIDDEN_SIZE]。Seq2Seq模型中不需要用到enc_outputs,而
# 后面介绍的attention模型会用到它。
#outputs是最后一层每个step的输出,它的结构是[batch_size,step,HIDDEN_SIZE] =
# states是每一层的最后那个step的输出
with tf.variable_scope("encoder"):
#构造编码器时,bidirectional_dynamic_rnn构造双向循环网络。
#双向循环网络的顶层输出enc_outputs是一个包含两个张量的元祖,每个张量的
#维度都是[batch_size,max_time,HIDDEN_SIZE],代表两个LSTM在每一步的输出
enc_outputs,enc_state=tf.nn.bidirectional_dynamic_rnn(
self.enc_cell_fw,self.enc_cell_bw,src_emb,src_size,
dtype=tf.float32)
enc_outputs=tf.concat([enc_outputs[0],enc_outputs[1]],-1)
# 使用dyanmic_rnn构造解码器。
# 解码器读取目标句子每个位置的词向量,输出的dec_outputs为每一步
# 顶层LSTM的输出。dec_outputs的维度是 [batch_size, max_time,HIDDEN_SIZE]。
# initial_state=enc_state表示用编码器的输出来初始化第一步的隐藏状态。
with tf.variable_scope("decoder"):
#选择注意力模型权重的计算模型。BahdanauAttention是使用一个隐藏层的前馈神经网络。
#memory_sequence_length是一个维度为[batch_size]的张量,代表batch中每个句子的长度,
# Attention需要根据这个信息把填充位置的权重设置为0
attention_mechanism=tf.contrib.seq2seq.BahdanauAttention(
HIDDEN_SIZE,enc_outputs,
memory_sequence_length=src_size)
attention_cell=tf.contrib.seq2seq.AttentionWrapper(
self.dec_cell,attention_mechanism,
attention_layer_size=HIDDEN_SIZE)
dec_outputs,_=tf.nn.dynamic_rnn(
attention_cell,trg_emb,trg_size,dtype=tf.float32)
# 计算解码器每一步的log perplexity。这一步与语言模型代码相同。
output = tf.reshape(dec_outputs,[-1,HIDDEN_SIZE])
logits = tf.matmul(output, self.softmax_weight)+self.softmax_bias
loss = tf.nn.sparse_softmax_cross_entropy_with_logits(
labels=tf.reshape(trg_label, [-1]), logits=logits)
# 在计算平均损失时,需要将填充位置的权重设置为0,以避免无效位置的预测干扰
# 模型的训练。
label_weights = tf.sequence_mask(
trg_size, maxlen=tf.shape(trg_label)[1], dtype=tf.float32)
label_weights = tf.reshape(label_weights, [-1])
cost = tf.reduce_sum(loss * label_weights)
cost_per_token = cost / tf.reduce_sum(label_weights)
# 定义反向传播操作。反向操作的实现与语言模型代码相同。
trainable_variables = tf.trainable_variables()
# 控制梯度大小,定义优化方法和训练步骤。
grads = tf.gradients(cost / tf.to_float(batch_size),
trainable_variables)
grads, _ = tf.clip_by_global_norm(grads, MAX_GRAD_NORM)
optimizer = tf.train.GradientDescentOptimizer(learning_rate=1.0)
train_op = optimizer.apply_gradients(
zip(grads, trainable_variables))
return cost_per_token, train_op
6.训练函数
# 使用给定的模型model上训练一个epoch,并返回全局步数。
# 每训练200步便保存一个checkpoint。
def run_epoch(session, cost_op, train_op, saver, step):
# 训练一个epoch。
# 重复训练步骤直至遍历完Dataset中所有数据。
while True:
try:
# 运行train_op并计算损失值。训练数据在main()函数中以Dataset方式提供。
cost, _ = session.run([cost_op, train_op])
if step % 10 == 0:
print("After %d steps, per token cost is %.3f" % (step, cost))
# 每200步保存一个checkpoint。
if step % 200 == 0:
saver.save(session, CHECKPOINT_PATH, global_step=step)
step += 1
except tf.errors.OutOfRangeError:
break
return step
def main():
# 定义初始化函数。
initializer = tf.random_uniform_initializer(-0.05, 0.05)
# 定义训练用的循环神经网络模型。
with tf.variable_scope("nmt_model", reuse=None,
initializer=initializer):
train_model = NMTModel()
# 定义输入数据。
data = MakeSrcTrgDataset(SRC_TRAIN_DATA, TRG_TRAIN_DATA, BATCH_SIZE)
iterator = data.make_initializable_iterator()
(src, src_size), (trg_input, trg_label, trg_size) = iterator.get_next()
# 定义前向计算图。输入数据以张量形式提供给forward函数。
cost_op, train_op = train_model.forward(src, src_size, trg_input,
trg_label, trg_size)
# 训练模型。
saver = tf.train.Saver()
step = 0
with tf.Session() as sess:
tf.global_variables_initializer().run()
for i in range(NUM_EPOCH):
print("In iteration: %d" % (i + 1))
sess.run(iterator.initializer)
step = run_epoch(sess, cost_op, train_op, saver, step)
if __name__ == "__main__":
main()
7.上面的代码只能来训练,接下来是测试
import tensorflow as tf
import codecs
import argparse
CHECKPOINT_PATH='C://RNN//Chat//./save'
HIDDEN_SIZE=1024
NUM_LAYERS=2
VOCAB_SIZE=2500
SHARE_EMB_AND_SOFTMAX=True
SOS_ID=1
EOS_ID=2
class NMTModel(object):
def __init__(self):
self.enc_cell_fw=tf.nn.rnn_cell.BasicLSTMCell(HIDDEN_SIZE)
self.enc_cell_bw=tf.nn.rnn_cell.BasicLSTMCell(HIDDEN_SIZE)
# self.enc_cell=tf.nn.rnn_cell.MultiRNNCell(
# [tf.nn.rnn_cell.BasicLSTMCell(HIDDEN_SIZE)
# for _ in range(NUM_LAYERS)])
self.dec_cell=tf.nn.rnn_cell.MultiRNNCell(
[tf.nn.rnn_cell.BasicLSTMCell(HIDDEN_SIZE)
for _ in range(NUM_LAYERS)])
self.src_embedding=tf.get_variable(
'src_emb',[VOCAB_SIZE,HIDDEN_SIZE])
self.trg_embedding=tf.get_variable(
'trg_emb',[VOCAB_SIZE,HIDDEN_SIZE])
if SHARE_EMB_AND_SOFTMAX:
self.softmax_weight=tf.transpose(self.trg_embedding)
else:
self.softmax_weight=tf.get_variable(
'weight',[HIDDEN_SIZE,VOCAB_SIZE])
self.softmax_bias=tf.get_variable(
'softmax_bias',[VOCAB_SIZE])
def inference(self,src_input):
src_size=tf.convert_to_tensor([len(src_input)],dtype=tf.int32)
src_input=tf.convert_to_tensor([src_input],dtype=tf.int32)
src_emb=tf.nn.embedding_lookup(self.src_embedding,src_input)
with tf.variable_scope('encoder'):
# enc_outputs,enc_state=tf.nn.dynamic_rnn(
# self.enc_cell,src_emb,src_size,dtype=tf.float32)
enc_outputs, enc_state = tf.nn.bidirectional_dynamic_rnn(
self.enc_cell_fw, self.enc_cell_bw, src_emb, src_size, dtype=tf.float32)
enc_outputs = tf.concat([enc_outputs[0], enc_outputs[1]], -1)
with tf.variable_scope('decoder'):
attention_mechanism=tf.contrib.seq2seq.BahdanauAttention(
HIDDEN_SIZE,enc_outputs,
memory_sequence_length=src_size
)
attention_cell=tf.contrib.seq2seq.AttentionWrapper(
self.dec_cell,attention_mechanism,
attention_layer_size=HIDDEN_SIZE)
MAX_DEC_LEN=100
with tf.variable_scope('decoder/rnn/attention_wrapper'):
init_array=tf.TensorArray(dtype=tf.int32,size=0,dynamic_size=True,clear_after_read=False)
init_array=init_array.write(0,SOS_ID)
init_loop_var=(
attention_cell.zero_state(batch_size=1,dtype=tf.float32),
init_array,0)
def continue_loop_condition(state,trg_ids,step):
return tf.reduce_all(tf.logical_and(
tf.not_equal(trg_ids.read(step),EOS_ID),
tf.less(step,MAX_DEC_LEN-1)))
def loop_body(state,trg_ids,step):
trg_input=[trg_ids.read(step)]
trg_emb=tf.nn.embedding_lookup(self.trg_embedding,
trg_input)
dec_outputs,next_state=attention_cell.call(
state=state,inputs=trg_emb)
output=tf.reshape(dec_outputs,[-1,HIDDEN_SIZE])
logits=(tf.matmul(output,self.softmax_weight)+self.softmax_bias)
next_id=tf.argmax(logits,axis=1,output_type=tf.int32)
trg_ids=trg_ids.write(step+1,next_id[0])
return next_state,trg_ids,step+1
state,trg_ids,step=tf.while_loop(
continue_loop_condition,loop_body,init_loop_var)
return trg_ids.stack()
def content(words,word_to_id):
test_sentence = words
test_sentence = [word_to_id[x] for x in test_sentence]
test_sentence.append(word_to_id[''])
return test_sentence
def main():
with codecs.open('C://RNN//Chat//vocab.txt', 'r', encoding='utf-8') as f:
vocab = [w.strip() for w in f.readlines()]
word_to_id = {k: v for (k, v) in zip(vocab, range(len(vocab)))}
id_to_word = {k: v for (k, v) in enumerate(vocab)}
with tf.Session() as sess:
with tf.variable_scope("nmt_model", reuse=None):
model = NMTModel()
words = input()
test_sentence = content(words, word_to_id)
output_op = model.inference(test_sentence)
saver=tf.train.Saver()
saver.restore(sess,CHECKPOINT_PATH)
output = sess.run(output_op)
output = ''.join([id_to_word[x] for x in output])
print(output[5:-5])
if __name__=='__main__':
main()
最后有个说大也大说小也小的缺陷,就是不能连续输入。。。。我开始在test里加了while True 但一直会报错。。。(ˉ▽ˉ;)...