本文是参考莫凡的代码,参考up主"Explorer2612",up主“EvilGeniusMR”以及自己的一些理解进行的DQN代码总结。
DQN的基本思想最开始在如下文章内提出
文中DQN的伪代码如下
1. 初始化
2. 循环各局游戏——循环1
3. 每一局游戏内的循环——循环2
import torch,gym
import torch.nn.functional as F
import torch.nn as nn
import numpy as np
根据MBGD(小批量梯度下降)的思想,每次学习选用一小批的样本进行学习,而不用单一样本,因为单一样本在计算梯度下降时非常不平稳。
BATCH_SIZE = 32
优化器optimizer能加速神经网络的训练,Adam优化器是最常用的优化器(融合了动量和自适应,采用修正避免了冷启动的问题),本质仍是梯度下降,计算梯度 g g g后通过学习率 υ \upsilon υ更新参数: θ k ⬅ θ k − 1 − υ g \theta_k⬅\theta_{k-1}-\upsilon g θk⬅θk−1−υg
学习率 υ \upsilon υ一般设置为0.01
LR = 0.01
生成一个在[0, 1)内的随机数,根据随机数与 ϵ \epsilon ϵ的大小比较来判断是选择最优动作还是随机选择动作
EPSILON = 0.9
用于计算TD Target: y j = r j + γ m a x Q ′ y_j = r_j +\gamma max Q' yj=rj+γmaxQ′
GAMMA = 0.9
学习100次以后,将目标网络的参数与评估网络的参数更新为一致(100)
TARGET_REPLACE_ITER = 100
经验池最多存储1000条数据,此数可以更改
MEMORY_CAPACITY = 1000
env = gym.make('CartPole-v0').unwrapped
N_ACTIONS = env.action_space.n
N_STATES = env.observation_space.shape[0]
import torch,gym
import torch.nn.functional as F
import torch.nn as nn
import numpy as np
# 超参数:Hyper Parameters
BATCH_SIZE = 32
LR = 0.01 # 学习率 learning rate
EPSILON = 0.9 # greedy policy
GAMMA = 0.9 # 折扣因子 reward discount
TARGET_REPLACE_ITER = 100 # target网络更新频率 Target Update Frequency
MEMORY_CAPACITY = 1000
# 环境与输入输出
env = gym.make('CartPole-v0').unwrapped
N_ACTIONS = env.action_space.n
N_STATES = env.observation_space.shape[0]
ENV_A_SHPAE = 0 if isinstance(env.action_space.sample(),int) else env.action_space.sample().shape
基于神经网络的机器学习是自己会学习的算法。
参考教学视频:大话神经网络
nn.Module的子类函数必须在构造函数中执行父类的构造函数
super(Net, self).__init__()
设置第一个全连接层,即输入层到隐藏层
注:
N_STATES = 4
,隐藏层神经元设置为20个self.fc1 = nn.Linear(N_STATES,20)
self.fc1.weight.data.normal_(0,0.1)
设置第二个全连接层,即隐藏层到输出层
注:
N_ACTIONS = 2
self.out = nn.Linear(20,N_ACTIONS)
self.out.weight.data.normal_(0,0.1)
输入到第一层隐藏层进行计算,对结果使用激活函数,输入到输出层进行计算,得到的结果即为该神经网络的最后输出结果,最后一层的结果无需使用激活函数,因为此时输出结果为各动作的得分而不是概率
x = self.fc1(x)
x = F.relu(x)
action_value = self.out(x)
return action_value
# 定义Net类 (定义网络)
class Net(nn.Module):
def __init__(self): # 定义Net的一系列属性
super(Net, self).__init__() # nn.Module的子类函数必须在构造函数中执行父类的构造函数
self.fc1 = nn.Linear(N_STATES,20) # 设置第一个全连接层(输入层到隐藏层): 状态数个(4个)神经元到20个神经元
self.fc1.weight.data.normal_(0,0.1) # 初始化,为了让神经网络在学习过程中更加容易收敛
self.out = nn.Linear(20,N_ACTIONS) # 设置第二个全连接层(隐藏层到输出层): 20个神经元到动作数个(2个)神经元
self.out.weight.data.normal_(0,0.1) # 权重初始化(均值为0,方差为0.1的正态分布)
def forward(self,x): # 定义forward函数 (x为状态)
x = self.fc1(x) # 连接输入层到隐藏层
x = F.relu(x) # 使用激励函数ReLU来处理经过隐藏层后的值
action_value = self.out(x) # 连接隐藏层到输出层,获得最终的输出值 (即动作值)
return action_value # 返回动作值
考虑到DQN的实现流程,在初始化类的操作里,需要定义两个神经网络、定义学习的次数、定义经验池的相关属性、定义学习率的大小、并定义优化器相关属性。
DQN的核心是采用两个神经网络,即评估网络和目标网络。评估网络是DQN选择动作的核心,每次选择Q值最大的动作,每次都根据TD error和梯度计算更新评估网络的参数;而目标网络定期更新,较为稳定。
self.eval_net = Net()
self.target_net = Net()
由于目标网络是定期更新,需要定义学习了多少次,以便将目标网络的参数与评估网络的参数更新为一致
self.learn_step_counter = 0
经验池用于存储已经学过的经验,经验池的大小可以人为设置,根据场景的不同设置为不同值,在CartPole实例中一般设置为1000
每次学习后都将学习到的经验转化为一条transition存入经验池,如果经验池已满就覆盖之前的经验,保证经验池的数据最多为1000条
存储的经验即四元组,当前状态,选择动作,与环境交互得到的奖励和下一个状态,数量为N_STATES + 1 + 1 + N_STATES = 10
self.memory_counter = 0 # 用于计数,已经存储了多少条经验
self.memory = np.zeros((MEMORY_CAPACITY, N_STATES * 2 + 2)) # 经验池的大小为1000×10
使用Adam优化器和均方损失函数
self.optimizer = torch.optim.Adam(self.eval_net.parameters(),lr = LR)
self.loss_func = nn.MSELoss()
def __init__(self): # 定义DQN的一系列属性
self.eval_net,self.target_net = Net(),Net() # 利用Net创建两个神经网络: 评估网络和目标网络
self.learn_step_counter = 0 # for target updating
self.memory_counter = 0 # for storing memory
self.memory = np.zeros((MEMORY_CAPACITY, N_STATES * 2 + 2)) # 初始化记忆库,一行代表一个transition,1000×10
self.optimizer = torch.optim.Adam(self.eval_net.parameters(),lr = LR) # 使用Adam优化器 (输入为评估网络的参数和学习率)
self.loss_func = nn.MSELoss() # 使用均方损失函数(loss(xi, yi)=(xi-yi)^2)
输入状态x
为numpy.ndarray
形式,大小为4的一维数组。为了便于计算,需要转变为tensor
形式,大小为torch.Size([1, 4])
。因此,需要在dim=0处增加维数为1的维度。
x = torch.unsqueeze(torch.FloatTensor(x),0)
unsqueeze()函数
函数功能:升维,参数表示在哪个地方加一个维度,简单理解如下:
- 0表示在张量最外侧加一个中括号变成第一维
- 1表示在每个数外面都加一个中括号
举例:
import torch
input = torch.arange(0,3)
print(input) # --→ tensor([0, 1, 2])
print(input.shape) # 大小为3 --→ torch.Size([3])
print(input.unsqueeze(0)) # --→ tensor([[0, 1, 2]])
print(input.unsqueeze(0).shape) # 大小为1x3 --→ torch.Size([1, 3])
print(input.unsqueeze(1)) # --→ tensor([[0],[1],[2]])
print(input.unsqueeze(1).shape) # 大小为3x1 --→ torch.Size([3, 1])
t = torch.as_tensor(np.array([1,0,0])).unsqueeze(-1) # -> tensor([[1],[0],[0]], dtype=torch.int32)
生成一个在[0, 1)内的随机数,如果该随机数小于 ϵ \epsilon ϵ,则选择最优动作,否则选择随机动作
将一个batch内的状态输入评估网络,通过前向传播,获得各动作对应的价值,找到各状态的动作最大价值所对应的动作
if np.random.uniform() < EPSILON:
actions_value = self.eval_net.forward(x) # 把状态x输入评估网络,前向传播获得动作值
action = torch.argmax(actions_value,1).data.numpy() # 输出每一行最大值的索引,转化为numpy ndarray的形式
action = action[0] # 输出动作最大的索引
动作只有两个值,0和1
else:
action = np.random.randint(0,N_ACTIONS) # 这里action随机等于0或1 (N_ACTIONS = 2)
上述示例中, ϵ \epsilon ϵ是个定值,但是在训练初始阶段,希望尽可能的选择随机动作,在训练的后期,更多的选择已有的经验,因此可以在动作选择时加入学习次数的考虑。即学习次数较少时,随机数小一点
decline = 0.6 # 衰减系数
if random.randint(0, 100) < 100 * (decline ** learn_time):
action = np.random.randint(0,N_ACTIONS)
else:
actions_value = self.eval_net.forward(x) # 把状态x输入评估网络,前向传播获得动作值
action = torch.argmax(actions_value,1).data.numpy() # 输出每一行最大值的索引,转化为numpy ndarray的形式
action = action[0] # 输出动作最大的索引
def choose_action(self,x): # 定义动作选择函数 (x为状态)
x = torch.unsqueeze(torch.FloatTensor(x),0) # 将x转换成32-bit floating point形式,并在dim=0增加维数为1的维度
if np.random.uniform() < EPSILON: # 生成一个在[0, 1)内的随机数
# 随机数如果小于EPSILON,选择最优动作
actions_value = self.eval_net.forward(x) # 把状态x输入评估网络,前向传播获得动作值
action = torch.argmax(actions_value,1).data.numpy() # 输出每一行最大值的索引,转化为numpy ndarray的形式
action = action[0] # 输出动作最大的索引
else:
# 随机数如果大于等于EPSILON,随机选择动作
action = np.random.randint(0,N_ACTIONS) # 这里action随机等于0或1 (N_ACTIONS = 2)
return action # 返回选择的动作 (0或1)
根据上文所述,存储的经验为四元组,经验池的每条transition长度为10,即我们希望每一行的transition是一个大小为10的数组,因此使用np.hstack()
函数进行拼接
注:
这里a和r都单独的数值,因此加[]
把它们变成一个数组再做拼接
transition = np.hstack((s,[a,r],s_))
np.hstack()
函数功能:将参数元组的元素数组按水平方向进行叠加
举例:
import numpy as np
arr1 = [1, 2, 3]
arr2 = [4, 5]
arr3 = [6, 7]
res = np.hstack((arr1, arr2, arr3)) # -> [1 2 3 4 5 6 7]
如果记忆库满了,就覆盖旧的数据
index = self.memory_counter % MEMORY_CAPACITY # 获取transition要置入的行数
self.memory[index,:] = transition # 置入transition
self.memory_counter += 1 # 计数加1
def store_transition(self,s,a,r,s_): # 定义记忆存储函数 (这里输入为一个transition),每次将四个量存入并更新下标
transition = np.hstack((s,[a,r],s_)) # 在水平方向上拼接数组(拼接的所有数组外面必须加括号,a和r是动作不是数组,需要转变为数组)
# 如果记忆库满了,覆盖旧的数据
index = self.memory_counter % MEMORY_CAPACITY # 获取transition要置入的行数
self.memory[index,:] = transition # 置入transition
self.memory_counter += 1 # 计数加1
每次训练都进行计数,当训练到一定次数后,更新目标网络的参数
if self.learn_step_counter % TARGET_REPLACE_ITER == 0:
self.target_net.load_state_dict(self.eval_net.state_dict()) # 将评估网络的参数赋给目标网络
self.learn_step_counter += 1 # 学习步数加1
从经验池中随机抽取一定数量的数据,本实例中设置为抽取32个数,这些数不一定相邻,完全随机抽取,且有可能重复。
sample_index = np.random.choice(MEMORY_CAPACITY, BATCH_SIZE) # 在[0, 2000)内随机抽取32个数
b_memory = self.memory[sample_index,:] # 将32个索引对应的那一行transition存入b_memory
# 分别将这32个transition中的s,a,r,s_取出存储到如下新的数组
b_s = torch.FloatTensor(b_memory[:, : N_STATES]) # 32×4
b_a = torch.LongTensor(b_memory[:,N_STATES:N_STATES+1].astype(int)) # 32×1
b_r = torch.FloatTensor(b_memory[:,N_STATES+1: N_STATES+2]) # 32×1
b_s_ = torch.FloatTensor(b_memory[:, -N_STATES: ]) # 32×4
利用评估网络得到各动作对应的值,这里不是选择值最大的动作,而是根据经验池里取出的批数据来选择动作,原来存储的是什么动作,现在就选择什么动作
action_values_b = self.eval_net(b_s) # 利用前向网络得到各动作的值(默认会调用forward函数)
q_eval = action_values_b.gather(1,b_a) # 原来选择什么动作,这次依然选择什么动作,得到该动作对应的值
对下一个状态,使用目标网络求取动作代价值,并预测动作。
注:
detach()
隔绝梯度)q_next = self.target_net(b_s_).detach() # q_next不进行反向传递误差,所以detach
q_next_max = q_next.max(1)[0] # 返回最大值,不返回索引,32×1
q_next_max_shape = q_next_max.view(BATCH_SIZE,1) # 将32×1的张量变成1×32
q_target = b_r + GAMMA * q_next_max_shape
将32个评估值和目标值依次输入优化器,使用均方损失函数
self.optimizer.zero_grad() # 清空上一步的残余更新参数值
loss.backward() # 误差反向传播, 计算参数更新值
self.optimizer.step() # 更新评估网络的所有参数
def learn(self): # 定义学习函数(记忆库已满后便开始学习)
# 目标网络参数更新
if self.learn_step_counter % TARGET_REPLACE_ITER == 0:
self.target_net.load_state_dict(self.eval_net.state_dict()) # 将评估网络的参数赋给目标网络
self.learn_step_counter += 1 # 学习步数加1
# 抽取记忆库中的批数据
sample_index = np.random.choice(MEMORY_CAPACITY, BATCH_SIZE) # 在[0, 2000)内随机抽取32个数,可能会重复
b_memory = self.memory[sample_index,:] # 将32个索引对应的那一行transition存入b_memory
# 分别将这32个transition中的s,a,r,s_取出存储到如下新的数组
b_s = torch.FloatTensor(b_memory[:, : N_STATES]) # 32×4
b_a = torch.LongTensor(b_memory[:,N_STATES:N_STATES+1].astype(int)) # 32×1
b_r = torch.FloatTensor(b_memory[:,N_STATES+1: N_STATES+2]) # 32×1
b_s_ = torch.FloatTensor(b_memory[:, -N_STATES: ]) # 32×4
# 获取批数据的评估值和目标值,利用损失函数和优化器对评估网络进行参数更新
# 评估值
action_values_b = self.eval_net(b_s) # 利用前向网络得到各动作的值(默认会调用forward函数)
q_eval = action_values_b.gather(1,b_a) # 原来选择什么动作,这次依然选择什么动作,得到该动作对应的值
# 目标值
q_next = self.target_net(b_s_).detach() # q_next不进行反向传递误差,所以detach
q_next_max = q_next.max(1)[0] # 返回最大值,不返回索引,32×1
q_next_max_shape = q_next_max.view(BATCH_SIZE,1) # 将32×1的张量变成1×32
q_target = b_r + GAMMA * q_next_max_shape
# 输入32个评估值和32个目标值,使用均方损失函数
loss = self.loss_func(q_eval, q_target)
# 更新的常用步骤
self.optimizer.zero_grad() # 清空上一步的残余更新参数值
loss.backward() # 误差反向传播, 计算参数更新值
self.optimizer.step() # 更新评估网络的所有参数
需要进行N多局游戏,游戏开始时重置环境为初始状态,每局游戏一直玩到结束为止,不断训练两个网络的参数,并记录得分情况
for i in range(400):
print('<<<<<<<< % i) # 记录是第i局游戏
s = env.reset() # 重置环境
episode_reward_sum = 0 # 该局游戏的得分初始化为0
while True:
# 一局游戏内的循环
env.close()
可以选择每走一步都显示动画,也可以隔断时间显示一次。本实例中每走一步都会更新显示。“走一步”即使用DQN的《动作选择函数》确定动作,然后执行该动作,与环境交互,获得奖励和下一个状态。
env.render() # 显示实验动画
a = dqn.choose_action(s) # 输入该步对应的状态s,选择动作
s_, r, done, info = env.step(a) # 执行动作,获得反馈
为了更好的训练摆杆,修改奖励,不修改也可以进行训练。
这里结合摆杆移动的位置和摆杆偏移的角度作为新的奖励函数
x, x_dot, theta, theta_dot = s_
r1 = (env.x_threshold - abs(x)) / env.x_threshold - 0.8
r2 = (env.theta_threshold_radians - abs(theta)) / env.theta_threshold_radians - 0.5
new_r = r1 + r2
将现有状态、选择的动作、与环境交互获得的下一状态以及上述重新定义的奖励作为一个新的transition存储到经验池中。并记录该步获得的新奖励,更新这一局的总得分。
dqn.store_transition(s, a, new_r, s_) # 存储样本
episode_reward_sum += new_r # 逐步加上一个episode内每个step的reward
最开始只探索,不学习,当经验池满了以后再开始学习
if dqn.memory_counter > MEMORY_CAPACITY: # 如果累计的transition数量超过了记忆库的固定容量2000
# 开始学习 (抽取记忆,即32个transition,并对评估网络参数进行更新,并在开始学习后每隔100次将评估网络的参数赋给目标网络)
dqn.learn()
与环境交互得到的done
为True
则游戏结束,结束该局游戏(while True
循环)
if done:
print('episode%s---reward_sum: %s' % (i_episode, round(episode_reward_sum, 2)))
break
s = s_ # 更新状态
'''
@Project: pythonProject1
@Author: xby
@File: try_dqn_2.py
@Date 2022/10/17 11:05
'''
import torch,gym
import torch.nn.functional as F
import torch.nn as nn
import numpy as np
# 超参数:Hyper Parameters
BATCH_SIZE = 32
LR = 0.01 # 学习率 learning rate
EPSILON = 0.9 # greedy policy
GAMMA = 0.9 # 折扣因子 reward discount
TARGET_REPLACE_ITER = 100 # target网络更新频率 Target Update Frequency
MEMORY_CAPACITY = 1000
# 环境与输入输出
env = gym.make('CartPole-v0').unwrapped
N_ACTIONS = env.action_space.n
N_STATES = env.observation_space.shape[0]
# 定义Net类 (定义网络)
class Net(nn.Module):
def __init__(self): # 定义Net的一系列属性
super(Net, self).__init__() # nn.Module的子类函数必须在构造函数中执行父类的构造函数
self.fc1 = nn.Linear(N_STATES,20) # 设置第一个全连接层(输入层到隐藏层): 状态数个(4个)神经元到20个神经元
self.fc1.weight.data.normal_(0,0.1) # 初始化,为了让神经网络在学习过程中更加容易收敛
self.out = nn.Linear(20,N_ACTIONS) # 设置第二个全连接层(隐藏层到输出层): 20个神经元到动作数个(2个)神经元
self.out.weight.data.normal_(0,0.1) # 权重初始化(均值为0,方差为0.1的正态分布)
def forward(self,x): # 定义forward函数 (x为状态)
x = self.fc1(x) # 连接输入层到隐藏层
x = F.relu(x) # 使用激励函数ReLU来处理经过隐藏层后的值
action_value = self.out(x) # 连接隐藏层到输出层,获得最终的输出值 (即动作值)
return action_value # 返回动作值
# DQN:有两个网络——eval_net是更加靠前的新网络,不停试探进行新游戏;target_net更加稳定
class DQN(object):
def __init__(self): # 定义DQN的一系列属性
self.eval_net,self.target_net = Net(),Net() # 利用Net创建两个神经网络: 评估网络和目标网络
self.learn_step_counter = 0 # for target updating
self.memory_counter = 0 # for storing memory
self.memory = np.zeros((MEMORY_CAPACITY, N_STATES * 2 + 2)) # 初始化记忆库,一行代表一个transition,1000×10
self.optimizer = torch.optim.Adam(self.eval_net.parameters(),lr = LR) # 使用Adam优化器 (输入为评估网络的参数和学习率)
self.loss_func = nn.MSELoss() # 使用均方损失函数(loss(xi, yi)=(xi-yi)^2)
def choose_action(self,x): # 定义动作选择函数 (x为状态)
x = torch.unsqueeze(torch.FloatTensor(x),0) # # 将x转换成32-bit floating point形式,并在dim=0增加维数为1的维度
if np.random.uniform() < EPSILON: # 生成一个在[0, 1)内的随机数
# 随机数如果小于EPSILON,选择最优动作
actions_value = self.eval_net.forward(x) # 把状态x输入评估网络,前向传播获得动作值
action = torch.argmax(actions_value,1).data.numpy() # 输出每一行最大值的索引,转化为numpy ndarray的形式
action = action[0] # 输出动作最大的索引
else:
# 随机数如果大于等于EPSILON,随机选择动作
action = np.random.randint(0,N_ACTIONS) # 这里action随机等于0或1 (N_ACTIONS = 2)
return action # 返回选择的动作 (0或1)
def store_transition(self,s,a,r,s_): # 定义记忆存储函数 (这里输入为一个transition),每次将四个量存入并更新下标
transition = np.hstack((s,[a,r],s_)) # 在水平方向上拼接数组(拼接的所有数组外面必须加括号,a和r是动作不是数组,需要转变为数组)
# 如果记忆库满了,覆盖旧的数据
index = self.memory_counter % MEMORY_CAPACITY # 获取transition要置入的行数
self.memory[index,:] = transition # 置入transition
self.memory_counter += 1 # 计数加1
def learn(self): # 定义学习函数(记忆库已满后便开始学习)
# 目标网络参数更新
if self.learn_step_counter % TARGET_REPLACE_ITER == 0:
self.target_net.load_state_dict(self.eval_net.state_dict()) # 将评估网络的参数赋给目标网络
self.learn_step_counter += 1 # 学习步数加1
# 抽取记忆库中的批数据
sample_index = np.random.choice(MEMORY_CAPACITY, BATCH_SIZE) # 在[0, 2000)内随机抽取32个数,可能会重复
b_memory = self.memory[sample_index,:] # 将32个索引对应的那一行transition存入b_memory
# 分别将这32个transition中的s,a,r,s_取出存储到如下新的数组
b_s = torch.FloatTensor(b_memory[:, : N_STATES]) # 32×4
b_a = torch.LongTensor(b_memory[:,N_STATES:N_STATES+1].astype(int)) # 32×1
b_r = torch.FloatTensor(b_memory[:,N_STATES+1: N_STATES+2]) # 32×1
b_s_ = torch.FloatTensor(b_memory[:, -N_STATES: ]) # 32×4
# 获取批数据的评估值和目标值,利用损失函数和优化器对评估网络进行参数更新
# 评估值
action_values_b = self.eval_net(b_s) # 利用前向网络得到各动作的值(默认会调用forward函数)
q_eval = action_values_b.gather(1,b_a) # 原来选择什么动作,这次依然选择什么动作,得到该动作对应的值
# 目标值
q_next = self.target_net(b_s_).detach() # q_next不进行反向传递误差,所以detach
q_next_max = q_next.max(1)[0] # 返回最大值,不返回索引,32×1
q_next_max_shape = q_next_max.view(BATCH_SIZE,1) # 将32×1的张量变成1×32
q_target = b_r + GAMMA * q_next_max_shape
# 输入32个评估值和32个目标值,使用均方损失函数
loss = self.loss_func(q_eval, q_target)
# 更新的常用步骤
self.optimizer.zero_grad() # 清空上一步的残余更新参数值
loss.backward() # 误差反向传播, 计算参数更新值
self.optimizer.step() # 更新评估网络的所有参数
dqn = DQN()
print('\nCollecting experience...')
for i_episode in range(400):
print('<<<<<<<< % i_episode)
s = env.reset() # 初始化游戏状态
episode_reward_sum = 0
while True:
env.render() # 显示实验动画
a = dqn.choose_action(s) # 输入该步对应的状态s,选择动作
s_, r, done, info = env.step(a) # 执行动作,获得反馈
# 修改奖励 (不修改也可以,修改奖励只是为了更快地得到训练好的摆杆)
x, x_dot, theta, theta_dot = s_
r1 = (env.x_threshold - abs(x)) / env.x_threshold - 0.8
r2 = (env.theta_threshold_radians - abs(theta)) / env.theta_threshold_radians - 0.5
new_r = r1 + r2
dqn.store_transition(s, a, new_r, s_) # 存储样本
episode_reward_sum += new_r # 逐步加上一个episode内每个step的reward
if dqn.memory_counter > MEMORY_CAPACITY: # 如果累计的transition数量超过了记忆库的固定容量2000
# 开始学习 (抽取记忆,即32个transition,并对评估网络参数进行更新,并在开始学习后每隔100次将评估网络的参数赋给目标网络)
dqn.learn()
if done:
print('episode%s---reward_sum: %s' % (i_episode, round(episode_reward_sum, 2)))
break
s = s_ # 更新状态