一、实验目的
1. 巩固对专家系统概念、原理、组成结构和推理方法的知识;
2. 掌握构造专家系统的方法;
3. 掌握使用专家系统进行推理的方法。
二、实验步骤
问题重述
运用所学知识,设计并编程实现一个小型动物识别系统,能识别虎、金钱豹、斑马、长颈鹿、鸵鸟、企鹅、信天翁等七种动物的产生式系统。
1. 仿照教材《知识工程:人工智能如何学贯古今》4.2.3小节创建动物知识库,存储到一个文本文件。
2. 定义读取知识库函数。
建议:创建一个空列表作为综合数据库来存储规则,创建一个空集合来存储所有不重复推理条件,方便后续给用户提示。
3. 定义推理机+解释器函数。包括如下步骤:
① 推理机使用用户输入的事实,依次与综合数据库中的规则匹配,并记录匹配的数量。
② 若某规则的前提全被事实满足,则规则可以得到运用,记录匹配的规则和推理结果。
③ 规则的结论部分若为中间结果,则作为新的事实存储在综合数据库中。
④ 用更新过的事实再与其他规则的前提匹配,直到不再有可匹配的规则为止。
4. 定义人机界面函数。该函数的主要作用是展示用户可输入的事实,并让用户循环输入。
5. 加载综合数据库。调用定义的函数加载知识库文件中的数据。
6. 专家系统人机交互。参考规则表,分别输入动物特征,按回车键进行系统推理。
包括如下步骤:
①测试成功识别动物的情况,并记录结果。
② 测试不成功识别动物的情况,并记录结果。
③ 测试推理得到中间结论的情况,并记录结果。
代码实现
基于产生式规则的专家系统完整结构
基于产生式规则的专家系统是一种模拟人类专家的推理思维过程的人工智能计算机程序。以下是这种专家系统的完整结构:
1. 知识库(Knowledge Base)
知识库是专家系统中的核心部分,它存储了从专家那里得到的专门知识。这些知识是专家所积累经验的信息数据库,包括事实和经验两种数据。在知识库中,知识是以产生式规则的形式存储的,每一条规则都有一个前提条件和一个结论。例如,规则可以表达为:“如果下雨,那么带伞”。
2. 推理机(Inference Engine)
推理机是专家系统中的另一个重要部分,它是专家系统的核心组件。它的任务是根据知识库中的知识推导出一定的结论,并能够执行各种任务进行各种推理或搜索等功能。推理机通过反复匹配知识库中的规则,获得新的结论,以得到问题求解结果。
3. 综合数据库(Composite Database)
综合数据库包含求解问题的世界范围内的事实和断言。在推理过程中,如果规则的前提条件与数据库中的事实相匹配,问题就得到解决;否则把这条规则的前提作为新的子目标,对新的子目标寻找可以运用的规则,执行逆向序列的前提,直到最后运用的规则的前提可以与数据库中的事实相匹配,或者直到没有规则再可以应用时,系统便以对话形式请求用户回答并输入必需的事实。
4. 人机交互界面(User Interface)
人机交互界面是专家系统与用户之间的桥梁,它接收用户的输入,将用户的命令进行识别,并把产生的结果传递给用户。用户可以通过人机交互界面向知识库添加、删除、修改规则。
5. 规则编辑器(Rule Editor)
规则编辑器是用来增添新的规则的工具,用户可以通过规则编辑器来扩充知识库中的知识内容,从而提高专家系统的性能。
6. 知识获取模块(Knowledge Acquisition Module)
知识获取模块是用来获取新的知识的模块,它可以对知识库进行知识补充、编辑增删等操作。通过知识获取模块,专家系统可以不断地学习和进步。
总的来说,基于产生式规则的专家系统的完整结构包括知识库、推理机、综合数据库、人机交互界面、规则编辑器和知识获取模块等六个部分。这些部分相互配合,共同实现了专家系统的功能。
规则库:
r1: IF 该动物有毛发 THEN 该动物是哺乳动物
r2: IF 该动物能产乳 THEN 该动物是哺乳动物
r3: IF 该动物有羽毛 THEN 该动物是鸟
r4: IF 该动物会飞 AND 会下蛋 THEN 该动物是鸟
r5: IF 该动物吃肉 THEN 该动物是食肉动物
r6: IF 该动物有犬齿 AND 有爪 AND 眼盯前方 THEN 该动物是食肉动物
r7: IF 该动物是哺乳动物 AND 有蹄 THEN 该动物是有蹄类动物
r8: IF 该动物是哺乳动物 AND 是反刍动物 THEN 该动物是有蹄类动物
r9: IF 该动物是哺乳动物 AND 是食肉动物 AND 是黄褐色 AND 身上有暗斑点 THEN 该动物是金钱豹
r10:IF 该动物是哺乳动物 AND 是食肉动物 AND 是黄褐色 AND 身上有黑色条纹 THEN 该动物是虎
r11:IF 该动物是有蹄类动物 AND 有长脖子 AND 有长腿 AND 身上有暗斑点 THEN 该动物是长颈鹿
r12:IF 该动物有蹄类动物 AND 身上有黑色条纹 THEN 该动物是斑马
r13:IF 该动物是鸟 AND 有长脖子 AND 有长腿 AND 不会飞 AND 有黑白二色 THEN 该动物是鸵鸟
r14:IF 该动物是鸟 AND 会游泳 AND 不会飞 AND 有黑白二色 THEN 该动物是企鹅
r15:IF 该动物是鸟 AND 善飞 THEN 该动物是信天翁
有毛发,是哺乳动物
能产乳,是哺乳动物
有羽毛,是鸟
会飞,会下蛋,是鸟
吃肉,是肉食动物
有犬齿,有爪,眼盯前方,是食肉动物
是哺乳动物,有蹄,是蹄类动物
是哺乳动物,是咀嚼反刍动物,是蹄类动物
是哺乳动物,是食肉动物,是黄褐色,身上有暗斑点,金钱豹
是哺乳动物,是肉食动物,是黄褐色,身上有黑色条纹,虎
是蹄类动物,有长脖子,有长腿,身上有暗斑点,长颈鹿
是蹄类动物,身上有黑色条纹,斑马
是鸟,有长脖子,有长腿,不会飞,有黑白二色,鸵鸟
是鸟,会游泳,不会飞,有黑白二色,企鹅
是鸟,善飞,信天翁
要求给定初始条件,能识别出是哪种动物。
比如已知初始事实存放在综合数据库中:
有毛发 吃肉 是黄褐色 身上有黑色条纹
运行后得该动物是:虎
相关原理解释可能比较浅显,因为博主也在学习阶段,可以参考一些内容更饱满的博客,比如:
动物识别系统(无GUI版)
# ①规则库直接赋值(方法并不局限于文中两种)
txt_rule = '''有毛发,是哺乳动物
能产乳,是哺乳动物
有羽毛,是鸟
会飞,会下蛋,是鸟
吃肉,是肉食动物
有犬齿,有爪,眼盯前方,是食肉动物
是哺乳动物,有蹄,是蹄类动物
是哺乳动物,是咀嚼反刍动物,是蹄类动物
是哺乳动物,是食肉动物,是黄褐色,身上有暗斑点,金钱豹
是哺乳动物,是肉食动物,是黄褐色,身上有黑色条纹,虎
是蹄类动物,有长脖子,有长腿,身上有暗斑点,长颈鹿
是蹄类动物,身上有黑色条纹,斑马
是鸟,有长脖子,有长腿,不会飞,有黑白二色,鸵鸟
是鸟,会游泳,不会飞,有黑白二色,企鹅
是鸟,善飞,信天翁'''
# # ②导入外部规则库
# with open('rule.txt', 'r', encoding='utf-8') as file:
# txt_rule = file.read()
# 特征值字典
character_dict = {'1': '有毛发', '2': '能产乳', '3': '有羽毛', '4': '会飞', '5': '会下蛋',
'6': '吃肉', '7': '有犬齿', '8': '有爪', '9': '眼盯前方', '10': '有蹄',
'11': '是咀嚼反刍动物', '12': '是黄褐色', '13': '身上有暗斑点',
'14': '身上有黑色条纹', '15': '有长脖子', '16': '有长腿',
'17': '不会飞', '18': '会游泳', '19': '有黑白二色',
'20': '善飞', '21': '是哺乳动物', '22': '是鸟',
'23': '是食肉动物', '24': '是蹄类动物', }
# 结果值字典
result_dict = {'25': '信天翁', '26': '鸵鸟', '27': '斑马', '28': '长颈鹿',
'29': '虎', '30': '金钱豹', '31': '企鹅'}
# 数据库对应的过程,合并两个字典
database = {**character_dict, **result_dict}
# 规则库数据转换为了列表
def get_data_list():
# 用于储存中间过程
data_process_list = []
# 用于存储过程对应的结果
data_result_list = []
# 将规则库数据预处理
data_str = txt_rule.split('\n')
for data in data_str:
data = data.split(',')
data_process_list.append(data[:-1])
data_result_list.append(data[-1].replace('\n', ''))
return data_process_list, data_result_list
# 特征值字典转为提示词
def character_dict_trans():
# 使用enumerate()函数获取字典的键值对及其索引
indexed_data = list(enumerate(character_dict.items()))
rsp_str = ''
# 使用for循环遍历这些键值对,每5个元素输出为1行
for i in range(0, len(indexed_data), 5):
line = ''
for j in range(5):
if i + j < len(indexed_data):
line += str(indexed_data[i + j][1][0] + ':' + indexed_data[i + j][1][1]) + ' '
# print(line)
rsp_str += line + '\n'
return rsp_str
# 通过传入的列表寻找结果
def find_data(process_data_list, dict_output):
# 依次进行循环查找并对过程排序
for index, data_process in enumerate(data_process_list):
# 用于判断此过程是否成立
num = 0
for data in process_data_list:
if data in data_process:
num += 1
# 过程成立则数值相同,可以进入下一步
if num == len(data_process):
# 此过程中结果是否为最终结果,不是将此过程结果加入到过程中
if data_result_list[index] not in result_dict.values():
# 弹出过程和此过程结果,因为此过程已经进行过,此结果存入需要查找的过程中
result = data_result_list.pop(index)
process = data_process_list.pop(index)
# 判断结果是否已经存在过程中,存在则重新寻找,不存在则加入过程,并将其存入最终结果
if result not in process_data_list:
dict_output[','.join(process)] = result
end_result = find_data(process_data_list + [result], dict_output)
if end_result == 1:
return 1
else:
return 0
# 存在则直接寻找
else:
end_result = find_data(process_data_list, dict_output)
if end_result == 1:
return 1
else:
return 0
# 找到最终结果,取出结果后返回
else:
process = data_process_list.pop(index)
dict_output[','.join(process)] = data_result_list[index]
return 1
# 快速排序算法
def quick_sort(arr):
if len(arr) <= 1:
return arr
pivot = arr[len(arr) // 2]
left = [x for x in arr if x < pivot]
middle = [x for x in arr if x == pivot]
right = [x for x in arr if x > pivot]
return quick_sort(left) + middle + quick_sort(right)
if __name__ == '__main__':
# 规则库数据转换为了列表
data_process_list, data_result_list = get_data_list()
print_str_start = '''输入对应条件前面的数字编号:
*************************************************************
'''
print_str_end = '''*************************************************************
*********************当输入数字0时!程序结束******************
'''
# 特征值字典转为提示词
character_str = character_dict_trans()
character_all_str = print_str_start + character_str + print_str_end
print(character_all_str)
# 存储用于查询的数据
list_data = []
# 循环进行输入,直到碰见0后退出
while 1:
input_num = input("请输入数字编号:")
# 当输入数字0时!程序结束
if input_num == '0':
break
# 输入数字编号,不在查询数据的列表中
if input_num not in list_data:
# 则加入查询列表
list_data.append(input_num)
# 将查询数字编号从小到大排序
sorted_list_data = quick_sort([int(i) for i in list_data])
# 打印查询条件
list_data_str = [character_dict[str(i)] for i in sorted_list_data]
print('查询条件为:' + ' '.join(list_data_str) + '\n')
# 用于存储输出结果
dict_output = {}
# 进行递归查找,直到找到最终结果,返回1则找到最终结果
end_result = find_data(list_data_str, dict_output)
# 查找成功时
if end_result == 1:
print('查询成功,推理过程如下:')
# 将结果进行打印
for data in dict_output.keys():
print(f"{data}->{dict_output[data]}")
# 得到最终结果即输出所识别动物
if dict_output[data] in result_dict.values():
print(f'所识别的动物为:{dict_output[data]}')
else: # 查找失败时
print('条件不足,无匹配规则,查询失败.')
本代码来源:
人工智能-产生式系统实验(动物识别) - 知乎 (zhihu.com)https://zhuanlan.zhihu.com/p/669264732
动物识别系统(有GUI版)(自写,代码比较简陋,不足之处多多见谅)
import tkinter as tk
from tkinter import messagebox
# ①规则库直接赋值
txt_rule = '''有毛发,是哺乳动物
能产乳,是哺乳动物
有羽毛,是鸟
会飞,会下蛋,是鸟
吃肉,是肉食动物
有犬齿,有爪,眼盯前方,是食肉动物
是哺乳动物,有蹄,是蹄类动物
是哺乳动物,是咀嚼反刍动物,是蹄类动物
是哺乳动物,是食肉动物,是黄褐色,身上有暗斑点,金钱豹
是哺乳动物,是肉食动物,是黄褐色,身上有黑色条纹,虎
是蹄类动物,有长脖子,有长腿,身上有暗斑点,长颈鹿
是蹄类动物,身上有黑色条纹,斑马
是鸟,有长脖子,有长腿,不会飞,有黑白二色,鸵鸟
是鸟,会游泳,不会飞,有黑白二色,企鹅
是鸟,善飞,信天翁'''
# 特征值字典
character_dict = {'1': '有毛发', '2': '能产乳', '3': '有羽毛', '4': '会飞', '5': '会下蛋',
'6': '吃肉', '7': '有犬齿', '8': '有爪', '9': '眼盯前方', '10': '有蹄',
'11': '是咀嚼反刍动物', '12': '是黄褐色', '13': '身上有暗斑点',
'14': '身上有黑色条纹', '15': '有长脖子', '16': '有长腿',
'17': '不会飞', '18': '会游泳', '19': '有黑白二色',
'20': '善飞', '21': '是哺乳动物', '22': '是鸟',
'23': '是食肉动物', '24': '是蹄类动物', }
# 结果值字典
result_dict = {'25': '信天翁', '26': '鸵鸟', '27': '斑马', '28': '长颈鹿',
'29': '虎', '30': '金钱豹', '31': '企鹅'}
# 数据库对应的过程,合并两个字典
database = {**character_dict, **result_dict}
def quick_sort(arr):
if len(arr) <= 1:
return arr
pivot = arr[len(arr) // 2]
left = [x for x in arr if x < pivot]
middle = [x for x in arr if x == pivot]
right = [x for x in arr if x > pivot]
return quick_sort(left) + middle + quick_sort(right)
# # 全局变量
# data_process_list = []
# data_result_list = []
#
# # 规则库数据转换为了列表
# def get_data_list():
# global data_process_list, data_result_list
# # 将规则库数据预处理
# data_str = txt_rule.split('\n')
# for data in data_str:
# data = data.split(',')
# data_process_list.append(data[:-1])
# data_result_list.append(data[-1].replace('\n', ''))
# return data_process_list, data_result_list
# 规则库数据转换为了列表
def get_data_list(txt_rule):
# 初始化列表
data_process_list = []
data_result_list = []
# 将规则库数据预处理
data_str = txt_rule.split('\n')
for data in data_str:
data = data.split(',')
data_process_list.append(data[:-1])
data_result_list.append(data[-1].replace('\n', ''))
return data_process_list, data_result_list
# 调用函数获取列表
data_process_list, data_result_list = get_data_list(txt_rule)
# 特征值字典转为提示词
def character_dict_trans():
# 使用enumerate()函数获取字典的键值对及其索引
indexed_data = list(enumerate(character_dict.items()))
rsp_str = ''
# 使用for循环遍历这些键值对,每5个元素输出为1行
for i in range(0, len(indexed_data), 5):
line = ''
for j in range(5):
if i + j < len(indexed_data):
line += str(indexed_data[i + j][1][0] + ':' + indexed_data[i + j][1][1]) + ' '
# print(line)
rsp_str += line + '\n'
return rsp_str
# 通过传入的列表寻找结果
# 通过传入的列表寻找结果
def find_data(process_data_list, dict_output, data_process_list, data_result_list):
# 创建数据处理和结果列表的副本,以避免修改原始数据
local_data_process_list = list(data_process_list)
local_data_result_list = list(data_result_list)
# 依次进行循环查找并对过程排序
for index, data_process in enumerate(local_data_process_list):
# 用于判断此过程是否成立
num = 0
for data in process_data_list:
if data in data_process:
num += 1
# 过程成立则数值相同,可以进入下一步
if num == len(data_process):
# 此过程中结果是否为最终结果,不是将此过程结果加入到过程中
if local_data_result_list[index] not in result_dict.values():
# 弹出过程和此过程结果,因为此过程已经进行过,此结果存入需要查找的过程中
result = local_data_result_list.pop(index)
process = local_data_process_list.pop(index)
# 判断结果是否已经存在过程中,存在则重新寻找,不存在则加入过程,并将其存入最终结果
if result not in process_data_list:
dict_output[','.join(process)] = result
end_result = find_data(process_data_list + [result], dict_output, local_data_process_list, local_data_result_list)
if end_result:
return True
# 存在则直接寻找
else:
end_result = find_data(process_data_list, dict_output, local_data_process_list, local_data_result_list)
if end_result:
return True
# 找到最终结果,取出结果后返回
else:
process = local_data_process_list.pop(index)
dict_output[','.join(process)] = local_data_result_list[index]
return True
return False
# GUI界面布局设置
def setup_gui(root):
global checkboxes_vars, conditions_text
# 创建一个滚动窗格来放置勾选框
checkboxes_frame = tk.Frame(root)
checkboxes_canvas = tk.Canvas(checkboxes_frame)
scrollbar = tk.Scrollbar(checkboxes_frame, orient="vertical", command=checkboxes_canvas.yview)
checkboxes_inner_frame = tk.Frame(checkboxes_canvas)
checkboxes_inner_frame.bind(
"<Configure>",
lambda e: checkboxes_canvas.configure(
scrollregion=checkboxes_canvas.bbox("all")
)
)
checkboxes_canvas.create_window((0, 0), window=checkboxes_inner_frame, anchor="nw")
checkboxes_canvas.configure(yscrollcommand=scrollbar.set)
# 在这个函数内用enumerate()生成带索引的checkbox
checkboxes_vars = []
for index, (key, description) in enumerate(character_dict.items()):
var = tk.StringVar(value="")
checkboxes_vars.append(var)
tk.Checkbutton(checkboxes_inner_frame, text=description, variable=var, onvalue=key, offvalue="",
command=update_conditions).pack(anchor='w')
# 装载滚动窗格
checkboxes_frame.pack(fill="both", expand=True)
checkboxes_canvas.pack(side="left", fill="both", expand=True)
scrollbar.pack(side="right", fill="y")
# 文本框显示选中的条件
conditions_text = tk.Text(root, height=5, width=50)
conditions_text.pack()
# 查询按钮
query_button = tk.Button(root, text="查询", command=do_query)
query_button.pack()
# 更新界面上的查询条件
def update_conditions():
selected_conditions = [var.get() for var in checkboxes_vars if var.get()]
sorted_list_data = quick_sort([int(i) for i in selected_conditions])
selected_conditions_names = [character_dict[str(i)] for i in sorted_list_data]
conditions_text.delete('1.0', tk.END)
conditions_text.insert(tk.END, "查询条件为:" + ' '.join(selected_conditions_names) + '\n')
# 查询按钮绑定的查询函数
# 查询按钮绑定的查询函数
def do_query():
selected_conditions_indexes = [var.get() for var in checkboxes_vars if var.get()]
selected_conditions = [database[var.get()] for var in checkboxes_vars if var.get()]
dict_output = {}
# 对用户选择的条件进行排序
sorted_list_data = quick_sort([int(i) for i in selected_conditions_indexes])
# 查询条件转换为文字
selected_conditions_names = [character_dict[str(i)] for i in sorted_list_data]
# 进行递归查找
end_result = find_data(selected_conditions_names, dict_output, data_process_list, data_result_list)
# 查找成功时
if end_result:
result_message = '查询成功,推理过程如下:\n'
for data in dict_output.keys():
result_message += f"{data}->{dict_output[data]}\n"
# 得到最终结果即输出所识别动物
if dict_output[data] in result_dict.values():
result_message += f'所识别的动物为:{dict_output[data]}\n'
messagebox.showinfo('查询结果', result_message)
else:
messagebox.showinfo('查询结果', '条件不足,无匹配规则,查询失败.')
# 主程序
if __name__ == '__main__':
# 创建主窗口
root = tk.Tk()
root.title("动物识别系统")
# 设置GUI界面
setup_gui(root)
# 启动事件循环
root.mainloop()
三、实验结果及分析
1.测试成功识别动物的情况(无GUI版)
2.测试不成功识别动物的情况(无GUI版)
3.测试成功识别动物的情况(有GUI版)
4.测试不成功识别动物的情况(有GUI版)
实验结果分析
这个代码实现了一个基于规则的动物识别系统,并使用Tkinter库为用户提供了图形用户界面(GUI)。以下是对这个代码的优点、缺点以及可以优化改进的地方的分析:
优点:
1. 用户界面:通过Tkinter库提供了一个直观的图形用户界面,用户可以通过勾选动物特征来进行查询,这比命令行界面更加友好和易于使用。
2. 模块化:代码被分成多个函数,每个函数负责一个特定的任务,如设置GUI、更新条件、执行查询等,这样的模块化设计使得代码更易于维护和扩展。
3. 错误处理:使用messagebox.showinfo来显示查询结果,包括成功和失败的情况,为用户提供明确的反馈。
4. 条件排序:在查询之前对用户选择的条件进行排序,确保了查询的一致性和准确性。
缺点:
1. 硬编码规则:规则库仍然是通过直接赋值的方式硬编码在代码中的,这使得系统的可扩展性较差。改进的方法是将规则存储在外部文件中,通过读取文件来动态加载规则。
2. 递归效率:使用递归函数进行规则匹配可能会导致效率问题,尤其是当规则库很大或者规则之间有复杂的依赖关系时。可以考虑使用更高效的算法,如正向链接或反向链接算法。
3. 全局变量:虽然代码中减少了一些全局变量的使用,但仍然存在一些全局变量(如data_process_list和data_result_list),这可能会使得代码的维护和调试变得更加困难。
4. 代码重复:在GUI设置和查询函数中存在一些重复的代码,比如对用户选择的条件进行排序的代码。可以通过创建一个公共函数来避免这种重复。
可以优化改进的地方:
1. 规则存储:将规则存储在文件中,并在程序启动时读取这些规则,以提供更好的灵活性和可扩展性。
2. 算法优化:考虑使用更高效的推理算法,比如正向链接或反向链接算法,以提高系统的性能。
3. 减少全局变量:尽可能避免使用全局变量,通过函数参数传递所需的数据。
4. 代码重构:对于重复的代码段,可以创建公共函数来减少重复,并提高代码的可维护性。
5. 错误处理和输入验证:添加对用户输入的验证,以确保输入的数据是有效的,并处理可能出现的错误情况。
6. 用户体验:进一步改进GUI的设计,比如提供清晰的指示和帮助信息,以增强用户体验。
总的来说,这个代码提供了一个基本的框架,并通过图形用户界面提高了用户体验,但在多个方面还有改进的空间,特别是在规则管理、算法效率和代码结构方面。