实验:编码实现正向推理和逆向推理过程


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