文章目录

  • 1.随便说几句
  • 2.为什么选择DQN作为第一个入手的模型
  • 2.工具准备
  • 3.实现思路
    • 3.1.环境采样
    • 3.2 Reward设计
    • 3.3 Q值近似计算
    • 3.4 主循环
  • 4.代码
  • 5.参考文献

1.随便说几句

疫情赋闲在家,就想着多学点东西吧。看了看GAN的东西,还看了看cs224的NLP,在做NLP作业的时候感觉虽然比单纯地刷准确率有意思点,但是a4的翻译作业竟然是翻译法语到英语的,我啥也分析不了,和刷准确率也差不多啊。
最后,我想去探索一下强化学习的领域了。强化学习可以用来玩游戏,而且也可以用于序列预测,和以后的方向有点关联还挺有意思的样子。以前听老师在AI导论课讲强化学习或者看CS231的强化学习,一般只有一节课,一堆概念乱糟糟的,也不怎么作为重点,所以学得一直很迷糊。然后在B站上看了David Silver的强化学习视频课,感觉收获很大。说老师讲得好确实有,但是可能更重要的是花了好多节课来学习,所以各个概念也弄得更清晰一点吧。老师确实很有耐心地解答问题,同学也很积极提问。
由于有读者想要倒立摆的源码,所以干脆就把这俩源码+训练好的模型打包放到这里了。训练好的模型放置于model目录下,其中带有goodmodel字样的是训练效果比较好的模型。
代码和训练完的模型下载地址

2.为什么选择DQN作为第一个入手的模型

这是在看到第5节课《免模型预测》后,想练练手解决个简单的问题。在这之前,课程讲了很多概念,也有挺多公式的。我对状态函数不是很感兴趣,我更倾向于使用Q值的估计,因为它更直观,哪个action的Q更大就选谁。但是在真正上手之后,有个问题摆在了我面前:课程里的Q(S,a)都是离散的(比方说走格子的游戏),但是许多游戏环境里的S是连续的(或者说实在太多了,不可能一一记录和遍历),怎么办呢,接着看下一节课呗。第6节课——值函数近似(也就是下一节课)老师就直白的说,“这节课过后,你们就可以将强化学习用于解决实际问题了”。这节课就是讲采用一些方法来近似估计连续的Q(S,a)。
在上CS231的强化学习课时,小姐姐就有说过:当你想要近似一个复杂函数的时候,你可以使用神经网络。当时还不太理解这句话,因为以为神经网络只能用来分类,对于拟合函数还没有太大的理解。
所以总结来说呢,采用DQN的原因就是:
1.使用神经网络估算Q值函数,普适性很强
2.策略就是取Q值最大的action,也就是贪婪方法,很直观
3.简单
依然是先讲解我一步一步思考问题的过程,然后再统一贴个代码。

2.工具准备

环境模拟器采用gym的简单小游戏:CartPole以及MountainCar
深度学习框架采用PyTorch
IDE嘛,这个随意了,我用的是VS2019(在写py这一块bug真不少)

3.实现思路

实现主要分为几个部分来讲解。第一个是环境采样部分,第二个是reward设计部分,第三个是Q值近似计算部分,第四个是主循环。

3.1.环境采样

数据肯定要从环境里采样。采样之前先要弄懂环境是什么样的,不然怎么指导zz模型学习呢:)
我先说CartPole的环境。这是一个倒立摆游戏,简单来说平衡得越久越好。
我们都知道gym每次step都会返回给我们四元组。(状态,奖励,游戏结束,额外信息)或者写作(state, reward, done, info)。其中,info是不能在学习中使用的,不然就是作弊了。接下来说的这些参数,都是gym内置的,并非我们规定的。
它的状态空间是4维的。具体是啥我就不细说了,因为好像这个问题里不需要特别地分析(分析的话可能更好),但是下面的几个就比较重要了。尤其和登山车对比来看,它涉及到对具体问题的具体分析。
reward:每活一个时间步,+1
done:游戏结束的标志,当倾角超过一定限度时或者到达200步后游戏结束
action:2维,[0,1],向左或者向右

接下来直接说登山车的环境,我们对比来看。
它的状态空间是2维的。第1维表示车现在的水平位置。很明显,位置越大越接近终点
reward:每一个时间步,-1
done:游戏结束的标志,当到达小旗时结束
action:3维,[0,1,2],依次是向左、不动、向右

对比两个环境我们可以发现不同:
1.reward是不一样,一个是尽量活的时间长,一个是尽量快到达终点。
2.action不一样,登山车有不动这个选项
3.done不一样,倒立摆坚持够200回合或者坚持不住了都会结束,但是登山车只有墨迹超过200回合才结束
有个重要的事情一定要看到:到达200回合后,两个游戏都结束了。根据环境分析,进行reward设计。

3.2 Reward设计

不是环境已经给了我们reward了吗,为什么还要设计reward呢???
一开始我在玩登山车的时候就没意识到这个问题,所以导致小车在低谷荡来荡去根本没法爬上坡,最令人崩溃的是训练出来的Q值近似函数永远都选择一个策略(也就是某个策略的Q一直最大),所以小车一直朝着一个方向开,或者一直熄火作周期运动。
后来我明白了:对于登山车来说,每个时间步减少1个奖励,但是达到目标并没有奖励。即使说就算达到目标有奖励,如果一直没法探索到目标,我们的模型也不可能知道有个地方还有奖励。
再者,这和我的Q函数设计有关。我的Q是基于最基础的TD-0,也就是只迭代一步,只看得到一步的奖励,对于整个游戏获得的奖励总和的感觉没那么深刻。
因此参考了别的博主的文章,改进并且设计了一个简单的分段reward。首先要把从环境观测的state打印出来,看看小车的位置一般对应哪些数值(state[0])。比方说,半山腰是-0.2,3/4山腰是-0.1,1/4山腰是-0.3这样。所以我决定在它获取阶段性胜利的时候给点甜头尝尝。先观察小车随机运动的规律,还是比较容易达到>-0.2的范围的,所以我设计的是:
1.当小车在-0.2~-0.15时,每一步给0.2的额外reward
2.当小车在-0.15~-0.1时,每一步给0.5的额外reward
3.当小车在-0.1~*时,每一步给0.7的额外reward

我还尝试过线性的reward,也就是额外reward=当前位置,但是小车容易卡在低谷;还试过线性截断的reward,当位置大于某值时才有额外的线性位置奖励。但是好像最终还是上面的方案比较好。

对于倒立摆,设计的reward就更简单了,因为倒立摆很容易就探索完整个状态空间了,所以给的额外reward就是,如果游戏结束,给-10的reward。

奖赏的设计其实应该是八仙过海各显神通的地方。比方说倒立摆的reward可以设计成坚持越久即时奖赏越多之类的,不过我这里就没试啦=v=

3.3 Q值近似计算

由于我觉得游戏本身不难,所以设计的神经网络也比较简单。三层的线性层(也可以说成是两层隐藏层),每层的输入输出分别为(以登山车为例,2入3出):
输入层:(2,16)+ LeakyReLU
隐藏层:(16,64)+ LeakyReLU
输出层:(64,3)
Loss:MSE
输入层的维度是根据state的维度来的,环境观测到几个维度就几个输入。隐藏层随意,毕竟要拟合的函数应该比较简单。输出层维度根据action来。
也就是说,这个网络输入是state,输出是各个action的Q值。

3.4 主循环

大致说一下思路:每次循环,首先进行环境采样,然后将每一步的(s, a, r, s_)存入数组中。但是原始数据和PyTorch内的数据并不兼容,所以要进行转换。再则,由于采用了batch训练以及随机抽样,所以将收集来的几千个样本做成一个dataset,采用dataloader进行批量加载。最后再进行训练并保存。因此这部分有如下三个部分:
1.环境采集函数,采集一批样本
2.数据转换部分,将采集到的数据生成dataset和dataloader,便于后续训练
3.训练部分,采用双网络进行训练,这里是直接借用课程里老师的思想的,代码也有参考别的博客的
4.保存模型。如果模型效果很好,则保存为好模型。

4.代码

我觉得该说明的基本上我都没漏呀。我只贴登山车的代码,倒立摆的只有网络输入输出个数不一样,reward不一样,还有对goodmodel的定义不一样。
有几个细节再提一下:
1.state(代码里是observation_xxx)的数据类型为numpy的数组,reward和action皆为基本类型浮点数和整数。
2.在采样环境的时候,加入了一定的随机action概率,便于在刚开始的时候采取不一样的行为
3.训练网络的时候,采用了双网络,这是老师的说法,说不容易发散。还采用了gather函数,因为一次(S, a)只影响一个Q值,也就是网络的一个输出,另外的两个输出不使用贝尔曼方程进行迭代更新。
4.登山车大概不到100次主循环就会出现游戏成功,也就是200步之内到达山顶的情况
5.env.render()是进行图形渲染的函数,训练的时候需要将其注释,否则采样很慢。测试的时候取消注释看效果

#这是一堆初始化
import gym
import random
import torch
import torch.nn as nn
from torch.utils.data import Dataset
#env = gym.make('CartPole-v0')
env = gym.make('MountainCar-v0') #action = (0,1,2) = (left, no_act, right)
#env = gym.make('Hopper-v3')
print(env.observation_space)
#print(env.action_space)#简单的线性模型
def GetModel():#In features:2(state) ,out:3 action qreturn nn.Sequential(nn.Linear(2, 16), nn.LeakyReLU(inplace=True), nn.Linear(16,24),nn.LeakyReLU(inplace=True), nn.Linear(24,3))#创建数据集
class RLDataset(Dataset):def __init__(self, samples, transform = None, target_transform = None):#samples = [(s,a,r,s_), ...]self.samples = self.transform(samples)def __getitem__(self, index):#if self.transform is not None:#    img = self.transform(img) return self.samples[index]def __len__(self):return len(self.samples)def transform(self, samples):transSamples = []for (s,a,r,s_) in samples:sT = torch.tensor(s,).float()sT_ = torch.tensor(s_).float()transSamples.append((sT, a, r, sT_))return transSamples#采样环境函数,可以设置随机操作的概率。重点在于reward的设计
def GetSamplesFromEnv(env, model, epoch, max_steps, drop_ratio = 0.8):train_samples = []each_sample = Noneenv.reset()observation_new = Noneobservation_old = Nonemodel.eval()for i_episode in range(epoch):observation_new = env.reset()observation_old = env.reset()for t in range(max_steps):#env.render()#print(observation)if random.random() > 1-drop_ratio:action = env.action_space.sample()else:inputT = torch.tensor(observation_new).float()action = torch.argmax(model(inputT)).item()#print(action)observation_new, reward, done, info = env.step(action)#print(reward)#We record samples.if t > 0 :#reward += observation_new[0]#if observation_new[0] > -0.35:#    reward += (observation_new[0] + 0.36)*5if observation_new[0] > -0.2:reward += 0.2elif observation_new[0] > -0.15:reward += 0.5elif observation_new[0] > -0.1:reward += 0.7each_sample = (observation_old, action, reward, observation_new)train_samples.append(each_sample)observation_old = observation_newif done:#失败的采样不打印出来if t != 199:print("Episode finished after {} timesteps".format(t+1))breakreturn train_samples#训练网络。这里可能gather函数比较绕,还有双网络更新比较费解。忽略掉这些,和正常训练循环一样
#gamma是贝尔曼方程里的衰减因子
def TrainNet(net_target, net_eval, trainloader, criterion, optimizer, device, epoch_total, gamma):running_loss = 0.0iter_times = 0net_target.eval()net_eval.train()for epoch in range(epoch_total + 1):if epoch > 0:           print('epoch %d, loss %.5f' % (epoch, running_loss))running_loss = 0.0if epoch == epoch_total: break        for i, data in enumerate(trainloader, 0):if iter_times % 100 == 0:net_target.load_state_dict(net_eval.state_dict())s,a,r,s_ = dataoptimizer.zero_grad()#output = Q_predicted.q_t0 = net_eval(s)q_t1 = net_target(s_).detach()q_t1 = gamma * (r + torch.max(q_t1,dim=1)[0])loss = criterion(q_t1, torch.gather(q_t0, dim=1, index=a.unsqueeze(1)).squeeze(1))loss.backward()optimizer.step()running_loss += loss.item()iter_times += 1net_target.load_state_dict(net_eval.state_dict())    print('Finished Training')#最后是一大堆主循环
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
net_target, net_eval = GetModel(), GetModel()
criterion = nn.MSELoss()
optimizer = torch.optim.Adam(net_eval.parameters(),lr=0.01)
train_samples = []#这一堆是测试看效果用的
#PATH = 'mountain_model/goodmodel42.pth'
#net_eval.load_state_dict(torch.load(PATH))
#net_target.load_state_dict(torch.load(PATH))
#GetSamplesFromEnv(env,net_eval, 20, 200, 0)
goodmodel_idx = 0
for i in range(300):drop_ratio = 0.8 - 0.0077*isample_times = 10tmpSample = GetSamplesFromEnv(env,net_eval, sample_times, 200, drop_ratio)train_samples += tmpSample#每次sample的长度就代表了采取的步数,登山车里是越小越好。如果是倒立摆,则是越大越好if len(tmpSample) < sample_times * 160:print("good model!save it!")torch.save(net_eval.state_dict(), "goodmodel" + str(goodmodel_idx) + ".pth")goodmodel_idx += 1#dataset里存着最新的不超过4000的样本if len(train_samples) > 4000:train_samples = train_samples[len(tmpSample):len(train_samples)]trainset = RLDataset(train_samples)trainloader = torch.utils.data.DataLoader(trainset, batch_size=64, shuffle=True, num_workers=0,pin_memory=True)TrainNet(net_target, net_eval, trainloader, criterion, optimizer, device, 10, 0.9)PATH = "model/model"+str(i)+".pth"torch.save(net_eval.state_dict(), PATH)
env.close()

5.参考文献

[1]强化学习 DQN 玩转 gym Mountain Car
[2]gym官方教程

强化学习初探 DQN+PyTorch+gym倒立摆登山车相关推荐

  1. 【强化学习】PPO算法求解倒立摆问题 + Pytorch代码实战

    文章目录 一.倒立摆问题介绍 二.PPO算法简介 三.详细资料 四.Python代码实战 4.1 运行前配置 4.2 主要代码 4.3 运行结果展示 4.4 关于可视化的设置 一.倒立摆问题介绍 Ag ...

  2. 【强化学习实战】基于gym和tensorflow的强化学习算法实现

    [新智元导读]知乎专栏强化学习大讲堂作者郭宪博士开讲<强化学习从入门到进阶>,我们为您节选了其中的第二节<基于gym和tensorflow的强化学习算法实现>,希望对您有所帮助 ...

  3. 深度强化学习-Double DQN算法原理与代码

    深度强化学习-Double DQN算法原理与代码 引言 1 DDQN算法简介 2 DDQN算法原理 3 DDQN算法伪代码 4 仿真验证 引言 Double Deep Q Network(DDQN)是 ...

  4. 强化学习(三) - Gym库介绍和使用,Markov决策程序实例,动态规划决策实例

    强化学习(三) - Gym库介绍和使用,Markov决策程序实例,动态规划决策实例 1. 引言 在这个部分补充之前马尔科夫决策和动态规划部分的代码.在以后的内容我会把相关代码都附到相关内容的后面.本部 ...

  5. 【强化学习】DQN 的三种改进在运筹学中的应用

    这篇文章主要介绍 DQN 的三种改进:Nature DQN.Double DQN.Dueling DQN 在运筹学中的应用,并给出三者的对比,同时也会给出不同增量学习下的效果. 这三种具体的改进方式可 ...

  6. 强化学习——蛇棋游戏gym环境搭建

    强化学习--蛇棋游戏gym环境搭建   学习强化学习精要核心算法与Tensorflow实现这本书中,关于蛇棋游戏利用gym搭建.游戏的规则非常简单,详细请参考冯超的书<<强化学习精要核心算 ...

  7. 快乐的强化学习2——DQN及其实现方法

    快乐的强化学习2--DQN及其实现方法 学习前言 简介 DQN算法的实现 具体实现代码 学习前言 刚刚从大学毕业,近来闲来无事,开始了机器学习的旅程,深度学习是机器学习的重要一环,其可以使得机器自我尝 ...

  8. 强化学习快餐教程(1) - gym环境搭建

    强化学习快餐教程(1) - gym环境搭建 欲练强化学习神功,首先得找一个可以操练的场地. 两大巨头OpenAI和Google DeepMind都不约而同的以游戏做为平台,比如OpenAI的长处是DO ...

  9. 强化学习之DQN超级进化版Rainbow

    阅读本文前可以先了解我前三篇文章<强化学习之DQN><强化学习之DDQN>.<强化学习之 Dueling DQN>. Rainbow结合了DQN算法的6个扩展改进, ...

最新文章

  1. 统一六国的另一个法宝
  2. codeforces 922E
  3. Hadoop之Hadoop基础知识面试复习
  4. linux中tar命令的使用
  5. leetcode刷题可以用python吗_LeetCode刷题——第四天(python)
  6. 【渝粤题库】陕西师范大学100071教育学作业(高起本)
  7. guns企业高级单体版(前后端不分离)运行启动
  8. 用node.js给图片加水印
  9. 批量生成 Hibernate Dao
  10. 山东大学软件学院计算机组成原理课程设计实验三
  11. noi linux 比赛使用哪个编译器,noi linux简介.pdf
  12. 数据建模:个人信用分是怎么计算出来的?
  13. NoScope:极速视频目标检测
  14. 视频提取关键帧的三种方式【已调通】
  15. 【编程训练-考研上机模拟】综合模拟1-2019浙大上机模拟(晴神)
  16. 【办公类-19-01-01】办公中的思考——Python,统计教职工的姓氏谁最多?
  17. 微服务架构分布式全局唯一ID生成策略及算法
  18. JWT无状态登录+跨域问题
  19. Java实现调用百度AI开放云平台(人脸识别API)
  20. Google如何在新标签打开页面打开链接?

热门文章

  1. C语言指针 — 函数指针
  2. php调用翻译,PHP调用有道词典翻译API实现翻译功能及代码
  3. python 文件夹拷贝
  4. 阿里云轻量和云服务器ECS区别对比大全(很详细)
  5. 【NVMe2.0b 13】NVMe 标准数据结构
  6. ATLAS/ICESAT-2 NASA 数据产品详细介绍及相关说明文档
  7. 将字符串的一部分拷贝到另一个字符串
  8. 【经典逻辑】自然演绎推理系统
  9. AI-人工智能学习线路图
  10. zigzag扫描matlab,Zigzag逆扫描