人工智慧 - 採用 JavaScript 實作

目錄 | 下載 陳鍾誠金門大學

實作:謂詞邏輯的推論引擎

接著、讓我們將上述的「布林推論引擎」擴充,成為一個可以包含「布林函數」的「謂詞邏輯」推論引擎。

程式碼:謂詞推理引擎

檔案:pkb.js (Predicate Knowledge Base 的簡寫)

var ml = require("./myLib");

var Predicate = function() {}

Predicate.prototype.load = function(str) {
  var m = str.match(/^([^\(]*)\(([^\)]*)\)$/);
  this.name   = (m[1]==null)?"":m[1].trim();
  this.params = (m[2]==null)?"":m[2].trim().split(/[,]/);
  return this;
}

Predicate.prototype.toString = function() {
  return ml.format("%s%j", this.name, this.params).replace(/\"/gi, "").replace(/\[/gi, "(").replace(/\]/gi, ")");
}

Predicate.prototype.unify = function(fact) {
  var map = {};
  if (this.name != fact.name || this.params.length != fact.params.length) 
    return null;
  for (var i=0; i<this.params.length; i++) {
    var p  = this.params[i];
    var fp = fact.params[i];
    if (map[p] == null) { // 參數 p 沒有 bind,所以就 bind 上去
      if (p.match(/^[a-z]+ ![](../timg/b66e6870f9c0.jpg) /)!=null)
        map[p] = fp;
    } else { // 參數 p 已 bind,檢查是否有衝突。
      if (map[p] == fp)
        map[p] = fp; // 沒衝突,加入。
      else
        return null; // 有衝突,傳回 null;
    }
  }
  return map;
}

// 若每個變數都填入了,那麼就是滿足了 (satisfy)
Predicate.prototype.satisfy = function(map) {
  for (var i in this.params) {
    var p = this.params[i];
    if (map[p] == null)
      return false;
  }
  return true;
}

Predicate.prototype.mapping = function(map) {
  var term = new Predicate();
  term.name = this.name;
  term.params = [];
  for (var i in this.params) {
    var p = this.params[i];
    term.params.push(map[p]);
  }
  return term;
}

var Rule = function() {}

Rule.prototype.clone = function() {
  var r=new Rule();
  r.head = this.head;
  r.terms = this.terms;
  r.map = this.map;
  return r;
}

Rule.prototype.load = function(line) {
  var m = line.match(/^([^<=]*)(<=([^\{ ![](../timg/0568953a55be.jpg) /);
  var head = (m[1]==null)?"":m[1].trim();
  var terms= (m[3]==null)?"":m[3].trim().split(/[&]+/);
  this.head = new Predicate().load(head);
  this.terms = [];
  for (i=0; i<terms.length; i++) {
    this.terms.push(new Predicate().load(terms[i]));
  }
  this.map = {};
  return this;
}

Rule.prototype.toString = function() {
  return ml.format("%s<=%s%j", this.head, this.terms.join("&"), this.map);
}

Rule.prototype.resolve = function(fact) {
  var rmap = this.map;
//  ml.log("resolve:rule=%s, fact=%s", this, fact);
  for (var i in this.terms) {
    var term = this.terms[i];
    var tmap = term.unify(fact);
    if (tmap == null) continue;
//    ml.log("unify:%s;%s=%j", term, fact, tmap);
    var isConflict = false;
    for (var mi in tmap) {
      if (rmap[mi]!=null && rmap[mi] != tmap[mi])
        isConflict = true;
    }
    if (!isConflict) {
//      ml.log(" return map=%j", tmap);
      return tmap;
    }
  }
  return null;
}

var KB = function() {
  this.rules = [];
  this.facts = [];
  this.ruleMap = {};
  this.factMap = {};
  this.resolveMap = {};
}

KB.prototype.load = function(code) {
  code = code.replace(/\s/gi, "");
  var lines = code.split(/[\.]+ ?/);
  ml.log("%j", lines);
  for (var i in lines) {
    var line = lines[i].trim();
    if (line.length == 0) continue;
    if (line.indexOf("<=")>=0) {
      this.addRule(new Rule().load(line));
    } else
      this.addFact(new Predicate().load(line));
  }
  this.dump();
}

KB.prototype.dump = function() {
  ml.log("=====facts========");
  ml.log(this.facts.join("\n"));
  ml.log("========rules=======");
  ml.log(this.rules.join("\n"));
// ml.log("========resolveMap=======\n");
// ml.log(Object.keys(this.resolveMap).join("|"));
}
  
KB.prototype.addFact = function(fact) {
  if (this.factMap[fact.toString()] == null) {
    ml.log("addFact:%s", fact);
    this.facts.push(fact);
    this.factMap[fact.toString()] = fact;
    return true;
  } else 
    return false;
}

KB.prototype.addRule = function(rule) {
  if (this.ruleMap[rule.toString()] == null) {
    ml.log("addRule:%s", rule.toString());
    this.rules.push(rule);
    this.ruleMap[rule.toString()] = rule;
    return true;
  } else
    return false;
}

KB.prototype.genNew = function(rule, fact) {
  var fmap = rule.resolve(fact);
  if (fmap == null) return null;
  var rmap = ml.merge(rule.map, fmap);
  if (rule.head.satisfy(rmap)) {
    var newFact = rule.head.mapping(rmap);
    if (this.addFact(newFact))
      return newFact;
  } else {
    if (!ml.isEmpty(fmap)) {
    var newRule = rule.clone();
    newRule.map  = rmap
    if (this.addRule(newRule))
      return newRule;
    }
  }
  return null;
}

KB.prototype.forwardChaining = function() {
  do {
    ml.log("======forwardChaining============");
    var anyNew = false;
    for (var fi=0; fi < this.facts.length; fi++) {
      var fact=this.facts[fi];
      for (var ri in this.rules) {
        var rule = this.rules[ri];
        if (this.resolveMap[fi+","+ri] == null) {
          var newObj = this.genNew(rule, fact);
          if (newObj != null) {
            ml.log("  %s;%s\n", rule, fact);
            anyNew = true;
          }
        }
        else
          this.resolveMap[fi+","+ri]=true;
      }
    }
  } while (anyNew);
  this.dump();
}

KB.prototype.test = function() {
  var fxy = new Predicate().load("father(x,y)");
  var fjj = new Predicate().load("father(John,Johnson)");
  var rp = new Rule().load("parent(x,y)<=father(x,y)");
  var ra = new Rule().load("ancestor(x,z)<=ancestor(x,y)&parent(y,z)");
  var pgj = new Predicate().load("ancestor(George,John)");
  var pjj = new Predicate().load("parent(John,Johnson)");
  ra.map = ra.resolve(pgj);
  ra.map = ml.merge(ra.map, ra.resolve(pjj));
  ml.log("ra=%s", ra);
  ml.log(" satisfy=%d", ra.head.satisfy(ra.map));
}

module.exports = KB;

前向推論主程式:pkbReason.js

檔案:pkbReason.js

var fs = require('fs'); // 引用檔案物件
var kb = require('./pkb');

var kb1 = new kb();
var code = fs.readFileSync(process.argv[2], "utf8").replace(/\n/gi, ""); // 讀取檔案
kb1.load(code);
kb1.forwardChaining();

推論規則範例

規則檔:family.pkb

parent(x,y)   <= father(x,y).
parent(x,y)   <= mother(x,y).
ancestor(x,y) <= parent(x,y).
ancestor(x,z) <= ancestor(x,y) & parent(y,z).

father(John, Johnson).
mother(Mary, Johnson).
father(George, John).
father(John, Jake).

執行結果

C:\Dropbox\Public\web\ai\code\PKB>node pkbReason family.pkb
["parent(x,y)<=father(x,y)","parent(x,y)<=mother(x,y)","ancestor(x,y)<=parent(x,
y)","ancestor(x,z)<=ancestor(x,y)&parent(y,z)","father(John,Johnson)","mother(Ma
ry,Johnson)","father(George,John)","father(John,Jake)",""]
addRule:parent(x,y)<=father(x,y){}
addRule:parent(x,y)<=mother(x,y){}
addRule:ancestor(x,y)<=parent(x,y){}
addRule:ancestor(x,z)<=ancestor(x,y)&parent(y,z){}
addFact:father(John,Johnson)
addFact:mother(Mary,Johnson)
addFact:father(George,John)
addFact:father(John,Jake)
=====facts========
father(John,Johnson)
mother(Mary,Johnson)
father(George,John)
father(John,Jake)
========rules=======
parent(x,y)<=father(x,y){}
parent(x,y)<=mother(x,y){}
ancestor(x,y)<=parent(x,y){}
ancestor(x,z)<=ancestor(x,y)&parent(y,z){}
======forwardChaining============
addFact:parent(John,Johnson)
  parent(x,y)<=father(x,y){};father(John,Johnson)

addFact:parent(Mary,Johnson)
  parent(x,y)<=mother(x,y){};mother(Mary,Johnson)

addFact:parent(George,John)
  parent(x,y)<=father(x,y){};father(George,John)

addFact:parent(John,Jake)
  parent(x,y)<=father(x,y){};father(John,Jake)

addFact:ancestor(John,Johnson)
  ancestor(x,y)<=parent(x,y){};parent(John,Johnson)

addRule:ancestor(x,z)<=ancestor(x,y)&parent(y,z){"y":"John","z":"Johnson"}
  ancestor(x,z)<=ancestor(x,y)&parent(y,z){};parent(John,Johnson)

addFact:ancestor(Mary,Johnson)
  ancestor(x,y)<=parent(x,y){};parent(Mary,Johnson)

addRule:ancestor(x,z)<=ancestor(x,y)&parent(y,z){"y":"Mary","z":"Johnson"}
  ancestor(x,z)<=ancestor(x,y)&parent(y,z){};parent(Mary,Johnson)

addFact:ancestor(George,John)
  ancestor(x,y)<=parent(x,y){};parent(George,John)

addRule:ancestor(x,z)<=ancestor(x,y)&parent(y,z){"y":"George","z":"John"}
  ancestor(x,z)<=ancestor(x,y)&parent(y,z){};parent(George,John)

addFact:ancestor(John,Jake)
  ancestor(x,y)<=parent(x,y){};parent(John,Jake)

addRule:ancestor(x,z)<=ancestor(x,y)&parent(y,z){"y":"John","z":"Jake"}
  ancestor(x,z)<=ancestor(x,y)&parent(y,z){};parent(John,Jake)

addRule:ancestor(x,z)<=ancestor(x,y)&parent(y,z){"x":"John","y":"Johnson"}
  ancestor(x,z)<=ancestor(x,y)&parent(y,z){};ancestor(John,Johnson)

addRule:ancestor(x,z)<=ancestor(x,y)&parent(y,z){"x":"Mary","y":"Johnson"}
  ancestor(x,z)<=ancestor(x,y)&parent(y,z){};ancestor(Mary,Johnson)

addRule:ancestor(x,z)<=ancestor(x,y)&parent(y,z){"x":"George","y":"John"}
  ancestor(x,z)<=ancestor(x,y)&parent(y,z){};ancestor(George,John)

addFact:ancestor(George,Johnson)
  ancestor(x,z)<=ancestor(x,y)&parent(y,z){"y":"John","z":"Johnson"};ancestor(Ge
orge,John)

addFact:ancestor(George,Jake)
  ancestor(x,z)<=ancestor(x,y)&parent(y,z){"y":"John","z":"Jake"};ancestor(Georg
e,John)

addRule:ancestor(x,z)<=ancestor(x,y)&parent(y,z){"x":"John","y":"Jake"}
  ancestor(x,z)<=ancestor(x,y)&parent(y,z){};ancestor(John,Jake)

addRule:ancestor(x,z)<=ancestor(x,y)&parent(y,z){"x":"George","y":"Johnson"}
  ancestor(x,z)<=ancestor(x,y)&parent(y,z){};ancestor(George,Johnson)

addRule:ancestor(x,z)<=ancestor(x,y)&parent(y,z){"x":"George","y":"Jake"}
  ancestor(x,z)<=ancestor(x,y)&parent(y,z){};ancestor(George,Jake)

======forwardChaining============
=====facts========
father(John,Johnson)
mother(Mary,Johnson)
father(George,John)
father(John,Jake)
parent(John,Johnson)
parent(Mary,Johnson)
parent(George,John)
parent(John,Jake)
ancestor(John,Johnson)
ancestor(Mary,Johnson)
ancestor(George,John)
ancestor(John,Jake)
ancestor(George,Johnson)
ancestor(George,Jake)
========rules=======
parent(x,y)<=father(x,y){}
parent(x,y)<=mother(x,y){}
ancestor(x,y)<=parent(x,y){}
ancestor(x,z)<=ancestor(x,y)&parent(y,z){}
ancestor(x,z)<=ancestor(x,y)&parent(y,z){"y":"John","z":"Johnson"}
ancestor(x,z)<=ancestor(x,y)&parent(y,z){"y":"Mary","z":"Johnson"}
ancestor(x,z)<=ancestor(x,y)&parent(y,z){"y":"George","z":"John"}
ancestor(x,z)<=ancestor(x,y)&parent(y,z){"y":"John","z":"Jake"}
ancestor(x,z)<=ancestor(x,y)&parent(y,z){"x":"John","y":"Johnson"}
ancestor(x,z)<=ancestor(x,y)&parent(y,z){"x":"Mary","y":"Johnson"}
ancestor(x,z)<=ancestor(x,y)&parent(y,z){"x":"George","y":"John"}
ancestor(x,z)<=ancestor(x,y)&parent(y,z){"x":"John","y":"Jake"}
ancestor(x,z)<=ancestor(x,y)&parent(y,z){"x":"George","y":"Johnson"}
ancestor(x,z)<=ancestor(x,y)&parent(y,z){"x":"George","y":"Jake"}

結語

以上我們用 JavaScript 實作了一個簡易的謂詞邏輯推論引擎,採用洪氏邏輯的語法,以及前向推論 (forwardChaining) 的方式。