人工智能實驗(A*,BP)
實驗一 A*算法
一、實驗目的:
熟悉和掌握啟發式搜索的定義、估價函數和算法過程,並利用A*算法求解N數碼難題,理解求解流程和搜索順序。
二、實驗原理:
A算法是一種啟發式圖搜索算法,其特點在於對估價函數的定義上。對於一般的啟發式圖搜索,總是選擇估價函數f值最小的節點作為擴展節點。因此,f是根據需要找到一條最小代價路徑的觀點來估算節點的,所以,可考慮每個節點n的估價函數值為兩個分量:從起始節點到節點n的實際代價以及從節點n*到達目標節點的估價代價。
三、實驗內容:
1 參考A算法核心代碼,以8數碼問題為例實現A算法的求解程序(編程語言不限),要求設計兩種不同的估價函數。
估價函數1:代價函數為擴展的層數,啟發函數為數碼中不在位的數字的個數。
估價函數2:代價函數位擴展的層數,啟發函數為由當前狀態當目標狀態所有節點需要移動的次數,即曼哈頓距離。
2 在求解8數碼問題的A*算法程序中,設置相同的初始狀態和目標狀態,針對不同的估價函數,求得問題的解,並比較它們對搜索算法性能的影響,包括擴展節點數、生成節點數等。
算法流程圖:
算法思路
定義一個h(n) = f(n)+g(n),即定義一個代價函數和啟發函數,代價函數這里取其探索的層數,啟發函數為不在位的數碼數碼。
f(n):探索的層數
g(n):不在位的數碼個數。
從初始結點開始,擴展可能的節點,並從可能節點中選取代價最小的節點進行下一步的擴展。
#獲取給定數碼的坐標
def get_loc(num, _arr): # 返回給定節點的坐標點
_arr = np.array(_arr).reshape(3, 3)
for i in range(len(_arr)):
for j in range(len(_arr[i])):
if num == _arr[i][j]: # 找到值所在的位置並返回
return i, j
# 定義啟發函數(1.以不在位的數量值為啟發函數進行度量,2.初始狀態和最終狀態的節點曼哈頓距離,3.寬度優先啟發為0)
def val(arr, arr_final, method=0):
if method == 1: # 曼哈頓距離
_arr = np.array(arr).reshape(3, 3)
_arr_final = np.array(arr_final).reshape(3, 3)
total = 0
for i in range(len(_arr)):
for j in range(len(_arr[i])):
m, n = get_loc(_arr[i][j], arr_final) # 找到給定值的橫坐標和縱坐標
total += np.abs(i - m) + np.abs(j - n) # 計算所有節點的曼哈頓距離之和
return total
if method == 2: # 寬度優先
return 0
# 不在位數量
total = []
for i in range(len(arr)): # 計算list中對不上的數量,0除外
if arr[i] != 0:
total.append(arr[i] - arr_final[i])
return len(total) - total.count(0)
# 定義一個函數用來執行,矩陣的變換工作,即移動"0",這里以0來代替空格的移動
def arr_swap(arr, destnation, flag=False):
if flag: # 如果flag為true那么直接修改矩陣
z_pos = arr.argmin() # 獲得0的位置
tmp = arr[destnation] # 和要修改的位置進行交換
arr[destnation] = arr[z_pos]
arr[z_pos] = tmp
return list(arr) # 返回結果
# 如果flag為false,不修改傳入的矩陣,而返回一個修改后的副本
_arr = np.array(arr.copy()) # 創建副本
z_pos = _arr.argmin() # 獲得0的位置
tmp = _arr[destnation] # 交換位置
_arr[destnation] = _arr[z_pos]
_arr[z_pos] = tmp
return list(_arr) # 返回副本
#定義節點類,用來記錄節點之間的信息和方便后續的路徑查找
class node:
par = None
value = -1
arr = None
step = 0
def __init__(self, p, val, a, s): # 根據傳入的值初始化節點
self.par = p
self.step = s
self.value = val
self.arr = np.array(a)
#定義向上移動的函數
def up(self, ss): # 定義節點中"0"向上為一個函數,
if np.array(self.arr).argmin() - 3 >= 0: # 當能夠向上移動時,返回向上移動后的節點
# 返回后的節點,計算啟發值,更新數組,並將新節點的值父節點設置為調用函數的節點
tmp = np.array(self.arr).argmin() - 3
ar = arr_swap(self.arr, tmp)
v = val(ar, arr_final, ss)
v += self.step + 1
new_node = node(p=self, val=v, a=ar, s=self.step + 1)
return new_node # 返回生成的子節點
else:
return None
#同理定義 向下,向左和向右的函數 不贅述,若要完整代碼去翻到最后的GitHub上下載即可。
檢查是否要擴展的節點已經生成,或者還沒生成
# 定義函數用來判斷,一個節點是否在生成的表中
def in_open(t, openl):
for i in openl:
if all(i.arr == t.arr): # 如果找到了arr相同的節點
if t.value < i.value: # 更新啟發值為較小的一個節點
i.value = t.value
# print("update")
return True # 該節點在open表中,返回true
return False # 節點不在open表中,返回false
# 定義函數用來判斷,一個節點是否在已經結束生成的表中
def in_close(t, closel, openl):
for i in closel:
if all(i.arr == t.arr): # 如果在結束的表中找到了相同的節點信息
if t.value < i.value: # 如果傳入的節點啟發值更優於close中的節點,那么說明有其他生成該節點的方式更優
i.value = t.value
openl.append(t) # 加入該節點到open表中
#
return True # 在close表中,返回true
return False # 不在則返回false
A*算法的主函數
# 開始進行循環查找
def Astar(arr_start, arr_final, val_method=1):
start = node(None, val(arr_start, arr_final, val_method), arr_start, 0)
# 定義兩個表,open,和close用於記錄正在准備生成的節點,和已經生成的節點
open_l = []
close_l = []
# 將start節點加入到open表中
open_l.append(start)
n = start
step = 0
while (1):
step += 1
# 節點開始進行生成,向上,向下,向左,向右運行,查看滿足生成條件。
# 節點的生成分為三種情況:
# (1)在open表中,那么將節點的啟發值更新為比較優的值
# (2)在close表中,如果新生成的節點啟發值更優,則加入該節點但open表中
# (3)如果都不在表中,那么直接加入當open表中
#
# len1=len(open_l)
# cnt=0
if n.up(val_method) is not None:
tmp = n.up(val_method)
f1 = in_open(tmp, open_l) # 是否在open中
f2 = in_close(tmp, close_l, open_l) # 是否在close中
if f1 == False and f2 == False: # 兩者都不在,加入open
open_l.append(n.up(val_method))
if n.down(val_method) is not None:
tmp = n.down(val_method)
f1 = in_open(tmp, open_l) # 是否在open中
f2 = in_close(tmp, close_l, open_l) # 是否在close中
if f1 == False and f2 == False: # 兩者都不在,加入open
open_l.append(n.down(val_method))
if n.left(val_method) is not None:
tmp = n.left(val_method)
f1 = in_open(tmp, open_l) # 是否在open中
f2 = in_close(tmp, close_l, open_l) # 是否在close中
if f1 == False and f2 == False: # 兩者都不在,加入open
open_l.append(n.left(val_method))
if n.right(val_method) is not None:
tmp = n.right(val_method)
f1 = in_open(tmp, open_l) # 是否在open中
f2 = in_close(tmp, close_l, open_l) # 是否在close中
if f1 == False and f2 == False: # 兩者都不在,加入open
open_l.append(n.right(val_method))
# 生成結束后,將生成完畢的節點移出open表中
open_l.remove(n)
# print("({name1},{name2},cnt={name3})".format(name1=len1,name2=len(open_l),name3=cnt))
if len(open_l) == 0: # 如果表元素為空,則退出
break
close_l.append(n) # 將該節點加入到close表中
node_v = [] # 新的open表中,各個節點的啟發值記錄
for i in open_l:
node_v.append(i.value) # 加入每個節點的啟發值
min_posi = np.array(node_v).argmin() # 找到最小的
n = open_l[min_posi] # 將最小的節點作為下一個循環要生成的節點
if list(n.arr) == list(arr_final): # 如果要生成的節點滿足最終狀態的需要,那么退出尋找
break
return start, n, step, len(close_l), len(open_l) + len(close_l)
對生成完的路徑進行輸出
def print_put(n, start):
final = [] # 最終的節點路線
ptr = n # 用於循環查找,n為最后一次生成的節點
index = [] # 查看節點的step
while ptr.par is not None:
# 查找父節點。加入當final中。
final.append(ptr.arr)
index.append(ptr.step)
ptr = ptr.par
# 輸出如何得到最后的輸出。
print(start.arr.reshape(3, 3))
for i in range(len(final)):
print("step: ", i + 1)
print(np.array(final[len(final) - i - 1]).reshape(3, 3))
判斷一個數碼問題是否有解
def getStatus(arr): # 用序偶奇偶性判斷是否有解,序偶相同的是一個等價集可以通過變換得到
sum = 0
for i in range(len(arr)):
for j in range(0, i):
if arr[j] < arr[i] and arr[j] != 0:
sum += 1
return sum % 2
最終結果展示
實驗二 BP網絡
BP網絡結構為(輸入層,隱藏層,輸出層)
一、實驗目的:
理解BP神經網絡的結構和原理,掌握反向傳播學習算法對神經元的訓練過程,了解反向傳播公式。通過構建BP網絡模式識別實例,熟悉前饋網絡和反饋網絡的原理及結構。
二、實驗原理
BP學習算法是通過反向學習過程使誤差最小,其算法過程從輸出節點開始,反向地向第一隱含層(即最接近輸入層的隱含層)傳播由總誤差引起的權值修正。BP網絡不僅含有輸入節點和輸出節點,而且含有一層或多層隱(層)節點。輸入信號先向前傳遞到隱節點,經過作用后,再把隱節點的輸出信息傳遞到輸出節點,最后給出輸出結果。
1.針對教材例8.1,設計一個三層的BP網絡結構模型,並以教材圖8.5 為訓練樣本數據,圖8.6為測試數據。
主要的代碼即實現一個前饋的網絡
#計算得到輸出的過程
def feedforward(self, data_index):
# 隱藏層的數據
self.hidden_data = [0 for i in range(self.hidden_num)]
# 輸出層的數據
self.output_data = [0 for i in range(self.outpu_num)]
# print self.hidden_data
# 只對一條數據進行訓練的
# 得到隱藏層的數據
for i in range(self.hidden_num):
total = 0.0
for j in range(len(self.train_data[0])):
total += self.train_data[data_index][j] * self.i_h_weight[j][i]
total += self.hidde_b[i]
self.hidden_data[i] = self.sigmod(total)
# 得到輸出層的數據
for i in range(self.outpu_num):
total = 0.0
for j in range(self.hidden_num):
total += self.hidden_data[j] * self.h_o_weight[j][i]
total += self.output_b[i]
self.output_data[i] = self.sigmod(total)
return self.output_data[0]
# BP反饋網絡
def feedback(self, MM, data_index):
# 前饋網絡
self.feedforward(data_index)
# #更新隱藏層到輸出層的weight和b
for i in range(len(self.output_data)):
# 求導后的就是兩個做差
self.error[i] = self.train_label[data_index][i] - self.output_data[i]
for i in range(self.outpu_num): #遍歷每個out節點
for j in range(self.hidden_num):# 更新每個hidden的節點對於i 的誤差
# 權重 : = 權重+學習率*導數
self.h_o_weight[j][i] += MM * self.hidden_data[j] * self.error[i] * self.output_data[i] * (
1 - self.output_data[i])
self.output_b[i] += MM * self.output_data[i] * (1 - self.output_data[i]) * self.error[i]
# 更行輸入層到輸出層的weight和b
for i in range(self.hidden_num):#遍歷每個hidden節點
sum_ek = 0.0
for k in range(self.outpu_num):
#計算每個input的節點對於該節點的誤差
sum_ek += self.h_o_weight[i][k] * self.error[k] * self.output_data[k] * (1 - self.output_data[k])
for j in range(len(self.train_data[0])):
self.i_h_weight[j][i] += MM * self.hidden_data[i] * (1 - self.hidden_data[i]) * \
self.train_data[data_index][j] * sum_ek
self.hidde_b[i] += MM * self.hidden_data[i] * (1 - self.hidden_data[i]) * sum_ek
#訓練的函數
def train(self, train_num, MM ):
for i in tqdm(range(train_num)):
#這里的10是hiddenlayer中有10個units
for j in range(10):
self.feedback(MM, j)
#預測
def predict(self, input_data):
total = []
for i in range(self.hidden_num):
result = 0
for j in range(len(input_data)):
result += input_data[j] * self.i_h_weight[j][i]
total.append(self.sigmod(result))
final = []
for i in range(self.outpu_num):
r2 = 0
for j in range(self.hidden_num):
r2 += total[j] * self.h_o_weight[j][i]
final.append(self.sigmod(r2))
out_lable = []
for i in final:
max_arg = np.array(i).argmax()
out_lable.append(max_arg)
return final
結果
有空再更新GA和產生式。代碼在github上可以下載