使用像素玩 Pong 的深度强化学习#
注意
由于底层 gym
和 atari-py
依赖项的许可/安装问题,本文目前未经过测试。通过开发一个依赖项占用空间更小的示例来帮助改进本文!
本教程演示如何使用策略梯度方法从头开始实现深度强化学习 (RL) 智能体,该方法学习使用屏幕像素作为输入,使用 NumPy 来玩 Pong 视频游戏。您的 Pong 智能体将使用人工神经网络作为其策略,从而获得经验。
Pong 是 1972 年的一款 2D 游戏,其中两名玩家使用“球拍”进行乒乓球形式的比赛。每位玩家在屏幕上上下移动球拍,并通过触摸球来尝试将其击向对手的方向。目标是击球,使其越过对手的球拍(他们错过击球)。根据规则,如果一位玩家达到 21 分,则该玩家获胜。在 Pong 中,学习与对手对战的 RL 智能体显示在右侧。
此示例基于 代码,该代码由 Andrej Karpathy 为 2017 年在加州大学伯克利分校举行的 深度强化学习训练营开发。他 2016 年的 博客文章还提供了有关 Pong RL 中使用的机制和理论的更多背景信息。
先决条件#
OpenAI Gym:为了帮助进行游戏环境,您将使用 Gym — 由 OpenAI 开发的一个开源 Python 接口,可帮助执行强化学习任务,同时支持许多模拟环境。
Python 和 NumPy:读者应该具备 Python、NumPy 数组操作和线性代数的一些知识。
深度学习和深度强化学习:您应该熟悉深度学习的主要概念,这些概念在 Yann LeCun、Yoshua Bengio 和 Geoffrey Hinton 于 2015 年发表的深度学习论文中进行了解释,他们被认为是该领域的一些先驱。本教程将尝试指导您了解深度强化学习的主要概念,并且为了方便起见,您将找到各种文献,其中包含原始来源的链接。
Jupyter 笔记本环境:由于强化学习实验可能需要较高的计算能力,您可以使用 Binder 或 Google Colaboratory(提供免费的有限 GPU 和 TPU 加速)在云端免费运行本教程。
Matplotlib:用于绘制图像。查看 安装指南,以便在您的环境中进行设置。
本教程也可以在隔离的环境(例如 Virtualenv 和 conda)中本地运行。
目录#
关于强化学习和深度强化学习的说明
深度强化学习词汇表
设置 Pong
预处理帧(观察结果)
创建策略(神经网络)和前向传递
设置更新步骤(反向传播)
定义折扣奖励(预期回报)函数
训练智能体 3 个回合
下一步
附录
关于强化学习和深度强化学习的说明
如何在 Jupyter 笔记本中设置视频播放
关于强化学习和深度强化学习的说明#
在强化学习中,您的智能体通过使用所谓的策略与环境交互来通过试错进行学习,从而获得经验。在采取一个行动后,智能体收到有关其奖励(它可能获得也可能不获得)的信息以及环境的下一个观察结果。然后,它可以继续采取另一个行动。这种情况会在多个回合中发生,和/或直到认为任务完成为止。
智能体的策略通过将智能体的观察结果“映射”到其行动来工作 — 即将智能体观察到的内容呈现形式分配给所需的行动。总体目标通常是优化智能体的策略,使其最大化每次观察的预期奖励。
有关强化学习的详细信息,请参阅 Richard Sutton 和 Andrew Barton 合著的入门书籍。
有关更多信息,请查看教程末尾的附录。
深度强化学习词汇表#
下面是深度强化学习术语的简明词汇表,您可能会在教程的剩余部分中发现它很有用
在有限视界的世界(例如,玩 Pong 游戏)中,学习智能体可以在一个回合中探索(和利用)环境。智能体通常需要许多回合才能学习。
智能体使用行动与环境交互。
采取行动后,智能体根据其采取的行动及其所处的状态,通过奖励(如果有)收到一些反馈。状态包含有关环境的信息。
智能体的观察结果是对状态的部分观察 — 这是本教程首选的术语(而不是状态)。
智能体可以基于累积奖励(也称为价值函数)和策略来选择行动。累积奖励函数使用其策略估计智能体访问的观察结果的质量。
策略(由神经网络定义)输出应最大化智能体所处状态的累积奖励的行动选择(以(对数)概率的形式)。
在给定行动的情况下,从观察结果获得的预期回报称为行动-价值函数。为了更多地关注短期奖励而不是长期奖励,您通常会使用折扣因子(通常是 0.9 到 0.99 之间的浮点数)。
智能体在每次策略“运行”期间的行动和状态(观察结果)序列有时称为轨迹 — 这样的序列会产生奖励。
您将使用策略梯度通过“在线”方法来训练您的 Pong 智能体 — 它是一种属于基于策略方法系列的算法。策略梯度方法通常使用机器学习中广泛使用的梯度下降,根据长期累积奖励来更新策略的参数。并且,由于目标是最大化函数(奖励),而不是最小化它,因此该过程也称为梯度上升。换句话说,您使用策略让智能体采取行动,目标是最大化奖励,您可以通过计算梯度并使用它们来更新策略(神经)网络中的参数来实现此目标。
设置 Pong#
1. 首先,您应该安装 OpenAI Gym(使用 pip install gym[atari]
- 此软件包当前在 conda 上不可用),并导入 NumPy、Gym 和必要的模块
import numpy as np
import gym
Gym 可以使用 Monitor
包装器监视和保存输出
from gym import wrappers
from gym.wrappers import Monitor
2. 为 Pong 游戏实例化一个 Gym 环境
env = gym.make("Pong-v0")
3. 让我们回顾一下 Pong-v0
环境中可用的操作
print(env.action_space)
print(env.get_action_meanings())
有 6 个操作。但是,LEFTFIRE
实际上是 LEFT
,RIGHTFIRE
— RIGHT
,NOOP
— FIRE
。
为简单起见,您的策略网络将只有一个输出 — 用于“向上移动”(索引为 2
或 RIGHT
)的(对数)概率。另一个可用的操作将在索引 3 处(“向下移动”或 LEFT
)。
4. Gym 可以将智能体学习的视频以 MP4 格式保存 — 通过运行以下内容将 Monitor()
包装在环境周围
env = Monitor(env, "./video", force=True)
虽然你可以在 Jupyter Notebook 中进行各种 RL 实验,但在训练后渲染 Gym 环境的图像或视频,以可视化你的智能体如何玩乒乓球游戏可能会相当具有挑战性。如果你想在 Notebook 中设置视频回放,可以在本教程末尾的附录中找到详细信息。
预处理帧(观察结果)#
在本节中,你将设置一个函数来预处理输入数据(游戏观察),使其能够被神经网络消化。神经网络只能处理浮点类型的张量(多维数组)形式的输入。
你的智能体将使用乒乓球游戏的帧——屏幕帧的像素——作为策略网络的输入观察。游戏观察告诉智能体球的位置,然后再将其(通过前向传递)馈送到神经网络(策略)。这类似于 DeepMind 的 DQN 方法(在附录中进一步讨论)。
乒乓球屏幕帧是 210x160 像素,具有 3 个颜色维度(红色、绿色和蓝色)。数组以 uint8
(或 8 位整数)编码,这些观察结果存储在 Gym Box 实例上。
1. 检查乒乓球的观察结果
print(env.observation_space)
在 Gym 中,智能体的动作和观察结果可以是 Box
(n 维)或 Discrete
(固定范围整数)类的一部分。
2. 你可以通过以下方式查看随机观察结果——一帧:
1) Setting the random `seed` before initialization (optional).
2) Calling Gym's `reset()` to reset the environment, which returns an initial observation.
3) Using Matplotlib to display the `render`ed observation.
(有关 Gym 核心类和方法的更多信息,你可以参考 OpenAI Gym 核心 API。)
import matplotlib.pyplot as plt
env.seed(42)
env.reset()
random_frame = env.render(mode="rgb_array")
print(random_frame.shape)
plt.imshow(random_frame)
要将观察结果馈送到策略(神经)网络,你需要将它们转换为 6,400 (80x80x1) 个浮点数组的 1D 灰度向量。(在训练期间,你将使用 NumPy 的 np.ravel()
函数来展平这些数组。)
3. 设置用于帧(观察)预处理的辅助函数
def frame_preprocessing(observation_frame):
# Crop the frame.
observation_frame = observation_frame[35:195]
# Downsample the frame by a factor of 2.
observation_frame = observation_frame[::2, ::2, 0]
# Remove the background and apply other enhancements.
observation_frame[observation_frame == 144] = 0 # Erase the background (type 1).
observation_frame[observation_frame == 109] = 0 # Erase the background (type 2).
observation_frame[observation_frame != 0] = 1 # Set the items (rackets, ball) to 1.
# Return the preprocessed frame as a 1D floating-point array.
return observation_frame.astype(float)
4. 预处理之前的随机帧以测试该函数——策略网络的输入是一个 80x80 的 1D 图像
preprocessed_random_frame = frame_preprocessing(random_frame)
plt.imshow(preprocessed_random_frame, cmap="gray")
print(preprocessed_random_frame.shape)
创建策略(神经网络)和前向传递#
接下来,你将策略定义为一个简单的前馈网络,该网络使用游戏观察作为输入,并输出动作对数概率
对于输入,它将使用乒乓球视频游戏帧——预处理后的 6,400 (80x80) 个浮点数组的 1D 向量。
然后,输出层将再次对权重参数和隐藏层的输出进行矩阵乘法运算(使用
np.dot()
),并将该信息通过 softmax 激活函数发送。最后,策略网络将为智能体输出一个动作对数概率(给定观察结果)——乒乓球动作在环境中索引为 2(“向上移动球拍”)的概率。
1. 让我们为输入、隐藏层和输出层实例化某些参数,并开始设置网络模型。
首先为实验创建一个随机数生成器实例(为可重现性设置种子)
rng = np.random.default_rng(seed=12288743)
然后
设置输入(观察)维度——你预处理的屏幕帧
D = 80 * 80
设置隐藏层神经元的数量。
H = 200
将你的策略(神经)网络模型实例化为一个空字典。
model = {}
在神经网络中,权重是重要的可调整参数,网络通过前向和后向传播数据来微调这些参数。
2. 使用一种称为 Xavier 初始化 的技术,使用 NumPy 的 Generator.standard_normal()
设置网络模型的初始权重,该函数返回标准正态分布上的随机数,以及 np.sqrt()
model["W1"] = rng.standard_normal(size=(H, D)) / np.sqrt(D)
model["W2"] = rng.standard_normal(size=H) / np.sqrt(H)
3. 你的策略网络首先随机初始化权重,并将输入数据(帧)从输入层通过隐藏层向前馈送到输出层。此过程称为前向传递或前向传播,并在 policy_forward()
函数中概述
def policy_forward(x, model):
# Matrix-multiply the weights by the input in the one and only hidden layer.
h = np.dot(model["W1"], x)
# Apply non-linearity with ReLU.
h[h < 0] = 0
# Calculate the "dot" product in the outer layer.
# The input for the sigmoid function is called logit.
logit = np.dot(model["W2"], h)
# Apply the sigmoid function (non-linear activation).
p = sigmoid(logit)
# Return a log probability for the action 2 ("move up")
# and the hidden "state" that you need for backpropagation.
return p, h
请注意,有两个激活函数用于确定输入和输出之间的非线性关系。这些 非线性函数 应用于层的输出
整流线性单元 (ReLU):定义为上面的
h[h<0] = 0
。它为负输入返回 0,如果为正输入,则返回相同的值。Sigmoid:在下面定义为
sigmoid()
。它“包裹”最后一层的输出,并返回 (0, 1) 范围内的动作对数概率。
4. 使用 NumPy 的 np.exp()
单独定义 sigmoid 函数,以计算指数
def sigmoid(x):
return 1.0 / (1.0 + np.exp(-x))
设置更新步骤(反向传播)#
在深度 RL 算法中的学习期间,你使用动作对数概率(给定观察结果)和折扣回报(例如,乒乓球中的 +1 或 -1)并执行反向传递或反向传播以更新参数——策略网络的权重。
1. 让我们在 NumPy 的数组乘法模块的帮助下定义反向传递函数 ( policy_backward()
)—— np.dot()
(矩阵乘法)、np.outer()
(外积计算)和 np.ravel()
(将数组展平为 1D 数组)
def policy_backward(eph, epdlogp, model):
dW2 = np.dot(eph.T, epdlogp).ravel()
dh = np.outer(epdlogp, model["W2"])
dh[eph <= 0] = 0
dW1 = np.dot(dh.T, epx)
# Return new "optimized" weights for the policy network.
return {"W1": dW1, "W2": dW2}
使用网络的中间隐藏“状态” (eph
) 和剧集的动作对数概率梯度 (epdlogp
), policy_backward
函数将梯度反向传播到策略网络,并更新权重。
2. 在智能体训练期间应用反向传播时,你需要为每个剧集保存几个变量。让我们实例化空列表来存储它们
# All preprocessed observations for the episode.
xs = []
# All hidden "states" (from the network) for the episode.
hs = []
# All gradients of probability actions
# (with respect to observations) for the episode.
dlogps = []
# All rewards for the episode.
drs = []
你将在训练期间的每个剧集结束后手动重置这些变量,并在使用 NumPy 的 np.vstack()
将它们“填满”后重塑它们。这将在本教程末尾的训练阶段进行演示。
3. 接下来,为了在优化智能体策略时执行梯度上升,通常使用深度学习优化器(你正在使用梯度进行优化)。在此示例中,你将使用 RMSProp——一种自适应优化 方法。让我们为优化器设置一个折扣因子——衰减率
decay_rate = 0.99
4. 你还需要存储梯度(在 NumPy 的 np.zeros_like()
的帮助下),以便在训练期间进行优化步骤
首先,保存累积批次梯度和的更新缓冲区
grad_buffer = {k: np.zeros_like(v) for k, v in model.items()}
其次,为梯度上升的优化器存储 RMSProp 内存
rmsprop_cache = {k: np.zeros_like(v) for k, v in model.items()}
定义折扣奖励(预期回报)函数#
在本节中,你将设置一个用于计算折扣奖励 ( discount_rewards()
) 的函数,它是来自观察的预期回报,该函数使用 1D 奖励数组作为输入(在 NumPy 的 np.zeros_like()
函数的帮助下)。
为了对短期奖励给予比长期奖励更多的权重,你将使用一个折扣因子(伽玛),它通常是 0.9 到 0.99 之间的浮点数。
gamma = 0.99
def discount_rewards(r, gamma):
discounted_r = np.zeros_like(r)
running_add = 0
# From the last reward to the first...
for t in reversed(range(0, r.size)):
# ...reset the reward sum
if r[t] != 0:
running_add = 0
# ...compute the discounted reward
running_add = running_add * gamma + r[t]
discounted_r[t] = running_add
return discounted_r
训练智能体进行多次剧集#
本节介绍如何设置训练过程,在此过程中,你的智能体将学习使用其策略来玩乒乓球。
乒乓球策略梯度方法的伪代码
实例化策略——你的神经网络——并随机初始化策略网络中的权重。
初始化随机观察。
随机初始化策略网络中的权重。
重复多次剧集
将观察结果输入到策略网络中,并输出智能体的动作概率(前向传播)。
智能体为每个观察结果采取一个动作,观察收到的奖励,并收集状态-动作经验的轨迹(在预定义的剧集数或批次大小上)。
计算 交叉熵(带正号,因为你需要最大化奖励,而不是最小化损失)。
对于每批剧集
使用交叉熵计算动作对数概率的梯度。
计算累积回报,为了更重视短期奖励而非长期奖励,使用折扣因子 discount。
将动作对数概率的梯度乘以折扣奖励(即“优势”)。
执行梯度上升(反向传播)以优化策略网络的参数(权重)。
最大化导致高奖励的动作的概率。
您可以随时停止训练,或者/和在 /video
目录中查看保存在磁盘上的已保存游戏 MP4 视频。您可以设置适合您设置的最大训练集数。
1. 为了演示目的,我们将训练的训练集数限制为 3。如果您使用硬件加速(CPU 和 GPU),则可以将该数字增加到 1,000 或更高。作为比较,Andrej Karpathy 的原始实验使用了约 8,000 个训练集。
max_episodes = 3
2. 设置批量大小和学习率值
批量大小 决定了模型执行参数更新的频率(以训练集为单位)。它是您的代理收集状态-动作轨迹的次数。在收集结束时,您可以执行动作概率乘积的最大化。
学习率 有助于限制权重更新的幅度,以防止它们过度校正。
batch_size = 3
learning_rate = 1e-4
3. 为 Gym 的 render
方法设置游戏渲染默认变量(它用于显示观察结果,是可选的,但在调试期间可能很有用)
render = False
4. 通过调用 reset()
来设置代理的初始(随机)观察
observation = env.reset()
5. 初始化先前的观察
prev_x = None
6. 初始化奖励变量和训练集计数
running_reward = None
reward_sum = 0
episode_number = 0
7. 为了模拟帧之间的运动,将策略网络的单个输入帧 (x
) 设置为当前和先前预处理帧之间的差异
def update_input(prev_x, cur_x, D):
if prev_x is not None:
x = cur_x - prev_x
else:
x = np.zeros(D)
return x
8. 最后,使用您预定义的函数开始训练循环
:tags: [output_scroll]
while episode_number < max_episodes:
# (For rendering.)
if render:
env.render()
# 1. Preprocess the observation (a game frame) and flatten with NumPy's `ravel()`.
cur_x = frame_preprocessing(observation).ravel()
# 2. Instantiate the observation for the policy network
x = update_input(prev_x, cur_x, D)
prev_x = cur_x
# 3. Perform the forward pass through the policy network using the observations
# (preprocessed frames as inputs) and store the action log probabilities
# and hidden "states" (for backpropagation) during the course of each episode.
aprob, h = policy_forward(x, model)
# 4. Let the action indexed at `2` ("move up") be that probability
# if it's higher than a randomly sampled value
# or use action `3` ("move down") otherwise.
action = 2 if rng.uniform() < aprob else 3
# 5. Cache the observations and hidden "states" (from the network)
# in separate variables for backpropagation.
xs.append(x)
hs.append(h)
# 6. Compute the gradients of action log probabilities:
# - If the action was to "move up" (index `2`):
y = 1 if action == 2 else 0
# - The cross-entropy:
# `y*log(aprob) + (1 - y)*log(1-aprob)`
# or `log(aprob)` if y = 1, else: `log(1 - aprob)`.
# (Recall: you used the sigmoid function (`1/(1+np.exp(-x)`) to output
# `aprob` action probabilities.)
# - Then the gradient: `y - aprob`.
# 7. Append the gradients of your action log probabilities.
dlogps.append(y - aprob)
# 8. Take an action and update the parameters with Gym's `step()`
# function; obtain a new observation.
observation, reward, done, info = env.step(action)
# 9. Update the total sum of rewards.
reward_sum += reward
# 10. Append the reward for the previous action.
drs.append(reward)
# After an episode is finished:
if done:
episode_number += 1
# 11. Collect and reshape stored values with `np.vstack()` of:
# - Observation frames (inputs),
epx = np.vstack(xs)
# - hidden "states" (from the network),
eph = np.vstack(hs)
# - gradients of action log probabilities,
epdlogp = np.vstack(dlogps)
# - and received rewards for the past episode.
epr = np.vstack(drs)
# 12. Reset the stored variables for the new episode:
xs = []
hs = []
dlogps = []
drs = []
# 13. Discount the rewards for the past episode using the helper
# function you defined earlier...
discounted_epr = discount_rewards(epr, gamma)
# ...and normalize them because they have high variance
# (this is explained below.)
discounted_epr -= np.mean(discounted_epr)
discounted_epr /= np.std(discounted_epr)
# 14. Multiply the discounted rewards by the gradients of the action
# log probabilities (the "advantage").
epdlogp *= discounted_epr
# 15. Use the gradients to perform backpropagation and gradient ascent.
grad = policy_backward(eph, epdlogp, model)
# 16. Save the policy gradients in a buffer.
for k in model:
grad_buffer[k] += grad[k]
# 17. Use the RMSProp optimizer to perform the policy network
# parameter (weight) update at every batch size
# (by default: every 10 episodes).
if episode_number % batch_size == 0:
for k, v in model.items():
# The gradient.
g = grad_buffer[k]
# Use the RMSProp discounting factor.
rmsprop_cache[k] = (
decay_rate * rmsprop_cache[k] + (1 - decay_rate) * g ** 2
)
# Update the policy network with a learning rate
# and the RMSProp optimizer using gradient ascent
# (hence, there's no negative sign)
model[k] += learning_rate * g / (np.sqrt(rmsprop_cache[k]) + 1e-5)
# Reset the gradient buffer at the end.
grad_buffer[k] = np.zeros_like(v)
# 18. Measure the total discounted reward.
running_reward = (
reward_sum
if running_reward is None
else running_reward * 0.99 + reward_sum * 0.01
)
print(
"Resetting the Pong environment. Episode total reward: {} Running mean: {}".format(
reward_sum, running_reward
)
)
# 19. Set the agent's initial observation by calling Gym's `reset()` function
# for the next episode and setting the reward sum back to 0.
reward_sum = 0
observation = env.reset()
prev_x = None
# 20. Display the output during training.
if reward != 0:
print(
"Episode {}: Game finished. Reward: {}...".format(episode_number, reward)
+ ("" if reward == -1 else " POSITIVE REWARD!")
)
一些注意事项
如果您之前运行过实验并且想重复它,您的
Monitor
实例可能仍在运行,这可能会在您下次尝试训练代理时引发错误。因此,您应该首先通过取消注释并运行以下单元格,通过调用env.close()
来关闭Monitor
# env.close()
在 Pong 中,如果一名玩家没有将球击回,他们会收到负奖励 (-1),而另一名玩家会获得 +1 奖励。代理通过玩 Pong 获得的奖励具有很大的差异。因此,最佳实践是使用相同的均值(使用
np.mean()
)和标准差(使用 NumPy 的np.std()
)来标准化它们。当仅使用 NumPy 时,深度 RL 训练过程(包括反向传播)会跨越多个代码行,这些代码行可能显得很长。其中一个主要原因是您没有使用具有自动微分库的深度学习框架,该库通常会简化此类实验。本教程展示了如何从头开始执行所有操作,但您也可以使用许多具有“自动微分”和“自动梯度”的基于 Python 的框架,您将在本教程的结尾学习到这些框架。
下一步#
您可能会注意到,如果您将训练集数从 100 增加到 500 或 1,000+,训练 RL 代理会花费很长时间,具体取决于您用于此任务的硬件(CPU 和 GPU)。
如果您给策略梯度方法足够的时间,它们可以学习任务,而 RL 中的优化是一个具有挑战性的问题。训练代理学习玩 Pong 或任何其他任务可能会效率低下,并且需要大量训练集。您还可能会在训练输出中注意到,即使在数百个训练集之后,奖励也可能具有很大的差异。
此外,与许多基于深度学习的算法一样,您应该考虑策略必须学习的大量参数。在 Pong 中,如果网络的隐藏层中有 200 个节点且输入维度为 6,400 (80x80),则此数字会达到 100 万或更多。因此,添加更多 CPU 和 GPU 来辅助训练始终是一种选择。
您可以使用更高级的基于策略梯度的算法,该算法可以帮助加快训练速度,提高对参数的敏感度并解决其他问题。例如,有一些“自博弈”方法,例如由 John Schulman 等人在 2017 年开发的 近端策略优化 (PPO),该方法被用于训练 OpenAI Five 代理,历时 10 个月,以达到竞技水平的 Dota 2 游戏水平。当然,如果您将这些方法应用于较小的 Gym 环境,则应该需要几个小时而不是几个月的时间才能完成训练。
一般来说,有许多 RL 挑战和可能的解决方案,您可以在 强化学习,快与慢 中探索其中的一些,作者为 Matthew Botvinick、Sam Ritter、Jane X. Wang、Zeb Kurth-Nelson、Charles Blundell 和 Demis Hassabis (2019)。
如果您想了解有关深度 RL 的更多信息,您应该查看以下免费教育资料
深度 RL 入门:由 OpenAI 开发。
讲座,由 David Silver (DeepMind, UCL) 讲授。
使用 NumPy 从头开始构建神经网络是学习有关 NumPy 和深度学习的绝佳方法。但是,对于实际应用,您应该使用专门的框架(例如 PyTorch、JAX、TensorFlow 或 MXNet),这些框架提供类似 NumPy 的 API,具有内置的 自动微分和 GPU 支持,并且专为高性能数值计算和机器学习而设计。
附录#
关于 RL 和深度 RL 的说明#
在用于图像识别、语言翻译或文本分类等任务的 监督深度学习中,您更可能使用大量标记数据。但是,在 RL 中,代理通常不会收到指示正确或错误操作的直接显式反馈 — 他们依赖于其他信号,例如奖励。
深度 RL 将 RL 与 深度学习 相结合。该领域在 2013 年在更复杂的环境中(例如视频游戏)取得了第一个重大成功 — 也就是 AlexNet 在计算机视觉领域取得突破的一年后。Volodymyr Mnih 和 DeepMind 的同事发表了一篇名为 使用深度强化学习玩 Atari(并在 2015 年进行了 更新)的研究论文,该论文表明,他们能够训练一个代理,该代理可以在街机学习环境中的多个经典游戏中达到人类水平。他们的 RL 算法(称为深度 Q 网络 (DQN))在神经网络中使用了 卷积层,该网络近似于 Q 学习,并使用了 经验回放。
与您在此示例中使用的简单策略梯度方法不同,DQN 使用一种“离策略”基于值的方法(近似于 Q 学习),而原始的 AlphaGo 使用策略梯度和 蒙特卡洛树搜索。
具有函数逼近的策略梯度(例如神经网络)由 Richard Sutton 等人在 2000 年 撰写。它们受到许多先前作品的影响,包括统计梯度跟踪算法,例如 REINFORCE(Ronald Williams,1992)以及帮助深度学习算法学习的 反向传播(Geoffrey Hinton,1986)。使用神经网络函数逼近的 RL 在 1990 年代由 Gerald Tesauro(时间差分学习和 td-gammon,1995)在研究中引入,他与 IBM 合作开发了一个代理,该代理学会了在 1992 年 玩双陆棋,以及 Long-Ji Lin (使用神经网络进行机器人强化学习,1993)。
自 2013 年以来,研究人员提出了许多使用深度 RL 学习解决复杂任务的值得注意的方法,例如用于围棋游戏的 AlphaGo(David Silver 等人,2016 年),通过自博弈掌握围棋、国际象棋和将棋的 AlphaZero(David Silver 等人,2017-2018 年),使用 自博弈的 Dota 2 的 OpenAI Five(OpenAI,2019 年),以及使用具有 经验回放的 actor-critic 算法的 StarCraft 2 的 AlphaStar、自模仿学习 和 策略蒸馏(Oriol Vinyals 等人,2019 年)。此外,还有其他实验,例如电子艺界/DICE 的工程师对 战地 1 进行深度 RL。
深度强化学习研究中电子游戏受欢迎的原因之一是,与现实世界的实验(例如使用遥控直升机的强化学习(Pieter Abbeel 等人,2006 年))不同,虚拟仿真可以提供更安全的测试环境。
如果您有兴趣了解深度强化学习对其他领域(如神经科学)的影响,您可以参考 Matthew Botvinick 等人(2020 年)撰写的一篇论文。
如何在 Jupyter 笔记本中设置视频回放#
如果您正在使用 Binder(一个基于 Jupyter 笔记本的免费工具),您可以设置 Docker 镜像,并将
freeglut3-dev
、xvfb
和x11-utils
添加到apt.txt
配置文件中以安装初始依赖项。然后,在binder/environment.yml
中的channels
下,添加gym
、pyvirtualdisplay
以及您可能需要的任何其他内容,例如python=3.7
、pip
和jupyterlab
。请查看以下文章以获取更多信息。如果您正在使用 Google Colaboratory(另一个基于 Jupyter 笔记本的免费工具),您可以通过安装和设置 X 虚拟帧缓冲/Xvfb、X11、FFmpeg、PyVirtualDisplay、PyOpenGL 以及其他依赖项来启用游戏环境的视频回放,如下文所述。
如果您正在使用 Google Colaboratory,请在笔记本单元格中运行以下命令以帮助进行视频回放
# Install Xvfb and X11 dependencies. !apt-get install -y xvfb x11-utils > /dev/null 2>&1 # To work with videos, install FFmpeg. !apt-get install -y ffmpeg > /dev/null 2>&1 # Install PyVirtualDisplay for visual feedback and other libraries/dependencies. !pip install pyvirtualdisplay PyOpenGL PyOpenGL-accelerate > /dev/null 2>&1
然后,添加此 Python 代码
# Import the virtual display module. from pyvirtualdisplay import Display # Import ipythondisplay and HTML from IPython for image and video rendering. from IPython import display as ipythondisplay from IPython.display import HTML # Initialize the virtual buffer at 400x300 (adjustable size). # With Xvfb, you should set `visible=False`. display = Display(visible=False, size=(400, 300)) display.start() # Check that no display is present. # If no displays are present, the expected output is `:0`. !echo $DISPLAY # Define a helper function to display videos in Jupyter notebooks:. # (Source: https://star-ai.github.io/Rendering-OpenAi-Gym-in-Colaboratory/) import sys import math import glob import io import base64 def show_any_video(mp4video=0): mp4list = glob.glob('video/*.mp4') if len(mp4list) > 0: mp4 = mp4list[mp4video] video = io.open(mp4, 'r+b').read() encoded = base64.b64encode(video) ipythondisplay.display(HTML(data='''<video alt="test" autoplay loop controls style="height: 400px;"> <source src="data:video/mp4;base64,{0}" type="video/mp4" /> </video>'''.format(encoded.decode('ascii')))) else: print('Could not find the video!')
如果您想在 Jupyter 笔记本中查看最后一次(非常快)的游戏过程,并且之前已经实现了
show_any_video()
函数,请在一个单元格中运行此代码show_any_video(-1)
如果您是在 Linux 或 macOS 的本地环境中按照本教程中的说明进行操作,则可以将大多数代码添加到一个 Python (
.py
) 文件中。然后,您可以通过在终端中运行python your-code.py
来运行您的 Gym 实验。要启用渲染,您可以按照 官方 OpenAI Gym 文档中的说明使用命令行界面(确保您已安装 Gym 和 Xvfb,如指南中所述)。