一、研究背景和目的
1.研究背景
(1)产生式概念
产生式是表征程序性知识的最小单位,是指人脑中贮存的一系列如果—那么形式表示的规则。一个产生式是一个由条件和动作组成的指令,即所谓的条件—活动规则。“产生式”这一术语是在1943年由美国数学家E.L.Post首先提出的,它根据串替代规则提出了一种称为Post机的计算模型,模型中的每一条规则称为产生式。产生式通常用于表示具有因果关系的知识,其基本形式为:P→Q 或者 IF P THEN Q。
(2)产生式系统概念
产生式系统简称产生式。它是指形如->或IF THEN或其等价形式的一条规则,其中箭头左边称为产生式的左部或前件;箭头右边称为产生式的右部或后件。如果前件和后见分别代表需要注视的一组条件及其成立时需要采取的行动,那么称为条件-行动型产生式;如果前件和后见分别代表前提及其相应的结论,那么称为前提-结论型产生式。
(3)产生式表示法的特点
1)产生式表示法的主要优点
a.自然性
产生式表示法用“如果……,则……”的形式表示知识,这是人们常用的一种表达因果关系的知识表示形式,既直观、自然,又便于进行推理。正是由于这一原因,才使得产生式表示法成为人工智能中最重要且应用最多的一种知识表示方法。
b.模块性
产生式是规则库中最基本的知识单元,它们同推理机构相对独立,而且每条规则都具有相同的形式。这就便于对其进行模块化处理,为知识的增、删、改带来了方便,为规则库的建立和扩展提供了可管理性。
c.有效性
产生式表示法既可表示确定性知识,又可表示不确定性知识;既有利于表示启发式知识,又可方便地表示过程性知识,目前已建造成功的专家系统大部分是用产生式来表达其过程性知识的。
d.清晰性
产生式有固定的格式。每一条产生式规则都由前提与结论(操作)这两部分组成,而且每一部分所含的知识量都比较少。这既便于对规则进行设计,又易于对规则库中知识的一致性及完整性进行检测。
2)产生式表示法的主要缺点
a.效率不高
在产生式系统求解问题的过程中,首先要用产生式的前提部分与综合数据库中的已知事实进行匹配,从规则库中选出可用的规则,此时选出的规则可能不止一个,这就需要按一定的策略进行“冲突消解”,然后把选中的规则启动执行。因此,产生式系统求解问题的过程是一个反复进行“匹配一冲突消解—执行”的过程。鉴于规则库一般都比较庞大,而匹配又是一件十分费时的工作,因此其工作效率不高,而且大量的产生式规则容易引起组合爆炸。
b.不能表达具有结构性的知识
产生式适合于表达具有因果关系的过程性知识,是一种非结构化的知识表示方法,所以对具有结构关系的知识却无能为力,它不能把具有结构关系的事物间的区别与联系表示出来。因此,产生式表示法除了可以独立作为一种知识表示模式外,还经常与其他表示法结合起来表示特定领域的知识。
(4)产生式表示法适合表示的知识
由上述关于产生式表不法的特点,可以看出产生式表示法适合于表示具有下列特点的领域知识。
1)由许多相对独立的知识元组成的领域知识,彼此间关系不密切,不存在结构关系,如化学反应方面的知识。
2)具有经验性及不确足性的知识,而且相关领域中对这些知识没有严格、统一的理论,如医疗诊断、故障诊断等方面的知识。
3)领域问题的求解过程可被表示为一系列相对独立的操作,而且每个操作可被表示为一条或多条产生式规则。
2.研究目的
本次研究的目的为熟悉产生式构成的三个部分:综合数据库、规则库、策略控制,通过实际操作,完成一个简单的产生式系统的设计,从设计数据库、规则库、处理规则、运用算法实现推理几个过程,完成对产生式系统的认识加深与运用。并在研究中锻炼自己自主学习、思考问题的习惯,去分析、解决算法逻辑、功能实现等方面的问题,培养自己的研究能力。
二、系统总体设计
1.系统功能
实现一个昆虫识别系统的创建,数据库部分包括昆虫特征,规则库内容,实现根据输入特征判断库中是否存在该昆虫,并输出查找结果。
2.设计原理
(1)一个产生式系统由规则库、控制系统、综合数据库三部分组成。
1)规则库
用于描述相应领域内知识的产生式集合称为规则库。
显然,规则库是产生式系统求解问题的基础,其知识是否完整、一致,表达是否准确、灵活,对知识的组织是否合理等,将直接影响到系统的性能。因此,需要对规则库中的知识进行合理的组织和管理,检测并排除冗余及矛盾的知识,保持知识的一致性。采用合理的结构形式,可使推理避免访问那些与求解当前问题无关的知识,从而提高求解问题的效率。
2)综合数据库
综合数据库又称为事实库、上下文、黑板等。它是一个用于存放问题求解过程中各种当前信息的数据结构,如问题的初始状态、原始证据、推理中得到的中间结论及最终结论。当规则库中某条产生式的前提可与综合数据库的某些已知事实相匹配时,该产生式就被激活,并把它推出的结论放入综合数据库中,作为后面推理的已知事实。显然,综合数据库的内容是在不断变化的。
3)控制系统
控制系统又称为推理机。它由一组程序组成,负责整个产生式系统的运行, 实现对问题的求解。
它们之间的关系如图所示:
(2)产生式系统的实现有三种方法
1)正向推理:从一组表示事实的谓词或命题出发,使用一组产生式规则,用以证明该谓词公式或命题是否成立。
2)逆向推理:从表示目标的谓词或命题出发,使用一组产生式规则证明事实谓词或命题成立,即首先提出一批假设目标,然后逐一验证这些假设。
3)双向推理:双向推理的推理策略是同时从目标向事实推理和从事实向目标推理,并在推理过程中的某个步骤,实现事实与目标的匹配。
3.设计步骤
(1)创建数据库
系统的实现首先应该创建一个昆虫数据库,将个体特征信息拆分为以下三个部分再创建数据库,三个部分分别是:
1)实现将个体名称的信息导入数据库
2)实现个体特征的定义,并将信息导入数据库
3)将个体与个体特征结合起来,产生一个关于个体或其他信息的规则,将规则导入数据库
(2)编写系统运行时要用的算法
产生式系统的实现有三种方法:1)正向推理、2)逆向推理、3)双向推理。
首先确定系统的实现方法为哪一种,进行算法的编写,再定义并构造系统所需要的其他函数,例如:计算置信度、重置置信度、排序算法等。
(3)设计流程及输入输出显示
系统的功能实现除了必备的数据库及核心算法等,还应该具备一个具有输入输出提示的界面,在输入特征时可以显示数据库的特征,提示使用者进行下一步的操作,以及其他功能,例如:停止输入、清屏等。
具体系统创建步骤如下图所示:
三、详细设计
1.系统实现方法
由昆虫识别系统的特点,我运用的是前提-结论型产生式,即前件和后件分别代表前提及其相应的结论。数据库中存储昆虫名及昆虫特征,规则库实现昆虫名与特征的结合,控制策略即根据采取的正向或逆向推理选取出此条规则遍历完后应该遍历的下一条规则。
根据系统特征,我采取的方法是双向推理,其中双向推理的过程是:将所有名词编号,然后用编号来组织成一条条件(规则库),遍历这些条件,根据用户给出的昆虫特征,进行比较,同时计算每个条件的符合程度,推理出的名词加入到已知的名词队列中,重新遍历条件,更新符合度,如果没有完全符合的条件,则寻找符合度最高的条件,进行逆向推理,询问可能的且没有在已知名词队列中的名词,进行判断,加入名词队列,重新遍历,更新符合度,直至找到属于结果类的名词,即是结果。双向推理实现方法如下图所示:
2.创建昆虫数据库
(1)规则库条件
1)昆虫个体:
中华盗虻、麻蝇、中华按蚊、巨圆臀大蜓、牛虻、绿蝇、乐仙蜻蜓、东亚飞蝗。
2)昆虫目类:
双翅目、直翅目、蜻蜓目、虻类、蚊类、蝇类。
3)昆虫特征:
a.区别个体的特征:
飞行快、尾部亮黑、有斑点、下唇黄褐色、体黄褐色、背棕黑、胸深蓝色、暗色斑纹、雌额宽、体青绿色、。
b.区别目类特征:
只有一对翅膀、前翅狭长、翅膀膜质透明、翅膀多、粗壮、头部半球形、复眼、刺吸式口器。
(2)规则库内容
R1:IF 只有一对翅膀 THEN双翅目;
R2:IF 前翅狭长 THEN直翅目;
R3:IF 翅膀膜质透明 AND翅膀
THEN蜻蜓目;
R4:IF 双翅目 AND粗壮
THEN虻类;
R5:IF 双翅目 AND头部半球形
THEN蝇类;
R6:IF 双翅目 AND复眼
THEN蝇类;
R7:IF 双翅目 AND刺吸式口器
THEN蚊类;
R8:IF 双翅目 AND虻类AND飞行快
THEN中华盗虻;
R9:IF 双翅目 AND尾部亮黑AND背棕黑AND雌额宽 THEN麻蝇;
R10:IF 双翅目 AND蚊类AND有斑点AND飞行快
THEN中华按蚊;
R11:IF 蜻蜓目 AND尾部亮黑AND下唇黄褐色
THEN巨圆臀大蜓;
R12:IF 双翅目 AND虻类AND体黄褐色AND背棕黑AND雌额宽
THEN牛虻;
R13:IF双翅目 AND蝇类AND青绿色AND背棕黑AND雌额宽
THEN绿蝇;
R14:IF蜻蜓目 AND尾部亮黑AND背棕黑AND胸深蓝色
THEN乐仙蜻蜓;
R15:IF直翅目 AND体黄褐色AND暗色斑纹AND雌额宽
THEN东亚飞蝗。
3.设计算法
(1)判断是否为昆虫或物种;
(2)菜单函数;
(3)去除已出现的父亲结点的子节点特征;
(4)正向推理函数;
(5)输出昆虫类别、个体;
(6)计算置信度与重新计算置信度函数;
(7)反向推理函数;
(8)用户输入特征函数;
(9)置信度比较函数;
(10)置信度排序函数。
其中正向、反向推理函数所形成的双方向推理为整个代码的重难点,在该函数中涉及置信度的计算与重新计算等步骤,其中要使用特征变量和临时变量,特征变量控制循环的跳出,临时变量标记每次正向、逆向推理后所用到的特征值,方便遍历查找以及计算置信度。
4.屏幕显示设置
(1)输入选择是否进入系统,否认则退出,确认则进入系统,打印显示用户可选特征,输出显示输入提示;
(2)用户输入特征,以-1标志特征输入结束;
(3)系统做出判断后显示判断的昆虫名或征求更多特征的指令,用户确认新增特征是否存在后则返回最终结果:正确昆虫名、系统查找不到或按置信度依次输出可能昆虫;
(4)系统显示是否继续下一轮查询,否认则退出,确认则进入系统,再清屏,打印显示用户可选特征,输出显示输入提示。
四、编码实现过程
1、判断置信度大小:
bool Compare(const Result& a, const Result& b)
{
return a.confidence > b.confidence;
}
2、改变特征值,推理是否有物种种类,并将用到的事实清空:
int change_speices()
{
int i, j, key;
bool t;
int temp[23] = { 0 }; //临时存储,方便修改
int f[23] = { 0 }; // 标记使用过的flag
for (i = 0; i < 7; i++)
{
//前7个规则
t = true;
j = 0;
key = rule[i].relation[j];
while (key != -1)//遍历该条关系
{
if (flag[key] == 1)
{
temp[key] = 1;
}
else
{
memset(temp, 0, sizeof(temp));
t = false;
break;
}
j++;
key = rule[i].relation[j];
}
if (t)
{
for (int k = 0; k <= 17; k++)
{
if (temp[k] == 1)
{
f[k] = 1;
}
}
flag[rule[i].name] = 1;
}
memset(temp, 0, sizeof(temp));
}
//删除推理过的事实,保留结果
for (i = 0; i <= 17; i++)
{
if (f[i] == 1)
{
flag[i] = 0;
}
}
return 1;
}
3、重新计算置信度:
void cal_confi()
{
for (int i = 0; i < result.size(); i++)
{
for (int j = 7; j < 15; j++)
{
if (result[i].insect == rule[j].name)
{
result[i].confidence = 1.0 * result[i].num / result[i].size;
break;
}
}
}
}
4、双向推理,正向推理不下去,事实不够,采用逆向推理:
int z_inference()
{
int key, num;
int i, j;
int fum = fnum();
cout << endl;
for (i = 7; i < 15; i++)
{
//检查规则库
Result temp;
j = 0; num = 0;
key = rule[i].relation[j];
while (key != -1)
{
if (flag[key] == 1)
{
num++;
}
j++;
key = rule[i].relation[j];
}
//此时j保存则rule[i]所含有的特征数
if (num != 0 && fum <= j)
{
//给定特征数小于等于的情况,即flag数组中标记位数目大于此昆虫的特征数则不放入result
if (IsInsect(rule[i].name))
{
// 是具体的昆虫
temp.insect = rule[i].name;
int size = j; //rule[i]所含有的特征数
temp.size = size;
temp.confidence = 1.0 * num / size;
temp.site = i;
temp.num = num;
result.push_back(temp);
}
}
}
if (!result.empty())
{
Rsort(result); //对置信度从高到低排序
}
//正向推理后
if (result.empty())
{
//给定特征数无法用任何一规则推理,可能没有这种昆虫,可能是一种类别
category();
}
else if (result.front().confidence == 1.0)
{
//可能给的特征刚好推理出,可能特征还没用完
cout << "昆虫为:" << feature[result.front().insect] << endl;
result.clear();//清空
return 1;
}
else//逆向推理,询问特征
{
r_inference();
}
}
5、特征不足,进入反向推理:
void r_inference()
{
vector<Result>::iterator it = result.begin();
int enquire[23];
//用来标记询问过的特征数组(0代表没有此特征,1代表有,2代表不请楚、 不知道)
memset(enquire, -1, sizeof(enquire));
for (int i = 0; i< result.size();)
{
//从置信度最高开始询问
bool in_i = true; // i ++ 的标记
int nu = result[i].size;
for (int j = 0; j < nu; j++)
{
//询问特征
if (flag[rule[result[i].site].relation[j]] == 0)
{
int en = rule[result[i].site].relation[j];
char c;
if (enquire[en] == -1)
{
//此特征未被询问过,则输出询问语句,否则直接判断处理
cout << "是否有以下特征:" << feature[rule[result[i].site].relation[j]] << endl;
cout << "Y(y) or N(n) or D(don't know) : ";
cin >> c;
while (c != 'Y' && c != 'y' && c != 'N' && c != 'n' && c != 'D' && c != 'd')
{
cout << "输出选择:Y(y) N(n) D(d)!" << endl;
cin >> c;
}
}
if (enquire[en] == 1 || c == 'Y' || c == 'y')
{
//有此特征 改变置信度
result[i].num++;
enquire[en] = 1;
}
else if (enquire[en] == 0 || c == 'N' || c == 'n')
{
// 没有此特征 直接去掉
enquire[en] = 0;
result.erase(it + i);
//erase删除后i不自增,就能删除最后的元素,迭代器指向删除之前元素后的第一个元素
in_i = false; //如果擦除了元素,则i不自增
if (result.empty())//result为空,输出类别后退出
{
category();
}
break;
}
else if (enquire[en] == 2 || c == 'D' || c == 'd')
{
enquire[en] = 2; // 不确定、不知道,则置信度不改变
}
}
}
if (in_i)
{
++i;
}
}
if (!result.empty())
{
// 改变置信度
cal_confi();
if (result.size() > 1) //重新排序
{
Rsort(result);
}
//判断询问后进行双向推理
if (result.front().confidence == 1.0)
{
cout << "昆虫是:" << feature[result.front().insect] << endl;
}
else
{
cout << "昆虫可能为(置信度从高到低):";
for (vector<Result>::iterator it = result.begin(); it != result.end(); ++it)
{
cout << setiosflags(ios::left) << setw(10) << feature[(*it).insect] << " ";
}
cout << endl;
}
result.clear(); // 清空
}
}
五、结果展示
1.当输入特征完全匹配系统规则时,正向推理,直接输出结果:
2.当输入特征不完全匹配系统规则,缺少特征时,从置信度最高的开始进行逆向推理,获得新特征,以此更新置信度:
3.当输入特征不完全匹配系统规则,缺少特征时,从置信度最高的开始进行逆向推理,若不更新特征,直至最后按照置信度高低输出可能的昆虫:
4.当输入特征不完全匹配系统规则,缺少特征时,从置信度最高的开始进行逆向推理,获得新特征,若新特征与系统数据相矛盾,则输出无该昆虫:
六、总结
1、系统完成情况:
该系统已经能基本满足需要,对输入的事实能给出相应的回答,判断出是何种昆虫。可以实现系统较为流畅的操作,用户可根据系统所给提示较好的完成特征输入,并根据提示补全特征以及进行继续操作或退出操作。
2、系统缺点:
该系统的规则库是静态的,不能动态增加新的规则。这使得在规则变化的情况下不能及时改变。
3、作业收获:
理解了产生式系统结构原理与实际应用。能够掌握产生式规则表示及规则库组件的实现方法。熟悉和掌握了产生式系统的运行机制,掌握了基于规则推理的基本方法。
附录:
#include<iostream>
#include<iomanip>
#include<string.h>
#include<stdlib.h>
#include<algorithm>
#include<vector>
using namespace std;
//记录数据库内昆虫个体
string insect[] = { "中华盗虻", "麻蝇", "中华按蚊", "巨圆臀大蜓", "牛虻", "绿蝇", "乐仙蜻蜓", "东亚飞蝗" };
//记录数据库昆虫特征
string feature[] = {
"飞行快", "尾部亮黑", "有斑点", "下唇黄褐色", "体黄褐色", "背棕黑", "胸深蓝色", "暗色斑纹", "雌额宽", "体青绿色",//区别个体的特征
//0 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
//存放规则的结构体
typedef struct
{
int relation[7]; //关系
int name; //推理结果
}Rule;// 存放可能的昆虫
typedef struct
{
int insect; // name
double confidence; //置信度 = 满足的特性数 / 所含特征数;
int site; // 在rule中的位置
int num; // 满足的特征数
int size; // 此animal的所含总特征数
}Result;
vector<Result> result;
//规则库
//输入规则时最后输入-1则代表规则结束
Rule rule[15] =
{
{ { 10, -1 }, 18 },
// R1:只有一对翅膀→双翅目;
{ { 11, -1 }, 19 },
// R2:前翅狭长→直翅目;
{ { 12, 13, -1 }, 20 },
// R3:翅膀膜质透明&翅膀多→蜻蜓目;
{ { 18, 14, -1 }, 21 },
// R4:双翅目&粗壮→虻类;
{ { 18, 15, -1 }, 22 },
// R5:双翅目&头部半球形→蝇类;
{ { 18, 16, -1 }, 22 },
// R6:双翅目&复眼→蝇类;
{ { 18, 17, -1 }, 23 },
// R7:双翅目&刺吸式口器→蚊类;
{ { 18, 21, 0, -1 }, 24 },
// R8:双翅目&虻类&飞行快→中华盗虻;
{ { 18, 1, 5, 8, -1 }, 25 },
// R9:双翅目&尾部亮黑&背棕黑&雌额宽→麻蝇;
{ { 18, 2, -1, 0}, 26 },
// R10:双翅目&蚊类&有斑点&飞行快→中华按蚊;
{ { 20, 1, 3, -1 }, 27 },
// R11:蜻蜓目&尾部亮黑&下唇黄褐色→巨圆臀大蜓;
{ { 18, 21, 4, 5, 8, -1 }, 28 },
// R12:双翅目&虻类&体黄褐色&背棕黑&雌额宽→牛虻;
{ { 18, 22, 9, 8, 5, -1 }, 29 },
// R13:双翅目&蝇类&青绿色&背棕黑&雌额宽→绿蝇;
{ { 20, 1, 5, 6, -1 }, 30 },
// R14:蜻蜓目&尾部亮黑&背棕黑&胸深蓝色→乐仙蜻蜓;
{ { 19, 4, 7, 8, -1 }, 31 }
// R15:直翅目&体黄褐色&暗色斑纹&雌额宽→东亚飞蝗
};
int flag[23] = { 0 }; //标记各个特征是否选择
int IsInsect(int a);
int change_speices(); //将可以推理出昆虫类的规则进行
int fnum(); //获取flag标记的数目
int z_inference(); //正向推理
int category(); //输出昆虫类别
void cal_confi(); //计算置信度
void r_inference(); //反向推理
void input(); //输入
void menu(); //菜单
//判断置信度大小
bool Compare(const Result& a, const Result& b)
{
return a.confidence > b.confidence;
}
//排序并返回排序结果
void Rsort(vector<Result>& r)
{
//调用数组容器的排序函数
sort(r.begin(), r.end(), Compare);
return;
}
//选择特征菜单
void menu()
{
//输出知识库中特征数组除昆虫名的成员,每输出4个换行
for (int i = 0; i < sizeof(feature) / sizeof(feature[0]) - sizeof(insect) / sizeof(insect[0]); i++)
{
if (i % 4 == 0 && i != 0)
{
cout << endl;
}
cout << setiosflags(ios::left) << setw(2) << i << ".";
cout << setiosflags(ios::left) << setw(15) << feature[i];
}
memset(flag, 0, sizeof(flag));//初始化标记数组为0
}
//特征输入值选择数字
void input()
{
for (int i = 0; i < sizeof(feature) / sizeof(feature[0]) - sizeof(insect) / sizeof(insect[0]); i++)
{
flag[i] = 0;
}
cout << endl;
int key = 0;
cout << "*****************************************************************************"<<endl;
cout << "请输入所选昆虫特征(输入完成后,请输入-1.):";
while (key != -1)//当输入-1时停止特征输入
{
cin >> key;
if (key >= 0 && key <= 23)
{
flag[key] = 1;
}
else if (key != -1)
{
cout << "输入错误,请输入一个特征:" << endl;
cin.clear(); //清除流错误错误标记
cin.sync(); //清空输入缓冲区
cout << "请继续输入:";
}
}
}
//是某昆虫而不是某种物种
int IsInsect(int a)
{
if (a >= sizeof(feature) / sizeof(feature[0]) - sizeof(insect) / sizeof(insect[0]) && a <= sizeof(feature) / sizeof(feature[0]))
{
return 1;
}
return 0;
}
//判断是否某一物种类
int IsPlant_speices(int a)
{
if (a >= 18 && a <= 23)
{
return 1;
}
return 0;
}
//返回flag数组中标记的总数
int fnum()
{
int fum = 0;
for (int i = 0; i < 24; i++)
if (flag[i] == 1)
{
fum++;
}
return fum;
}
//输出打印物种类别
int category()
{
bool k;
int count = 0;
for (int i = 18; i < 24; i++)
{
k = false;
if (flag[i] == 1)
{
k = true;
count++;
if (count == 1)
{
cout << "无法识别昆虫! 所属类为:";
}
cout << setiosflags(ios::left) << setw(10) << feature[i];
}
}
cout << endl;
if (!k)
{
cout << "系统无该昆虫" << endl;
}
return 1;
}
//改变特征值,变化flag,推理是否有物种种类,并将用到的事实清空,改flag为0
int change_speices()
{
int i, j, key;
bool t;
int temp[23] = { 0 }; //临时存储,方便修改
int f[23] = { 0 }; // 标记使用过的flag
for (i = 0; i < 7; i++)
{
//前7个规则
t = true;
j = 0;
key = rule[i].relation[j];
while (key != -1)//遍历该条关系
{
if (flag[key] == 1)
{
temp[key] = 1;
}
else
{
memset(temp, 0, sizeof(temp));
t = false;
break;
}
j++;
key = rule[i].relation[j];
}
if (t)
{
for (int k = 0; k <= 17; k++)
{
if (temp[k] == 1)
{
f[k] = 1;
}
}
flag[rule[i].name] = 1;
}
memset(temp, 0, sizeof(temp));
}
//删除推理过的事实,保留结果
for (i = 0; i <= 17; i++)
{
if (f[i] == 1)
{
flag[i] = 0;
}
}
return 1;
}
//重新计算置信度
void cal_confi()
{
for (int i = 0; i < result.size(); i++)
{
for (int j = 7; j < 15; j++)
{
if (result[i].insect == rule[j].name)
{
result[i].confidence = 1.0 * result[i].num / result[i].size;
break;
}
}
}
}
//双向推理,正向推理不下去,事实不够,采用逆向推理
int z_inference()
{
int key, num;
int i, j;
int fum = fnum();
cout << endl;
for (i = 7; i < 15; i++)
{
//检查规则库
Result temp;
j = 0; num = 0;
key = rule[i].relation[j];
while (key != -1)
{
if (flag[key] == 1)
{
num++;
}
j++;
key = rule[i].relation[j];
}
//此时j保存则rule[i]所含有的特征数
if (num != 0 && fum <= j)
{
//给定特征数小于等于的情况,即flag数组中标记位数目大于此昆虫的特征数则不放入result
if (IsInsect(rule[i].name))
{
// 是具体的昆虫
temp.insect = rule[i].name;
int size = j; //rule[i]所含有的特征数
temp.size = size;
temp.confidence = 1.0 * num / size;
temp.site = i;
temp.num = num;
result.push_back(temp);
}
}
}
if (!result.empty())
{
Rsort(result); //对置信度从高到低排序
}
//正向推理后
if (result.empty())
{
//给定特征数无法用任何一规则推理,可能没有这种昆虫,可能是一种类别
category();
}
else if (result.front().confidence == 1.0)
{
//可能给的特征刚好推理出,可能特征还没用完
cout << "昆虫为:" << feature[result.front().insect] << endl;
result.clear();//清空
return 1;
}
else//逆向推理,询问特征
{
r_inference();
}
}
//特征不足,进入反向推理
void r_inference()
{
vector<Result>::iterator it = result.begin();
int enquire[23];
//用来标记询问过的特征数组(0代表没有此特征,1代表有,2代表不请楚、 不知道)
memset(enquire, -1, sizeof(enquire));
for (int i = 0; i< result.size();)
{
//从置信度最高开始询问
bool in_i = true; // i ++ 的标记
int nu = result[i].size;
for (int j = 0; j < nu; j++)
{
//询问特征
if (flag[rule[result[i].site].relation[j]] == 0)
{
int en = rule[result[i].site].relation[j];
char c;
if (enquire[en] == -1)
{
//此特征未被询问过,则输出询问语句,否则直接判断处理
cout << "是否有以下特征:" << feature[rule[result[i].site].relation[j]] << endl;
cout << "Y(y) or N(n) or D(don't know) : ";
cin >> c;
while (c != 'Y' && c != 'y' && c != 'N' && c != 'n' && c != 'D' && c != 'd')
{
cout << "输出选择:Y(y) N(n) D(d)!" << endl;
cin >> c;
}
}
if (enquire[en] == 1 || c == 'Y' || c == 'y')
{
//有此特征 改变置信度
result[i].num++;
enquire[en] = 1;
}
else if (enquire[en] == 0 || c == 'N' || c == 'n')
{
// 没有此特征 直接去掉
enquire[en] = 0;
result.erase(it + i);
//erase删除后i不自增,就能删除最后的元素,迭代器指向删除之前元素后的第一个元素
in_i = false; //如果擦除了元素,则i不自增
if (result.empty())//result为空,输出类别后退出
{
category();
}
break;
}
else if (enquire[en] == 2 || c == 'D' || c == 'd')
{
enquire[en] = 2; // 不确定、不知道,则置信度不改变
}
}
}
if (in_i)
{
++i;
}
}
if (!result.empty())
{
// 改变置信度
cal_confi();
if (result.size() > 1) //重新排序
{
Rsort(result);
}
//判断询问后进行双向推理
if (result.front().confidence == 1.0)
{
cout << "昆虫是:" << feature[result.front().insect] << endl;
}
else
{
cout << "昆虫可能为(置信度从高到低):";
for (vector<Result>::iterator it = result.begin(); it != result.end(); ++it)
{
cout << setiosflags(ios::left) << setw(10) << feature[(*it).insect] << " ";
}
cout << endl;
}
result.clear(); // 清空
}
}
int main()
{
char q;
cout << "*****************************************************************************"<<endl;
cout << "* 昆虫识别系统 *"<<endl;
cout << "*****************************************************************************"<<endl;
cout << "是否进入系统:Y(y) N(n)"<<endl;
cin >> q;
cout << "*****************************************************************************"<<endl;
while (q != 'N' && q != 'n')
{
menu();
input();
change_speices();
z_inference();
cout << endl << "继续?(Y/N)" << endl;
cin >> q;
system("cls");//清屏进行新的操作
}
return 0;
}