中级项目_人机对话系统

参考资料:


2023/4/25 22:01 选择的题目是基于文本的人机对话系统,相对来说是所有题目中虽简单的一个,但是网上的相关资料仍然不是很多,所以还是需要进行大范围的筛选得到合适的代码和参考;

2023/4/25 23:11 刚刚稍微了解了一下,然后发现之所以网上没有相关资料是因为检索的内容不正确,只需要检索“聊天机器人”即可获得大量参考资料;

2023/4/26 10:17 网上的chatbot的相关资料比较少,可能是因为这不是最近的热门?大部分代码还是较老的版本,尽量选择较新的版本作为参考;另一方面,大部分代码并不是自己手动撸的,仅仅只是调用各种各样的语音转文本接口、智能机器人接口等实现,这种代码参考不得;

2023/4/26 16:03 刚才尝试运行了代码,非常顺利的得到了较好的运行结果(只是不知道为什么有些时候会答非所问,但是一开始的表现已经足够支持我选择这份代码作为参考了)。代码主要分为两部分,前端部分是一个html网页,主要用于与用户进行交互,后端部分主要分为模型训练部分和前端交互部分。个人认为需要改的就是模型训练部分和前端展示部分,参考资料(教程类)目前还没有搜集到很多,一步一步来;

2023/4/27 20:41 这个视频教程说实话非常水,大概看一看了解使用了什么技术之后自行Google深入学习;基本等于啥也没讲(刚才速刷基本刷完了),先简单的把近几年的NLP技术过一遍,然后从代码入手,本质上我们还是基于代码来理解整个项目;


一、背景介绍

1.对话机器人概述

一般来说一个完整的对话机器人(例如Siri、小艺)的实现包含以下三个部分:对话系统、问答系统+聊天机器人,当然如果仅仅只是想实现一个陪伴闲聊的系统那么只需实现一个聊天机器人即可。

1.1 对话系统

对话系统特指Task-oriented dialogue system,即为了完成某一种任务而发明的对话系统,这种任务的特征有:

  • 结果唯一,例如买一张机票,订一场电影,买一杯咖啡;
  • 任务需要多项要素,例如机票需要时间、起始地、到达地;电影需要名称、电影院、场次时间;咖啡需要时间、大杯小杯、口味;
  • 任务需要通过多轮对话、多轮反复确认达成,例如:你要大杯的咖啡还是小杯的?你需要几点送?你确定要 9 点送?

1.2 问答系统

QA系统既不能帮你买咖啡也不能帮你订机票,仅仅只是一个问答系统。

一个最简单的问答系统就是当数据足够多的时候,去匹配问题的相似度,若匹配成功则接给用户这个问题的答案,该方法存在很多问题:

1、准确率取决于相似度算法;
2、很难设置相似度算法的阈值;
3、数据库要求比较大;
4、扩展性比较差;

但实际上该算法作为最基本的一部分,是对话机器人不可缺少的:

1、不需要训练,假设你的领导要你给线上产品添加一个问答的时候,他并不喜欢:“我的深度学习模型训练5个小时之后,这个答案就上线了”,这样的回答;
2、自定义问题成本低;
3、非常容易解释;
4、对于完全匹配的问题可以绝对准确,如果是其他的概率模型的话,就必然有可能不准确,而简单的模型不会出错;

1.2.1 问答对

注意与“Knowledge based QA”区分,该系统使用的训练数据都是问答对(即问题是一句话,回答是确定的一个词或一句话),而问答对并不能构成知识,知识必须是有结构的、有属性的、互相关联的数据。

1.2.2 基于知识的问答

(此部分实际就是知识图谱课程的问答系统那部分实现的功能)

学术上不区分基于知识的问答,还是基于知识图谱的问答(知识和知识图谱的概念可以认为等价)。

要构建这种类型的问答系统,需要提供一个知识库,例如一堆有关联的三元组(Triples),它们可以存储于普通数据库或者专门的图数据库(graph database),或者语义网相关的 RDF 数据库。

系统的输入是自然语言,上下文是知识库,输出结果往往是知识库中的一个关系或者一个实体。

1.2.3 基于检索的问答

基于检索的问答系统也可以被称为Text-based的问答,问答的输入是用户的一个问题,而上下文是一个或多个文字信息碎片,输出答案可能是一个实体、一个属性、或者一个短句。

1.3 聊天机器人

聊天机器人chatbot也可以被称为Non-task-oriented dialogue system,或者 General dialogue system,此类系统不需要完成一个明确任务(和上面的对话系统相反),它的存在目的往往只是为了尽可能的延长对话,并且完成一些的模糊的目标。例如排解用户无聊,打发时间,一些隐含的心理状况分析,鼓励用户,讨好用户。

模板方法是机器人相关实现的最重要的方法(没有之一),实际上在绝大多数的对话系统中都依然存在(Siri,小冰),并且在一些学术文献上被证明与其他系统相比依然拥有更好的效果(当然现在(2023/4/27)已经证明GPT才是最强的)。

与模板方法相对的,是基于深度学习的 Neural Conversation 模型,是一种基于神经机器翻译技术演变而来的对话生成模型。类似机器翻译(输入的是问题,给出的“翻译”是回答,对话中,上一句对话也可以“翻译”到下一句对话;类似的还有作诗机,上一句诗“翻译”出下一句诗(GPT本质上就是一种基于Transformer的生成式对话模型)。

另外一种兼容上述两者的模型是混合模式,即兼具检索模式和生成模式,在之前的一段时间作为聊天机器人最常用的一种解决方案。其中的检索模式主要产生候选数据集,生成模式来产生最终的答案。

2.Seq2Seq架构

Seq2Seq架构与语言模型的关系:

  • 语言模型是一种可以生成文本或预测一系列单词的可能性的模型。它们通常用于语言生成、文本分类和语言建模等任务。

  • Seq2Seq(序列到序列)是一种神经网络架构,通常用于自然语言处理(NLP)任务,例如机器翻译和文本摘要。该架构旨在接受一系列输入标记(例如单词或字符)并生成一系列输出标记。

Seq2Seq序列可以用作一种语言模型(应用领域交叉),其中输入和输出序列代表同一文本的不同部分。例如,在机器翻译中,输入序列可能是一种语言中的句子,而输出序列则是将该句子翻译成另一种语言的结果。同时Seq2Seq模型可以使用类似于语言模型的技术进行训练,例如最大似然估计或困惑度最小化。还可以使用注意力机制进行增强,这在语言模型中通常用于提高性能。

Seq2Seq架构和语言模型都是NLP中重要的工具,这两个工具没有孰优孰劣之分(毕竟它俩的应用范围不同):

  • Seq2Seq架构通常用于涉及生成或转换序列的任务,例如机器翻译、文本摘要和对话生成。旨在将可变长度的标记序列作为输入,并生成可变长度的标记序列作为输出;
  • 语言模型在NLP中用于广泛的任务,包括语言生成、文本分类、情感分析等。语言模型旨在预测一系列单词的概率或根据给定提示生成新的文本;

使用哪种工具取决于具体的任务和输入输出数据的性质。

Seq2Seq(Sequence-to-sequence)正如字面意思:输入一个序列,输出另一个序列,其中输入序列和输出序列的长度是可变的。任何满足[输入序列,输出序列]条件的架构都可以称为Seq2Seq序列架构。

Seq2Seq序列问题 -- 翻译“知识就是力量”

针对Seq2Seq序列问题,比如翻译一句话,可以通过Encoder-Decoder模型来解决。

Encoder-Decoder指的是同时具备编码和解码的框架(如下图所示),借助中间的定长向量C传递信息

针对不同的任务,可以选择不同的编码器和解码器(常选用RNN、LSTM或GRU)

二、前端网页

整体目录结构如下

1
2
3
4
5
6
7
8
├─ chatCSS
| └─ chat.css
├─ chatImages
├─ chatJs
| ├─ chat.js
| ├─ flexible.js
| └─ jquery.min.js
└─ index.html

其中

  • .html文件为网页文件,默认使用浏览器打开渲染得到与用户进行交互的可视化界面;
  • chat.css使用css文件对html网页进行可视化的修饰;
  • chatImages保存了前端网页中可能会使用到的一些图像文件;
  • chatJs为javascript语言编写的网页逻辑交互代码;

1.chatJs

1.1 chat.js

主要实现了一个简单的聊天界面,用户输入消息后将其发送给服务器,并将服务器返回的消息显示在界面上。下面是对代码的一些解释

定义show函数,用于在聊天界面中显示聊天信息。它接受三个参数:headSrc表示头像的路径,str表示要显示的文本消息,className表示要应用于消息容器的类名。函数会生成一个包含头像和消息文本的HTML字符串,并调用upView函数将该字符串添加到聊天界面中。

1
2
3
4
5
6
/*聊天信息*/
function show(headSrc, str, className) {
var html = "<div class=" + className + "><div class='msg'><img src=" + headSrc + " />" +
"<p><i class='msg_input'></i>" + str + "</p></div></div>";
upView(html);
}

定义upView的函数,用于更新聊天界面的视图。它接受一个包含HTML代码的字符串作为参数,并将该字符串添加到具有类名为message的容器中。然后,它使用动画效果将页面滚动到最底部,以确保最新的消息可见。

1
2
3
4
5
6
7
/*更新视图*/
function upView(html) {
$('.message').append(html);
$('body,html').animate({
scrollTop: $('.message').outerHeight() - window.innerHeight
}, 200); // 自动将页面移动到最底部
}

接着定义两个变量:flag和message。flag用于控制防止连续点击提交消息的功能,message用于接收接口返回的数据。

1
2
var flag = true; // 防止连续点击提交消息
var message = ''; // 接收接口返回的数据

下面是一个jQuery的文档就绪函数,表示当文档加载完成后执行其中的代码。在这个函数内部,首先给具有id为inputVal的元素设置焦点,然后为具有类名为footer的元素的子元素input绑定了一个keyup事件处理程序。当输入框中有内容时,将背景颜色设置为#114F8E,否则设置为#ddd。

1
2
3
4
5
6
7
8
9
$(function () {
$('#inputVal').focus();
$('.footer').on('keyup', 'input', function () {
if ($(this).val().length > 0) {
$(this).next().css('background', '#114F8E');
} else {
$(this).next().css('background', '#ddd');
}
});

接下来的两行代码为具有类名为footer的元素下的p元素绑定了一个click事件处理程序,并为整个文档绑定了一个keyup事件处理程序。当用户点击footer元素下的p元素时,会调用getMessage函数。当用户按下回车键时,也会调用getMessage函数。

1
2
3
4
5
6
$('.footer p').click(getMessage);
$(document).keyup(function (ev) {
if (ev.keyCode == 13) {
getMessage();
}
})

getMessage函数用于获取用户输入的消息,并发送给服务器进行处理。函数首先获取具有id为inputVal的输入框的值,并存储在变量val中。然后,它检查输入框是否为空,如果是空的话则直接返回。接下来,它检查flag变量,如果为true,则执行以下操作:

  1. 将flag设置为false,以防止连续点击提交消息。
  2. 调用show函数,将用户输入的消息显示在聊天界面中。
  3. 设置变量url为一个API的地址。
  4. 清空输入框的值并将背景颜色设置为#ddd。
  5. 发起一个异步的AJAX请求,使用GET方法向服务器发送数据。请求的URL是上面定义的url,数据是用户输入的消息。
  6. 在请求完成后,将flag设置为true,以允许再次点击提交消息。
  7. 将服务器返回的响应文本存储在message变量中。
  8. 使用setTimeout函数延迟一定时间后,调用show函数将服务器返回的消息显示在聊天界面中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
	function getMessage() {
var 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";
$(".footer input").val("").next().css('background', '#ddd'); //清空input
$.ajax({
type: "get",
dataType: "json",
async: true,
url: url,
data: {
infos: val,
},
complete: function (data) {
flag = true;
message = data.responseText
setTimeout(function () {
show("chatImages/man.png", message, "send");
}, 500);
}
});
}
}
});

1.2 flexible.js

该文件主要定义了一个自执行函数,根据屏幕尺寸动态调整网页的字体大小以实现响应式布局。代码整体逻辑如下:

  1. 定义了一个匿名函数,并立即执行该函数。函数接受两个参数:doc和win,分别表示document对象和window对象。
  2. 在函数内部,首先获取了docEl变量,它引用了doc.documentElement,即网页的根元素。
  3. 根据当前浏览器支持的事件,将resizeEvt变量设置为’orientationchange’(用户水平或垂直翻转设备时触发的事件)或’resize’(调整窗口大小时触发的事件)。
  4. 定义了一个名为recalc的函数,用于计算并设置根元素的字体大小。在该函数内部,首先获取了当前浏览器窗口的宽度,保存在clientWidth变量中。
  5. 判断clientWidth是否存在(不为0或undefined)。如果为0或不存在,表示无法获取到有效的窗口宽度,函数直接返回。
  6. 接下来,根据窗口宽度clientWidth的大小来设置根元素的字体大小。如果clientWidth大于等于720,将根元素的fontSize属性设置为’100px’;否则,根据当前窗口宽度与720的比例,动态计算并设置根元素的字体大小。
  7. 在函数末尾,通过条件判断检测是否支持addEventListener方法。如果不支持,则直接返回,不进行后续的事件监听。
  8. 使用win.addEventListener方法监听resizeEvt事件,当浏览器窗口大小改变时,调用recalc函数重新计算并设置根元素的字体大小。
  9. 使用doc.addEventListener方法监听DOMContentLoaded事件,即当初始的 HTML 文档被完全加载和解析完成时,调用recalc函数。

通过执行这段代码,页面加载时会根据浏览器窗口的大小动态调整根元素的字体大小,以适应不同的设备和屏幕尺寸,实现响应式的布局效果。

1.3 jquery.min.js

jquery.min.js是jQuery库的压缩版本,它包含了jQuery框架的所有功能和方法,但经过了压缩和优化,使得文件大小更小,加载速度更快。

jQuery是一个JavaScript库,提供了许多简化和增强JavaScript编程的功能。它封装了复杂的DOM操作、事件处理、动画效果、AJAX请求等常见的Web开发任务,使得开发者可以使用更简洁的语法来完成这些任务。

使用jquery.min.js文件,可以在网页中引入jQuery库,并在代码中使用jQuery提供的方法和功能。通过使用jQuery,开发者可以更快速、高效地编写和管理JavaScript代码,简化了对浏览器差异性的处理,提高了开发效率。

如果在项目中使用了jQuery库,通常会在HTML文件中通过

博客在允许 JavaScript 运行的环境下浏览效果更佳