参考了一些文章,针对OpenAI gym环境,使用tf2.x实现了DQN算法;加上了一些没有太大必要(?)的小功能,比如:自动保存视频,保存训练日志从而利用TensorBoard实现数据可视化,保存和读取训练记忆,保存和读取DQN权重。适用于CartPole环境,只需少量改动也可以用于MountainCar环境。代码下载及本文参考见文末。

本篇目录

  • 代码框架

    • 主循环
    • 单局游戏
    • 自定义reward
    • 自动录制视频
  • DQN算法主体
    • 神经网络
    • DQN
    • 效果
    • TensorBoard可视化
    • 关于Reward
  • 代码下载
  • 参考

CartPole环境

代码框架

主循环

-------------初始化---------------
- 创建Gym环境实例
- 设置DQN参数
- 创建DQN实例- 载入DQN记忆(可选)- 载入DQN权重(可选)
- 创建tensorflow summary writer
- 设置训练参数
--------------训练----------------
- 循环N次
|     运行一局,得到该局内的reward和losses
|     记录数据到summary
|     显示数据到命令行
- 结束循环
--------------结束----------------- 保存DQN记忆- 保存DQN权重- 录制视频
- 关闭环境

代码:

from cart_pole import MyModel
from cart_pole import DQN
from cart_pole import play_game
from cart_pole import make_videoimport numpy as np
import tensorflow as tf
import gym
import os
import datetime
from statistics import mean
from gym import wrappersdef main():####################初始化#####################env = gym.make('CartPole-v0')gamma = 0.9num_states = len(env.observation_space.sample())num_actions = env.action_space.nhidden_units = [20, 20]max_experiences = 2000min_experiences = 1000batch_size = 32lr = 0.01e_greedy = 0.9e_greedy_increment = 1.001replace_target_iter = 50DQN_ = DQN(num_states, num_actions, hidden_units, gamma, max_experiences, min_experiences, batch_size, lr,e_greedy, replace_target_iter,e_greedy_increment)DQN_.loadModel()DQN_.loadMemory()current_time = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")log_dir = 'logs/dqn/' + current_timesummary_writer = tf.summary.create_file_writer(log_dir)N = 10000 # 总训练轮次Dispaly_interval = 100total_rewards = np.empty(N)####################主循环#####################for n in range(N):total_reward, losses = play_game(env, DQN_)total_rewards[n] = total_rewardavg_rewards = total_rewards[max(0, n - Dispaly_interval):(n + 1)].mean()with summary_writer.as_default():tf.summary.scalar('episode reward', total_reward, step=n)tf.summary.scalar('running avg reward(100)', avg_rewards, step=n)tf.summary.scalar('average loss)', losses, step=n)if n % Dispaly_interval == 0:print("episode:", n, "eps:", DQN_.epsilon, "avg reward (last ",Dispaly_interval,"):", avg_rewards,"episode loss: ", losses)####################结束#####################make_video(env, DQN_)# DQN_.saveModel()# DQN_.saveMemory()env.close()

单局游戏

DQN的主算法

在此基础上,做了一些更改

  • 增加每一局内总reward和loss的记录,用于可视化
  • 使用自定义的reward计算,而非gym环境返回的reward
  • Epsilon-greedy的系数e每局递增,这一步也可以放进DQN的训练函数中,放在这里意味着“每一局游戏”epsilon递增一次,放在DQN的训练函数中意味着"每一次训练"递增一次,一局游戏中包含多次训练,相同的系数下,后者会让epsilon增大的特别快。
  • 将梯度下降的过程和Q_target的更新都包装进了DQN.train() 函数中
def play_game(env, DQN):rewards = 0done = Falseobservations = env.reset()losses = list()DQN.epsilon_incre()while not done:# env.render() #注释掉则不显示GUI,训练加速action = DQN.get_action(observations)prev_observations = observationsobservations, reward, done, _ = env.step(action)reward = My_reward(env,observation)rewards += rewardif done:reward = -10 #适用于cart_pole环境env.reset()exp = {'s': prev_observations, 'a': action, 'r': reward, 's2': observations, 'done': done}DQN.add_experience(exp)loss = DQN.train()if isinstance(loss, int): #当记忆库数量不足时loss=0losses.append(loss)else:losses.append(loss.numpy()) #类型转换return rewards, mean(losses)

自定义reward

gym环境的done的定义是位置不超过边界,角度不超过某一阈值(0.2左右)

gym环境的reward的定义是,只要done=false->reward=1,done=true之后reward=0,这样的设置会让训练很低效。

在我们有环境、预期目标和被控对象的一定知识之后,把这份知识通过reward的方式“告诉“被控对象,能达到加速训练的效果,这也就是reward shaping。

在这个例子中,我们的目标是杆子一直立着,越久越好,同时车也越靠近中心越好。因此,使用这样的函数自定义reward

def My_reward(env,observations):x, _, theta, _ = observations # observations = [x,x导数,theta,theta导数]rp = (env.x_threshold - abs(x))/env.x_threshold - 0.8 # reward positionra = (env.theta_threshold_radians - abs(theta))/env.theta_threshold_radians - 0.5 # reward anglereward = rp + ra return reward

env.x_threshold和env.theta_threshold_radians是gym环境自带的参数,代表失败的阈值,这里的函数定义可以理解为:

横轴为角度或位置,纵轴为reward,位置和角度越靠近0,reward越大,超出一定范围reward为负,图示并非实际数值

同时,以上的讨论依旧局限于Done=false的时刻,当Done=true时,即车超出边界or棒子与竖直的夹角大于一定阈值,需要令reward等于一个比较大的负数,从而让被控对象以后避免这个导致Done为true的action,这其实也是reward shaping的一部分,不加这里的设置,系统照样会收敛,不过加上之后,学习会更加高效,系统会倾向于更加谨慎,避免done=true。但也带来了一些代价。

实现这个的代码并没有写进My_reward函数里,而是写在play_game的循环里了。具体的原因在本文的最后一部分给了说明。

自动录制视频

为了加速,在训练过程中默认是不显示窗口界面的,如果需要可以使用env.render()函数调出窗口,为了录制训练结束之后的系统表现,使用warppers.Monitor函数

在录制时,DQN的epsilon参数被设置为1,这意味着不会随机选择动作,epsilon的目的在于一定程度上避免系统陷入局部最优,适用于学习的过程,在测试的过程中没有意义。

def make_video(env, DQN):env = wrappers.Monitor(env, r"D:桌面RLvideos", force=True)rewards = 0steps = 0done = Falseobservation = env.reset()DQN.epsilon = 1 #epsilon = 1代表严格按照DQN输出行动,没有随机因素while not done:env.render() #调出并更新窗口action = DQN.get_action(observation)observation, reward, done, _ = env.step(action)reward = My_reward(env,observations)steps += 1rewards += rewardprint("Testing steps: {} rewards {}: ".format(steps, rewards))

DQN算法主体

这里使用keras.Model的自定义子类作为Q神经网络的实现,但完全不一定非要这么写,也可以用keras.Model,Tensorlayer之类的其他方式,毕竟gym的这几个问题用到的神经网路结构还是相对很简单的。

神经网络

# keras.model的自定义子类
class MyModel(tf.keras.Model):def __init__(self, num_states, hidden_units, num_actions):super(MyModel, self).__init__()self.input_layer = tf.keras.layers.InputLayer(input_shape=(num_states,))self.hidden_layers = []for i in hidden_units:self.hidden_layers.append(tf.keras.layers.Dense(i, activation='tanh', kernel_initializer='RandomNormal'))self.output_layer = tf.keras.layers.Dense(num_actions, activation='linear', kernel_initializer='RandomNormal')@tf.functiondef call(self, inputs):z = self.input_layer(inputs)for layer in self.hidden_layers:z = layer(z)output = self.output_layer(z)return outputdef predict(self, inputs):return self(np.atleast_2d(inputs.astype('float32')))

DQN

import numpy as np
import tensorflow as tf
import gym
import os
import datetime
from statistics import mean
from gym import wrappers
import pickleclass DQN:def __init__(self, num_states, num_actions, hidden_units, gamma, max_experiences, min_experiences,batch_size, lr,e_greedy, replace_target_iter = 50,e_greedy_increment=None):self.num_actions = num_actionsself.batch_size = batch_sizeself.optimizer = tf.optimizers.Adam(lr)self.gamma = gammaself.model = MyModel(num_states, hidden_units, num_actions)self.target = MyModel(num_states, hidden_units, num_actions)self.experience = {'s': [], 'a': [], 'r': [], 's2': [], 'done': []} #字典形式的记忆库self.max_experiences = max_experiencesself.min_experiences = min_experiencesself.learn_step_counter = 0self.replace_target_iter = replace_target_iterself.epsilon_increment = e_greedy_incrementself.epsilon_max = e_greedyself.epsilon = 0.1 if self.epsilon_increment is not None else self.epsilon_max# 以Target net作为参考训练def train(self):if self.learn_step_counter % self.replace_target_iter == 0:self.learn_step_counter = 0self.copy_weights()if len(self.experience['s']) < self.min_experiences:return 0 # 0 代表啥也没学到ids = np.random.randint(low=0, high=len(self.experience['s']), size=self.batch_size)# {'s': array([-0.02874029, -0.58186904,  0.00809103,  0.89257765]), # 'a': 1, 'r': 1.0, 's2': array([-0.04037767, -0.38685777,  0.02594258,  0.60244905]), 'done': False}# {'s': array([-0.13791506, -0.78732364,  0.19086538,  1.4356326 ]),#  'a': 0, 'r': -200, 's2': array([-0.15366153, -0.98421693,  0.21957803,  1.78138363]), 'done': True}#  state = x, x_dot, theta, theta_dotstates = np.asarray([self.experience['s'][i] for i in ids])actions = np.asarray([self.experience['a'][i] for i in ids])rewards = np.asarray([self.experience['r'][i] for i in ids])states_next = np.asarray([self.experience['s2'][i] for i in ids])dones = np.asarray([self.experience['done'][i] for i in ids])value_next = np.max(self.target.predict(states_next), axis=1)# np.where(condition, x, y)# 满足条件(condition),输出x,不满足输出y。actual_values = np.where(dones, rewards, rewards+self.gamma*value_next)with tf.GradientTape() as tape:selected_action_values = tf.math.reduce_sum(self.model.predict(states) * tf.one_hot(actions, self.num_actions), axis=1)loss = tf.math.reduce_mean(tf.square(actual_values - selected_action_values))variables = self.model.trainable_variablesgradients = tape.gradient(loss, variables)self.optimizer.apply_gradients(zip(gradients, variables))self.learn_step_counter += 1   return loss # 选取行动def get_action(self, states):if np.random.random() > self.epsilon:return np.random.choice(self.num_actions)else:return np.argmax(self.model.predict(np.atleast_2d(states))[0])# 记忆库def add_experience(self, exp):if len(self.experience['s']) >= self.max_experiences:for key in self.experience.keys():self.experience[key].pop(0) # 满了就删除一个for key, value in exp.items():self.experience[key].append(value)# 更新fronzen/target netdef copy_weights(self):variables1 = self.target.trainable_variablesvariables2 = self.model.trainable_variablesfor v1, v2 in zip(variables1, variables2):v1.assign(v2.numpy())def epsilon_incre(self):self.epsilon = self.epsilon * self.epsilon_increment if self.epsilon < self.epsilon_max else self.epsilon_maxdef saveModel(self):self.model.save_weights(r'D:桌面RLDQNeval_net.h5')self.target.save_weights(r'D:桌面RLDQNtarget_net.h5')print('Weights Saved.')def loadModel(self):# 模型没有被调用过则没有权重,调用一下生成初始权重self.model.predict(np.array([1,1,1,1]))self.target.predict(np.array([1,1,1,1]))self.model.load_weights(r'D:桌面RLDQNeval_net.h5')self.target.load_weights(r'D:桌面RLDQNtarget_net.h5')print('Weights loaded.')def saveMemory(self):f = open(r'D:桌面RLMemory.data', 'wb')# 将变量存储到目标文件中区pickle.dump(self.experience, f)# 关闭文件f.close()print('Memory saved.')def loadMemory(self):fr = open(r'D:桌面RLMemory.data','rb')self.experience  = pickle.load(fr)print('Memory loaded.')

效果

训练200回合之后的效果

用两层每层20个节点的神经网络,epsilon初值0.1,每回合递乘1.02,最大值0.9,在200回合后的效果

TensorBoard可视化

打开日志存放的文件夹,打开终端,运行tensorboard --logdir=日志所在的文件夹名,例如:

tensorboard --logdir=20201112-111458
#或者
#tensorboard --logdir=D:RLlogsdqn20201112-111458

打开输出的链接,即可进入TensorBoard界面

loss曲线

loss代表了神经网络的学习程度,在没有过拟合的时候,loss越带代表神经网络的”学到的东西“越多,可以看到前100回合内loss有一个高峰,代表“学到了很多东西”,然后慢慢降低,代表”学完了“

平均reward曲线

2000回合左右就有很好的性能,之后有过拟合的倾向

这里用了相同参数,但是更长训练时间的结果,可以看到DQN的性能远非能达到的最好,在2k之后有过拟合倾向,越学越拉跨,但是毕竟只使用了20*2个中间神经元,想要获得更好的训练效果,可以考虑 增加神经元节点(hidden unit)数、层数、降低epsilon的递增率、增加记忆池大小,更高层的改动包括改进神经网络结构,改进DQN算法,使用Double DQN等等,但是训练时间自然也会相应地提高。


关于Reward

除此之外,其实本代码的reward的处理并不严谨,gym环境在一定时间过去之后,无论系统状态如何,Done也会被设置为true,这是为了给单局时间设置一个上界,避免每次时间无限地递增,从而避免训练时间无限地递增。

在每局游戏时间结束将导致一个reward在done=true时被设置为-10,这其实相当于:在系统通过学习,能够撑到时间终点之后,在每一局结束时,为DQN的[状态,动作]映射,加上一些随机的噪声。更通俗地讲:假设游戏在第300步结束,done在此时被设置为true,那么第299步的reward会被设置为-10,系统会认为“是299步的这个[状态,动作]映射对带来了惩罚”,因此会降低这个映射对的权重,但其实这个映射对并没有任何错误,只是时间到了而已。

不过这个随机的噪声对系统的性能影响依旧是十分有限的,因为在每局完整的比赛中,假设会记录300次动作,只有1个[状态,动作]映射对受到污染。

但从本质上讲,这个问题的原因在于DQN并没有把时间当作输入,只能感受到位置和角度的状态,因此解决方法也有三个:

0:更改reward shaping的方式,不对done=true时的reward做任何修改,或者干脆就不reward shaping,用gym原生的reward.

1:在DQN的神经网络输入层中加入这一时间维度,这虽然能让DQN感知到时间这个维度了,但是这个方法很明显太笨了,会让DQN有更复杂的结构,需要更长的时间收敛,需要更多的训练,仅仅是为了避免稳态下约1/300的噪声,并不可取。

2:在reward处理中考虑时间,即如果done=true是由角度和位置的偏移带来的,那么给与对应的惩罚:reward=-10,而如果done=true是由该局游戏结束带来的,那么reward=reward,即不做出更改,依旧按照自定义函数给出的值记录进记忆。这样做需要增加一个判断的流程,代码会更加复杂,好处是系统收敛先验地更快。

代码下载

MUCZ/Gym_DQN

参考

https://mofanpy.com/tutorials/machine-learning/reinforcement-learning/DQN1/

https://towardsdatascience.com/deep-reinforcement-learning-build-a-deep-q-network-dqn-to-play-cartpole-with-tensorflow-2-and-gym-8e105744b998

https://blog.csdn.net/november_chopin/article/details/107913103

dqn在训练过程中loss越来越大_强化学习笔记:OpenAI Gym+DQN+Tensorflow2实现相关推荐

  1. dqn在训练过程中loss越来越大_用DQN算法玩FlappyBird

    DQN算法可以用于解决离散的动作问题,而FlappyBird的操作正好是离散的. FlappyBird的游戏状态一般可以通过图像加卷积神经网络(CNN)来进行强化学习.但是通过图像分析会比较麻烦,因为 ...

  2. dqn在训练过程中loss越来越大_深度强化学习——从DQN到DDPG

    想了解更多好玩的人工智能应用,请关注公众号"机器AI学习 数据AI挖掘","智能应用"菜单中包括:颜值检测.植物花卉识别.文字识别.人脸美妆等有趣的智能应用.. ...

  3. dqn在训练过程中loss越来越大_[动手学强化学习] 2.DQN解决CartPole-v0问题

    强化学习如何入门: 强化学习怎么入门好?​www.zhihu.com 最近在整理之前写的强化学习代码,发现pytorch的代码还是老版本的. 而pytorch今年更新了一个大版本,更到0.4了,很多老 ...

  4. dqn在训练过程中loss越来越大_DQN算法实现注意事项及排错方法

    在学习强化学习过程中,自己实现DQN算法时,遇到了比较多的问题,花了好几天的时间才得以解决.最后分析总结一下,避免再走弯路. 有可能开始实现出来的DQN算法,无论怎么训练总是看不错成果.需要注意的地方 ...

  5. pytorch训练 loss=inf或者训练过程中loss=Nan

    造成 loss=inf的原因之一:data underflow 最近在测试Giou的测试效果,在mobilenetssd上面测试Giou loss相对smoothl1的效果: 改完后训练出现loss= ...

  6. pytorch训练过程中loss出现NaN的原因及可采取的方法

    在pytorch训练过程中出现loss=nan的情况 1.学习率太高. 2.loss函数 3.对于回归问题,可能出现了除0 的计算,加一个很小的余项可能可以解决 4.数据本身,是否存在Nan,可以用n ...

  7. dqn 应用案例_强化学习(十二) Dueling DQN

    在强化学习(十一) Prioritized Replay DQN中,我们讨论了对DQN的经验回放池按权重采样来优化DQN算法的方法,本文讨论另一种优化方法,Dueling DQN.本章内容主要参考了I ...

  8. matlab损失函数出现nan,[译]在训练过程中loss出现NaN的原因以及可以采取的方法。...

    1.梯度爆炸 原因:在学习过程中,梯度变得非常大,使得学习的过程偏离了正常的轨迹. 症状:观察输出日志(runtime log)中每次迭代的loss值,你会发现loss随着迭代有明显的增长,最后因为l ...

  9. 有关神经网络模型训练过程中loss值始终变化不大的问题

    最近在训练一个神经网络模型,遇到了loss值始终不变的问题. 简要说一下,我训练的是一个分类器,是一个最后分类数为6的多元分类问题,但模型在训练多个轮次后准确度仍然在16.67%左右,loss值基本没 ...

最新文章

  1. LeetCode简单题之分糖果 II
  2. tkinter 笔记:创建输入框并显示结果 (莫烦python笔记)
  3. springboot使用HttpSessionListener监听器统计在线用户数
  4. ASP.NET Core 简单实现七牛图片上传(FormData 和 Base64)
  5. Ubuntu下面对Chrome浏览器护眼插件的调研
  6. 以后可能用到的一些OQL
  7. C++之虚函数是如何实现的
  8. GPU是如何工作的?
  9. 小程序加入人脸识别_进口香料——强势加入小程序买卖平台出售
  10. java方法不写访问权限_【JAVA小白】 问关于访问权限的问题,写接口遇到错误
  11. 【java笔记】字符编码和字符集
  12. 聊一聊HTTP缓存机制
  13. 【IRA/GSM/UCS2】the difference of IRA/GSM/UCS2 character set
  14. 《PowerMock实战手册》读书笔记及个人总结
  15. robocode java_IBM Robocode Java学习利器(1)Robocode 入门
  16. JavaScript 技术篇 - js读取Excel文档里的内容实例演示,js如何读取excel指定单元格的内容,js将excel的内容转化为json字符串方法
  17. 分布式技术(2)大型网站架构利器-CDN技术
  18. php 图片折角处理,如何使用CSS3实现折角效果
  19. 杭州电子科技大学计算机研究生很好考吗,杭州电子科技大学考研难吗?一般要什么水平才可以进入?...
  20. element上传多个视频/多个图片与限制数量

热门文章

  1. python元祖组成字典_Python基础之元组和字典
  2. Python+OpenCV:理解支持向量机(SVM)
  3. 染整色差技术分析大全
  4. 函数作为参数传递至函数内部进行调用
  5. 工业相机选型:相机接口
  6. Halcon例程学习之距离变换(distance_transform)
  7. 五道口服装市场关闭前 职业“甩货人”赚一笔
  8. PHP实现图片的等比缩放和Logo水印功能示例
  9. 如何发布一个Android库
  10. 《Hadoop MapReduce实战手册》一1.10 使用MapReduce监控UI