基于像素的 Pong 深度强化学习#

注意

本文目前尚未经过测试,因为底层 gymatari-py 依赖项存在许可/安装问题。通过开发一个依赖项占用空间更小的示例来帮助改进本文!

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

Pong 是一款 1972 年的 2D 游戏,两位玩家使用“球拍”进行类似乒乓球的比赛。每个玩家上下移动球拍,并试图通过触球将球击向对手方向。目标是将球击打到对手球拍无法触及的位置(对手失误)。根据规则,如果一名玩家达到 21 分,则获胜。在 Pong 中,学习与对手对抗的 RL 代理显示在右侧。

Diagram showing operations detailed in this tutorial

此示例基于 代码,该代码由 Andrej Karpathy 于 2017 年在加州大学伯克利分校的 深度 RL 集训营 开发。他 2016 年的 博文 还提供了有关 Pong RL 中使用的机制和理论的更多背景信息。

先决条件#

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

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

  • 深度学习和深度 RL:您应该熟悉 深度学习 的主要概念,这些概念在 Yann LeCun、Yoshua Bengio 和 Geoffrey Hinton 于 2015 年发表的 深度学习 论文中进行了阐述,他们被认为是该领域的一些先驱。本教程将尝试引导您了解深度 RL 的主要概念,并且为了方便起见,您会发现各种包含原始来源链接的文献。

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

  • Matplotlib:用于绘制图像。查看 安装 指南,以便在您的环境中进行设置。

本教程也可以在隔离的环境中本地运行,例如 Virtualenvconda

目录#

  • 关于 RL 和深度 RL 的说明

  • 深度 RL 术语表

  1. 设置 Pong

  2. 预处理帧(观测值)

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

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

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

  6. 训练代理 3 个回合

  7. 后续步骤

  8. 附录

    • 关于 RL 和深度 RL 的注释

    • 如何在 Jupyter notebook 中设置视频回放


关于 RL 和深度 RL 的说明#

RL 中,您的代理通过使用所谓的策略与环境交互来从试错中学习,从而获得经验。在采取一项行动后,代理会收到有关其奖励(它可能获得也可能不获得)和环境的下一个观测值的信息。然后,它可以继续采取另一项行动。这会在一定数量的回合中发生,或者直到任务被认为已完成。

代理的策略通过“映射”代理的观测值到其行动来工作——也就是说,将代理观察到的内容的表现形式与所需的行动进行关联。总体目标通常是优化代理的策略,使其最大化每次观测值的预期奖励。

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

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

深度 RL 术语表#

下面是深度 RL 术语的简明术语表,您可能会发现它对本教程的其余部分很有用

  • 在有限范围的世界中,例如 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 实际上是 LEFTRIGHTFIRERIGHT,而 NOOPFIRE

为简单起见,您的策略网络将有一个输出——“向上移动”(在环境中索引为 2RIGHT)的(对数)概率。另一个可用行动将在环境中索引为 3(“向下移动”或 LEFT)。

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

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

虽然您可以在 Jupyter notebook 中执行各种 RL 实验,但在训练后将 Gym 环境的图像或视频呈现出来以可视化您的代理如何玩 Pong 游戏可能会比较困难。如果您想在 notebook 中设置视频回放,可以在本教程末尾的附录中找到详细信息。

预处理帧(观测值)#

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

您的代理将使用来自 Pong 游戏的帧——屏幕帧的像素——作为策略网络的输入观测值。游戏观测值告诉代理球在哪里,然后将其(通过前向传播)馈送到神经网络(策略)。这类似于 DeepMind 的 DQN 方法(在附录中进一步讨论)。

Pong 屏幕帧在 3 个颜色维度(红色、绿色和蓝色)上为 210x160 像素。数组使用 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)

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

接下来,您将定义策略作为简单的前馈网络,该网络使用游戏观测值作为输入并输出行动对数概率

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

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

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

  • 最后,策略网络将输出一个行动对数概率(给定该观测值)供代理使用——Pong 行动(在环境中索引为 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))

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

在你的深度强化学习算法的学习过程中,你使用动作的对数概率(给定观察结果)和折扣回报(例如,在 Pong 中为 +1 或 -1),并执行反向传递反向传播来更新参数——策略网络的权重。

1. 让我们借助 NumPy 的数组乘法模块来定义反向传递函数(policy_backward())——np.dot()(矩阵乘法)、np.outer()(外积计算)和np.ravel()(将数组展平为一维数组)。

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。

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

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

  • 初始化一个随机观察结果。

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

  • 重复一定数量的回合。

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

    • 代理针对每个观察结果采取行动,观察接收到的奖励并收集状态-动作经验的轨迹(在预定义的回合数或批次大小内)。

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

    • 对于每一批回合。

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

      • 计算累积回报,并为了给短期奖励相对于长期奖励赋予更大的权重,使用折扣因子进行折扣。

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

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

        • 最大化导致高奖励的动作的概率。

Diagram showing operations detailed in this tutorial

你可以随时停止训练,以及检查磁盘上/video目录中保存的播放的 MP4 视频。你可以设置更适合你的设置的最大回合数。

1. 出于演示目的,让我们将训练的回合数限制为 3。如果你正在使用硬件加速(CPU 和 GPU),你可以将其增加到 1000 或更高。相比之下,Andrej Karpathy 的原始实验进行了大约 8000 个回合。

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 的框架,这些框架具有“自动微分”和“自动梯度”,你将在教程结束时了解这些框架。

后续步骤#

你可能会注意到,如果你将回合数从 100 增加到 500 或 1000 以上,训练强化学习代理需要很长时间,具体取决于你用于此任务的硬件——CPU 和 GPU。

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

此外,就像许多基于深度学习的算法一样,你应该考虑到你的策略必须学习的大量参数。在 Pong 中,这个数字加上隐藏层中有 200 个节点并且输入维度大小为 6400(80x80)的网络,总计超过 100 万个。因此,添加更多 CPU 和 GPU 来协助训练始终是一个选择。

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

总的来说,强化学习有很多挑战和可能的解决方案,你可以在强化学习,快与慢中探索其中一些,作者是Matthew Botvinick、Sam Ritter、Jane X. Wang、Zeb Kurth-Nelson、Charles BlundellDemis Hassabis(2019)。


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

使用 NumPy 从头开始构建神经网络是了解 NumPy 和深度学习的好方法。但是,对于实际应用,你应该使用专门的框架——例如PyTorchJAXTensorFlowMXNet——这些框架提供了类似 NumPy 的 API,具有内置的自动微分和 GPU 支持,并且专为高性能数值计算和机器学习而设计。

附录#

关于强化学习和深度强化学习的说明#

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

  • 深度强化学习将强化学习与深度学习相结合。该领域在 2013 年在更复杂的环境(如电子游戏)中取得了首个重大成功——在AlexNet在计算机视觉领域取得突破的一年后。DeepMind 的 Volodymyr Mnih 及其同事发表了一篇名为使用深度强化学习玩 Atari(并在 2015 年更新)的研究论文,该论文表明他们能够训练一个能够在街机学习环境中玩多个经典游戏的代理,达到人类水平。他们的强化学习算法——称为深度 Q 网络 (DQN)——在神经网络中使用了卷积层,这些卷积层逼近Q 学习,并使用了经验回放

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

  • 使用函数逼近的策略梯度,例如神经网络,由 Richard Sutton 等人在 2000 年撰写。它们受到许多先前工作的启发,包括统计梯度跟踪算法,例如REINFORCE(Ronald Williams,1992),以及反向传播(Geoffrey Hinton,1986),这有助于深度学习算法学习。强化学习与神经网络函数逼近在 20 世纪 90 年代由 Gerald Tesauro(时差学习和 TD-Gammon,1995)在研究中引入,他与 IBM 合作开发了一个在 1992 年学会玩西洋双陆棋的代理,以及 Long-Ji Lin(使用神经网络的机器人强化学习,1993)。

  • 自2013年以来,研究人员提出了许多用于学习解决复杂任务的深度强化学习(Deep RL)方法,例如用于围棋游戏的AlphaGo(David Silver等人,2016),通过自我博弈掌握围棋、国际象棋和将棋的AlphaZero(David Silver等人,2017-2018),使用自我博弈进行Dota 2游戏的OpenAI Five(OpenAI,2019),以及使用Actor-Critic算法,结合经验回放自我模仿学习策略蒸馏,用于星际争霸2游戏的AlphaStar(Oriol Vinyals等人,2019)。此外,还有一些其他的实验,例如Electronic Arts/DICE的工程师使用深度强化学习进行战地1游戏。

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

  • 如果您有兴趣了解深度强化学习对其他领域(如神经科学)的影响,可以参考一篇论文,作者为Matthew Botvinick等人(2020)。

如何在Jupyter Notebook中设置视频播放#

  • 如果您使用的是Binder(一个免费的基于Jupyter Notebook的工具),您可以设置Docker镜像,并在apt.txt配置文件中添加freeglut3-devxvfbx11-utils来安装初始依赖项。然后,在binder/environment.ymlchannels下,添加gympyvirtualdisplay以及您可能需要的任何其他内容,例如python=3.7pipjupyterlab。查看以下文章以获取更多信息。

  • 如果您使用的是Google Colaboratory(另一个免费的基于Jupyter Notebook的工具),您可以通过安装和设置X虚拟帧缓冲区/XvfbX11FFmpegPyVirtualDisplayPyOpenGL以及其他依赖项来启用游戏环境的视频播放,如下所述。

  1. 如果您使用的是Google Colaboratory,请在Notebook单元格中运行以下命令以帮助视频播放

    # 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,如指南中所述)。