目录
概述聊天机器人
目前机器学习,尤其是深度学习,已经成功的解决了图像识别的问题。从IMAGENET大赛的近几年成绩看,识别类问题准确度已经接近100%。
与此同时,机器学习在解决“语音到文字”(Speech to Text)以及“文字到语音” (Text to Speech)方面也有了飞跃。
而一群更加疯狂的人在尝试用机器学习解决自然语音理解,甚至在自然语言理解的基础上,开发聊天机器人。
服务 | 描述 | 地址 |
---|---|---|
Botframework by Microsoft | 提供会话管理,跨平台连接方案 | https://dev.botframework.com/ |
API.AI | 会话训练,会话管理,语音识别,意图识别,一系列训练好的主题 | https://api.ai/ |
Telegram Bot Store | 聊天机器人应用商店 | https://storebot.me/ |
通过这三个服务, 就可以构建聊天机器人并且发布上线。
- Step 1 - 在Telegram上注册账号
通过 BotFather创建Bot。
- Step 2 - 在Botframework上注册账号
创建一个Bot, 同时下载Botframework提供的SDK/Sample( Node.js|C#),连接到Telegram。
基于Botframework的对话,要写很多代码实现,这样我们更需要一个连接到已经提供一些对话的服务上。
- Step 3 - 接入 API.AI
API.AI可以提供标注对话,开放域对话和语音识别,意图识别等功能。
- Step 4 - 服务发布
Telegram是一个神奇的IM,它提供了聊天机器人应用商店。使用Telegram IM的用户可以快速体验和使用这些Bot。
一些Bot的体验真的很棒,尤其是使用了人工智能技术的Bot,以至于会出现下面的评论。
还有其他聊天机器人的玩家:wit.ai, Chatfuel, Facebook Messager, Apple Siri, 腾讯机器人平台, Microsoft LUIS.AI, etc.
不管是像微软这样的大公司,还是像Operator在垂直领域提供服务的创业公司,都将聊天机器人看成是下一代人机交互的服务形态,聊天机器人不单纯的提供了一个新的服务渠道,它还改变了服务本身,即通过历史数据训练Language Model,来部分取代人的作用,聊天机器人对信息的组织和处理能力,在搜索引擎基础上,又往前迈了一大步。比如,京东JIMI依靠DeepQA系统,实现“最强大脑”,JIMI就是聊天机器人的一个形态。
聊天机器人模型分类
基于检索的模型
回答是提前定义的,使用规则引擎、正则匹配或者深度学习训练好的分类器从数据库中挑选一个最佳的回复。
基于生成的模型
不依赖于提前定义的回答,但是在训练的过程中,需要大量的语料,语料包含了context和response 。当下流行使用LSTM和 RNN训练生成的模型,这种方法最早用来完成机器翻译的任务 - Sequence to Sequence Learning with Neural Networks。
目前,在生产环境下,提供聊天服务的,一般都是基于检索的模型,而Seq2Seq的出现,有可能使基于生成的模型成为主流,因为Seq2Seq在长对话的情况下,依然可以表现的很好。
长对话和短对话
长对话需要考虑的因素更多,就像目前API.AI提供的服务中,要完成一个任务,比如预定酒店。
小明: 帮我订今天晚上,上海浦东香格里拉酒店。
这时,API.AI得到了时间,地点和人员。它可能正好检索到了我们在订酒店故事里的一条被标注的记录。Intent, Entity确定了, Action就被确定了。
可是,如果是下面:
小明: 帮我订今天晚上,上海的酒店。
Chatbot就要询问:
Bot: 你需要订哪家酒店?
长对话,其实就是能在用户场景下对话,要识别场景,就需要考虑时间、地点、刚刚用户都说了什么以,用户和Bot的关系。
"订酒店"属于个人助理类服务,目前,api.ai已经支持了这种“追问用户更多信息”的功能,属于简单的问题。
而类似于客服机器人,更多情况是多问题-多交织的对话,就是长对话中,很难解决的问题。
所以,当下,大量机器人是面向短对话的。比如,微软小冰,小娜,图灵机器人, etc.
开放领域和封闭领域
这两个主要从话题层面进行区分。在开放语境下,用户可以和聊天机器人聊任何话题。在封闭语境下,只能聊机器人设定的主题。
这主要取决于数据:有什么数据,就能聊什么主题。
比如在车载系统中,对话的机器人一般都是十个左右的意图,围绕意图进行训练聊天主题。
老司机一般都聊什么?
- 服务区还有多远?
- 我买的股票怎么样?
- 播放一个音乐
- 听交通台
- 呼叫一个电话
- …
挑战
关联上下文
关联上下文,就需要在设计机器人的时候,给它一个问题,获得一个回复。生成回复的时候,要考虑 P, U, L.
- P - Personality matrix
- U - User Relationship with Bot
- L - Lexicon
这需要在训练LSTM Net的时候,要将更多信息注入,而且也更像是将基于检索的模型和基于生成的模式混合起来完成。
意图识别
就像API.AI, 及其WIT.AI, LUIS.AI们构想的一样,要完成有效的对话,先要搞清楚用户在表达什么意图。但是目前API.AI们提供的方案需要人工标注Entity和Intent,这种工作很繁琐,效率低。
能通过历史数据,无监督或者半监督的完成意图的分类模型是亟须解决的一个挑战。
如何判断一个模型的好坏
在使用LSTM训练基于生成的模型的过程中,一个很大的挑战就是没有自动化的量化的标准:除了人工的和模型对话意外,不确定模型间的好坏。
这个问题的解决办法,应该是在训练时,就同时训练正确的回答和错误的回答,然后使用recall@k机制验证。
一种设想
在经过了很多调研和尝试后,一种比较Smart的机器人的实现方案可能是下面这个样子:
-
从社交网络上对接到服务需要走InboundMessage, 从OutboundMessage中异步获取回复。
-
Bot Engine 处理session, context, personality,知识图谱,对话规则和主题。
对话主题是基于人工经验制作的。除了包括引导用户做自我介绍类的"系统对话",还要包括实现业务价值的"服务对话",比如“学习英语单词”,还要有“日常对话”,比如打招呼,询问最近看的电影等生活场景。
- Bot Engine不能做到回复所有问题,因为基于规则的原因,能覆盖的聊天内容范围小,当在Bot Engine中,得不到好的答案或者没有命中一个规则时,就请求背后的Bot Model.
Bot Model是通过深度神经网络训练而来,可以回答任何问题。
-
在对话服务过程中,会产生新的数据,使用强化学习,给Bot Model正向的激励。
-
使用知识图谱记录Bot,User, World三层知识。
以上主要介绍聊天机器人目前发展的状况和分类,下面,将对上图所设想的方案做更多描述。
问题域
Speech to Text => Logic => Text to Speech
STT和TTS,目前有很多厂商提供技术产品:
Speech to Text 语音识别技术
Google Cloud Platform, IBM Watson API, 云知声,科大讯飞
Text to Speech 语音合成技术
经过多年的研究,尤其是深度学习的采用,在这两项技术上取得了突破性进展。今天本文所要讨论的是logic,而且是基于规则引擎的logic, 基于机器学习的部分将在以后的文章中讨论。
Conversation Model
在两个人之间的对话,可以用下面这个模型表示,双方头脑中所要向对方表达的目标,需要通过语言来交换意见,为了达成共识,二者需要在一个语境下。
https://vimeo.com/43677920
为了支撑这个模型,在设计Bot Engine过程中,要考虑如下的要点:
- 低成本的构建对话
- 能区分不同类型的对话
- 规范化输入
- 高效率的规则引擎
- 用户画像
- 回复时,考虑对话的历史记录
低成本的构建对话
构建聊天内容最好是不需要有开发技能,而且有的开发者也没有很好的聊天的技能。即便像Botframework这样的大厂的产品,在构建对话时,都不够友好,只能面向有开发技能的人,而且是一种硬编码。这样对于维护对话很不利。
使用Botframework的waterfall,设计对话的人需要了解builder.Prompts接口和session.beginDialog|endDialog。这样做很不合理。
exports.start = [(session, arg, next) => {
builder.Prompts.text(session, "Do you want to start Class now?");
}, (session, results) => {
co(function*() {
return yield watson.sentiment(results.response);
}).then(function(o) {
let reply;
switch (o.docSentiment.type.toLowerCase()) {
case 'positive':
reply = '_begin_';
break;
case 'negative':
reply = "Got it."
break;
case 'neutral':
reply = "Ok, then.";
break;
}
if (reply == '_begin_') {
session.beginDialog('/daily_lessons/vocabulary');
} else {
builder.Prompts.text(session, reply);
session.endDialog();
}
});
}];
而另外一方面,使用script的方式,显得更合理,比如SuperScript.
+ Do you want to start Class now?
- start_class
+ ~yes
% Do you want to start Class now
- Great, ^redirectTo(/daily_lessons/vocabulary)
+ ~no
% Do you want to start Class now
- Ok, then.
还有rivescript, chatscript, 同样类似于superscript方式进行构建对话。
能区分不同类型的对话
设计对话时,至少有三种类型的对话:
- system
系统对话,只能聊一次,或者只能由系统主动发出。比如自我介绍,bot和小明进行初次对话,bot会问:“你叫什么名字?”。小明回答“小明”。那么bot就知道"id:xxx"是小明。而将来bot都不应该再问这个问题。
- daily
这些是bot可以重复和用户聊的主题,可能并不是每天,它们可以每隔一段频率就触发,比如:问候,节日祝福,“你在做什么”, etc.
- business
和一些闲聊的机器人不同,bot应该提供一些价值,这些价值可能是个人信息助手, 导购, 教育, 播放音乐。
声明对话类型:
> topic:business (vocabulary class)
+ Do you want to start Class now?
- start_class
+ ~yes
% Do you want to start Class now
- Great, ^redirectTo(/daily_lessons/vocabulary)
+ ~no
% Do you want to start Class now
- Ok, then.
<
所以,一个对话看起来像是这个样子。
规范化输入
表达同样的意思,可以有多种表示方法。
whats the color of the calanders
what is the colour of the calenders
what be the colour of the calender
在将输入语句传给规则引擎前,要先做规则化处理。比如:
-
tokenized - 分词
-
stemmed - 英文单词取词根
-
lemmatized - 英文单词变形的归类(例如单复数归类)
-
part-of-speech (POS) tagger - reads text in some language and assigns parts of speech to each word
-
named entity recognizer (NER) - [ labels sequences of words in a text which are the names of things]
(http://nlp.stanford.edu/software/CRF-NER.html)
专有名词 - 人名、地名、组织名、URL链接、系统路径等
这里需要结合很多工具库来实现:NLTK, Stanford CoreNLP, Jieba分词,Wordnet, ConceptNet.
比如,借助Stanford CoreNLP,可以有下面的标注:
经过规范化输入,在规则引擎中,可以依赖词性和函数实现更智能的回答。
高效率的规则引擎
Bot可以有大量的主题,即便是只有100主题,每个主题15个对话,那就是1500个规则。如果只是单机运行,至少要进行下面两个优化:
- 排序
通过聊天的记录和关键字,先给对话栈排序。
排序的思路大概是这样:
1) 查看当前对话,是否还有下文,一个对话的下文可以对应多个规则。
如果有下文,检测是否一个规则能匹配上输入。如果匹配上了,回复。
如果没有下文,或者没有规则能匹配上,进入次优匹配。
2) 次优匹配是将聊天主题的历史记录,使用TF-IDF算法进行排序。
简单说,就是使用一个函数计算用户聊天的对应主题频率。给不同的聊天主题加权重。在次优匹配中,都是处理用户曾经聊过的主题。
3) 在次优匹配中,没有命中,进入其他匹配。
其他匹配包括了以前没有聊过的主题。
- 并发
在排序后,去同时处理匹配运算,将命中的规则的回复,按照排序的顺序放到数组里,然后,从数组中取第一个元素。这样就比按照顺序一个一个检测快很多。
比如,一些Node.js模块:async。
用户画像
在和用户聊天的过程中,获取到的用户相关的信息,有必要记录在数据库中,这其实是构建知识图谱的过程。
知识图谱所用的数据库是存在三个字段的结构化数据:
{
"subject": "Mao",
"predict": "chairman",
"object": "China"
}
由此构建了一个关系:
而B又可以跳转到D。
目前,较为成熟的商业产品和开源方案都有。
在Bot Engine中,可以得到相关用户的Knowledge Graph.
this.user.memory.get( ...)
this.bot.createUserFact( ...)
使用知识图谱,除了对实体之间完成关系构建外,还有一个原因是,搜索速度非常快,搜索功能强大。
开源的脚本引擎
介绍了这么多,那么到底怎么实现一个Bot Engine呢?经过了很多比较后,我觉得基于SuperScript实现Bot Engine是可行的。主要是下面这几点:
-
社区活跃:目前稳定版本v0.12.2没有bug, 最新版v1.0.0也在快速开发。
-
轻便灵活: 将SuperScript的源码读了一遍,觉得即便是作者不维护了,我也可以维护。
-
功能强大:在上面讨论的问题中,SuperScript都是有涉及的。
对话脚本
- topic type - 话题
- conversation - 对话
- function - 插件和函数
快速开始
npm install superscript
var superscript = require("superscript");
new superscript({ ...}, function(err, bot){
bot.reply("userId", "hello", function(err, reply){
// do your magic
})
})
未来发展
很多人预计2017年,AI方向最可能取得成功的领域是聊天机器人。那么,在这种情况下,面向聊天机器人的架构设计,是一个热门问题。包括Google,Facebook都有可能发布类似于微软的Botframework平台。Bot Engine, 一种处理对话的引擎,起着很关键的作用。在开源社区,还没有看到哪个呼声非常高的实现,SuperScript,至少在JavaScript社区,是一个不错的选择。
接下来,一起看看使用深度学习技术,依靠聊天语料,训练Bot Model。
数据预处理
模型能聊的内容也取决于选取的语料。如果已经具备了原始聊天数据,可以用SQL通过关键字查询一些对话,也就是从大库里选取出一个小库来训练。从一些论文上,很多算法都是在数据预处理层面的,比如Mechanism-Aware Neural Machine
for Dialogue Response Generation就介绍了,从大库中抽取小库,然后再进行融合,训练出有特色的对话来。
【图 3-1】 语料预处理, Ref. #7
对于英语,需要了解NLTK,NLTK提供了加载语料,语料标准化,语料分类,PoS词性标注,语意抽取等功能。
另一个功能强大的工具库是CoreNLP,作为 Stanford开源出来的工具,特色是实体标注,语意抽取,支持多种语言。
下面主要介绍两个内容:
中文分词
现在有很多中文分词的SDK,分词的算法也比较多,也有很多文章对不同SDK的性能做比较。做中文分词的示例代码如下。
# coding:utf8
'''
Segmenter with Chinese
'''
import jieba
import langid
def segment_chinese_sentence(sentence):
'''
Return segmented sentence.
'''
seg_list = jieba.cut(sentence, cut_all=False)
seg_sentence = u" ".join(seg_list)
return seg_sentence.strip().encode('utf8')
def process_sentence(sentence):
'''
Only process Chinese Sentence.
'''
if langid.classify(sentence)[0] == 'zh':
return segment_chinese_sentence(sentence)
return sentence
if __name__ == "__main__":
print(process_sentence('飞雪连天射白鹿'))
print(process_sentence('I have a pen.'))
以上使用了langid先判断语句是否是中文,然后使用jieba进行分词。
在功能上,jieba分词支持全切分模式,精确模式和搜索引擎模式。
全切分:输出所有分词。
精确:概率上的最佳分词。
所有引擎模式:对精确切分后的长句再进行分词。
jieba分词的实现
主要是分成下面三步:
1)加载字典,在内存中建立字典空间。
字典的构造是每行一个词,空格,词频,空格,词性。
上诉书 3 n
上诉人 3 n
上诉期 3 b
上诉状 4 n
上课 650 v
建立字典空间的是使用python的dict,采用前缀数组的方式。
使用前缀数组的原因是树结构只有一层 - word:freq,效率高,节省空间。比如单词"dog", 字典中将这样存储:
{
"d": 0,
"do": 0,
"dog": 1 # value为词频
}
字典空间的主要用途是对输入句子建立有向无环图,然后根据算法进行切分。算法的取舍主要是根据模式 - 全切,精确还是搜索。
2)对输入的语句分词,首先是建立一个有向无环图。
有向无环图, Directed acyclic graph (音 /ˈdæɡ/)。
DAG
DAG对于后面计算最大概率路径和使用HNN模型识别新词有直接关系。
3)按照模式,对有向无环图进行遍历,比如,在精确模式下,便利就是求最大权重和的路径,权重来自于在字典中定义的词频。对于没有出现在词典中的词,连续的单个字符也许会构成新词。然后用HMM模型和Viterbi算法识别新词。
精确模型切词:使用动态规划对最大概率路径进行求解。
最大概率路径:求route = (w1, w2, w3 ,…, wn),使得Σweight(wi)最大。Wi为该词的词频。
更多的细节还需要读一下jieba的源码。
自定义字典
jieba分词默认的字典是:1998人民日报的切分语料还有一个msr的切分语料和一些txt小说。开发者可以自行添加字典,只要符合字典构建的格式就行。
jieba分词同时提供接口添加词汇。
Word embedding
使用机器学习训练的语言模型,网络算法是使用数字进行计算,在输入进行编码,在输出进行解码。word embedding就是编解码的手段。
word embedding
word embedding是文本的数值化表示方法。表示法包括one-hot,bag of words,N-gram,分布式表示,共现矩阵等。
Word2vec
近年来,word2vec被广泛采用。Word2vec输入文章或者其他语料,输出语料中词汇建设的词向量空间。详细可参考word2vec数学原理解析。
- 使用word2vec
安装完成后,得到word2vec命令行工具。
word2vec -train "data/review.txt" \
-output "data/review.model" \
-cbow 1 \
-size 100 \
-window 8 \
-negative 25 \
-hs 0 \
-sample 1e-4 \
-threads 20 \
-binary 1 \
-iter 15
-train “data/review.txt” 表示在指定的语料库上训练模型
-cbow 1 表示用cbow模型,设成0表示用skip-gram模型
-size 100 词向量的维度为100
-window 8 训练窗口的大小为8 即考虑一个单词的前八个和后八个单词
-negative 25 -hs 0 是使用negative sample还是HS算法
-sample 1e-4 采用阈值
-threads 20 线程数
-binary 1 输出model保存成2进制
-iter 15 迭代次数
在训练完成后,就得到一个model,用该model可以查询每个词的词向量,在词和词之间求距离,将不同词放在数学公式中计算输出相关性的词。比如:
vector("法国") - vector("巴黎) + vector("英国") = vector("伦敦")"
对于训练不同的语料库,可以单独的训练词向量模型,可以利用已经训练好的模型。
其它训练词向量空间工具推荐:Glove。
Seq2Seq
2014年,Sequence to Sequence Learning with Neural Networks提出了使用深度学习技术,基于RNN和LSTM网络训练翻译系统,取得了突破,这一方法便应用在更广泛的领域,比如问答系统,图像字幕,语音识别,撰写诗词等。Seq2Seq完成了【encoder + decoder -> target】的映射,在上面的论文中,清晰的介绍了实现方式。
Seq2Seq
也有很多文章解读它的原理。在使用Seq2Seq的过程中,虽然也研究了它的结构,但我还不认为能理解和解释它。下面谈两点感受:
a. RNN保存了语言顺序的特点,这和CNN在处理带有形状的模型时如出一辙,就是数学模型的设计符合物理模型。
RNN
b. LSTM Cell的复杂度对应了自然语言处理的复杂度。
LSTM
理由是,有人将LSTM Cell尝试了多种其它方案传递状态,结果也很好。
GRU
LSTM的一个替代方案:GRU。只要RNN的Cell足够复杂,它就能工作的很好。
使用DeepQA2训练语言模型
准备工作,下载项目:
git clone git@github.com:chatopera/deep-qa.git
cd deep-qa
open README.md # 根据README.md安装依赖包
DeepQA2将工作分成三个过程:
a. 数据预处理:从语料库到数据字典。
b. 训练模型:从数据字典到语言模型。
c. 提供服务:从语言模型到RESt API。
预处理
DeepQA2使用Cornell Movie Dialogs Corpus作为demo语料库。
原始数据就是movie_lines.txt 和movie_conversations.txt。这两个文件的组织形式参考README.txt
deepqa2/dataset/preprocesser.py是将这两个文件处理成数据字典的模块。
train_max_length_enco就是问题的长度,train_max_length_deco就是答案的长度。在语料库中,大于该长度的部分会被截断。
程序运行后,会生成dataset-cornell-20.pkl文件,它加载到python中是一个字典:
word2id存储了{word: id},其中word是一个单词,id是int数字,代表这个单词的id。
id2word存储了{id: word}。
trainingSamples存储了问答的对话对。
比如 [[[1,2,3],[4,5,6]], [[7,8,9], [10, 11, 12]]]
1,2,3 … 12 都是word id。
[1,2,3] 和 [4,5,6] 构成一个问答。
[7,8,9] 和 [10, 11, 12] 构成一个问答。
开始训练
cp config.sample.ini config.ini # modify keys
python deepqa2/train.py
config.ini是配置文件, 根据config.sample.ini进行修改。训练的时间由epoch,learning rate, maxlength和对话对的数量而定。
deepqa2/train.py大约100行,完成数据字典加载、初始化tensorflow的session,saver,writer、初始化神经元模型、根据epoch进行迭代,保存模型到磁盘。
session是网络图,由placeholder, variable, cell, layer, output 组成。
saver是保存model的,也可以用来恢复model。model就是实例化variable的session。
writer是查看loss fn或者其他开发者感兴趣的数据的收集器。writer的结果会被saver保存,然后使用tensorboard查看。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QvfKN6ti-1602475108350)(https://ischlag.github.io/images/cost_graph.png)]
TensorBoard
Model
Model的构建要考虑输入,状态,softmax,输出。
定义损耗函数,使用AdamOptimizer进行迭代。
最后,参考一下训练的loop部分。
每次训练,model会被存储在 save路径下,文件夹的命名根据机器的hostname,时间戳生成。
提供服务
在TensorFlow中,提供了标准的serving模块 - tensorflow serving。但研究了很久,还专门看了一遍 《C++ Essentials》,还没有将它搞定,社区也普遍抱怨tensorflow serving不好学,不好用。训练结束后,使用下面的脚本启动服务,DeepQA2的serve部分还是调用TensorFlow的python api。
cd DeepQA2/save/deeplearning.cobra.vulcan.20170127.175256/deepqa2/serve
cp db.sample.sqlite3 db.sqlite3
python manage.py runserver 0.0.0.0:8000
测试
POST /api/v1/question HTTP/1.1
Host: 127.0.0.1:8000
Content-Type: application/json
Authorization: Basic YWRtaW46cGFzc3dvcmQxMjM=
Cache-Control: no-cache
{"message": "good to know"}
response
{
"rc": 0,
"msg": "hello"
}
serve的核心代码在serve/api/chatbotmanager.py中。
使用脚本
scripts/start_training.sh 启动训练
scripts/start_tensorboard.sh 启动Tensorboard
如果你觉得本文对你有帮助,请帮忙star。
https://github.com/chatopera/deep-qa
对模型的评价
目前代码具有很高的维护性,这也是从DeepQA项目进行重构的原因,更清晰的数据预处理、训练和服务。有新的变更可以添加到deepqa2/models中,然后在train.py和chatbotmanager.py变更一下。
有待改进的地方
a. 新建models/rnn2.py, 使用dropout。目前DeepQA中已经使用了Drop.
b. tensorflow rc0.12.x中已经提供了seq2seq network,可以更新成tf版本.
c. 融合训练,目前model只有一个库,应该是设计一个新的模型,支持一个大库和小库,不同权重进行,就如Mechanism-Aware Neural Machine
for Dialogue Response Generation的介绍。
d. 代码支持多机多GPU运行。
e. 目前训练的结果都是QA对,对于一个问题,可以有多个答案。
f. 目前没有一个方法进行accuracy测试,一个思路是在训练中就提供干扰项,因为当前只有正确的答案,如果提供错误的答案(而且越多越好),就可以使用recall_at_k方法进行测试。
本系列文章
延伸阅读
NaturalNode - General
natural language facilities for node.
SuperScript - A dialog system and bot engine for conversational UI’s.
Stanford CoreNLP - a suite of core NLP tools
Natural Language Toolkit - NLTK is a leading platform for building Python programs to work with human language data.
How to Cook a Graph Database in a Night - A Knowledge Graphic tool based on LevelDB.
Sequence to Sequence Learning with Neural Networks
Efficient Estimation of Word Representations in Vector Space
Tensorflow and deep learning - without a PhD by Martin Görner