1 实验概述
1.1 实验目的
熟悉一阶谓词逻辑和产生式表示法,掌握产生式系统的运行机制,以及基于规则推理的基本方法。
1.2 实验内容
设计并编程实现一个小型产生式系统(如分类、诊断等类型)
1.3 实验要求
- 具体应用领域自选,具体系统名称自选
- 用一阶谓词逻辑和产生式规则作为知识表示,利用产生式系统实验程序,建立知识库,分别运行正向、逆向推理
1.4 实验报告要求
- 系统设计,包括系统名称和系统谓词,给出谓词名及其含义
- 编辑知识库,通过输入规则或修改规则等,建立规则库
- 建立事实库(综合数据库),输入多条事实或结论
- 运行推理,包括正向、逆向,给出相应的推理过程、事实区、规则区
- 总结实验心得
2 心路历程
什么系统?
简单起见,动物识别系统,至于规则,书上都有,共计15条
知识库
在进行推理时,会要求输入初始条件,以及目标结果,会从这里面进行选择;
编辑规则、创建规则时,前件、后件皆从这里进行选择搭配
public enum KB {
// 外观特征、自身属性
HasFur("有毛发"),
HasBosom("有奶"),
HasFeather("有羽毛"),
HasLaniarii("有犬齿"),
HasClaw("有爪子"),
HasHoof("有蹄"),
HasDarkSpots("有暗斑点"),
HasDarkFringe("有暗条纹"),
HasBlackStripe("有黑色条纹"),
HasLongLeg("有长腿"),
HasLongNeck("有长脖子"),
SkinTawny("黄褐色"),
SkinBlackWhite("黑白二色"),
// 具备的能力、行为特征
GoodFly("擅飞"),
CanFly("会飞"),
NoFly("不会飞"),
CanEgg("会下蛋"),
CanSwim("会游泳"),
EatMeat("食肉"),
StareFront("眼盯前方"),
// 下定义
IsChewingCud("是咀嚼反刍动物"),
IsPredator("是食肉动物"),
IsMammal("是哺乳动物"),
IsBird("是鸟"),
IsUngulate("是有蹄类动物"),
IsLeopard("是金钱豹"),
IsTiger("是虎"),
IsGiraffe("是长颈鹿"),
IsZebra("是斑马"),
IsOstrich("是鸵鸟"),
IsPenguin("是企鹅"),
IsAlbatross("是信天翁");
private String knowledge;
Noun(String knowledge) {
this.knowledge = knowledge;
}
public String getKnowledge() {
return knowledge;
}
public void setKnowledge(String knowledge) {
this.knowledge = knowledge;
}
}
事实库(综合数据库)采用什么数据结构?
事实库用来存储推理过程中得到的事实,需要不断判断事实库中是否已经存在目标,那么采用map或set或许是个不错的选择,方便判断某个知识
是否存在于事实库中
@Data
@NoArgsConstructor
public class DB{
// 事实库
Set<KB> truthSet = new HashSet<>();
}
规则库(知识库)采用什么数据结构?
规则库,意味着是规则的集合体。
规则:if <前件> then <后件>
,易知,这个前件可以很复杂,可能是and,or等连接词的混搭。当前暂不考虑前件中包含or的情况,故规则
可以进行如下的设计:
class Rule {
// 前件,里面每个元素之间的关系都是and关系
// List中所有的条件都是and连接
private List<KB> conditions;
// 后件,也就是结论
private Noun conclusion;
// 该规则编号(这里用不上)
private String no;
}
规则库,即为规则
的集合。在推理的过程中,当规则A被使用后,应该将其标记为被访问
,防止后面重复使用,浪费时间
@Data
@NoArgsConstructor
public class RB {
// 规则库中的诸多规则
// key: 规则
// value: 是否被访问
private Map<Rule, Boolean> ruleMap = new HashMap();
// 链式调用,给规则库添加规则
public void addRuleToRB(Rule rule) {
ruleMap.put(rule, false);
}
}
3 正向推理
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ControlSystem {
private DB db; // 综合数据库
private RB rb; // 规则库
// 正向推理,target是推理的目标,推理的终点
public String forwardInfer(KB target) {
Set<KB> truthSet = db.getTruthSet(); // 事实库
Map<Rule, Boolean> ruleMap = rb.getRuleMap(); // 规则库
// 当事实库中包含目标值,说明已经推出来了,结束循环
while (!truthSet.contains(target)) {
List<Rule> ruleList;
do {
// 形成可用知识集
ruleList = getUsefulRules();
// 如果可用知识集为空,说明推不下去了,只能告知用户补充事实,一旦无法增加新事实,便推理失败,终止
if (ruleList.isEmpty() && !supplementFacts()) {
return "forward inference failed";
}
} while (ruleList.isEmpty());
// ruleList,里面有很多尚未被使用的可用规则,但存在一种情况:里面所有的规则所推出来的结果都不是新事实,换句话说,该规则的使用前使用后,没有丝毫差别,无用功
// 当出现这种情况时,也是需要用户去增加新事实的,企图达成某条规则的前件,能够得到一些新结论加入DB中
boolean flag = false;
for (Rule rule : ruleList) {
boolean addRet = truthSet.add(rule.getConclusion());
// 添加成功,说明该规则推出来的是新事实
if (addRet) {
// 标记该规则已经用过
ruleMap.put(rule, true);
flag = true;
}
}
// flag = false,说明之前的ruleList没有一条是有用的,需要增加一些新事实
if (!flag && !supplementFacts()) {
return "forward inference failed";
}
}
return "forward inference success";
}
// 补充事实
public boolean supplementFacts() {
PrintUtil.divide(); // 这是分割线,可有可无
System.out.println("已知事实:");
db.getTruthSet().forEach(v -> System.out.print(v.getKnowledge() + "、"));
System.out.println("\n请勿键入已经存在的事实,规避无用操作");
// 控制台,让用户输入知识库中知识的编号,作为新事实加入DB中
Set<KB> newTruth = InputUtil.inputTruth(KB.values());
// 说明没有新事实加入
if (newTruth.isEmpty()) return false;
return db.getTruthSet().addAll(newTruth);
}
// 事实库,与规则库进行对照,看看有哪些规则的前件满足,可以使用
public List<Rule> getUsefulRules() {
// 保存当前可用规则(前件所需条件都在事实库中出现,即为可用规则)
List<Rule> ret = new ArrayList<>();
// 规则库中的所有规则
Map<Rule, Boolean> rules = rb.getRuleMap();
// 已知的知识
Set<KB> truthSet = db.getTruthSet();
// 遍历rb中每条规则的前件,判断该前件在db中是否都出现
rules.forEach((rule, visited) -> {
if (!visited) {
List<KB> conditions = rule.getConditions();
// flag为true,表示所有条件都在db中出现过
boolean flag = true;
for (KB condition : conditions) {
if (!truthSet.contains(condition)) {
flag = false;
break;
}
}
if (flag) ret.add(rule);
}
});
return ret;
}
}
4 逆向推理
与正向推理相反。
比如:①A推B,②B推C,③C推D,三个规则,且已知A,想知道这三个规则能不能推出D
逆向推理:
- 通过③,我知道:想要推出D,只需要推出C即可
- 通过②,我知道:想要推出C,只需要推出B即可
- 通过①,我知道:想要推出B,只需要推出A即可
- 已知A,故可以推出D
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ControlSystem {
private DB db; // 综合数据库
private RB rb; // 规则库
// 逆向推理
// initialEvidence:初始证据,可以为null
// target:上面示例中的D
public String backwardInfer(KB initialEvidence, KB target) {
// 初始化综合数据库、假设集
Set<KB> dbSet = new HashSet<>();
// 假设集使用队列实现
Queue<KB> assumeQueue = new ArrayDeque<>();
if (initialEvidence != null)
dbSet.add(initialEvidence);
assumeQueue.add(target);
while (!assumeQueue.isEmpty()) {
// 取出一个假设
KB assume = assumeQueue.poll();
// 该假设已经被证实是事实,那么继续取假设
if (dbSet.contains(assume)) continue;
// 假设尚未被证明,需要到rb中找,看看有哪些规则是能推出这个假设的
List<Rule> rules = getUsefulRules(assume);
// 说明该假设不能被rb中的知识推导出,需要用户去判断该假设成不成立
if (rules.isEmpty()) {
boolean res = askUser(assume);
// 假设不成立
if (!res) {
return "backward inference failed";
}
// 假设成立,作为已知事实加入db中
dbSet.add(assume);
} else {
Rule r = rules.get(0);
// 从rules中获取一个规则,将该规则的前件放入假设集中
assumeQueue.addAll(r.getConditions());
// 标记该规则已经被用过了
rb.getRuleMap().put(r, true);
}
}
return "backward inference success";
}
// 从rb中获取所有能够推导出target的规则
public List<Rule> getUsefulRules(KB target) {
List<Rule> ret = new ArrayList<>();
rb.getRuleMap().forEach((rule, visited) -> {
if (!visited && rule.getConclusion().equals(target)) {
ret.add(rule);
}
});
return ret;
}
// 询问用户,这个假设是否为事实
public boolean askUser(KB assume) {
System.out.println("请问:\"" + assume.getKnowledge() + "\"是否是事实?(Y/N)");
Scanner sc = new Scanner(new BufferedInputStream(System.in));
String s = sc.nextLine();
return "Y".equalsIgnoreCase(s);
}
}