注意

由于底层的 gymatari-py 依赖存在许可和安装问题,本文尚未经过测试。请通过减少依赖的方式来改进本文!

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

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

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

这个例子基于 Andrej Karpathy 在 2017 年加州大学伯克利分校的 深度 RL 训练营 上开发的 代码。他在 2016 年的 博文 中还提供了有关 Pong RL 中使用的机制和理论的更多背景知识。

先决条件

  • OpenAI Gym:为了帮助处理游戏环境,你将使用 Gym —— 一个由 OpenAI 开发的开源 Python 接口,它可以在支持许多模拟环境的同时执行 RL 任务。
  • Python 和 NumPy:读者应该对 Python、NumPy 数组操作和线性代数有一定的了解。
  • 深度学习和深度 RL:你应该熟悉 深度学习 的主要概念,这些概念在 2015 年由 Yann LeCun、Yoshua Bengio 和 Geoffrey Hinton 发表的 深度学习 论文中有详细解释,他们被认为是该领域的先驱之一。本教程将尝试指导你了解深度 RL 的主要概念,并为你提供各种文献,其中包含了原始来源的链接,以方便你阅读。
  • Jupyter 笔记本环境:由于 RL 实验可能需要较高的计算能力,你可以使用 BinderGoogle Colaboratory 在云端免费运行本教程(后者提供免费的有限 GPU 和 TPU 加速)。
  • Matplotlib:用于绘制图像。请查看 安装 指南以在你的环境中进行设置。

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

目录

  • 关于 RL 和深度 RL 的说明
  • 深度 RL 术语表
  1. 设置 Pong
  2. 预处理帧(观察)
  3. 创建策略(神经网络)和前向传递
  4. 设置更新步骤(反向传播)
  5. 定义折扣奖励(预期回报)函数
  6. 对代理进行 3 个回合的训练
  7. 下一步
  8. 附录
    • 关于 RL 和深度 RL 的注释
    • 如何在 Jupyter 笔记本中设置视频播放

关于 RL 和深度 RL 的说明

RL 中,你的代理通过与环境交互,使用所谓的策略进行试错学习。在采取一种行动之后,代理会收到关于其奖励(可能得到或可能不得到)和环境的下一个观察的信息。然后它可以继续采取另一种行动。这在一定数量的回合中发生,直到任务被认为完成为止。

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

有关 RL 的详细信息,请参阅 Richard Sutton 和 Andrew Barton 的 入门书籍

有关更多信息,请参阅本教程末尾的附录。

深度 RL 术语表

以下是深度 RL 术语的简明词汇表,你可能会在本教程的其余部分中发现它们有用:

  • 在有限时间段内的世界中,例如 Pong 游戏,学习代理可以在一个回合内探索(和利用)环境。代理通常需要多个回合来学习。
  • 代理使用行动与环境进行交互。
  • 在采取行动之后,代理通过奖励(如果有的话)接收一些反馈,这取决于它采取的行动和它所处的状态。状态包含有关环境的信息。
  • 代理的观察是状态的部分观察 —— 这是本教程更喜欢的术语(而不是状态)。
  • 代理可以根据累积奖励(也称为价值函数)和策略选择行动。累积奖励函数估计代理使用其策略访问观察的质量。
  • 策略(由神经网络定义)输出行动选择(作为(对数)概率),应该最大化代理所处状态的累积奖励。
  • 对于观察的预期回报,条件是采取的行动,称为行动值函数。为了更重视短期奖励而不是长期奖励,通常使用一个折扣因子(通常是 0.9 到 0.99 之间的浮点数)。
  • 代理通过每个策略“运行”期间的行动和状态(观察)序列,有时被称为轨迹 —— 这样的序列产生奖励。

你将通过使用策略梯度的“on-policy”方法来训练你的 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 实际上是 LEFTRIGHTFIRERIGHTNOOPFIRE

为了简化起见,你的策略网络将只有一个输出 —— “向上移动” 的(对应于 2RIGHT)(对数)概率。另一个可用的行动将在 3(“向下移动” 或 LEFT)。

4. Gym 可以将代理的学习保存为 MP4 格式的视频 —— 通过运行以下代码将 Monitor() 包装器包装在环境周围:

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

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

预处理帧(观察)

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

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

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

1. 检查 Pong 的观察结果:

print(env.observation_space)

在 Gym 中,代理的行动和观察可以是 Box(n 维)或 Discrete(固定范围整数)类的一部分。

2. 你可以查看一个随机的观察结果 —— 一个帧 —— 的方法是:

1) 在初始化之前设置随机的 `seed`(可选)。

2) 调用 Gym 的 `reset()` 来重置环境,它会返回一个初始观察结果。

3) 使用 Matplotlib 来显示 `render` 后的观察结果。

(您可以参考 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)

为了将观测输入到策略(神经)网络中,您需要将它们转换为 1D 灰度向量,其中包含 6,400(80x80x1)个浮点数组。(在训练过程中,您将使用 NumPy 的 np.ravel() 函数将这些数组展平。)

3. 设置一个帮助函数来进行帧(观测)预处理:

def frame_preprocessing(observation_frame):
    # 裁剪帧。
    observation_frame = observation_frame[35:195]
    # 对帧进行二次采样。
    observation_frame = observation_frame[::2, ::2, 0]
    # 移除背景并应用其他增强。
    observation_frame[observation_frame == 144] = 0  # 擦除背景(类型 1)。
    observation_frame[observation_frame == 109] = 0  # 擦除背景(类型 2)。
    observation_frame[observation_frame != 0] = 1  # 将物体(球拍、球)设置为 1。
    # 将预处理后的帧作为 1D 浮点数组返回。
    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 视频游戏帧 —— 经过预处理的 1D 向量,包含 6,400(80x80)个浮点数组。
  • 隐藏层 将使用 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):
    # 使用点积函数将权重与输入在唯一的隐藏层中进行矩阵乘法。
    h = np.dot(model["W1"], x)
    # 使用 ReLU 进行非线性激活。
    h[h < 0] = 0
    # 在外层计算“点”积。
    # sigmoid 函数的输入称为 logit。
    logit = np.dot(model["W2"], h)
    # 应用 sigmoid 函数(非线性激活)。
    p = sigmoid(logit)
    # 返回动作 2(“向上移动”)的对数概率
    # 和反向传播所需的隐藏“状态”。
    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. 让我们定义反向传递函数(policy_backward()),借助 NumPy 的数组乘法模块 —— 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 {"W1": dW1, "W2": dW2}

使用网络的中间隐藏“状态”(eph)和动作对数概率的梯度(epdlogp)来自一个 episode,policy_backward 函数通过策略网络将梯度向后传播,并更新权重。

2. 在代理训练期间应用反向传播时,您需要为每个 episode 保存多个变量。让我们实例化空列表来存储它们:

# 该 episode 的所有预处理观测。
xs = []
# 该 episode 的所有隐藏“状态”(来自网络)。
hs = []
# 该 episode 的所有概率动作的梯度
# (相对于观测)。
dlogps = []
# 该 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() 函数)。

为了对短期奖励给予更多权重,而不是长期奖励,您将使用一个 折扣因子(gamma),通常是 0.9 到 0.99 之间的浮点数。

gamma = 0.99


def discount_rewards(r, gamma):
    discounted_r = np.zeros_like(r)
    running_add = 0
    # 从最后一个回报到第一个...
    for t in reversed(range(0, r.size)):
        # ...重置回报总和
        if r[t] != 0:
            running_add = 0
        # ...计算折扣回报
        running_add = running_add * gamma + r[t]
        discounted_r[t] = running_add
    return discounted_r

对代理进行一定数量的 episode 进行训练

Pong 游戏的策略梯度方法的伪代码如下:

  • 实例化策略 - 你的神经网络 - 并随机初始化策略网络中的权重。
  • 初始化一个随机观察。
  • 随机初始化策略网络中的权重。
  • 重复以下步骤一定次数的回合:
    • 将观察输入到策略网络中,并输出代理的动作概率(前向传播)。
    • 代理根据每个观察采取动作,观察获得的奖励,并收集状态-动作经验的轨迹(在预定义的回合数或批次大小上)。
    • 计算交叉熵(带正号,因为你需要最大化奖励而不是最小化损失)。
    • 对于每个批次的回合:
      • 使用交叉熵计算动作对数概率的梯度。
      • 计算累积回报,并使用折扣因子 discount 来给短期回报和长期回报赋予不同的权重。
      • 将动作对数概率的梯度乘以折扣回报(“优势”)。
      • 执行梯度上升(反向传播)来优化策略网络的参数(权重)。
        • 最大化导致高回报的动作的概率。

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

你可以随时停止训练,或者在 /video 目录中检查保存的游戏录像。你可以根据你的设置选择适当的最大回合数。

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:
    # (用于渲染)
    if render:
        env.render()

    # 1. 预处理观察(游戏帧)并使用 NumPy 的 `ravel()` 进行扁平化。
    cur_x = frame_preprocessing(observation).ravel()

    # 2. 为策略网络实例化观察
    x = update_input(prev_x, cur_x, D)
    prev_x = cur_x

    # 3. 使用观察(预处理帧作为输入)通过策略网络进行前向传递,并在每个回合过程中存储动作对数概率和隐藏的“状态”(用于反向传播)。
    aprob, h = policy_forward(x, model)
    # 4. 如果动作索引为 `2`("向上移动"),则将其概率作为动作
    # 如果它高于随机采样值,则使用动作 `3`("向下移动")。
    action = 2 if rng.uniform() < aprob else 3

    # 5. 在单独的变量中缓存观察和隐藏的“状态”(来自网络)以进行反向传播。
    xs.append(x)
    hs.append(h)

    # 6. 计算动作对数概率的梯度:
    # - 如果动作是“向上移动”(索引为 `2`):
    y = 1 if action == 2 else 0
    # - 交叉熵:
    # `y*log(aprob) + (1 - y)*log(1-aprob)`
    # 或者 `log(aprob)` 如果 y = 1,否则:`log(1 - aprob)`。
    # (回想一下:你使用了 sigmoid 函数(`1/(1+np.exp(-x))`)输出了 `aprob` 动作概率。)
    # - 然后梯度:`y - aprob`。
    # 7. 追加动作对数概率的梯度。
    dlogps.append(y - aprob)
    # 8. 执行动作并使用 Gym 的 `step()` 函数更新参数;获得新的观察。
    observation, reward, done, info = env.step(action)
    # 9. 更新总奖励的总和。
    reward_sum += reward
    # 10. 追加先前动作的奖励。
    drs.append(reward)

    # 完成一个回合后:
    if done:
        episode_number += 1
        # 11. 使用 `np.vstack()` 收集和重塑存储的值:
        # - 观察帧(输入),
        epx = np.vstack(xs)
        # - 隐藏的“状态”(来自网络),
        eph = np.vstack(hs)
        # - 动作对数概率的梯度,
        epdlogp = np.vstack(dlogps)
        # - 过去回合的获得奖励。
        epr = np.vstack(drs)

        # 12. 为新回合重置存储的变量:
        xs = []
        hs = []
        dlogps = []
        drs = []

        # 13. 使用之前定义的辅助函数折扣过去回合的奖励...
        discounted_epr = discount_rewards(epr, gamma)
        # ...并对其进行归一化,因为它们具有较高的方差
        # (下面会解释)。
        discounted_epr -= np.mean(discounted_epr)
        discounted_epr /= np.std(discounted_epr)

        # 14. 将折扣奖励乘以动作对数概率的梯度(“优势”)。
        epdlogp *= discounted_epr
        # 15. 使用梯度执行反向传播和梯度上升。
        grad = policy_backward(eph, epdlogp, model)
        # 16. 将策略梯度保存在缓冲区中。
        for k in model:
            grad_buffer[k] += grad[k]
        # 17. 使用 RMSProp 优化器在每个批次大小(默认为每 10 个回合)上执行策略网络参数(权重)更新。
        if episode_number % batch_size == 0:
            for k, v in model.items():
                # 梯度。
                g = grad_buffer[k]
                # 使用 RMSProp 折扣因子。
                rmsprop_cache[k] = (
                    decay_rate * rmsprop_cache[k] + (1 - decay_rate) * g ** 2
                )
                # 使用学习率和 RMSProp 优化器更新策略网络(因此没有负号)
                model[k] += learning_rate * g / (np.sqrt(rmsprop_cache[k]) + 1e-5)
                # 在最后重置梯度缓冲区。
                grad_buffer[k] = np.zeros_like(v)

        # 18. 测量总折扣奖励。
        running_reward = (
            reward_sum
            if running_reward is None
            else running_reward * 0.99 + reward_sum * 0.01
        )
        print(
            "重置 Pong 环境。回合总奖励:{} 平均奖励:{}".format(
                reward_sum, running_reward
            )
        )

        # 19. 通过调用 Gym 的 `reset()` 函数设置代理的初始观察
        # 为下一个回合,并将奖励总和重置为 0。
        reward_sum = 0
        observation = env.reset()
        prev_x = None

    # 20. 在训练过程中显示输出。
    if reward != 0:
        print(
            "回合 {}: 游戏结束。奖励:{}...".format(episode_number, reward)
            + ("" if reward == -1 else " 正奖励!")
        )

一些建议:

  • 如果之前运行过实验并希望重复运行,你的 Monitor 实例可能仍在运行,这可能会导致下次尝试训练代理时出错。因此,你应该先通过取消注释并运行下面的单元格来关闭 Monitor
# env.close()
  • 在 Pong 游戏中,如果玩家没有将球击回,他们会收到负奖励(-1),而另一个玩家会得到 +1 的奖励。代理通过玩 Pong 获得的奖励具有显着的方差。因此,最佳做法是使用相同的均值(使用 np.mean())和标准差(使用 NumPy 的 np.std())对其进行归一化。
  • 当仅使用 NumPy 时,深度强化学习训练过程(包括反向传播)涉及多行代码,可能看起来相当长。其中一个主要原因是你没有使用具有自动微分库的深度学习框架,这通常简化了此类实验。本教程展示了如何从头开始执行所有操作,但你也可以使用许多基于 Python 的框架,其中包含“autodiff”和“autograd”,你将在教程末尾了解到。

下一步操作

策略梯度方法可以在给予足够时间的情况下学习任务,而强化学习中的优化是一个具有挑战性的问题。训练智能体学会玩乒乓球或其他任务可能会导致样本效率低下,并且需要大量的回合。您可能还会注意到,在训练输出中,即使经过数百个回合,奖励也可能具有很高的方差。

此外,与许多基于深度学习的算法一样,您应该考虑到您的策略需要学习的大量参数。在乒乓球中,这个数量加起来超过了100万,隐藏层中有200个节点,输入维度的大小为6,400(80x80)。因此,增加更多的CPU和GPU来辅助训练总是一个选择。

您可以使用更先进的基于策略梯度的算法,可以帮助加快训练速度,提高对参数的敏感性,并解决其他问题。例如,有一种名为Proximal Policy Optimization (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等人发表了一篇名为《Playing Atari with deep reinforcement learning》(2013年)(更新于2015年),展示了他们能够训练一个能够以人类水平玩几个经典游戏的智能体。他们的强化学习算法 - 称为深度Q网络(DQN) - 在近似Q学习的神经网络中使用了卷积层,并使用了经验回放
  • 与您在此示例中使用的简单策略梯度方法不同,DQN使用一种“离策略”基于值方法(近似Q学习),而原始的AlphaGo使用策略梯度和蒙特卡洛树搜索
  • 2000年,Richard Sutton等人撰写了关于带有函数近似(如神经网络)的策略梯度的论文。他们受到了许多先前工作的影响,包括统计梯度跟随算法,如REINFORCE(Ronald Williams,1992),以及反向传播(Geoffrey Hinton,1986),它有助于深度学习算法的学习。在20世纪90年代,Gerald Tesauro(Temporal difference learning and td-gammon,1995)在IBM工作,与IBM合作开发了一个学会玩backgammon的智能体,并引入了使用神经网络函数近似的强化学习方法,以及Long-Ji Lin(Reinforcement learning for robots using neural networks,1993)的研究。
  • 自2013年以来,研究人员已经提出了许多值得注意的方法,用于使用深度强化学习学习解决复杂任务,例如AlphaGo(David Silver等人,2016年),AlphaZero通过自我对弈掌握围棋、国际象棋和将棋(David Silver等人,2017-2018年),OpenAI Five通过自我对弈(OpenAI,2019年)训练Dota 2智能体,以及AlphaStar通过使用演员-评论家算法、经验回放自我模仿学习策略蒸馏(Oriol Vinyals等人,2019年)训练StarCraft 2智能体。此外,还有其他实验,例如由Electronic Arts/DICE的工程师开发的Battlefield 1的深度强化学习。
  • 视频游戏在深度强化学习研究中受欢迎的原因之一是,与真实世界的实验(例如使用遥控直升机进行的强化学习)相比,虚拟模拟可以提供更安全的测试环境。
  • 如果您对深度强化学习对其他领域(如神经科学)的影响感兴趣,可以参考Matthew Botvinick等人(2020年)的一篇论文

如何在Jupyter笔记本中设置视频播放

  • 如果您使用的是Binder - 一个免费的基于Jupyter笔记本的工具 - 您可以设置Docker镜像,并将freeglut3-devxvfbx11-utils添加到apt.txt配置文件中以安装初始依赖项。然后,在binder/environment.yml中的channels下添加gympyvirtualdisplay和其他可能需要的内容,例如python=3.7pipjupyterlab。有关更多信息,请查看以下文章
  • 如果您使用的是Google Colaboratory - 另一个免费的基于Jupyter笔记本的工具 - 您可以通过安装和设置X虚拟帧缓冲/XvfbX11FFmpegPyVirtualDisplayPyOpenGL和其他依赖项来启用游戏环境的视频播放,如下所示。
  1. 如果您使用的是Google Colaboratory,请在笔记本单元格中运行以下命令以帮助进行视频播放:

    # 安装Xvfb和X11依赖项。
    !apt-get install -y xvfb x11-utils > /dev/null 2>&1
    # 为了使用视频,安装FFmpeg。
    !apt-get install -y ffmpeg > /dev/null 2>&1
    # 安装PyVirtualDisplay以进行可视反馈和其他库/依赖项。
    !pip install pyvirtualdisplay PyOpenGL PyOpenGL-accelerate > /dev/null 2>&1
    
  2. 然后,添加以下Python代码:

    # 导入虚拟显示模块。
    from pyvirtualdisplay import Display
    # 从IPython中导入ipythondisplay和HTML以进行图像和视频渲染。
    from IPython import display as ipythondisplay
    from IPython.display import HTML
    
    # 在400x300(可调整大小)上初始化虚拟缓冲区。
    # 使用Xvfb时,应将`visible=False`。
    display = Display(visible=False, size=(400, 300))
    display.start()
    
    # 检查是否没有显示。
    # 如果没有显示,预期输出是`:0`。
    !echo $DISPLAY
    
    # 定义一个辅助函数以在Jupyter笔记本中显示视频。
    #(来源: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,如指南中所述)。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐