人工智能实验(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上可以下载

  • 2
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值