人工智能實驗(A,BP)


人工智能實驗(A*,BP)

實驗一 A*算法

一、實驗目的:

熟悉和掌握啟發式搜索的定義、估價函數和算法過程,並利用A*算法求解N數碼難題,理解求解流程和搜索順序。

二、實驗原理:

A算法是一種啟發式圖搜索算法,其特點在於對估價函數的定義上。對於一般的啟發式圖搜索,總是選擇估價函數f值最小的節點作為擴展節點。因此,f是根據需要找到一條最小代價路徑的觀點來估算節點的,所以,可考慮每個節點n的估價函數值為兩個分量:從起始節點到節點n的實際代價以及從節點n*到達目標節點的估價代價。

三、實驗內容:

1 參考A算法核心代碼,以8數碼問題為例實現A算法的求解程序(編程語言不限),要求設計兩種不同的估價函數。

估價函數1:代價函數為擴展的層數,啟發函數為數碼中不在位的數字的個數。

估價函數2:代價函數位擴展的層數,啟發函數為由當前狀態當目標狀態所有節點需要移動的次數,即曼哈頓距離。

2 在求解8數碼問題的A*算法程序中,設置相同的初始狀態和目標狀態,針對不同的估價函數,求得問題的解,並比較它們對搜索算法性能的影響,包括擴展節點數、生成節點數等。

算法流程圖:

image-20210129170436517

算法思路

定義一個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	

最終結果展示

image-20210129171742568

image-20210129171756322

image-20210129171804665

實驗二 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

結果

image-20210129225253928

有空再更新GA和產生式。代碼在github上可以下載


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM