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);
}
}