实验三 产生式系统推理
一、实验目的
本实验课程是计算机、智能、物联网等专业学生的一门专业课程,通过实验,帮助学生更好地掌握人工智能相关概念、技术、原理、应用等;通过实验提高学生编写实验报告、总结实验结果的能力;使学生对智能程序、智能算法等有比较深入的认识。
1.掌握人工智能中涉及的相关概念、算法;
2.熟悉人工智能中的知识表示方法;
3.掌握问题表示、求解及编程实现;
4.理解产生式系统的结构原理与实际应用;
5.掌握产生式规则表示及规则库组建的实现方法;
6.熟悉和掌握产生式系统的运行机制,掌握基于规则推理的基本方法。
二、基本要求
1.实验前,复习《人工智能》课程中的有关内容。
2.准备好实验数据。
3.编程要独立完成,程序应加适当的注释。
4.完成实验报告。
三、实验软件
使用C或C++(Visual studio或其它开发环境)(不限制语言使用)。
四、实验内容:
以动物识别系统为例,用选定的编程语言建造规则库和综合数据库,开发能进行正确的正向推理或反向推理的推理机。
正向推理过程
从已知事实出发,通过规则库求得结论,或称数据驱动方式。推理过程是:
规则集中的规则前件与事实库中的事实进行匹配,得匹配的规则集合。
从匹配规则集合中选择一条规则作为使用规则。
执行使用规则的后件,将该使用规则的后件送入事实库中。
重复这个过程直至达到目标。
1 动物分类规则集
(1)若某动物有奶,则它是哺乳动物。
(2)若某动物有毛发,则它是哺乳动物。
(3)若某动物有羽毛,则它是鸟。
(4)若某动物会飞且生蛋,则它是鸟。
(5)若某动物是哺乳动物且有爪且有犬齿且目盯前方,则它是食肉动物。
(6)若某动物是哺乳动物且吃肉,则它是食肉动物。
(7)若某动物是哺乳动物且有蹄,则它是有蹄动物。
(8)若某动物是有蹄动物且反刍食物,则它是偶蹄动物。
(9)若某动物是食肉动物且黄褐色且有黑色条纹,则它是老虎。
(10)若某动物是食肉动物且黄褐色且有黑色斑点,则它是金钱豹。
(11)若某动物是有蹄动物且长腿且长脖子且黄褐色且有暗斑点,则它是长颈鹿。
(12)若某动物是有蹄动物且白色且有黑色条纹,则它是斑马。
(13)若某动物是鸟且不会飞且长腿且长脖子且黑白色,则它是驼鸟。
(14)若某动物是鸟且不会飞且会游泳且黑白色,则它是企鹅。
(15)若某动物是鸟且善飞且不怕风浪,则它是海燕。
下面是该规则集所形成的(部分)推理网络:
图1 动物识别系统部分推理网络
1.2 问题描述
由上述动物识别规则组成规则库,推理机采用正向推理算法或反向推理算法,实现对动物的查询。
如给出初始事实:
F1:某动物有毛发
F2:吃肉
F3:黄褐色
F4:有黑色条纹
目标条件为:该动物是什么?
3 规则库扩充 (选做)
在上述规则集(Ⅰ)基础上增加以下规则集(Ⅱ):
(1)兔子:有毛发,有奶,善跳跃,唇裂;
(2)猫:有毛发,有奶,善捕鼠,脚有肉垫;
(3)犀牛:有毛发,有奶,鼻子上有角,褐色,皮糙肉后,皮糙肉厚,有蹄;
(4)熊猫:有毛发,有奶,黑眼圈,四肢短小;
(5)鹦鹉:鸟类,上嘴鹰钩,会模仿人说话;
(6)鸭子:鸟类,腿短,嘴扁平,善潜水游泳;
(7)鹰:鸟类,上嘴鹰钩,有爪,吃肉;
(8)鸭子:有羽毛,卵生,善游泳,嘴扁平,腿短;
(9)鹅:有羽毛,卵生,善潜水游泳,白色或黑色,颈长,嘴大,腿长,颈部有肉只凸起;
(10)鸦:有羽毛,卵生,黑色,嘴大;
(11)鹰:有羽毛,卵生,有爪,吃肉,上嘴鹰钩;
(12)鹦鹉:有羽毛,卵生,上嘴鹰钩,能模仿人说话;
(13)青蛙:卵生,生活在水中,生活在陆地,有皮肤呼吸,用肺呼吸,皮肤光滑,吃昆虫,会变色;
(14)蝾螈:卵生,生活在水中,生活在陆地,有皮肤呼吸,用肺呼吸,吃昆虫,皮肤粗糙,四肢扁,背部黑色;
(15)蟾蜍:卵生,生活在水中,生活在陆地,有皮肤呼吸,用肺呼吸,吃昆虫,皮肤粗糙;
(16)比目鱼:用鳃呼吸,身体有鳍,生活在海洋中,身体扁平,两眼在头部同侧;
(17)鲫鱼:用鳃呼吸,身体有鳍,生活在淡水中,身体扁平,头高尾部窄;
(18)蛇:生活在陆地,用肺呼吸,胎生,身体有鳞或甲,身体圆而细长,吃小动物;
(19)壁虎:生活在陆地,用肺呼吸,胎生,身体有鳞或甲,有四肢,尾巴细长易断,吃昆虫;
(20)乌龟:生活在陆地,用肺呼吸,胎生,身体有鳞或甲,身体圆而扁,有坚硬的壳;
(21)玳瑁:生活在陆地,用肺呼吸,胎生,身体有鳞或甲,壳为黄褐色,皮肤光滑,有黑斑;
(22)鳄鱼:生活在陆地,用肺呼吸,胎生,身体有鳞或甲,有四肢,善游泳,皮硬黑褐色。
要求在动物分类规则集(Ⅰ)的基础上添加上述22条知识,共构成29种动物的知识库系统,对原有动物分类系统进行扩充和修改。
五、实验程序组成
(1)使用的结构
Rule类,类中存放一个int类型的指针和重载了一个运算符 == ,用于后续与int类型的数组作比较。
(2)使用的各种函数
//用于中间状态操作的函数
Bool include(Rule rule, int *ch); //判断ch中含有rule全部元素
Void clean(Rule rule, int *ch); //删除ch中与rule相同的元素
bool zero(int *ch); //判断ch中是否全部元素都为0
Bool check(int *ch, int *p); //判断ch中是否还含有p中元素
//规则库函数
Int rule1(int *ch); //一级规则,可以直接由条件推理出
Int rule2(int *ch, int rule1); //二级规则,需要结合一级推理结果
Int rule3(int *ch, int rule2); //三级规则,需要结合二级推理结果
//推理结果处理函数
Int result(int *ch, int rule3); //根据rule3的结果进行分析
Void show(int a); //根据推理得出的序号输出相应文字
(3)部分实验代码:
//规则库
int rule1(int* ch) {
if (!zero(ch)) {
//有奶->哺乳动物
Rule rule1;
int t1=1;
rule1.rule = &t1;
if (include(rule1, ch)) {
clean(rule1, ch);
return 24;
}
//有毛发->哺乳动物
Rule rule2;
int t2=2;
rule2.rule = &t2;
if (include(rule2, ch)) {
clean(rule2, ch);
return 24;
}
//有羽毛->鸟
Rule rule3;
int t3 = 3;
rule3.rule = &t3;
if (include(rule3, ch)) {
clean(rule3, ch);
return 25;
}
//会飞,能生蛋->鸟
Rule rule4;
int t[2] = { 4,5 };
rule4.rule = &t[0];
if (include(rule4, ch)) {
clean(rule4, ch);
return 25;
}
}
else
return 0;
}
int rule2(int* ch, int rule1) {
if (!zero(ch)) {
switch (rule1) {
//哺乳动物
case 24: {
//有爪,有犬齿,目盯前方->食肉动物
Rule rule5;
int t[3] = { 6,7,8 };
rule5.rule = &t[0];
if (include(rule5, ch)) {
clean(rule5, ch);
return 26;
}
//吃肉->食肉动物
Rule rule6;
int t1 = 9;
rule6.rule = &t1;
if (include(rule6, ch)) {
clean(rule6, ch);
return 26;
}
//有蹄->有蹄动物
Rule rule7;
int t2 = 10;
rule7.rule = &t2;
if (include(rule7, ch)) {
clean(rule7, ch);
return 27;
}
return 24;
};
//鸟
case 25: {
//不会飞,长腿,长脖子,黑白色->鸵鸟
Rule rule8;
int t[4] = { 14,15,19,20 };
rule8.rule = &t[0];
if (include(rule8, ch)) {
clean(rule8, ch);
return 33;
}
//不会飞,会游泳,黑白色->企鹅
Rule rule9;
int t1[3] = { 19,20,21 };
rule9.rule = &t1[0];
if (include(rule9, ch)) {
clean(rule9, ch);
return 34;
}
//善飞,不怕风浪->海燕
Rule rule10;
int t2[2] = { 22,23 };
rule10.rule = &t2[2];
if (include(rule10, ch)) {
clean(rule10, ch);
return 35;
}
return 25;
};
}
}
else
return rule1;
}
int rule3(int* ch, int rule2) {
if (!zero(ch)) {
switch (rule2) {
//食肉动物
case 26: {
//黄褐色,黑色条纹->老虎
Rule rule11;
int t1[2] = { 12,17 };
rule11.rule = &t1[0];
if (include(rule11, ch)) {
clean(rule11, ch);
return 29;
}
//黄褐色,黑色斑点->金钱豹
Rule rule12;
int t2[2] = { 12,13 };
rule12.rule = &t2[0];
if (include(rule12, ch)) {
clean(rule12, ch);
return 30;
}
return 26;
};
//有蹄动物
case 27: {
//反刍->偶蹄动物
Rule rule13;
int t1 = 11;
rule13.rule = &t1;
if (include(rule13, ch)) {
clean(rule13, ch);
return 28;
}
//长腿,长脖子,黄褐色,暗斑点->长颈鹿
Rule rule15;
int t3[4] = { 12,14,15,16 };
rule15.rule = &t3[0];
if (include(rule15, ch)) {
clean(rule15, ch);
return 31;
}
//白色,黑色条纹->斑马
Rule rule14;
int t4[2] = { 17,18 };
rule14.rule = &t4[0];
if (include(rule14, ch)) {
clean(rule14, ch);
return 32;
}
return 27;
};
}
return rule2;
}
else
return rule2;
}
六、实验结果分析:
初始界面:
输入题设条件:
得出结果:
实验心得:
①实验中采用了对规则的分级保证了推理的中间过程的完善性,不过这也使得本程序中的可用规则全为最基本的规则,若是从中间过程开始的推理,如推理条件以哺乳动物为基本条件的推理,需要用于自行推理出哺乳动物的基本条件。②规则库与记录用户输入条件的数组以指针*ch的形式存放,原本在确定条件数量的时候想用ch[]!=NULL的,可是会出现越界的情况,后来观察错误发现越界后数值都十分大或小,故重新设置边界条件为-50~50用来计算条件数量,也算一种取巧。
完整代码:
#include<iostream>
using namespace std;
//单条规则形式
class Rule {
public:
int *rule;
bool operator ==(int *ch);
};
bool Rule::operator==(int *ch) {
int i = 0;
while (ch[i] > -50 && ch[i] < 50) {
if (rule[i] != ch[i])
return true;
i++;
}
return false;
}
//读取拥有函数
bool include(Rule rule,int *ch) {
int rulelong=0, same=0;
while (-50<rule.rule[rulelong] && rule.rule[rulelong]<50)
rulelong++;;
//检测ch有效长度内使用含有与rule数组相同数量的元素
for (int i = 0; ch[i]>-50 && ch[i]<50; i++) {
for (int m = 0; m < rulelong; m++) {
if (rule.rule[m] == ch[i])
same++;
}
}
if (same == rulelong)
return true;
else
return false;
}
//删除已使用过的条件:在ch中删除相应规则rule1
void clean(Rule rule,int* ch) {
int rulelong = 0, i = 0;
while (rule.rule[rulelong] > -50 && rule.rule[rulelong] <50)
rulelong++;
while (ch[i] > -50 && ch[i] < 50) {
int m = 0;
while (m < rulelong) {
if (rule.rule[m] == ch[i]) {
ch[i] = 0;
break;
}
m++;
}
i++;
}
}
//全0判断
bool zero(int* ch) {
int i = 0;
while (ch[i] > -50 && ch[i] < 50) {
if (ch[i] != 0)
return false;
i++;
}
return true;
}
//重复元素检查:ch中是否还含有p中的元素,true为可用重复,false为不可用重复
bool check(int* ch,int *p) {
int same = 0,chlong=0;
while (ch[chlong] >-50 && ch[chlong]<50)
chlong++;
//复制ch数组
int* chcopy=new int[chlong];
for (int i = 0; i < chlong; i++) {
chcopy[i] = ch[i];
}
//先将0项加入same值中
for (int i = 0; i < chlong; i++)
if (chcopy[i] == 0)
same++;
//做相同分析
for (int i = 0; i<chlong; i++)
for (int m = 0; -50<p[m] && p[m]<50; m++)
if (chcopy[i] == p[m] ) {
same++;
chcopy[i] = 0;
}
//比较same和ch长度
if (same == chlong)
return true;
else
return false;
}
//规则库
int rule1(int* ch) {
if (!zero(ch)) {
//有奶->哺乳动物
Rule rule1;
int t1=1;
rule1.rule = &t1;
if (include(rule1, ch)) {
clean(rule1, ch);
return 24;
}
//有毛发->哺乳动物
Rule rule2;
int t2=2;
rule2.rule = &t2;
if (include(rule2, ch)) {
clean(rule2, ch);
return 24;
}
//有羽毛->鸟
Rule rule3;
int t3 = 3;
rule3.rule = &t3;
if (include(rule3, ch)) {
clean(rule3, ch);
return 25;
}
//会飞,能生蛋->鸟
Rule rule4;
int t[2] = { 4,5 };
rule4.rule = &t[0];
if (include(rule4, ch)) {
clean(rule4, ch);
return 25;
}
}
else
return 0;
}
int rule2(int* ch, int rule1) {
if (!zero(ch)) {
switch (rule1) {
//哺乳动物
case 24: {
//有爪,有犬齿,目盯前方->食肉动物
Rule rule5;
int t[3] = { 6,7,8 };
rule5.rule = &t[0];
if (include(rule5, ch)) {
clean(rule5, ch);
return 26;
}
//吃肉->食肉动物
Rule rule6;
int t1 = 9;
rule6.rule = &t1;
if (include(rule6, ch)) {
clean(rule6, ch);
return 26;
}
//有蹄->有蹄动物
Rule rule7;
int t2 = 10;
rule7.rule = &t2;
if (include(rule7, ch)) {
clean(rule7, ch);
return 27;
}
return 24;
};
//鸟
case 25: {
//不会飞,长腿,长脖子,黑白色->鸵鸟
Rule rule8;
int t[4] = { 14,15,19,20 };
rule8.rule = &t[0];
if (include(rule8, ch)) {
clean(rule8, ch);
return 33;
}
//不会飞,会游泳,黑白色->企鹅
Rule rule9;
int t1[3] = { 19,20,21 };
rule9.rule = &t1[0];
if (include(rule9, ch)) {
clean(rule9, ch);
return 34;
}
//善飞,不怕风浪->海燕
Rule rule10;
int t2[2] = { 22,23 };
rule10.rule = &t2[2];
if (include(rule10, ch)) {
clean(rule10, ch);
return 35;
}
return 25;
};
}
}
else
return rule1;
}
int rule3(int* ch, int rule2) {
if (!zero(ch)) {
switch (rule2) {
//食肉动物
case 26: {
//黄褐色,黑色条纹->老虎
Rule rule11;
int t1[2] = { 12,17 };
rule11.rule = &t1[0];
if (include(rule11, ch)) {
clean(rule11, ch);
return 29;
}
//黄褐色,黑色斑点->金钱豹
Rule rule12;
int t2[2] = { 12,13 };
rule12.rule = &t2[0];
if (include(rule12, ch)) {
clean(rule12, ch);
return 30;
}
return 26;
};
//有蹄动物
case 27: {
//反刍->偶蹄动物
Rule rule13;
int t1 = 11;
rule13.rule = &t1;
if (include(rule13, ch)) {
clean(rule13, ch);
return 28;
}
//长腿,长脖子,黄褐色,暗斑点->长颈鹿
Rule rule15;
int t3[4] = { 12,14,15,16 };
rule15.rule = &t3[0];
if (include(rule15, ch)) {
clean(rule15, ch);
return 31;
}
//白色,黑色条纹->斑马
Rule rule14;
int t4[2] = { 17,18 };
rule14.rule = &t4[0];
if (include(rule14, ch)) {
clean(rule14, ch);
return 32;
}
return 27;
};
}
return rule2;
}
else
return rule2;
}
//推理结果处理
int result(int *ch,int rule3) {
//三重推理后还有剩余元素
if (!zero(ch)) {
//剩余有效重复元素正常返回,无效重复元素返回0
switch (rule3) {
case 24: {
int t1[2] = { 1,2 };
int* p1 = &t1[0];
if (check(ch, p1)) //未通过check判定
return 24;
else
return 0; //查无此动物
}
case 25: {
int t2[3] = { 3,4,5 };
int* p2 = &t2[0];
if (check(ch, p2))
return 25;
else
return 0;
}
case 26: {
int t3[6] = { 1,2,6,7,8,9 };
int* p3 = &t3[0];
if (check(ch, p3))
return 26;
else
return 0;
}
case 27: {
int t4[] = { 1,2,10 };
int* p4 = &t4[0];
if (check(ch, p4))
return 27;
else
return 0;
}
case 28: {
int t5[] = { 1,2,10,11 };
int* p5 = &t5[0];
if (check(ch, p5))
return 28;
else
return 0;
}
case 29: {
int t6[] = {1,2,6,7,8,9,12,17};
int* p6 = &t6[0];
if (check(ch, p6))
return 29;
else
return 0;
}
case 30: {
int t7[] = {1,2,6,7,8,9,12,13};
int* p7 = &t7[0];
if (check(ch, p7))
return 30;
else
return 0;
}
case 31: {
int t8[] = {1,2,10,12,14,15,16};
int* p8 = &t8[0];
if (check(ch, p8))
return 31;
else
return 0;
}
case 32: {
int t9[] = {1,2,10,17,18};
int* p9 = &t9[0];
if (check(ch, p9))
return 32;
else
return 0;
}
case 33: {
int t10[] = {3,4,5,14,15,19,20};
int* p10 = &t10[0];
if (check(ch, p10))
return 33;
else
return 0;
}
case 34: {
int t11[] = {3,4,5,19,20,21};
int* p11 = &t11[0];
if (check(ch, p11))
return 34;
else
return 0;
}
case 35: {
int t12[] = { 3,4,5,22,23 };
int* p12 = &t12[0];
if (check(ch, p12))
return 35;
else
return 0;
}
}
}
//三种推理后无剩余元素
else
return rule3;
}
//根据返回值进行输出
void show(int a){
switch (a) {
case 0: {
cout << "根据给出条件,数据库中不存在此类动物,请重新输入" << endl;
break;
}
case 24: {
cout << "根据给出条件,所查询出结果为:哺乳动物" << endl;
break;
}
case 25: {
cout << "根据给出条件,所查询出结果为:鸟" << endl;
break;
}
case 26: {
cout << "根据给出条件,所查询出结果为:食肉动物" << endl;
break;
}
case 27: {
cout << "根据给出条件,所查询出结果为:有蹄动物" << endl;
break;
}
case 28: {
cout << "根据给出条件,所查询出结果为:偶蹄动物" << endl;
break;
}
case 29: {
cout << "根据给出条件,所查询出结果为:老虎" << endl;
break;
}
case 30: {
cout << "根据给出条件,所查询出结果为:金钱豹" << endl;
break;
}
case 31: {
cout << "根据给出条件,所查询出结果为:长颈鹿" << endl;
break;
}
case 32: {
cout << "根据给出条件,所查询出结果为:斑马" << endl;
break;
}
case 33: {
cout << "根据给出条件,所查询出结果为:鸵鸟" << endl;
break;
}
case 34: {
cout << "根据给出条件,所查询出结果为:企鹅" << endl;
break;
}
case 35: {
cout << "根据给出条件,所查询出结果为:海燕" << endl;
break;
}
}
}
void main() {
aceu:
cout << "********产生式系统推理——动物识别系统*********" << endl;
//输出序号以及所有条件图
cout << "1.有奶 2.有毛发 3.有羽毛 4.会飞" << endl;
cout << "5.能生蛋 6.有爪 7.有犬齿 8.目盯前方" << endl;
cout << "9.吃肉 10.有蹄 11.反刍 12.黄褐色" << endl;
cout << "13.黑色斑点 14.长腿 15.长脖子 16.暗斑点" << endl;
cout << "17.黑色条纹 18.白色 19.不会飞 20.黑白色" << endl;
cout << "21.会游泳 22.善飞 23.不怕风浪" << endl<<endl;
cout << "请输入查询动物的特征序号,以‘0’结束" << endl;
//测试创建指针数组
int test[20] = { 0 };
int q = 1;
for (int i = 0; q != 0; i++) {
cin >> test[i];
q = test[i];
}
//读数,并写入ch数组
int num = 0;
while (test[num] != 0)
num++;
int* ch = new int[num];
for (int i = 0; i < num; i++)
ch[i] = test[i];
show(result(ch, rule3(ch, rule2(ch, rule1(ch)))));
int re;
cout <<endl<<endl<<"******************************************************"<<endl<< "请输入一个数以便继续,输入0则退出" << endl;
cin >> re;
if (re != 0) {
system("cls");
goto aceu;
}
}