人工智慧(5)深度,廣度搜索
Search一類的問題通常是人工智慧方面最先接觸到的話題,在小的時候大家想必都遇到過這樣的一類益智題目:
一個農夫,一隻羊,一頭狼,和一棵白菜。現在有一條船,這條船每次只能盛這四個中的兩個,當然只有農夫會開船。問:
農夫怎樣才能把狼,樣,和白菜運到河對岸,有兩個限制條件:
狼和羊不能單獨在一起,羊和白菜這兩樣也不能落單。
遇到這樣的問題,相信大家一定在腦補:農夫先帶過去一隻,看看後面那兩個有沒有後果發生,沒的話繼續,有的話退回一步重新考慮。嗯,羊不能與狼和白菜共存,所以先帶過去,然後第二次隨便帶狼或者白菜,帶過去,然後把羊帶回來,然後把第二次剩下的帶過去,把羊留下,這樣狼和白菜到了對岸,最後把羊帶過去。當然這樣的問題很簡單,想一下就能找到方案。當遇到比較複雜的問題,比如:
路徑規劃問題,從一個城市到另一座城市,根據你的目標不同,可以搜尋最短的,最快的或者風景最好的,這是一個問題的目標(objective),然後根據設計選擇不同的方向,這是行動(action),又或者:機器人機械臂的運動,速度快慢,旋轉和移動,字謎遊戲,機器翻譯等等。
對於一個classifier(分類器),如果給定一個輸入,經過一個轉換之後得出的是一個單一的操作,而對於search(搜尋)問題,輸出是一系列的操作,重點是要根據現狀考慮未來的動作,並且是基於狀態的一種操作。
再回到剛剛提到的那個過河的問題,其實從初始狀態可以畫一棵樹,初始狀態分別有三種選擇,這樣每做出一個選擇可以畫出子分支,最後會發現能到達最終結果的就是剛剛跟大家分享的那兩種方案。歸結起來,對於一個搜尋問題,包含:
初始狀態,動作,對應的動作的代價,下一個狀態,和最終的狀態。
通常我們會用到資料結構中常用的tree的概念,構建一個search tree:
根節點是初始狀態,葉節點是最終的狀態,連線節點之間的邊就是一個action,並伴隨著一定的代價。從根節點到葉節點的所有路徑的和就是這個方案的代價,通常會找出一條代價最小的方案。但是注意的是:
在程式設計中,我們通常不會用到樹這樣的資料結構,樹在這裡只是一種形象的表達,後面示例程式碼中可以看到更多解釋。
在分析一棵樹的過程中,通常會有這樣幾個引數:最大的深度D,也即每一條路徑包含D條邊,然後每個狀態可以有b 種動作,經常把這個叫做branching factor。想找到一個最少代價的路徑,儲存的成本是O(D),每次從葉節點回溯即可,時間成本就比較大,O(b的D次方),(1 + b + b*b ,,,, + b的D次方 ,等比數列求和,最後結果是 O(b的D次方))。面對這樣一個指數級別的時間成本,通常會有兩種做法,一是限定最大的搜尋深度,而是對於之前到達過的狀態,不重複訪問。
最基本的:
回溯搜尋:
先判斷是不是最終結束的狀態,是的話,更新最小的代價路徑
對於當前狀態的每一個action:
尋找這個狀態所有的子節點和到達子節點對應的代價。
對於當前的狀態重新呼叫回溯搜尋。
返回最小代價的路徑
深度優先搜尋(depth-first search, DFS):
基於回溯搜尋,當遇到終止狀態的時候即停止。
儲存的成本是O(D), 每次從葉節點回溯即可,時間成本就比較大,O(b的D次方)。
需要注意的是,這個時候並沒有考慮每一個action的代價,比如從初始狀態到下一個狀態,預設代價是0。這跟之前接觸的每一條邊都有賦值不同,後面會再提到考慮代價的問題。
深度優先搜尋適用於整個搜尋樹中有很多方案的搜尋辦法,這樣,能比較快的返回搜尋結果。不然,如果整棵樹都沒有方案,需要把整個樹搜尋一遍。
廣度優先搜尋(breadth-first search, BFS):
假設每一個action有固定的一個代價值,c,整個搜尋過程是按照由樹的每一層進行的。
假設每個狀態有b種可能的操作,最後的方案深度是d,樹的深度是D, 那麼:
儲存的成本是O(b的d次方),時間成本也是O(b的d次方),與D 沒有關係。
由於讀取搜尋的方式的不同,一般,DFS使用堆疊儲存節點的資訊,這樣保證最後入棧的節點得以出棧,然後再入棧該節點的所有子節點。BFS用佇列儲存,這樣第一次存入的所有節點可以依次從頭部出佇列,然後再把所有的子節點壓入佇列。並且不難想象的是,這樣的儲存方式,儲存的都是葉節點,中間的節點在產生子節點之前都已經出棧或者隊列了。
來看一下一個用python寫的簡單的例子:






好,搜尋是人工智慧很基礎的一個分支,下次接著來看其他搜尋的方式。