基于脚本的方法¶
基于脚本的方法也可以叫作基于模板或者规则的方法。脚本系统包括一个脚本引擎及其支持的脚本语言,以及由人工编写一系列模板(或者规则)。接受用户输入后,脚本引擎拿用户输入与模板相匹配,匹配到有效的模板后,根据模板指定的方式产生回答。
通常脚本引擎支持在模板中对输入输出的处理:
根据输入匹配到一个模板
根据模板的规则从输入中提取内容
对输入及提取的内容作替换、添加等操作
映射到一个回答
替换回答中的占位符
返回处理好的回答
对于脚本引擎,从简单的Eliza到AIML等很多功能强大的开源项目,我们有众多选择。
在了解这些功能强大的脚本引擎之前,让我们尝试实现一些最简单的基于脚本的对话引擎。
实现简单的脚本系统¶
价值一亿的人工智能程序
基于正则匹配的脚本引擎
带模板的脚本引擎
价值几亿的人工智能核心代码¶
有一段号称价值几亿的人工智能核心代码曾流传甚广,它实现了一个简单的脚本引擎,不需要编写人工规则,当然也没定义编写规则需要的脚本语言。
class JokeChatAgent(object):
def reply(self, message):
return message.replace('吗', '').replace('?', '!')
如果我们在前面的对话系统中使用这个脚本引擎并运行,会得到这样的结果:
User: 你好
AI: 你好
User: 吃饭了吗?
AI: 吃饭了!
User: 开心吗
AI: 开心
User: Q
基于正则匹配的脚本引擎¶
基于正则表达式的脚本引擎是最常见的形式,在很多对话产品中都会使用正则表达式匹配用户输入,如果匹配成功,则输出对应回答。
下面是一个简单的正则表达式脚本引擎的实现。
import re
import random
class RegexChatAgent(object):
def __init__(self, patterns):
self.patterns = [(re.compile(regex), answers) for regex, answers in patterns]
def reply(self, message):
for regex, answers in self.patterns:
if regex.match(message):
return random.choice(answers)
return ''
使用时:
class ConversationSystem(object):
patterns = [
('你好[吗啊呀]?', ['你好', '嗨']),
('你是谁', ['我是一个AI', '我是个机器人']),
('你(多大|几岁)[吗啊呀]?', ['我还年轻', '这是个秘密']),
('你在(哪里|哪儿|什么地方)', ['我在云端', '我在你的身边']),
('.*', ['你说什么', '不好意思,没明白你的话'])
]
def __init__(self):
self.chat_engine = RegexChatAgent(ConversationSystem.patterns)
def reply(self, uid, message):
return self.chat_engine.reply(message)
def interact_cli(self):
while True:
query = input('User:')
if query == 'Q':
break
print('AI:', self.reply('UserA', query))
conv_system = ConversationSystem()
conv_system.interact_cli()
运行上面代码,我们可以得到:
User: 你好
AI: 嗨
User: 你是谁
AI: 我是个机器人
User: 你多大了
AI: 我还年轻
User: 你在哪里呢
AI: 我在你的身边
User: Q
带模板的脚本引擎¶
前面我们说过,一个脚本引擎通常上会支持从输入中提取内容,映射并处理回答模板后返回作为输出。
现在我们在基于正则的脚本引擎上支持这些功能。
import re
import random
class TemplateChatAgent(object):
DEFAULT_MARK = '*'
def __init__(self, patterns):
self.patterns = [(re.compile(regex), templates) for regex, templates in patterns]
def _process_match(self, match, templates):
if not match:
return ''
candidate = ''
named_groups = match.groupdict()
if named_groups:
for k, v in named_groups.items():
if v in templates:
candidates = templates[v]
candidate = random.choice(candidates)
break
if not candidate and TemplateChatEngine.DEFAULT_MARK in templates:
candidate = random.choice(templates[TemplateChatEngine.DEFAULT_MARK])
if named_groups:
for k, v in named_groups.items():
candidate = candidate.replace('{%s}' % k, v)
return candidate
def reply(self, message):
for regex, templates in self.patterns:
match = regex.match(message)
if match:
return self._process_match(match, templates)
return ''
class ConversationSystem(object):
patterns = [
(r'你好[吗啊呀]?', {'*': ['你好', '嗨']}),
(r'你是男是女', {'*': ['你觉得呢', '你呢']}),
(r'你是谁', {'*': ['我是一个AI', '我是个机器人']}),
(r'你(多大|几岁)[吗啊呀]?', {'*': ['我还年轻', '这是个秘密']}),
(r'你在(哪里|哪儿|什么地方)', {'*': ['我在云端,你在哪儿', '我在你的身边,你在哪里呢']}),
(r'我是(?P<Gender>[男女])(的|生|人)', {'男': ['帅哥好'], '女': ['美女好']}),
(r'我也?在(?P<Place>.+)', {'北京': ['北京现在很冷吧'], '*': ['你在{Place}?']}),
(r'.*', {'*': ['你说什么', '不好意思,没明白你的话']})
]
def __init__(self):
self.chat_engine = TemplateChatAgent(ConversationSystem.patterns)
def reply(self, uid, message):
return self.chat_engine.reply(message)
def interact_cli(self):
while True:
query = input('User:')
if query == 'Q':
break
print('AI:', self.reply('UserA', query))
conv_system = ConversationSystem()
conv_system.interact_cli()
User: 你好
AI: 你好
User: 你是谁
AI: 我是个机器人
User: 你是男是女
AI: 你觉得呢
User: 男的
AI: 不好意思,没明白你的话
User: 你在哪里
AI: 我在云端,你在哪儿
User: 我也在云端
AI: 你在云端?
User: Q
如果我们把 patterns 中的规则写到一个模板文件中,并从模板文件中加载规则,这样一个脚本引擎就完成了。 它可以加载模板,模板的脚本语言就是由正则表达式组成的。
比如,我们可以使用如下的格式保存规则到文件中。
- 你好[吗啊呀]?
- *
- 你好
- 嗨
- 你是男是女
- *
- 你觉得呢
- 你呢
- 我也?在(?P<Place>.+)
- 北京
- 北京现在很冷吧
- *
- 你在{Place}?
使用RiveScript作为脚本引擎¶
使用脚本引擎构建对话系统已有很长的历史,几种常见的脚本引擎:
Eliza
AIML
RiveScript
ChatScript
SuperScript
这些脚本引擎功能大同小异,可以根据项目情况进行选择。如果你喜欢雍容华贵的XML,可能会选择AIML;如果你是Javascript的爱好者,可以会选择SupserScript。
下面我们使用 RiveScript 来构建一个对话系统。
RiveScript的脚本定义,以及编写规则在官方文档里写得很详细,这里不再赘述。
我们编写两个脚本文件,一个保存聊天机器人基本信息。
! version = 2.0
> begin
+ request // This trigger is tested first.
- {ok} // An {ok} in the response means it's okay to get a real reply
< begin
// Bot Variables
! var name = 小小
! var fullname = 小小
! var age = 5
! var birthday = 10月12日
! var sex = 男的
! var city = 北京
! var color = 蓝色
// Set arrays
! array malenoun = 男 男生 男的 男人 帅哥
! array femalenoun = 女 女生 女的 女人 美女
myself.rive 这个脚本文件提供规则来回答与机器人自身相关的一些问题。
// Tell the user stuff about ourself.
+ 小小
- 在呢
+ 小小 *
- 在呀 {@<star>}
+ (你是谁|你叫什么名字|你的名字是什么)
- 我是 <bot name>。
- 你可以叫我 <bot name>。
+ 你多大了
- 我 <bot age> 岁了。
- <bot age> 岁呀。
+ 你是(@malenoun)还?是(@femalenoun)
- 我是 <bot sex>。
+ 你在(哪里|哪儿|什么地方)
- 我在 <bot city>。
+ 你喜欢什么颜色
- 当然是 <bot color>。
然后把这两个文件保存在一个 brain 文件夹下。
class RiveChatAgent(object):
def __init__(self, brain_path):
self.rive = RiveScript(utf8=True)
self.unicode_punctuation = re.compile(r'[。,!?;:.,!?;:]')
self.rive.load_directory(brain_path)
self.rive.sort_replies()
def reply(self, message):
response = self.rive.reply("", message)
if response == '[ERR: No Reply Matched]':
response = ''
return response
最后在对话系统中使用这个基于 RiveScript 的对话引擎。
class ConversationSystem(object):
def __init__(self):
self.chat_engine = RiveChatAgent('brain')
def reply(self, uid, message):
return self.chat_engine.reply(message)
def interact_cli(self):
while True:
query = input('User:')
if query == 'Q':
break
print('AI:', self.reply('UserA', query))
conv_system = ConversationSystem()
conv_system.interact_cli()
User:你是谁
AI: 我是 小小。
User:你多大了
AI: 我 5 岁了。
User:你在哪里
AI: 我在 北京。
User:你是男是女
AI: 我是 男的。
到目前为止,我们通过使用脚本引擎,实现了一个简单的对话系统。由于脚本引擎只能根据人工编写的脚本来进行对话,所以不能涵盖很多话题。接下来,我们来看一下如何利用检索的方式来产生回答进行对话。