人工智能标记语言(Artificial Intelligence Markup Language,AIML)是由 Richard Wallace 和世界各地的自由软件社区在 1995 年至 2002 年发明的。它是一种基于 XML 的语言,可用于创建智能机器人或虚拟助。
AIML中的主要元素是模式(pattern)、模板(template)、类别(category)和变量(variable)。
- 模式(pattern)是用户可能会输入的问题或陈述
- 模板(template)是机器人对该问题或陈述的响应
- 类别(category)是模式和模板的组合
- 变量(variable)用于存储和处理用户输入和机器人输出中的信息
基于 AIML 可以快速开发基于规则的机器人,并可以轻松地扩展和修改现有机器人的功能。它的缺点是需要手工编写大量的匹配规则,当然构建工作也可以基于一些开源的 AIML 语料库,这样会节省不少的时间。安装命令如下:
pip install aiml
这篇文章简单介绍下,AIML 的 Hello World 程序,以及其常用的一些标签。
https://www.udemy.com/course/artificial-intelligence-markup-language/
1. Hello World
一个基于 AIML 的聊天机器人构建的基本过程有:
- 首先,创建对话语料。这个对话语料其实就是针对不同领域、不同意图、或者不同侧重点的对话规则,每一类的规则创建一个 .aiml 文件
- 然后,使用 aiml 库加载学习我们创建的一系列规则
- 最后,输入问题匹配规则输出响应
创建一个语料文件,暂时命名为 test.aiml,内容如下:
<aiml version="1.0.1" encoding="UTF-8"> <category> <pattern>你好</pattern> <template>你也好啊,朋友。</template> </category> <category> <pattern>你叫什么名字啊?</pattern> <template>我叫刘德华。</template> </category> <category> <pattern>你今年几岁了?</pattern> <template>我刚诞生 1000 年!</template> </category> </aiml>
接下来创建 main.py 文件,内容如下:
import aiml def test(): # 初始化 AIML 对象 robot = aiml.Kernel() # 学习 test.aiml 语料文件 robot.learn('test.aiml') # 输入问题,返回响应 response = robot.respond('你好') print(response) response = robot.respond('你叫什么名字啊?') print(response) response = robot.respond('你今年几岁了?') print(response) if __name__ == '__main__': test()
程序输出结果:
Loading test.aiml...done (0.17 seconds) 你也好啊,朋友。 我叫刘德华。 我刚诞生 1000 年!
这里需要注意的是,上面例子中输入的问题要和 test.aiml 中定义的 pattern 完全匹配,否则将不会得到对应的相应。这给我们构造对话规则带来很大的麻烦,所以,接下来需要学习一些 AIML 中的特殊标签,来帮助我们增加规则构造的灵活性。
Doc:http://www.aiml.foundation/
2. <star/> 标记
我们在预先设定问题时,可以使用 * 号这样的通配符来进行模糊匹配,并可以在回答中通过使用 <star/> 来引用模糊匹配到的内容。下面举个使用例子:
假设问题 pattern 设置为:我 喜 欢 *
那么,当输入问题为:我 喜 欢 篮 球,上面的 pattern 也能够匹配到,并且我们在 template 中使用 <star/> 就使用匹配到的 “篮球”
我们编写 test.aiml 文件内容如下:
<aiml version="1.0.1" encoding="UTF-8"> <category> <pattern>我 喜 欢 *</pattern> <template>我知道了,你喜欢<star/></template> </category> <category> <pattern>* 是 *</pattern> <template>原来<star index="1"/>是<star index="2"/>啊!</template> </category> </aiml>
上述内容中,<star index=”1″> 表示引用第一个通配符 * 号匹配到的内容,如果 index=”2″ 的话,那么就匹配第二个 * 号匹配到的内容。main.py 文件内容如下:
import aiml def test(): robot = aiml.Kernel() robot.learn('test.aiml') response = robot.respond('我 喜 欢 苹 果') print(response) # xml 中问题以空格隔开,并且输入问题也用空格隔开 response = robot.respond('A I M L 是 人 工 智 能 标 记 语 言') print(response) if __name__ == '__main__': test()
输出内容:
Loading test.aiml...done (0.18 seconds) 我知道了,你喜欢苹 果 原来A I M L是人 工 智 能 标 记 语 言啊!
这里你可能注意到,在 test.aiml 和 main.py 中我设置的 pattern 和输入的问题,都已经用空格隔开了。
3. <srai> 标记
假设:我们定义了一组 pattern 和 template,对于 pattern 还有一不同的表达方式,我们都可以通过 <srai> 标签将这些表达转到先前定义的 pattern 上进行匹配。
<aiml version="2.0" encoding="UTF-8"> <category> <pattern>再 见</pattern> <template>再见,我的朋友</template> </category> <category> <pattern>拜 拜</pattern> <template><srai>再 见</srai></template> </category> <category> <pattern>拜 拜 *</pattern> <template><srai>再 见</srai></template> </category> <category> <pattern>A I M L</pattern> <template>AIML 是人工智能标记语言啊</template> </category> <category> <pattern>* A I M L</pattern> <template><srai>A I M L</srai></template> </category> <category> <pattern>A I M L *</pattern> <template><srai>A I M L</srai></template> </category> <category> <pattern>* A I M L *</pattern> <template><srai>A I M L</srai></template> </category> </aiml>
main.py 内容如下:
import aiml def test(): robot = aiml.Kernel() robot.learn('test.aiml') response = robot.respond('再 见') print(response) response = robot.respond('拜 拜') print(response) response = robot.respond('拜 拜 ,我 的 朋 友') print(response) response = robot.respond('A I M L') print(response) response = robot.respond('我 以 前 听 过 A I M L') print(response) response = robot.respond('A I M L 是 一 个 什 么 东 西') print(response) response = robot.respond('我 以 前 听 过 A I M L , 但 是 不 理 解') print(response) if __name__ == '__main__': test()
程序输出结果:
再见,我的朋友 再见,我的朋友 再见,我的朋友 AIML 是人工智能标记语言啊 AIML 是人工智能标记语言啊 AIML 是人工智能标记语言啊 AIML 是人工智能标记语言啊
我们将 “拜 拜” 和 “拜 拜 ,我 的 朋 友” 都转到 “再见” 这个 pattern 进行匹配。第二个例子是将输入中包含了指定的 “A I M L” 的所有 pattern 都转到 “A I M L” pattern 对应的 template 作为输出。
4. <set> 和 <get> 标记
<set> 和 <get> 标记用于定义一些变量,这些变量可以保存聊天过程中的一些重要信息,以便于在下文中引用该信息。例如:聊天上文中提到了用户的名字,那么可以用 <set> 标记存储下来,下文中需要使用该名字时,就用 <get> 获得改名字。test.aiml 文件内容如下:
<aiml version="1.0.1" encoding="UTF-8"> <category> <pattern>你 叫 什 么 名 字?</pattern> <template>我叫 Robot, 你叫什么名字?</template> </category> <category> <pattern>我 叫 *</pattern> <template>很高兴认识你: <set name="name"><star/></set></template> </category> <category> <pattern>你 还 记 得 我 叫 什 么 吗 ?</pattern> <template>我知道你叫<get name="name"></get></template> </category> </aiml>
main.py 文件内容如下:
import aiml def test(): robot = aiml.Kernel() robot.learn('test.aiml') response = robot.respond('你 叫 什 么 名 字 ?') print(response) response = robot.respond('我 叫 刘 德 华') print(response) response = robot.respond('你 还 记 得 我 叫 什 么 吗 ?') print(response) if __name__ == '__main__': test()
程序输出结果:
Loading test.aiml...done (0.16 seconds) 我叫 Robot, 你叫什么名字? 很高兴认识你: 刘 德 华 我知道你叫刘 德 华
5. <that> 标记
<that> 标记可以用来限定上文。这么说其实很不明确。我们举个例子:
Human:你读过《实战俄语》吗 ? Robot:我读过,你觉得这本书怎么样? Human:不错 Robot:我也觉得这本书不错!
上面的对话中,当 Robot 说过 “我读过,你觉得这本书怎么样?” ,此时 Human 再回答 “不错”,那么 Robot 才会输出 “我也觉得这本书不错!”
也就是说,Robot 最后一句话的输出必须之前输出过 “我读过,你觉得这本书怎么样?” 这句话。这个 that 其实就是将上文的某个输出作为当前 pattern 匹配的限制条件。再举个例子:
Human:不 好 Robot:什么不好?
假设没有上文限制条件,那么当 Human 直接输入 “不好”,则 Robot 直接匹配不带限制条件的 “不好” 对应的输出。
再比如,如下的例子:
Human:你读过《实战俄语》吗 ? Robot:我读过,你觉得这本书怎么样? Human:你吃饭了吗? Robot:我不会饿,不需要吃饭哦! Human:不好 Robot:什么不好?
这个例子中,虽然上文中存在上文 “我读过,你觉得这本书怎么样?”,但是中间被其他问题打断了,那么 AIML 会匹配不带限制条件的 “不好” 对应的输出。
我们看下完整的例子,test.aiml 文件内容如下:
<aiml version="1.0.1" encoding="UTF-8"> <category> <pattern>你 读 过 《 实 战 俄 语 》 吗 ?</pattern> <template>我读过,你觉得这本书怎么样?</template> </category> <category> <pattern>你 吃 饭 了 吗?</pattern> <template>我不会饿,不需要吃饭哦!</template> </category> <category> <pattern>不 错</pattern> <that>我读过,你觉得这本书怎么样?</that> <template>我也觉得这本书不错!</template> </category> <category> <pattern>不 错</pattern> <template>我也觉得这本书不错!</template> </category> <category> <pattern>不 好</pattern> <template>什么不好?</template> </category> <category> <pattern>不 好</pattern> <that>我读过,你觉得这本书怎么样?</that> <template>我也觉得马马虎虎吧!</template> </category> </aiml>
main.py 文件内容如下:
import aiml def test01(): robot = aiml.Kernel() robot.learn('test.aiml') response = robot.respond('你 读 过 《 实 战 俄 语 》 吗 ?') print(response) response = robot.respond('不 错') print(response) def test02(): robot = aiml.Kernel() robot.learn('test.aiml') response = robot.respond('你 读 过 《 实 战 俄 语 》 吗 ?') print(response) response = robot.respond('不 好') print(response) def test03(): robot = aiml.Kernel() robot.learn('test.aiml') response = robot.respond('不 好') print(response) def test04(): robot = aiml.Kernel() robot.learn('test.aiml') response = robot.respond('你 读 过 《 实 战 俄 语 》 吗 ?') print(response) response = robot.respond('你 吃 饭 了 吗?') print(response) response = robot.respond('不 好') print(response) if __name__ == '__main__': test01() test02() test03() test04()
程序输出结果:
Loading test.aiml...done (0.16 seconds) 我读过,你觉得这本书怎么样? 我也觉得这本书不错! Loading test.aiml...done (0.00 seconds) 我读过,你觉得这本书怎么样? 我也觉得马马虎虎吧! Loading test.aiml...done (0.00 seconds) 什么不好? Loading test.aiml...done (0.00 seconds) 我读过,你觉得这本书怎么样? 我不会饿,不需要吃饭哦! 什么不好?
6. <think> 标记
我们在第 4 部分介绍 <set> 用法时,知道该标记可以定义一些变量,以便用于到下文中的对话。但是,细心的你可能发现了,<set> 在设置变量的时候,会将变量的值输出。如果我们就像在后台偷偷设置,而不进行显示的话,可以将其放在 <think> 标记内部。test.aiml 内容如下:
<aiml version="1.0.1" encoding="UTF-8"> <category> <pattern>你 叫 什 么 名 字?</pattern> <template>我叫 Robot, 你叫什么名字?</template> </category> <category> <pattern>我 叫 *</pattern> <!-- 将set操作放到think标记内,就不会进行输出操作 --> <template>很高兴认识你! <think><set name="name"><star/></set></think></template> </category> <category> <pattern>你 还 记 得 我 叫 什 么 吗 ?</pattern> <template>我知道你叫<get name="name"></get></template> </category> </aiml>
main.py 内容如下:
import aiml def test(): robot = aiml.Kernel() robot.learn('test.aiml') response = robot.respond('你 叫 什 么 名 字 ?') print(response) response = robot.respond('我 叫 刘 德 华') print(response) response = robot.respond('你 还 记 得 我 叫 什 么 吗 ?') print(response) if __name__ == '__main__': test()
程序输出结果:
Loading test.aiml...done (0.05 seconds) 我叫 Robot, 你叫什么名字? 很高兴认识你! 我知道你叫刘 德 华
可以看到 <set> 标记在记录变量时候,并不会将变量的值输出。
7. <condition> 标记
从名字上来看,该比较可以设置条件。也就是说,template 内部可以设置多条不同的输出,具体输出什么值要看能够满足哪个条件。test.aiml 文件内容如下:
<aiml version="1.0.1" encoding="UTF-8"> <category> <pattern>*</pattern> <template>我不理解你的问题!</template> </category> <category> <pattern>名 字 是 * </pattern> <template> <!-- 先设置条件变量 --> <think><set name="username"><star /></set></think> <!-- 根据username选择不同分支输出 --> <condition name="username"> <li value="张 三">张三名字真简洁!</li> <li value="李 四">李四,我是赵六啊!</li> <li>我不认识你</li> </condition> </template> </category> </aiml>
main.py 内容如下:
import aiml def test(): robot = aiml.Kernel() robot.learn('test.aiml') response = robot.respond('名 字 是 张 三') print(response) response = robot.respond('名 字 是 李 四') print(response) response = robot.respond('名 字 是 王 五') print(response) response = robot.respond('你 知 道 我 是 谁 吗 ?') print(response) if __name__ == '__main__': test()
程序输出结果:
Loading test.aiml...done (0.16 seconds) 张三名字真简洁! 李四,我是赵六啊! 我不认识你 我不理解你的问题!
8. <topic> 标记
通过 <topic> 标记,我们可以将多个 pattern 按照主题进行分组。需要注意的是,当确定一个主题时,只能匹配该主题内部定义的 pattern,否则会匹配失败。test.aiml 文件内容如下:
<aiml version="1.0.1" encoding="UTF-8"> <category> <pattern>我 们 聊 下 关 于 足 球 的 话 题 吧 ?</pattern> <!-- 输出内容,并设定接下来要聊天的主题 --> <template>好哇,你想了解哪些关于足球的问题呢?<think><set name="topic">足球</set></think></template> </category> <category> <pattern>我 们 聊 下 关 于 篮 球 的 话 题 吧 ?</pattern> <!-- 输出内容,并设定接下来要聊天的主题 --> <template>好哇,你想了解哪些关于篮球的问题呢?<think><set name="topic">篮球</set></think></template> </category> <!-- 如果 topic=足球的话,那么问篮球主题的问题会找不到匹配的问题 --> <topic name="足球"> <category> <pattern>足 球 比 赛 一 般 多 少 人 参 加 ?</pattern> <template>这个我不太清楚啊!</template> </category> <category> <pattern>中 国 男 足 球 技 怎 么 样 ?</pattern> <template>这么说吧,那些世界列强从没赢过中国足球。</template> </category> </topic> <!-- 如果 topic=篮球的话,那么问足球主题的问题会找不到匹配的问题 --> <topic name="篮球"> <category> <pattern>中 国 篮 球 明 星 都 有 谁 ?</pattern> <template>我就知道姚明,别的我就不清楚了!</template> </category> <category> <pattern>中 国 男 篮 能 打 得 过 中 国 男 足 吗 ?</pattern> <template>够呛,因为人多的不要脸啊!</template> </category> </topic> </aiml>
main.py 文件内容如下:
import aiml def test(): robot = aiml.Kernel() robot.learn('test.aiml') # 切换到足球话题 response = robot.respond('我 们 聊 下 关 于 足 球 的 话 题 吧 ?') print(response) response = robot.respond('中 国 男 足 球 技 怎 么 样 ?') print(response) # 切换到篮球话题 response = robot.respond('我 们 聊 下 关 于 篮 球 的 话 题 吧 ?') print(response) # 错误: 由于单签主题是篮球,询问足球的问题,此时会提示匹配不到问题答案 response = robot.respond('足 球 比 赛 一 般 多 少 人 参 加 ?') print(response) response = robot.respond('中 国 男 篮 能 打 得 过 中 国 男 足 吗 ?') print(response) if __name__ == '__main__': test()
程序输出结果:
Loading test.aiml...done (0.18 seconds) 好哇,你想了解哪些关于足球的问题呢? 这么说吧,那些世界列强从没赢过中国足球。 好哇,你想了解哪些关于篮球的问题呢? 够呛,因为人多的不要脸啊! WARNING: No match found for input: 足 球 比 赛 一 般 多 少 人 参 加 ?
9. <random> 标记
有时候我们想从多个回答中随机选择一个,就可以使用 <random> 标记来完成。test.aiml 文件内容如下:
<aiml version="1.0.1" encoding="UTF-8"> <category> <pattern>你 好</pattern> <template> <!-- 随机选择一个回复 --> <random> <li>HI 朋友!</li> <li>你也好!</li> <li>Hello Friend!</li> </random> </template> </category> </aiml>
main.py 文件内容如下:
import aiml def test(): robot = aiml.Kernel() robot.learn('test.aiml') response = robot.respond('你 好') print(response) response = robot.respond('你 好') print(response) response = robot.respond('你 好') print(response) if __name__ == '__main__': test()
程序执行结果:
Loading test.aiml...done (0.16 seconds) Hello Friend! 你也好! 你也好!
10. <system> 标记
<system> 标记中可以编写一些命令,例如:ls,或者使用 python 命令执行一个 python 文件,并将文件的 print 结果作为响应。test.aiml 文件内容如下:
<aiml version="1.0.1" encoding="UTF-8"> <category> <pattern>生 成 一 个随 机 列 表</pattern> <template> <!--执行一个python文件--> <system>python my.py</system> </template> </category> </aiml>
my.py 内容如下:
import random def test(): number = [random.randint(0, 10) for _ in range(10)] return str(number) if __name__ == '__main__': print(test())
main.py 内容如下:
import aiml def test(): robot = aiml.Kernel() robot.learn('test.aiml') response = robot.respond('生 成 一 个随 机 列 表') print(response) if __name__ == '__main__': test()
程序输出内容如下:
Loading test.aiml...done (0.16 seconds) [5, 0, 4, 6, 8, 3, 8, 6, 0, 10]