实现动物专家系统

人工智能初学者课程的一个例子。

在此示例中,我们将实现一个简单的基于知识的系统,以根据某些物理特征来确定动物。该系统可以用以下AND-OR树来表示(这是整个树的一部分,我们可以轻松添加更多规则):

我们自己的带有后向推理的专家系统 shell

让我们尝试定义一种简单的基于产生式规则的知识表示语言。我们将使用 Python 类作为关键字来定义规则。基本上有 3 种类型的类:

  • Ask 代表需要向用户询问的问题。它包含一组可能的答案。
  • If 代表一条规则,它只是一个语法糖,用来存储规则的内容
  • AND / OR 是表示树的 AND/OR 分支的类。他们只是将参数列表存储在里面。为了简化代码,所有功能都定义在父类 Content 中
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
29
30
31
32
33
34
35
36
37
38
39
# 类 Ask: 用于提示用户输入并提供选择。
class Ask():
# 构造函数 __init__:
# choices: 一个选择列表,默认值为 ['y', 'n'](表示是和否)。
def __init__(self,choices=['y','n']):
# self.choices: 将 choices 参数存储在实例变量中。
self.choices = choices

# 方法 ask: 提示用户进行选择并返回结果。
def ask(self):
# 如果选择列表中的任何一个选择长度大于1
if max([len(x) for x in self.choices])>1:
# 则逐行打印每个选择
for i,x in enumerate(self.choices):
print("{0}. {1}".format(i,x),flush=True)
# 并提示用户输入索引。
x = int(input())
return self.choices[x]
else:
# 如果所有选择都是单个字符,则将其用斜杠连接并打印,提示用户输入。
print("/".join(self.choices),flush=True)
return input()

# 类 Content: 一个基类,用于存储逻辑表达式。
class Content():
# x: 存储逻辑表达式的内容。
def __init__(self,x):
# self.x: 将 x 参数存储在实例变量中。
self.x=x

# 类 If、AND、OR: 继承自 Content,用于表示逻辑结构,没有额外的功能。
class If(Content):
pass

class AND(Content):
pass

class OR(Content):
pass

在我们的系统中,工作记忆将包含作为属性值对的事实列表。知识库可以定义为一本大字典,它将动作(新的事实应插入工作记忆中)映射到条件,以 AND-OR 表达式表示。此外,一些事实可以被 Ask 编辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 规则定义
# 规则字典 rules: 定义了各种动物的特征和分类规则。
# 每个键代表一个规则或字段。
# 每个值是一个表达式,可能是 Ask、If、AND 或 OR 的实例,或是一个字符串列表。
rules = {
'default': Ask(['y','n']),
'color' : Ask(['red-brown','black and white','other']),
'pattern' : Ask(['dark stripes','dark spots']),
'mammal': If(OR(['hair','gives milk'])),
'carnivor': If(OR([AND(['sharp teeth','claws','forward-looking eyes']),'eats meat'])),
'ungulate': If(['mammal',OR(['has hooves','chews cud'])]),
'bird': If(OR(['feathers',AND(['flies','lies eggs'])])),
'animal:monkey' : If(['mammal','carnivor','color:red-brown','pattern:dark spots']),
'animal:tiger' : If(['mammal','carnivor','color:red-brown','pattern:dark stripes']),
'animal:giraffe' : If(['ungulate','long neck','long legs','pattern:dark spots']),
'animal:zebra' : If(['ungulate','pattern:dark stripes']),
'animal:ostrich' : If(['bird','long nech','color:black and white','cannot fly']),
'animal:pinguin' : If(['bird','swims','color:black and white','cannot fly']),
'animal:albatross' : If(['bird','flies well'])
}

为了执行向后推理,我们将定义 Knowledgebase 类。它将包含:

  • 工作记忆 - 将属性映射到值的字典
  • 知识库 rules 采用上面定义的格式

两种主要方法是:

  • get 获取属性的值,必要时进行推理。例如, get(‘color’) 将获取颜色槽的值(它会询问是否需要,并将该值存储在工作内存中以供以后使用)。如果我们询问 get(‘color:blue’) ,它会询问颜色,然后根据颜色返回 y / n 值。
  • eval 执行实际的推理,即遍历 AND/OR 树、评估子目标等。
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# 知识库管理
# 类 KnowledgeBase: 管理逻辑规则和缓存评估结果。
class KnowledgeBase():
# 构造函数 __init__
# rules: 包含字段名称和逻辑表达式的字典。
def __init__(self,rules):
# self.rules: 将 rules 参数存储在实例变量中。
self.rules = rules
# self.memory: 初始化一个空字典,用于缓存评估结果。
self.memory = {}

# 方法 get: 根据字段名称检索值并进行评估。
# 参数 name: 要检索和评估的字段名称。
def get(self,name):
if ':' in name:
# 如果字段名称包含 :,将其分成键和值。
k,v = name.split(':')
# 递归调用 get 方法获取基础字段的值,并将其与子字段值进行比较。
vv = self.get(k)
return 'y' if v==vv else 'n'

# 如果字段值已在 memory 中缓存,则直接返回缓存值。
if name in self.memory.keys():
return self.memory[name]
# 在规则中搜索字段或任何子字段。
for fld in self.rules.keys():
# 如果找到匹配项,评估相应的表达式。
if fld==name or fld.startswith(name+":"):
# 根据评估结果更新缓存并返回结果。
# print(" + proving {}".format(fld))
value = 'y' if fld==name else fld.split(':')[1]
res = self.eval(self.rules[fld],field=name)
if res!='y' and res!='n' and value=='y':
self.memory[name] = res
return res
if res=='y':
self.memory[name] = value
return value
# field is not found, using default
# 如果在规则中未找到字段,评估 default 规则。
# 将结果缓存并返回。
res = self.eval(self.rules['default'],field=name)
self.memory[name]=res
return res

# 方法 eval: 评估一个表达式。
# 参数 expr: 要评估的表达式。
# 参数 field: 当前评估的字段(用于提示)。
def eval(self,expr,field=None):
# print(" + eval {}".format(expr))
# 根据表达式类型进行相应的评估:
# Ask 实例:提示用户进行输入。
if isinstance(expr,Ask):
print(field)
return expr.ask()
# If 实例:递归评估其内容。
elif isinstance(expr,If):
return self.eval(expr.x)
# AND 实例或列表:逐个评估其内容,若有任何评估结果为 'n',返回 'n',否则返回 'y'。
elif isinstance(expr,AND) or isinstance(expr,list):
expr = expr.x if isinstance(expr,AND) else expr
for x in expr:
if self.eval(x)=='n':
return 'n'
return 'y'
# OR 实例:逐个评估其内容,若有任何评估结果为 'y',返回 'y',否则返回 'n'。
elif isinstance(expr,OR):
for x in expr.x:
if self.eval(x)=='y':
return 'y'
return 'n'
# 字符串:调用 get 方法进行评估。
elif isinstance(expr,str):
return self.get(expr)
# 未知类型:打印错误信息。
else:
print("Unknown expr: {}".format(expr))

现在让我们定义我们的动物知识库并进行咨询。请注意,此通话将询问您问题。您可以通过键入 y / n 来回答是非问题,或者通过指定数字 (0..N) 来回答具有较长多项选择答案的问题。

1
2
3
4
# 创建一个 KnowledgeBase 实例,并传入规则。
kb = KnowledgeBase(rules)
# 调用 get 方法以评估并获取字段 'animal' 的值。
kb.get('animal')

使用 PyKnow 进行前向推理

在下一个示例中,我们将尝试使用知识表示库之一 PyKnow 来实现前向推理。 PyKnow 是一个用于在 Python 中创建前向推理系统的库,其设计类似于经典的旧系统 CLIPS。

我们也可以自己实现前向链接,不会出现很多问题,但简单的实现通常效率不高。为了更有效地匹配规则,使用了特殊算法 Rete。

1
2
3
4
5
6
7
8
# 导入 sys 模块: 这个模块提供了一些接口来访问和操作 Python 解释器。这里主要是为了获取当前 Python 解释器的位置。
import sys
# 执行一条 shell 命令: ! 用于在 Jupyter Notebook 或 IPython 中执行 shell 命令。
!{sys.executable} -m pip install git+https://github.com/buguroo/pyknow/
# {sys.executable}: 使用当前 Python 解释器的路径。这可以确保在正确的 Python 环境中执行命令,尤其在使用虚拟环境时非常有用。
# -m pip install: 使用 -m 选项通过指定的 Python 解释器运行 pip 模块,并安装指定的软件包。
# git+https://github.com/buguroo/pyknow/: 直接从 GitHub 仓库安装 pyknow 包。git+https:// 表示 pip 将从给定的 Git 存储库安装包,而不是从 PyPI(Python Package Index)安装包。
# 总结一下,这行代码的作用是在当前 Python 解释器的环境中,从 GitHub 仓库安装 pyknow 包。
1
2
3
# 从 pyknow 模块导入所有内容: 这行代码将 pyknow 模块中的所有类、函数和变量导入到当前命名空间中。
from pyknow import *
#import pyknow

我们将我们的系统定义为 KnowledgeEngine 子类的类。每个规则都由带有 @Rule 注释的单独函数定义,该注释指定何时触发规则。在规则内部,我们可以使用 declare 函数添加新事实,添加这些事实将导致前向推理引擎调用更多规则。

下面这个代码片断是一个使用了expertsystem库的专家系统的例子,专门用于对动物进行分类。下面是对每一行代码及参数的详细解释:
定义类 Animals:
class Animals(KnowledgeEngine): 这定义了一个名为Animals的类,它继承自KnowledgeEngine。KnowledgeEngine是构建专家系统的基础,在这里用来处理和推导知识或事实(Facts)。
定义规则:
专家系统中的每一个规则允许系统根据提供的事实(Facts)推导出新的结论。
规则定义的通用元素:
@Rule: 是一个装饰器,用于定义一个规则。每个规则都会在满足特定条件时触发某些动作。
OR, AND: 是逻辑运算符,用于组合条件。AND表示所有条件都必须满足,OR表示只要满足其中一个条件即可。
Fact: 用于声明事实。事实是专家系统用来推理的基础,通过匹配规则定义中的条件来进行逻辑推导。
定义的规则:
carnivor: 表示如果一个动物具有锋利的牙齿、爪子、前视眼的特征,并吃肉,那么它会被归类为食肉动物(carnivor)。
mammal: 如果一个动物有毛发或能够哺乳,它会被认为是哺乳动物(mammal)。
hooves: 如果一个哺乳动物拥有蹄子或反刍,它会被认定为有蹄类(ungulate)。
bird: 如果一个动物拥有羽毛,或者能够飞行并下蛋,它会被认为是鸟类。
monkey, tiger, giraffe, zebra, ostrich, pinguin, albatross: 这些规则根据动物的特定特征,如颜色、图案、身体特征等,将动物分类为特定种类。

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# 定义 Animals 类: 继承自 KnowledgeEngine 类,用于实现动物分类的专家系统
class Animals(KnowledgeEngine):
# 定义规则 carnivor: 如果一个动物具有 "锋利的牙齿"、"爪子" 和 "前视眼" 的特征,或者吃肉,则声明其为食肉动物 (carnivor)。
# @Rule: 装饰器,定义一个规则。
# OR, AND: 逻辑运算符,用于组合条件。
# Fact: 声明事实。
@Rule(OR(
AND(Fact('sharp teeth'),Fact('claws'),Fact('forward looking eyes')),
Fact('eats meat')))
def carnivor(self):
self.declare(Fact('carnivor'))
# 定义规则 mammal: 如果一个动物有毛发或会哺乳,则声明其为哺乳动物 (mammal)。
@Rule(OR(Fact('hair'),Fact('gives milk')))
def mammal(self):
self.declare(Fact('mammal'))

@Rule(Fact('mammal'),
OR(Fact('has hooves'),Fact('chews cud')))
def hooves(self):
self.declare('ungulate')

@Rule(OR(Fact('feathers'),AND(Fact('flies'),Fact('lays eggs'))))
def bird(self):
self.declare('bird')

@Rule(Fact('mammal'),Fact('carnivor'),
Fact(color='red-brown'),
Fact(pattern='dark spots'))
def monkey(self):
self.declare(Fact(animal='monkey'))

@Rule(Fact('mammal'),Fact('carnivor'),
Fact(color='red-brown'),
Fact(pattern='dark stripes'))
def tiger(self):
self.declare(Fact(animal='tiger'))

@Rule(Fact('ungulate'),
Fact('long neck'),
Fact('long legs'),
Fact(pattern='dark spots'))
def giraffe(self):
self.declare(Fact(animal='giraffe'))

@Rule(Fact('ungulate'),
Fact(pattern='dark stripes'))
def zebra(self):
self.declare(Fact(animal='zebra'))

@Rule(Fact('bird'),
Fact('long neck'),
Fact('cannot fly'),
Fact(color='black and white'))
def straus(self):
self.declare(Fact(animal='ostrich'))

@Rule(Fact('bird'),
Fact('swims'),
Fact('cannot fly'),
Fact(color='black and white'))
def pinguin(self):
self.declare(Fact(animal='pinguin'))

@Rule(Fact('bird'),
Fact('flies well'))
def albatros(self):
self.declare(Fact(animal='albatross'))

@Rule(Fact(animal=MATCH.a))
def print_result(self,a):
print('Animal is {}'.format(a))
# def factz(self,l): 这个方法接受一个包含事实的列表,然后逐一声明这些事实。
# 通过调用self.declare()方法来添加事实,这是推理过程的基础。
def factz(self,l):
for x in l:
self.declare(x)

每个@Rule装饰的方法都会在其条件被满足时执行。例如,如果一个动物有“锋利的牙齿”,“爪子”,“前视眼”并且“吃肉”,那么carnivor规则会触发,并通过执行self.declare(Fact(‘carnivor’))将这个动物分类为食肉动物(carnivor)。

一旦我们定义了知识库,我们就会用一些初始事实填充我们的工作记忆,然后调用 run() 方法来执行推理。您可以看到结果是新的推断事实被添加到工作记忆中,包括有关动物的最终事实(如果我们正确设置所有初始事实)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# ex1 = Animals(): 创建Animals类的一个实例。
ex1 = Animals()
# ex1.reset(): 重置ex1实例,以便开始一个新的推理过程。
ex1.reset()
# ex1.factz(...): 调用factz方法,并传入一个包含多个Fact的列表,这些事实定义了要进行分类的动物的特征。
ex1.factz([
Fact(color='red-brown'),
Fact(pattern='dark stripes'),
Fact('sharp teeth'),
Fact('claws'),
Fact('forward looking eyes'),
Fact('gives milk')])
# ex1.run(): 运行专家系统,开始基于提供的事实和定义的规则进行推理。
ex1.run()
# ex1.facts: 这个属性包含了系统推导出的所有事实,可以用来检查推理结果。
ex1.facts