基于开源Alice的聊天机器人

lili

发布日期: 2018-11-20 23:31:30 浏览量: 2058
评分:
star star star star star star star star star_border star_border
*转载请注明来自write-bug.com

一、聊天机器人基本原理

语料库中的pattern是模式的意思,可理解为问题,而相应的template可理解为回答(而这一对问答被包裹在了category标签里面)。假如你的语料库像上面的xml文件这样简单,那么当你输入“你是谁”,机器人就会在内存中去一个一个的匹配pattern,最后匹配到了,就会回答“我是小龙”,而你输入其他任何语句,机器人就无从匹配了,程序会出现匹配不到的错误。那么怎样避免程序出错呢?我们修改语料库如下:

上图中的*,是AIML中的通配符,它匹配任何你输入的语句。当你输入的语句成功匹配,那么返回相应的template后,就不会再去匹配其他的category了。假如程序没有任何相匹配的,那么*总是可以匹配你的输入,机器人会输出“对不起,主人还没有教我怎么回答这个问题呢

当然AIML解析器所支持的xml标签种类远不止这些,上述是最基本的。AIML所支持的标签种类目测有20种。

二、为什么AIML解析器不支持中文

国外的一款做的很好的聊天机器人(通过了图灵测试),她叫“Alice”(你可以用英文和Alice聊天),它内部有很庞大的语料库,几乎所有用户可能说的话,语料库中都有,而且它用的就是AIML解析器。然而AIML程序中有一些地方会用正则表达式将除了英文字母和数字外的其他字符全部用空格替代,这就是Aiml解析器不支持中文的重要原因,这种正则表达式出现在bitoflife.chatterbean.text.Transformations 类中,比如下面这个函数:

其中fitting为:

上面的normalization函数是用来对用户输入做规范化处理的,它做了一下工作:

  • 在原始输入的内容两头加空格

  • 把句子中间的任何2个以上连续的空白字符都替换成一个空格

  • 并对input进行字符过滤

修改完正则表达式之后,算是成功了一半,那还需要做怎么处理呢?

我们知道AIML当初仅仅是针对English语言开发的,而English单词之间都是有空格的,所以在前期载入语料库阶段,解析器默认xml语料库中的词之间都有空格,然后通过空格将句子分成一个个单词,最后在内存构建一个匹配树,而且在处理用户输入的句子时也是将其进行了规范化处理,如下代码:

而为了让它支持中文,一个比较直观的方法是在对用户输入做规范化处理的时候,我们将输入的中文句子中加入空格,比如上述代码的chineseTranslate函数:

同理,在载入语料库的时候,同样需要写一个类似的函数,功能就是将语料库中的字之间加上空格。

三、关于标签的支持与使用

Aiml标签使用的官方文档

http://www.alicebot.org/TR/2005/WD-aiml

http://www.pandorabots.com/botmaster/en/tutorial?ch=4

根据上面的连接我们可以初步了解各种标签的作用和用法。k

然而,就我现在的中文聊天机器人,有些标签还不能正常使用,这是需要改进的地方。

下面是一些我尝试过的一些标签用法:

最基本:

  1. <category>
  2. <pattern>你好</pattern>
  3. <template>呵呵</template>
  4. </category>

每一个问题和回答都被包裹在<category>标签中

随机返回功能:

  1. <category>
  2. <pattern>你好</pattern> (或者在后面加一个*)
  3. <template>
  4. <random>
  5. <li>你好呀!</li>
  6. <li>嘿嘿</li>
  7. <li>我很好,你呢?</li>
  8. </random>
  9. </template>
  10. </category>
  11. <li>是library的意思,不是list

当你输入“你好”的时候,机器人会从random里面随机取出一句回答你。不过默认的都是先取第一句回答。

输入重定向功能(<srai>):

  1. <category>
  2. <pattern>你好</pattern>
  3. <template>
  4. <random>
  5. <li>你好呀!</li>
  6. <li>嘿嘿</li>
  7. <li>我很好,你呢?</li>
  8. </random>
  9. </template>
  10. </category>
  11. <category>
  12. <pattern>HELLO</pattern>
  13. <template><srai>你好</srai></template>
  14. </category>

输入“hello”的时候,会匹配到第二个category,而srai标签的功能是,将“你好”当成用户的输入,并重新到语料库里去匹配,最后就匹配到了第一个category。换句话说,用户输入“hello”和输入“你好”的效果是一样的。但是在使用srai标签的时候有可能会形成死循环,所以请慎重。

另外需要注意的是,如果你想要在语料库里面写英文的语料库,那么英文单词都要是大写的,而用户输入的英文可以不用大写。如果你觉得用大写很不爽,那么你可以去修改源代码。

*,<think>,<set>,<get>,<star>的使用:

  1. <category>
  2. <pattern>我叫*</pattern>
  3. <template>
  4. <think>
  5. <set name="myname"><star/></set>
  6. </think>
  7. hello, <get name="myname"/>.
  8. </template>
  9. </category>

测试结果为:

  • you say>

  • 我叫小龙

  • Alice>hello, 小龙.

标签解释:

set和get里面的myname相当与参数名,首先在set标签中给myname赋值,然后用get标签得到相应参数的值,如果myname之前没有被赋值,那么就是空字符串。<star/>指的是pattern标签中*所匹配的内容。它还能指定index,举个例子:

  1. <pattern>我叫*呵呵*</pattern>
  2. <template>
  3. <think>
  4. <set name="myname"><star index=”2”/></set>
  5. </think>
  6. hello, <get name="myname"/>.
  7. </template>

那么这时star标签就会被pattern中第2个*号所匹配的内容替代。而<star/>其实相当与<star index=”1”/> <think>标签可以理解为机器人在思考,它只会在“脑子”里默默的记住一些事情,或者完成一些不会被用户看到的工作。

Condition标签使用:

  1. <category>
  2. <pattern>我叫*</pattern>
  3. <template>
  4. <think>
  5. <set name="myname"><star/></set>
  6. </think>hello, <get name="myname"/>.
  7. </template>
  8. </category>
  9. <category>
  10. <pattern>你好*</pattern>
  11. <template>
  12. 你好啊!
  13. <condition name="myname" value="jack">怎么又是你?</condition>
  14. </template>
  15. </category>

测试结果:

  • you say>

  • 我叫jack

  • Alice>hello, jack.

  • you say>

  • 你好啊

  • Alice>你好啊! 怎么又是你?

  • you say>

  • 我叫job

  • Alice>hello, job.

  • you say>

  • 你好啊

  • Alice>你好啊!

标签解释:

<condition>标签中的myname是在set中被赋值的。然后在匹配到“你好*”后,就要判断是不是“jack”

input标签的用法:

  1. <category>
  2. <pattern>我叫*</pattern>
  3. <template>
  4. <think><set name="name"><star/></set></think>
  5. 你好啊,<get name="name"/>
  6. </template>
  7. </category>
  8. <category>
  9. <pattern>嘿嘿</pattern>
  10. <template>
  11. 你刚才说:“<input index="2"/>”?
  12. </template>
  13. </category>

测试结果:

  • you say>

  • 我叫jack

  • Alice>你好啊,jack

  • you say>

  • 嘿嘿

  • Alice>你刚才说:“我 叫 jack”?

标签解释:

<input>标签指的是用户之前的输入,加上一个index,那就是说,用户倒数第几句输入,注意是“倒数”!index=”1”,就是用户倒数第一句输入的内容,以此类推,当然index是会出现越界错误的。

date标签的使用:

  1. <category>
  2. <pattern>现在什么时间*</pattern>
  3. <template><date format="h:mm a"/>.</template>
  4. </category>

测试结果:

  • you say>

  • 现在什么时间啊

  • Alice>It is 9:49 下午.

Date标签将获得当前的系统时间

<that>元素表示先前机器人说的话,例如:

  1. <category>
  2. <pattern>聊什么好呢*</pattern>
  3. <template>一起聊聊电影好吗?</template>
  4. </category>
  5. <category>
  6. <pattern></pattern>
  7. <that>一起聊聊电影好吗?</that>
  8. <template>那你喜欢什么电影呢?</template>
  9. </category>
  10. <category>
  11. <pattern>不好</pattern>
  12. <that>一起聊聊电影好吗?</that>
  13. <template>那我也不知道聊什么了~</template>
  14. </category>

测试结果:

  • you say>

  • 聊什么好呢?

  • Alice>一起聊聊电影好吗?

  • you say>

  • Alice>那你喜欢什么电影呢?

  • you say>

  • 聊什么好呢

  • Alice>一起聊聊电影好吗?

  • you say>

  • 不好

  • Alice>那我也不知道聊什么了~

这个标签还能取前面任意机器人说的话,不过不太熟…没有试验过

如果要取前面的前面机器人的话,可以用:<that index=”nx,ny”>,例如:<that index=”2,1”表示取机器人倒数第2句的话,<that index=”2,1”>也等于<justbeforethat/>

<thatstar>标签:

  1. <category>
  2. <pattern>你好</pattern>
  3. <template>计算机的型号是什么</template>
  4. </category>
  5. <category>
  6. <pattern>*</pattern>
  7. <that>*的型号是什么</that>
  8. <template>
  9. <star/> --》》这里的star标签匹配的是pattern中的*,但是奇怪的,如果把index改成2以后,却也不会出错。
  10. 这个型号是 <thatstar/> 里面
  11. <random>
  12. <li>很好的商品</li>
  13. <li>很流行的商品</li>
  14. <li>很华丽的商品</li>
  15. </random>
  16. </template>
  17. </category>

测试结果:

  • you say>

  • 你好

  • Alice>计算机的型号是什么

  • you say>

  • d

  • Alice>d 这个型号是 里面 很好的商品。

thatstar是匹配pattern-side that标签里面的*号的,但是这里没匹配到。

我想这里也还需要修改源代码。

set标签也有问题。

  1. <category>
  2. <pattern>他做到了</pattern>
  3. <template>谁 ?</template>
  4. </category>
  5. <category>
  6. <pattern>*</pattern>
  7. <that>谁 *</that>
  8. <template>
  9. Oh, why do you think <set name="他"><star/></set> did that? I wouldn't expect that kind of behavior from <get name="他"/>.
  10. </template>
  11. </category>
  12. <category>
  13. <pattern>*</pattern>
  14. <template>啊哦~</template>
  15. </category>

测试结果:

  • you say>

  • 他做到了

  • Alice>谁?

  • you say>

  • 小龙

  • Alice>Oh, why do you think did that? I wouldn’t expect that kind of behavior from .

假如这样写:<set name="he"><star/>

那么测试结果为:

  • you say>

  • 他做到了

  • Alice>谁 ?

  • you say>

  • jack

  • Alice>Oh, why do you think jack did that? I wouldn’t expect that kind of behavior from jack.

也就是说这个标签不支持中文。还是需要修改源代码。

template-side input有问题:

  1. <aiml:input
  2. index = (single-integer-index | comma-separated-integer-pair) />
  3. <category>
  4. <pattern>HELLO</pattern>
  5. <template>吃饭了吗?</template>
  6. </category>
  7. <category>
  8. <pattern>吃了</pattern>
  9. <template>我也吃了</template>
  10. </category>
  11. <category>
  12. <pattern>你好</pattern>
  13. <template>计算机 的 型 号 是 什 么</template>
  14. </category>
  15. <category>
  16. <pattern>*</pattern>
  17. <that>* 的 型 号 是 什 么</that>
  18. <template>
  19. <input index="4,1"></input> 《《----
  20. </template>
  21. </category>

测试结果:

  • you say>

  • hello

  • Alice>吃饭了吗?

  • you say>

  • 吃了

  • Alice>我也吃了

  • you say>

  • 你好

  • Alice>计算机的型号是什么

  • you say>

  • 345

  • Alice>hello

Input标签中的index貌似当第一个参数是几,就返回倒数第几个用户的说的话,而第二个参数好像只能是1,其他的就会出现数组越界的错误。不知道为什么?

上面描述的标签部分还有问题,需要改进。

另外我想说的是,在写xml语料库的时候,一定要写一点,马上重启程序测试一次,看新加的预料是否工作正常,否则你写了一堆的预料后在去测试如果出错的话,就很难跟踪到错误的地方了。

四、项目结构总览

五、将数据库集成到聊天机器人中

5.1 为什么需要使用数据库

Xml文件是AIML所支持的预料载体,而且凭借AIML提供的各种丰富的标签,作者可以设计并编写出很人性化的语料库。显然,通过这种方式写语料库的特点是灵活性很好,能很容易写出“唠嗑”类型的聊天内容。然而,当时对这个项目的定位是客服机器人,也就是说,语料库还应该包含具有业务针对的预料,这部分预料将随着业务的不同而不同。于是我想把这部分预料存储在数据库中形成动态的语料库(我把xml文件中的预料称为静态预料,也就是说这部分预料完善之后就不去频繁的修改),这样做的好处有一下几点:

  • 客服不用去学习怎样写xml预料,降低门槛

  • 可以避免xml中预料越来越凌乱,到最后难以管理

  • 以后可以针对数据库在开发一个语料库管理系统,方便客服管理有业务针对的这部分预料

5.2 数据库的表是怎样设计的

字段解释:

  • Id:预料的编号,自动生成(identify)

  • Createtime:该条数据创建时间,该条数据产生时自动生成(触发器)

  • Lastmodifytime:最近更新时间,该条数据的可填写字段被更新时自动修改(触发器)

  • Question:具体问题,自己填写

  • Replay:具体回答,自己填写

  • Label:标签字段(词语之间用空格隔开),里面填写的词语是要能体现question字段主题的,可以理解为一种补充说明,例:question:书是什么?那么书应该是被讨论的主题,lebel就可以填写和书意义相近的词,比如课本,教科书,教材,book,有了这个字段可以从某种程度上增强匹配效果

  • Copyfield:拷贝字段,这个字段会在你填写完(或者更新)question,replay,label这3个字段后自动生成(触发器),其内容为上述3个字段的合体,期间用空格隔开。这个字段是要被索引的重点字段

在以上的描述中,也许你会对某些字段存在的必要性产生疑惑,没有关系,在下面的叙述中也许能解决你的问题。

5.3 数据库里面的预料怎样使用到机器人当中

这就是基于lucene的处理。当程序启动的时候,程序会在载入xml语料库后,lucene就开始对数据库进行全量索引(这其实也是一种载入语料库行为),并在项目的根目录下产生相应的索引文件index以及时间戳文件(timesTamp.txt:该文件记录了当前索引行为发生的时间。将在增量索引时用到)。索引文件会在后面响应用户输入的时候用到。

下面是索引操作的代码(在com.job包):

其中sql语句是这样的:

5.4 在机器人处于运行状态时修改了数据库的预料,怎样做到与客户端的同步

这里将用到时间戳的概念。首先当机器人程序运行的时候,里面的一个timmer任务也会同时运行,这个任务做的工作是定期(比如每隔10秒),进行一次增量索引—-lucene中的概念。增量索引所针对的数据等同与这样一条sql语句所返回的数据,该sql语句满足的逻辑是:查找出数据库中所有Lastmodifytime字段值大于timesTamp.txt中记录的时间。那么这样,每次客服对数据库做的预料修改,都会在隔一段时间后同步到客户端。

六、机器人怎样响应用户的输入

在没有引入数据库前,只要调用Aiml提供的聊天接口就能得到一个字符串返回了,但是现在加入了数据库,那么我的处理的思路是这样的:
我在xml语料库里面的*通配符所对应的templete做了标记,如下图:

注意到上面的红点了吗?我在这个回答最前面加上了“#”的标记。逻辑如下:

代码如下:

七、聊天机器人学习功能实现

其实之前在介绍Aiml标签的时候,有2个很重要的标签还没有介绍到,那就是\<system>和\<gossip>标签。

在我的xml语料库中有一个文件叫Study.xml,它的内容如下:

(ps:如果不懂这里面一些标签的功能,可以回顾之前的标签功能解释)

我们看看这样的测试结果是什么:

等重启聊天机器人程序的时候,问同样的问题:

这其中都做了哪些工作呢?下面解释:

system标签的工作原理我还不是很清楚,但是我们可以看一下AIML解析器对应的System.java里面的process方法干了什么:

这个函数我暂时解释不清楚,但我知道上面的语料库中system标签只是会被learn函数的返回值替代。

我重点介绍一下gossip标签的工作过程:

首先看AIML解析器中对应的gossip.java文件的process方法干了什么:

match参数其实已经封装了上述语料库中learn函数的返回值“你的主人帅吗:帅到爆棚”。而super.process(match)就是取出这个字符串。

然后我们在看print函数:

看起来像是在什么文件里面写入了什么内容,我们在看outputStream()函数:

上述代码中的path其实就是指的这个文件:

很显然,客户每一次对机器人“教学”的内容,都会被写入gossip.txt文件当中,而且在写操作时是append的(即不会把原来的覆盖掉)。
而当每次重启机器人程序的时侯,GossipLoad.java类都会去读gossip.txt的内容,并构造一个gossip.xml文件将其写到预料库中.
该类主要代码如下:

Load函数在加载xml语料库到内存之前调用。这是必须的,因为必须先通过load函数生成gossip.xml文件后,然后统一加载到内存中去。
代码如下:

Gossip.xml中的内容如下:

八、聊天机器人存在的不足

  • 数据库匹配做的不好,或者说匹配率低,而且还不是很准确

  • 两种语料库的结合显的有点牵强

  • xml语料库的设计还是比较欠缺的,首先预料不够丰富,而且靠人工编写预料不是一个好办法

上传的附件 cloud_download 基于alice的聊天机器人.zip ( 7.57mb, 19次下载 )
error_outline 下载需要12点积分
eject