1.基于seq2seq的中文聊天机器人(一)
2.基于seq2seq的中文聊天机器人(二)
3.基于seq2seq的中文聊天机器人(三)
设计一个聊天机器人需要:
想要用户体验良好,我们就需要一个好用的web界面给用户使用,屏蔽掉底层,这样用户才能简单的使用我们的产品。
(1)主要组成部分简介
Web界面的设计主要由HTML,CSS,JS文件构成,他们的主要功能如下。
HTML(HyperText Markup
Language)超文本标记语言,决定网页的结构及内容,即“显示哪些内容”。可以把HTML说成是静态代码。“超文本”就是指页面内可以包含图片、链接,甚至音乐、程序等非文字元素。
CSS(Cascading Style
Sheet)层叠样式表单,设计网页的表现样式,即“如何显示有关内容”,CSS是将样式信息与网页内容分离的一种标记语言,我们使用css为每个元素定义样式;它主要用于美化HTML页面。
JS(JavaScript)一种动态脚本语言,控制网页的行为(效果),即“内容应该如何对事件做出反应”,使用JavaScript代码可以让前台变的有交互(点击事件),常用来为网页添加各式各样的动态功能,为用户提供更流畅美观的浏览效果。通常JavaScript脚本是通过嵌入在HTML中来实现自身的功能的。
JS的常用功能: ·嵌入动态文本于HTML页面 ·对浏览器事件作出响应 ·读写HTML元素 ·在数据被提交到服务器之前验证数据;检测访客的浏览器信息 ·控制cookies,包括创建和修改等
对于一个网页来说,HTML定义网页的结构,CSS描述网页的样子,而JavaScript控制网页行为,设置一个很经典的例子是说HTML就像 一个人的身体,而CSS就是人的衣服,Javascript就像人的思想和行为。
(2)分析与设计
这是一个会话界面,输入一句话,输出一句应答的话。需要设计提供对外调用的API,该API接受到用户输入的问答信息,根据接受到的信息调用模型预测应答会话并返回给服务器。
API的调用需要通过web服务器提供。Tornado是使用Python编写的一个强大的、可扩展的Web服务器。它在处理严峻的网络流量时表现得足够强健,在创建和编写时有着足够的轻量级,并能够被用在大量的应用和工具中。所以这里使用tornado绑定API对象,设置监听端口,循环监听用户的服务请求。
设计HTML的结构与内容,设置文章标题,对话框,文本输入框,发送信息,显示信息,还有背景以及头像的加载,引用js文件用于响应浏览器事件,读取数据和页面更新。
设计CSS用于美化HTML页面,选择器选择元素对其长宽高位置等进行布局设计。
编写js脚本用于页面事件的响应,页面动态更新,使用ajax实现异步通信效果,ajax可以实现动态不刷新(局部刷新)就是能在不更新整个页面的前提下维护数据。这使得Web应用程序更为迅捷地回应用户动作,并避免了在网络上发送那些没有改变过的信息。
RestfulAPI.py开启web容器,监听8000端口,聊天机器人前端网页通过ajax请求后端接口,返回机器人应答数据。具体流程如下
打开接口访问地址,本地服务器默认http://127.0.0.1:8000/api/chatbot
浏览器加载index.html,引用css文件进行渲染,在有引用js的地方执行js
页面加载好了之后如上图,页面加载完成之后,chat.js就开始执行了,这时可以进行会话,输入问答信息,点击发送,启动一个事件
(1) api文件
提供对外调用的接口,根据接收到的信息调用seq2seq模型返回预测结果,如果输入为空格,返回请输入聊天信息。
def chatbot_api(infos):
du = DataProcessing.DataUnit(**data_config)
save_path = os.path.join(BASE_MODEL_DIR, MODEL_NAME)
batch_size = 1
tf.reset_default_graph()
model = Seq2Seq(batch_size=batch_size,
encoder_vocab_size=du.vocab_size,
decoder_vocab_size=du.vocab_size,
mode='decode',
**model_config)
# 创建session的时候允许显存增长
config = tf.ConfigProto()
config.gpu_options.allow_growth = True
with tf.Session(config=config) as sess:
init = tf.global_variables_initializer()
sess.run(init)
model.load(sess, save_path)
while True:
q = infos
#判断接受到的参数是否为空格字符串
if q is None or q.strip() == '':
return "请输入聊天信息"
continue
q = q.strip()
indexs = du.transform_sentence(q)
x = np.asarray(indexs).reshape((1, -1))
xl = np.asarray(len(indexs)).reshape((1,))
pred = model.predict(
sess, np.array(x),
np.array(xl)
)
result = du.transform_indexs(pred[0])
return result
在resultful.py中创建应用对象,使用tornado服务器,绑定监听端口8000,监听端口的连接。
f __name__ == '__main__':
# 创建一个应用对象
app = tornado.web.Application([(r'/api/chatbot', IndexHandler)])
# 绑定一个监听端口
app.listen(8000)
# 启动web程序,开始监听端口的连接
tornado.ioloop.IOLoop.current().start()
服务器增加解决跨域名请求访问和异常处理的功能,若调用api过程中出现了异常,则向用户返回服务器内部错误。
class BaseHandler(RequestHandler):
"""解决JS跨域请求问题"""
def set_default_headers(self):
self.set_header('Access-Control-Allow-Origin', '*')
self.set_header('Access-Control-Allow-Methods', 'POST, GET')
self.set_header('Access-Control-Max-Age', 1000)
self.set_header('Access-Control-Allow-Headers', '*')
# self.set_header('Content-type', 'application/json')
class IndexHandler(BaseHandler):
# 添加一个处理get请求方式的方法
def get(self):
# 向响应中,添加数据
infos = self.get_query_argument("infos")
print("Q:", infos)
# 捕捉服务器异常信息
try:
result = chatbot_api(infos=infos)
except:
result = "服务器内部错误"
print("A:", "".join(result))
self.write("".join(result))
(2) HTML文件
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="shortcut icon" href="chatImages/favicon.ico" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
<title>聊天界面</title>
<link rel="stylesheet" type="text/css" href="chatCss/chat.css" />
<script src="chatJs/jquery.min.js"></script>
<script src="chatJs/flexible.js"></script>
</head>
<body>
<header class="header" align="center">
<span style="font-size:18px">聊天机器人</span>
<input type="hidden" style="font-size:15px;text-align: center;" type="text" id="picker" placeholder="参数选择" readonly="readonly"
value='LSTM 2层 25轮'>
</header>
<div class="message">
<div class="send">
<div class="msg">
<img src="chatImages/man.png" alt="" />
<p><i class="msg_input"></i>请输入内容开始对话</p>
</div>
</div>
<div class="send">
</div>
</div>
<div class="footer">
<input type="text" id='inputVal' placeholder='请输入对话内容'/>
<p>发送</p>
</div>
<script src="chatJs/chat.js" type="text/javascript" charset="utf-8"></script>
<script src="chatJs/picker.min.js"></script>
<script type="text/javascript" src="chatJs/city.js"></script>
<script type="text/javascript" src="chatJs/index.js"></script>
</body>
</html>
分析:
IE使用最新的引擎渲染网页,chrome=1则可以激活Chrome Frame. html文档开头加载了chatJs/jquery.min.js这个文件, 可以使用丰富的jQuery函数来做开发,做更多的事情(Write less, Do more),主要是方便 DOM操作,当然它的功能挺多的。<input type="hidden" style="font-size:15px;text-align: center;" type="text" id="picker" placeholder="参数选择" readonly="readonly"
value='LSTM 2层 25轮'>
上面一段隐藏域在页面中对于用户是不可见的,在表单中插入隐藏域的目的在于收集或发送信息,以利于被处理表单的程序所使用。浏览者单击发送按钮发送表单的时候,隐藏域的信息也被一起发送到服务器。用来对参数进行选择,这里想要通过picker实现网络属性的三级联动选择,选择编码器和解码器的神经元是LSTM还是GRU,网络层数,以及训练的轮数,这里默认参数是LSTM 2层 25轮。
加载移动端适配js chatJs/flexible.js 在移动设备上使用时自适应屏幕。
中间为显示对话内容,显示发送的信息和接受到的服务器返回的信息。
下部为输入文本框和发送框,发送框绑定一个事件,引入了chat.js响应发送事件。
Picker.min.js 以及index.js实现模型属性的联动选择,为筛选器组件,这里没有使用到。
city.js为模型属性的三级联动库,用于模型的属性选择。
(3)js文件
js文件主要功能是通过ajax实现DOM树的更新。
Upview函数的功能是选择message插入html内容,并且实现页面的对话自动滚动到底部。
Show函数定义了发送和接受信息的html变量,并且调用了upview函数更新视图
*聊天信息,发送和接受的信息*/
function show(headSrc, str, className) {
var html = "+ className + ">+ headSrc + " />" +
""
+ str + "";
upView(html);
}
/*更新视图*/
function upView(html) {
$('.message').append(html); //选择message插入html内容
$('body,html').animate({
scrollTop: $('.message').outerHeight() - window.innerHeight
}, 200); // 自动将页面移动到最底部
}
jQuery主要函数,DOM加载完成之后,jQuery获取输入框焦点为inputval赋值,并且需要绑定键盘的up状态进行信息输入,输入框的p标签(发送)绑定信息获取事件,调用getMessage函数与服务器的数据交换和页面更新。
getMessage函数执行数据交换和网页更新,首先判断输入是否为空,为空则返回,什么也不做,不空则为发送框上锁,防止连续提交,将输入信息显示到对话框,清空输入框。通过ajax异步执行请求,与服务器交换数据,先向服务器发送数据,服务器调用绑定的API返回数据。请求服务成功之后,再调用show函数将接收到的数据用于更新DOM ,再显示到对话框中。
//页面DOM文档加载完成后加载执行
$(function () {
$('#inputVal').focus();//点击获得输入框焦点
$('.footer').on('keyup', 'input', function () {//绑定输入框键盘up
if ($(this).val().length > 0) { //状态
$(this).next().css('background', '#114F8E');
} else {
$(this).next().css('background', '#ddd');
}
});
$('.footer p').click(getMessage);//绑定信息获取事件
$(document).keyup(function (ev) { //文本框按下回车键执行事件
if (ev.keyCode == 13) {
getMessage();
}
})
//获取输入的文本信息
function getMessage() {
var val = $('#inputVal').val(); //返回inputVal的值给val
if (val == '')
return; //若为空,则直接返回
if (flag) {
flag = false; //上锁,防止连续提交
//输入信息显示到对话框
show("./chatImages/woman.png", $(".footer input").val(), "show");
// 替换为各自的接口地址
var url = "http://127.0.0.1:8000/api/chatbot";
//清空input
$(".footer input").val("").next().css('background', '#ddd');
$.ajax({ //异步执行请求,与服务器交换数据
type: "get", //请求的方式,有POST和GET两种,默认是GET,数据类型是String
dataType: "json", //预期服务器返回的数据类型json
async: true, //异步请求
url: url, //请求的服务器地址,默认是当前页面,数据类型是String
data: {
infos: val, //发送到服务器的数据,必须为key/value格式
},
complete: function (data) { //请求成功之后前端处理的代码
flag = true;
message = data.responseText
setTimeout(function () { //浏览器会在合适的时间,将代码插入任务队列
show("chatImages/man.png", message, "send"); //显示接受到的信息
}, 500); //500毫秒后调用function函数
}
});
}
}
});
我们已将项目部署到阿里云服务器上,地址http://47.100.77.54:8088/(如果不回复就是我把服务关掉了)
输入空格则会提示返回请输入聊天信息,输入简单直接的问题基本上可以得到预期的效果。
最后附上github地址,欢迎star~
https://github.com/daniellibin/seq2seq-chatbot