人工智能之动物识别专家系统
本文介绍动物识别专家系统,使用C#语言Windows窗体(.NET Framework)制作的系统界面。如果想了解制作过程可参考文章:应用程序访问数据库之Visual Studio 2022 连接SQL server数据库_vs2022连接sqlserver数据库-CSDN博客
在这篇文章有具体的从创建文件到界面设计的过程,可以参考。如果你制作的专家系统需要使用数据库,也可以在这篇文章找到连接数据库的方式。
图1 动物识别专家系统用户交互界面
sun(没错,我本人)制作的动物识别专家系统没有采用数据库,而是使用了两个txt文件以及List存放,这个专家系统并不复杂,所以这样也完全够了。
接下来是前置知识和具体的代码实现了。不需要看前置知识的可以直接看代码部分。
一、前置知识
基础概念:什么是专家系统?(内容来自gpt)
专家系统是一种模拟人类专家决策能力的计算机程序,它能够解决那些通常需要人类专家知识才能解决的复杂问题。专家系统通常包含以下几个关键组成部分:
1. **知识库(Knowledge Base)**:存储专家的知识和经验,这些知识通常以规则的形式表示,例如“如果...那么...”。
2. **推理机(Inference Engine)**:使用知识库中的规则来模拟专家的推理过程,根据输入的数据和规则推导出结论。
3. **用户接口(User Interface)**:允许用户与专家系统交互,输入问题和数据,获取专家系统的解答。
4. **解释器(Explanation Facility)**:向用户提供推理过程的解释,帮助用户理解专家系统是如何得出结论的。
5. **知识获取组件(Knowledge Acquisition Component)**:用于从专家那里获取新的知识,以便不断更新和维护知识库。
专家系统在医疗诊断、金融分析、工程设计、法律咨询等领域有广泛的应用。它们能够提供快速、一致和可重复的决策支持,但同时也依赖于知识库的质量和完整性。随着人工智能技术的发展,专家系统也在不断进化,比如通过机器学习技术来自动更新和优化知识库。
二、动物识别专家系统实现
如图1所示,在动物识别专家系统用户交互界面,用户可以通过按钮实现添加新事实(动物特征)、添加规则、删除规则、选择事实、删除选中事实(用户在已选中的事实中删除自己不想要加入的事实)、刷新、推理。
产生式系统是一种基于规则的人工智能系统,它由知识库、综合数据库和推理机三部分组成。在动物识别专家系统中:
知识库:存储了一系列关于动物特征和动物种类之间关系的产生式规则。例如 “如果动物有毛发,则是哺乳动物”“如果动物有羽毛,则是鸟” 等规则。这些规则是通过对动物特征的观察和分类总结得出的。
综合数据库:在本实验中,综合数据库由两部分组成。一部分是枚举类型Features中定义的各种动物特征,另一部分是用户选择的事实列表factsList和推理结果列表。用户可以通过交互界面选择动物的特征作为已知事实添加到综合数据库中。
推理机:根据用户提供的事实,运用知识库中的规则进行推理,得出动物的种类。在本实验中,推理机通过遍历规则集,检查规则的前提条件(动物特征)是否在用户选择的事实列表中都存在。如果存在,则将规则的结论(动物种类)添加到综合数据库中,并在推理过程列表中记录应用的规则。推理过程会不断循环,直到没有新的结论可以推出为止。
设计了合理的交互界面,包括事实列表框、规则集列表框、推理过程列表框、推理结果列表框以及各种操作按钮。用户可以通过选择事实、添加新事实、添加新规则、删除规则等操作与系统进行交互。
利用文件存储功能,将用户选择的事实和规则分别存储在 “facts.txt” 和 “rules.txt” 文件中。在程序启动时,从文件中加载事实和规则,以便在不同的运行实例中保留用户的操作记录。当用户进行添加、修改或删除操作时,及时将更新后的事实和规则保存到文件中。
下面是对专家系统核心代码的介绍。
核心代码
图2 初始动物特征
使用枚举类型Features定义了各种动物特征,如 “有毛发”“有羽毛”“会飞” 等。这样可以方便地在程序中表示和处理动物特征。
图3 初始规则
规则采用列表元组的形式存储在rules列表中,每个元组包含一个特征列表和一个字符串结果,表示如果动物具有这些特征,则属于该结果所代表的动物种类。例如(new List<Features> { Features.有毛发 }, "哺乳动物")表示如果动物有毛发,则是哺乳动物。
图4 按下“添加新事实”按钮函数
图5 保存事实(动物特征)到事实文件
当用户点击触发addfact_Click_1事件时(按下“添加新事实”按钮),弹出添加新事实窗体用于让用户输入新的事实内容。若用户在添加新事实窗体中点击了 “确定” 按钮,从添加新事实窗体中获取用户输入的新事实内容,并存储在newFact字符串变量中。然后检查newFact是否为空字符串或仅包含空白字符。如果不是,说明用户输入了有效的新事实内容。将新事实添加到factsList列表中,这个列表用于存储所有的事实。同时,将新事实也添加到showfacts列表框中,以便在用户界面上显示新添加的事实。最后调用SaveFactsToFile方法将更新后的事实列表保存到文件 “facts.txt” 中。这样可以确保在程序关闭后,新添加的事实不会丢失,下次启动程序时可以从文件中重新加载这些事实。
图6 添加新规则按钮函数
图7 保存规则文件
图8 显示到规则集控件
添加新规则的整体流程:
1.用户交互行为:
当用户点击添加新规则按钮时,弹出添加规则窗口用于让用户输入新的规则内容。检查用户是否在该窗体中点击了确定按钮。
2.用户点击了确定按钮进行添加新规则,处理新规则内容:
如果用户点击了确定按钮,从添加规则窗体中获取用户输入的新规则,并存储在newRule字符串变量中检查newRule是否为空字符串或仅包含空白字符。如果不是,说明用户输入了有效的新规则内容。然后将新规则按照 “->” 进行分割,得到一个包含两部分内容的字符串数组parts。如果分割后正好是两部分,说明规则格式正确。
(1)对第一部分(特征部分)进行处理:
去除两端的空白字符后,再按照逗号进行分割,得到一个特征名称的数组featureNames。
(2)创建一个名为features的列表,用于存储解析后的特征枚举值。
遍历featureNames数组中的每个特征名称,尝试将其解析为枚举类型Features。如果解析成功,将解析后的枚举值添加到features列表中。
(3)对第二部分(结果部分)进行处理:
去除两端的空白字符,得到规则的结果部分,并存储在result字符串变量中。
将解析后的特征列表和结果添加到rules列表中。这个列表的类型是List<(List<Features>, string)>,即包含一个特征列表和一个字符串结果的元组列表。
(4)将用户输入的完整新规则添加到showrules列表框中,以便在用户界面上显示新添加的规则。
3.保存新规则并更新显示
(1)调用SaveRulesToFile方法将新规则保存到文件 “rules.txt” 中。
首先清空文件内容,以确保不会有旧的规则残留。然后创建一个新的ruleLines列表,用于存储格式化后的规则文本。遍历rules列表中的每个规则,将特征列表和结果组合成一个字符串,并添加到ruleLines列表中。最后将ruleLines列表中的内容写入到文件 “rules.txt” 中。
(2)调用PopulateRuleListBox方法更新规则集列表框的显示。
先清空showrules列表框中的内容。遍历rules列表中的每个规则,将特征列表和结果组合成一个字符串,并添加到showrules列表框中。这样可以确保用户界面上显示的规则集始终与内存中的规则列表保持一致。
图9 删除规则按钮函数
当用户点击 “删除规则” 按钮时,首先检查showrules列表框中是否有选中的项。如果有选中的项,说明用户想要删除该规则。获取选中规则的文本内容,并存储在selectedRuleText字符串变量中。将选中的规则文本按照 “->” 进行分割,得到一个包含两部分内容的字符串数组parts。对第一部分(特征部分)进行处理:去除两端的空白字符后,再按照逗号进行分割,得到一个特征名称的数组featureNames。创建一个名为features的列表,用于存储解析后的特征枚举值。遍历featureNames数组中的每个特征名称,尝试将其解析为枚举类型Features。如果解析成功,将解析后的枚举值添加到features列表中。对第二部分(结果部分)进行处理:去除两端的空白字符,得到规则的结果部分,并存储在result字符串变量中。
使用FirstOrDefault方法在rules列表中查找满足条件的规则。条件是规则的特征列表与解析得到的features列表完全相等,并且规则的结果部分与解析得到的result字符串相等。如果找到了满足条件的规则,将其存储在ruleToRemove变量中。如果ruleToRemove不为默认值,说明找到了要删除的规则。从rules列表中删除找到的规则。调用SaveRulesToFile方法将更新后的规则列表保存到文件中,以确保删除操作的结果被持久化保存。调用PopulateRuleListBox方法更新规则集列表框的显示,使其与内存中的规则列表保持一致。
图10 选择事实按钮函数
如果有选中的事实,将该选中的事实添加到showselectfacts列表框中。这样用户可以通过这个操作将想要的事实从所有可用事实列表中挑选出来。
图11 删除事实按钮函数
如果有选中的事实,从showselectfacts列表框中移除该选中的事实。这样用户可以通过这个操作从已选择的事实列表中删除不需要的事实,以便调整用于推理的事实集合。
图12 刷新按钮函数
将showselectfacts列表框、result列表框、Reasoning_process列表框中的所有项清空。这一步的目的是为了重新开始选择或者清除错误的选择。
图13 开始推理按钮函数
首先清空result和Reasoning_process两个列表框,分别用于存储推理结果和推理过程。这样做是为了确保在新的推理过程中不会有旧的结果和过程干扰。
创建一个新的List<string>类型的列表selectedFacts,用于存储用户从showselectfacts列表框中选择的事实。通过遍历showselectfacts.Items,将每个项转换为字符串并添加到selectedFacts列表中。
设置一个标志变量foundNewResult为true,用于控制循环的执行。这个变量表示在每次循环中是否找到了新的推理结果。
进入一个while循环,只要foundNewResult为true,循环就会继续执行。在每次循环开始时,将foundNewResult设置为false,以便在本次循环中检测是否有新的结果产生。
遍历规则列表rules中的每一个规则。对于每个规则,进行以下操作:
首先设置一个标志变量allFeaturesPresent为true,用于表示当前规则的所有特征是否都在选中的事实列表中。
遍历当前规则的特征列表rule.Item1中的每个特征。如果在选中的事实列表selectedFacts中找不到当前特征,就将allFeaturesPresent设置为false,并跳出当前特征的循环。
如果allFeaturesPresent为true,说明当前规则的所有特征都在选中的事实列表中。此时,获取当前规则的结果resultAnimal(即动物种类)。如果这个结果不在选中的事实列表中,就进行以下操作:
将foundNewResult设置为true,表示找到了新的结果。
将结果动物种类添加到selectedFacts列表中,以便在后续的推理中使用。
将结果动物种类添加到result列表框中,显示在用户界面上。
将应用的规则添加到Reasoning_process列表框中,显示推理过程。
这个推理过程会不断循环,直到在一次循环中没有找到新的结果为止。这样就可以根据用户选择的事实和规则,逐步推导出动物的种类。
运行结果
图14 动物识别系统界面
图15 在事实框中选择动物特征
在事实框中点击要选择的事实(动物特征),然后点击选择事实按钮,可以看到选择的事实被添加到了已选择事实中。
图16 在已选中事实框中删除的动物特征
如果选中某个不打算选中的事实可以在已选中事实框中选中该事实,再点击删除选中事实即可完成删除。
图17 推理结果
点击开始推理按钮,系统根据已选择的事实按照规则集的规则推理出动物结果,并显示出推理用到的规则。
图18 刷新
刷新后已选中事实框、推理过程框、推理结果框被清空,可以进行再重新进行推理。
图19 规则集中选中要删除的规则进行删除
图20删除后的规则集
如果想删除某个规则,可以在规则集中选中规则,然后点击删除规则即可完成删除。
图21添加新事实
点击添加新事实按钮,直接输入要添加的事实点击确定即可添加,点击取消则不进行添加操作。
图22新事实添加到事实框中
图23事实添加到存储新添加事实的文件中
Facts.txt文件用来存储新添加的事实,目的是为了实现新添加的事实在系统下次运行的时候仍然存在。系统会从这个文件中加载新的事实到事实框中。
图24 添加新规则
点击添加新规则按钮,直接输入要添加的规则点击确定即可添加,点击取消则不进行添加操作。
图25新规则添加到规则集框中
图26 规则添加到存储规则文件中
Rules.txt文件存储所有的规则,新添加的规则也要显示到其中。目的是为了实现新添加的规则在系统下次运行的时候仍然存在。
核心代码
private void LoadFactsFromFile()//加载事实文件
{
if (File.Exists("facts.txt"))
{
string[] lines = File.ReadAllLines("facts.txt");
foreach (var line in lines)
{
factsList.Add(line);
}
}
}
//初始化规则列表,规则 “如果动物有毛发,则是哺乳动物” 被表示为(new List<Features> { Features.有毛发 }, "哺乳动物")
private void InitializeRules()
{
rules.Add((new List<Features> { Features.有毛发 }, "哺乳动物"));
rules.Add((new List<Features> { Features.有羽毛 }, "鸟"));
rules.Add((new List<Features> { Features.会飞, Features.会下蛋 }, "鸟"));
rules.Add((new List<Features> { Features.吃肉 }, "食肉动物"));
rules.Add((new List<Features> { Features.有犀利牙齿, Features.有爪, Features.眼盯前方 }, "食肉动物"));
rules.Add((GetFeaturesFromCategory("哺乳动物").Concat(new List<Features> { Features.有蹄 }).ToList(), "有蹄类动物"));
rules.Add((GetFeaturesFromCategory("哺乳动物").Concat(new List<Features> { Features.嚼反刍 }).ToList(), "有蹄类动物"));
rules.Add((GetFeaturesFromCategory("哺乳动物").Concat(GetFeaturesFromCategory("食肉动物")).Concat(new List<Features> { Features.黄褐色, Features.身上有暗斑点 }).ToList(), "豹"));
rules.Add((GetFeaturesFromCategory("哺乳动物").Concat(GetFeaturesFromCategory("食肉动物")).Concat(new List<Features> { Features.黄褐色, Features.身上有黑色条纹 }).ToList(), "虎"));
rules.Add((GetFeaturesFromCategory("有蹄类动物").Concat(new List<Features> { Features.有长脖子, Features.有长腿, Features.身上有暗斑点 }).ToList(), "长颈鹿"));
rules.Add((GetFeaturesFromCategory("有蹄类动物").Concat(new List<Features> { Features.身上有黑色条纹 }).ToList(), "斑马"));
rules.Add((new List<Features> { Features.有羽毛 }.Concat(new List<Features> { Features.不会飞, Features.有长脖子, Features.有长腿, Features.有黑白二色 }).ToList(), "鸵鸟"));
rules.Add((new List<Features> { Features.有羽毛 }.Concat(new List<Features> { Features.不会飞, Features.会游泳, Features.有黑白二色 }).ToList(), "企鹅"));
rules.Add((new List<Features> { Features.有羽毛 }.Concat(new List<Features> { Features.善飞, Features.会下蛋 }).ToList(), "信天翁"));
}
List<Features> GetFeaturesFromCategory(string category)
{
switch (category)
{
case "哺乳动物":
return new List<Features> { Features.有毛发 };
case "食肉动物":
return new List<Features> { Features.吃肉, Features.有犀利牙齿, Features.有爪, Features.眼盯前方 };
case "有蹄类动物":
return new List<Features> { Features.有蹄, Features.嚼反刍 };
case "豹":
return new List<Features> { Features.有毛发, Features.吃肉, Features.有犀利牙齿, Features.有爪, Features.眼盯前方, Features.黄褐色, Features.身上有暗斑点 };
case "虎":
return new List<Features> { Features.有毛发, Features.吃肉, Features.有犀利牙齿, Features.有爪, Features.眼盯前方, Features.黄褐色, Features.身上有黑色条纹 };
case "长颈鹿":
return new List<Features> { Features.有蹄, Features.嚼反刍, Features.有长脖子, Features.有长腿, Features.身上有暗斑点 };
case "斑马":
return new List<Features> { Features.有蹄, Features.嚼反刍, Features.身上有黑色条纹 };
case "鸵鸟":
return new List<Features> { Features.有羽毛, Features.不会飞, Features.有长脖子, Features.有长腿, Features.有黑白二色 };
case "企鹅":
return new List<Features> { Features.有羽毛, Features.不会飞, Features.会游泳, Features.有黑白二色 };
case "信天翁":
return new List<Features> { Features.有羽毛, Features.善飞, Features.会下蛋 };
case "鸟":
return new List<Features> { Features.会飞, Features.会下蛋 };
default:
return new List<Features>();
}
}
private void PopulateFactListBox()//显示到事实
{
showfacts.Items.Clear();
foreach (Features feature in Enum.GetValues(typeof(Features)))
{
showfacts.Items.Add(feature.ToString());
}
foreach (var fact in factsList)
{
showfacts.Items.Add(fact);
}
}
private void PopulateRuleListBox()//显示到规则集
{
showrules.Items.Clear();
foreach (var rule in rules)
{
string ruleText = string.Join(", ", rule.Item1.Select(f => f.ToString())) + " -> " + rule.Item2;
showrules.Items.Add(ruleText);
}
}
private void deletefacts_Click(object sender, EventArgs e)//删除事实按钮
{
if (showselectfacts.SelectedItem != null)
{
showselectfacts.Items.Remove(showselectfacts.SelectedItem);
}
}
private void addrule_Click(object sender, EventArgs e)//添加新规则
{
add_rule addRuleForm = new add_rule();
if (addRuleForm.ShowDialog() == DialogResult.OK)//点击确定
{
string newRule = addRuleForm.NewRule;//从addRuleForm窗体中获取用户输入的新规则,并将其存储在newRule字符串变量中
if (!string.IsNullOrWhiteSpace(newRule))//如果不为空
{
//将newRule按照 “->” 进行分割。分割后的结果存储在parts数组中
string[] parts = newRule.Split(new string[] { "->" }, StringSplitOptions.None);
if (parts.Length == 2)//正好分割成两部分
{
//提取特征部分(分割后的第一个元素),去除两端的空白字符,然后再按照逗号进行分割,得到一个特征名称的数组
string[] featureNames = parts[0].Trim().Split(',');
//创建一个名为features的列表,用于存储解析后的特征枚举值。
List<Features> features = new List<Features>();
//遍历特征名称数组中的每个特征名称
foreach (string featureName in featureNames)
{
//尝试将每个特征名称解析为枚举类型Features。如果解析成功,将解析后的枚举值添加到features列表中。
if (Enum.TryParse(featureName.Trim(), out Features feature))
{
features.Add(feature);
}
}
//提取结果部分(分割后的第二个元素),去除两端的空白字符,得到规则的结果部分。
string result = parts[1].Trim();
//将解析后的特征列表和结果添加到rules列表中。rules列表的类型是List<(List<Features>, string)>,即包含一个特征列表和一个字符串结果的元组列表。
rules.Add((features, result));
//将用户输入的完整新规则添加到showrules列表框中,以便在用户界面上显示新添加的规则。
showrules.Items.Add(newRule);
// 保存新规则到文件
SaveRulesToFile();
PopulateRuleListBox(); // 添加这行,确保添加规则后更新列表框显示
}
}
}
}
private void LoadRulesFromFile()//从文件加载规则
{
if (File.Exists("rules.txt"))
{
string[] lines = File.ReadAllLines("rules.txt");
foreach (var line in lines)
{
string[] parts = line.Split(new string[] { "->" }, StringSplitOptions.None);
if (parts.Length == 2)
{
string[] featureNames = parts[0].Trim().Split(',');
List<Features> convertedFeatures = new List<Features>();
foreach (string featureName in featureNames)
{
if (Enum.TryParse(featureName.Trim(), out Features feature))
{
convertedFeatures.Add(feature);
}
}
string result = parts[1].Trim();
rules.Add((convertedFeatures, result));
}
}
PopulateRuleListBox(); // 添加这行,确保加载规则后更新列表框显示
}
}
public void SaveRulesToFile()
{
// 先清空文件
File.WriteAllText("rules.txt", string.Empty);
List<string> ruleLines = new List<string>();
foreach (var rule in rules)
{
string ruleText = string.Join(", ", rule.Item1) + " -> " + rule.Item2;
ruleLines.Add(ruleText);
}
File.WriteAllLines("rules.txt", ruleLines);
}
private void addfact_Click_1(object sender, EventArgs e)
{
add_fact addFactForm = new add_fact();
if (addFactForm.ShowDialog() == DialogResult.OK)
{
string newFact = addFactForm.NewFact;
if (!string.IsNullOrWhiteSpace(newFact))
{
factsList.Add(newFact);
showfacts.Items.Add(newFact);
SaveFactsToFile();
}
}
}
private void beginwork_Click(object sender, EventArgs e)//开始推理按钮
{
// 清空结果和推理过程列表框
result.Items.Clear();
Reasoning_process.Items.Clear();
// 获取选中的事实列表
List<string> selectedFacts = new List<string>();
foreach (var item in showselectfacts.Items)
{
selectedFacts.Add(item.ToString());
}
// 推理过程
List<string> possibleAnimals = new List<string>();
foreach (var rule in rules)
{
bool allFeaturesPresent = true;
foreach (var feature in rule.Item1)
{
if (!selectedFacts.Contains(feature.ToString()))
{
allFeaturesPresent = false;
break;
}
}
if (allFeaturesPresent)
{
string resultAnimal = rule.Item2;
if (!possibleAnimals.Contains(resultAnimal))
{
possibleAnimals.Add(resultAnimal);
Reasoning_process.Items.Add($"应用规则:{string.Join(", ", rule.Item1)} -> {resultAnimal}");
}
}
}
// 遍历所有规则,对可能的结果进行更全面的判断
foreach (var rule in rules)
{
if (rule.Item2 != "哺乳动物" && rule.Item2 != "食肉动物" && rule.Item2 != "有蹄类动物" && rule.Item2 != "鸟")
{
bool allFeaturesPresent = true;
foreach (var feature in rule.Item1)
{
if (!selectedFacts.Contains(feature.ToString()))
{
allFeaturesPresent = false;
break;
}
}
if (allFeaturesPresent)
{
possibleAnimals.Add(rule.Item2);
}
}
}
// 将可能的动物添加到结果列表框
foreach (var animal in possibleAnimals)
{
result.Items.Add(animal);
}
}
private void deleterule_Click(object sender, EventArgs e)
{
if (showrules.SelectedItem != null)
{
string selectedRuleText = showrules.SelectedItem.ToString();
string[] parts = selectedRuleText.Split(new string[] { "->" }, StringSplitOptions.None);
string[] featureNames = parts[0].Trim().Split(',');
List<Features> features = new List<Features>();
foreach (string featureName in featureNames)
{
if (Enum.TryParse(featureName.Trim(), out Features feature))
{
features.Add(feature);
}
}
string result = parts[1].Trim();
var ruleToRemove = rules.FirstOrDefault(r => Enumerable.SequenceEqual(r.Item1, features) && r.Item2 == result);
if (ruleToRemove != default((List<Features>, string)))
{
rules.Remove(ruleToRemove);
SaveRulesToFile();
PopulateRuleListBox();
}
}
} |