人工智能導論實驗導航
實驗一:斑馬問題 https://blog.csdn.net/weixin_46291251/article/details/122246347
實驗二:圖像恢復 https://blog.csdn.net/weixin_46291251/article/details/122561220
實驗三:花卉識別 https://blog.csdn.net/weixin_46291251/article/details/122561505
實驗四:手寫體生成 https://blog.csdn.net/weixin_46291251/article/details/122576478
實驗源碼: xxx
1.1實驗介紹
1.1.1實驗背景
演繹推理(Deductive Reasoning)是由一般到特殊的推理方法。與“歸納法”相對。推論前提與結論之間的聯系是必然的,是一種確實性推理。
運用此法研究問題,首先要正確掌握作為指導思想或依據的一般原理、原則;其次要全面了解所要研究的課題、問題的實際情況和特殊性;然后才能推導出一般原理用於特定事物的結論。
演繹推理的形式有三段論、假言推理和選言推理等。在教育工作中, 依據一定的科學原理設計和進行教育與教學實驗等,均離不開此法。
1.1.2實驗目的
本章實驗的主要目的是掌握邏輯與推理相關基礎知識點,了解算法推理相關基礎知識,掌握歸納法和演繹推理的主要步驟,熟悉python編程。
通過實驗,基本掌握邏輯編程的思想,了解邏輯編程與命令式編程的區別。能夠依據給定的事實以及規則編寫代碼,解決邏輯約束問題(CLP)。
1.1.3實驗簡介
5個不同國家且工作各不相同的人分別住在一條街上的5所房子里,每所房子的顏色不同,每個人都有自己養的不同寵物,喜歡喝不同的飲料。
根據以下提示,你能告訴我哪所房子里的人養斑馬,哪所房子里的人喜歡喝礦泉水嗎?
存在的條件匯總:
- 國籍:英國、西班牙、日本、意大利、挪威
- 房子顏色:紅色、綠色、白色、藍色、黃色
- 工作:油漆工、攝影師、外交官、小提琴家、醫師
- 寵物:狗、蝸牛、狐狸、馬、斑馬
- 飲料:茶、牛奶、咖啡、橘子汁、礦泉水
已知條件如下:
- 英國人住在紅色的房子里
- 西班牙人養了一條狗
- 日本人是一個油漆工
- 意大利人喜歡喝茶
- 挪威人住在左邊的第一個房子里
- 綠房子在白房子的右邊
- 攝影師養了一只蝸牛
- 外交官住在黃房子里
- 中間那個房子的人喜歡喝牛奶
- 喜歡喝咖啡的人住在綠房子里
- 挪威人住在藍色的房子旁邊
- 小提琴家喜歡喝橘子汁
- 養狐狸的人所住的房子與醫生的房子相鄰
- 養馬的人所住的房子與外交官的房子相鄰
1.2概要設計
1.2.1手動求解
-
1.羅列初始條件。
-
2.首先由“英國人住在紅色的房子里,綠房子在白房子的右邊,外交官住在黃房子里”以及上述條件可知:
a:紅白綠房子必在右邊三個房子。
b:挪威人必住在黃房子且他是外交官
c:紅房子要么是第三間,要么是第五間。 -
3.然后假設在紅房子在第三間外交官,可得:
a.由表格知挪威人不喝咖啡和牛奶,又意大利喝茶,小提琴家喝桔子汁(挪威人是外交官),所以挪威人喝礦泉水。
b.由小提琴家和桔子汁知,小提琴家不是英國人(喝牛奶),挪威人(礦泉水),意大利人(茶),日本人(油漆工),所以小提琴家是西班牙人。 -
4.結合上述分析可得
a.由“養狐狸的人所住的房子與醫師的房子相鄰”知,醫師不可能在綠色和紅色房子(相鄰寵物不為狐狸),也不可能在黃色房子(外交官),所以醫師是藍色房子。
b.由外交官,小提琴,油漆工的國籍已定,且醫師不是英國人(藍色房子),知攝影師是英國人,養了蝸牛。所以挪威人養了狐狸(養狐狸的人所住的房子與醫師的房子相鄰)。
c.狐狸,馬,蝸牛,狗的主人身份已確定,知斑馬是油漆工,即日本人的寵物。
d.由礦泉水,牛奶,桔子汁,咖啡主人已確定,且意大利人喝茶,知意大利人所住房子為藍色。 -
5.綜合以上分析可得下面的表格:
1.2.2 python求解
通過上述分析,結合python求解邏輯推理問題的常見庫,得到以下思路:
解法1:窮舉法
將所有可能的組合通過全排列的形式窮舉出來
然后根據已有的條件依次匹配
若全部通過則為正確答案
否則為錯誤方案
解法2:
利用google or-tools 。
OR-Tools是一個用於優化的開源軟件套件,用於解決車輛路徑、流程、整數和線性規划以及約束編程等世界上最棘手的問題。
當前ortools提供的優化器包括: - 約束規划 - 線性與混合整數規划 - 路徑規划 - 調度規划 - 網絡規划 - 裝箱。
並且在其GitHub的源代碼中有解決斑馬問題的示例代碼,地址如下:
https://github.com/google/or-tools/blob/master/examples/python/zebra_sat.py
解法3:
使用python邏輯編程kanren庫,將題目中的條件形式化為一個個的表達式,加入約束到kanren的一個集合中,然后利用kanren內置的run求解即可。
綜上:
通過上述對三種方法的分析可知,方法1過於簡單粗暴,能夠簡單的解決問題,但是算法的復雜度太高,不可取。
方法二和方法三都是通過調用python對應的邏輯分析庫完成,但鑒於方法2的google or-tools 官方已有較為完善的示例代碼,與獨立完成實驗的要求不符,故采用方法3。下面只給出方法3的解法設計與分析。
1.3詳細設計
首先導入實驗所需的包
from kanren import *
from kanren.core import lall
下面根據已知條件的性質將條件分為幾組,首先有着確定關系的實體和其屬性的條件,以及同屬於一個實體的屬性間的關系,有如下幾個:
- 英國人住在紅房子里
- 西班牙人養了一條狗
- 日本人是一個油漆工
- 意大利人喝茶。
- 攝影師養了一只蝸牛
- 外交官住在黃房子里
- 外交官住在黃房子里
- 喜歡喝咖啡的人住在綠房子里
- 小提琴家喜歡喝橘子汁
然后介紹kanren中加入一個邏輯表達式的方法:
首先用houses = var() 創建一個邏輯變量house。
然后利用membero函數表示一個約束, membero(item, coll) 表示 item 是 coll集合中的一個成員。如:
(membero, ('Britain', var(), var(), var(), 'red'), houses), # 英國人住在紅房子里
代碼中的var表示任意變量。
5個var()分別代表 國籍、工作、飲料、寵物、屋子顏色
(membero, ('Britain', var(), var(), var(), 'red'), houses), # 英國人住在紅房子里
(membero, ('Spain', var(), var(), 'dog', var()), houses), # 西班牙人養了一條狗
(membero, ('Japan', 'painter', var(), var(), var()), houses), # 日本人是一個油漆工
(membero, ('Italy', var(), 'tea', var(), var()), houses), # 意大利人喝茶。
(membero, (var(), 'Photographer', var(), 'snail', var()), houses), # 攝影師養了一只蝸牛
(membero, (var(), 'diplomat', var(), var(), 'yellow'), houses), # 外交官住在黃房子里
(membero, (var(), var(), 'coffee', var(), 'green'), houses), # 喜歡喝咖啡的人住在綠房子里
(membero, (var(), 'Violinist', 'juice', var(), var()), houses), # 小提琴家喜歡喝橘子汁
然后是表示實體在整體中的確切屬性的條件,有以下兩個:
- 挪威人住在左邊的第一個房子里
- 中間房子的人喜歡喝牛奶
添加方法和第一種一樣,不一樣的是這里用的是eq函數,eq(a,b ) 表示ab相等。
(eq, (('Norwegian', var(), var(), var(), var())
, var(), var(), var(), var()), houses), # 挪威人住在左邊的第一個房子里
(eq, (var(), var(),
(var(), var(), 'milk', var(), var()),
var(), var()), houses), # 中間那個房子的人喜歡喝牛奶
然后是描述兩個實體間的不確定的(模糊的)關系的表達式,有以下幾個:
- 綠房子在白房子的右邊
- 挪威人住在藍房子旁邊。
- 養狐狸的人所住的房子與醫生的房子相鄰
- 養馬的人所住的房子與外交官的房子相鄰
但是在kanren中沒有能夠直接描述以上關系的函數,所以定義了以下兩個函數,用來實現上述要求:
其中利用到的kanren內的函數:
- zip() 函數用於將可迭代的對象作為參數,將對象中對應的元素打包成一個個元組,然后返回由這些元組組成的列表。
- CONDE:一個用於邏輯和/或OR的目標構造函數。
實現的函數:
Left(a,b,x)表示在關系集合x中a在b的右側
Next(a,b,x)表示在關系集合x中a和b相鄰
其中next是借助left表示出來的,即:兩個變量a,b相鄰只有兩種情況,a在b左邊和b在a左邊。
def left(q, p, list):
return membero((q, p), zip(list, list[1:]))
def next(q, p, list): # p q 相鄰意味着要么p在q左邊,要么q在p左邊
return conde([left(q, p, list)], [left(p, q, list)])
然后借助以上兩個函數就可以將對應的四個邏輯表達式表達出來了:
(left, # 綠房子在白房子的右邊
(var(), var(), var(), var(), 'green'),
(var(), var(), var(), var(), 'white'),
houses),
(next, ('Norwegian', var(), var(), var(), var()),
(var(), var(), var(), var(), 'blue'), houses), # 挪威人住在藍房子旁邊。
(next, (var(), 'physician', var(), var(), var()), # 養狐狸的人所住的房子與醫生的房子相鄰
(var(), var(), var(), 'fox', var()), houses),
(next, (var(), 'diplomat', var(), var(), var()), # 養馬的人所住的房子與外交官的房子相鄰
(var(), var(), var(), 'horse', var()), houses),
到這里所有的表達式都已經表達完畢,已經可以利用run運行了,但是此時的表達式中還沒有斑馬和礦泉水這兩個變量,程序處理到他們時會用符號代替,下面添加這兩個變量進入表達式:
(membero, (var(), var(), var(), 'zebra', var()), houses), # 有人養斑馬
(membero, (var(), var(), 'water', var(), var()), houses) # 有人喝水
現在完成了所有表達式和變量的表達。
接下來開始運算。
solutions = run(0, houses, rules_zebraproblem)
根據運算的結果,判斷誰是斑馬的主人以及誰愛喝礦泉水。
判斷是否有解
if len(solutions):
zebra_owner = ""
water_drinker = ""
for i in solutions[0]:
if "zebra" in i:
zebra_owner = i[0] # 找到斑馬的主人
if "water" in i:
water_drinker = i[0] # 找到喝礦泉水的人
print(i)
print('\nzebra_owner:\t\t' + zebra_owner) # 打印結果
print('water_drinker:\t' + water_drinker)
else:
print("no answer")
程序設計完畢,下面進行運行測試。
1.4運行測試
運行程序,輸出結果
分析程序輸出的結果可知,日本人的寵物是斑馬,挪威人愛喝礦泉水。
結合前面手動計算出的結果,可知兩個結果完全一致,程序正確運行。
窮舉法
# -*- coding: GBK -*-
import time
import itertools
""" 本題的思路: 將所有可能的組合通過全排列的形式窮舉出來 然后根據已有的條件依次匹配 若全部通過則為正確答案 否則為錯誤方案 """
# **********************匹配函數******************************
def handle(data):
# 英國人住紅色房子
index = data[0].index('紅色')
if data[1][index] != '英國':
return -1
# 瑞典人養狗
index = data[4].index('狗')
if data[1][index] != '瑞典':
return -2
# 丹麥人喝茶
index = data[2].index('茶')
if data[1][index] != '丹麥':
return -3
# 抽Pall Mall香煙的人養鳥
index = data[3].index('Pall Mall')
if data[4][index] != '鳥':
return -6
# 黃色房子主人抽Dunhill香煙
index = data[3].index('Dunhill')
if data[0][index] != '黃色':
return -7
# 抽Blue Master的人喝啤酒
index = data[3].index('BlueMaster')
if data[2][index] != '啤酒':
return -12
# 德國人抽Prince香煙
index = data[3].index('Prince')
if data[1][index] != '德國':
return -13
# 綠色房子在白色房子左面
index = data[0].index('綠色')
if index == 4: # 綠色房子在最后
return -4
if data[0][index + 1] != '白色':
return -4
# 綠色房子主人喝咖啡
if data[2][index] != '咖啡':
return -5
# 抽Blends香煙的人住在養貓的人隔壁
index = data[3].index('Blends')
cat_index = data[4].index('貓')
if cat_index - index != 1 and cat_index - index != -1:
return -10
# 養馬的人住抽Dunhill香煙的人隔壁
index = data[3].index('Dunhill')
horse_index = data[4].index('馬')
if horse_index - index != 1 and horse_index - index != -1:
return -11
# 抽Blends香煙的人有一個喝水的鄰居
index = data[3].index('Blends')
water_index = data[2].index('水')
if water_index - index != 1 and water_index - index != -1:
return -15
print('找到答案:')
for d_item in data:
print(d_item)
return 0
# **********************全排列列表******************************
# itertools.permutations 返回可迭代對象的所有數學全排列方式
# 以下是所有元素的全排列構成的列表
colour_list_all = list(itertools.permutations(['紅色', '黃色', '藍色', '白色', '綠色'], 5))
country_list_all = list(itertools.permutations(['英國', '丹麥', '挪威', '德國', '瑞典'], 5))
drinks_list_all = list(itertools.permutations(['茶', '水', '咖啡', '啤酒', '牛奶'], 5))
smoke_list_all = list(itertools.permutations(['Pall Mall', 'Dunhill', 'BlueMaster', 'Blends', 'Prince'], 5))
pet_list_all = list(itertools.permutations(['貓', '馬', '魚', '鳥', '狗'], 5))
# **********************過濾后的全排列列表******************************
# 全排列數量過大以下是過濾后出的列表
colour_list = []
country_list = []
drinks_list = []
smoke_list = []
pet_list = []
for colour in colour_list_all:
colour = list(colour)
if colour[1] == '藍色':
colour_list.append(colour)
for country in country_list_all:
country = list(country)
if country[0] == '挪威':
country_list.append(country)
for drinks in drinks_list_all:
drinks = list(drinks)
if drinks[2] == '牛奶':
drinks_list.append(drinks)
# 5! = 120
for i in range(len(smoke_list_all)):
smoke_list.append(list(smoke_list_all[i]))
for i in range(len(pet_list_all)):
pet_list.append(list(pet_list_all[i]))
# **********************遍歷所有可能的方案並進行匹配******************************
# 由於程序采用窮舉法,效率較低,故記錄時間。
start = time.time()
for country in country_list:
for drinks in drinks_list:
for smoke in smoke_list:
for pet in pet_list:
for colour in colour_list:
data_list = [colour, country, drinks, smoke, pet]
# ~ num += 1
if handle(data_list) == 0:
# ~ print("總運行次數 %d" % num)
exit()
end = time.time()
print("\r計算用時:%0.4f秒" % (end - start), end="")
其他解法
利用google or-tools 求解
https://github.com/google/or-tools/blob/master/examples/python/zebra_sat.py
prolog求解
https://blog.csdn.net/weixin_45841983/article/details/109813808