当前位置:   article > 正文

强化学习之DQN(附莫烦代码)_self.sess.run(self.q_eval, feed_dict={self.s: obse

self.sess.run(self.q_eval, feed_dict={self.s: observation})

1.简介

想象用Q-learning 电子游戏的每一帧来学习电子游戏,每个图片就可以是一种状态,游戏中的角色又可以有多种动作(上下左右,下蹲跳跃等等)。如果用Q表来记录每一个动作所对应的状态,那么这张Q表将大到无法想象。DQN不用Q表记录Q值,而是用神经网络来预测Q值,并通过不断更新神经网络从而学习到最优的行动路径。

深度 Q 网络(DQN)是将 Q learning 和卷积神经网络(CNN)结合在一起

Off-policy是Q-Learning的特点,DQN中也延用了这一特点。而不同的是,Q-Learning中用来计算target和预测值的Q是同一个Q,也就是说使用了相同的神经网络。这样带来的一个问题就是,每次更新神经网络的时候,target也都会更新,这样会容易导致参数不收敛。回忆在有监督学习中,标签label都是固定的,不会随着参数的更新而改变。

因此DQN在原来的Q网络的基础上又引入了一个target Q网络,即用来计算target的网络。它和Q网络结构一样,初始的权重也一样,只是Q网络每次迭代都会更新,而target Q网络是每隔一段时间才会更新。DQN的target是 Rt+1+γmaxa′Q(St+1,a′;ω−)。用 ω−表示它比Q网络的权重 ω更新得要慢一些。在训练神经网络参数时用到的损失函数(Loss function),实际上就是q_target 减 q_eval的结果 (loss = q_target- q_eval )

反向传播真正训练的网络是只有一个,就是eval_net。target_net 只做正向传播得到q_target (q_target = r +γ*max Q(s,a)). 其中 Q(s,a)是若干个经过target-net正向传播的结果。

相比于Q-Learning,DQN做的改进:一个是使用了卷积神经网络来逼近行为值函数,一个是使用了target Q network来更新target,还有一个是使用了经验回放Experience replay。由于在强化学习中,我们得到的观测数据是有序的,step by step的,用这样的数据去更新神经网络的参数会有问题。回忆在有监督学习中,数据之间都是独立的。因此DQN中使用经验回放,即用一个Memory来存储经历过的数据,每次更新参数的时候从Memory中抽取一部分的数据来用于更新,以此来打破数据间的关联。

  • 首先初始化Memory D,它的容量为N;
  • 初始化Q网络,随机生成权重ω;
  • 初始化target Q网络,权重为ω−=ω;
  • 循环遍历episode =1, 2, …, M:
  • 初始化initial state S1;
  • 循环遍历step =1,2,…, T:
    • 用ϵ−greedy策略生成action at:以ϵ概率选择一个随机的action,或选择at=maxaQ(St,a;ω);
    • 执行action at,接收reward rt及新的state St+1;
    • 将transition样本 (St,at,rt,St+1)存入D中;
    • 从D中随机抽取一个minibatch的transitions(Sj,aj,rj,Sj+1);
    • 令yj=rj,如果 j+1步是terminal的话,否则,令 yj=rj+γmaxa′Q(St+1,a′;ω−);
    • 对(yj−Q(St,aj;ω))2关于ω使用梯度下降法进行更新;
    • 每隔C steps更新target Q网络,ω−=ω。
  • End For;
  • End For.

2.代码展示

(1)这里利用 Scipy 的 imresize 函数来下采样图像。函数 preprocess 会在将图像输入到 DQN 之前,对图像进行预处理:

  1. def preprocess(img):
  2. img_temp=img[31:195] #choose the important area of the image
  3. img_temp=img_temp.mean(axis=2) #convert to Grayscale
  4. #downsample image
  5. img_temp=imresize(img_temp,size=(IM_SIZE,IM_SIZE),interp='nearest')
  6. return img_temp

IM_SIZE 是一个全局参数,这里设置为 80。该函数具有描述每个步骤的注释。下面是预处理前后的观测空间:

考虑四个动作和观测序列来确定当前情况并训练智能体。update_state 函数用来将当前观测状态附加到以前的状态,从而产生状态序列:

  1. def update_state(state,obs):
  2. obs_small=preprocess(obs)
  3. return np.append(state[1:],np.expand_dims(obs_small,0),axis=0)

(2)导入必要的模块。使用 sys 模块的 stdout.flush() 来刷新标准输出(此例中是计算机屏幕)中的数据。random 模块用于从经验回放缓存(存储过去经验的缓存)中获得随机样本。datatime 模块用于记录训练花费的时间:

定义训练的超参数,可以尝试改变它们,定义了经验回放缓存的最小和最大尺寸,以及目标网络更新的次数:

定义 DQN 类,构造器使用 tf.contrib.layers.conv2d 函数构建 CNN 网络,定义损失和训练操作:

 

类中用 set_session() 函数建立会话,用 predict() 预测动作值函数,用 update() 更新网络,在 sample_action() 函数中用 Epsilon 贪婪算法选择动作:

另外还定义了加载和保存网络的方法,因为训练需要消耗大量时间:

定义将主 DQN 网络的参数复制到目标网络的方法如下:

定义函数 learn(),预测价值函数并更新原始的 DQN 网络:

 

现在已经在主代码中定义了所有要素,下面构建和训练一个 DQN 网络来玩 Atari 的游戏。代码中有详细的注释,这主要是之前 Q learning 代码的一个扩展,增加了经验回放缓存:

下图是每 100 次运行的平均奖励,更清晰地展示了奖励的提高:

这只是在前 500 次运行后的训练结果。要想获得更好的结果,需要训练更多次,大约 1 万次。训练智能体需要运行很多次游戏,消耗大量的时间和内存。OpenAI Gym 提供了一个封装,将游戏保存为一个视频,因此,无须 render 函数,你可以使用这个封装来保存视频并在以后查看智能体是如何学习的。AI 工程师和爱好者可以上传这些视频来展示他们的结果。

 

莫烦代码

神经网络的搭建
为了使用 Tensorflow 来实现 DQN, 比较推荐的方式是搭建两个神经网络, target_net 用于预测 q_target 值, 他不会及时更新参数,eval_net 用于预测 q_eval, 这个神经网络拥有最新的神经网络参数. 不过这两个神经网络结构是完全一样的, 只是里面的参数不一样。两个神经网络是为了固定住一个神经网络 (target_net) 的参数, target_net 是 eval_net 的一个历史版本, 拥有 eval_net 很久之前的一组参数, 而且这组参数被固定一段时间, 然后再被 eval_net 的新参数所替换. 而 eval_net 是不断在被提升的, 所以是一个可以被训练的网络 trainable=True. 而 target_net 的 trainable=False。

  1. class DeepQNetwork:
  2. def _build_net(self):
  3. # ------------------ build evaluate_net ------------------
  4. self.s = tf.placeholder(tf.float32, [None, self.n_features], name='s') # input
  5. self.q_target = tf.placeholder(tf.float32, [None, self.n_actions], name='Q_target') # for calculating loss
  6. with tf.variable_scope('eval_net'):
  7. # c_names(collections_names) are the collections to store variables
  8. c_names, n_l1, w_initializer, b_initializer = \
  9. ['eval_net_params', tf.GraphKeys.GLOBAL_VARIABLES], 10, \
  10. tf.random_normal_initializer(0., 0.3), tf.constant_initializer(0.1) # config of layers
  11. # first layer. collections is used later when assign to target net
  12. with tf.variable_scope('l1'):
  13. w1 = tf.get_variable('w1', [self.n_features, n_l1], initializer=w_initializer, collections=c_names)
  14. b1 = tf.get_variable('b1', [1, n_l1], initializer=b_initializer, collections=c_names)
  15. l1 = tf.nn.relu(tf.matmul(self.s, w1) + b1)
  16. # second layer. collections is used later when assign to target net
  17. with tf.variable_scope('l2'):
  18. w2 = tf.get_variable('w2', [n_l1, self.n_actions], initializer=w_initializer, collections=c_names)
  19. b2 = tf.get_variable('b2', [1, self.n_actions], initializer=b_initializer, collections=c_names)
  20. self.q_eval = tf.matmul(l1, w2) + b2
  21. with tf.variable_scope('loss'):
  22. self.loss = tf.reduce_mean(tf.squared_difference(self.q_target, self.q_eval))
  23. with tf.variable_scope('train'):
  24. self._train_op = tf.train.RMSPropOptimizer(self.lr).minimize(self.loss)
  25. # ------------------ build target_net ------------------
  26. self.s_ = tf.placeholder(tf.float32, [None, self.n_features], name='s_') # input
  27. with tf.variable_scope('target_net'):
  28. # c_names(collections_names) are the collections to store variables
  29. c_names = ['target_net_params', tf.GraphKeys.GLOBAL_VARIABLES]
  30. # first layer. collections is used later when assign to target net
  31. with tf.variable_scope('l1'):
  32. w1 = tf.get_variable('w1', [self.n_features, n_l1], initializer=w_initializer, collections=c_names)
  33. b1 = tf.get_variable('b1', [1, n_l1], initializer=b_initializer, collections=c_names)
  34. l1 = tf.nn.relu(tf.matmul(self.s_, w1) + b1)
  35. # second layer. collections is used later when assign to target net
  36. with tf.variable_scope('l2'):
  37. w2 = tf.get_variable('w2', [n_l1, self.n_actions], initializer=w_initializer, collections=c_names)
  38. b2 = tf.get_variable('b2', [1, self.n_actions], initializer=b_initializer, collections=c_names)
  39. self.q_next = tf.matmul(l1, w2) + b2

2.思维决策的过程
定义完上次的神经网络部分以后, 这次来定义其他部分,首先是函数值的初始化。

  1. class DeepQNetwork:
  2. def __init__(
  3. self,
  4. n_actions,
  5. n_features,
  6. learning_rate=0.01,
  7. reward_decay=0.9,
  8. e_greedy=0.9,
  9. replace_target_iter=300,
  10. memory_size=500,
  11. batch_size=32,
  12. e_greedy_increment=None,
  13. output_graph=False,
  14. ):
  15. self.n_actions = n_actions
  16. self.n_features = n_features
  17. self.lr = learning_rate
  18. self.gamma = reward_decay
  19. self.epsilon_max = e_greedy # epsilon 的最大值
  20. self.replace_target_iter = replace_target_iter # 更换 target_net 的步数
  21. self.memory_size = memory_size # 记忆上限
  22. self.batch_size = batch_size # 每次更新时从 memory 里面取多少记忆出来
  23. self.epsilon_increment = e_greedy_increment # epsilon 的增量
  24. self.epsilon = 0 if e_greedy_increment is not None else self.epsilon_max # 是否开启探索模式, 并逐步减少探索次数
  25. # total learning step
  26. self.learn_step_counter = 0
  27. # initialize zero memory [s, a, r, s_]
  28. self.memory = np.zeros((self.memory_size, n_features * 2 + 2))
  29. # consist of [target_net, evaluate_net]
  30. self._build_net()
  31. t_params = tf.get_collection('target_net_params') # 提取 target_net 的参数
  32. e_params = tf.get_collection('eval_net_params') # 提取 eval_net 的参数
  33. self.replace_target_op = [tf.assign(t, e) for t, e in zip(t_params, e_params)]
  34. self.sess = tf.Session()
  35. if output_graph:
  36. # $ tensorboard --logdir=logs
  37. # tf.train.SummaryWriter soon be deprecated, use following
  38. tf.summary.FileWriter("logs/", self.sess.graph)
  39. self.sess.run(tf.global_variables_initializer())
  40. self.cost_his = [] # 记录所有 cost 变化, 用于最后 plot 出来观看

记忆存储,DQN 的精髓部分之一: 记录下所有经历过的步, 这些步可以进行反复的学习, 所以是一种 off-policy 方法

  1. class DeepQNetwork:
  2. def store_transition(self, s, a, r, s_):
  3. if not hasattr(self, 'memory_counter'):
  4. self.memory_counter = 0
  5. transition = np.hstack((s, [a, r], s_))
  6. # replace the old memory with new memory
  7. index = self.memory_counter % self.memory_size
  8. self.memory[index, :] = transition
  9. self.memory_counter += 1

行为选择,让 eval_net 神经网络生成所有 action 的值, 并选择值最大的 action;学习过程就是在 DeepQNetwork 中, 是如何学习, 更新参数的. 这里涉及了 target_net 和 eval_net 的交互使用,这是非常重要的一步。

  1. class DeepQNetwork:
  2. def choose_action(self, observation):
  3. # to have batch dimension when feed into tf placeholder
  4. observation = observation[np.newaxis, :]
  5. if np.random.uniform() < self.epsilon:
  6. # forward feed the observation and get q value for every actions
  7. actions_value = self.sess.run(self.q_eval, feed_dict={self.s: observation})
  8. action = np.argmax(actions_value)
  9. else:
  10. action = np.random.randint(0, self.n_actions)
  11. return action
  12. def learn(self):
  13. # check to replace target parameters
  14. if self.learn_step_counter % self.replace_target_iter == 0:
  15. self.sess.run(self.replace_target_op)
  16. print('\ntarget_params_replaced\n')
  17. # sample batch memory from all memory
  18. if self.memory_counter > self.memory_size:
  19. sample_index = np.random.choice(self.memory_size, size=self.batch_size)
  20. else:
  21. sample_index = np.random.choice(self.memory_counter, size=self.batch_size)
  22. batch_memory = self.memory[sample_index, :]
  23. # 获取 q_next (target_net 产生了 q) 和 q_eval(eval_net 产生的 q)
  24. q_next, q_eval = self.sess.run(
  25. [self.q_next, self.q_eval],
  26. feed_dict={
  27. self.s_: batch_memory[:, -self.n_features:], # fixed params
  28. self.s: batch_memory[:, :self.n_features], # newest params
  29. })
  30. # 下面这几步十分重要. q_next, q_eval 包含所有 action 的值,
  31. # 而我们需要的只是已经选择好的 action 的值, 其他的并不需要.
  32. # 所以我们将其他的 action 值全变成 0, 将用到的 action 误差值 反向传递回去, 作为更新凭据.
  33. # 这是我们最终要达到的样子, 比如 q_target - q_eval = [1, 0, 0] - [-1, 0, 0] = [2, 0, 0]
  34. # q_eval = [-1, 0, 0] 表示这一个记忆中有我选用过 action 0, 而 action 0 带来的 Q(s, a0) = -1, 所以其他的 Q(s, a1) = Q(s, a2) = 0.
  35. # q_target = [1, 0, 0] 表示这个记忆中的 r+gamma*maxQ(s_) = 1, 而且不管在 s_ 上我们取了哪个 action,
  36. # 我们都需要对应上 q_eval 中的 action 位置, 所以就将 1 放在了 action 0 的位置.
  37. # 下面是为了达到上面说的目的, 不过为了更方面让程序运算, 达到目的的过程有点不同.
  38. # 是将 q_eval 全部赋值给 q_target, 这时 q_target-q_eval 全为 0,
  39. # 不过 我们再根据 batch_memory 当中的 action 这个 column 来给 q_target 中的对应的 memory-action 位置来修改赋值.
  40. # 使新的赋值为 reward + gamma * maxQ(s_), 这样 q_target-q_eval 就可以变成我们所需的样子.
  41. # change q_target w.r.t q_eval's action
  42. q_target = q_eval.copy()
  43. batch_index = np.arange(self.batch_size, dtype=np.int32)
  44. eval_act_index = batch_memory[:, self.n_features].astype(int)
  45. reward = batch_memory[:, self.n_features + 1]
  46. q_target[batch_index, eval_act_index] = reward + self.gamma * np.max(q_next, axis=1)
  47. # train eval network
  48. _, self.cost = self.sess.run([self._train_op, self.loss],
  49. feed_dict={self.s: batch_memory[:, :self.n_features],
  50. self.q_target: q_target})
  51. self.cost_his.append(self.cost)
  52. # increasing epsilon
  53. self.epsilon = self.epsilon + self.epsilon_increment if self.epsilon < self.epsilon_max else self.epsilon_max
  54. self.learn_step_counter += 1

3.交互过程
DQN 与环境交互的过程总体与Q-Learning一致,仅仅增加了记忆存储的过程,这与前边提到的 “Q-Leaning 方法基于当前策略进行交互和改进,每一次模型利用交互生成的数据进行学习,学习后的样本被直接丢弃” 是一致的。

  1. from maze_env import Maze
  2. from RL_brain import DeepQNetwork
  3. def run_maze():
  4. step = 0
  5. for episode in range(1000):
  6. # initial observation
  7. observation = env.reset()
  8. while True:
  9. # fresh env
  10. env.render()
  11. # RL choose action based on observation
  12. action = RL.choose_action(observation)
  13. # RL take action and get next observation and reward
  14. observation_, reward, done = env.step(action)
  15. RL.store_transition(observation, action, reward, observation_)
  16. if (step > 200) and (step % 5 == 0):
  17. RL.learn()
  18. # swap observation
  19. observation = observation_
  20. # break while loop when end of this episode
  21. if done:
  22. break
  23. step += 1
  24. # end of game
  25. print('game over')
  26. env.destroy()
  27. if __name__ == "__main__":
  28. # maze game
  29. env = Maze()
  30. RL = DeepQNetwork(env.n_actions, env.n_features,
  31. learning_rate=0.01,
  32. reward_decay=0.9,
  33. e_greedy=0.9,
  34. replace_target_iter=200,
  35. memory_size=20000,
  36. output_graph=True
  37. )
  38. env.after(100, run_maze)
  39. env.mainloop()
  40. RL.plot_cost()

最后表示对莫烦的感谢!!

 

 

 

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/295902
推荐阅读
相关标签
  

闽ICP备14008679号