4.14 積木世界問(wèn)題
另一個(gè)引起AI領(lǐng)域中廣泛關(guān)注的問(wèn)題就是積木世界問(wèn)題。想像一群在桌面上玩堆積木的孩子。問(wèn)題就是把當(dāng)前的積木配置,轉(zhuǎn)化成目標(biāo)配置。我們假設(shè),一塊積木,有且僅有另一塊積木在它的上面,即使他們可以聚合成人意的高度。在這個(gè)世界中唯一可以進(jìn)行的操作就是移動(dòng)一塊上面沒(méi)有其他積木的積木到另一個(gè)地方。創(chuàng)建一個(gè)可以移動(dòng)能移動(dòng)的積木的操作符。
(defun make-block-ops (blocks)
?(let ((ops nil))
? ? (dolist (a blocks)
? ? ? (dolist (b blocks)
? ? ? ? (unless (equal a b)
? ? ? ? ? (dolist (c blocks)
? ? ? ? ? ? (unless (or (equal c a) (equal c b))
? ? ? ? ? ? ? (push (move-op a b c) ops)))
? ? ? ? ? (push (move-op a ‘table b) ops)
? ? ? ? ? (push (move-op a b ‘table) ops))))
? ? ?ops))
(defun move-op (a b c)
? “Make an operator to move A from B to C.”
? (op ‘(move ,a from ,b to ,c)
? ?:preconds ‘((space on ,a) (space on ,c) (,a on ,b))
? ?:add-list (move-ons a b c)
? ?:del-list (move-ons a c b)))
(defun move-ops (a b c)
(if (eq b ‘table)
‘((,a on ,c))
‘((,a on ,c) (space on ,b))))
現(xiàn)在我們嘗試用這些操作符解決問(wèn)題,首先是最簡(jiǎn)單的情況,把一個(gè)積木移動(dòng)到另一個(gè)積木上。
> (use (make-block-ops '(a b))) => 4
> (gps '((a on table) (b on table) (space on a) (space on b)
(space on table))
'((a on b) (b on table)))
((START)
(EXECUTING (MOVE A FROM TABLE TO B)))
下面的問(wèn)題更加復(fù)雜一些,把兩塊積木的棧倒過(guò)來(lái),這次我們使用調(diào)試輸出。
> (debug :gps) => (:GPS)
> (gps '((a on b) (b on table) (space on a) (space on table))
'((b on a)))
Goal: (B ON A)
Consider: (MOVE B FROM TABLE TO A)
Goal: (SPACE ON B)
Consider: (MOVE A FROM B TO TABLE)
Goal: (SPACE ON A)
Goal: (SPACE ON TABLE)
Goal: (A ON B)
Action: (MOVE A FROM B TO TABLE)
Goal: (SPACE ON A)
Goal: (B ON TABLE)
Action: (MOVE B FROM TABLE TO A)
((START)
(EXECUTING (MOVE A FROM B TO TABLE))
(EXECUTING (MOVE B FROM TABLE TO A)))
> (undebug) => NIL
有時(shí)候動(dòng)作連接的順序也是很重要的。例如,你不可以擁有一塊蛋糕的同時(shí)也吃掉它,但是你可以先拍照片然后吃掉它,只要在吃之前拍照就可以了,在積木世界我們有這樣一個(gè)順序:
> (use (make-block-ops '(a b c))) => 18
> (gps '((a on b) (b on c) (c on table) (space on a) (space on table))
'((b on a) (c on b)))
(( START)
(EXECUTING (MOVE A FROM B TO TABLE))
(EXECUTING (MOVE B FROM C TO A))
(EXECUTING (MOVE C FROM TABLE TO B)))
> (gps '((a on b) (b on c) (c on table) (space on a) (space on table))
'((c on b) (b on a)))
? NIL
在第一種情況中,是首先把B放在A上,之后把C放在B上。第二種情況是,程序首先把C放在B上,但是之后要想把B放在A上就會(huì)任務(wù)失敗了。這種被稱(chēng)作先決條件兄弟沖突問(wèn)題,但是程序不會(huì)采取任何行動(dòng)。我們能做的就是改變連接的順序,我們可以修改achieve-all如下:
(defun achieve-all (state goals goal-stack)
?“Achieve each goal, trying several orderings.”
?(some #’(lambda (goals) (achieve-each state goals goal-stack))
? ?(orderings goals)))
(defun achieve-each (state goals goal-stack)
?“Achieve each goal, and make sure they still hold at the end.”
?(let ((current-state state))
? ?(if (and (every #’(lambda (g)
? ? ? ? ?(setf sucrrent-state
? ? ? ? ? ?(achieve current-state g goal-stack)))
? ? ? ?goals)
? ? ?(subset goals current-state :test #’equal))
? ?Current-state)))
(defun orderings (l)
?(if (> (length l) 1)
? ?(list l (reserve l))
? ?(list l)))
我們可以用幾種方式展示,但是會(huì)得到同一個(gè)答案。注意,我們只考慮兩種順序:給定的順序和倒轉(zhuǎn)的順序。顯然對(duì)于一個(gè)或者連個(gè)連接的目標(biāo)集合這就是所有的順序了。一般來(lái)說(shuō),如果每一個(gè)目標(biāo)集合都只有一個(gè)交互,兩個(gè)順序中的其中一個(gè)就是可行的。因此,我們假定先決條件兄弟目標(biāo)沖突問(wèn)題交互是很罕見(jiàn)的。而且很少有目標(biāo)集合會(huì)超過(guò)一種交互。另一種可能性就是考慮所有的目標(biāo)順序排列,但是會(huì)在規(guī)模增長(zhǎng)的時(shí)候消耗很多時(shí)間。
另一個(gè)要考慮的就是解決方案的效率。我們看一下,下圖中將積木C移動(dòng)到桌面上的任務(wù):
> (gps '((c on a) (a on table) (b on table)
(space on c) (space on b) (space on table))
'((c on table)))
((START)
(EXECUTING (MOVE C FROM A TO B)))
(EXECUTING (MOVE C FROM B TO TABLE)))
誰(shuí)說(shuō)方法是正確的,但是還有一個(gè)更好的辦法就是把C直接移動(dòng)到桌子上。這個(gè)更簡(jiǎn)單的方法被忽視了是由于一個(gè)意外:make-blocks-ops定義的操作符是把C從B上移開(kāi)要在把C從A上移動(dòng)到桌面上之前的。所以第一個(gè)移動(dòng),操作符會(huì)嘗試,成功的把C放在了B上,因此,兩步的解決方法要先于一步的解決方法得到。下面的例子在可以?xún)刹礁愣ǖ那闆r下用了四步的解決方法:
> (gps '((c on a) (a on table) (b on table)
(space on c) (space on b) (space on table)
'((c on table) (a on b)))
((START)
(EXECUTING (MOVE C FROM A TO B)
(EXECUTING (MOVE C FROM B TO TABLE))
(EXECUTING (MOVE A FROM TABLE TO C))
(EXECUTING (MOVE A FROM C TO B)))
如何找到更簡(jiǎn)短的解決方法?一種是我們可以進(jìn)行全部的搜索:嘗試更簡(jiǎn)短的方法,臨時(shí)放棄一些看上去更值得推薦的方法,之后在來(lái)考慮。這種方式在第六章詳細(xì)介紹,使用一般搜索函數(shù)。一個(gè)不太極端的方式是吧操作符檢索的結(jié)果做一個(gè)有限的重新排序:需要更少先決條件的那個(gè)就會(huì)首先嘗試執(zhí)行。也就是所這意味著滿足所有先決條件的操作總是會(huì)排在其他操作之前。為了實(shí)現(xiàn)這個(gè)方法,我們來(lái)修改一下achieve:
(defun achieve (state goal goal-stack)
?“A goal is achieved if it already holds,
?Or if there is an appropriate op for it that is applicable.”
?(dbg-indent :gps (length goal-stack) “Goal :~a” goal)
?(cond ((member-equal goal state) state)
? ?((member-equal goal goal-stack) nil)
?(t (some #’(lambda (op) (apply-op state goal op goal-stack))
? ? ?(appropriate-ops goal state)))));***
(defun appropriate-ops (goal state)
?“Return a list of appropriate opertators,
?Sorted by the number of unfulfilled preconditions.”
?(sort (copy-list (find-all goal *ops* :test #’appropriate-p)) #’<
? ?:key #’(lambda (op)
? ? ?(count-if #’(lambda (precond)
?? ? ? ?(not (member-equal precond state)))
? ? ? ?(op-preconds op)))))
現(xiàn)在我們可以得到想要的解決方案了:
> (gps '(c on a) (a on table) (b on table)
(space on c) (space on b) (space on table))
'(c on table) (a on b)))
(( START)
(EXECUTING (MOVE C FROM A TO TABLE))
(EXECUTING (MOVE A FROM TABLE TO B)))
> (gps '((a on b) (b on c) (c on table) (space on a) (space on table))
'((b on a) (c on b)))
(START)
(EXECUTING (MOVE A FROM B TO TABLE))
(EXECUTING (MOVE B FROM C TO A))
(EXECUTING (MOVE C FROM TABLE TO B)))
> (gps '((a on b) (b on c) (c on table) (space on a) (space on table))
'((c on b) (b on a)))
(START)
(EXECUTING (MOVE A FROM B TO TABLE))
(EXECUTING (MOVE B FROM C TO A))
(EXECUTING (MOVE C FROM TABLE TO B)))
薩斯曼異常
其實(shí)有的問(wèn)題是不能通過(guò)目標(biāo)的重新排序來(lái)解決的:
看上去也不是很難么,我們來(lái)看看GPS是如何解決的:
> (setf start '((c on a) (a on table) (b on table) (space on c)
(space on b) (space on table)))
((C ON A) (A ON TABLE) (B ON TABLE) (SPACE ON C)
(SPACE ON B) (SPACE ON TABLE)
? > (gps start '((a on b) (b on c))) => NIL
? > (gps start '((b on c) (a on b)) => NIL
這是一個(gè)與疊放順序沒(méi)有關(guān)系的先決條件兄弟目標(biāo)沖突問(wèn)題。換句話說(shuō),沒(méi)有什么計(jì)劃可以解決兩個(gè)目標(biāo)之間的連接。這是一個(gè)令人驚異的事實(shí),這個(gè)例子也被稱(chēng)為薩斯曼異常,我們會(huì)在第六章討論。