摘要

《Playing Atari with Deep Reinforcement Learning》是首篇将强化学习与深度学习结合起来的深度强化学习经典论文,由DeepMind团队设计开发,算法在Atari 2600 游戏环境进行测试,在部分游戏中的测试表现优于人类玩家。

论文网址:https://paperswithcode.com/paper/playing-atari-with-deep-reinforcement

01 用Pycharm创建虚拟环境项目

项目代码和训练结果上传到百度网盘了,可以先下载下来,但是由于虚拟环境太大了所以没有上传,需要自己下载安装一遍,具体操作可以查看下文介绍。
链接:https://pan.baidu.com/s/1zoh0glqH4xcNSbOUuR2r7g?pwd=00wd
提取码:00wd

首先使用Pycharm创建一个新项目,然后如下图所示在设置中添加虚拟环境:

创建虚拟环境项目的目的在于使当前项目的运行环境与自己的Python环境分开,后续会在虚拟环境中安装需要的包,以免影响自己之前的Python环境。我用的Pycharm版本是2019版的,新版Pycharm的设置应该是类似的,可以根据自身情况百度。每个人的Anaconda路径不同,需要根据自己安装位置选择基本解释器。

虚拟环境的配置参考CSDN文章:Pycharm 创建并管理虚拟环境。

image.png

虚拟环境创建完成后,还需要在设置里面把终端程序设置一下:

image.png

这个时候打开Pycharm下面的终端选项卡,可以看到终端前面提示(venv),表示当前终端是处于虚拟环境中的:

image.png

这个时候我们需要的包都可以在这个终端这里通过pip进行安装了。

记得把从百度云下载的文件code、Imgs、model这三个文件夹给复制到当前的项目文件夹里面。项目需要的Python包已经包含在code文件夹下的requirements.tx文件中了,打开Pycharm的终端选项卡,通过cd命令进入code文件夹:

cd code

然后pip安装需要的包:

pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple

正常来讲上面的环境配置完之后,code文件夹下的代码应该都可以正常运行了。如果无法正常运行,有可能是Atari的游戏环境的问题,具体可以参考这篇CSDN文章:【gym】新版安装(0.21以上)以及配置Atari环境,超简单(Windows)。

02 论文模型解释

简单来讲,论文设计了一个DQN网络,将连续4帧、裁切为84X84的游戏画面堆叠成4X84X84的输入,然后通过卷积+ReLU、卷积+ReLU、Flatten、全连接+ReLU、全连接得到与动作维数相对应的输出。这里主要对 BreakOut (弹球打方块) 这款游戏进行了训练和测试,这款游戏对应的动作有4种,所以这里的输出维数为4。

输出的4维数组,分别代表4种动作对应的Q(s,a)值,选择最大的Q值所对应编号作为网络所输出的动作代号:

0:表示不移动

1:表示开始游戏 (如果游戏已经开始,那么1仍然不移动)

2:表示右移

3:表示左移

卷积的大小计算:

输出大小 = (输入大小 - 卷积核大小 + 2 x padding) / 步长 + 1

image.png

cke_6108.png

cke_8210.png

03 昇思MindSpore代码实现

打开 code文件夹中 playing_atari.py文件,代码的具体含义如下:

3.1 游戏环境创建

在导入相应的库之后,首先创建游戏环境env:

env = gym.make("BreakoutNoFrameskip-v4")  # 游戏环境
env = gym.wrappers.RecordEpisodeStatistics(env)
env = gym.wrappers.ResizeObservation(env, (84, 84))  # 设置图片放缩
env = gym.wrappers.GrayScaleObservation(env)  # 设置图片为灰度图
env = gym.wrappers.FrameStack(env, 4)  # 4帧图片堆叠在一起作为一个观测
env = MaxAndSkipEnv(env, skip=4)  # 跳帧,一个动作维持4帧

这里已经对env环境进行了封装,对其输出的图片进行了预处理,每一次的观测输出都是4X84X84的堆叠的灰度图片。

3.2 DQN网络定义

利用昇思MindSpore定义DQN网络,直接利用nn.SequentialCell(),按设计的网络进行定义即可:

class DQN(nn.Cell):
    def __init__(self, nb_actions):
        super().__init__()
        self.network = nn.SequentialCell(
            nn.Conv2d(in_channels=4, out_channels=16, kernel_size=8, stride=4, pad_mode='valid'),
            nn.ReLU(),
            nn.Conv2d(in_channels=16, out_channels=32, kernel_size=4, stride=2, pad_mode='valid'),
            nn.ReLU(),
            nn.Flatten(),
            nn.Dense(in_channels=2592, out_channels=256),
            nn.ReLU(),
            nn.Dense(in_channels=256, out_channels=nb_actions),
        )

    def construct(self, x):
        return self.network(x / 255.)

construct() 表示网络的输出,类似于Pytorch框架里面的forward()

3.3 设计经验存放池

class ReplayBuffer():
    def __init__(self, replay_memory_size):

    def add(self, obs, next_obs, action, reward, done):

    def sample(self, sample_num):
        ...
        return Tensor(temp_obs, ms.float32), Tensor(temp_next_obs, ms.float32), Tensor(temp_action, ms.int32), Tensor(temp_reward, ms.float32), Tensor(temp_done, ms.float32)

这里不贴出具体代码了,简单来说就是实现了经验元组的保存,以及批量采样方便用于后续神经网络的训练。

3.4 损失函数、优化器、训练函数的定义

首先对定义的DQN类实例化一个网络q_network,然后定义优化器为nn.Adam,定义损失函数为nn.HuberLOss()

q_network = DQN(nb_actions=env.action_space.n)  # 网络实例化
optimizer = nn.Adam(params=q_network.trainable_params(), learning_rate=1.25e-4)  # 优化器
loss_fn = nn.HuberLoss()  # 损失函数

后面是昇思MindSpore定义网络训练时特有的步骤,叫函数式自动微分,可以参考官网关于函数式自动微分的教程。具体而言就是先定义一个Loss计算函数forward_fn,然后根据Loss计算函数生成梯度计算函数grad_fn,然后利用梯度计算函数来定义网络训练一步的函数train_step。这样利用train_step函数,只需要输入所需要的数据,就可以对网络的参数进行一次更新,完成一步训练。

# 损失值计算函数
def forward_fn(observations, actions, y):
    current_q_value = q_network(observations).gather_elements(dim=1, index=actions).squeeze()  # 把经验对中这个动作对应的q_value给提取出来
    loss = loss_fn(current_q_value, y)
    return loss

cke_14837.png

ms.ops.value_and_grad利用定义好的Loss计算函数forward_fn,可以返回得到一个梯度计算函数grad_fn。

然后在训练函数train_step中,我们就可以利用grad_fn计算梯度,然后利用优化器optimizer进行梯度反向传播,更新网络参数,完成一步训练。

3.4 网络训练

接下来就可以对网络进行训练了,这里对主要一些关键代码做出解释:

def Deep_Q_Learning(env, replay_memory_size=100_000, nb_epochs=40000_000, update_frequency=4, batch_size=32,
                    discount_factor=0.99, replay_start_size=5000, initial_exploration=1, final_exploration=0.01,
                    exploration_steps=100_000):

首先定义好训练需要的相关参数,包括经验池容量大小100_000,总训练epochs=40000_000,每4个epoch更新一次网络参数,折扣因子为0.99,经验池满5000时开始训练,初始探索概率为1,总探索epochs为100_000

这里的探索是指为了DQN学到更好的策略,在训练之前先随机产生动作进行探索,探索概率会逐渐减小,然后就会完全依靠DQN产生动作,称这个策略为ε−greedy策略。

在训练之前要将网络设置为训练模式:

q_network.set_train()  # 设置网络为训练模式

然后就是让DQN与游戏进行交互,产生动作的相应的代码为(随机探索或者由DQN产生动作):

if random.random() < epsilon:  # With probability ε select a random action a
    action = np.array(env.action_space.sample())
else:  # Otherwise select a = max_a Q∗(φ(st), a; θ)
    temp_input = Tensor(obs, ms.float32).unsqueeze(0)
    q_values = q_network(temp_input)
    action = q_values.argmax(axis=1).item().asnumpy()

保存每次经验元组到经验池:

rb.add(obs, real_next_obs, action, reward, done)

cke_21372.png

data_obs, data_next_obs, data_action, data_reward, data_done = rb.sample(batch_size)
# 这一部分不用求梯度,所以写在forward_fn和train_step函数之外
max_q_value = q_network(data_next_obs).max(1)
y = data_reward.flatten() + discount_factor * max_q_value * (1 - data_done.flatten())
loss = train_step(data_obs, data_action, y)

cke_26946.png

image.png

04 实验结果

可以看出训练之后的DQN已经学会玩这个游戏了,一般能得150分左右,运气好的的话像这样能打到300分:

image.png

往期回顾

项目分享 | 如何通过MindNLP将HuggingFace Datasets嫁接到昇思MindSpore

项目分享 | 昇思MindSpore接入强化学习的新环境和新算法

项目分享 | 腺形智消——新一代儿童腺样体肥大诊疗方案领航者

项目分享 | 基于昇思MindSpore AI框架的肾脏肿瘤分割一等奖[咸鱼]团队思路

Logo

昇腾万里,让智能无所不及

更多推荐