DQN实验报告

一、DQN实现方式

助教给的参考代码由两个文件组成,一个是game.py,一个是train.py。game.py的内容是迷宫界面绘制和agent行走方式、奖励规则的有关代码,而train.py中是神经网络、训练DQN神经网络以及寻找最路径的代码。

我在完成作业时,由于不需要单独进行界面绘制,所以就将助教game.py中的内容和train.py中的内容写在了一起。但是总体上的内容还是不变的。大致可以分为以下几个部分,Env类、trans_torch函数、Net类、DQN类以及训练神经网络的相关代码和寻找最短路径的相关代码。

1Env

Env类分为两个部分。

一个部分为start_env方法,主要负责模拟训练过程中agent状态的初始化。另一个部分为step函数,负责对action作出响应,改变agent的状态以及返回游戏状态和当步动作的奖励值。

Trans_torch负责把当前agent的状态转化为可卷积的形式以便于卷积神经网络提取特征。

2Net

Net类则是定义了网络结构和前向传播方式。

在Net类的初始化方法中定义了网络的结构并对全连接层的权重进行了初始化。本实验中用到的网络结构由输入为三通道的有25个大小为5x5的filter的卷积神经网络,输入为7225输出为16的全连接层以及输入为16输出为4的全连接层依次相连构成。

Net类的forward方法中则固定了该网络前向计算的过程,最后返回网络前向计算的结果。

3DQN

DQN类由四个方法构成。

首先是初始化方法。初始化方法中定义了两个网络,一个是eval_net,一个是target_net。Target_net在一段时间内是保持不变的,eval_net在更新100次之后会把自己的全部参数值复制给target_net。除此之外,初始化方法中还定义了优化器和损失函数,需要注意的是这里优化器已经定义为了对eval_net的优化器。

DQN中的choose_action方法采用epsilon_greedy策略,以epsilon的概率选择贪心策略,以1-epsilon的概率选择随机策略。

Store_transitions方法定义了将新的采样数据加入记忆库的方式。

Learn方法完整描述了网络的训练过程。具体操作是,每次进行学习时,从记忆库中随机抽取32个数据。将先之间通过eval_net和动作预测当前状态下的动作价值函数得到q_eval,再通过用tatget_net和下一状态预测下一个状态的动作价值函数,然后选取最大的那个与当步收益一起近似最优收益,得到q_target。(说的太复杂了,重来。)简而言之这个就是Q_learning把q_tabel变成net之后的样子,选用两个网络可以很好的模拟进行Q_learning时,在遍历完一遍所有状态之前,前面的选择并不会改变后面状态的价值函数的这一特性。这里的网络训练目标就是最小化q_eval和q_target。(实际上就是让q_tabel尽可能收敛)具体操作就是计算两者的loss,然后用optimizer进行梯度计算,然后进行反向传递,然后更新网络中各参数的值。

4、训练网络的有关代码

训练网络的有关代码主要决定了采样的次数和学习开始的时机,我在观察了实验结果后将迭代的幕数调到了603次,并且当幕数大于400时直接采用贪心算法,以便让结果更快收敛。此处开始学习的标准是记忆库满,即样本数大于2000时开始学习。一旦开始学习后,每一次采样eval_net的参数都会更新,但是当样本数目小于2000时,eval_net是不会学习的,这一点非常关键。如果忽略这一点会造成很多问题,具体问题见第二版块。

5、寻找最短的有关代码

最后寻找最短路径就是按照eval_net给出的方案走,然后结合当前状态和动作输出下一时刻的状态即可,唯一需要注意的就是把二维坐标转换成一维的。

二、遇到的问题和解决方案

这次作业遇到的问题主要有三个,但是除了这三个之外还有一个是MC的作业中遇到的,也是导致我MC作业跑不出来结果的原因,我也觉得非常重要,所以一起列在这里。

1agent原地打转

这个是mc作业中遇到的问题,也是在听了同学的建议后获得了启发。在每一幕开始时设置一个空列表tmp,当agent第一次走到一个位置时,就把这个位置加到列表中。当agent再次走到这个位置时,将得到惩罚。为达到这一目的,tmp列表需要作为参数传到返回奖励值的函数中。

2agent走不到终点

一开始我盲目照抄参考代码但是发现出现了一个非常奇怪的问题就是agent明明到终点了但是不停下来,反而又走到了其他的地方。在一个一个打印状态后我终于发现了问题所在。

参考代码中是先修改agent的状态(agent每到达一个方块,该方块的值就会变成1,而之前agent所在方块的值则变为0),再判断是否到达终点。因为参考代码中判断终点是用方块所在的位置判断的而不是用方块的值判断的,所以在运行时是正常的。但是在我的世界的情景下,由于是空气块的地方太多,只能用方块的值判断是否是陷阱,所以在判断终点的时候我也理所当然用方块的值进行了判断。但是由于我在到达终点时先修改了agent所在的状态,把agent所在的地方的值改成了1,导致终点的值变成了1而不是初始时的2,进一步导致下一步判定是否为终点时出错,无法走到终点。

3、卷积核的大小

卷积核太大会加大学习的困难程度,导致学习不到有用的特征,并且会减慢程序的运行速度。

4、运行了1000多幕agent还在重复犯错

在运行了十几次后发现这种情况经常出现后我对神经网络的学习能力产生了深刻的怀疑。后来才发现不是神经网络的问题,而是我自己的问题。导致这个问题的原因也是盲目照抄参考代码而忽略了走迷宫和Minecraft中找最短路径的地图特征方面的差异。

走迷宫问题的障碍物极少,所以参考代码中遇到陷阱就结束这一幕开始下一幕的设定是合理的,但是MineCraft里面空气格的数量远远多于非空气格的数量,即陷阱数量远远多于非陷阱的格子的数量。如果照搬参考代码里的设定,让agent可以走到陷阱格里,并且走到陷阱格就结束这一幕开始下一幕的话,很有可能出现一千多幕都是第一步就掉到陷阱里,只采集了一个状态样本就结束了一幕。

由于eval_net必须在样本量到达了两千后才会开始学习,所以样本总量到达两千之前agent就是在随便走。因为MineCraft中agent很容易走到陷阱里,所以前一千多幕都是走一步就掉进陷阱里的现象非常有可能发生。这样不仅会使幕数增多,还会带来一个问题就是数据雷同,因为agent在经历一千多幕开始学习之后很有可能学到的只是前一两个状态怎么走,整个学习过程就会变得及其缓慢。

改进方式:让agent不要往陷阱里走,杜绝一开始就调入陷阱的情况发生。同时,为了避免初始时agent因为是随机走会一直在原地打转造成死循坏,人为设定agent在走了20步之后如果还没有到达终点就结束该幕开始下一幕。

三、实验总结

不要照搬参考代码,在不同情景下需要特别注意如何设置每一幕的终止条件以便让神经网络能够在更短时间内学习到更多内容。找不到问题时把状态和动作都打印出来,模拟agent的行走路径走一走,有时可以带来启发。还有在使用到神经网络时,要注意神经网络开始学习的触发条件;另外,可以注意观察agent在每一幕开始时选择的动作的特点(当陷阱格很多的时候),这个可以反应出神经网络的学习成果。

核心代码如下

import random
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as Fgrid = list()
for i in range(441):grid.append("air")grid[217] = "emerald_block"grid[218] = "diamond_block"
grid[219] = "diamond_block"
grid[220] = "diamond_block"
#grid[221] = "diamond_block"
grid[238] = "diamond_block"
#grid[241] = "diamond_block"
grid[259] = "diamond_block"
grid[263] = "diamond_block"
grid[280] = "diamond_block"
grid[282] = "diamond_block"
#grid[284] = "diamond_block"
grid[301] = "diamond_block"
grid[302] = "diamond_block"grid[303] = "redstone_block"#参数
BATCH_SIZE = 32
LR = 0.01                   # 学习率
EPSILON = 0.9               # 最优选择动作百分比(有0.9的几率是最大选择,还有0.1是随机选择,增加网络能学到的Q值)
GAMMA = 0.9                 # 奖励递减参数(衰减作用,如果没有奖励值r=0,则衰减Q值)
TARGET_REPLACE_ITER = 100   # Q 现实网络的更新频率100次循环更新一次
MEMORY_CAPACITY = 2000      # 记忆库大小
N_ACTIONS = 4  # 棋子的动作0,1,2,3
N_STATES = 1class Env():def start_env(self, grid):migoing = np.zeros([21, 21], dtype=np.int32)for i in range(21):for j in range(21):if grid[i*21 + j] == "air":migoing[i][j] = 3elif grid[i*21 + j] == "diamond_block":migoing[i][j] = 0elif grid[i*21 + j] == "emerald_block":migoing[i][j] = 1x1 = iy1 = jelif grid[i*21 + j] == "redstone_block":migoing[i][j] = 2self.x1 = x1self.y1 = y1self.migoing = migoingself.end_game = 0return self.migoingdef step(self, action, tmp):    # tmp为列表,元素为元组,记录已经走过的位置r = 10self.end_game = 0if action[0] == 0: # 向上走if self.x1 == 0:r = -20else:self.x1 -= 1#print((self.x1,self.y1))#print(self.migoing[self.x1][self.y1])if self.migoing[self.x1][self.y1] == 3:   # 走到空气中self.x1 += 1self.end_game = 1r = -40elif self.migoing[self.x1][self.y1] == 2: #走到终点,游戏成功self.end_game = 2r = 40if self.end_game == 0:self.migoing[self.x1][self.y1] = 1self.migoing[self.x1+1][self.y1] = 0if action[0] == 1: # 向下走if self.x1 == 20:r = -20else:self.x1 += 1#print((self.x1,self.y1))#print(self.migoing[self.x1][self.y1])if self.migoing[self.x1][self.y1] == 3:   # 走到空气中self.end_game = 1self.x1 -= 1r = -40elif self.migoing[self.x1][self.y1] == 2: #走到终点,游戏成功self.end_game = 2r = 40if self.end_game == 0:self.migoing[self.x1][self.y1] = 1self.migoing[self.x1-1][self.y1] = 0if action[0] == 2: # 向左走if self.y1 == 0:r = -20else:self.y1 -= 1#print((self.x1,self.y1))#print(self.migoing[self.x1][self.y1])if self.migoing[self.x1][self.y1] == 3:   # 走到空气中self.end_game = 1self.y1 += 1r = -40elif self.migoing[self.x1][self.y1] == 2: #走到终点,游戏成功self.end_game = 2r = 40if self.end_game == 0:self.migoing[self.x1][self.y1] = 1self.migoing[self.x1][self.y1+1] = 0if action[0] == 3: # 向右走if self.y1 == 20:r = -20else:self.y1 += 1#print((self.x1,self.y1))#print(self.migoing[self.x1][self.y1])if self.migoing[self.x1][self.y1] == 3:   # 走到空气中self.end_game = 1self.y1 -= 1r = -40elif self.migoing[self.x1][self.y1] == 2: #走到终点,游戏成功self.end_game = 2r = 40if self.end_game == 0:self.migoing[self.x1][self.y1] = 1self.migoing[self.x1][self.y1-1] = 0if (self.x1, self.y1) in tmp:r = -20else:tmp.append((self.x1, self.y1))return self.end_game, r, self.migoing, tmpdef trans_torch(list1):list1=np.array(list1)l1=np.where(list1==1,1,0) # 标识agent所在位置l2=np.where(list1==2,1,0)   # 标识障碍物所在位置l3=np.where(list1==3,1,0)   # 标识终点位置 b=np.array([l1,l2,l3])      # 拼接得到三维目标矩阵,第一个矩阵为agent所在位置,第二个矩阵为障碍物所在位置,第三个矩阵为终点所在位置return b#神经网络
class Net(nn.Module):def __init__(self):super(Net, self).__init__()# 输入为441,输出为4       # torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True)self.c1=nn.Conv2d(3,25,5,1,0) # 对应上方的前5个参数  25个filter每个filter的卷积结果为1,最后输出为25*1*1,相当于把二维拉平self.f1=nn.Linear(7225,16) # 全连接层 输入441,输出16self.f1.weight.data.normal_(0, 0.1) # 初始化权重self.f2=nn.Linear(16,4) # 隐藏层self.f2.weight.data.normal_(0, 0.1) # 初始化权重def forward(self, x):# Input: (N, C_{in}, H_{in}, W_{in})x=self.c1(x)# size: N x 441 x 1 x 1 对应 N C H Wx=F.relu(x)x = x.view(x.size(0),-1) # size: N x 441x=self.f1(x)x=F.relu(x)   action=self.f2(x) # size: N x 4return action# 以上为前向传播网络,不涉及到学习权重的部分,相当于输入x(三维矩阵 3*21*21),输出该状态下执行四个动作的可能性class DQN(object):def __init__(self):self.eval_net, self.target_net = Net(), Net() #DQN需要使用两个神经网络#eval为Q估计神经网络 target为Q现实神经网络self.learn_step_counter = 0 # 用于 target 更新计时,100次更新一次self.memory_counter = 0 # 记忆库记数self.memory = list(np.zeros((MEMORY_CAPACITY, 4))) # 初始化记忆库用numpy生成一个(2000,4)大小的全0矩阵,self.optimizer = torch.optim.Adam(self.eval_net.parameters(), lr=LR) # torch 的优化器 ,此处已经指定了优化的是eval_netself.loss_func = nn.MSELoss()   # 误差公式def choose_action(self, x):# size: 3x5x5 (batchsize = 1, channels/depth = 3, height = 21, width = 21)选动作是传入一个样本,即当前状态x = torch.unsqueeze(torch.FloatTensor(x), 0) # size: 1x3x21x21 (.FloatTensor将x转化为tensor类型; .unsqueeze在0位置扩展一个维度)# 这里只输入一个 sample,x为场景if np.random.uniform() < EPSILON:   # 选最优动作actions_value = self.eval_net.forward(x) #将场景输入Q估计神经网络#torch.max(input,dim)返回dim最大值并且在第二个位置返回位置比如(tensor([0.6507]), tensor([2]))action = torch.max(actions_value, 1)[1].data.numpy() # 返回动作最大值else:   # 选随机动作action = np.array([np.random.randint(0, N_ACTIONS)]) # 比如np.random.randint(0,2)是选择1或0return actiondef store_transition(self, s, a, r, s_):# 如果记忆库满了, 就覆盖老数据,2000次覆盖一次index = self.memory_counter % MEMORY_CAPACITYself.memory[index] = [s,a,r,s_]self.memory_counter += 1def learn(self):# target net 参数更新,每100次if self.learn_step_counter % TARGET_REPLACE_ITER == 0:# 将所有的eval_net里面的参数复制到target_net里面self.target_net.load_state_dict(self.eval_net.state_dict())self.learn_step_counter += 1# target_net是固定的# 抽取记忆库中的批数据# 从2000以内选择32个数据标签sample_index = np.random.choice(MEMORY_CAPACITY, BATCH_SIZE)b_s=[]b_a=[]b_r=[]b_s_=[]for i in sample_index:b_s.append(self.memory[i][0])b_a.append(np.array(self.memory[i][1],dtype=np.int32))b_r.append(np.array([self.memory[i][2]],dtype=np.int32))b_s_.append(self.memory[i][3])b_s = np.array(b_s)#取出s, b_a的每一个元素都是一个1x1的数组,所以b_a的size是32*1b_a = np.array(b_a) #取出ab_r = np.array(b_r) #取出rb_s_ = np.array(b_s_) #取出s_    #print(b_r.shape)#print(b_a.shape)b_s = torch.FloatTensor(b_s)#取出sb_a = torch.LongTensor(b_a) #取出ab_r = torch.FloatTensor(b_r) #取出rb_s_ = torch.FloatTensor(b_s_) #取出s_        # s是32 * 3 * 5 * 5的张量# 针对做过的动作b_a, 来选 q_eval 的值, (q_eval 原本有所有动作的值)q_eval = self.eval_net(b_s).gather(1, b_a)  # shape (batch, 1) 找到action的Q估计(关于gather使用下面有介绍)#a.gather(0, b) a-被提取元素的矩阵, 提取元素的维度为0, 提取元素在该维度的索引为b# eval_net用于计算当前状态的期望收益,target_net用于计算下一步的期望收益# 实际上与离散时用的Q_table是一样的,因为计算下一步的最大期望收益时,所用的数据并没有因为当前状态对应的q_table的更新而受到影响q_next = self.target_net(b_s_).detach()     # q_next 不进行反向传递误差, 所以 detach Q现实q_target = b_r + GAMMA * q_next.max(1)[0].view(BATCH_SIZE, 1)   # shape (batch, 1) DQL核心公式#print(b_r)#print(q_next.max(1)[0])#print(q_target.shape)# print(1)loss = self.loss_func(q_eval, q_target) #计算误差, 因为此处更新的是eval_net,所以q_target是作为标准出现# 计算, 更新 eval netself.optimizer.zero_grad() #loss.backward() #反向传递self.optimizer.step()return loss###
# 训练网络
dqn = DQN() # 定义 DQN 系统
#400步
study=1
env=Env()for i_episode in range(503):print(i_episode,'epoch')s = env.start_env(grid) # size: s=trans_torch(s) # size: 3x21x21loss=0tmp = []if i_episode > 300:EPSILON = 1cnt = 0while True:cnt += 1if cnt > 20:break# env.display()   # 显示实验动画a = dqn.choose_action(s) #选择动作# 选动作, 得到环境反馈print((env.x1, env.y1, a))done,r,s_,tmp = env.step(a, tmp) #print(done)s_=trans_torch(s_) # size: 3x21x21# 存记忆dqn.store_transition(s, a, r, s_)        if dqn.memory_counter > MEMORY_CAPACITY:loss=dqn.learn() # 记忆库满了就进行学习#print(loss)if done==2:    # 如果回合结束, 进入下回合# print(loss)              breaks = s_#print(tmp)if done == 2:print("成功")else:print("失败")# 根据训练好的网络得到最短路径
path = []
path.append(217)
s = env.start_env(grid)
s = trans_torch(s)
EPSILON = 1
while True:tmp = []a = dqn.choose_action(s)done, r, s_, tmp = env.step(a, tmp)path.append((env.x1)*21 + (env.y1))s_= trans_torch(s_)if done==1 or done==2:    # 如果回合结束, 进入下回合# print(loss)if done==1:print('失败')if done==2:print('成功')                breaks = s_print(path)

BUAA 强化学习DQN代码及实验报告参考相关推荐

  1. kaggle经典题--“泰坦尼克号”--0.8275准确率--东北大学20级python大作业开源(附详细解法与全部代码以及实验报告)

    kaggle经典题--"泰坦尼克号"--0.8275准确率--东北大学20级python大作业开源(附详细解法与全部代码以及实验报告) 前言 开发环境 一.导入包: 二.实验数据的 ...

  2. 用强化学习DQN算法玩合成大西瓜游戏!(提供Keras版本和Paddlepaddle版本)

    本文禁止转载,违者必究! 用强化学习玩合成大西瓜 代码地址:https://github.com/Sharpiless/play-daxigua-using-Reinforcement-Learnin ...

  3. 强化学习系列文章(二十八):进化强化学习EvoRL的预实验

    强化学习系列文章(二十八):进化强化学习EvoRL的预实验 最近在研究强化学习解决离散空间的组合优化问题时,接触到了很多进化算法,实际体验也是与RL算法不相上下.进化算法也常用于优化神经网络的参数,C ...

  4. ROS开发笔记(10)——ROS 深度强化学习dqn应用之tensorflow版本(double dqn/dueling dqn/prioritized replay dqn)

    ROS开发笔记(10)--ROS 深度强化学习dqn应用之tensorflow版本(double dqn/dueling dqn/prioritized replay dqn) 在ROS开发笔记(9) ...

  5. 沈航C语言上机实验题答案,大学大一c语言程序设计实验室上机题全部代码答案(实验报告).doc...

    大学大一c语言程序设计实验室上机题全部代码答案(实验报告) C语言实验报告 实验1-1: hello world程序: 源代码: #include main() { printf("hell ...

  6. 北京电大c语言实验作业二,大学大一c语言程序设计实验室上机题全部代码答案(实验报告).doc...

    大学大一c语言程序设计实验室上机题全部代码答案(实验报告).doc C语言实验报告实验1-1:helloworld程序:源代码:#includemain(){printf("hellowor ...

  7. 强化学习DQN(Deep Q-Learning)、DDQN(Double DQN)

    强化学习DQN(Deep Q-Learning).DDQN(Double DQN) _学习记录-有错误感谢指出 Deep Q-Learning 的主要目的在于最小化以下目标函数: J ( ω ) = ...

  8. c语言程序设计综合性设计实验报告,《C语言程序设计》-综合性实验实验报告(参考格式...

    <<C语言程序设计>-综合性实验实验报告(参考格式>由会员分享,可在线阅读,更多相关<<C语言程序设计>-综合性实验实验报告(参考格式(9页珍藏版)>请 ...

  9. c语言实验报告第四章答案,理工大学2010C语言实验报告参考答案

    理工大学2010C语言实验报告参考答案 2010C语言实验报告参考答案 实验一 熟悉C语言程序开发环境及数据描述 四.程序清单 1.编写程序实现在屏幕上显示以下结果: The dress is lon ...

最新文章

  1. ​我国科学家成功研制全球神经元规模最大的类脑计算机
  2. VS2012下基于Glut OpenGL glScissor示例程序:
  3. VMware推出vRealize,全面加速数字化与混合云的迁移
  4. ▲我的css架构理念
  5. RHEL/Fedora/CentOs的系统服务优化
  6. 李洋疯狂C语言之n个人报数,报到3的退出,最后留在场上的是原来的第几位(约瑟夫环)
  7. python 机器学习_基于 Python 语言的机器学习工具Sklearn
  8. angular图片上传
  9. 通过Azure Kinect DK 基于Ubuntu18.04实现室内三维重建(一)
  10. Android笔记 隐式意图demo
  11. 我的网站被黑了,关键词被劫持,总结一下是怎么解决的。
  12. Twitter开源软件项目列表
  13. oracle官方文档下载使用
  14. c语言中strncpy的用法,C语言中函数strcpy ,strncpy ,strlcpy的用法【转】
  15. 网站移动端和pc端服务器是分开的,PC端和移动端选择哪种URL路径比较好
  16. catalina 无法验证macos_macOS Catalina 无法安装是什么原因?
  17. 万字+图片解析计算机网络应用层
  18. python自制免费代理IP服务
  19. 中文图书期刊数据库文献检索
  20. 微电网数字孪生 | 智能时代,部署源网荷储一体化管控平台

热门文章

  1. mysql 千位分隔符号_如何在MySQL记录中放置千位分隔符?
  2. Windows虚拟内存如何设置
  3. Airpods2连接Win10电脑麦克风不可用的情况(例如腾讯会议等场景)
  4. CelebA-Spoof: Large-Scale Face Anti-Spoofing Dataset with Rich Annotations
  5. java输入流输出流的互相转换(不需要经过临时文件啦超方便)
  6. 黑马程序员--Struts2复习笔记
  7. python画图时x轴自定义_python画图系列之个性化显示x轴区段文字的实例
  8. 如何快速打通镜像发布流程?
  9. android root弊端,Root工具有安全风险 Android用户需谨慎
  10. js继承的方法及原理