产生式系统的设计及代码实现(植物识别系统)

一、研究背景和目的

1.研究背景

(1)产生式概念
产生式系统简称产生式。它是指形如->或IF THEN或其等价形式的一条规则,其中箭头左边称为产生式的左部或前件;箭头右边称为产生式的右部或后件。如果前件和后见分别代表需要注视的一组条件及其成立时需要采取的行动,那么称为条件-行动型产生式;如果前件和后见分别代表前提及其相应的结论,那么称为前提-结论型产生式。
(2)产生式特点
产生式是系统的单元程序,它与常规程序不同之处在于,产生式是否执行并不在事前硬性规定,各产生式之间也不能相互直接调用,而完全决定于该产生式的作用条件能否满足,即能否与全局数据库的数据条款匹配。因此在人工智能中常将产生式称为一种守护神(demon),即“伺机而动”之意。另一方面,产生式在执行之后工作环境即发生变化,因而必须对全局数据库的条款作相应修改,以反映新的环境条件。全部工作是在控制程序作用下进行的。现代产生式系统的一个工作循环通常包含匹配、选优、行动三个阶段。匹配通过的产生式组成一个竞争集,必须根据选优策略在其中选用一条,当选的产生式除了执行规定动作外,还要修改全局数据库的有关条款。因此现代产生式系统的控制程序常按功能划分为若干程序。
(3)产生式优缺点
产生式系统的优点是:模块性,每一产生式可以相对独立地增加、删除和修改;均匀性,每一产生式表示整体知识的一个片段,易于为用户或系统的其他部分理解;自然性,能自然地表示直观知识。它的缺点是执行效率低,此外每一条产生式都是一个独立的程序单元,一般相互之间不能直接调用也不彼此包含,控制不便,因而不宜用来求解理论性强的问题。
(4)产生式应用
人工智能中的推理很多是建立在直观经验基础上的不精确推理,而产生式在表示和运用不精确知识方面具有灵活性,因此许多专家系统采用产生式系统为体系结构,产生式系统被广泛地运用于专家系统等模块。

2.研究目的

本次研究的目的为熟悉产生式构成的三个部分:综合数据库、规则库、策略控制,通过实际操作,完成一个简单的产生式系统的设计,从设计数据库、规则库、处理规则、运用算法实现推理几个过程,完成对产生式系统的认识加深与运用。并在研究中锻炼自己自主学习、思考问题的习惯,去分析、解决算法逻辑、功能实现等方面的问题,培养自己的研究能力。

二、系统总体设计

1.系统功能

实现一个植物识别系统的创建,数据库部分包括植物特征,规则库内容,实现根据输入特征判断库中是否存在该植物,并输出查找结果。

2.设计原理:

(1)一个产生式系统由三个部分组成
1)综合数据库:用来存放与求解问题有关的数据以及推理过程环境的当前状态的描述。
2)产生式规则库:主要存放问题求解中的规则。
3)控制策略:其作用是说明下一步应该选用什么规则,也就是说如何应用规则。
三个部分如图所示:
在这里插入图片描述
(2)产生式系统的实现有三种方法
1)正向推理:从一组表示事实的谓词或命题出发,使用一组产生式规则,用以证明该谓词公式或命题是否成立。
2)逆向推理:从表示目标的谓词或命题出发,使用一组产生式规则证明事实谓词或命题成立,即首先提出一批假设目标,然后逐一验证这些假设。
3)双向推理:双向推理的推理策略是同时从目标向事实推理和从事实向目标推理,并在推理过程中的某个步骤,实现事实与目标的匹配。

3.设计步骤

(1)创建数据库
系统的实现首先应该创建一个植物数据库,将个体特征信息拆分为以下三个部分再创建数据库,三个部分分别是:
1)实现将个体名称的信息导入数据库
2)实现个体特征的定义,并将信息导入数据库
3)将个体与个体特征结合起来,产生一个关于个体或其他信息的规则,将规则导入数据库
(2)编写系统运行时要用的算法
产生式系统的实现有三种方法:
1)正向推理:
2)逆向推理:
3)双向推理:
首先确定系统的实现方法为哪一种,进行算法的编写,再定义并构造系统所需要的其他函数,例如:计算置信度、重置置信度、排序算法等。
(3)设计流程及输入输出显示
系统的功能实现除了必备的数据库及核心算法等,还应该具备一个具有输入输出提示的界面,在输入特征时可以显示数据库的特征,提示使用者进行下一步的操作,以及其他功能,例如:停止输入、清屏等。
具体系统创建步骤如下图所示:
在这里插入图片描述

三、详细设计

1.系统实现方法——双向推理

由植物识别系统的特点,我运用的是前提-结论型产生式,即前件和后见分别代表前提及其相应的结论。数据库中存储植物名及植物特征,规则库实现植物名与特征的结合,控制策略即根据采取的正向或逆向推理选取出此条规则遍历完毕后应该遍历的下一条规则。
根据系统特征,我采取的方法是双向推理,其中双向推理的过程是:将所有名词编号,然后用编号来组织成一条条件(规则库),遍历这些条件,根据用户给出的植物特征,进行比较,同时计算每个条件的符合程度,推理出的名词加入到已知的名词队列中,重新遍历条件,更新符合度,如果没有完全符合的条件,则寻找符合度最高的条件,进行逆向推理,询问可能的且没有在已知名词队列中的名词,进行判断,加入名词队列,重新遍历,更新符合度,直至找到属于结果类的名词,即是结果。双向推理实现方法如下图所示:
在这里插入图片描述

2.创建植物数据库

(1)植物个体
玫瑰、荷花、仙人球、水棉、苹果树、油菜、海带、松树
(2)个体特征
1)区别个体的特征
有刺、水生、喜阳、药用、木本、可食用、有白色粉末、叶片针状、结果实、黄色花;
2)区别种类的特征
种子有果皮、种子无果皮、无茎叶、无根、有托叶、吸引菜粉蝶、十字形花冠、缺水环境;
3)种类
被子植物、裸子植物、藻类植物、蔷薇科、十字花科、仙人掌科;
4)个体
玫瑰、荷花、仙人球、水棉、苹果树、油菜、海带、松树
(3)规则库
R1:种子有果皮 -> 被子植物
R2:种子无果皮 -> 裸子植物
R3:无茎叶 & 无根 -> 藻类植物
R4:被子植物 & 有托叶 -> 蔷薇科
R5:被子植物 & 吸引菜粉蝶 -> 十字花科
R6:被子植物 & 十字形花冠 -> 十字花科
R7:被子植物 & 缺水环境 -> 仙人掌科
R8:被子植物 & 蔷薇科 & 有刺 -> 玫瑰
R9:被子植物 & 水生 & 可食用 & 结果实 -> 荷花
R10:被子植物 & 仙人掌科 & 喜阳 & 有刺 -> 仙人球
R11:藻类植物 & 水生 & 药用 -> 水棉
R12:被子植物 & 蔷薇科 & 木本 & 可食用 & 结果实 -> 苹果树
R13:被子植物 & 十字花科 & 黄色花 & 可食用 & 结果实 -> 油菜
R14:藻类植物 & 水生 & 可食用 & 有白色粉末 -> 海带
R15:裸子植物 & 木本 & 叶片针状 & 结果实 -> 松树
规则库的具体显示如下图所示:
在这里插入图片描述

3.设计算法

(1)判断是否为植物或物种
(2)菜单函数
(3)去除已出现的父亲结点的子节点特征
(4)正向推理函数
(5)输出植物类别、个体
(6)计算置信度与重新计算置信度函数
(7)反向推理函数
(8)用户输入特征函数
(9)置信度比较函数
(10)置信度排序函数
其中正向、反向推理函数所形成的双方向推理为整个代码的重难点,在该函数中涉及置信度的计算与重新计算等步骤,其中要使用特征变量和临时变量,特征变量控制循环的跳出,临时变量标记每次正向、逆向推理后所用到的特征值,方便遍历查找以及计算置信度。

4.屏幕显示设置

(1)输入选择是否进入系统,否认则退出,确认则进入系统,打印显示用户可选特征,输出显示输入提示
(2)用户输入特征,以-1标志特征输入结束
(3)系统做出判断后显示判断的植物名或征求更多特征的指令,用户确认新增特征是否存在后则返回最终结果:正确植物名、系统查找不到或按置信度依次输出可能植物
(4)系统显示是否继续下一轮查询,否认则退出,确认则进入系统,再清屏,打印显示用户可选特征,输出显示输入提示
四、编码实现过程

#include<iostream>
#include<iomanip>
#include<string>
#include<stdlib.h>
#include<algorithm> 
#include<vector>

using namespace std;
//记录数据库内植物名
string plant[] = { "玫瑰", "荷花", "仙人球", "水棉", "苹果树", "油菜", "海带", "松树" };

//记录数据库植物特征
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 plant;         // 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 IsPlant(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(plant) / sizeof(plant[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(plant) / sizeof(plant[0]); i++)
	{
		flag[i] = 0;
	 }
	int  key = 0;
	cout << endl << "输入所选植物特征:";
	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 IsPlant(int a)
{
	if (a >= sizeof(feature) / sizeof(feature[0]) - sizeof(plant) / sizeof(plant[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].plant == 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 (IsPlant(rule[i].name))
			{ 
				// 是具体的植物 
				temp.plant = 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().plant] << 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().plant] << endl;
		}
		else
		{
			cout << "植物可能为(置信度从高到低):";
			for (vector<Result>::iterator it = result.begin(); it != result.end(); ++it)
			{
				cout << setiosflags(ios::left) << setw(10) << feature[(*it).plant] << " ";
			}
			cout << endl;
		}
		result.clear(); // 清空 
	}
}
int main()
{
	char q;
	cout << "是否进入系统:Y(y) N(n)";
	cin >> q;
	while (q != 'N' && q != 'n')
	{
		menu();
		input();
		change_speices();
		z_inference();
		cout << endl << "继续?(Y/N)" << endl;
		cin >> q;
		system("cls");//清屏进行新的操作
	}
	return 0;
}

五、结果展示

1.当输入特征完全匹配系统规则时,正向推理,直接输出结果:(如下图所示)

已知:有刺、被子植物、蔷薇科
正向推理:R8
正向推理,有刺->仙人球、玫瑰,被子植物、蔷薇科->玫瑰,排除仙人球
在这里插入图片描述

2.当输入特征不完全匹配系统规则,缺少特征时,从置信度最高的开始进行逆向推理,获得新特征,以此更新置信度:(如下图所示)

正向推理:R9、R11、R14
逆向推理,水生->藻类植物、被子植物->海带、水棉->水棉
在这里插入图片描述

3.当输入特征不完全匹配系统规则,缺少特征时,从置信度最高的开始进行逆向推理,若不更新特征,直至最后按照置信度高低输出可能的植物:(如下图所示)

正向推理:R6、R11、R14
逆向推理,十字形花冠、藻类植物->被子植物->海带、水棉
在这里插入图片描述

4.当输入特征不完全匹配系统规则,缺少特征时,从置信度最高的开始进行逆向推理,获得新特征,若新特征与系统数据相矛盾,则输出无该植物:(如下图所示)

正向推理:R10
逆向推理,从被子植物往回查找,与不是被子植物相违背,系统找不到相对应的植物
在这里插入图片描述

六、总结

1.系统完成情况分析

(1)基本功能实现
系统创建了数据库、规则库,并对一条规则的结束标志做出了规定。
该系统已经能基本满足对输入的特征给出相应的回答,通过双向推理的方法判断是否有该植物,并询问补充特征,完成对数据库的数据访问。
(2)用户操作实现
可以实现系统较为流畅的操作,用户可根据系统所给提示较好的完成特征输入,并根据提示补全特征以及进行继续操作或退出操作。

2.系统不足

本系统的规则库是静态的,不能很好的进行增删改操作,这使得在实际运用是不便于系统数据库的修改和维护,对于系统功能更方便地实现增加了一定难度。

发布了47 篇原创文章 · 获赞 108 · 访问量 4734

猜你喜欢

转载自blog.csdn.net/qq_44759710/article/details/103451174