勇闖新世界︰ 《 pyDatalog 》 導引《七》

《 Simply Logical
Intelligent Reasoning by Example 》

之第三章開始處, Peter Flach 說︰

3 Logic Programming and Prolog
In the previous chapters we have seen how logic can be used to represent knowledge about a particular domain, and to derive new knowledge by means of logical inference. A distinct feature of logical reasoning is the separation between model theory and proof theory: a set of logical formulas determines the set of its models, but also the set of formulas that can be derived by applying inference rules. Another way to say the same thing is: logical formulas have both a declarative meaning and a procedural meaning. For instance, declaratively the order of the atoms in the body of a clause is irrelevant, but procedurally it may determine the order in which different answers to a query are found.

Because of this procedural meaning of logical formulas, logic can be used as a programming language. If we want to solve a problem in a particular domain, we write down the required knowledge and apply the inference rules built into the logic programming language. Declaratively, this knowledge specifies what the problem is, rather than how it should be solved. The distinction between declarative and procedural aspects of problem solving is succinctly expressed by Kowalski’s equation

algorithm = logic + control

Here, logic refers to declarative knowledge, and control refers to procedural knowledge. The equation expresses that both components are needed to solve a problem algorithmically.

In a purely declarative programming language, the programmer would have no means to express procedural knowledge, because logically equivalent programs would behave identical. However, Prolog is not a purely declarative language, and therefore the procedural meaning of Prolog programs cannot be ignored. For instance, the order of the literals in the body of a clause usually influences the efficiency of the program to a large degree. Similarly, the order of clauses in a program often determines whether a program will give an answer at all. Therefore, in this chapter we will take a closer look at Prolog’s inference engine and its built-in features (some of which are non-declarative). Also, we will discuss some common programming techniques.

就讓我們舉個典型例子 ── 『台北捷運網』一小部份 ──,講講『陳述地』  declaratively 以及『程序地』procedurally 的『意義』不同,如何展現在程式『思考』和『寫作』上。

在這個例子裡,我們將以『忠孝新生』站為中心,含括了二十五個捷運站,隨意不依次序給定站名如下︰

松江南京大安森林公園、善導寺、南京復興、忠孝復興  
台北小巨蛋、小南門、忠孝新生、中山、台北車站  
龍山寺、忠孝敦化、西門、雙連、中山國中  
信義安和、中正紀念堂、古亭、大安、行天宮  
東門、台大醫院、北門、科技大樓 、世貿台北101

 

【例子捷運圖】

台北捷運

 

假使我們定義

連接( □, ○) 代表 □ 捷運站,僅經過 □ 站,直通 ○ 捷運站,方向是 □ → ○。如此我們可以把這部份『捷運網』,表示為︰

pi@raspberrypi ~ $ python3
Python 3.2.3 (default, Mar  1 2013, 11:53:50) 
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.

# 定義站站間『連接』事實
>>> from pyDatalog import pyDatalog
>>> pyDatalog.create_terms('連接, 鄰近, 能達, 所有路徑')
>>> +連接('台北車站', '中山')
>>> +連接('台北車站', '善導寺')
>>> +連接('台北車站', '西門')
>>> +連接('台北車站', '台大醫院')
>>> +連接('西門', '龍山寺')
>>> +連接('西門', '小南門')
>>> +連接('西門', '北門')
>>> +連接('中山', '北門')
>>> +連接('中山', '雙連')
>>> +連接('中山', '松江南京')
>>> +連接('中正紀念堂', '小南門')
>>> +連接('中正紀念堂', '台大醫院')
>>> +連接('中正紀念堂', '東門')
>>> +連接('中正紀念堂', '古亭')
>>> +連接('東門', '古亭')
>>> +連接('東門', '忠孝新生')
>>> +連接('東門', '大安森林公園')
>>> +連接('善導寺', '忠孝新生')
>>> +連接('松江南京', '忠孝新生')
>>> +連接('忠孝復興', '忠孝新生')
>>> +連接('松江南京', '行天宮')
>>> +連接('松江南京', '南京復興')
>>> +連接('中山國中', '南京復興')
>>> +連接('台北小巨蛋', '南京復興')
>>> +連接('忠孝復興', '南京復興')
>>> +連接('忠孝復興', '忠孝敦化')
>>> +連接('忠孝復興', '大安')
>>> +連接('大安森林公園', '大安')
>>> +連接('科技大樓', '大安')
>>> +連接('信義安和', '大安')
>>> +連接('信義安和', '世貿台北101')
>>> 

 

此處『連接』次序是『隨興』輸入的,一因『網路圖』沒有個經典次序,次因『事實』『陳述』不會因次序而改變,再因『程序』上『pyDatalog』對此『事實』『次序』也並不要求。由於『連接』之次序有『起止』方向性,上面的陳述並不能代表那個『捷運網』,這可以從下面程式片段得知。【※ 在 pyDatalog 中,沒有變元的『查詢』 ask or query ,以輸出『set([()]) 』表示一個存在的事實,以輸出『None』表達所查詢的不是個事實。

# 單向性
>>> pyDatalog.ask("連接('信義安和', '世貿台北101')") == set([()]) 
True
>>> pyDatalog.ask("連接('世貿台北101', '信義安和')") == set([()]) 
False
>>> pyDatalog.ask("連接('世貿台北101', '信義安和')") == None
True
>>> 

 

所以我們必須給定『連接( □, ○)』是具有『雙向性』的,也就是

連接(X站名,Y站名)<=連接(Y站名,X站名)

,這樣的『規則』 Rule 。由於 pyDatalog 的『語詞』 Term 使用前都必須『宣告』,而且『變元』必須『大寫開頭』,因此我們得用

pyDatalog.create_terms(‘X站名, Y站名, Z站名, P路徑甲, P路徑乙’)

這樣的『陳述句』 Statement。【※ 中文沒有大小寫,也許全部被當成了小寫,所以變元不得不以英文大寫起頭。

繼續前,就讓我們先閱讀一小段程式︰

#
>>> pyDatalog.create_terms('X站名, Y站名, Z站名, P路徑甲, P路徑乙')

# 定義雙向性規則
>>> 連接(X站名, Y站名) <= 連接(Y站名, X站名)
連接(X站名,Y站名) <= 連接(Y站名,X站名)

# 兩站當然『能達』?
>>> 能達(X站名, Y站名) <= 能達(X站名, Z站名) & 連接(Z站名, Y站名) & (X站名 != Y站名) 
能達(X站名,Y站名) <= 能達(X站名,Z站名)&連接(Z站名,Y站名)&!=(X站名,Y站名)

>>> 能達(X站名, Y站名) <= 連接(X站名, Y站名)
能達(X站名,Y站名) <= 連接(X站名,Y站名)

# 測試
>>> print(能達(X站名, '世貿台北101'))
X站名   
------
雙連    
松江南京  
大安森林公園
中山    
善導寺   
台大醫院  
科技大樓  
台北小巨蛋 
行天宮   
中正紀念堂 
南京復興  
中山國中  
台北車站  
忠孝敦化  
西門    
小南門   
龍山寺   
忠孝新生  
東門    
北門    
大安    
古亭    
忠孝復興  
信義安和  

# 嘗試驗證
>>> Ans = pyDatalog.ask("能達(X站名, '世貿台北101')")
>>> Ans.answers
[('東門',), ('西門',), ('龍山寺',), ('忠孝敦化',), ('台北車站',), ('中正紀念堂',), ('台北小巨蛋',), ('中山國中',), ('科技大樓',), ('台大醫院',), ('南京復興',), ('中山',), ('行天宮',), ('善導寺',), ('信義安和',), ('大安森林公園',), ('松江南京',), ('忠孝復興',), ('雙連',), ('大安',), ('北門',), ('小南門',), ('忠孝新生',), ('古亭',)]
>>> Ans1 = pyDatalog.ask("能達('世貿台北101', X站名)") 
>>> set(Ans1.answers) == set(Ans.answers)
True
>>> Ans2 = pyDatalog.ask("能達('忠孝新生', X站名)") 
>>> set(Ans.answers + [('世貿台北101',)]) == set(Ans2.answers +[('忠孝新生',)])
True
>>> 

 

如果此刻我們思考如下三條『規則』︰

‧ 連接(X站名, Y站名) <= 連接(Y站名, X站名)

‧ 能達(X站名, Y站名) <= 能達(X站名, Z站名) & 連接(Z站名, Y站名) & (X站名 != Y站名)

‧ 能達(X站名, Y站名) <= 連接(X站名, Y站名)

,加上『事實陳述』到底完成了些什麼?辜且不論其它規則擴張,僅就此事而論,寫作一個『對當的』 equivalent 演算法程序,搜尋『網路』這種『資料結構』也不簡單!一不小心還很容易犯錯!!如是假使『邏輯編程』語言越『純』,越用不著『程序』的『控制 』,至此也許可以說所謂『程式』就是『知識的呈現』,那個自動『推理引擎』,早已建置在能夠『呈現知識』的語言中了。

不管是出於『必要』,或者是『好奇』,我們能夠如何『驗證』 pyDatalog 語言的『推論』是正確的呢?這個

Ans = pyDatalog.ask(“能達(X站名, ‘世貿台北101’)”)

Ans2 = pyDatalog.ask(“能達(‘忠孝新生’, X站名)”)

set( Ans.answers + [ ( ‘世貿台北101’ ,  ) ] )   ==   set( Ans2.answers + [  ( ‘忠孝新生’ ,  ) ] )

True 

一方面為了這個『驗證』目的,另一方面提示 pyDatalog 『文件』中沒有講的部份。【※ 請讀者先行思索,要如何知道的耶?】如果我們對這二十五個捷運站,都做了這樣的『測試』,那麼我們能否做出『已驗證』的結論呢?!

就讓我們仿效 Peter Flach 定義一個稱之為『鄰近』 nearby 述詞︰

‧ 鄰近(X站名, Y站名) <= 連接(X站名, Z站名) & 連接(Z站名, Y站名) & (X站名 != Y站名)

‧ 鄰近(X站名, Y站名) <= 連接(X站名, Y站名)

多作一些『確認』︰

# 定義兩站『鄰近』為兩站『連接』或相距一站之遙
>>> 鄰近(X站名, Y站名) <= 連接(X站名, Z站名) & 連接(Z站名, Y站名) & (X站名 != Y站名)
鄰近(X站名,Y站名) <= 連接(X站名,Z站名)&連接(Z站名,Y站名)&!=(X站名,Y站名)

>>> 鄰近(X站名, Y站名) <= 連接(X站名, Y站名)
鄰近(X站名,Y站名) <= 連接(X站名,Y站名)

# 再次驗證檢查
>>> print(鄰近('雙連', X站名))
X站名 
----
中山  
北門  
松江南京
台北車站
>>> 

,假使我們也對這二十五個捷運站,都做了這樣的『測試』,那麼我們能否做出『已驗證』的結論呢!?也許我們應當仔細的思考︰一隻黑天鵝能證明,不是所有的天鵝都是白的;但是再多的白天鵝雖能增加可信度,卻邏輯上不可能證明所有的天鵝都是白的。因此『二十五個捷運站』正確的程式,用於『□□個捷運站』只可能是正確的吧!!

 

── 待續………