开源|ns4_chatbot通信组件的工作原理

导语:宜信于2019年3月29日正式开源nextsystem4(以下简称“NS4”)系列模块。此次开源的NS4系列模块是围绕当前支付系统笨重、代码耦合度高、维护成本高而产生的分布式业务系统解决方案。NS4系列框架允许创建复杂的流程/业务流,对于业务服务节点的实现可串联,可分布式。其精简、轻量,实现了“脱容器”(不依赖tomcat、jetty等容器)独立运行。NS4系列框架的设计理念是将业务和逻辑进行分离,开发人员只需通过简单的配置和业务实现就可以实现逻辑复杂、性能高效、功能稳定的业务系统。点击查看框架整体介绍 NS4系列包括4个开源模块,分别是:ns4_frame分布式服务框架(详情点击查看:开源|ns4_frame分布式服务框架开发指南)、ns4_gear_idgen ID生成器组件(NS4框架Demo示例)(详情点击查看:开源|为什么要使用ns4_gear_idgen ID生成器?)、ns4_gear_watchdog 监控系统组件(服务守护、应用性能监控、数据采集、自动化报警系统)和ns4_chatbot通讯组件。本文将重点介绍ns4_chatbot通讯组件的工作原理。 项目开源地址:https://github.com/newsettle/... 一、项目简介ns4_chatbot是针对业务的一个聊天机器人的聊天框架,集成了qqbot、wxchat、rasa以及web服务。提供微信和QQ聊天接口,可以对某个群组发送系统监控消息等。QQ和微信聊天机器人都无法直接发消息给群组中的非好友用户,只能通过@的的方式提醒。 微信聊天机器人 使用的是开源的wxpy 机器人框架。使用@register这种python wapper机制来绑定消息响应函数。微信机器人需要对群组进行注册才能对消息进行发送。 QQ聊天机器人 采用酷Q方案。酷Q是windows下的一个程序,可以模拟QQ。在linux系统中,采用wine技术。需要单独部署成为一个docker,然后通过一个http的接口暴露,各类http接口,用来让我们发送消息。提供http消息回调。 二、全局目录结构 三、实现功能接受内部系统(如监控系统)的系统调用,从而把消息推送给QQ或者微信用户。内部系统调用服务的时候,需要提供以下信息 发给哪个群组发给这个群组中的那个用户发送的消息可以接受QQ、微信用户的对话,理解其意图,并且回应用户。四、项目部署&安装此项目主要针对linux系统,采用python2.7+环境进行部署说明。 4.1 依赖环境安装安装必需依赖包:取项目中的 ns4_chatbot/requirements.txt 文件,运行pip install –r requirements.txt命令安装redis安装mysql数据库,导入ns4_chatbot/import_data/production/ddl.sql 文件4.2 酷Q聊天机器人安装酷Q目前可以在Wine中运行,见酷Q Air / Pro on Wine,因此也就自然而然有了相应的Docker镜像coolq/wine-coolq。 要在Docker中使用本插件,可以使用酷Q官方的Docker镜像,然后在其中安装本插件(下载cpk、编辑配置文件、启用插件),也可以使用维护的已安装并启用了插件的镜像 richardchien/cqhttp(基于酷Q官方的镜像修改)。 下面介绍这个镜像的用法。 docker 安装sudo apt-get install -y docker.io 酷Q安装$ docker pull richardchien/cqhttp:latest $ mkdir coolq# 用于存储酷 Q 的程序文件$ docker run -ti --rm --name cqhttp-test \# 将宿主目录挂载到容器内用于持久化酷 Q 的程序文件-v $(pwd)/coolq:/home/user/coolq \-p 9000:9000 \ # noVNC 端口,用于从浏览器控制酷 Q-p 5700:5700 \ # HTTP API 插件开放的端口-e COOLQ_ACCOUNT=123456 \ # 要登录的 QQ 账号,可选但建议填-e CQHTTP_POST_URL=http://example.com:8080 \ # 事件上报地址# 允许通过 HTTP 接口访问酷 Q 数据文件 -e CQHTTP_SERVE_DATA_FILES=yes \ richardchien/cqhttp:latest配置酷Q回调地址:在/coolq/app/io.github.richardchien.coolqhttpapi/config/<QQ_ID>.ini 中添加 post_url=http://[IP]:8080/coolq_callback 酷Q启动:在浏览器中输入 http://[ip]:5700, 进入如下页面: ...

April 22, 2019 · 1 min · jiezi

引入Redis|tensorflow实现 聊天AI--PigPig养成记(3)

引入Redis项目github链接在集成Netty之后,为了提高效率,我打算将消息存储在Redis缓存系统中,本节将介绍Redis在项目中的引入,以及前端界面的开发。引入Redis后,完整代码链接。想要直接得到训练了13000步的聊天机器人可以直接下载链接中这三个文件,以及词汇表文件然后直接运行连接中的py脚本进行测试即可。最终实现效果如下:在Netty中引入Redisimport java.io.BufferedReader;import java.io.BufferedWriter;import java.io.File;import java.io.FileNotFoundException;import java.io.FileReader;import java.io.FileWriter;import java.io.IOException;import java.time.LocalDateTime;import io.netty.channel.ChannelHandlerContext;import io.netty.channel.SimpleChannelInboundHandler;import io.netty.channel.group.ChannelGroup;import io.netty.channel.group.DefaultChannelGroup;import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;import io.netty.util.concurrent.GlobalEventExecutor;import redis.clients.jedis.Jedis;public class ChatHandler extends SimpleChannelInboundHandler<TextWebSocketFrame>{ private static ChannelGroup clients= new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); @Override protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception { System.out.println(“channelRead0…”); //连接redis Jedis jedis=new Jedis(“localhost”); System.out.println(“连接成功…”); System.out.println(“服务正在运行:"+jedis.ping()); //得到用户输入的消息,需要写入文件/缓存中,让AI进行读取 String content=msg.text(); if(content==null||content==”") { System.out.println(“content 为null”); return ; } System.out.println(“接收到的消息:"+content); //写入缓存中 jedis.set(“user_say”, content+":user”); Thread.sleep(1000); //读取AI返回的内容 String AIsay=null; while(AIsay==“no”||AIsay==null) { //从缓存中读取AI回复的内容 AIsay=jedis.get(“ai_say”); String [] arr=AIsay.split(":"); AIsay=arr[0]; } //读取后马上向缓存中写入 jedis.set(“ai_say”, “no”); //没有说,或者还没说 if(AIsay==null||AIsay=="") { System.out.println(“AIsay==null||AIsay==""”); return; } System.out.println(“AI说:"+AIsay); clients.writeAndFlush( new TextWebSocketFrame( “AI_PigPig在”+LocalDateTime.now() +“说:"+AIsay)); } @Override public void handlerAdded(ChannelHandlerContext ctx) throws Exception { System.out.println(“add…”); clients.add(ctx.channel()); } @Override public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { System.out.println(“客户端断开,channel对应的长id为:” +ctx.channel().id().asLongText()); System.out.println(“客户端断开,channel对应的短id为:” +ctx.channel().id().asShortText()); } }在Python中引入Rediswith tf.Session() as sess:#打开作为一次会话 # 恢复前一次训练 ckpt = tf.train.get_checkpoint_state(’.’)#从检查点文件中返回一个状态(ckpt) #如果ckpt存在,输出模型路径 if ckpt != None: print(ckpt.model_checkpoint_path) model.saver.restore(sess, ckpt.model_checkpoint_path)#储存模型参数 else: print(“没找到模型”) r.set(‘user_say’,’no’) #测试该模型的能力 while True: line=‘no’ #从缓存中进行读取 while line==‘no’: line=r.get(‘user_say’).decode() #print(line) list1=line.split(’:’) if len(list1)==1: input_string=‘no’ else: input_string=list1[0] r.set(‘user_say’,’no’) # 退出 if input_string == ‘quit’: exit() if input_string != ’no’: input_string_vec = []#输入字符串向量化 for words in input_string.strip(): input_string_vec.append(vocab_en.get(words, UNK_ID))#get()函数:如果words在词表中,返回索引号;否则,返回UNK_ID bucket_id = min([b for b in range(len(buckets)) if buckets[b][0] > len(input_string_vec)])#保留最小的大于输入的bucket的id encoder_inputs, decoder_inputs, target_weights = model.get_batch({bucket_id: [(input_string_vec, [])]}, bucket_id) #get_batch(A,B):两个参数,A为大小为len(buckets)的元组,返回了指定bucket_id的encoder_inputs,decoder_inputs,target_weights _, _, output_logits = model.step(sess, encoder_inputs, decoder_inputs, target_weights, bucket_id, True) #得到其输出 outputs = [int(np.argmax(logit, axis=1)) for logit in output_logits]#求得最大的预测范围列表 if EOS_ID in outputs:#如果EOS_ID在输出内部,则输出列表为[,,,,:End] outputs = outputs[:outputs.index(EOS_ID)] response = “".join([tf.compat.as_str(vocab_de[output]) for output in outputs])#转为解码词汇分别添加到回复中 print(‘AI-PigPig > ’ + response)#输出回复 #向缓存中进行写入 r.set(‘ai_say’,response+’:AI’)下一节将讲述通信规则的制定,以规范应用程序。 ...

February 18, 2019 · 2 min · jiezi

集成Netty|tensorflow实现 聊天AI--PigPig养成记(2)

集成Netty项目github链接通过上一节的学习我们已经可以训练得到一只傲娇的聊天AI_PigPig了。本章将介绍项目关于Netty的集成问题,将其我们的AI_PigPig可以通过web应用与大家日常互撩。由于只是一个小测试,所以不考虑性能方面的问题,在下一章我们将重点处理效率难关,集成Redis。关于Netty的学习大家可以看我的另一篇文章,本节中关于Netty部分的代码改编自该文章中的netty聊天小练习,文章中会有详细的讲解。Python代码改动首先对测试训练结果的代码进行改动,将输入输出流重定向自作为中间媒介的测试文件中。完整代码链接with tf.Session() as sess:#打开作为一次会话 # 恢复前一次训练 ckpt = tf.train.get_checkpoint_state(’.’)#从检查点文件中返回一个状态(ckpt) #如果ckpt存在,输出模型路径 if ckpt != None: print(ckpt.model_checkpoint_path) model.saver.restore(sess, ckpt.model_checkpoint_path)#储存模型参数 else: print(“没找到模型”) #测试该模型的能力 while True: #从文件中进行读取 #input_string = input(‘me > ‘) #测试文件输入格式为"[内容]:[名字]" #eg.你好:AI【表示AI的回复】 #你好:user【表示用户的输入】 with open(’./temp.txt’,‘r+’,encoding=‘ANSI’) as myf: #从文件中读取用户的输入 line=myf.read() list1=line.split(’:’) #长度为一,表明不符合输入格式,设置为"no",则不进行测试处理 if len(list1)==1: input_string=‘no’ else: #符合输入格式,证明是用户输入的 #input_string为用户输入的内容 input_string=list1[0] myf.seek(0) #清空文件 myf.truncate() #写入"no",若读到"no",则不进行测试处理 myf.write(’no’) # 退出 if input_string == ‘quit’: exit() #若读到"no",则不进行测试处理 if input_string != ’no’: input_string_vec = []#输入字符串向量化 for words in input_string.strip(): input_string_vec.append(vocab_en.get(words, UNK_ID))#get()函数:如果words在词表中,返回索引号;否则,返回UNK_ID bucket_id = min([b for b in range(len(buckets)) if buckets[b][0] > len(input_string_vec)])#保留最小的大于输入的bucket的id encoder_inputs, decoder_inputs, target_weights = model.get_batch({bucket_id: [(input_string_vec, [])]}, bucket_id) #get_batch(A,B):两个参数,A为大小为len(buckets)的元组,返回了指定bucket_id的encoder_inputs,decoder_inputs,target_weights _, _, output_logits = model.step(sess, encoder_inputs, decoder_inputs, target_weights, bucket_id, True) #得到其输出 outputs = [int(np.argmax(logit, axis=1)) for logit in output_logits]#求得最大的预测范围列表 if EOS_ID in outputs:#如果EOS_ID在输出内部,则输出列表为[,,,,:End] outputs = outputs[:outputs.index(EOS_ID)] response = “".join([tf.compat.as_str(vocab_de[output]) for output in outputs])#转为解码词汇分别添加到回复中 print(‘AI-PigPig > ’ + response)#输出回复 #将AI的回复以要求的格式进行写入,方便Netty程序读取 with open(’./temp1.txt’,‘w’,encoding=‘ANSI’) as myf1: myf1.write(response+’:AI’)Netty程序完整代码参见链接netty包下。在原本的ChatHandler类中添加了从文件中读取数据的方法readFromFile,以及向文件中覆盖地写入数据的方法writeToFile。 //从文件中读取数据 private static String readFromFile(String filePath) { File file=new File(filePath); String line=null; String name=null; String content=null; try { //以content:name的形式写入 BufferedReader br=new BufferedReader(new FileReader(file)); line=br.readLine(); String [] arr=line.split(”:"); if(arr.length==1) { name=null; content=null; }else { content=arr[0]; name=arr[1]; } br.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return content; } //向文件中覆盖地写入 private static void writeToFile(String filePath,String content) { File file =new File(filePath); try { FileWriter fileWriter=new FileWriter(file); fileWriter.write(""); fileWriter.flush(); fileWriter.write(content); fileWriter.close(); } catch (IOException e) { e.printStackTrace(); } }对原来的channelRead0方法进行修改,将输入输出流重定向到临时文件中。 @Override protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception { System.out.println(“channelRead0”); //得到用户输入的消息,需要写入文件/缓存中,让AI进行读取 String content=msg.text(); if(content==null||content=="") { System.out.println(“content 为null”); return ; } System.out.println(“接收到的消息:"+content); //写入 writeToFile(writeFilePath, content+":user”); //给AI回复与写入的时间,后期会增对性能方面进行改进 Thread.sleep(1000); //读取AI返回的内容 String AIsay=readFromFile(readFilePath); //读取后马上写入 writeToFile(readFilePath,“no”); //没有说,或者还没说 if(AIsay==null||AIsay==""||AIsay==“no”) { System.out.println(“AIsay为空或no”); return; } System.out.println(“AI说:"+AIsay); clients.writeAndFlush( new TextWebSocketFrame( “AI_PigPig在”+LocalDateTime.now() +“说:"+AIsay)); } 客户端代码<!DOCTYPE html><html> <head> <meta charset=“utf-8” /> <title></title> </head> <body> <div>发送消息:</div> <input type=“text” id=“msgContent”/> <input type=“button” value=“点我发送” onclick=“CHAT.chat()”/> <div>接受消息:</div> <div id=“receiveMsg” style=“background-color: gainsboro;"></div> <script type=“application/javascript”> window.CHAT = { socket: null, init: function() { if (window.WebSocket) { CHAT.socket = new WebSocket(“ws://192.168.0.104:8088/ws”); CHAT.socket.onopen = function() { console.log(“连接建立成功…”); }, CHAT.socket.onclose = function() { console.log(“连接关闭…”); }, CHAT.socket.onerror = function() { console.log(“发生错误…”); }, CHAT.socket.onmessage = function(e) { console.log(“接受到消息:” + e.data); var receiveMsg = document.getElementById(“receiveMsg”); var html = receiveMsg.innerHTML; receiveMsg.innerHTML = html + “<br/>” + e.data; } } else { alert(“浏览器不支持websocket协议…”); } }, chat: function() { var msg = document.getElementById(“msgContent”); CHAT.socket.send(msg.value); } }; CHAT.init(); </script> </body></html>测试结果客户端发送消息用户与AI日常互撩 ...

February 16, 2019 · 2 min · jiezi

tensorflow实现 聊天AI--PigPig养成记(1)

Chapter1.代码详解完整代码github链接,Untitled.ipynb文件内。【里面的测试是还没训练完的时候测试的,今晚会更新训练完成后的测试结果】修复了网上一些代码的bug,解决了由于tensorflow版本不同引起的一些问题。数据集链接 ,下载数据集后,解压提取dgk_shooter_min.conv文件,最好进行转码操作。建议用记事本打开后将其另存为,选择编码为utf-8后进行保存。代码详解(1)数据预处理#coding=utf-8#(1)数据预处理import osimport randomfrom io import openconv_path = ‘dgk_shooter_min.conv.txt’#判断数据集是否存在?if not os.path.exists(conv_path): print(‘数据集不存在’) exit() # 数据集格式"““EM 畹/华/吾/侄/M 你/接/到/这/封/信/的/时/候/M 不/知/道/大/伯/还/在/不/在/人/世/了/EM 咱/们/梅/家/从/你/爷/爷/起/M 就/一/直/小/心/翼/翼/地/唱/戏/M 侍/奉/宫/廷/侍/奉/百/姓/M 从/来/不/曾/遭/此/大/祸/M 太/后/的/万/寿/节/谁/敢/不/穿/红/M 就/你/胆/儿/大/M 唉/这/我/舅/母/出/殡/M 我/不/敢/穿/红/啊/M 唉/呦/唉/呦/爷/M 您/打/得/好/我/该/打/M 就/因/为/没/穿/红/让/人/赏/咱/一/纸/枷/锁/M 爷/您/别/给/我/戴/这/纸/枷/锁/呀/EM 您/多/打/我/几/下/不/就/得/了/吗/M 走/M 这/是/哪/一/出/啊/…/ / /这/是/M 撕/破/一/点/就/弄/死/你/M 唉/M 记/着/唱/戏/的/再/红/M 还/是/让/人/瞧/不/起/M 大/伯/不/想/让/你/挨/了/打/M 还/得/跟/人/家/说/打/得/好/M 大/伯/不/想/让/你/再/戴/上/那/纸/枷/锁/M 畹/华/开/开/门/哪/E…””" # 我首先使用文本编辑器sublime把dgk_shooter_min.conv文件编码转为UTF-8,一下子省了不少麻烦convs = [] # 对话集合with open(conv_path, encoding=“utf8”) as f: one_conv = [] # 一次完整对话 for line in f: line = line.strip(’\n’).replace(’/’, ‘’)#将分隔符去掉 if line == ‘’: continue if line[0] == ‘E’: if one_conv: convs.append(one_conv) one_conv = [] elif line[0] == ‘M’: one_conv.append(line.split(’ ‘)[1])#将对话转成utf-8格式,并将其保存在dgk_shooter_min.conv文件中 print(convs[:3]) # 个人感觉对白数据集有点不给力啊#[ [‘畹华吾侄’, ‘你接到这封信的时候’, ‘不知道大伯还在不在人世了’],# [‘咱们梅家从你爷爷起’, ‘就一直小心翼翼地唱戏’, ‘侍奉宫廷侍奉百姓’, ‘从来不曾遭此大祸’, ‘太后的万寿节谁敢不穿红’, ‘就你胆儿大’, ‘唉这我舅母出殡’, ‘我不敢穿红啊’, ‘唉呦唉呦爷’, ‘您打得好我该打’, ‘就因为没穿红让人赏咱一纸枷锁’, ‘爷您别给我戴这纸枷锁呀’],# [‘您多打我几下不就得了吗’, ‘走’, ‘这是哪一出啊 ‘, ‘撕破一点就弄死你’, ‘唉’, ‘记着唱戏的再红’, ‘还是让人瞧不起’, ‘大伯不想让你挨了打’, ‘还得跟人家说打得好’, ‘大伯不想让你再戴上那纸枷锁’, ‘畹华开开门哪’], ….] # 把对话分成问与答ask = [] # 问response = [] # 答for conv in convs: if len(conv) == 1: continue if len(conv) % 2 != 0: # 奇数对话数, 转为偶数对话 conv = conv[:-1] for i in range(len(conv)): if i % 2 == 0: ask.append(conv[i])#偶数对,填写问题 else: response.append(conv[i])#回答 print(len(ask), len(response))print(ask[:3])print(response[:3])#[‘畹华吾侄’, ‘咱们梅家从你爷爷起’, ‘侍奉宫廷侍奉百姓’]#[‘你接到这封信的时候’, ‘就一直小心翼翼地唱戏’, ‘从来不曾遭此大祸’] def convert_seq2seq_files(questions, answers, TESTSET_SIZE=8000): # 创建文件 train_enc = open(’train.enc’, ‘w’,encoding=‘utf-8’) # 问 train_dec = open(’train.dec’, ‘w’,encoding=‘utf-8’) # 答 test_enc = open(’test.enc’, ‘w’,encoding=‘utf-8’) # 问 test_dec = open(’test.dec’, ‘w’,encoding=‘utf-8’) # 答 # 选择8000数据作为测试数据 test_index = random.sample([i for i in range(len(questions))], TESTSET_SIZE) for i in range(len(questions)): if i in test_index:#创建测试文件 test_enc.write(questions[i] + ‘\n’) test_dec.write(answers[i] + ‘\n’) else:#创建训练文件 train_enc.write(questions[i] + ‘\n’) train_dec.write(answers[i] + ‘\n’) if i % 1000 == 0:#表示处理了多少个i print(len(range(len(questions))), ‘处理进度:’, i) train_enc.close() train_dec.close() test_enc.close() test_dec.close() convert_seq2seq_files(ask, response)# 生成的*.enc文件保存了问题# 生成的*.dec文件保存了回答将数据集进行处理后分成问与答的形式进行保存,选择其中的8000数据作为测试数据。处理完毕后生成的.enc文件保存了问题,.dec文件保存了回答。问题文件*.enc预览:爷爷您戏改得真好您怎么不进去呀王老板见过地球再也无法承受人类的数量我现在是和摩兰达说话吗?我们不是告诉他们应该想什么回答文件*.dec预览:这回跟您可真是一棵菜了我等人拿钥匙呢唉什么事我们发现了一个新的太阳系不是我们仅仅是想告诉他们应该怎么想(2)创建词汇表#coding=utf-8#(2)创建词汇表# 前一步生成的问答文件路径train_encode_file = ’train.enc’train_decode_file = ’train.dec’test_encode_file = ’test.enc’test_decode_file = ’test.dec’ print(‘开始创建词汇表…’)# 特殊标记,用来填充标记对话PAD = “PAD“GO = “GO“EOS = “EOS” # 对话结束UNK = “UNK” # 标记未出现在词汇表中的字符START_VOCABULART = [PAD, GO, EOS, UNK]PAD_ID = 0GO_ID = 1EOS_ID = 2UNK_ID = 3# 参看tensorflow.models.rnn.translate.data_utils vocabulary_size = 5000 # 生成词汇表文件def gen_vocabulary_file(input_file, output_file): vocabulary = {} with open(input_file, encoding=“utf8”) as f: counter = 0 for line in f: counter += 1 tokens = [word for word in line.strip()] for word in tokens: if word in vocabulary: vocabulary[word] += 1 else: vocabulary[word] = 1 vocabulary_list = START_VOCABULART + sorted(vocabulary, key=vocabulary.get, reverse=True) # 取前5000个常用汉字, 应该差不多够用了(额, 好多无用字符, 最好整理一下. 我就不整理了) if len(vocabulary_list) > 5000: vocabulary_list = vocabulary_list[:5000] print(input_file + " 词汇表大小:”, len(vocabulary_list)) with open(output_file, “w”, encoding=“utf8”) as ff: for word in vocabulary_list: ff.write(word + “\n”) gen_vocabulary_file(train_encode_file, “train_encode_vocabulary”)gen_vocabulary_file(train_decode_file, “train_decode_vocabulary”) train_encode_vocabulary_file = ’train_encode_vocabulary’train_decode_vocabulary_file = ’train_decode_vocabulary’ print(“对话转向量…”) # 把对话字符串转为向量形式def convert_to_vector(input_file, vocabulary_file, output_file): tmp_vocab = [] with open(vocabulary_file, “r”, encoding=“utf8”) as f: tmp_vocab.extend(f.readlines()) tmp_vocab = [line.strip() for line in tmp_vocab] vocab = dict([(x, y) for (y, x) in enumerate(tmp_vocab)]) # {‘硕’: 3142, ‘v’: 577, ‘I’: 4789, ‘\ue796’: 4515, ‘拖’: 1333, ‘疤’: 2201 …} output_f = open(output_file, ‘w’) with open(input_file, ‘r’, encoding=“utf8”) as f: for line in f: line_vec = [] for words in line.strip(): line_vec.append(vocab.get(words, UNK_ID)) output_f.write(” “.join([str(num) for num in line_vec]) + “\n”) output_f.close() convert_to_vector(train_encode_file, train_encode_vocabulary_file, ’train_encode.vec’)convert_to_vector(train_decode_file, train_decode_vocabulary_file, ’train_decode.vec’) convert_to_vector(test_encode_file, train_encode_vocabulary_file, ’test_encode.vec’)convert_to_vector(test_decode_file, train_decode_vocabulary_file, ’test_decode.vec’)提取前5000个常用的汉字创建词汇表词汇表文件*_vocabulary预览:__PAD____GO____EOS____UNK__我的你是,不了们对话转向量,把对话字符串转为向量形式向量文件*.vec预览:6 269 31 13 1022 157 5 60 19028 14 226 92 113 2047 2047 98 909 724137 22 9 644 1331 278 63 168528 6 1363 118 634 9 652 514 824 88433 131 51 24 4 127 1311093 433 94 81 4 884 13 840 3435 1010 366生成的train_encode.vec和train_decode.vec用于训练,对应的词汇表train_encode_vocabulary和train_decode_vocabulary。(3)训练这里选取部分代码进行讲解,完整代码链接。导入向量文件进行训练,定义一个read_data的函数对训练集与测试集的问题向量文件encode.vec,回答向量文件decode.vec,进行读取。读取的时候将问题向量文件encode.vec中的每一行默认以空格为分隔符,构成一个源序列。将回答向量文件decode.vec中的每一行默认以空格为分隔符,构成一个目标序列。然后将两个序列添加到data_set中。对文件中的每一行都进行处理与添加后,将得到的data_set返回。# 读取encode.vec和decode.vec数据(数据还不算太多, 一次读入到内存)def read_data(source_path, target_path, max_size=None): data_set = [[] for _ in buckets]#生成了[[],[],[],[]],即当值与参数不一样 with tf.gfile.GFile(source_path, mode=“r”) as source_file:#以读格式打开源文件(source_file) with tf.gfile.GFile(target_path, mode=“r”) as target_file:#以读格式打开目标文件 source, target = source_file.readline(), target_file.readline()#只读取一行 counter = 0#计数器为0 while source and target and ( not max_size or counter < max_size):#当读入的还存在时 counter += 1 source_ids = [int(x) for x in source.split()]#source的目标序列号,默认分隔符为空格,组成了一个源序列 target_ids = [int(x) for x in target.split()]#target组成一个目标序列,为目标序列 target_ids.append(EOS_ID)#加上结束标记的序列号 for bucket_id, (source_size, target_size) in enumerate(buckets):#enumerate()遍历序列中的元素和其下标 if len(source_ids) < source_size and len(target_ids) < target_size:#判断是否超越了最大长度 data_set[bucket_id].append([source_ids, target_ids])#读取到数据集文件中区 break#一次即可,跳出当前循环 source, target = source_file.readline(), target_file.readline()#读取了下一行 return data_set构建模型model = seq2seq_model.Seq2SeqModel(source_vocab_size=vocabulary_encode_size, target_vocab_size=vocabulary_decode_size, buckets=buckets, size=layer_size, num_layers=num_layers, max_gradient_norm=5.0, batch_size=batch_size, learning_rate=0.5, learning_rate_decay_factor=0.97, forward_only=False)开始训练with tf.Session(config=config) as sess: # 恢复前一次训练 ckpt = tf.train.get_checkpoint_state(’.’) if ckpt != None: print(ckpt.model_checkpoint_path) model.saver.restore(sess, ckpt.model_checkpoint_path) else: sess.run(tf.global_variables_initializer()) train_set = read_data(train_encode_vec, train_decode_vec) test_set = read_data(test_encode_vec, test_decode_vec) train_bucket_sizes = [len(train_set[b]) for b in range(len(buckets))]#分别计算出训练集中的长度【1,2,3,4】 train_total_size = float(sum(train_bucket_sizes))#训练实例总数 train_buckets_scale = [sum(train_bucket_sizes[:i + 1]) / train_total_size for i in range(len(train_bucket_sizes))]#计算了之前所有的数的首战百分比 loss = 0.0#损失置位0 total_step = 0 previous_losses = [] # 一直训练,每过一段时间保存一次模型 while True: random_number_01 = np.random.random_sample()#每一次循环结果不一样 #选出最小的大于随机采样的值的索引号 bucket_id = min([i for i in range(len(train_buckets_scale)) if train_buckets_scale[i] > random_number_01]) encoder_inputs, decoder_inputs, target_weights = model.get_batch(train_set, bucket_id) #get_batch()函数首先获取bucket的encoder_size与decoder_size _, step_loss, _ = model.step(sess, encoder_inputs, decoder_inputs, target_weights, bucket_id, False)#损失 loss += step_loss / 500 total_step += 1 print(total_step) if total_step % 500 == 0: print(model.global_step.eval(), model.learning_rate.eval(), loss) # 如果模型没有得到提升,减小learning rate if len(previous_losses) > 2 and loss > max(previous_losses[-3:]):#即损失比以前的大则降低学习率 sess.run(model.learning_rate_decay_op) previous_losses.append(loss) # 保存模型 checkpoint_path = “./chatbot_seq2seq.ckpt” model.saver.save(sess, checkpoint_path, global_step=model.global_step) #返回路径checkpoint_file = “%s-%s” % (save_path, “{:08d}".format(global_step)) loss = 0.0#置当前损失为0 # 使用测试数据评估模型 for bucket_id in range(len(buckets)): if len(test_set[bucket_id]) == 0: continue #获取当前bucket的encoder_inputs, decoder_inputs, target_weights encoder_inputs, decoder_inputs, target_weights = model.get_batch(test_set, bucket_id) #计算bucket_id的损失权重 _, eval_loss, _ = model.step(sess, encoder_inputs, decoder_inputs, target_weights, bucket_id, True) eval_ppx = math.exp(eval_loss) if eval_loss < 300 else float(‘inf’) print(bucket_id, eval_ppx)#输出的是bucket_id与eval_ppx(4)模型测试#coding=utf-8#(4)使用训练好的模型import tensorflow as tf # 0.12# from tensorflow.models.rnn.translate import seq2seq_modelfrom tensorflow.models.tutorials.rnn.chatbot import seq2seq_model#注意 seq2seq_model这个需要自己去下载,根据自己的路径进行导入# 本人将seq2seq_model模块下载后 复制到tensorflow/models/tutorials/rnn/chatbot/路径下,所以才这样进行导入import osimport numpy as np PAD_ID = 0GO_ID = 1EOS_ID = 2UNK_ID = 3tf.reset_default_graph()#词汇表路径pathtrain_encode_vocabulary = ’train_encode_vocabulary’train_decode_vocabulary = ’train_decode_vocabulary’ #读取词汇表def read_vocabulary(input_file): tmp_vocab = [] with open(input_file, “r”,encoding=‘utf-8’) as f: tmp_vocab.extend(f.readlines())#打开的文件全部读入input_file中 tmp_vocab = [line.strip() for line in tmp_vocab]#转换成列表 vocab = dict([(x, y) for (y, x) in enumerate(tmp_vocab)]) return vocab, tmp_vocab#返回字典,列表 vocab_en, , = read_vocabulary(train_encode_vocabulary)#得到词汇字典, vocab_de, = read_vocabulary(train_decode_vocabulary)#得到词汇列表 # 词汇表大小5000vocabulary_encode_size = 5000vocabulary_decode_size = 5000 buckets = [(5, 10), (10, 15), (20, 25), (40, 50)]layer_size = 256 # 每层大小num_layers = 3 # 层数batch_size = 1 model = seq2seq_model.Seq2SeqModel(source_vocab_size=vocabulary_encode_size, target_vocab_size=vocabulary_decode_size, buckets=buckets, size=layer_size, num_layers=num_layers, max_gradient_norm=5.0, batch_size=batch_size, learning_rate=0.5, learning_rate_decay_factor=0.99, forward_only=True)#模型说明:源,目标词汇尺寸=vocabulary_encode(decode)_size;batch_size:训练期间使用的批次的大小;#forward_only:仅前向不传递误差 model.batch_size = 1#batch_size=1 with tf.Session() as sess:#打开作为一次会话 # 恢复前一次训练 ckpt = tf.train.get_checkpoint_state(’.’)#从检查点文件中返回一个状态(ckpt) #如果ckpt存在,输出模型路径 if ckpt != None: print(ckpt.model_checkpoint_path) model.saver.restore(sess, ckpt.model_checkpoint_path)#储存模型参数 else: print(“没找到模型”) #测试该模型的能力 while True: input_string = input(‘me > ‘) # 退出 if input_string == ‘quit’: exit() input_string_vec = []#输入字符串向量化 for words in input_string.strip(): input_string_vec.append(vocab_en.get(words, UNK_ID))#get()函数:如果words在词表中,返回索引号;否则,返回UNK_ID bucket_id = min([b for b in range(len(buckets)) if buckets[b][0] > len(input_string_vec)])#保留最小的大于输入的bucket的id encoder_inputs, decoder_inputs, target_weights = model.get_batch({bucket_id: [(input_string_vec, [])]}, bucket_id) #get_batch(A,B):两个参数,A为大小为len(buckets)的元组,返回了指定bucket_id的encoder_inputs,decoder_inputs,target_weights _, _, output_logits = model.step(sess, encoder_inputs, decoder_inputs, target_weights, bucket_id, True) #得到其输出 outputs = [int(np.argmax(logit, axis=1)) for logit in output_logits]#求得最大的预测范围列表 if EOS_ID in outputs:#如果EOS_ID在输出内部,则输出列表为[,,,,:End] outputs = outputs[:outputs.index(EOS_ID)] response = “".join([tf.compat.as_str(vocab_de[output]) for output in outputs])#转为解码词汇分别添加到回复中 print(‘AI–PigPig > ’ + response)#输出回复测试结果:以下为训练5500步后的测试结果:【最终结果有待更新】傲娇属性get ...

February 15, 2019 · 5 min · jiezi