<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                接下來三章提供了大量的 Lisp 程序例子。選擇這些例子來說明那些較長的程序所采取的形式,和 Lisp 所擅長解決的問題類型。 在這一章中我們將要寫一個基于一組?`if-then`?規則的推論程序。這是一個經典的例子 —— 不僅在于其經常出現在教科書上,還因為它反映了 Lisp 作為一個“符號計算”語言的本意。這個例子散發著很多早期 Lisp 程序的氣息。 [TOC] ## 15.1 目標 (The Aim)[](http://acl.readthedocs.org/en/latest/zhCN/ch15-cn.html#the-aim "Permalink to this headline") 在這個程序中,我們將用一種熟悉的形式來表示信息:包含單個判斷式,以及跟在之后的零個或多個參數所組成的列表。要表示 Donald 是 Nancy 的家長,我們可以這樣寫: ~~~ (parent donald nancy) ~~~ 事實上,我們的程序是要表示一些從已有的事實作出推斷的規則。我們可以這樣來表示規則: ~~~ (<- head body) ~~~ 其中,?`head`?是?**那么...部分**?(then-part),?`body`?是?**如果...部分**?(if-part)。在?`head`?和?`body`?中我們使用以問號為前綴的符號來表示變量。所以下面這個規則: ~~~ (<- (child ?x ?y) (parent ?y ?x)) ~~~ 表示:如果 y 是 x 的家長,那么 x 是 y 的孩子;更恰當地說,我們可以通過證明?`(parent?y?x)`?來證明?`(child?x?y)`?的所表示的事實。 可以把規則中的?*body*?部分(if-part) 寫成一個復雜的表達式,其中包含?`and`?,?`or`?和?`not`?等邏輯操作。所以當我們想要表達 “如果 x 是 y 的家長,并且 x 是男性,那么 x 是 y 的父親” 這樣的規則,我們可以寫: ~~~ (<- (father ?x ?y) (and (parent ?x ?y) (male ?x))) ~~~ 一些規則可能依賴另一些規則所產生的事實。比如,我們寫的第一個規則是為了證明?`(child?x?y)`?的事實。如果我們定義如下規則: ~~~ (<- (daughter ?x ?y) (and (child ?x ?y) (female ?x))) ~~~ 然后使用它來證明?`(daughter?x?y)`?可能導致程序使用第一個規則去證明?`(child?x?y)`?。 表達式的證明可以回溯任意數量的規則,只要它最終結束于給出的已知事實。這個過程有時候被稱為反向鏈接 (backward-chaining)。之所以說?*反向*?(backward) 是因為這一類推論先考慮?*head*?部分,這是為了在繼續證明?*body*?部分之前檢查規則是否有效。*鏈接*?(chaining) 來源于規則之間的依賴關系,從我們想要證明的內容到我們的已知條件組成一個鏈接 (盡管事實上它更像一棵樹)。?[λ](http://acl.readthedocs.org/en/latest/zhCN/notes-cn.html#notes-248) ## 15.2 匹配 (Matching)[](http://acl.readthedocs.org/en/latest/zhCN/ch15-cn.html#matching "Permalink to this headline") 我們需要有一個函數來做模式匹配以完成我們的反向鏈接 (back-chaining) 程序,這個函數能夠比較兩個包含變量的列表,它會檢查在給變量賦值后是否可以使兩個列表相等。舉例,如果?`?x`?和?`?y`?是變量,那么下面兩個列表: ~~~ (p ?x ?y c ?x) (p a b c a) ~~~ 當?`?x?=?a`?且?`?y?=?b`?時匹配,而下面兩個列表: ~~~ (p ?x b ?y a) (p ?y b c a) ~~~ 當?`?x?=??y?=?c`?時匹配。 我們有一個?`match`?函數,它接受兩棵樹,如果這兩棵樹能匹配,則返回一個關聯列表(assoc-list)來顯示他們是如何匹配的: ~~~ (defun match (x y &optional binds) (cond ((eql x y) (values binds t)) ((assoc x binds) (match (binding x binds) y binds)) ((assoc y binds) (match x (binding y binds) binds)) ((var? x) (values (cons (cons x y) binds) t)) ((var? y) (values (cons (cons y x) binds) t)) (t (when (and (consp x) (consp y)) (multiple-value-bind (b2 yes) (match (car x) (car y) binds) (and yes (match (cdr x) (cdr y) b2))))))) (defun var? (x) (and (symbolp x) (eql (char (symbol-name x) 0) #\?))) (defun binding (x binds) (let ((b (assoc x binds))) (if b (or (binding (cdr b) binds) (cdr b))))) ~~~ **圖 15.1: 匹配函數。** ~~~ > (match '(p a b c a) '(p ?x ?y c ?x)) ((?Y . B) (?X . A)) T > (match '(p ?x b ?y a) '(p ?y b c a)) ((?Y . C) (?X . ?Y)) T > (match '(a b c) '(a a a)) NIL ~~~ 當?`match`?函數逐個元素地比較它的參數時候,它把?`binds`?參數中的值分配給變量,這被稱為綁定 (bindings)。如果成功匹配,`match`?函數返回生成的綁定;否則,返回?`nil`?。當然并不是所有成功的匹配都會產生綁定,我們的?`match`?函數就像?`gethash`?函數那樣返回第二個值來表明匹配成功: ~~~ > (match '(p ?x) '(p ?x)) NIL T ~~~ 如果?`match`?函數像上面那樣返回?`nil`?和?`t`?,表明這是一個沒有產生綁定的成功匹配。下面用中文來描述?`match`?算法是如何工作的: 1. 如果 x 和 y 在?`eql`?上相等那么它們匹配;否則, 2. 如果 x 是一個已綁定的變量,并且綁定匹配 y ,那么它們匹配;否則, 3. 如果 y 是一個已綁定的變量,并且綁定匹配 x ,那么它們匹配;否則, 4. 如果 x 是一個未綁定的變量,那么它們匹配,并且為 x 建立一個綁定;否則, 5. 如果 y 是一個未綁定的變量,那么它們匹配,并且為 y 建立一個綁定;否則, 6. 如果 x 和 y 都是?`cons`?,并且它們的?`car`?匹配,由此產生的綁定又讓?`cdr`?匹配,那么它們匹配。 下面是一個例子,按順序來說明以上六種情況: ~~~ > (match '(p ?v b ?x d (?z ?z)) '(p a ?w c ?y ( e e)) '((?v . a) (?w . b))) ((?Z . E) (?Y . D) (?X . C) (?V . A) (?W . B)) T ~~~ `match`?函數通過調用?`binding`?函數在一個綁定列表中尋找變量(如果有的話)所關聯的值。這個函數必須是遞歸的,因為有這樣的情況 “匹配建立一個綁定列表,而列表中變量只是間接關聯到它的值:?`?x`?可能被綁定到一個包含?`(?x?.??y)`?和?`(?y?.?a)`?的列表”: ~~~ > (match '(?x a) '(?y ?y)) ((?Y . A) (?X . ?Y)) T ~~~ 先匹配?`?x`?和?`?y`?,然后匹配?`?y`?和?`a`?,我們間接確定?`?x`?是?`a`?。 ## 15.3 回答查詢 (Answering Queries)[](http://acl.readthedocs.org/en/latest/zhCN/ch15-cn.html#answering-queries "Permalink to this headline") 在介紹了綁定的概念之后,我們可以更準確的說一下我們的程序將要做什么:它得到一個可能包含變量的表達式,根據我們給定的事實和規則返回使它正確的所有綁定。比如,我們只有下面這個事實: ~~~ (parent donald nancy) ~~~ 然后我們想讓程序證明: ~~~ (parent ?x ?y) ~~~ 它會返回像下面這樣的表達: ~~~ (((?x . donald) (?y . nancy))) ~~~ 它告訴我們只有一個可以讓這個表達式為真的方法:?`?x`?是?`donald`?并且?`?y`?是?`nancy`?。 在通往目標的路上,我們已經有了一個的重要部分:一個匹配函數。 下面是用來定義規則的一段代碼: ~~~ (defvar *rules* (make-hash-table)) (defmacro <- (con &optional ant) `(length (push (cons (cdr ',con) ',ant) (gethash (car ',con) *rules*)))) ~~~ **圖 15.2 定義規則** 規則將被包含于一個叫做?`*rules*`?的哈希表,通過頭部 (head) 的判斷式構建這個哈系表。這樣做加強了我們無法使用判斷式中的變量的限制。雖然我們可以通過把所有這樣的規則放在分離的列表中來消除限制,但是如果這樣做,當我們需要證明某件事的時侯不得不和每一個列表進行匹配。 我們將要使用同一個宏?`<-`?去定義事實 (facts)和規則 (rules)。一個事實將被表示成一個沒有?*body*?部分的規則。這和我們對規則的定義保持一致。一個規則告訴我們你可以通過證明?*body*?部分來證明?*head*?部分,所以沒有?*body*?部分的規則意味著你不需要通過證明任何東西來證明?*head*?部分。這里有兩個對應的例子: ~~~ > (<- (parent donald nancy)) 1 > (<- (child ?x ?y) (parent ?y ?x)) 1 ~~~ 調用?`<-`?返回的是給定判斷式下存儲的規則數量;用?`length`?函數來包裝?`push`?能使我們免于看到頂層中的一大堆返回值。 下面是我們的推論程序所需的大多數代碼: ~~~ (defun prove (expr &optional binds) (case (car expr) (and (prove-and (reverse (cdr expr)) binds)) (or (prove-or (cdr expr) binds)) (not (prove-not (cadr expr) binds)) (t (prove-simple (car expr) (cdr expr) binds)))) (defun prove-simple (pred args binds) (mapcan #'(lambda (r) (multiple-value-bind (b2 yes) (match args (car r) binds) (when yes (if (cdr r) (prove (cdr r) b2) (list b2))))) (mapcar #'change-vars (gethash pred *rules*)))) (defun change-vars (r) (sublis (mapcar #'(lambda (v) (cons v (gensym "?"))) (vars-in r)) r)) (defun vars-in (expr) (if (atom expr) (if (var? expr) (list expr)) (union (vars-in (car expr)) (vars-in (cdr expr))))) ~~~ **圖 15.3: 推論。** 上面代碼中的?`prove`?函數是推論進行的樞紐。它接受一個表達式和一個可選的綁定列表作為參數。如果表達式不包含邏輯操作,它調用?`prove-simple`?函數,前面所說的鏈接 (chaining)正是在這個函數里產生的。這個函數查看所有擁有正確判斷式的規則,并嘗試對每一個規則的?*head*?部分和它想要證明的事實做匹配。對于每一個匹配的?*head*?,使用匹配所產生的新的綁定在?*body*?上調用?`prove`。對?`prove`?的調用所產生的綁定列表被?`mapcan`?收集并返回: ~~~ > (prove-simple 'parent '(donald nancy) nil) (NIL) > (prove-simple 'child '(?x ?y) nil) (((#:?6 . NANCY) (#:?5 . DONALD) (?Y . #:?5) (?X . #:?6))) ~~~ 以上兩個返回值指出有一種方法可以證明我們的問題。(一個失敗的證明將返回 nil。)第一個例子產生了一組空的綁定,第二個例子產生了這樣的綁定:?`?x`?和?`?y`?被(間接)綁定到?`nancy`?和?`donald`?。 順便說一句,這是一個很好的例子來實踐 2.13 節提出的觀點。因為我們用函數式的風格來寫這個程序,所以可以交互式地測試每一個函數。 第二個例子返回的值里那些?*gensyms*?是怎么回事?如果我們打算使用含有變量的規則,我們需要避免兩個規則恰好包含相同的變量。如果我們定義如下兩條規則: ~~~ (<- (child ?x ?y) (parent ?y ?x)) (<- (daughter ?y ?x) (and (child ?y ?x) (female ?y))) ~~~ 第一條規則要表達的意思是:對于任何的?`x`?和?`y`?, 如果?`y`?是?`x`?的家長,則?`x`?是?`y`?的孩子。第二條則是:對于任何的?`x`?和?`y`?, 如果?`y`?是?`x`?的孩子并且?`y`?是女性,則?`y`?是?`x`?的女兒。在每一條規則內部,變量之間的關系是顯著的,但是兩條規則使用了相同的變量并非我們刻意為之。 如果我們使用上面所寫的規則,它們將不會按預期的方式工作。如果我們嘗試證明“ a 是 b 的女兒”,匹配到第二條規則的?*head*?部分時會將?`a`?綁定到?`?y`?,將?`b`?綁定到 ?x。我們無法用這樣的綁定匹配第一條規則的?*head*?部分: ~~~ > (match '(child ?y ?x) '(child ?x ?y) '((?y . a) (?x . b))) NIL ~~~ 為了保證一條規則中的變量只表示規則中各參數之間的關系,我們用?*gensyms*?來代替規則中的所有變量。這就是?`change-vars`?函數的目的。一個?*gensym*?不可能在另一個規則中作為變量出現。但是因為規則可以是遞歸的,我們必須防止出現一個規則和自身沖突的可能性,所以在定義和使用一個規則時都要調用?`chabge-vars`?函數。 現在只剩下定義用以證明復雜表達式的函數了。下面就是需要的函數: ~~~ (defun prove-and (clauses binds) (if (null clauses) (list binds) (mapcan #'(lambda (b) (prove (car clauses) b)) (prove-and (cdr clauses) binds)))) (defun prove-or (clauses binds) (mapcan #'(lambda (c) (prove c binds)) clauses)) (defun prove-not (clause binds) (unless (prove clause binds) (list binds))) ~~~ **圖 15.4 邏輯操作符 (Logical operators)** 操作一個?`or`?或者?`not`?表達式是非常簡單的。操作?`or`?時,我們提取在?`or`?之間的每一個表達式返回的綁定。操作?`not`?時,當且僅當在?`not`?里的表達式產生?`none`?時,返回當前的綁定。 `prove-and`?函數稍微復雜一點。它像一個過濾器,它用之后的表達式所建立的每一個綁定來證明第一個表達式。這將導致?`and`?里的表達式以相反的順序被求值。除非調用?`prove`?中的?`prove-and`?函數則會先逆轉它們。 現在我們有了一個可以工作的程序,但它不是很友好。必須要解析?`prove-and`?返回的綁定列表是令人厭煩的,它們會變得更長隨著規則變得更加復雜。下面有一個宏來幫助我們更愉快地使用這個程序: ~~~ (defmacro with-answer (query &body body) (let ((binds (gensym))) `(dolist (,binds (prove ',query)) (let ,(mapcar #'(lambda (v) `(,v (binding ',v ,binds))) (vars-in query)) ,@body)))) ~~~ **圖 15.5 介面宏 (Interface macro)** 它接受一個?`query`?(不被求值)和若干表達式構成的?`body`?作為參數,把?`query`?所生成的每一組綁定的值賦給?`query`?中對應的模式變量,并計算?`body`?。 ~~~ > (with-answer (parent ?x ?y) (format t "~A is the parent of ~A.~%" ?x ?y)) DONALD is the parent of NANCY. NIL ~~~ 這個宏幫我們做了解析綁定的工作,同時為我們在程序中使用?`prove`?提供了一個便捷的方法。下面是這個宏展開的情況: ~~~ (with-answer (p ?x ?y) (f ?x ?y)) ;;將被展開成下面的代碼 (dolist (#:g1 (prove '(p ?x ?y))) (let ((?x (binding '?x #:g1)) (?y (binding '?y #:g1))) (f ?x ?y))) ~~~ **圖 15.6: with-answer 調用的展開式** 下面是使用它的一個例子: ~~~ (<- (parent donald nancy)) (<- (parent donald debbie)) (<- (male donald)) (<- (father ?x ?y) (and (parent ?x ?y) (male ?x))) (<- (= ?x ?y)) (<- (sibling ?x ?y) (and (parent ?z ?x) (parent ?z ?y) (not (= ?x ?y)))) ;;我們可以像下面這樣做出推論 > (with-answer (father ?x ?y) (format t "~A is the father of ~A.~%" ?x ?y)) DONALD is the father of DEBBIE. DONALD is the father of NANCY. NIL > (with-answer (sibling ?x ?y)) (format t "~A is the sibling of ~A.~%" ?x ?y)) DEBBLE is the sibling of NANCY. NANCY is the sibling of DEBBIE. NIL ~~~ **圖 15.7: 使用中的程序** ## 15.4 分析 (Analysis)[](http://acl.readthedocs.org/en/latest/zhCN/ch15-cn.html#analysis "Permalink to this headline") 看上去,我們在這一章中寫的代碼,是用簡單自然的方式去實現這樣一個程序。事實上,它的效率非常差。我們在這里是其實是做了一個解釋器。我們能夠把這個程序做得像一個編譯器。 這里做一個簡單的描述。基本的思想是把整個程序打包到兩個宏?`<-`?和?`with-answer`?,把已有程序中在*運行期*做的多數工作搬到*宏展開期*(在 10.7 節的?`avg`?可以看到這種構思的雛形) 用函數取代列表來表示規則,我們不在運行時用?`prove`?和?`prove-and`?這樣的函數來解釋表達式,而是用相應的函數把表達式轉化成代碼。當一個規則被定義的時候就有表達式可用。為什么要等到使用的時候才去分析它呢?這同樣適用于和?`<-`?調用了相同的函數來進行宏展開的?`with-answer`?。 聽上去好像比我們已經寫的這個程序復雜很多,但其實可能只是長了兩三倍。想要學習這種技術的讀者可以看?*On Lisp*?或者*Paradigms of Artificial Intelligence Programming*?,這兩本書有一些使用這種風格寫的示例程序。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看