从像素开始的Pong深度强化学习#

小心

由于底层依赖 gymatari-py 的许可/安装问题,本文目前未经过测试。通过开发一个依赖性更小的示例来帮助改进本文!

本教程演示了如何使用策略梯度方法从头开始实现一个深度强化学习(RL)代理,该代理使用屏幕像素作为输入,通过 NumPy 学习玩 Pong 视频游戏。你的 Pong 代理将使用 人工神经网络 作为其 策略 来获取经验。

Pong 是一款1972年的2D游戏,两名玩家使用“球拍”进行一种形式的乒乓球比赛。每个玩家在屏幕上上下移动球拍,并通过触碰球试图将其击向对手的方向。目标是击球使其越过对手的球拍(他们错过击球)。根据规则,如果一名玩家达到21分,他们就获胜。在Pong中,学习与对手对战的RL代理显示在右侧。

本教程中详细介绍的操作图

这个例子基于 Andrej Karpathy 为 2017 年在 UC Berkeley 举办的 Deep RL Bootcamp 开发的 代码。他在 2016 年的 博客文章 也提供了更多关于 Pong RL 中使用的机制和理论的背景。

前提条件#

  • OpenAI Gym:为了帮助游戏环境,您将使用 Gym — 一个由 OpenAI 开发的开放源代码 Python 接口 由 OpenAI 开发,它帮助执行 RL 任务,同时支持许多模拟环境。

  • Python 和 NumPy:读者应具备一些 Python、NumPy 数组操作和线性代数的知识。

  • 深度学习和深度强化学习: 你应该熟悉 深度学习 的主要概念,这些概念在2015年由Yann LeCun、Yoshua Bengio和Geoffrey Hinton发表的 深度学习 论文中有解释,他们被认为是该领域的先驱。本教程将尝试引导你了解深度强化学习的主要概念,你将找到各种带有原始来源链接的文献,以方便你的学习。

  • Jupyter notebook 环境:由于强化学习实验可能需要高计算能力,您可以使用 BinderGoogle Colaboratory(提供免费限量的 GPU 和 TPU 加速)在云端免费运行教程。

  • Matplotlib:用于绘制图像。查看 安装 指南以在你的环境中设置它。

本教程也可以在本地独立环境中运行,例如 Virtualenvconda

目录#

  • 关于强化学习和深度强化学习的注意事项

  • 深度强化学习词汇表

  1. 设置 Pong

  2. 预处理帧(观察)

  3. 创建策略(神经网络)和前向传播

  4. 设置更新步骤(反向传播)

  5. 定义折扣奖励(预期回报)函数

  6. 训练代理进行3个回合

  7. 下一步

  8. 附录

    • 关于强化学习和深度强化学习的注意事项

    • 如何在您的 Jupyter notebook 中设置视频播放


关于强化学习和深度强化学习的注意事项#

RL 中,你的代理通过使用所谓的策略与环境互动,从试错中学习以获得经验。在采取一个行动后,代理会收到关于其奖励(它可能会也可能不会获得)和环境的下一个观察的信息。然后它可以继续采取另一个行动。这在一个或多个情节中发生,或者直到任务被认为完成。

代理的策略通过“映射”代理的观察到其行动来工作——也就是说,将代理观察到的表现与所需行动相匹配。总体目标通常是优化代理的策略,使其最大化从每个观察中获得的预期奖励。

关于RL的详细信息,有一本由Richard Sutton和Andrew Barton编写的入门书籍

查看教程末尾的附录以获取更多信息。

深度强化学习词汇表#

以下是深度强化学习术语的简明词汇表,您可能会发现这对教程的其余部分很有用:

  • 在一个有限时域的世界中,比如打乒乓球游戏,学习代理可以在一个 episode 中探索(并利用)环境。代理通常需要很多个 episode 才能学习。

  • 代理使用_动作_与_环境_进行交互。

  • 在采取行动后,代理通过一个 奖励(如果有的话)收到一些反馈,这取决于它采取的行动和它所处的 状态。状态包含有关环境的信息。

  • 代理的 observation 是状态的部分观察——这是本教程首选的术语(而不是 state)。

  • 代理可以根据累积的 奖励(也称为 价值函数)和 策略 选择一个动作。累积奖励函数 使用代理的 策略 估计代理访问的观察结果的质量。

  • 策略(由神经网络定义)输出动作选择(以(对数)概率形式),应最大化从智能体所在状态获得的累积奖励。

  • 观察的_预期回报_,条件是行动,被称为_行动值_函数。为了给短期奖励比长期奖励更多的权重,你通常使用一个_折扣因子_(通常是一个介于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. 为乒乓球游戏实例化一个 Gym 环境:

env = gym.make("Pong-v0")

3. 让我们回顾一下在 Pong-v0 环境中可用的操作:

print(env.action_space)
print(env.get_action_meanings())

有6个动作。然而,LEFTFIRE实际上是LEFTRIGHTFIRERIGHT,而NOOPFIRE

为了简单起见,你的策略网络将有一个输出——一个“向上移动”的(对数)概率(索引在 2RIGHT)。其他可用的动作将索引在 3(“向下移动”或 LEFT)。

4. Gym 可以以 MP4 格式保存代理学习过程的视频 — 通过运行以下命令,将 Monitor() 包裹在环境中:

env = Monitor(env, "./video", force=True)

虽然你可以在 Jupyter 笔记本中进行各种强化学习实验,但在训练后渲染 Gym 环境的图像或视频以可视化你的代理如何玩 Pong 游戏可能会相当具有挑战性。如果你想在笔记本中设置视频回放,可以在本教程末尾的附录中找到详细信息。

预处理帧(观察)#

在本节中,您将设置一个函数来预处理输入数据(游戏观察),使其适合神经网络处理,神经网络只能处理以张量(多维数组)形式的浮点类型输入。

你的代理将使用来自 Pong 游戏的帧——屏幕帧中的像素——作为策略网络的输入观察。游戏观察告诉代理在将其(通过前向传递)输入到神经网络(策略)之前球的位置。这类似于 DeepMind 的 DQN 方法(在附录中进一步讨论)。

Pong 屏幕帧的大小是 210x160 像素,覆盖 3 个颜色维度(红色、绿色和蓝色)。数组使用 uint8(或 8 位整数)编码,这些观察结果存储在一个 Gym Box 实例中。

1. 检查 Pong 的观察:

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.

(你可以参考 OpenAI Gym 核心 API 以获取更多关于 Gym 核心类和方法的信息。)

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)

创建策略(神经网络)和前向传播#

接下来,您将定义策略为一个简单的全连接网络,该网络使用游戏观察作为输入并输出动作对数概率:

  • 对于 input ,它将使用 Pong 视频游戏帧——预处理的 1D 向量,包含 6,400 (80x80) 浮点数组。

  • 隐藏层将使用 NumPy 的点积函数 np.dot() 计算输入的加权和,然后应用一个非线性激活函数,例如 ReLU

  • 然后,输出层 将再次执行权重参数和隐藏层输出(使用 np.dot())的矩阵乘法,并通过 softmax 激活函数 传递该信息。

  • 最终,策略网络将为代理输出一个动作对数概率(给定该观察)—— 在环境中索引为2的Pong动作的概率(“向上移动球拍”)。

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))

设置更新步骤(反向传播)#

在你的深度强化学习算法学习过程中,你使用动作对数概率(给定一个观察)和折扣回报(例如,在Pong中为+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()) —— 从一个观察中预期的回报 —— 该函数使用一维奖励数组作为输入(在 NumPy 的 np.zeros_like() 函数的帮助下)。

为了在长期奖励中给予短期奖励更多的权重,你将使用一个 折扣因子 (gamma),它通常是一个介于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

训练代理进行一定数量的回合#

本节介绍如何在训练过程中设置,在此过程中您的代理将使用其策略学习玩乒乓球。

Pong 策略梯度方法的伪代码:

  • 实例化策略——你的神经网络——并随机初始化策略网络中的权重。

  • 初始化一个随机观察。

  • 随机初始化策略网络中的权重。

  • 重复多个回合:

    • 将观察结果输入到策略网络中,并为代理输出动作概率(前向传播)。

    • 代理为每个观察采取行动,观察收到的奖励并收集状态-行动经验轨迹(在预定义的集数或批量大小内)。

    • 计算 交叉熵(带正号,因为你需要最大化奖励而不是最小化损失)。

    • 对于每一批剧集:

      • 使用交叉熵计算你的动作对数概率的梯度。

      • 计算累计回报,并且为了给短期奖励比长期奖励更多的权重,使用一个折扣因子折扣。

      • 将动作对数概率的梯度乘以折扣奖励(即“优势”)。

      • 执行梯度上升(反向传播)以优化策略网络的参数(其权重)。

        • 最大化导致高回报行动的概率。

本教程中详细介绍的操作图

你可以在任何时间停止训练,和/或检查保存在磁盘上 /video 目录中的 MP4 视频。你可以设置更适合你的设置的最大集数。

1. 为了演示目的,我们将训练的集数限制为3集。如果你使用硬件加速(CPUs和GPUs),你可以将集数增加到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 时,包括反向传播在内的深度强化学习训练过程会跨越几行代码,可能显得相当长。这主要原因是你没有使用具有自动微分库的深度学习框架,这些库通常会简化此类实验。本教程展示了如何从头开始执行所有操作,但你也可以使用许多基于 Python 的框架,如 “autodiff” 和 “autograd”,这些你将在教程结束时了解。

下一步#

你可能会注意到,如果你将训练一个 RL 代理的集数从 100 增加到 500 或 1,000+,这会花费很长时间,具体取决于你用于此任务的硬件 — CPU 和 GPU。

策略梯度方法如果给它们足够的时间可以学会一个任务,而且在强化学习中的优化是一个具有挑战性的问题。训练代理学习玩Pong或其他任何任务可能样本效率低下,并且需要大量的剧集。你也可能在训练输出中注意到,即使在数百个剧集之后,奖励也可能有很大的方差。

此外,像许多基于深度学习的算法一样,你应该考虑到你的策略必须学习的大量参数。在Pong中,这个数字加起来达到100万或更多,网络的隐藏层有200个节点,输入维度大小为6,400(80x80)。因此,增加更多的CPU和GPU来协助训练总是一个选项。

你可以使用一种更高级的基于策略梯度的算法,这可以帮助加速训练,提高对参数的敏感性,并解决其他问题。例如,有“自我对弈”方法,如 近端策略优化 (PPO)John Schulman 等人在2017年开发,这些方法被 用于 训练 OpenAI Five 代理在10个月内达到竞争水平的Dota 2。当然,如果你将这些方法应用于较小的Gym环境,训练时间应该是几小时,而不是几个月。

总的来说,有许多强化学习挑战和可能的解决方案,你可以在 Matthew Botvinick, Sam Ritter, Jane X. Wang, Zeb Kurth-Nelson, Charles Blundell, 和 Demis Hassabis (2019) 的 Reinforcement learning, fast and slow 中探索其中一些。


如果你想了解更多关于深度强化学习的知识,你应该查看以下免费的教育资料:

使用 NumPy 从头构建一个神经网络是学习更多关于 NumPy 和深度学习的一个好方法。然而,对于实际应用,你应该使用专门的框架——例如 PyTorchJAXTensorFlowMXNet——它们提供了类似 NumPy 的 API,内置了 自动微分 和 GPU 支持,并且设计用于高性能数值计算和机器学习。

附录#

关于强化学习和深度强化学习的注意事项#

  • 监督深度学习任务中,例如图像识别、语言翻译或文本分类,你更有可能使用大量标记数据。然而,在强化学习中,代理通常不会收到直接的明确反馈来指示正确或错误的动作——它们依赖于其他信号,例如奖励。

  • 深度强化学习 结合了强化学习与深度学习。该领域在2013年取得了第一个重大成功,能够在更复杂的环境中,如电子游戏中表现出色——这一年是在计算机视觉领域AlexNet突破后的第二年。DeepMind的Volodymyr Mnih和同事们发表了一篇名为使用深度强化学习玩Atari游戏(并在2015年更新)的研究论文,展示了他们能够训练一个代理,该代理可以在Arcade Learning Environment中以人类水平玩几款经典游戏。他们的强化学习算法——称为深度Q网络(DQN)——在一个神经网络中使用了卷积层来近似Q学习,并使用了经验回放

  • 与你在本示例中使用的简单策略梯度方法不同,DQN 使用了一种“离策略”的 基于价值 方法(近似于 Q 学习),而原始的 AlphaGo 使用策略梯度和 蒙特卡洛树搜索

  • 带有函数逼近的策略梯度,例如神经网络,在2000年由Richard Sutton等人撰写。他们受到了许多先前作品的影响,包括统计梯度跟踪算法,如REINFORCE(Ronald Williams,1992年),以及反向传播(Geoffrey Hinton,1986年),这有助于深度学习算法学习。使用神经网络函数逼近的强化学习在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),以及使用演员-评论家算法、经验回放自我模仿学习策略蒸馏AlphaStar用于星际争霸2(Oriol Vinyals 等,2019)。此外,还有其他实验,例如Electronic Arts/DICE的工程师使用深度RL进行战地1

  • 视频游戏在深度强化学习研究中受欢迎的原因之一是,与现实世界的实验不同,例如使用遥控直升机Pieter Abbeel 等,2006年)的强化学习,虚拟模拟可以提供更安全的测试环境。

  • 如果你对深度强化学习对其他领域(如神经科学)的影响感兴趣,可以参考 Matthew Botvinick 等人的 论文 (2020)。

如何在您的 Jupyter notebook 中设置视频播放#

  • 如果你使用 Binder — 一个免费的基于 Jupyter notebook 的工具 — 你可以设置 Docker 镜像并在 apt.txt 配置文件中添加 freeglut3-dev, xvfb, 和 x11-utils 来安装初始依赖。然后,在 binder/environment.ymlchannels 下添加 gym, pyvirtualdisplay 以及你可能需要的其他任何东西,例如 python=3.7, pip, 和 jupyterlab。查看以下 文章 获取更多信息。

  • 如果你使用 Google Colaboratory(另一个基于Jupyter notebook的免费工具),你可以通过安装和设置 X virtual frame buffer/XvfbX11FFmpegPyVirtualDisplayPyOpenGL 和其他依赖项来启用游戏环境的视频播放,如下面进一步描述的那样。

  1. 如果你在使用 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
    
  2. 然后,添加这段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 notebook 中查看最后的(非常快的)游戏玩法,并且之前已经实现了 show_any_video() 函数,请在一个单元格中运行这个:

    show_any_video(-1)
    
  • 如果你在本教程的本地环境中使用Linux或macOS,你可以将大部分代码添加到一个 Python (.py) 文件中。然后,你可以在终端中通过 python your-code.py 运行你的Gym实验。要启用渲染,你可以按照 官方OpenAI Gym文档 通过命令行接口操作(确保你已经安装了Gym和Xvfb,如指南中所述)。