實驗:編碼實現正向推理和逆向推理過程


1 實驗概述

1.1 實驗目的

熟悉一階謂詞邏輯和產生式表示法,掌握產生式系統的運行機制,以及基於規則推理的基本方法。

1.2 實驗內容

設計並編程實現一個小型產生式系統(如分類、診斷等類型)

1.3 實驗要求

  1. 具體應用領域自選,具體系統名稱自選
  2. 用一階謂詞邏輯和產生式規則作為知識表示,利用產生式系統實驗程序,建立知識庫,分別運行正向、逆向推理

1.4 實驗報告要求

  1. 系統設計,包括系統名稱和系統謂詞,給出謂詞名及其含義
  2. 編輯知識庫,通過輸入規則或修改規則等,建立規則庫
  3. 建立事實庫(綜合數據庫),輸入多條事實或結論
  4. 運行推理,包括正向、逆向,給出相應的推理過程、事實區、規則區
  5. 總結實驗心得

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

image

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

image

5 完整代碼文件

點擊下載


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM