个人博客:http://www.chenjianqu.com/

原文链接:http://www.chenjianqu.com/show-39.html

本文介绍了基于keras框架,使用seq2seq模型,如何使用自己的QQ聊天记录训练一个 聊天机器人——另一个’你‘。

NLP使我快乐!这段时间在写Unity的同时也会看下NLP的知识,这不刚学了seq2seq,就想着用自己过去一年来的QQ聊天记录做一个简易的聊天机器人。做的时候发现网上的资料大多是关于字符级的seq2seq实现,而单词级的又是用keras实现的基本没有,只能摸索着,现在终于做个大概出来。就分享一下吧。

Seq2seq

Seq2Seq(Sequence to Sequence), 就是一种能够根据给定的序列,通过特定的方法生成另一个序列的方法。比如在人机对话的场景下,你输入:"Are you free tomorrow?"到seq2seq模型,它会生成:"Yes,what's up?"。

Seq2Seq主要的应用场景有① 机器翻译(当前最为著名的Google翻译,就是完全基于Seq2Seq+Attention机制开发出来的)。② 聊天机器人(小爱,微软小冰等也使用了Seq2Seq的技术(不是全部))。③ 文本摘要自动生成(今日头条等使用了该技术)。④ 图片描述自动生成。⑤ 机器写诗歌、代码补全、生成 commit message、故事风格改写等。

原理

Seq2Seq的主要思路是通过深度神经网络模型将输入的序列映射为中间语义向量,再将中间语义向量解码得到输出序列,这一过程由编码输入(encoder)与解码输出(decoder)两个环节组成。encoder和decoder一般都是用RNN,通常是LSTM或GRU,详情可看RNN、LSTM、GRU的原理和实现.

假如原句子为X=(a,b,c,d,e,f)X=(a,b,c,d,e,f),目标输出为Y=(P,Q,R,S,T)Y=(P,Q,R,S,T),那么一个基本的seq2seq就如下图所示。

左边是对输入的encoder,它负责把输入(可能是变长的)编码为一个固定大小的向量,这个可选择的模型就很多了,用GRU、LSTM等RNN结构或者CNN+Pooling、Google的纯Attention等都可以,这个固定大小的向量,理论上就包含了输入句子的全部信息。

decoder负责将刚才我们编码出来的向量解码为我们期望的输出。与encoder不同,我们在图上强调decoder是“单向递归”的,因为解码过程是递归进行的,具体流程为:

1、所有输出端,都以一个通用的<start>标记开头,以<end>标记结尾,这两个标记也视为一个词/字;
2、将<start>输入decoder,然后得到隐藏层向量,将这个向量与encoder的输出混合,然后送入一个分类器,分类器的结果应当输出P;
3、将P输入decoder,得到新的隐藏层向量,再次与encoder的输出混合,送入分类器,分类器应输出Q;
4、依此递归,直到分类器的结果输出<end>。

这就是一个基本的seq2seq模型的解码过程,在解码的过程中,将每步的解码结果送入到下一步中去,直到输出<end>位置。

下面开始做聊天机器人

神经网络模型

训练时使用的神经网络模型结构如下:

详细的结构

转存失败重新上传取消

作为一个单词级的生成模型,如果使用one-hot数据输入的话,那么LSTM的计算量和所需要的内存将无比巨大。因此需要将one-hot降维,故在lstm层前面加上一个词嵌入层。这里使用的词向量我前面的博客《文本CNN-中文酒店评论的二分类》有提到。

训练过程是“teacher force”,这和预测过程是不同的,因此需要把预测模型和训练模型分开,训练模型和预测模型使用相同的层,但是他们的模型结构不同。

预测模型的第一个子模型是encoder:

第二个子模型是decoder_embedding:

第三个子模型是decoder:

其实理论上应该要把第二个和第三个模型合起来,但是我这里合起来的时候有点问题。

数据集获取和去噪

本次使用的是QQ聊天记录作为训练集,QQ本身提供了聊天记录导出的功能,步骤如下:打开消息管理器,右上角点击导出全部消息记录,保存为.txt文件。

这样就可以得到我们想要的数据了。

接下来进行数据清洗。代码如下:

import re
data=[]
#读取数据集
file = open("D:/NLP/dataset/对话数据集/聊天记录.txt",encoding='utf-8')
last_name=''
name=''
multiLine=''
flag=0
count=0
keyNoise=['的口令红包','邀请加入','申请加入','点击查看','撤回了','(无)','对方已成功接收']
for line in file:#去除空行line=line.strip().replace('\n','')if(len(line)==0):continue#去噪if(len(line)>4 and (line[:4]=='消息记录' or line[:4]=='消息分组' or line[:4]=='消息对象' or line[:4]=='===='  or line[:4]=='http' or line[:6]=='[QQ红包]' or line[:3]=='管理员')):continuecontinueflag=Falsefor s in keyNoise:if(s in line):continueflag=Trueif(continueflag):continue#同一个聊天对象的多行连接起来if(line[:4]=='2018' or line[:4]=='2019'):name=line.split(' ')[-1]if(name==last_name):flag=1else:flag=0last_name=name#print(name)continueif(flag==1):multiLine+=(' '+line)continueelse:temp=lineline=multiLine.replace('\n','')multiLine=tempif(name=='轨迹'):#添加“我”标记multiLine='CJQ'+tempelse:#添加“朋友”标记multiLine='FRI'+temp#去除@某人的消息obj=re.findall( r'(@\S*\s)',line)for s in obj:line=line.replace(s,'')#去除图片和表情line=line.replace('[图片]','')line=line.replace('[表情]','')line=line.strip()#去除空行if(len(line)==3):continuedata.append(line)count+=1if(count==30678):#我这里只提取前30678行break
print(count)
#写入数据
with open('data.txt','w',encoding='utf-8') as f:f.write('\n'.join(data))

将原始的聊天数据处理之后得到的如下类似的数据。

FRI你在寝室吗
CJQ不在 等会回去
FRI你回到了告诉我吧。我来拿
CJQok 我回来了
FRI太晚了。我都要睡了。明天再来找你拿吧
CJQ卧槽我给你吧 我现在拿给你?
FRI别了吧。太麻烦你了

再把这个对话集拆分为两个文件,代码如下:

input_text=[]
target_text=[]
for i in range(len(data)-1):if(data[i][:3]=='FRI' and data[i+1][:3]=='CJQ'):input_text.append(data[i][3:].strip())target_text.append(data[i+1][3:].strip())with open('cjq.txt','w',encoding='utf-8') as f:f.write('\n'.join(target_text))
with open('fri.txt','w',encoding='utf-8') as f:f.write('\n'.join(input_text))

这样就得到了"朋友说.txt"和"我回答.txt"两个文件。

数据集向量化

使用keras的文本预处理器,将文本映射为序列。

import jieba
from keras.models import Model
from keras.layers import Input, LSTM, Dense,Embedding
from keras.preprocessing.text import Tokenizer
from keras.preprocessing.sequence import pad_sequences
import numpy as npMAX_WORDS=15000 #使用20000个词语
SEN_LEN=32 #每条聊天的长度#将句子分词并用空格隔开
inputTextList=[' '.join([w for w in jieba.cut(text)]) for text in input_text]
targetTextList=[' '.join([w for w in jieba.cut(text)]) for text in target_text]
tokenizer=Tokenizer(num_words=MAX_WORDS)
tokenizer.fit_on_texts(texts=inputTextList+targetTextList)#将文本映射为数字序列
input_sequences=tokenizer.texts_to_sequences(texts=inputTextList)
target_sequence=tokenizer.texts_to_sequences(texts=targetTextList)
word_index=tokenizer.word_index#添加两个转义字符到词典
word_index['\t']=len(word_index)+1
word_index['\n']=len(word_index)+1
reverse_word_index = dict([(i, t) for t, i in word_index.items()])#得到反转词典,用于恢复
print(len(input_sequences))
print(len(target_sequence))

由于数据集的单词量很大,如果想直接一次训练模型,内存肯定是不够的,因此需要定义一个生成器。

#数据生成器
def train_gen(input_seq,target_seq, m, batch_size=64):input_seq=np.array(input_seq)target_seq=np.array(target_seq)permutation = np.random.permutation(input_seq.shape[0])shuffled_inputs = input_seq[permutation]shuffled_targets = target_seq[permutation]num_batches = int(m/batch_size)while 1:for i in range(num_batches):input_seq_batch=shuffled_inputs[i*batch_size:(i+1)*batch_size]target_seq_batch=shuffled_targets[i*batch_size:(i+1)*batch_size]encoder_x = np.zeros((batch_size, SEN_LEN),dtype='float32')decoder_x = np.zeros((batch_size, SEN_LEN),dtype='float32')decoder_y = np.zeros((batch_size, SEN_LEN, MAX_WORDS),dtype='float32')for i, (in_t, tar_t) in enumerate(zip(input_seq_batch, target_seq_batch)):lentext=len(tar_t[:SEN_LEN])lentext_in=len(in_t[:SEN_LEN])if(lentext==0 or lentext_in==0):continuefor j, w_index in enumerate(in_t[:SEN_LEN]):encoder_x[i,j]=w_indexfor j, w_index in enumerate(tar_t[:SEN_LEN]):if(j==0):#开始符号decoder_x[i,0]=word_index['\t']decoder_y[i,0, w_index] = 1.elif(j==lentext-1):#结束符号decoder_x[i,j]=tar_t[j-1]if(lentext>=SEN_LEN):decoder_y[i, j,word_index['\n']] = 1.else:decoder_y[i, j, w_index] = 1.else:decoder_x[i,j]=tar_t[j-1]decoder_y[i, j, w_index] = 1.#解码器输出序列提前一个时间步if(lentext<SEN_LEN):#补上长度decoder_x[i,lentext]=tar_t[lentext-1]decoder_y[i,lentext,word_index['\n']] = 1.yield [encoder_x,decoder_x],decoder_y

载入词向量矩阵

我是用的是300d的中文词向量,下面的代码载入词向量文件并设置词向量矩阵

#解析词向量文件
embeddings_index={}
f=open(r'D:\NLP\wordvector\sgns.zhihu.word\sgns.zhihu.word',encoding='utf-8')
for line in f:values=line.split()word=values[0]#第一个是单词coefs=np.asarray(values[1:],dtype='float32')#后面都是系数embeddings_index[word]=coefs
f.close()
#准备词向量矩阵
EMBEDDING_DIM=300#词向量的长度
embedding_matrix=np.zeros((MAX_WORDS,EMBEDDING_DIM))
for word,i in word_index.items():word_vector=embeddings_index.get(word)if(word_vector is not None):#若是未登录词,则词向量为初始值0embedding_matrix[i]=word_vector

搭建神经网络模型

网络结构如上面所述,下面代码搭建模型,同时载入词嵌入层的参数,并编译模型。

from keras.models import Model
from keras.layers import Input, LSTM, Dense,Embedding
#编码器
encoder_inputs = Input(shape=(None,))
encoder_eb = Embedding(MAX_WORDS, EMBEDDING_DIM)
encoder_eb_outputs = encoder_eb(encoder_inputs)#嵌入文本
encoder_lstm=LSTM(EMBEDDING_DIM,return_state=True)
encoder_outputs, state_h, state_c = encoder_lstm(encoder_eb_outputs)
encoder_states = [state_h, state_c]
#解码器
decoder_inputs = Input(shape=(None,))
decoder_eb = Embedding(MAX_WORDS, EMBEDDING_DIM)
decoder_eb_outputs=decoder_eb(decoder_inputs)
decoder_lstm = LSTM(EMBEDDING_DIM, return_sequences=True,return_state=True)
decoder_outputs,_,_=decoder_lstm(decoder_eb_outputs, initial_state=encoder_states)decoder_dense_1 = Dense(int(MAX_WORDS/8), activation='relu')
decoder_outputs_1 = decoder_dense_1(decoder_outputs)
decoder_dense = Dense(MAX_WORDS, activation='softmax')
decoder_outputs = decoder_dense(decoder_outputs_1)#将编码器和解码器串联起来
model = Model([encoder_inputs, decoder_inputs], decoder_outputs)model.summary()
plot_model(model,to_file='chatbot_qq_model.png',show_shapes=True)#把词嵌入矩阵载入到词嵌入层中
model.layers[2].set_weights([embedding_matrix])
model.layers[2].trainable=False#
model.layers[3].set_weights([embedding_matrix])
model.layers[3].trainable=False
#编译模型
model.compile(optimizer='rmsprop', loss='categorical_crossentropy')

模型训练

使用生成器进行训练,训练50轮。受制于显存,我这里的batch_size只能设置为16,更大可能效果好一点,训练速度也更快点。我这里也没有设置验证集,主要是我太懒了。

import kerascallbacks_list=[keras.callbacks.EarlyStopping(monitor='acc',patience=10,),keras.callbacks.ModelCheckpoint(filepath='chatbot1_model_checkpoint.h5',monitor='loss',save_best_only=True),keras.callbacks.TensorBoard(log_dir='chatbot1_log')
]model.fit_generator(train_gen(input_sequences,target_sequence,len(input_sequences), batch_size=16),steps_per_epoch=1000,callbacks=callbacks_list,epochs=50)

训练结果如下:

可以看到50轮的训练让训练精度达到0.25,如果轮数更多的话,其实可以达到更高的精度。

搭建预测模型

如前面所述搭建预测模型。

encoder_model = Model(encoder_inputs, encoder_states)
encoder_model.summary()
plot_model(encoder_model,to_file='chatbot_qq_encoder_model.png',show_shapes=True)decoder_inputs = Input(shape=(None,))
decoder_eb = Embedding(MAX_WORDS, EMBEDDING_DIM)
decoder_eb_outputs=decoder_eb(decoder_inputs)
emb_model = Model(decoder_inputs, decoder_eb_outputs)
emb_model.summary()
plot_model(emb_model,to_file='chatbot_qq_emb_model.png',show_shapes=True)decoder_eb_input = Input(shape=(None,EMBEDDING_DIM))decoder_state_input_h = Input(shape=(None,EMBEDDING_DIM))
decoder_state_input_c = Input(shape=(None,EMBEDDING_DIM))
decoder_states_inputs = [decoder_state_input_h, decoder_state_input_c]decoder_outputs, state_h, state_c = decoder_lstm(decoder_eb_input, initial_state=decoder_states_inputs)
decoder_states = [state_h, state_c]decoder_outputs_1 = decoder_dense_1(decoder_outputs)
decoder_outputs = decoder_dense(decoder_outputs_1)decoder_model = Model([decoder_eb_input] + decoder_states_inputs, [decoder_outputs]+decoder_states)
decoder_model.summary()
plot_model(decoder_model,to_file='chatbot_qq_decoder_model.png',show_shapes=True)

预测

读取字典

f = open('word_index_chatbot.txt','r',encoding='utf-8')
dictStr = f.read()
f.close()
tk = eval(dictStr)
rtk = dict([(i, t) for t, i in tk.items()])

输入聊天文本,预测生成。

text='早点睡吧你'
#将输入转换为序列
input_texts=[w for w in jieba.cut(text)]
input_sequences=[tk[w] for w in input_texts if w in tk]
x = np.zeros((1, SEN_LEN),dtype='float32')
y = np.zeros((1, 1),dtype='float32')
y[0,0]=tk['\t']
for j, w_index in enumerate(input_sequences[:SEN_LEN]):x[0,j]=w_index
result=''#序列预测
states_value = encoder_model.predict(x)#序列编码
#维度变换
h,c=states_value
h_3=np.zeros((1,1, 300),dtype='float32')
c_3=np.zeros((1,1, 300),dtype='float32')
h_3[0]=h
c_3[0]=c
states_value=[h_3,c_3]
i=0
stop_condition = False
while not stop_condition:embeded_vector=emb_model.predict(y)output_tokens, h, c = decoder_model.predict([embeded_vector] + states_value)#序列解码#将输出转换为词index = np.argmax(output_tokens[0, -1, :])word = rtk[index]result += word#结束解码if (index>=SEN_LEN):stop_condition = True#更新状态states_value = [h, c]i+=1y[0,0]=index
print(result)

预测结果:

效果其实一般般,主要原因是数据集太小了,怪我聊天聊得不多咯。

参考资料

[1]笨拙的石头.NLP之Seq2Seq.https://blog.csdn.net/qq_32241189/article/details/81591456. 2018-08-12

[2]苏剑林.玩转Keras之seq2seq自动生成标题.https://spaces.ac.cn/archives/5861/comment-page-1.2018-09-01

聊天机器人-基于QQ聊天记录训练相关推荐

  1. QQ聊天机器人--基于酷Q写的插件

      闲着无聊,百度了一下,在微信上调戏微软小冰,感觉很有趣,于是乎百度了一系列关于自动回复的,最后得知了,图灵机器人和酷Q这两个软件,在找的时候发现酷Q(基于易语言)有C++的sdk,所以就打算借助酷 ...

  2. linux智能聊天机器人,基于bluemix智能聊天机器人开发过程(一)

    基于bluemix智能聊天机器人开发过程(一)--入门及地址部署 前期准备工作: 创建bluemix账号 GIT(可选) node.js cf(cloud foundry) bluemix Cli t ...

  3. python开发qq聊天机器人_Python qqbot 实现qq机器人的示例代码

    qqbot 是一个用 python 实现的.基于腾讯 SmartQQ 协议的 QQ 机器人框架,可运行在 Linux . Windows 和 Mac OSX 平台下. 你可以通过扩展 qqbot 来实 ...

  4. 基于tensorflow的聊天机器人

    ** 基于tensorflow的聊天机器人 ** 基于Tensorflow的聊天机器人,主要基于机器深度学习,采用seq2seq+Attention模型,先由jieba中文分词框架对汉字文本语句分词再 ...

  5. 【NLP】创建强大聊天机器人的初学者指南

    作者 | Louis Teo 编译 | VK 来源 | Towards Data Science 你是否面临着太多来自客户的标准要求和问题,并且难以应对?你是否在寻找一种既不增加成本又扩大客户服务的方 ...

  6. python nltk lemmatizer_Python聊天机器人–使用NLTK和Keras构建第一个聊天机器人

    什么是聊天机器人? 聊天机器人是一款智能软件,能够传达和执行类似于人类的动作.聊天机器人可以直接与客户互动,在社交网站上进行营销以及即时向客户发送消息等方面被广泛使用.根据聊天机器人的构建方式,它有两 ...

  7. 从零开始造一个“智障”聊天机器人

    腾讯DeepOcean原创文章:dopro.io/nlp_seq2seq- 智能机器人在生活中随处可见:iPhone里会说话的siri.会下棋的阿法狗.调皮可爱的微软小冰--她们都具有一定的智能,能够 ...

  8. 让阿宅不再寂寞的聊天机器人

    阿宅爱上了阿美 在一个有星星的夜晚 飞机从头顶飞过 流星也划破那夜空 虽然说人生并没有什么意义 但是爱情确实让生活更加美丽 阿美嫁给了二富 在一个有香槟的晴天 豪车从眼前驶过 车笛也震动那烈阳 虽然说 ...

  9. 揭开ChatGPT 4.0的神秘面纱:聊天机器人的新篇章

    随着人工智能技术的飞速发展,聊天机器人逐渐成为我们日常生活中不可或缺的一部分.作为该领域的领导者之一,OpenAI近期推出了最新一代的聊天机器人--ChatGPT 4.0.接下来,就让我们一起来探讨这 ...

  10. 图灵学院python_Python——利用图灵创建聊天机器人

    前几天在开始接触微信公众号,想做一个类似于QQ小冰的聊天机器人,QQ小冰的技术支持是微软小冰,所以最开始想用微软小冰的API,但是由于前面做测颜值功能的时候用到了微软小冰的接口,本着多尝试的心理,最终 ...

最新文章

  1. (转)pipe row的用法, Oracle split 函数写法.
  2. JavaScript 技术篇-chrome浏览器读取剪切板命令document.execCommand(‘paste‘)返回false原因及解决方法
  3. [C++调试笔记]define.h
  4. 《算法竞赛入门经典》习题4-2 正方形 (Squares,ACM,ICPC World Finals 1990,UVa201)——仅提供大体方法
  5. CodeForces - 1494D Dogeforces(贪心+构造)
  6. 本机速度文件支持的“纯” Java大数据存储
  7. 5年前我在博客中写的三目运算符的空指针问题,终于被阿里巴巴开发手册收录了。...
  8. pandas显示全部数据内容_vue项目,当鼠标移入时文本长度超出才显示全部内容
  9. json是什么_如何利用Python处理JSON格式的数据,建议收藏!!!
  10. 使用dig或nslookup指定dns服务器查询域名解析
  11. sql文件中捕获异常_使用更改数据捕获监视SQL Server中的更改
  12. python问题:NameError: name 'reload' is not defined
  13. vue项目强制清除页面缓存
  14. 深度卷积网络:第三课
  15. golang控制结构之select
  16. ipv6 dns修改方法
  17. CPU负载与CPU使用率
  18. cross-site tracing XST攻击
  19. 2021-08-05
  20. C++ 哈希表及unordered_set + unordered_map容器

热门文章

  1. JAVA_观察者模式例子
  2. vue 滑动置顶功能_CSS3 移动端 滚动置顶 吸顶
  3. SQL SERVER数据库日常使用总结
  4. JavaScript写入文件到本地
  5. debian or ubuntu下 anjuta配置
  6. 笔记:盖洛普《伟大管理的12要素》中的12原则
  7. html表格行的悬停事件,jQuery实现HTML表格隔行变色及鼠标悬停变色效果
  8. WebFont-前端字体
  9. RPLIDAR A1激光雷达学习笔记
  10. 【担心照片被冒用?】活体检测新增人脸合成图鉴别