tensorflow seq2seq 聊天机器人

下面使用了双向LSTM和注意力机制

一.数据集准备

    我用的是 小黄鸡问答语料库,分为分词前,分词后,我用的是分词后

tensorflow seq2seq 聊天机器人_第1张图片

二.数据预处理

   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")

   2.把语料库里的问题与回答分开tensorflow seq2seq 聊天机器人_第2张图片

这里可以看出,从零开始计数的话,偶数的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  但一直会报错。。。(ˉ▽ˉ;)...

你可能感兴趣的:(tensorflow)