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