游戏深度学习实用指南(二)

原文:annas-archive.org/md5/0ce7869756af78df5fbcd0d4ae8d5259

译者:飞龙

协议:CC BY-NC-SA 4.0

第二部分:深度强化学习

本节我们将详细研究深度强化学习,使用各种框架和技术,探索多个有趣的示例。

本节将涵盖以下章节:

  • 第五章,深度强化学习简介

  • 第六章,Unity ML-Agents

  • 第七章,代理与环境

  • 第八章,理解 PPO

  • 第九章,奖励与强化学习

  • 第十章,模仿与迁移学习

  • 第十一章,构建多智能体环境

第五章:介绍 DRL

深度强化学习DRL)目前正席卷全球,被视为机器学习技术中的“热点”,即达到某种形式的通用人工智能的目标。也许正是因为 DRL 接近了通用人工智能的边界,或者是我们所理解的通用智能。它也可能是你正在阅读本书的主要原因之一。幸运的是,本章以及本书大部分内容,都将深入探讨强化学习RL)及其许多变体。在本章中,我们将开始学习 RL 的基础知识,以及它如何与深度学习DL)相结合。我们还将探索OpenAI Gym环境,这是一个很棒的 RL 实验场,并学习如何利用一些简单的 DRL 技术进行实践。

请记住,这本书是一本实践性很强的书,因此我们将尽量减少技术理论的讨论,而是通过大量的工作实例进行探索。一些读者可能会觉得没有理论背景会感到迷茫,并且希望自己去深入探索 RL 的理论部分。

对于那些不熟悉 RL 理论背景的读者,我们将介绍几个核心概念,但这是简略版,因此建议您在准备好之后从其他来源学习更多的理论知识。

在本章中,我们将开始学习 DRL,这一主题将贯穿多个章节。我们将从基础开始,然后探索一些适应于 DL 的工作示例。以下是本章内容:

  • 强化学习

  • Q 学习模型

  • 运行 OpenAI gym

  • 第一个深度 Q 网络(DQN)与 DRL

  • RL 实验

对于喜欢跳跃阅读的读者:是的,从本章开始阅读本书是完全可以的。不过,您可能需要回到前几章来完成一些练习。我们还假设您的 Python 环境已经配置好 TensorFlow 和 Keras,如果不确定,可以查看项目文件夹中的requirements.txt文件。

本书中的所有项目都是在 Visual Studio 2017(Python)中构建的,这是本书示例推荐的编辑器。如果您使用 VS 2017 与 Python,您可以通过打开章节解决方案文件轻松管理示例。当然,也有许多其他优秀的 Python 编辑器和工具,您可以使用自己习惯的工具。

强化学习

相较于其他机器学习方法,RL 目前在进展方面领先于其他技术。注意,这里使用的是“方法论”而非“技术”这个词。RL 是一种方法论或算法,它应用了一个我们可以与神经网络一起使用的原理,而神经网络是可以应用于多种方法论的机器学习技术。之前,我们讨论了与 DL 结合的其他方法论,但我们更专注于实际的实现。然而,RL 引入了一种新的方法论,它要求我们在理解如何应用之前,先了解更多的内在和外在机制。

强化学习(RL)由加拿大人理查德·萨顿(Richard Sutton)普及,他是阿尔伯塔大学的教授。萨顿还参与了 Google DeepMind 的 RL 开发,并且经常被视为 RL 的奠基人。

任何机器学习系统的核心都需要训练。通常,AI 代理/大脑一开始什么也不知道,我们通过某种自动化过程向其输入数据让它学习。正如我们所见,最常见的做法是监督训练。这是指我们首先给训练数据打上标签。我们还研究了无监督训练,在这种情况下,生成对抗网络GANs)通过相互对抗进行训练。然而,这两种方法都没有复制我们在生物学中看到的那种学习或训练方式,这种方式通常被称为奖励或强化学习(RL):一种让你教会狗狗为零食叫唤、捡报纸并在户外排泄的学习方式,一种让代理探索自己的环境并自我学习的方式。这与我们期望通用 AI 所使用的学习方式并无太大不同;毕竟,RL 可能与我们所使用的系统类似,或者说我们是这么认为的。

大卫·西尔弗(David Silver),前萨顿教授的学生,现在是 DeepMind 的负责人,拥有一系列关于强化学习(RL)理论背景的优秀视频。前五个视频非常有趣,推荐观看,但后续内容较为深奥,可能并不适合所有人。以下是视频链接:www.youtube.com/watch?v=2pWv7GOvuf0

强化学习定义了一种名为自身的训练方式。这种基于奖励的训练在下图中显示:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/9b176d0d-9b93-423d-9d0c-cb1998759379.png

强化学习

图中展示了一个代理在一个环境中。该代理读取环境的状态,然后决定并执行一个动作。这个动作可能会或不会带来奖励,而奖励可能是好是坏。在每次动作和可能的奖励后,代理会再次收集环境的状态。这个过程会重复,直到代理达到一个终结状态。也就是说,直到它达到目标;也许它死掉,或者只是累了。需要注意的是,前图中有一些细微的地方。首先,代理并不总是会获得奖励,这意味着奖励可能会延迟,直到某个未来的目标达到。这与我们之前探索的其他学习方式不同,后者会立即给训练网络反馈。奖励可以是好是坏,而负面训练代理同样有效,但对人类来说则不太适用。

现在,正如你所料,任何强大的学习模型,其数学可能会相当复杂,且对新手来说确实具有一定的挑战性。我们不会深入探讨理论细节,只会在下一节中描述一些强化学习的基础。

多臂老虎机

我们之前看到的图示描述了完整的强化学习(RL)问题,这也是我们在本书后续大部分内容中使用的模型。然而,我们经常讲授一个简化的单步变体问题,称为多臂老虎机。多臂老虎机这个名称源于拉斯维加斯的老虎机,而并非指某些其他不正当的含义。我们使用这些简化的场景来解释强化学习的基础知识,呈现为单步或单状态问题。

在多臂老虎机的情况下,可以想象一个虚构的多臂拉斯维加斯老虎机,根据拉动的臂不同奖励不同的奖品,但每个臂的奖品始终相同。智能体在这种情况下的目标是找出每次应拉动哪个臂。我们可以进一步将其建模为如下所示的方程:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/cce0e6d0-2ea0-4ea0-b7e2-6e13a93eb5fb.png

考虑以下方程:

该方程计算智能体所采取每个行动的值(V),这是一个向量。然后,它将这些值反馈到自身,从奖励中减去并乘以学习率。计算得出的值可以用来决定拉动哪个臂,但首先智能体需要至少拉动每个臂一次。让我们快速在代码中建模这一过程,作为游戏/仿真程序员,我们可以看到它是如何工作的。打开 Chapter_5_1.py 代码并按照以下步骤操作:

  1. 本练习的代码如下:
alpha = .9
arms = [['bronze' , 1],['gold', 3], ['silver' , 2], ['bronze' , 1]]
v = [0,0,0,0]

for i in range(10):
    for a in range(len(arms)):
        print('pulling arm '+ arms[a][0])
        v[a] = v[a] + alpha * (arms[a][1]-v[a])

print(v)
  1. 这段代码创建了所需的设置变量,armsgoldsilverbronze)以及值向量 v(全为零)。然后,代码会进行多次迭代(10次),每次都拉动一个臂并根据方程计算并更新值 v。请注意,奖励值被臂拉动的值所替代,即项 arms[a][1]

  2. 运行示例,你将看到生成的输出,显示每个行动的值,或者在这种情况下是一个臂的拉动。

正如我们所见,通过一个简单的方程式,我们能够建模多臂老虎机问题,并得出一个解决方案,使得智能体能够持续地选择正确的臂。这为强化学习奠定了基础,在接下来的章节中,我们将迈出下一步,探讨上下文老虎机

上下文老虎机

现在,我们可以将单个多臂老虎机问题提升为包含多个多臂老虎机的问题,每个老虎机都有自己的一组臂。现在,我们的问题引入了上下文或状态到方程中。随着每个老虎机定义自己的上下文/状态,我们现在在质量和行动的角度来评估我们的方程。我们修改后的方程如下所示:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/c461e128-42f5-4362-9277-a839f0c1589d.png

考虑以下方程:

[1,2,3,4

2,3,4,5

4,2,1,4]

让我们打开Chapter_5_2.py并观察以下步骤:

  1. 打开代码,如下所示,并按照与之前示例所做的更改进行操作:
import random

alpha = .9
bandits = [[['bronze' , 1],['gold', 3], ['silver' , 2], ['bronze' , 1]],
           [['bronze' , 1],['gold', 3], ['silver' , 2], ['bronze' , 1]],
           [['bronze' , 1],['gold', 3], ['silver' , 2], ['bronze' , 1]],
           [['bronze' , 1],['gold', 3], ['silver' , 2], ['bronze' , 1]]]
q = [[0,0,0,0],
     [0,0,0,0],
     [0,0,0,0],
     [0,0,0,0]]

for i in range(10): 
    for b in range(len(bandits)):
        arm = random.randint(0,3)
        print('pulling arm {0} on bandit {1}'.format(arm,b))
        q[b][arm] = q[b][arm] + alpha * (bandits[b][arm][1]-q[b][arm])

print(q)
  1. 这段代码设置了多个多臂老虎机,每个都有一组臂。然后,它会进行多次迭代,但这次在每次循环时,它还会循环遍历每个老虎机。在每次循环中,它会随机选择一个臂来拉动,并评估其质量。

  2. 运行示例并查看q的输出。注意,即使在选择随机臂后,方程依然持续选择金臂,即回报最高的臂来拉动。

随意多玩这个示例,并查看练习以获取更多灵感。当我们讨论 Q-Learning 时,我们将扩展我们的 RL 问题的复杂度。然而,在进入那个部分之前,我们将稍作偏离,看看如何设置 OpenAI Gym,以便进行更多的 RL 实验。

使用 OpenAI Gym 进行强化学习(RL)

强化学习(RL)已经变得非常流行,现在有一场竞赛,专门开发帮助构建 RL 算法的工具。目前,这个领域的两个主要竞争者是OpenAI GymUnity。Unity 迅速成为了我们稍后将深入探索的 RL 竞速机器。现在,我们将启用辅助功能,使用 OpenAI Gym 来进一步探索 RL 的基本原理。

我们需要安装 OpenAI Gym 工具包才能继续,安装方法可能因操作系统的不同而有所差异。因此,我们将在这里专注于 Windows 的安装说明,其他操作系统用户可能会遇到较少的困难。按照以下步骤在 Windows 上安装 OpenAI Gym:

  1. 安装一个 C++编译器;如果你已经安装了 Visual Studio 2017,可能已经有一个推荐的编译器了。你可以在这里找到其他支持的编译器:wiki.python.org/moin/WindowsCompilers

  2. 确保已安装 Anaconda,并打开 Anaconda 命令提示符,然后运行以下命令:

conda create -n gym
conda activate gym
conda install python=3.5  # reverts Python, for use with TensorFlow later
pip install tensorflow
pip install keras pip install gym
  1. 就我们目前的目的而言,短期内不需要安装任何其他 Gym 模块。Gym 有很多示例环境,其中 Atari 游戏和 MuJoCo(机器人仿真器)是最有趣的几种。我们将在本章稍后查看 Atari 游戏模块。

这应该会为你的系统安装 Gym 环境。我们需要的大多数功能将通过最小设置即可工作。如果你决定深入使用 Gym,那么你可能需要安装其他模块;有很多模块。在下一节中,我们将测试这个新环境,边学习 Q-Learning。

一个 Q-Learning 模型

RL 深深地与几种数学和动态编程概念交织在一起,这些概念可以填满一本教科书,实际上也确实有几本类似的书。然而,对于我们的目的来说,我们只需要理解关键概念,以便构建我们的 DRL 智能体。因此,我们将选择不去过多纠结于数学细节,但仍有一些关键概念是你需要理解的,才能取得成功。如果你在第一章《深度学习与游戏》中覆盖了数学内容,那么这一部分将会非常轻松。对于那些没有覆盖的朋友,慢慢来,但这个部分你不能错过。

为了理解 Q-Learning 模型,这是 RL 的一种形式,我们需要回到基础知识。在接下来的部分,我们将讨论 马尔可夫决策过程贝尔曼 方程的重要性。

马尔可夫决策过程和贝尔曼方程

在 RL 的核心是 马尔可夫决策过程 (MDP) 。一个 MDP 通常被描述为离散时间随机控制过程。简单来说,这意味着它是一个通过时间步来运作的控制程序,用于确定每个动作的概率,并且每个动作都会导致一个奖励。这个过程已经被广泛用于大多数机器人控制、无人机、网络控制,当然也包括 RL。我们通常通过下面的图示来描述这个过程:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/434ef2cd-a9d0-4c70-9cd8-e0cddd217734.png

马尔可夫决策过程

我们通过一个元组或向量来表示一个 MDP https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/2f668c37-4a3c-4fb3-bea6-89ee1da65240.png,使用以下变量:

该图通过将你自己想象成一个处于某个状态的智能体来工作。然后你根据概率确定动作,总是采取一个随机动作。当你移动到下一个状态时,该动作会给你一个奖励,你根据这个奖励更新概率。同样,David Silver 在他的讲座中非常好地涵盖了这一部分内容。

现在,前面的过程是有效的,但随后出现了另一种变体,通过引入 Bellman 方程 和策略/价值迭代的概念,提供了更好的未来奖励评估。之前我们有一个值,https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/d62b7e72-e9f9-470c-aafa-95bf8254b13f.png,现在我们有一个策略(https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/8aedee8a-b54e-4b76-96ec-ebd39c12acc0.png)来表示一个值(https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/6ae870a0-2615-43ca-a224-1b08d456a58f.png),这为我们提供了一个新的方程,如下所示:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/16cb44c4-34b7-4914-b933-ce07125fabf0.png

我们不会进一步讲解这个方程,只是提醒大家牢记质量迭代的概念。在接下来的部分中,我们将看到如何将这个方程简化为每个动作的质量指标,并将其用于 Q-Learning。

Q-learning

随着质量迭代方法的引入,一个称为 Q-learning质量学习 的有限状态方法被提出。Q 使用质量迭代技术来解决给定的有限状态问题,确定智能体的最佳行动。我们在前面看到的方程现在可以表示为以下形式:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/649eaf34-9453-4328-9183-5f8437af651f.png

考虑以下方程:

Q 值现在在智能体穿越其环境时被迭代更新。没有什么比一个例子更能展示这些概念了。打开 Chapter_5_3.py,并按照以下步骤操作:

  1. 我们从各种导入开始,并按照以下代码设置:
from collections import deque
import numpy as np
import os
clear = lambda: os.system('cls') #linux/mac use 'clear'
import time
import gym
from gym import wrappers, logger
  1. 这些导入仅加载了我们这个示例所需的基本库。记住,运行这个示例前,你需要安装 Gym

  2. 接下来,我们设置一个新环境;在这个例子中,我们使用基础的 FrozenLake-v0 示例,这是一个测试 Q-learning 的完美示例:

environment = 'FrozenLake-v0'
env = gym.make(environment)
  1. 然后我们设置 AI 环境(env)以及其他一些参数:
outdir = os.path.join('monitor','q-learning-{0}'.format(environment))
env = wrappers.Monitor(env, directory=outdir, force=True)
env.seed(0)
env.is_slippery = False
q_table = np.zeros([env.observation_space.n, env.action_space.n])

#parameters
wins = 0
episodes = 40000
delay = 1

epsilon = .8
epsilon_min = .1
epsilon_decay = .001
gamma = .9
learning_rate = .1
  1. 在这部分代码中,我们设置了若干变量,稍后会详细说明。在这个示例中,我们使用了一个包装工具来监控环境,这对于确定潜在的训练问题非常有用。另一个需要注意的是 q_table 数组的设置,这个数组由环境的 observation_space(状态)和 action_space(动作)定义;空间定义的是数组而不仅仅是向量。在这个例子中,action_space 是一个向量,但它也可以是一个多维数组或张量。

  2. 跳过下一个函数部分,直接跳到最后一部分,在那里训练迭代发生并显示在以下代码中:

for episode in range(episodes): 
    state = env.reset()
    done = False
    while not done:
        action = act(env.action_space,state)
        next_state, reward, done, _ = env.step(action)
        clear()
        env.render()
        learn(state, action, reward, next_state)
        if done:
            if reward > 0:
                wins += 1
            time.sleep(3*delay)
        else:
            time.sleep(delay)

print("Goals/Holes: %d/%d" % (wins, episodes - wins))
env.close() 
  1. 前面的代码大部分都比较直接,应该容易理解。看看 env(环境)是如何使用从 act 函数生成的 action;这会用于执行智能体的步骤或动作。step 函数的输出是 next_staterewarddone,我们用这些来通过 learn 函数来确定最佳的 Q 策略。

  2. 在我们深入研究动作和学习函数之前,先运行样本,观察智能体如何进行训练。训练可能需要一些时间,所以可以随时返回书本继续阅读。

以下是 OpenAI Gym FrozenLake 环境运行我们 Q-learning 模型的示例:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/c2cbf3e7-ae6a-4e0c-a5c8-3961aa6119c6.png

FrozenLake Gym 环境

在样本运行时,你将看到一个简单的文本输出,显示环境的状态。S 代表起点,G 代表目标,F 代表冻结区域,H 代表陷阱。智能体的目标是找到一条通过环境的路径,避免掉入陷阱,并到达目标。特别注意智能体是如何移动的,如何在环境中找到自己的路径。在下一节中,我们将深入分析 learnact 函数,并理解探索的重要性。

Q-learning 和探索

我们在使用如 Q-learning 等策略迭代模型时,面临的一个问题是探索与利用的权衡。Q 模型方程假设通过最大化质量来决定一个行动,我们称之为利用(exploiting the model)。这个问题在于,它常常会将智能体局限于只追求最佳短期收益的解法。相反,我们需要允许智能体有一定的灵活性去探索环境并自主学习。我们通过引入一个逐渐消失的探索因子到训练中来实现这一点。让我们通过再次打开 Chapter_5_3.py 示例来看看这一过程是怎样的:

  1. 向下滚动,查看 actis_explore 函数,如下所示:
def is_explore():
    global epsilon, epsilon_decay, epsilon_min
    epsilon = max(epsilon-epsilon_decay,epsilon_min)
    if np.random.rand() < epsilon:
        return True
    else:
        return False

def act(action_space, state):
    # 0 - left, 1 - Down, 2 - Right, 3 - Up
    global q_table
    if is_explore():
        return action_space.sample()
    else:
        return np.argmax(q_table[state])
  1. 请注意,在 act 函数中,首先测试智能体是否希望或需要进行探索,使用的是 is_explore()。在 is_explore 函数中,我们可以看到,全局的 epsilon 值在每次迭代时都会通过 epsilon_decay 逐渐衰减,直到达到全局最小值 epsilon_min。当智能体开始一个回合时,他们的探索 epsilon 值较高,这使得他们更可能进行探索。随着时间的推移,回合的进行,epsilon 值会逐渐降低。我们这样做是基于假设,随着时间的推移,智能体将需要进行越来越少的探索。探索与利用之间的权衡非常重要,特别是在环境状态空间较大时,理解这一点至关重要。本书的后续内容将深入探讨这一权衡。

    请注意,智能体使用探索函数并随机选择一个动作。

  2. 最后,我们来到了 learn 函数。这个函数是用来计算 Q 值的,具体如下:

def learn(state, action, reward, next_state):
    # Q(s, a) += alpha * (reward + gamma * max_a' Q(s', a') - Q(s, a))
    global q_table
    q_value = gamma * np.amax(q_table[next_state])
    q_value += reward
    q_value -= q_table[state, action]
    q_value *= learning_rate
    q_value += q_table[state, action]
    q_table[state, action] = q_value
  1. 在这里,方程被拆解并简化,但这是计算代理在利用时将使用的值的步骤。

保持代理继续运行,直到完成。我们刚刚完成了第一个完整的强化学习问题,尽管这是一个有限状态的问题。在下一节中,我们将大大扩展我们的视野,探索深度学习与强化学习的结合。

第一个深度强化学习与深度 Q-learning

现在我们已经详细了解了强化学习过程,我们可以着手将我们的 Q-learning 模型与深度学习结合。这正如你可能猜到的,是我们努力的巅峰所在,也是强化学习真正威力显现的地方。正如我们在前面的章节中所学,深度学习本质上是一个复杂的方程系统,可以通过非线性函数映射输入,从而生成训练输出。

神经网络只是一种更简单的求解非线性方程的方法。我们稍后会学习如何使用 DNN 来解决其他方程,但现在我们专注于使用它来解决我们在上一节看到的 Q-learning 方程。

我们将使用 OpenAI Gym 工具包中的CartPole训练环境。这个环境几乎是学习深度 Q-learningDQN)的标准。

打开Chapter_5_4.py文件,按照以下步骤查看如何将我们的求解器转换为使用深度学习:

  1. 像往常一样,我们查看导入的模块和一些初始的起始参数,如下所示:
import random
import gym
import numpy as np
from collections import deque
from keras.models import Sequential
from keras.layers import Dense
from keras.optimizers import Adam

EPISODES = 1000
  1. 接下来,我们将创建一个类来包含 DQN 代理的功能。__init__函数如下所示:
class DQNAgent:
    def __init__(self, state_size, action_size):
        self.state_size = state_size
        self.action_size = action_size
        self.memory = deque(maxlen=2000)
        self.gamma = 0.95 # discount rate
        self.epsilon = 1.0 # exploration rate
        self.epsilon_min = 0.01
        self.epsilon_decay = 0.995
        self.learning_rate = 0.001
        self.model = self._build_model()
  1. 大部分参数已经涵盖,但需要注意一个新参数叫做memory,它是一个deque集合,保存了最近的 2,000 步。这使得我们能够在一种回放模式下批量训练神经网络。

  2. 接下来,我们查看神经网络模型是如何通过_build_model函数构建的,如下所示:

def _build_model(self):
    # Neural Net for Deep-Q learning Model
    model = Sequential()
    model.add(Dense(24, input_dim=self.state_size, activation='relu'))
    model.add(Dense(24, activation='relu'))
    model.add(Dense(self.action_size, activation='linear'))
    model.compile(loss='mse',
                      optimizer=Adam(lr=self.learning_rate))
    return model
  1. 这个模型比我们已经看到的其他模型要简单一些,包含三个dense层,为每个动作输出一个值。这个网络的输入是状态。

  2. 跳到文件的底部,查看训练迭代循环,如下所示:

if __name__ == "__main__":
    env = gym.make('CartPole-v1')
    state_size = env.observation_space.shape[0]
    action_size = env.action_space.n
    agent = DQNAgent(state_size, action_size)
    # agent.load("./save/cartpole-dqn.h5")
    done = False
    batch_size = 32

    for e in range(EPISODES):
        state = env.reset()
        state = np.reshape(state, [1, state_size]) 
        for time in range(500):
            # env.render()
            action = agent.act(state)
            env.render()
            next_state, reward, done, _ = env.step(action)
            reward = reward if not done else -10
            next_state = np.reshape(next_state, [1, state_size])
 agent.remember(state, action, reward, next_state, done)
            state = next_state
            if done:
                print("episode: {}/{}, score: {}, e: {:.2}"
                      .format(e, EPISODES, time, agent.epsilon))
                break
            if len(agent.memory) > batch_size:
                agent.replay(batch_size)
  1. 在这个示例中,我们的训练发生在一个实时render循环中。代码中的重要部分被高亮显示,展示了状态的重塑和调用agent.remember函数。最后的agent.replay函数是网络进行训练的地方。remember函数如下所示:
def remember(self, state, action, reward, next_state, done):
    self.memory.append((state, action, reward, next_state, done))
  1. 这个函数只是存储stateactionrewardnext_statedone参数,以供回放训练。向下滚动查看replay函数,具体如下:
def replay(self, batch_size):
    minibatch = random.sample(self.memory, batch_size)
    for state, action, reward, next_state, done in minibatch:
        target = reward
        if not done:
            target = (reward+self.gamma*
                      np.amax(self.model.predict(next_state)[0]))
            target_f = self.model.predict(state)
            target_f[0][action] = target
            self.model.fit(state, target_f, epochs=1, verbose=0)
        if self.epsilon > self.epsilon_min:
            self.epsilon *= self.epsilon_decay
  1. replay函数是网络训练发生的地方。我们首先定义一个minibatch,它是通过从先前的经验中进行随机抽样并按batch_size分组定义的。然后,我们循环遍历批次,将reward设置为target,如果没有done,则根据模型对next_state的预测计算一个新的目标。之后,我们在state上使用model.predict函数来确定最终的目标。最后,我们使用model.fit函数将训练后的目标反向传播回网络。

    由于这一部分很重要,我们再强调一下。注意target变量计算并设置的那一行。这些代码行可能看起来很熟悉,因为它们与我们之前看到的 Q 值方程一致。这个target值是当前动作应该预测的值。这就是当前动作的回报值,并由返回的reward设置。

  2. 运行示例并观察智能体训练如何使杆子在小车上保持平衡。以下是训练过程中环境的显示:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/48db2129-1bae-40e8-8dd2-1c61deb1dbc1.png

CartPole OpenAI Gym 环境

示例环境使用的是我们通常用于学习构建第一个 DRL 模型的典型第一个环境——CartPole。下一节中,我们将查看如何在其他场景和通过 Keras-RL API 提供的其他模型中使用 DQNAgent。

强化学习实验

强化学习正在迅速发展,我们刚刚看到的 DQN 模型很快就被更先进的算法所取代。RL 算法有许多变种和进展,足以填满几章内容,但大多数材料会被认为是学术性的。因此,我们将重点介绍 Keras RL API 提供的各种 RL 模型的更实际示例。

我们可以使用的第一个简单示例是将之前的示例修改为适应新的gym环境。打开Chapter_5_5.py并按照以下练习进行操作:

  1. 在下面的代码中更改环境名称:
if __name__ == "__main__":
    env = gym.make('MountainCar-v0')
  1. 在本例中,我们将使用MountainCar环境,如下所示:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/e4ed1377-b31b-4cc7-adf3-e86c57db2247.png

MountainCar 环境示例

  1. 像平常一样运行代码,看看 DQNAgent 是如何解决爬山问题的。

你可以看到我们是如何快速切换环境并在另一个环境中测试 DQNAgent 的。在下一节中,我们将讨论如何使用 Keras-RL API 提供的不同 RL 算法来训练 Atari 游戏。

Keras RL

Keras 提供了一个非常有用的 RL API,它封装了几种变体,如 DQN、DDQN、SARSA 等。我们现在不会详细讨论这些不同的 RL 变体,但随着我们进入更复杂的模型,稍后会介绍重要的部分。不过,目前,我们将看看如何快速构建一个 DRL 模型来玩 Atari 游戏。打开Chapter_5_6.py并按照这些步骤操作:

  1. 我们首先需要通过 pip 安装几个依赖项;打开命令行或 Anaconda 窗口,并输入以下命令:
pip install Pillow
pip install keras-rl

pip install gym[atari] # on Linux or Mac
pip install --no-index -f https://github.com/Kojoley/atari-py/releases atari_py  # on Windows thanks to Nikita Kniazev
  1. 这将安装 Keras RL API、Pillow(一个图像框架)以及 gym 的 Atari 环境。

  2. 按照平常的方式运行示例代码。这个示例确实需要脚本参数,但在这里我们不需要使用它们。以下是渲染的 Atari Breakout 环境示例:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/3f3e5a5c-4ad3-419f-8f5b-4a57934c63c3.png

Atari Breakout 环境

不幸的是,你无法看到智能体玩游戏的过程,因为所有的动作都在后台进行,但可以让智能体运行直到它完成并保存模型。以下是我们运行示例的方法:

  1. 你可以使用 --mode test 作为参数重新运行示例,让智能体在 10 回合中运行并查看结果。

  2. 在示例运行时,浏览代码并特别注意模型,如下所示:

model = Sequential()
if K.image_dim_ordering() == 'tf':
    # (width, height, channels)
    model.add(Permute((2, 3, 1), input_shape=input_shape))
elif K.image_dim_ordering() == 'th':
    # (channels, width, height)
    model.add(Permute((1, 2, 3), input_shape=input_shape))
else:
    raise RuntimeError('Unknown image_dim_ordering.')
model.add(Convolution2D(32, (8, 8), strides=(4, 4)))
model.add(Activation('relu'))
model.add(Convolution2D(64, (4, 4), strides=(2, 2)))
model.add(Activation('relu'))
model.add(Convolution2D(64, (3, 3), strides=(1, 1)))
model.add(Activation('relu'))
model.add(Flatten())
model.add(Dense(512))
model.add(Activation('relu'))
model.add(Dense(nb_actions))
model.add(Activation('linear'))
print(model.summary())
  1. 注意我们的模型如何使用 Convolution(卷积),并且带有池化。这是因为这个示例将每一帧游戏画面作为输入(状态)并做出响应。在这种情况下,模型的状态非常庞大,这展示了深度强化学习(DRL)的真正威力。在本例中,我们仍在训练一个状态模型,但在未来的章节中,我们将研究如何训练一个策略,而不是模型。

这只是强化学习(RL)的简单介绍,我们省略了一些可能让新手困惑的细节。由于我们计划在接下来的章节中更详细地讨论 RL,特别是在第八章中深入讲解 近端策略优化PPO)和 理解 PPO,因此不必过于担心策略和基于模型的 RL 等差异。

这里有一个很好的 TensorFlow DQN 示例,位于 GitHub 链接:github.com/floodsung/DQN-Atari-Tensorflow。代码可能有点过时,但它是一个简单且优秀的示例,值得一看。

我们不会进一步查看代码,但读者可以自由地深入研究。现在让我们尝试一些练习。

练习

一如既往,使用本节中的练习来更好地理解你学到的内容。尽量做至少两到三个本节中的练习:

  1. 返回 Chapter_5_1.py 示例,并更改 alphalearning_rate)变量,看看这对计算结果的影响。

  2. 返回 Chapter_5_2.py 示例并更改各个老虎机的位置。

  3. 在示例 Chapter_5_2.py 中更改学习率,并查看这对 Q 值结果输出的影响。

  4. 改变 Chapter_5_3.py 示例中的 gamma 奖励折扣因子,并查看这对智能体训练的影响。

  5. Chapter_5_3.py 中改变探索的 epsilon 值并重新运行示例,看看更改各种探索参数对训练智能体的影响。

  6. 修改Chapter_5_4.py示例中的各种参数(explorationalpha,和gamma),观察这些参数对训练的影响。

  7. 修改Chapter_5_4.py示例中内存的大小,增加或减少,观察这对训练的影响。

  8. 尝试在Chapter_5_5.py中的 DQNAgent 示例中使用不同的 Gym 环境。你可以通过快速 Google 搜索查看其他可选的环境。

  9. Chapter_5_6.py示例当前使用了一种叫做LinearAnnealedPolicy的形式探索策略;将策略更改为使用代码注释中提到的BoltzmannQPolicy策略。

  10. 一定要从github.com/keras-rl/keras-rl下载并运行其他 Keras-RL 示例。同样,你可能需要安装其他 Gym 环境才能使它们正常工作。

还有大量关于 RL 的其他示例、视频和学习资料可供研究。尽可能多地学习,因为这些材料广泛且复杂,不是短时间内能够掌握的。

总结

RL(强化学习)是目前主导许多研究者兴趣的机器学习技术。它之所以吸引我们,通常是因为它非常适合游戏和仿真。在本章中,我们通过从多臂老虎机和上下文赌博机的基本入门问题开始,介绍了 RL 的一些基础知识。然后,我们快速了解了如何安装 OpenAI Gym RL 工具包。接着,我们研究了 Q 学习以及如何在代码中实现它并在 OpenAI Gym 环境中训练。最后,我们看到了如何通过加载其他环境(包括 Atari 游戏模拟器)来进行各种其他实验。

在下一章中,我们将探讨 Unity 目前正在开发的一个快速发展的前沿 RL 平台。

第六章:Unity ML-Agents

Unity 已经坚定并充满活力地拥抱了机器学习,尤其是深度强化学习(DRL),目标是为游戏和仿真开发者提供一个有效的深度强化学习DRL)SDK。幸运的是,Unity 团队在 Danny Lange 的带领下,成功开发了一个强大的前沿 DRL 引擎,能够实现令人印象深刻的效果。这个引擎在多个方面超越了我们之前介绍的 DQN 模型,是顶级的。Unity 使用近端策略优化PPO)模型作为其 DRL 引擎的基础。这个模型显著更为复杂,且可能在某些方面有所不同,但幸运的是,这只是许多章节的开始,我们会有足够的时间介绍这些概念——毕竟这是一本实践性强的书籍。

在本章中,我们介绍了Unity ML-Agents工具和 SDK,用于构建 DRL 代理来玩游戏和仿真。虽然这个工具既强大又前沿,但它也很容易使用,并且提供了一些帮助我们边学边用的工具。在本章中,我们将涵盖以下主题:

  • 安装 ML-Agents

  • 训练一个代理

  • 大脑里有什么?

  • 使用 TensorBoard 监控训练过程

  • 运行一个代理

我们想感谢 Unity 团队成员们在 ML-Agents 方面的卓越工作;以下是写作时的团队成员名单:

在继续本章之前,请确保已按照第四章中的说明安装 Unity,构建深度学习游戏聊天机器人

安装 ML-Agents

本节中,我们将概述成功安装 ML-Agents SDK 所需的高层步骤。这些材料仍然处于 beta 版本,并且在各版本之间已经发生了显著变化。因此,如果在执行这些高层步骤时遇到困难,只需返回到最新的 Unity 文档;它们写得非常清晰。

到你的计算机上操作并跟随这些步骤;可能会有很多子步骤,所以预计这会花费一些时间:

  1. 确保你的计算机已安装 Git;它可以通过命令行使用。Git 是一个非常流行的源代码管理系统,关于如何安装和使用 Git 的资源非常丰富,适用于你的平台。安装 Git 后,确保它正常工作,可以通过克隆一个仓库来测试,任意一个仓库都可以。

  2. 打开命令窗口或常规 shell。Windows 用户可以打开 Anaconda 窗口。

  3. 切换到一个工作文件夹,放置新代码的文件夹,并输入以下命令(Windows 用户可以选择使用 C:\ML-Agents):

git clone https://github.com/Unity-Technologies/ml-agents
  1. 这将把 ml-agents 仓库克隆到你的计算机上,并创建一个同名的文件夹。你可能还想额外为文件夹名称添加版本号。Unity,以及整个 AI 领域,至少在当前阶段,是在不断变化的。这意味着新的变化和持续的更新始终在发生。写作时,我们将仓库克隆到名为 ml-agents.6 的文件夹中,如下所示:
git clone https://github.com/Unity-Technologies/ml-agents ml-agents.6

本书的作者曾经写过一本关于 ML-Agents 的书,并且在短时间内重新编写了几章,以适应重要的变更。实际上,本章也经历了几次重写,以应对更多的重大变化。

  1. ml-agents 创建一个新的虚拟环境并将其设置为 3.6,像这样:
#Windows 
conda create -n ml-agents python=3.6

#Mac
Use the documentation for your preferred environment
  1. 激活该环境,同样通过 Anaconda:
activate ml-agents
  1. 安装 TensorFlow。使用 Anaconda,我们可以通过以下命令进行安装:
pip install tensorflow==1.7.1
  1. 安装 Python 包。在 Anaconda 中,输入以下命令:
cd ML-Agents #from root folder
cd ml-agents or cd ml-agents.6 #for example
cd ml-agents
pip install -e . or pip3 install -e .
  1. 这将安装 Agents SDK 所需的所有软件包,可能需要几分钟时间。请确保保持此窗口打开,因为我们稍后会使用它。

这是 TensorFlow 的基本安装,不使用 GPU。请查阅 Unity 文档,了解如何安装 GPU 版本。这可能会对你的训练性能产生显著影响,具体取决于你的 GPU 性能。

这应该完成 Unity Python SDK for ML-Agents 的设置。在下一部分中,我们将学习如何设置并训练 Unity 提供的多个示例环境之一。

训练一个智能体

本书的大部分时间,我们都在研究代码以及 深度学习 (DL) 和 强化学习 (RL) 的内部知识。基于这些知识,我们现在可以进入并查看 深度强化学习 (DRL) 的实际应用。幸运的是,新的代理工具包提供了多个示例,展示了引擎的强大功能。打开 Unity 或 Unity Hub 并按照以下步骤操作:

  1. 点击项目对话框顶部的“打开项目”按钮。

  2. 定位并打开UnitySDK项目文件夹,如下图所示:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/ecbdfb91-2cfb-47ef-b264-b8cfe8e45d3b.png

打开 UnitySDK 项目

  1. 等待项目加载完成后,打开编辑器底部的项目窗口。如果系统提示更新项目,请确保选择“是”或“继续”。到目前为止,所有代理代码都已设计为向后兼容。

  2. 定位并打开如截图所示的 GridWorld 场景:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/3456149c-1ca2-4371-b20e-bbba7590e13f.png

打开 GridWorld 示例场景

  1. 在层级窗口中选择 GridAcademy 对象。

  2. 然后将注意力集中在 Inspector 窗口,点击 Brains 旁边的目标图标以打开大脑选择对话框:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/3dfa4068-3490-4a17-b3fb-8f968b14433f.png

检查 GridWorld 示例环境

  1. 选择 GridWorldPlayer 大脑。这个大脑是 玩家 大脑,意味着玩家(即你)可以控制游戏。我们将在下一部分详细探讨这个大脑概念。

  2. 按下编辑器顶部的播放按钮,观察网格环境的形成。由于游戏当前设置为玩家控制,你可以使用 WASD 控制键来移动方块。目标与我们之前为 FrozenPond 环境构建的 DQN 类似。也就是说,你需要将蓝色方块移动到绿色的 + 符号处,并避免碰到红色的 X。

随时可以玩游戏,注意游戏运行的时间是有限的,并且不是回合制的。在下一部分中,我们将学习如何使用 DRL 代理运行这个示例。

大脑中有什么?

ML-Agents 平台的一个亮点是能够非常快速且无缝地从玩家控制切换到 AI/代理控制。为了实现这一点,Unity 引入了 大脑 的概念。大脑可以是玩家控制的,也可以是代理控制的,后者被称为学习大脑。亮点在于,你可以构建一个游戏并进行测试,之后玩家可以将游戏交给 RL 代理来控制。这一流程的额外好处是,让任何用 Unity 编写的游戏都能通过极少的努力由 AI 控制。事实上,这种强大的工作流让我们决定专门花一整章时间来讨论,第十二章,使用 DRL 调试/测试游戏,来学习如何使用 RL 测试和调试你的游戏。

使用 Unity 训练 RL 代理非常简单,设置和运行都很直接。Unity 在外部使用 Python 来构建学习脑模型。使用 Python 是更有意义的,因为正如我们之前所见,多个深度学习库都是基于 Python 构建的。按照以下步骤训练 GridWorld 环境中的代理:

  1. 重新选择 GridAcademy,并将 Brains 从 GridWorldPlayer 切换为 GridWorldLearning,如下所示:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/574c1a5c-98dd-4d89-8d2a-5eb9a81d1eb4.png

切换脑部使用 GridWorldLearning

  1. 确保点击最后的 Control 选项。这个简单的设置告诉脑部它可以被外部控制。一定要再次确认该选项已启用。

  2. 在层级窗口中选择trueAgent对象,然后在检查器窗口中,将 Grid Agent 组件下的 Brain 属性更改为 GridWorldLearning 脑:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/438357ea-b6c5-49c3-a2a2-9e6d44c7f0ee.png

将代理的脑设置为 GridWorldLearning

  1. 对于此示例,我们希望将 Academy 和 Agent 切换为使用相同的 brain,即 GridWorldLearning。在我们稍后探索的更高级用法中,情况不总是如此。当然,你也可以让一个玩家和一个代理脑同时运行,或者其他多种配置。

  2. 确保你已打开 Anaconda 或 Python 窗口,并设置为ML-Agents/ml-agents文件夹或你的版本化ml-agents文件夹。

  3. 在 Anaconda 或 Python 窗口中,使用ml-agents虚拟环境运行以下命令:

mlagents-learn config/trainer_config.yaml --run-id=firstRun --train
  1. 这将启动 Unity PPO 训练器,并根据配置运行代理示例。在某些时候,命令窗口将提示你运行已加载环境的 Unity 编辑器。

  2. 在 Unity 编辑器中按下播放按钮,运行 GridWorld 环境。不久之后,你应该能看到代理正在训练,且结果会输出到 Python 脚本窗口中:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/28c402ce-1770-4986-ad61-3250d518b949.png

在训练模式下运行 GridWorld 环境

  1. 注意mlagents-learn脚本是构建 RL 模型以运行代理的 Python 代码。从脚本的输出可以看出,有多个参数,或者我们称之为超参数,需要进行配置。部分参数可能会让你感到熟悉,没错,但也有一些可能不太明了。幸运的是,在本章和本书的后续内容中,我们将详细探讨如何调整这些参数。

  2. 让代理训练几千次,并注意它学习的速度。这里的内部模型,称为PPO,已被证明在多种任务中非常有效,且非常适合游戏开发。根据你的硬件,代理可能在不到一小时的时间内就能完美完成此任务。

继续让代理训练,我们将在下一节中探讨更多检查代理训练进度的方法。

使用 TensorBoard 监控训练过程

使用强化学习(RL)或任何深度学习(DL)模型来训练智能体,虽然很有趣,但通常不是一件简单的任务,需要注意细节。幸运的是,TensorFlow 自带了一套名为TensorBoard的图形工具,我们可以用它来监控训练进度。按照以下步骤运行 TensorBoard:

  1. 打开一个 Anaconda 或 Python 窗口。激活ml-agents虚拟环境。不要关闭运行训练器的窗口;我们需要保持它运行。

  2. 导航到ML-Agents/ml-agents文件夹,并运行以下命令:

tensorboard --logdir=summaries
  1. 这将启动 TensorBoard 并运行它内置的 Web 服务器。你可以通过运行前述命令后显示的 URL 加载页面。

  2. 输入窗口中显示的 TensorBoard URL,或者在浏览器中使用localhost:6006machinename:6006。大约一个小时后,你应该会看到类似以下的内容:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/40e5492f-96ed-4f05-b12a-b74a9ddcd12c.png

TensorBoard 图形窗口

  1. 在上面的截图中,你可以看到每个不同的图表表示了训练的一个方面。理解这些图表对于了解智能体的训练过程非常重要,因此我们将逐个分析每个部分的输出:

    • 环境:这一部分展示了智能体在环境中的整体表现。接下来的截图展示了每个图表的更详细查看及其优选趋势:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/f404a429-9388-4c55-8bef-433bd24e2523.png

进一步查看环境部分的图表

  • 累积奖励:这是智能体正在最大化的总奖励。通常你希望看到它上升,但也有可能出现下降的情况。最好将奖励最大化在 1 到-1 的范围内。如果你在图表中看到超出这个范围的奖励,也需要进行修正。

  • 训练回合长度:如果这个值减小,通常是个好兆头。毕竟,较短的回合意味着更多的训练。然而,请记住,回合长度可能因需要而增加,因此这个值可能会有所波动。

  • 课程:这表示智能体当前所在的课程,适用于课程学习。我们将在第九章中学习更多关于课程学习的内容,奖励和强化学习

  • 损失:这一部分显示了表示策略和价值计算损失或成本的图表。当然,我们并没有花太多时间解释 PPO 及其如何使用策略,所以在此时,只需理解训练时的优选方向。接下来是该部分的截图,箭头显示了最佳的偏好方向:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/357e6132-9381-4c6b-bde8-3b66bd378e4f.png

损失和优选训练方向

  • 策略损失:这个值决定了策略随时间的变化程度。策略是决定行为的部分,通常这个图表应该显示出下降趋势,表明策略在做决策时越来越好。

  • 值损失:这是函数的平均损失。它基本上表示代理对下一个状态的价值预测得有多准确。最初,这个值应增加,奖励稳定后它应减少。

  • 策略:PPO 使用策略的概念,而不是模型来确定行动的质量。同样,我们将在第八章《理解 PPO》中花更多时间讨论这一点,并揭示 PPO 的更多细节。下一张截图展示了策略图及其优先趋势:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/cc002fa5-7562-415a-93ea-f4f5ab381170.png

策略图和优先趋势

  • 熵:表示代理探索的程度。随着代理对环境了解得更多,它需要探索的程度减少,因此该值应减少。

  • 学习率:当前该值设置为随时间线性下降。

  • 值估计:这是所有代理状态访问的平均值或均值。此值应增加,以代表代理知识的增长,然后稳定下来。

这些图表都旨在与 Unity 基于 PPO 方法的实现一起使用。暂时不用太担心理解这些新术语,我们将在第七章《代理与环境》中深入探讨 PPO 的基础知识。

  1. 让代理运行到完成,并保持 TensorBoard 运行。

  2. 返回到正在训练大脑的 Anaconda/Python 窗口,并运行以下命令:

mlagents-learn config/trainer_config.yaml --run-id=secondRun --train
  1. 你将再次被提示在编辑器中按下播放按钮;请确保这样做。让代理开始训练并运行几轮。在此过程中,监控 TensorBoard 窗口,并注意图表上如何显示 secondRun。你也可以让这个代理运行到完成,但如果你愿意,现在可以停止它。

在 ML-Agents 的早期版本中,你需要先构建一个 Unity 可执行文件作为游戏训练环境并运行它。外部 Python 大脑依旧按照相同的方式运行。这种方法使得调试代码问题或游戏中的问题变得非常困难。所有这些问题已经通过当前的方法得到解决;然而,对于某些自定义训练,我们可能需要以后再使用旧的可执行文件方法。

现在我们已经看到设置和训练代理是多么简单,接下来我们将通过下一部分来了解如何在没有外部 Python 大脑的情况下直接在 Unity 中运行该代理。

运行代理

使用 Python 进行训练效果很好,但它不是实际游戏中会使用的方式。理想情况下,我们希望能够构建一个 TensorFlow 图并在 Unity 中使用它。幸运的是,构建了一个名为 TensorFlowSharp 的库,它允许 .NET 使用 TensorFlow 图。这使我们能够构建离线的 TF 模型,并在之后将其注入到我们的游戏中。不幸的是,我们只能使用已训练的模型,暂时不能以这种方式进行训练。

让我们通过使用刚刚为 GridWorld 环境训练的图表并将其作为 Unity 中的内部大脑来看看它是如何工作的。请按下一个小节中的练习步骤来设置并使用内部大脑:

  1. 从此链接下载 TFSharp 插件: s3.amazonaws.com/unity-ml-agents/0.5/TFSharpPlugin.unitypackage

如果此链接无法使用,请查阅 Unity 文档或 Asset Store 获取新链接。当前版本被描述为实验性,并可能发生变化。

  1. 从编辑器菜单中选择 Assets | Import Package | Custom Package…

  2. 找到刚刚下载的资产包,并使用导入对话框将插件加载到项目中。如果您需要有关这些基础 Unity 任务的帮助,网上有大量的资料可以进一步指导您。

  3. 从菜单中选择 Edit | Project Settings。这将打开设置窗口(2018.3 新增功能)

  4. 在 Player 选项下找到 Scripting Define Symbols,并将文本设置为 ENABLE_TENSORFLOW,并启用 Allow Unsafe Code,如下图所示:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/ab714c99-1b2d-4e27-89e4-eff8395c7f43.png

设置 ENABLE_TENSORFLOW 标志

  1. 在 Hierarchy 窗口中找到 GridWorldAcademy 对象,并确保它使用的是 Brains | GridWorldLearning。然后关闭 Grid Academy 脚本中 Brains 部分下的 Control 选项。

  2. Assets/Examples/GridWorld/Brains 文件夹中找到 GridWorldLearning 大脑,并确保在 Inspector 窗口中设置了 Model 参数,如下图所示:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/321101c9-21cb-4a31-b298-dc4c3a51cc32.png

为大脑设置使用的模型

  1. Model 应该已经设置为 GridWorldLearning 模型。在这个例子中,我们使用的是与 GridWorld 示例一起提供的 TFModel。您也可以通过将我们在前一个示例中训练的模型导入到项目中并将其设置为模型来轻松使用它。

  2. 按下 Play 运行编辑器并观察代理控制立方体。

目前,我们正在使用预训练的 Unity 大脑运行环境。在下一个小节中,我们将查看如何使用我们在前一节中训练的大脑。

加载已训练的大脑

所有 Unity 示例都带有预训练的大脑,您可以使用它们来探索示例。当然,我们希望能够将我们自己的 TF 图加载到 Unity 中并运行它们。请按照以下步骤加载已训练的图表:

  1. 找到 ML-Agents/ml-agents/models/firstRun-0 文件夹。在此文件夹中,您应看到一个名为 GridWorldLearning.bytes 的文件。将该文件拖入 Unity 编辑器中的 Project/Assets/ML-Agents/Examples/GridWorld/TFModels 文件夹,如下所示:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/a41646b7-b3df-4fdf-a9bc-2ca3f9370961.png

将字节图表拖入 Unity

  1. 这将把图形导入 Unity 项目作为资源,并将其重命名为GridWorldLearning 1。这样做是因为默认模型已经有了相同的名称。

  2. brains文件夹中找到GridWorldLearning,在 Inspector 窗口中选择它,并将新的GridWorldLearning 1模型拖动到大脑参数下的模型插槽中:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/21bef292-7d4d-4f6f-a20c-9243c3c4bb8e.png

在大脑中加载图形模型插槽。

  1. 在这一阶段,我们不需要更改其他参数,但请特别注意大脑的配置。默认设置目前是可行的。

  2. 在 Unity 编辑器中按下播放按钮,观察智能体成功地完成游戏。

  3. 训练智能体的时间将真正决定它在游戏中的表现。如果让它完成训练,智能体的表现应当与已经训练好的 Unity 智能体相当。

现在有很多 Unity 示例,您可以自行运行和探索。可以随意训练多个示例,或者按照下一节中的练习进行训练。

练习

使用本节中的练习来增强和巩固您的学习。至少尝试其中一些练习,记住这些练习对您有益:

  1. 设置并运行 3DBall 示例环境以训练一个有效的智能体。这个环境使用多个游戏/智能体来进行训练。

  2. 设置 3DBall 示例,让一半的游戏使用已经训练好的大脑,另一半使用训练或外部学习。

  3. 使用外部学习训练 PushBlock 环境中的智能体。

  4. 训练 VisualPushBlock 环境。注意这个示例如何使用视觉摄像头捕捉环境状态。

  5. 作为玩家运行 Hallway 场景,然后使用外部学习大脑训练该场景。

  6. 作为玩家运行 VisualHallway 场景,然后使用外部学习大脑训练该场景。

  7. 运行 WallJump 场景,然后在训练条件下运行它。这个示例使用了课程训练,稍后我们将在第九章中深入探讨,奖励与强化学习

  8. 运行金字塔场景,然后为训练进行设置。

  9. 运行 VisualPyramids 场景并为训练进行设置。

  10. 运行 Bouncer 场景并为训练进行设置。

虽然您不必运行所有这些练习/示例,但熟悉它们是很有帮助的。它们往往可以作为创建新环境的基础,正如我们在下一章中所看到的那样。

总结

如你所学,Unity 中训练 RL 和 DRL 代理的工作流比在 OpenAI Gym 中更加集成和无缝。我们不需要写一行代码就能在网格世界环境中训练代理,而且视觉效果要好得多。对于本章,我们从安装 ML-Agents 工具包开始。然后我们加载了一个 GridWorld 环境,并设置它与 RL 代理进行训练。从那时起,我们查看了 TensorBoard 来监控代理的训练和进展。在训练完成后,我们首先加载了一个 Unity 预训练的大脑,并在 GridWorld 环境中运行它。接着,我们使用了一个刚刚训练好的大脑,并将其作为资产导入到 Unity 中,作为 GridWorldLearning 大脑的模型。

在下一章中,我们将探讨如何构建一个新的 RL 环境或游戏,我们可以使用代理来学习和玩耍。这将使我们进一步了解本章中略过的各种细节。

第七章:智能体与环境

玩转和探索实验性的强化学习环境是很有趣的,但最终,大多数游戏开发者希望开发自己的学习环境。为了做到这一点,我们需要更深入地了解训练深度强化学习环境,特别是一个智能体如何接收和处理输入。因此,在本章中,我们将仔细研究如何在 Unity 中训练一个更为复杂的示例环境。这将帮助我们理解输入和状态对训练智能体的重要性,以及 Unity ML-Agents 工具包中许多使我们能够探索多种选项的特性。本章对于任何希望在自己的游戏中构建环境并使用 ML-Agents 的人来说都至关重要。所以,如果你需要反复阅读本章以理解细节,请务必这样做。

在本章中,我们将涵盖许多与智能体如何处理输入/状态相关的细节,以及你如何调整这些内容以适应你的智能体训练。以下是本章内容的总结:

  • 探索训练环境

  • 理解状态

  • 理解视觉状态

  • 卷积与视觉状态

  • 循环神经网络

确保你已经阅读、理解并运行了上一章的部分示例练习,第六章,Unity ML-Agents。在继续之前,确保你已经正确配置并运行了 Unity 和 ML-Agents 工具包。

探索训练环境

经常推动我们成功或推动我们学习的因素之一就是失败。作为人类,当我们失败时,通常会发生两件事:我们要么更加努力,要么选择放弃。有趣的是,这与强化学习中的负奖励很相似。在 RL 中,智能体如果获得负奖励,可能会因为看不到未来的价值或预测无法带来足够的好处而放弃探索某条路径。然而,如果智能体认为需要更多的探索,或者它没有完全探索完这条路径,它就会继续前进,并且通常这会引导它走上正确的道路。同样,这与我们人类的情况非常相似。因此,在本节中,我们将训练一个较为复杂的示例智能体,促使我们学习如何面对失败并修正训练中的问题。

Unity 目前正在构建一个多级基准塔环境,具有多个难度等级。这将允许深度强化学习爱好者、从业者和研究人员在基准环境上测试他们的技能和模型。作者获得的相对可靠的消息是,这个环境应该会在 2019 年年初或年中完成。

我们最终需要使用 Unity ML-Agents 工具包的许多高级功能来使这个示例正常工作。这要求你对本书的前五章有良好的理解。如果你跳过了这些章节来到这里,请根据需要回去复习。在本章的许多地方,我们提供了指向之前相关章节的有用链接。

我们将关注的训练示例环境是 VisualHallway,不要与标准的 Hallway 示例混淆。VisualHallway 的不同之处在于它使用摄像头作为模型的完整输入状态,而我们之前看到的其他 Unity 示例则使用某种形式的多传感器输入,通常允许代理在任何时候看到 90 度到 360 度的视角,并提供其他有用的信息。这对大多数游戏来说是可以接受的,事实上,许多游戏仍然允许这样的“作弊”或直觉作为 NPC 或计算机对手 AI 的一部分。将这些“作弊”加入游戏 AI 一直是一个被接受的做法,但也许这很快就会发生改变。

毕竟,好游戏是有趣的,并且对玩家来说是合乎逻辑的。过去不久的游戏可能能让 AI 作弊,但现在,玩家对 AI 的期望更高了,他们希望 AI 和他们遵循相同的规则。之前认为计算机 AI 受到技术限制的观念已经消失,现在游戏 AI 必须遵循与玩家相同的规则,这使得我们专注于使 VisualHallway 示例正常工作和训练变得更加有意义。

当然,教 AI 像玩家一样玩/学习还有另一个额外的好处,那就是能够将这种能力转移到其他环境中,这个概念叫做迁移学习。我们将在第十章《模仿与迁移学习》中探索迁移学习,学习如何调整预训练模型/参数,并将其应用于其他环境。

VisualHallway/Hallway 示例首先会将代理随机放入一个长房间或走廊。在这个空间的中央是一个彩色方块,每个走廊的两端角落都有一个覆盖地板的彩色方形区域。这个方块的颜色要么是红色,要么是金色(橙色/黄色),用来告诉代理目标方块的颜色与之相同。目标是让代理移动到正确的彩色方块。在标准的 Hallway 示例中,代理拥有 360 度的传感器感知。而在 VisualHallway 示例中,代理只能看到房间的摄像机视图,就像玩家在游戏中看到的一样。这使得我们的代理与玩家站在了同一起跑线。

在开始训练之前,让我们像玩家一样打开示例并玩一下,看看我们能做得怎么样。按照这个练习打开 VisualHallway 示例:

  1. 在继续之前,确保你已经正确安装了 ML-Agents,并且能够在 Python 中外部训练大脑。如果需要帮助,请参考上一章。

  2. 从项目窗口的 Assets | ML-Agents | Examples | Hallway | Scenes 文件夹中打开 VisualHallway 场景。

  3. 确保 Agent | Hallway Agent | Brain 设置为 VisualHallwayPlayer,如下图所示:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/259082a8-06ae-4583-9189-fa851ca24130.png

Hallway Agent | Brain 设置为 player

  1. 在编辑器中按下播放按钮运行场景,并使用 WASD 键来控制代理。记住,目标是移动到与中心方块颜色相同的方块。

  2. 玩游戏并移动到两个颜色方块,观察在进入奖励方块时,正向或负向奖励给予时会发生什么。游戏画面在进入奖励方块时会闪烁绿色或红色。

这个游戏环境典型地模拟了第一人称射击游戏,非常适合训练代理以第一人称视角进行游戏。训练代理以类似人类的方式玩游戏是许多 AI 从业者的目标,虽然这可能是你是否会在游戏中实现的功能。正如我们所见,根据你游戏的复杂性,这种学习/训练可能甚至不是一个可行的选项。此时,我们应该了解如何设置并通过视觉训练代理。

直观地训练代理

幸运的是,设置代理进行视觉训练相当简单,特别是如果你已经完成了上一章的练习。打开 Unity 编辑器并加载 VisualHallway 场景,准备好 Python 命令行或 Anaconda 窗口,我们就可以开始了:

  1. 在 Unity 中,将 Agent | Hallway Agent | Brain 更改为 VisualHallwayLearning,如下图所示:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/3f960c28-6e36-45f1-b6c7-aeff1738ac2a.png

将大脑更改为学习模式

  1. 点击 VisualHallwayLearning 大脑,在项目窗口中定位它。

  2. 点击 VisualHallwayLearning 大脑,在检查器窗口中查看其属性,如下图所示:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/c8bbaee9-105f-4b1f-8982-64535af4acf8.png

确认学习大脑的属性设置正确

  1. 确保大脑参数设置为接受分辨率为 84 x 84 像素的单一视觉观察,并且不使用灰度。灰度仅是去除颜色通道,使输入变为一个通道,而非三个通道。回顾我们在第二章中讨论的卷积神经网络(CNN)层,卷积和递归网络。同时,确保 Vector Observation | Space Size 设置为 0,如前图所示。

  2. 在菜单中选择文件 | 保存和文件 | 保存项目,以保存所有更改。

  3. 切换到你的 Python 窗口或 Anaconda 提示符,确保你在 ML-Agents/ml-agents 目录下,并运行以下命令:

mlagents-learn config/trainer_config.yaml --run-id=visualhallway --train
  1. 在命令执行后,等待提示以启动编辑器。然后,在提示时运行编辑器,并让示例运行完成,或者运行到你有耐心的时长为止。

  2. 在示例运行完成后,你应该会看到如下所示的内容:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/7856918a-91b1-4722-9212-9e1b1f86e07e.png

完整的训练运行直到完成

假设你训练智能体直到运行结束,即训练了 500K 次迭代,那么你可以确认智能体确实什么也没学到。那么,为什么 Unity 会在他们的示例中加入这样的示例呢?嗯,你可以认为这是一个故意设计的挑战,或者只是他们的一次疏忽。无论如何,我们将其视为一个挑战,借此更好地理解强化学习。

在我们应对这个挑战之前,先回过头来重新确认我们对这个环境的理解,通过查看下一个部分中更易于训练的 Hallway 示例。

回归基础

当你在问题上卡住时,回到最基础的地方确认一切是否按预期工作是很有帮助的。公平地说,我们还没有深入探索 ML-Agents 的内部机制,也没有真正理解深度强化学习(DRL),因此我们实际上并没有从一开始就出发。但为了本示例的目的,我们将回过头来,详细查看 Hallway 示例。返回编辑器并执行以下操作:

  1. 在编辑器中打开 Hallway 示例场景。记住,场景位于 Assets | ML-Agents | Examples | Hallway | Scenes 文件夹中。

  2. 这个示例配置为使用多个并发训练环境。我们能够使用相同的大脑训练多个并发训练环境,因为 近端策略优化PPO),支持这个智能体的强化学习算法,是基于策略进行训练,而不是基于模型。我们将在 第八章《理解 PPO》中详细讲解基于策略和基于模型的学习。为了简化操作,目前我们将暂时禁用这些额外的环境。

  3. 按下 Shift 键,然后在层级面板中选择所有编号的 HallwayArea(1-15)对象。

  4. 选中所有额外的 HallwayArea 对象,点击 “Active” 复选框将其禁用,如下图所示:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/bc31838a-bed8-4a26-aff8-f888099a6171.png

禁用所有额外的训练走廊

  1. 打开层级窗口中剩下的活动 HallwayArea 并选择 Agent。

  2. 将 Brain 智能体设置为使用 HallwayLearning brain。默认情况下,它可能设置为使用玩家的大脑。

  3. 在层级窗口中重新选择 Academy 对象,确保 Hallway Academy 组件的大脑设置为 Learning,并且启用了 Control 复选框。

  4. 打开 Python 或 Anaconda 窗口,进入 ML-Agents/ml-agents 文件夹。确保你的 ML-Agents 虚拟环境已激活,并运行以下命令:

mlagents-learn config/trainer_config.yaml --run-id=hallway --train
  1. 让训练器启动并提示你点击编辑器中的 Play 按钮。观察代理运行,并将其表现与 VisualHallway 示例进行对比。

通常,在 50,000 次迭代之前,你会注意到代理有一些训练活动,但这可能会有所不同。所谓训练活动,是指代理的平均奖励(Mean Reward)大于-1.0,标准奖励(Standard Reward)不等于零。即使你让示例运行完成,即 500,000 次迭代,它也不太可能训练到正的平均奖励。我们通常希望奖励范围从-1.0 到+1.0,并且有一定的变化来展示学习活动。如果你还记得 VisualHallway 示例,代理在整个训练过程中没有显示任何学习活动。我们本可以延长训练迭代次数,但不太可能看到任何稳定的训练成果。原因在于状态空间的增加和奖励的处理。我们将在下一节扩展对状态的理解,并讨论它与强化学习的关系。

理解状态

Hallway 和 VisualHallway 示例本质上是相同的游戏问题,但提供了不同的视角,或者我们在强化学习中所说的环境或游戏状态。在 Hallway 示例中,代理通过传感器输入进行学习,这一点我们将很快讨论,而在 VisualHallway 示例中,代理通过摄像头或玩家视角进行学习。此时,理解每个示例如何处理状态,以及我们如何修改状态,将会非常有帮助。

在接下来的练习中,我们将修改 Hallway 输入状态并查看结果:

  1. 跳回到上一个练习结束时启用学习的 Hallway 场景。

  2. 我们需要修改几行 C#代码,没什么难的,但安装 Visual Studio(Community 版或其他版本)会比较有用,因为这是我们推荐的编辑器。当然,你也可以使用任何你喜欢的代码编辑器,只要它与 Unity 兼容。

  3. 在层级窗口中找到 Agent 对象,然后在检视窗口中点击 Hallway Agent 组件上的齿轮图标,如下图所示:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/62b836fb-b5d0-405a-aaef-059a8205417c.png

打开 HallwayAgent.cs 脚本

  1. 从上下文菜单中选择编辑脚本选项,如上图所示。这将会在你选择的代码编辑器中打开脚本。

  2. 在编辑器中找到以下 C#代码部分:

public override void CollectObservations()
{
  if (useVectorObs)
  {
    float rayDistance = 12f;
    float[] rayAngles = { 20f, 60f, 90f, 120f, 160f };
    string[] detectableObjects = { "orangeGoal", "redGoal", "orangeBlock", "redBlock", "wall" };
    AddVectorObs(GetStepCount() / (float)agentParameters.maxStep);
    AddVectorObs(rayPer.Perceive(rayDistance, rayAngles, detectableObjects, 0f, 0f));
  }
}
  1. CollectObservations 方法是智能体收集观察或输入状态的地方。在 Hallway 示例中,智能体将 useVectorObs 设置为 true,意味着它通过 if 语句内部的代码块来检测状态。所有这些代码做的事情就是从智能体发射一束射线,角度分别为 20f60f120f160f 度,距离由 rayDistance 定义,并检测在 detectableObjects 中定义的物体。这些射线感知是通过一个名为 rayPer 的辅助组件完成的,rayPer 的类型是 RayPerception,并执行 rayPer.Percieve 来收集它所感知到的环境状态。这些信息与步骤的比例一起,添加到智能体输入的向量观察或状态中。此时,状态是长度为 36 的向量。根据这个版本,必须在代码中构造它,但未来可能会有所变化。

  2. 修改 rayAngles 这一行代码,使其与以下内容匹配:

float[] rayAngles = { 20f, 60f };
  1. 这样做的效果是显著缩小了智能体的视野或感知范围,从 180 度缩小到 60 度。换句话说,就是减少了输入状态。

  2. 完成编辑后,保存文件并返回 Unity。当你返回编辑器时,Unity 会重新编译代码。

  3. 在 Assets | ML-Agents | Examples | Hallway | Brains 文件夹中找到 HallwayLearning 大脑,并将 Vector Observation | Space Size 修改为 15,如以下截图所示:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/39bc0790-79d7-4128-ac3d-1fedb99babb0.png

设置向量观察空间大小

  1. 我们将其减少到 15 的原因是:现在的输入由两个角度输入加一个步骤输入组成。每个角度输入包括五个可检测的物体,再加上两个边界,总共七个感知或输入。因此,两个角度乘以七个感知,再加上一个步骤,等于 15。之前,我们有五个角度乘以七个感知,再加上一个步骤,等于 35。

  2. 在修改 Brain 可编程对象后,请确保保存项目。

  3. 再次运行示例进行训练,并观察智能体如何训练。花些时间关注智能体采取的动作以及它是如何学习的。务必让这个示例运行的时间与其他 Hallway 示例相同,希望能够完整运行。

结果让你感到惊讶吗?是的,我们的智能体在较小的视野下实际上训练得更快。这个结果可能看起来完全不合常理,但从数学角度考虑,小的输入空间或状态意味着智能体有更少的路径可供探索,因此应该训练得更快。这正是我们在减少输入空间超过一半后在这个示例中看到的情况。在此时,我们肯定需要观察在 VisualHallway 示例中,减少视觉状态空间会发生什么。

理解视觉状态

强化学习(RL)是一种非常强大的算法,但当我们开始处理大量状态输入时,计算复杂性会变得非常高。为了应对庞大的状态,许多强大的强化学习算法使用无模型或基于策略的学习概念,这一点我们将在后面的章节中讨论。如我们所知,Unity 使用基于策略的算法,允许它通过推广到策略来学习任何大小的状态空间。这使得我们可以轻松地将我们刚才运行的示例中的 15 个向量输入转变为更大规模的状态空间,就像在 VisualHallway 示例中那样。

让我们打开 Unity 到 VisualHallway 示例场景,并看看如何在接下来的练习中减少视觉输入空间:

  1. 在打开 VisualHallway 场景的同时,找到位于 Assets | ML-Agents | Examples | Hallway | Brains 文件夹中的 HallwayLearningBrain 并选择它。

  2. 将 Brain 参数 | 视觉观察的第一个相机可观察输入修改为 32 x 32 灰度。如下截图所示:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/6655a1f7-7595-454c-863c-60b2c4107101.png

设置代理的视觉观察空间

  1. 当视觉观察设置在大脑上时,每一帧都会以所选的分辨率从相机中捕捉。之前,捕捉的图像大小为 84 x 84 像素,虽然远不如玩家模式下的游戏屏幕那么大,但仍然明显大于 35 个向量输入。通过减小图像大小并将其转换为灰度,我们将输入框架从 84 x 84 x 3 = 20,172 个输入,减少到 32 x 32 x 1 = 1,024 个输入。反过来,这大大减少了所需的模型输入空间以及学习所需的网络复杂度。

  2. 保存项目和场景。

  3. 使用以下命令再次以学习模式运行 VisualHallway:

mlagents-learn config/trainer_config.yaml --run-id=vh_reduced --train
  1. 注意我们在每次运行时都在更改 --run-id 参数。回想一下,如果我们要使用 TensorBoard,那么每次运行都需要一个唯一的名称,否则它会覆盖之前的运行。

  2. 让示例训练的时间与之前运行 VisualHallway 练习的时间一样,因为这样你可以很好的比较我们在状态上所做的变化。

结果是否如你所预期?是的,代理仍然没有学会,即使在减少了状态后。原因在于,较小的视觉状态实际上在这种情况下对代理是有害的。就像我们人类在尝试通过针孔看事物时的效果一样。然而,还有另一种方法可以通过卷积将视觉状态减少为特征集。正如你可能记得的,我们在第二章《卷积神经网络和循环神经网络》中详细讨论了卷积。在接下来的章节中,我们将研究如何通过添加卷积层来减少示例的视觉状态。

卷积与视觉状态

在 ML-Agents 工具包中,代理使用的视觉状态是通过一个过程定义的,这个过程在特定的分辨率下截取截图,然后将截图输入到卷积网络中,以训练某种形式的嵌入状态。在接下来的练习中,我们将打开 ML-Agents 的训练代码,并增强卷积代码以获得更好的输入状态:

  1. 使用文件浏览器打开 ML-Agents trainers文件夹,路径为ml-agents.6\ml-agents\mlagents\trainers**。**在这个文件夹中,你会找到几个用于训练代理的 Python 文件。我们感兴趣的文件叫做models.py

  2. 在你选择的 Python 编辑器中打开models.py文件。Visual Studio 与 Python 数据扩展是一个非常好的平台,并且还提供了交互式调试代码的功能。

  3. 向下滚动文件,找到create_visual_observation_encoder函数,其内容如下:

def create_visual_observation_encoder(self, image_input, h_size, activation, num_layers, scope,reuse):
  #comments removed        
  with tf.variable_scope(scope):
    conv1 = tf.layers.conv2d(image_input, 16, kernel_size=[8, 8], strides=[4, 4],activation=tf.nn.elu, reuse=reuse, name="conv_1")
 conv2 = tf.layers.conv2d(conv1, 32, kernel_size=[4, 4], strides=[2, 2],activation=tf.nn.elu, reuse=reuse, name="conv_2")
    hidden = c_layers.flatten(conv2)

    with tf.variable_scope(scope + '/' + 'flat_encoding'):
      hidden_flat = self.create_vector_observation_encoder(hidden, h_size, activation, num_layers, scope, reuse)
 return hidden_flat
  1. 代码使用的是 Python 和 TensorFlow,但你应该能够识别conv1conv2卷积层。注意层的卷积核和步幅是如何定义的,以及缺少的池化层。Unity 没有使用池化,以避免丢失数据中的空间关系。然而,正如我们之前讨论的,这并非总是那么简单,实际上,这取决于你要识别的视觉特征类型。

  2. 在两个卷积层后添加以下代码行,并修改hidden层的设置,如下所示:

conv1 = tf.layers.conv2d(image_input, 16, kernel_size=[8, 8], strides=[4, 4], activation=tf.nn.elu, reuse=reuse, name="conv_1")
conv2 = tf.layers.conv2d(conv1, 32, kernel_size=[4, 4], strides=[2, 2], activation=tf.nn.elu, reuse=reuse, name="conv_2")
conv3 = tf.layers.conv2d(image_input, 64, kernel_size=[2, 2], strides=[2, 2], activation=tf.nn.elu, reuse=reuse, name="conv_3")

hidden = c_layers.flatten(conv3)
  1. 这将产生在代理的游戏视图中添加另一个卷积层的效果,以提取更细节的内容。正如我们在第二章中看到的,卷积神经网络与递归网络,增加额外的卷积层会增加训练时间,但确实会提高训练表现——至少在图像分类器上是这样。

  2. 跳回你的命令行或 Anaconda 窗口,并使用以下命令以学习模式运行示例:

mlagents-learn config/trainer_config.yaml --run-id=vh_conv1 --train
  1. 观察训练过程并查看代理的表现——在示例运行时,一定要在游戏窗口中观察代理的动作。代理是否按你预期的方式执行?将结果与之前的运行进行比较,并注意其中的差异。

你一定会注意到,代理变得稍微更优雅,能够执行更精细的动作。虽然训练过程可能需要更长的时间,但这个代理能够观察到环境中的细微变化,因此会做出更精细的动作。当然,你也可以将整个 CNN 架构替换为使用更明确的架构。然而,需要注意的是,大多数图像分类网络忽略空间相关性,而正如我们在下一节中看到的,空间相关性对于游戏代理非常重要。

是否使用池化

正如我们在第二章中讨论的,卷积和递归网络,ML-Agents 不使用任何池化操作,以避免数据中的空间关系丢失。然而,正如我们在自动驾驶车辆的例子中看到的那样,实际上在更高特征级别的提取(卷积层)上,加入一个或两个池化层是有帮助的。虽然我们的例子在一个更复杂的网络上进行了测试,但它有助于了解这对更复杂的 ML-Agents CNN 嵌入的应用。让我们尝试一下,通过完成以下练习,在上一个例子中添加一个池化层:

  1. 打开你选择的 Python 编辑器中的models.py文件。Visual Studio 配合 Python 数据扩展是一个很好的平台,同时也提供了交互式调试代码的功能。

  2. 找到以下代码块,这是我们在上一个练习中留下的样子:

conv1 = tf.layers.conv2d(image_input, 16, kernel_size=[8, 8], strides=[4, 4], activation=tf.nn.elu, reuse=reuse, name="conv_1")
conv2 = tf.layers.conv2d(conv1, 32, kernel_size=[4, 4], strides=[2, 2], activation=tf.nn.elu, reuse=reuse, name="conv_2")
conv3 = tf.layers.conv2d(image_input, 64, kernel_size=[2, 2], strides=[2, 2], activation=tf.nn.elu, reuse=reuse, name="conv_3")

hidden = c_layers.flatten(conv3)
  1. 现在,我们将通过修改代码块来注入一个池化层,代码如下:
conv1 = tf.layers.conv2d(image_input, 16, kernel_size=[8, 8], strides=[4, 4], activation=tf.nn.elu, reuse=reuse, name="conv_1")
#################### ADD POOLING
conv2 = tf.layers.conv2d(conv1, 32, kernel_size=[4, 4], strides=[2, 2], activation=tf.nn.elu, reuse=reuse, name="conv_2")
conv3 = tf.layers.conv2d(image_input, 64, kernel_size=[2, 2], strides=[2, 2], activation=tf.nn.elu, reuse=reuse, name="conv_3")

hidden = c_layers.flatten(conv3)
  1. 现在,这将设置我们的前一个示例,使用单层池化。你可以将其视为提取所有上层特征,比如天空、墙壁或地板,并将结果池化在一起。仔细想想,代理需要知道多少空间信息才能区分一个天空区域和另一个天空区域?代理实际上只需要知道天空总是在上面。

  2. 打开你的命令行窗口或 Anaconda 窗口,通过运行以下代码来训练示例:

mlagents-learn config/trainer_config.yaml --run-id=vh_conv_wpool1 --train
  1. 和往常一样,观察代理的表现,注意代理在训练过程中是如何移动的。观察训练直到完成,或者观察你之前看到的其他人的训练过程。

现在,根据你的机器或环境,你可能已经注意到训练时间有了显著的改进,但实际表现略有下降。这意味着每次训练迭代执行得更快了,可能快了两到三倍甚至更多,但代理需要更多的交互。在这种情况下,代理训练的时间会更短,但在其他环境中,高级别的池化可能会更具破坏性。最终,这取决于你环境中的视觉效果、你希望代理表现得有多好,以及你个人的耐心。

在接下来的部分,我们将探讨状态的另一个特征——记忆,或者序列。我们将了解如何使用递归网络来捕捉记住序列或事件系列的重要性。

记忆序列的递归网络

本章中我们运行的示例环境默认使用一种递归记忆形式来记住过去的事件序列。这种递归记忆由长短期记忆LSTM)层构成,允许代理记住可能有助于未来奖励的有益序列。请记住,我们在第二章中深入讲解了 LSTM 网络,卷积与递归网络。例如,代理可能反复看到相同的帧序列,可能是朝着目标移动,然后将这一状态序列与增加的奖励关联起来。以下是摘自Khan Aduil 等人的论文Training an Agent for FPS Doom Game using Visual Reinforcement Learning and VizDoom中的图示,展示了这种网络的原始形式:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/42b80820-40ef-424c-8c27-8651f58f377d.png

DQRN 架构

作者将该网络架构称为 DQRN,代表深度 Q 递归网络。可能有点奇怪的是,他们没有称之为 DQCRN,因为图示清楚地显示了卷积的加入。虽然 ML-Agents 的实现略有不同,但概念基本相同。无论如何,添加 LSTM 层对代理训练有很大帮助,但在这一阶段,我们还没有看到不使用 LSTM 的训练效果。

因此,在接下来的练习中,我们将学习如何禁用递归网络,并查看这对训练的影响:

  1. 打开标准的走廊示例场景,即没有视觉学习的那个,位置在Assets/ML-Agents/Examples/Hallway/Scenes文件夹中。

  2. 打开命令行窗口或 Anaconda 窗口,并确保你的 ML-Agent 虚拟 Python 环境已激活。

  3. 找到并打开位于ML-Agents/ml-agents/config文件夹中的trainer_config.xml文件,使用你喜欢的文本或 XML 编辑器。

  4. 找到如下所示的配置块:

HallwayLearning:
    use_recurrent: true
    sequence_length: 64
    num_layers: 2
    hidden_units: 128
    memory_size: 256
    beta: 1.0e-2
    gamma: 0.99
    num_epoch: 3
    buffer_size: 1024
    batch_size: 128
    max_steps: 5.0e5
    summary_freq: 1000
    time_horizon: 64
  1. 名为HallwayLearning的配置块与我们在场景中的 Academy 中设置的大脑名称相匹配。如果你需要确认这一点,可以继续检查。

  2. 我们通常将所有这些配置参数称为超参数,它们对训练有很大的影响,尤其是在设置不正确时。如果你滚动到文件的顶部,你会注意到一组默认参数,接着是每个命名大脑的例外设置。每个大脑的参数部分将覆盖默认设置。

  3. 通过如下修改代码来禁用use_recurrent网络:

HallwayLearning:
    use_recurrent: false
  1. use_recurrent设置为false可以禁用递归编码的使用。现在我们可以看到这对训练的影响。

  2. 保存配置文件。

  3. 按照平常的方式运行学习示例。现在你应该已经能够轻松地运行一个训练示例了。

  4. 和往常一样,观察代理的表现,并确保关注代理的动作。

如你所见,在这个示例中,代理的表现显著较差,显然使用循环网络来捕捉重要动作序列起到了很大的作用。事实上,在大多数重复性游戏环境中,比如 Hallway 和 VisualHallway,增加循环状态非常有效。然而,也有一些环境可能不会受益,或者实际上会因使用状态序列而受到影响。那些需要广泛探索或包含新内容的环境,可能会受到影响。由于代理可能更倾向于使用较短的动作序列,这会受到为代理配置的内存量的限制。在开发新环境时,记得考虑这一点。

现在我们已经有了一个没有循环或 LSTM 层时样本运行的比较,我们可以通过在下一节调整一些相关的循环超参数,重新测试样本。

调整循环超参数

正如我们在讨论循环网络时了解到的,LSTM 层可能接收可变输入,但我们仍然需要定义希望网络记住的最大序列长度。使用循环网络时,我们需要调整两个关键的超参数。以下是这些参数的描述(截至本文撰写时,按 ML-Agents 文档中的列表):

  • sequence_lengthC对应于在训练过程中传递通过网络的经验序列的长度。这个长度应该足够长,以捕捉代理可能需要记住的任何信息。例如,如果你的代理需要记住物体的速度,那么这个值可以是一个小数值。如果你的代理需要记住一条在回合开始时只给定一次的信息,那么这个值应该更大:

    • 典型范围:4 – 128
  • memory_size:对应于用于存储循环神经网络隐藏状态的浮点数数组的大小。该值必须是四的倍数,并且应根据你预期代理需要记住的任务信息量进行缩放:

    • 典型范围:64 – 512

循环的sequence_lengthmemory_size超参数的描述直接来自 Unity ML-Agents 文档。

如果我们查看trainer_config.yaml文件中的 VisualHallway 示例配置,可以看到这些参数定义如下:

VisualHallwayLearning:
    use_recurrent: true
    sequence_length: 64
    num_layers: 1
    hidden_units: 128
    memory_size: 256
    beta: 1.0e-2
    gamma: 0.99
    num_epoch: 3
    buffer_size: 1024
    batch_size: 64
    max_steps: 5.0e5
    summary_freq: 1000
    time_horizon: 64

这实际上意味着我们的代理将使用 256 的内存大小记住 64 帧或输入状态。文档并未明确说明单个输入占用多少内存,因此我们只能假设默认的视觉卷积编码网络——原始的两层模型——每帧需要四个内存单元。我们可以假设,通过增加我们之前示例中的卷积编码,代理可能无法记住每一帧状态。因此,让我们修改 VisualHallway 示例中的配置,以适应这种内存增加,并查看它在以下练习中的效果:

  1. 打开 VisualHallway 示例,回到我们上次在之前的练习中离开的地方,无论是否启用池化。只要记住你是否启用了池化,因为这将影响所需的内存。

  2. 打开位于 ML-Agents/ml-agents/config 文件夹中的 trainer_config.yaml 文件。

  3. 修改 VisualHallwayLearning 配置部分,如下所示:

VisualHallwayLearning:
    use_recurrent: true
    sequence_length: 128
    num_layers: 1
    hidden_units: 128
    memory_size: 2048 without pooling, 1024 with pooling
    beta: 1.0e-2
    gamma: 0.99
    num_epoch: 3
    buffer_size: 1024
    batch_size: 64
    max_steps: 5.0e5
    summary_freq: 1000
    time_horizon: 64
  1. 我们将代理的记忆从 64 个序列增加到 128 个序列,从而使其记忆翻倍。接着,当不使用池化时,我们将记忆增加到 2,048,而使用池化时为 1,024。记住,池化会收集特征并减少每次卷积步骤中生成的特征图数量。

  2. 编辑完成后,保存文件。

  3. 打开你的命令行或 Anaconda 窗口,使用以下命令开始训练:

mlagents-learn config/trainer_config.yaml --run-id=vh_recurrent --train
  1. 当提示时,通过按下播放按钮开始编辑器中的训练会话,并观看操作的展开。

  2. 等待代理训练完成,像我们之前运行的其他示例一样。你应该能注意到训练性能的再次提高,以及代理选择的动作,应该显得更加协调。

如我们所见,轻微调整超参数使得我们能够改善代理的性能。理解在训练中使用的众多参数的作用,对于你成功构建出色的代理至关重要。在下一部分,我们将介绍一些进一步的练习,帮助你提高理解和技能。

练习

一如既往,尽量独立完成两到三个练习,为了你自己的利益。虽然这是一本实践书籍,但花一些额外时间将你的知识应用于新问题总是有益的。

独立完成以下练习:

  1. 浏览并探索 VisualPushBlock 示例。这个示例与走廊示例非常相似,是一个不错的类比,可以进行尝试。

  2. 修改走廊示例中的 HallwayAgent 脚本,以使用更多的扫描角度,从而获得更多的向量观察。

  3. 修改走廊示例,使用组合的传感器扫描和视觉观察输入。这将要求你通过添加相机来修改学习大脑配置,并可能需要更新一些超参数。

  4. 修改其他视觉观测环境,以使用某种形式的向量观测。一个不错的例子是尝试在 VisualPushBlock 示例中应用此功能。

  5. 修改视觉观测摄像头的空间,使其比 84 x 84 像素更大或更小,并选择是否使用灰度化。这是测试更复杂或更简单的 CNN 网络架构时一个很好的练习。

  6. 修改create_visual_observation_encoder卷积编码函数,使其能够使用不同的 CNN 架构。这些架构可以根据你的需求是简单还是复杂。

  7. 修改create_visual_observation_encoder卷积编码函数,以使用不同级别和数量的池化层。尝试在每个卷积层后使用池化,探索其对训练的影响。

  8. 在其他一些示例环境中禁用并重新启用递归网络,探索其对结果的影响。

  9. 在启用递归网络的情况下,调整sequence_lengthmemory_size参数,观察不同序列长度对智能体表现的影响。如果增加sequence_length,请务必相应地增加memory_size参数。

  10. 考虑为智能体添加额外的向量或视觉观测。毕竟,智能体不一定只能有单一形式的感官输入。智能体可以始终检测其所处的方向,或者可能有其他感官输入方式,例如能够听到声音。我们将在后续章节中为智能体提供听觉能力,但也可以尝试自己实现这一功能。

记住,这些练习是为你的利益和享受而提供的,因此确保至少尝试几个。

总结

在这一章中,我们详细探讨了 ML-Agents 中的智能体如何感知环境并处理输入。智能体对环境的感知完全由开发者控制,这通常是关于给予智能体多少或多少输入/状态的微妙平衡。在本章中,我们进行了许多示例,并从深入分析 Hallway 示例及智能体如何使用射线感知环境中的物体开始。接着,我们研究了智能体如何使用视觉观测作为输入或状态,类似于我们人类,从中学习。随后,我们探讨了 ML-Agents 使用的卷积神经网络(CNN)架构,该架构用于编码提供给智能体的视觉观测。我们学习了如何通过添加或删除卷积层或池化层来修改这一架构。最后,我们研究了记忆的作用,或者说如何通过输入状态的递归序列化来帮助智能体训练。递归网络使得智能体能够为提供奖励的动作序列增加更多的价值。

在下一章,我们将更详细地探讨强化学习(RL)以及智能体如何使用 PPO 算法。我们将在过程中深入学习 RL 的基础知识,并了解在训练中使用的许多超参数的重要性。

第八章:理解 PPO

我们避免深入探讨 近端策略优化PPO)算法的更高级的内部工作原理,甚至避免讨论策略与模型的对比。如果你记得的话,PPO 是最早在 OpenAI 开发的 简化级别RL)方法,支撑着 ML-Agents,并且是一种基于策略的算法。在本章中,我们将探讨基于策略和基于模型的强化学习算法之间的差异,以及 Unity 实现的更高级的内部工作原理。

以下是本章我们将覆盖的主要主题:

  • 马拉松强化学习

  • 部分可观察马尔可夫决策过程

  • Actor-Critic 和连续动作空间

  • 理解 TRPO 和 PPO

  • 使用超参数调优 PPO

本章内容属于高级内容,假设你已经学习过前几章和相关练习。为了本章的目的,我们还假设你能够在 Unity 中使用 ML-Agents 顺利打开并运行学习环境。

马拉松强化学习(RL)

到目前为止,我们的关注点一直在离散动作和情景化环境上,其中智能体通常学习解决谜题或完成某些任务。最典型的环境包括 GridWorld,以及当然的 Hallway/VisualHallway 示例,智能体在这些环境中离散地选择诸如上、左、下或右等动作,并且利用这些动作必须导航到某个目标。虽然这些是很好的环境,用来练习并学习强化学习的基本概念,但它们也可能是比较枯燥的学习环境,因为结果往往不是自动产生的,需要大量的探索。然而,在马拉松强化学习环境中,智能体始终通过控制反馈的奖励不断学习。事实上,这种形式的强化学习类似于机器人控制系统和仿真系统。由于这些环境充满了反馈奖励,因此当我们调整/调整超参数时,它们为我们提供了更好的即时反馈,这使得这些类型的环境非常适合我们的学习目的。

Unity 提供了几个马拉松强化学习(RL)环境示例,在撰写本文时,包含了 Crawler、Reacher、Walker 和 Humanoid 示例环境,但这些环境在未来可能会有所更改。

马拉松环境的构建方式不同,我们应该在深入之前了解这些差异。打开 Unity 编辑器和你选择的 Python 命令窗口,设置以运行 mlagents-learn,并完成以下练习:

  1. 打开 Assets/ML-Agents/Examples/Crawler/Scenes 文件夹中的 CrawlerDynamicTarget 示例场景。这个示例展示了一个具有四个可移动肢体的智能体,每个肢体都有两个可以移动的关节。目标是让智能体朝着一个不断变化的动态目标移动。

  2. 在层级窗口中选择 DynamicPlatform | Crawler 对象,并注意爬行者代理组件和 CrawlerDynamicLearning 脑部,如下所示。

    截图:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/45721812-4056-48f1-9b7a-cde4486d892d.png

检查爬行者代理和脑部

  1. 请注意,脑部的空间大小为 129 个向量观察和 20 个连续动作。一个连续动作返回一个值,确定关节可能旋转的程度,从而让智能体学习如何将这些关节动作协调成能够让它爬行到目标的动作。

  2. 点击爬行者代理组件旁边的目标图标,在上下文菜单中选择编辑脚本

  3. 打开脚本后,向下滚动并寻找CollectObservations方法:

public override void CollectObservations()
{
  jdController.GetCurrentJointForces();

  AddVectorObs(dirToTarget.normalized);
  AddVectorObs(body.transform.position.y);
  AddVectorObs(body.forward);
  AddVectorObs(body.up);
  foreach (var bodyPart in jdController.bodyPartsDict.Values)
  {
    CollectObservationBodyPart(bodyPart);
  }
}
  1. 再次提醒,代码是用 C#编写的,但智能体感知的输入应该是相当直观的。我们首先看到智能体接收目标方向、向上和向前的方向,以及每个身体部位的观察作为输入。

  2. 在场景中选择Academy,并确保Brain配置设置为Control(学习模式)。

  3. 从你之前准备好的命令窗口或 Anaconda 窗口,按如下方式运行mlagents-learn脚本:

mlagents-learn config/trainer_config.yaml --run-id=crawler --train
  1. 训练开始后不久,你会看到智能体立即取得可度量的进展。

这个智能体能够非常快速地进行训练,并将在接下来的章节中极大地帮助我们测试对强化学习工作原理的理解。可以自由浏览和探索这个示例,但避免调整任何参数,因为我们将在下一部分开始做这件事。

部分可观察马尔可夫决策过程

在第五章《引入 DRL》中,我们了解到马尔可夫决策过程MDP)用于定义智能体计算动作/价值所用的状态/模型。在 Q 学习的情况下,我们已经看到如何通过表格或网格来存储一个完整的 MDP,用于像 Frozen Pond 或 GridWorld 这样的环境。这些类型的强化学习是基于模型的,意味着它们完全建模环境中的每一个状态——例如,网格游戏中的每一个方格。然而,在大多数复杂的游戏和环境中,能够映射物理或视觉状态会变成一个部分可观察问题,或者我们可能称之为部分可观察马尔可夫决策过程POMDP)。

POMDP 定义了一种过程,其中智能体永远无法完全看到其环境,而是学会基于派生的通用策略进行行动。这个过程在爬行者示例中得到了很好的展示,因为我们可以看到智能体只通过有限的信息——目标方向——来学习如何移动。下表概述了我们通常用于强化学习的马尔可夫模型定义:

所有状态可观察?马尔可夫链MDP
隐马尔可夫模型POMDP

由于我们通过动作控制智能体的状态,因此我们研究的马尔可夫模型包括 MDP 和 POMDP。同样,这些过程也常常被称为开模型或关模型,如果一个强化学习算法完全了解状态,我们称之为基于模型的过程。相反,POMDP 则指的是关模型过程,或者我们所说的基于策略的方法。基于策略的算法提供了更好的泛化能力,并且能够在具有未知或无限可观察状态的环境中进行学习。部分可观察状态的示例包括走廊环境、视觉走廊环境,以及当然还有爬行者。

马尔可夫模型为机器学习的许多方面提供了基础,你可能会在更先进的深度学习方法中遇到它们的应用,这些方法被称为深度概率编程。深度 PPL,正如它所称,是变分推断和深度学习方法的结合。

无模型方法通常使用经验缓冲区来存储一组经验,这些经验将在以后用于学习通用策略。这个缓冲区由一些超参数定义,称为time_horizonbatch_sizebuffer_size。以下是从 ML-Agents 文档中提取的这些参数的定义:

  • time_horizon:这对应于每个智能体在将经验添加到经验缓冲区之前收集的步数。当在一个回合结束之前达到了此限制时,将使用一个值估计来预测智能体当前状态的整体预期奖励。因此,该参数在较长时间跨度(较少偏差但较高方差的估计)和较短时间跨度(较大偏差但较少变化的估计)之间进行权衡。在回合内存在频繁奖励,或者回合过大时,较小的数字可能更为理想。这个数字应该足够大,以捕捉智能体动作序列中的所有重要行为:

    • 典型范围:32 – 2,048
  • buffer_size:这对应于在我们更新模型或进行任何学习之前,需要收集的经验数量(智能体的观察、动作和奖励)。它应该是batch_size的倍数。通常,较大的buffer_size参数对应于更稳定的训练更新。

    • 典型范围:2,048 – 409,600
  • batch_size:这是用于一次梯度下降更新的经验数量。它应该始终是buffer_size参数的一部分。如果你使用的是连续动作空间,那么这个值应该很大(通常在千级别)。如果你使用的是离散动作空间,那么这个值应该较小(通常在十级别)。

    • 典型范围(连续型):512 – 5,120

    • 典型范围(离散型):32 – 512

我们可以通过查看CrawlerDynamicLearning大脑配置来了解这些值是如何设置的,并通过修改它来观察它对训练的影响。打开编辑器并在正确配置的 Python 窗口中进入CrawlerDynamicTarget场景,按照以下步骤操作:

  1. 打开位于ML-Agents/ml-agents/config文件夹中的trainer_config.yaml文件。

  2. 向下滚动到CrawlerDynamicLearning大脑配置部分:

CrawlerDynamicLearning:
  normalize: true
  num_epoch: 3
  time_horizon: 1000
 batch_size: 2024
 buffer_size: 20240
  gamma: 0.995
  max_steps: 1e6
  summary_freq: 3000
  num_layers: 3
  hidden_units: 512
  1. 注意突出显示的行,显示了time_horizonbatch_sizebuffer_size参数。如果你还记得我们早期的 Hallway/VisualHallway 示例,time_horizon参数仅为 32 或 64。由于这些示例使用了离散动作空间,我们可以为time_horizon设置一个较低的值。

  2. 翻倍所有参数值,如以下代码片段所示:

time_horizon: 2000
batch_size: 4048
buffer_size: 40480
  1. 本质上,我们在这里做的是将智能体用于构建其周围环境策略的经验量翻倍。实际上,我们是在给智能体提供更多的经验快照来进行训练。

  2. 按照之前的操作,运行智能体进行训练。

  3. 让智能体训练的时间与运行之前的基础示例相同。这将为你提供一个良好的训练性能对比。

一件很明显的事情是,智能体训练的稳定性提高了,这意味着智能体的平均奖励将更加稳定地增长,且波动更小。回想一下,我们希望避免训练中的跳跃、尖峰或波动,因为这些可能表示网络优化方法的收敛性差。这意味着渐进的变化通常更好,并且表明训练表现良好。通过将time_horizon及相关参数翻倍,我们增加了智能体用于学习的经验量。反过来,这有助于稳定训练,但你可能会注意到,智能体需要更长的时间才能完成相同次数的迭代训练。

部分可观察的强化学习算法被归类为基于策略、无模型或离模型的算法,是 PPO 的基础。在接下来的章节中,我们将探讨强化学习中的改进,重点是更好地管理连续动作空间带来的额外复杂性。

Actor-Critic 和连续动作空间

我们在查看马拉松强化学习或控制学习时引入的另一个复杂性是连续动作空间的引入。连续动作空间表示智能体可以采取的无限可能动作的集合。在之前,我们的智能体可能会选择一个离散动作,比如是或否,现在它必须从一个无限的动作空间中为每个关节选择一个点作为动作。将无限动作空间映射到一个具体动作并不容易解决——然而,我们有神经网络可供使用,这为我们提供了一个非常好的解决方案,采用的架构与我们在第三章中看到的生成对抗网络GAN)类似。

正如我们在生成对抗网络(GAN)章节中发现的那样,我们可以提出一种由两个竞争网络组成的网络架构。这些竞争网络将迫使每个网络通过相互竞争,寻找最佳解决方案,将一个随机空间映射到一个可信的伪造物。类似的概念也可以应用于这种情况,这被称为演员-评论家模型。该模型的示意图如下:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/76a62795-5bcf-47ca-8854-38bec7a20d00.png

演员-评论家架构

这里发生的事情是,演员根据给定的状态从策略中选择一个动作。然后状态首先通过一个评论家,评论家根据当前状态评估最佳的动作,并给出一定的误差。更简单地说,评论家根据当前状态批评每一个动作,然后演员根据状态选择最佳动作。

这种动作选择方法最早在一种叫做对抗双 Q 网络DDQN)的算法中进行了探索。现在它已成为大多数高级强化学习算法的基础。

演员-评论家模型本质上是为了解决连续动作空间问题,但鉴于其性能,这种方法也已被融入到一些高级离散算法中。ML-Agents 使用演员-评论家模型处理连续空间,但不使用离散动作空间的演员-评论家模型。

使用演员-评论家方法需要,或者在我们的网络中最有效的是,额外的层和神经元,这是我们可以在 ML-Agents 中配置的内容。这些超参数的定义来自 ML-Agents 文档,具体如下:

  • num_layers:这对应于在观测输入之后,或者在视觉观测的 CNN 编码之后存在的隐藏层数量。对于简单的问题,较少的层可能会训练得更快、更高效。对于更复杂的控制问题,可能需要更多的层:

    • 典型范围:1 – 3
  • hidden_units:这些对应于神经网络中每个全连接层的单元数。对于那些正确动作是观测输入的简单组合的问题,这个值应该较小。对于那些动作是观测变量间复杂交互的问题,这个值应该更大:

    • 典型范围:32 – 512

让我们打开一个新的 ML-Agents 马拉松或控制示例,看看修改这些参数对训练的影响。按照这个练习来理解向控制问题中添加层和神经元(单元)的效果:

  1. 打开Assets/ML-Agents/Examples/Walker/Scenes文件夹中的 Walker 场景。这个示例展示了一个行走的类人动画。

  2. 在层次窗口中找到并选择 WalkerAgent 对象,然后查看检查器窗口并检查 Agent 和 Brain 设置,如下图所示:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/ccf7c439-29cb-4b2d-8745-f26137f898dc.png

WalkerAgent 和 WalkerLearning 属性

  1. 在层级窗口中选择WalkerAcademy,并确保为Brains参数启用了 Control 选项。

  2. 打开位于ML-Agents/ml-agents/config文件夹中的trainer_config.yaml文件,向下滚动至WalkerLearning部分,如下所示:

WalkerLearning:
    normalize: true
    num_epoch: 3
    time_horizon: 1000
    batch_size: 2048
    buffer_size: 20480
    gamma: 0.995
    max_steps: 2e6
    summary_freq: 3000
    num_layers: 3
 hidden_units: 512
  1. 注意这个示例使用了多少层和单位。是更多还是更少于我们为离散动作问题使用的数量?

  2. 保存所有内容并为训练设置样本。

  3. 从 Python 控制台启动训练会话,使用以下命令:

mlagents-learn config/trainer_config.yaml --run-id=walker --train
  1. 这个代理可能需要更长的训练时间,但请尝试等待大约 100,000 次迭代,以便更好地了解它的训练进度。

现在我们更好地理解了 Actor-Critic 及其在连续动作空间中的应用,我们可以继续探索改变网络大小对训练这些更复杂网络的影响,接下来会讲到这一部分。

扩展网络架构

Actor-Critic 架构增加了问题的复杂性,因此也增加了解决这些问题所需的网络的复杂性和规模。这与我们之前对 PilotNet 的分析没有太大区别,PilotNet 是 Nvidia 用于自驾的多层 CNN 架构。

我们想要看到的是,增加网络大小对复杂示例(如 Walker 示例)产生的即时效果。打开 Unity 中的Walker示例并完成以下练习:

  1. 打开通常存放的trainer_config.yaml文件。

  2. 修改WalkerLearning配置,如下所示的代码:

WalkerLearning:
    normalize: true
    num_epoch: 3
    time_horizon: 1000
    batch_size: 2048
    buffer_size: 20480
    gamma: 0.995
    max_steps: 2e6
    summary_freq: 3000
    num_layers: 1
 hidden_units: 128
  1. 设置num_layers: 1hidden_units: 128。这些是我们在离散动作空间问题中常用的典型值。你可以通过查看另一个离散样本,如VisualHallwayLearning配置,来确认这一点,具体如下:
VisualHallwayLearning:
    use_recurrent: false
    sequence_length: 64
    num_layers: 1
 hidden_units: 128
    memory_size: 256
    beta: 1.0e-2
    gamma: 0.99
    num_epoch: 3
    buffer_size: 1024
    batch_size: 64
    max_steps: 5.0e5
    summary_freq: 1000
    time_horizon: 64
  1. 这个样本使用了我们刚才为连续动作问题设置的相同设置。

  2. 编辑完成后,保存所有内容并准备开始训练。

  3. 启动一个新的训练会话,并使用新的run-id参数。记住,每次运行时都更改run-id参数,这样在 TensorBoard 中更容易区分每次运行。

  4. 和往常一样,让样本运行的时间与之前未经修改的运行时间相同,以便进行良好的比较。

你可能会立即注意到运行这个示例时,训练的稳定性非常好。第二个你可能会注意到的是,虽然训练稳定性增加了,但性能略微下降。请记住,较小的网络有更少的权重,通常会更稳定且训练速度更快。然而,在这个问题中,虽然网络的训练更稳定且速度更快,但你可能会注意到训练会遇到瓶颈。现在,受限于网络规模,智能体能够更快地优化较小的网络,但却没有以前看到的精细控制。事实上,这个智能体永远不会像第一次未修改的运行那样好,因为它现在受限于一个较小的网络。这是构建深度强化学习(DRL)智能体时需要平衡的另一个权衡点,尤其是在游戏和仿真中。

在下一部分,我们将进一步探讨我们所说的优势函数,或者像演员-评论家中使用的那些,并首先探索 TRPO,当然还有 PPO。

理解 TRPO 和 PPO

有许多变体的策略和无模型算法已经变得流行,用于解决强化学习(RL)问题,优化未来奖励的预测。正如我们所见,许多算法使用优势函数,例如演员-评论家(Actor-Critic),其中有两方试图收敛到最优解。在这种情况下,优势函数试图找到最大期望的折扣奖励。TRPO 和 PPO 通过使用一种叫做**最小化最大化(MM)**的优化方法来实现这一目标。下面的图表展示了 MM 算法如何解决这个问题:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/2942a112-735d-42c4-8c0b-91d658772882.png

使用 MM 算法

这个图表摘自 Jonathon Hui 的一系列博客,这些博客优雅地描述了 MM 算法,并且详细讲解了 TRPO 和 PPO 方法*。* 详细来源请见以下链接:medium.com/@jonathan_hui/rl-proximal-policy-optimization-ppo-explained-77f014ec3f12

本质上,MM 算法通过交互式地最大化和最小化函数参数,直到达到收敛的解,从而找到最优配对函数。在图表中,红线表示我们希望逼近的函数,蓝线表示收敛的函数。你可以看到算法如何通过选择最小值/最大值来找到一个解的过程。

使用 MM 时我们遇到的问题是函数逼近有时会偏离,或者掉入一个谷底。为了更好地理解这个问题,我们可以将其视为使用直线爬升不平的山丘。以下是这种情况的示例:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/7a561b98-be5b-4d4f-bf2f-b5ac4303dd07.png

尝试使用线性方法爬升山丘

你可以看到,仅仅使用线性路径来尝试穿越这条危险的山脊实际上是非常危险的。虽然危险可能不那么明显,但当使用线性方法来解决 MM 问题时,它仍然是一个大问题,就像你在陡峭的山脊上只使用直线固定路径进行徒步旅行一样。

TRPO 通过使用二次方法解决了使用线性方法的问题,并通过限制每次迭代可以采取的步骤数,形成一个信任区域。也就是说,算法确保每个位置都是正的且安全的。如果我们再次考虑我们的爬坡示例,我们可以将 TRPO 看作是设置一条路径或信任区域,如下图所示:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/1f867818-2804-442b-9cec-fdd67792c95c.png

一条信任区域路径沿着山坡

在前面的照片中,路径仅作为示例展示,表示一组连接的圆圈或区域;真实的信任路径可能靠近实际的峰顶或山脊,也可能不靠近。不管怎样,这种方式的效果是使代理能够以更渐进和逐步的速度学习。通过 TRPO,信任区域的大小可以调整,使其变大或变小,以配合我们偏好的策略收敛。TRPO 的问题在于它相当复杂,因为它需要对一些复杂方程进行二次导数计算。

PPO 通过限制或裁剪两种策略之间的 Kulbach-Leibler(KL)散度来解决这个问题,KL 散度在每次迭代中进行测量。KL 散度衡量的是概率分布之间的差异,可以通过以下图示描述:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/15484cb4-865c-4d5b-b788-e193fbf80aeb.png

理解 KL 散度

在前面的图示中,p(x)q(x) 各自代表不同的策略,其中 KL 散度被衡量。算法随后使用这种散度的度量来限制或裁剪每次迭代中可能发生的策略变化量。ML-Agents 使用两个超参数,允许你控制应用于目标或确定每次迭代中策略变化量的函数的裁剪量。以下是 Unity 文档中描述的 beta 和 epsilon 参数的定义:

  • Beta:这对应于熵正则化的强度,使策略变得更加随机。这确保了代理在训练过程中能正确地探索动作空间。增加这个值将确保采取更多的随机动作。应该调整此值,使得熵(可以从 TensorBoard 中衡量)随着奖励的增加而缓慢减少。如果熵下降得太快,请增加 beta。如果熵下降得太慢,请减少 beta:

  • 典型范围:1e-4 – 1e-2

  • Epsilon:这对应于梯度下降更新过程中,旧策略和新策略之间可接受的散度阈值。将此值设置为较小将导致更稳定的更新,但也会减慢训练过程:

  • 典型范围:0.1 – 0.3

需要记住的关键点是,这些参数控制了一个策略从一个迭代到下一个迭代的变化速度。如果你注意到一个智能体的训练表现有些不稳定,可能需要将这些参数调小。epsilon的默认值是**.2**,beta的默认值是1.0e-2,但是,当然,我们需要探索这些值如何影响训练,无论是正面还是负面的方式。在接下来的练习中,我们将修改这些策略变化参数,并观察它们在训练中的效果:

  1. 对于这个示例,我们将打开Assets/ML-Agents/Examples/Crawler/Scenes文件夹中的CrawlerDynamic场景。

  2. 打开位于ML-Agents/ml-agents/config文件夹中的trainer_config.yaml文件。由于我们已经评估了这个样本的表现,有几种方法可以将训练配置恢复,并对 beta 和 epsilon 参数进行一些修改。

  3. 向下滚动到CrawlerDynamicLearning配置部分,并按照以下方式修改:

CrawlerDynamicLearning:
    normalize: true
    num_epoch: 3
    time_horizon: 1000
    batch_size: 1024
    buffer_size: 20240
    gamma: 0.995
    max_steps: 1e6
    summary_freq: 3000
    num_layers: 3
    hidden_units: 512
    epsilon: .1
 beta: .1 
  1. 我们将epsilonbeta参数修改为更高的值,这意味着训练将变得不那么稳定。然而,如果你还记得,这些马拉松示例通常会以更稳定的方式进行训练。

  2. 打开一个配置正确的 Python 控制台,并运行以下命令以启动训练:

mlagents-learn config/trainer_config.yaml --run-id=crawler_policy --train
  1. 和往常一样,等待若干训练回合,以便从一个示例到下一个示例进行良好的比较。

你可能会发现意外的情况,那就是智能体似乎开始回退,实际上它确实在回退。这是因为我们将那些信任区间设置得太大(一个大的beta),而虽然我们允许变化速率较低(epsilon为.1),但是我们可以看到beta值对训练更为敏感。

请记住,Unity ML-Agents 实现使用了多个交叉特征,这些特征一起构成了一个强大的 RL 框架。在接下来的部分,我们将简要回顾 Unity 最近添加的一个优化参数。

泛化优势估计

RL 领域正在经历爆炸式增长,得益于不断推进的研究,推动了可能性的边界。每一次小的进展都会带来额外的超参数和小的调整,可以用来稳定和/或改善训练性能。Unity 最近添加了一个名为 lambda 的新参数,其定义来自文档,具体如下:

  • lambda:这对应于计算广义优势估计GAE)时使用的 lambda 参数arxiv.org/abs/1506.02438。可以将其看作代理在计算更新后的价值估计时,依赖当前价值估计的程度。较低的值对应更多地依赖当前价值估计(这可能导致较高的偏差),较高的值则对应更多地依赖环境中实际收到的奖励(这可能导致较高的方差)。该参数提供了两者之间的折中,正确的值可以带来更稳定的训练过程:

    • 典型范围:0.9 – 0.95

GAE 论文描述了一个名为 lambda 的函数参数,可以用于调整奖励估计函数,并且最适用于控制或马拉松型强化学习任务。我们不会深入探讨细节,感兴趣的读者应该下载论文并自行查阅。然而,我们将探索改变此参数如何影响控制样本,如接下来的Walker场景练习:

  1. 打开 Unity 编辑器,加载Walker示例场景。

  2. 在层级结构中选择 Academy 对象,并确认场景仍然设置为训练/学习模式。如果是,你无需做其他操作。如果场景未设置为学习模式,你知道该怎么做。

  3. 打开trainer_config.yaml文件,并按如下方式修改WalkerLearning

WalkerLearning:
    normalize: true
    num_epoch: 3
    time_horizon: 1000
    batch_size: 2048
    buffer_size: 20480
    gamma: 0.995
    max_steps: 2e6
    summary_freq: 3000
    num_layers: 3
    hidden_units: 512
    lambd: .99
  1. 注意我们如何设置lambda参数,并确保num_layershidden_units被重置为原始值。论文中,作者描述了最优值在.95.99之间,但这与 Unity 文档中的描述有所不同。

  2. 编辑完成后保存文件。

  3. 打开一个用于训练的 Python 控制台设置,并使用以下命令运行:

mlagents-learn config/trainer_config.yaml --run-id=walker_lambd --train
  1. 确保让样本运行的时间与之前一样,以便进行良好的比较。

经过大量训练后,你会注意到在这个示例中,代理的训练速度确实慢了大约 25%。这一结果告诉我们,通过增加 lambda,我们在要求代理更多地重视奖励。现在,这可能看起来有些反直觉,但在这个样本或这种类型的环境中,代理会收到持续的小的正向奖励。这导致每个奖励出现偏差,正如我们所见,这会扭曲训练并妨碍代理的进展。对于感兴趣的读者来说,尝试在 Hallway 环境中调整 lambda 参数可能会是一个有趣的练习,在该环境中代理只会收到一个正向的单次奖励。

强化学习优势函数有许多形式,并且它们的作用是解决许多与离线模型或策略驱动算法(如 PPO)相关的问题。在下一节中,我们将通过修改并创建一个新的样本控制/马拉松学习环境来结束本章内容。

学习调整 PPO

在这一部分,我们将学习如何调整一个修改过的/全新的控制学习环境。这将帮助我们深入了解 Unity 示例的内部工作原理,并向你展示如何稍后修改或创建新的示例。让我们首先打开 Unity 编辑器,开始进行以下练习:

  1. 打开 Reacher 场景,将其设置为学习模式,并进行训练。你现在应该能轻松完成这一部分。让代理训练足够长的时间,以便像往常一样建立基准。

  2. 从菜单中选择 Assets/Import Package/Custom Package。从下载的源代码中的 Chapter08 文件夹找到 Chapter_8_Assets.unitypackage

  3. 打开 Assets/HoDLG/Scenes 文件夹中的 Reacher_3_joint 场景。这是一个修改过的场景,我们将一起学习其构建过程。

  4. 首先,注意只有一个 Reacher 臂部是活动的,但现在有三个关节,如下图所示:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/e473ca06-9250-4c2f-90a8-ae6225372b74.png

检查 Agent 游戏对象

  1. 注意现在这个手臂有三个部分,其中新的部分被称为 Capsule(2),并标识为 Pendulum C。关节的顺序现在是错乱的,这意味着 Pendulum C 实际上是中间的摆,而不是底部的摆。

  2. 选择每个 Capsule 对象,检查它们的配置和位置,如下图所示:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/909c7124-2b2d-427d-93ff-c5a7257beedb.png

检查 Capsule 对象

  1. 请务必注意每个 Capsule 的 Configurable Joint | Connected Body 对象。此属性设置对象将连接或铰接的物体。Configurable Joint 组件上还有许多其他属性,可以让你模拟这种关节交互的任何形式,甚至是生物学上的。例如,你可能希望使这个手臂的关节更具人类特征,只允许某些角度的运动。同样,如果你设计的是一个动作受限的机器人,那么也可以通过这个关节组件来模拟。

  2. 在此阶段,我们可以设置并运行示例。打开并设置一个 Python 控制台或 Anaconda 窗口进行训练。

  3. 运行示例进行训练并观察代理的进展。让代理运行足够多的迭代,以便将训练性能与基准进行比较。

在这个阶段,我们已经启动了示例,并准备开始调整新的参数以优化训练。然而,在这之前,我们将回顾一下为了使上一个示例可行所需的 C# 代码更改。下一部分将介绍 C# 代码更改,对于不感兴趣代码的开发者来说是可选的。如果你打算在 Unity 中构建自己的控制或马拉松环境,你将需要阅读下一部分。

控制项目所需的编码更改

正如我们之前提到的,这一部分是可选的,适用于那些对使用 Unity C#构建自己控制样本的细节感兴趣的人。未来可能不再需要任何编码更改来修改这些类型的样本,这也是该部分是可选的另一个原因。

完成以下练习,了解在 Reacher 控制示例中添加关节所需的编码更改:

  1. 在层级窗口中选择 Agent 对象,然后在检查器窗口中注意到 Reacher Agent_3 组件。这是我们将要检查的修改后的脚本。

  2. 点击 Reach Agent_3 组件旁边的目标图标,从上下文菜单中选择编辑脚本。

  3. 这将会在你选择的 C#代码编辑器中打开ReacherAgent_3.cs脚本。

  4. 在声明部分需要注意的第一件事是新增变量的添加,以下是以粗体显示的内容:

public GameObject pendulumA;
public GameObject pendulumB;
public GameObject pendulumC;
public GameObject hand;
public GameObject goal;
private ReacherAcademy myAcademy;
float goalDegree;
private Rigidbody rbA;
private Rigidbody rbB;
private Rigidbody rbC;
private float goalSpeed;
private float goalSize;
  1. 添加了两个新变量,pendulumCrbC,用于保存新的关节 GameObject 和 RigidBody。现在,Unity 物理中的Rigidbody表示可以被物理引擎移动或操作的物体。

    Unity 正在对其物理引擎进行升级,这将改变这里的一些教学内容。当前版本的 ML-Agents 使用的是旧的物理系统,因此这个示例也将使用旧系统。

  2. 接下来需要注意的重要事项是添加了额外的代理观察项,具体请参见以下CollectObservations方法:

public override void CollectObservations()
    {
        AddVectorObs(pendulumA.transform.localPosition);
        AddVectorObs(pendulumA.transform.rotation);
        AddVectorObs(rbA.angularVelocity);
        AddVectorObs(rbA.velocity);

        AddVectorObs(pendulumB.transform.localPosition);
        AddVectorObs(pendulumB.transform.rotation);
        AddVectorObs(rbB.angularVelocity);
        AddVectorObs(rbB.velocity);

        AddVectorObs(pendulumC.transform.localPosition);
 AddVectorObs(pendulumC.transform.rotation);
 AddVectorObs(rbC.angularVelocity);
 AddVectorObs(rbC.velocity);

        AddVectorObs(goal.transform.localPosition);
        AddVectorObs(hand.transform.localPosition);

        AddVectorObs(goalSpeed);
  }
  1. 粗体部分添加了新的观察项pendulumCrbC,它们总共增加了 13 个向量。回顾一下,这意味着我们还需要将大脑的观察数从 33 个向量切换到 46 个向量,具体如下图所示:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/15dec040-4804-44a0-9dac-cc20311fad51.png

检查更新的 ReacherLearning_3 大脑

  1. 接下来,我们将查看AgentAction方法;这是 Python 训练器代码调用代理并告诉它进行什么动作的地方,代码如下:
public override void AgentAction(float[] vectorAction, string textAction)
  {
        goalDegree += goalSpeed;
        UpdateGoalPosition();

        var torqueX = Mathf.Clamp(vectorAction[0], -1f, 1f) * 150f;
        var torqueZ = Mathf.Clamp(vectorAction[1], -1f, 1f) * 150f;
        rbA.AddTorque(new Vector3(torqueX, 0f, torqueZ));

        torqueX = Mathf.Clamp(vectorAction[2], -1f, 1f) * 150f;
        torqueZ = Mathf.Clamp(vectorAction[3], -1f, 1f) * 150f;
        rbB.AddTorque(new Vector3(torqueX, 0f, torqueZ));

        torqueX = Mathf.Clamp(vectorAction[3], -1f, 1f) * 150f;
 torqueZ = Mathf.Clamp(vectorAction[4], -1f, 1f) * 150f;
 rbC.AddTorque(new Vector3(torqueX, 0f, torqueZ));
    }
  1. 在这种方法中,我们扩展了代码,允许代理以rigidbody rbC的形式移动新的关节。你注意到新的学习大脑也添加了更多的动作空间吗?

  2. 最后,我们查看AgentReset方法,看看代理如何在新的肢体加入后重置自己,代码如下:

public override void AgentReset()
    {
        pendulumA.transform.position = new Vector3(0f, -4f, 0f) + transform.position;
        pendulumA.transform.rotation = Quaternion.Euler(180f, 0f, 0f);
        rbA.velocity = Vector3.zero;
        rbA.angularVelocity = Vector3.zero;

        pendulumB.transform.position = new Vector3(0f, -10f, 0f) + transform.position;
        pendulumB.transform.rotation = Quaternion.Euler(180f, 0f, 0f);
        rbB.velocity = Vector3.zero;
        rbB.angularVelocity = Vector3.zero;

        pendulumC.transform.position = new Vector3(0f, -6f, 0f) + transform.position;
 pendulumC.transform.rotation = Quaternion.Euler(180f, 0f, 0f);
 rbC.velocity = Vector3.zero;
 rbC.angularVelocity = Vector3.zero;

        goalDegree = Random.Range(0, 360);
        UpdateGoalPosition();

        goalSize = myAcademy.goalSize;
        goalSpeed = Random.Range(-1f, 1f) * myAcademy.goalSpeed;

        goal.transform.localScale = new Vector3(goalSize, goalSize, goalSize);
    }
  1. 这段代码的作用仅仅是将手臂的位置重置为初始位置并停止所有运动。

这涵盖了本示例所需的唯一代码更改。幸运的是,只有一个脚本需要修改。未来可能完全不需要再修改这些脚本了。在下一部分,我们将通过调整额外参数并引入另一种训练优化方法来进一步完善样本训练。

多重代理策略

在这一部分,我们将探讨如何通过引入多个智能体来训练相同的策略,从而改进基于策略或非模型方法(如 PPO)。你将在这一部分使用的示例练习完全由你决定,应该是你熟悉的和/或感兴趣的内容。为了我们的目的,我们将探索一个我们已 extensively 研究过的示例——Hallway/VisualHallway。如果你已经跟随本书的大多数练习,你应该完全有能力适应这个示例。不过,请注意,在本次练习中,我们希望使用一个已设置为使用多个智能体进行训练的示例。

之前,我们避免讨论多个智能体;我们避免探讨这个训练方面是因为它可能会让关于模型内与模型外方法的讨论更加复杂。现在,你已经理解了使用基于策略的方法的差异和原因,你可以更好地理解,由于我们的智能体使用的是基于策略的方法,我们可以同时训练多个智能体针对同一个策略。然而,这可能会对其他训练参数和配置产生影响,正如你可能已经想象的那样。

打开 Unity 编辑器,进入Hallway/VisualHallway示例场景,或者你选择的其他场景,并完成以下练习:

  1. 打开一个 Python 或 Anaconda 控制台窗口,并准备开始训练。

  2. 选择并启用 HallwayArea,选择区域(1)到(19),使它们变为激活状态并可在场景中查看。

  3. 选择每个HallwayArea中的 Agent 对象,并确保Hallway Agent | Brain设置为 HallwayLearning,而不是 HallwayPlayer。这将启用所有额外的训练区域。

  4. 根据你之前的经验,你可能会选择是否将示例修改回原始状态。回想一下,在早期的练习中,我们修改了 HallwayAgent 脚本,使它只扫描一个较小的角度范围。这可能还需要你调整脑参数。

  5. 设置完场景后,保存它和项目。

  6. 使用唯一的run-id运行场景并等待多个训练迭代。这个示例的训练速度可能会明显变慢,甚至加快,这取决于你的硬件配置。

现在我们已经为 Hallway 环境建立了一个新的基准线,我们可以确定修改一些超参数对离散动作样本的影响。我们将重新审视的两个参数是num_epochs(训练轮数)和batch_size(每个训练轮的经验数量),这些我们在之前的连续动作(控制)样本中也看过。在文档中,我们提到,在训练控制智能体时,更大的批处理大小更为理想。

在我们继续之前,打开trainer_config.yaml文件并检查如下的 HallwayLearning 配置部分:

HallwayLearning:
    use_recurrent: true
    sequence_length: 64
    num_layers: 2
    hidden_units: 128
    memory_size: 256
    beta: 1.0e-2
    gamma: 0.99
    num_epoch: 3
    buffer_size: 1024
    batch_size: 128
    max_steps: 5.0e5
    summary_freq: 1000
    time_horizon: 64

在 Unity 文档中,专门提到只有在增加批量大小时才增加 epoch 数量,这是为了考虑额外的训练经验。我们了解到,控制示例通常从更大的批量大小中受益,因此需要更大的 epoch 数量。然而,我们还想确定的最后一件事是,在多个代理共同学习同一策略的离散动作示例中,修改batch_sizenum_epoch参数的效果。

出于这个练习的目的,我们将只修改batch_sizenum_epoch为如下值:

  1. 更新你正在使用的HallwayLearning或大脑配置,使用以下参数:
HallwayLearning:
    use_recurrent: true
    sequence_length: 64
    num_layers: 2
    hidden_units: 128
    memory_size: 256
    beta: 1.0e-2
    gamma: 0.99
    num_epoch: 10
    buffer_size: 1024
    batch_size: 1000
    max_steps: 5.0e5
    summary_freq: 1000
    time_horizon: 64
  1. 我们将num_epoch设置为 10,batch_size设置为 1000。这些设置对于控制样本来说是典型的,正如我们之前看到的那样,但现在我们想要查看在多个代理训练相同策略的离散动作示例中的效果。

  2. 为训练准备样本,并准备好 Python 控制台并打开。

  3. 使用以下命令运行训练会话:

mlagents-learn config/trainer_config.yaml --run-id=hallway_e10b1000 --train
  1. 注意我们是如何使用帮助器前缀设置run-id来命名迭代的。我们使用e10来表示num_epoch参数被设置为10b1000代表batch_size值为1000。这种命名方案很有帮助,也是我们在本书中将继续使用的方式。

在代理进行训练时,尝试回答以下问题:

  • 代理的训练效果是否比你预期的更好或更差?

  • 你认为这是为什么?

你需要运行样本来学习这些问题的答案。在接下来的部分中,我们将讨论一些有助于你理解这些复杂主题的练习。

练习

尝试自行完成以下一两个练习:

  1. 运行 CrawlerStaticTarget 示例场景,并将其性能与动态示例进行比较。

  2. 将另一个控制示例中的time_horizonbatch_sizebuffer_size大脑超参数加倍:

time_horizon: 2000
batch_size: 4048
buffer_size: 40480
  1. 在另一个控制样本上执行相同的time_horizonbatch_sizebuffer_size的修改,并观察它们的联合效果。

  2. 修改num_layershidden_units大脑超参数为我们在控制样本中使用的值,并将其应用于离散动作示例,如 Hallway 示例,代码如下。它对训练有什么影响?

num_layers: 3
hidden_units: 512
  1. 修改另一个连续或离散动作示例中的num_layershidden_units超参数,并将其与其他参数修改结合使用。

  2. 将离散动作示例中的lambd大脑超参数修改为.99。记住,这将加强奖励的效果:

lambd: .99
  1. 创建你自己的控制生物,带有关节和肢体。一个好的开始是使用爬行器示例并对其进行修改。

  2. 通过添加新肢体或关节来修改其中一个控制样本。

  3. 修改 Walker 控制示例,为代理添加武器和目标。你需要结合 Walker 和 Reacher 示例的元素。

  4. 运行修改过num_epochbatch_size参数的 VisualHallwayLearning 示例场景。结果如你所预期吗?

随着我们深入本书,这些练习可能会变得越来越繁琐,特别是在旧的、较慢的系统上运行时。然而,理解这些参数如何影响代理的训练是很重要的。

与深度学习和强化学习(RL)从业者交流时,他们常常将训练的微妙差别比作做饭的好与坏之间的区别。一个好的厨师可能能做出美味的菜肴,并提供一顿完全可以接受的饭菜,但只有伟大的厨师,凭借他们对细节的关注,才能做出一顿让你难以忘怀的杰出餐点。

总结

在这一章中,我们深入研究了强化学习(RL)的内部运作,通过理解基于模型和非基于模型和/或基于策略的算法之间的差异。正如我们所学,Unity ML-Agents 使用 PPO 算法,这是一种强大而灵活的策略学习模型,在训练控制任务时表现出色,有时这种任务被称为马拉松式强化学习。了解了更多基础知识后,我们跳入了其他形式的强化学习改进,例如 Actor-Critic(演员-评论家)或优势训练,并了解了 ML-Agents 所支持的选项。接下来,我们探讨了 PPO 的演变以及它的前身 TRPO 算法,了解了它们如何在基本层面上运作以及如何影响训练。在这一部分,我们学习了如何修改其中一个控制示例,创建 Reacher 手臂上的新关节。最后,我们通过调整超参数,探讨了如何改进多代理策略训练。

我们已经涵盖了强化学习(RL)的许多方面和细节,以及代理如何训练,但训练中最重要的部分——奖励,我们将留到下一章。在下一章中,我们将探讨奖励、奖励函数,以及奖励如何被模拟。

第九章:奖励和强化学习

奖励是强化学习的一个基本概念,且易于理解。毕竟,我们在一定程度上是通过奖励来训练和教导他人——比如训练狗或小孩。将奖励或reward函数实现到仿真中可能会有些困难,并且容易出现很多试错过程。这也是为什么我们要等到更后面、更高级的章节再讨论奖励、构建reward函数以及奖励辅助方法,如课程学习、反向播放、好奇心学习和模仿学习/行为克隆。

下面是本章将要讲解概念的简要总结:

  • 奖励和reward函数

  • 奖励的稀疏性

  • 课程学习

  • 理解反向播放

  • 好奇心学习

虽然本章内容较为高级,但它也是至关重要的一章,不容错过。同样,许多顶级的强化学习示范,如 DeepMind 的 AlphaStar,都是利用本章中的高级算法来教导代理执行以前认为不可能完成的任务。

奖励和奖励函数

我们常常面对奖励学习或训练的先入为主的观念,即任务完成后会有奖励,可能是好的也可能是坏的。虽然这种基于奖励的强化学习概念对于单一动作的任务完全适用,比如之前提到的经典多臂赌博机问题,或是教狗做一个动作,但请记住,强化学习实际上是关于代理通过一系列动作预测未来奖励,进而学习动作的价值。在每个动作步骤中,当代理不再进行探索时,它会根据所感知到的最佳奖励来决定下一步的行动。并不是总能清楚地知道这些奖励应该在数值上表示什么,以及这一点有多重要。因此,通常需要绘制出一组简单的reward函数,描述我们希望代理训练的学习行为。

让我们打开 Unity 编辑器中的 GridWorld 示例,并学习如何创建一组reward函数和映射,描述该训练过程,如下所示:

  1. 从 Assets | ML-Agents | Examples | GridWorld | Scenes 文件夹中打开GridWorld示例。

  2. 在层级视图中选择 trueAgent 对象,然后将代理的脑部(Grid Agent | Brain)切换为 GridWorldLearning。

  3. 选择 GridAcademy 并将 Grid Academy | Brains | Control 选项设置为启用。

  4. 选择并禁用场景中的主摄像机。这样代理的摄像机将成为主要摄像机,且我们可以通过它来查看场景。

  5. 打开并准备一个 Python 或 Anaconda 窗口用于训练。如果需要记起如何操作,请查看前几章或 Unity 文档。

  6. 保存场景和项目。

  7. 使用以下命令在 Python/Anaconda 窗口中启动示例进行训练:

mlagents-learn config/trainer_config.yaml --run-id=gridworld --train
  1. 你会很快感受到这个示例的训练速度。记住,代理训练如此快速的主要原因是状态空间非常小;在这个例子中是 5x5。以下截图展示了模拟运行的一个例子:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/9c4ababe-df7d-4469-8711-394029344d77.png

GridWorld 示例运行在 5x5 网格上

  1. 运行示例直到完成。即使在较老的系统上运行,也不会花费太长时间。

注意,当代理学习将立方体放置到绿色+号上时,代理迅速从负奖励转变为正奖励。然而,你是否注意到代理从一个负的平均奖励开始训练?代理初始时的奖励值为零,所以让我们来看看负奖励是从哪里来的。在接下来的章节中,我们将通过查看代码来了解如何构建 reward 函数。

构建奖励函数

构建 reward 函数可以非常简单,像这个例子一样,或者非常复杂,正如你可能想象的那样。虽然在训练这些示例时这一步是可选的,但在你构建自己的环境时几乎是必需的。它还可以帮助你发现训练中的问题,以及提高或简化训练的方法。

打开 Unity 编辑器,并按照本练习构建这些示例 reward 函数:

  1. 在层级窗口中选择 trueAgent 对象,然后点击网格代理组件旁边的目标图标。

  2. 从联系菜单中选择编辑脚本。

  3. 在脚本在编辑器中打开后,向下滚动到 AgentAction 方法,如下所示:

public override void AgentAction(float[] vectorAction, string textAction)
{
  AddReward(-0.01f);
  int action = Mathf.FloorToInt(vectorAction[0]);

  ... // omitted for brevity

  Collider[] blockTest = Physics.OverlapBox(targetPos, new Vector3(0.3f, 0.3f, 0.3f));
  if (blockTest.Where(col => col.gameObject.CompareTag("wall")).ToArray().Length == 0)
  {
    transform.position = targetPos;
    if (blockTest.Where(col => col.gameObject.CompareTag("goal")).ToArray().Length == 1)
    {
      Done();
      SetReward(1f);
    }
    if (blockTest.Where(col => col.gameObject.CompareTag("pit")).ToArray().Length == 1)
    {
      Done();
      SetReward(-1f);
    }
  }
}
  1. 我们要关注高亮显示的行,AddRewardSetReward

    • AddReward(-.1f):这一行表示步骤奖励。代理每走一步都会受到负奖励。这也是我们看到代理展示负奖励,直到它找到正奖励的原因。

    • SetReward(1f):这是代理收到的最终正奖励,并且设置为最大值 1。在这类训练场景中,我们通常使用从 -1 到 +1 的奖励范围。

    • SetReward(-1f):这是死亡深渊奖励,也是最终的负奖励。

  2. 使用之前的每个语句,我们可以将这些映射到 reward 函数,如下所示:

  3. 这里需要注意的一点是,AddReward 是增量奖励,而 SetReward 设置最终值。所以,代理只有通过达到最终目标才能看到正奖励。

通过映射这些reward函数,我们可以看到,代理要想学习到正奖励,唯一的办法就是找到目标。这就是为什么代理一开始会收到负奖励的原因,代理本质上是先学会避免浪费时间或行动,直到它随机遇到目标。从那时起,代理可以根据之前获得的正奖励快速给状态赋值。问题在于,代理首先需要遇到正奖励,然后我们才能开始实际的训练。我们将在下一节讨论这个问题。

奖励稀疏

我们称代理没有得到足够的正奖励,或者根本没有正奖励的情况为奖励稀疏。展示奖励稀疏如何发生的最简单方法是通过示例,幸运的是,GridWorld 示例能够轻松地为我们演示这一点。打开编辑器中的 GridWorld 示例,并按照本练习操作:

  1. 打开上一个练习中我们离开的 GridWorld 示例场景。为了本次练习的目的,最好已经将原始示例训练完成。GridWorld 是一个紧凑的小示例,训练速度快,是测试基本概念甚至超参数的绝佳场所。

  2. 选择 GridAcademy 并将 Grid Academy | Reset Parameters | gridSize 更改为25,如下图所示:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/2fe193fd-2c8a-4336-ac0b-1d21aa78cf42.png

设置 GridAcademy 的 gridSize 参数

  1. 保存场景和项目。

  2. 从你的 Python/Anaconda 窗口使用以下命令启动示例进行训练:

mlagents-learn config/trainer_config.yaml --run-id=grid25x25 --train
  1. 这将启动示例,并且假设你仍然将 agentCam 作为主摄像机,你应该在游戏窗口中看到以下内容:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/a0dfe420-200c-4c5c-bd83-ccb2c2ce722f.png

具有 25x25 网格大小的 GridWorld

  1. 我们已经将游戏的空间从 5x5 的网格扩展到 25x25 的网格,使得代理随机找到目标(+)符号变得更加困难。

  2. 你会很快注意到,在几轮训练后,代理在某些情况下的表现非常差,甚至报告的平均奖励小于-1。更糟糕的是,代理可能会继续这样训练很长时间。事实上,代理可能在 100、200、1,000 次甚至更多的迭代中都无法发现奖励。现在,这看起来像是状态的问题,从某种角度来看,你可能会这么认为。然而,记住,我们给代理的输入状态始终是相同的摄像机视图,84x84 像素的状态图像,我们并没有改变这一点。因此,在这个示例中,可以认为策略 RL 算法中的状态保持不变。因此,解决问题的最佳方法是增加奖励。

  3. 在 Python/Anaconda 窗口中通过输入Ctrl + C停止训练示例。为了公平起见,我们将平等地增加目标和死亡的奖励数量。

  4. 返回编辑器,选择 GridAcademy 并增加 Grid Academy | Reset Parameters 组件属性中的 numObstacles 和 numGoals,如下所示:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/a0980bb1-9142-4054-8d0d-873996c266bb.png

更新障碍物和目标的数量

  1. 保存场景和项目。

  2. 使用以下代码启动训练会话:

mlagents-learn config/trainer_config.yaml --run-id=grid25x25x5 --train
  1. 这表示我们正在用五倍数量的障碍物和目标运行示例。

  2. 让智能体训练 25,000 次迭代,并观察性能的提高。让智能体训练直到完成,并将结果与我们的第一次训练进行比较。

奖励稀疏性问题通常更常见于离散动作任务中,例如 GridWorld/Hallway 等,因为reward函数通常是绝对的。在连续学习任务中,reward函数往往更为渐进,通常通过达到某个目标的进展来衡量,而不仅仅是目标本身。

通过增加障碍物和目标的数量——即负奖励和正奖励——我们能够更快速地训练智能体,尽管你可能会看到非常不稳定的训练周期,而且智能体的表现永远不会真正达到最初的水平。实际上,训练在某个点可能会发生偏离。造成这种情况的原因部分是由于其有限的视野,并且我们仅部分解决了稀疏奖励问题。当然,我们可以通过简单地增加目标和障碍物的数量来解决这个稀疏奖励问题。你可以返回并尝试将障碍物和奖励的数量设置为 25,看看能否得到更稳定的长期结果。

当然,在许多强化学习问题中,增加奖励的数量并不是一个选项,我们需要寻找更巧妙的方法,正如我们在下一节中将看到的那样。幸运的是,很多方法在非常短的时间内相继出现,旨在解决稀疏或困难奖励的问题。Unity 作为领先者,迅速采纳并实施了多种方法,其中我们将讨论的第一个方法叫做课程学习(Curriculum Learning),我们将在下一节中详细讨论。

课程学习

课程学习允许智能体通过逐步提升reward函数来逐步学习一个困难的任务。当奖励保持绝对时,智能体以更简单的方式找到或实现目标,从而学习到奖励的目的。然后,随着训练的进行和智能体的学习,获得奖励的难度增加,这反过来迫使智能体进行学习。

当然,Unity 有一些此类示例,我们将在接下来的练习中查看WallJump示例,了解如何设置一个课程学习样本:

  1. 打开 WallJump 场景,路径为 Assets | ML-Agents | Examples | WallJump | Scenes 文件夹。

  2. 在层次窗口中选择 Academy 对象。

  3. 如下所示,点击 Wall Jump Academy | Brains | Control 参数中的两个控制选项:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/561b1d1e-5aca-4ddf-8e80-565983988cad.png

设置多个大脑进行学习

  1. 这个示例使用多个大脑来更好地按任务分离学习。实际上,所有大脑将同时进行训练。

  2. 课程学习使用第二个配置文件来描述代理将经历的课程或学习步骤。

  3. 打开ML-Agents/ml-agents/config/curricul/wall-jump文件夹。

  4. 在文本编辑器中打开SmallWallJumpLearning.json文件。文件如下所示:

      {
          "measure" : "progress",
          "thresholds" : [0.1, 0.3, 0.5],
          "min_lesson_length": 100,
          "signal_smoothing" : true, 
          "parameters" : 
          {
              "small_wall_height" : [1.5, 2.0, 2.5, 4.0]
          }
      }
  1. 这个 JSON 文件定义了 SmallWallJumpLearning 大脑作为其课程或学习步骤的一部分所采取的配置。这些参数的定义在 Unity 文档中有详细说明,但我们将按照文档中的参数进行查看:

    • measure 衡量学习进展和课程进度的指标:

      • reward – 使用衡量标准接收到的奖励。

      • progress – 使用步骤与最大步骤数的比率。

    • thresholds(浮动数组)– 衡量标准值的点,在这些点上课程应当提升。

    • min_lesson_length(整数) 在课程可以更改之前应完成的最小回合数。如果设置了奖励衡量标准,则将使用最近min_lesson_length回合的平均累积奖励来决定是否应更改课程。必须是非负数。

  2. 通过阅读此文件我们可以看到,设定了三个课程,通过progressmeasure来定义,progress由回合数来表示。回合边界定义为.1或 10%,.3或 30%,和.5或 50%总回合数。每个课程我们都通过边界定义参数,在这个例子中,参数是small_wall_height,第一个课程的边界是1.52.0,第二个课程的边界是2.02.5,第三个课程的边界是2.54.0

  3. 打开一个 Python/Anaconda 窗口并准备好进行训练。

  4. 使用以下命令启动训练会话:

mlagents-learn config/trainer_config.yaml --curriculum=config/curricula/wall-jump/ --run-id=wall-jump-curriculum --train
  1. 高亮的额外部分将文件夹添加到辅助课程配置中。

  2. 你需要等待至少一半的完整训练步骤才能看到所有三个训练阶段。

这个例子介绍了一种我们可以用来解决稀疏或难以获得奖励问题的技术。在接下来的部分中,我们将讨论一种专门的课程训练形式,叫做 Backplay。

理解 Backplay

2018 年末,Cinjon Resnick 发布了一篇创新论文,题为Backplay: Man muss immer umkehren,(arxiv.org/abs/1807.06919),其中介绍了一种叫做 Backplay 的课程学习改进方法。基本前提是,训练时将代理从目标开始,然后逐步将其移回。这种方法可能并不适用于所有情况,但我们将使用这种方法结合课程训练,看看如何在以下练习中改进 VisualHallway 示例:

  1. 从 Assets | ML-Agents | Examples | Hallway | Scenes 文件夹中打开 VisualHallway 场景。

  2. 确保场景重置为默认的起始点。如果需要,可以再次从 ML-Agents 拉取源代码。

  3. 使用 VisualHallwayLearning 大脑设置学习场景,并确保智能体仅使用默认的 84x84 视觉观察。

  4. 选择 Academy 对象,在检查器窗口中添加一个新的 Hallway Academy | Reset Parameter,命名为distance,如下所示:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/cf28b7dd-01eb-4697-bab2-15e476686232.png

在 Academy 上设置新的重置参数

  1. 你可以将重置参数用于不仅仅是课程学习,因为它们可以帮助你轻松配置编辑器中的训练参数。我们在这里定义的参数将设置智能体距离目标区域的距离。此示例旨在展示 Backplay 的概念,为了正确实现它,我们需要将智能体移动到目标的正前方——但我们暂时不进行这一操作。

  2. 选择 VisualHallwayArea | Agent,并在你喜欢的代码编辑器中打开 Hallway Academy 脚本。

  3. 向下滚动到AgentReset方法,并将顶部代码行调整为如下所示:

public override void AgentReset()
{
  float agentOffset = academy.resetParameters["distance"];
  float blockOffset = 0f;
  // ... rest removed for brevity
  1. 这一行代码将调整智能体的起始偏移量,以适应现在预设的 Academy 重置参数。同样,当 Academy 在训练过程中更新这些参数时,智能体也会看到更新的值。

  2. 保存文件并返回编辑器。编辑器会重新编译你的代码更改,并告知你一切是否正常。如果控制台出现红色错误,通常意味着有编译错误,可能是由语法错误引起的。

  3. 打开一个准备好的 Python/Anaconda 窗口,并运行以下命令来开始训练:

mlagents-learn config/trainer_config.yaml --run-id=vh_backplay --train
  1. 这将以常规模式运行会话,不使用课程学习,但它会将智能体的起始位置调整得更接近目标区域。让这个示例运行,并观察智能体在如此接近目标时的表现如何。

让训练运行一段时间,并观察与原始训练的区别。你会注意到的一点是,智能体现在不自觉地跑向奖励区域,这正是我们所期望的。接下来我们需要实现的是课程学习部分,即智能体在学习如何在下一部分找到奖励的过程中逐渐向后移动。

通过课程学习实现 Backplay

在上一节中,我们实现了 Backplay 的第一部分,即让智能体起始于目标附近或非常接近目标的位置。接下来我们需要完成的部分是使用课程学习(Curriculum Learning)将智能体逐步移回到它的预定起始点。请再次打开 Unity 编辑器并进入 VisualHallway 场景,按照以下步骤操作:

  1. 使用文件浏览器或命令行打开ML-Agents/ml-agents/config文件夹。

  2. 创建一个名为hallway的新文件夹并进入该文件夹。

  3. 打开文本编辑器或在新目录下创建一个名为 VisualHallwayLearning.json 的新 JSON 文本文件。JavaScript 对象表示法JSON)用于描述 JavaScript 中的对象,它也成为了一种配置设置的标准。

  4. 在新文件中输入以下 JSON 文本:

{
    "measure" : "rewards",
    "thresholds" : [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7],
    "min_lesson_length": 100,
    "signal_smoothing" : true, 
    "parameters" : 
    {
        "distance" : [12, 8, 4, 2, -2, -4, -8, -12]
    }
  1. 这个配置文件定义了我们将在 Backplay 上训练代理的课程。该文件定义了一个 measure,包括 rewardsthresholds,用于确定代理何时会晋级到下一个训练阶段。当奖励阈值达到最低 100 步长时,训练将进入下一个 distance 参数。注意,我们如何将距离参数定义为 12,表示距离目标较近,然后逐渐减少。当然,你也可以创建一个函数来映射不同的范围值,但这部分留给你自己完成。

  2. 编辑完成后保存文件。

  3. 从 Python/Anaconda 窗口启动训练会话,使用以下命令:

mlagents-learn config/trainer_config.yaml --curriculum=config/curricula/hallway/ --run-id=hallway-curriculum --train
  1. 训练开始后,注意 Python/Anaconda 窗口中课程如何被设置,如下图所示:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/6fe16960-1238-46f1-ba9e-737ee9c2831b.png

观看训练中课程参数的设置

  1. 等待代理训练完成,看看它能在会话结束前完成多少个训练级别。

现在,我们需要澄清的一点是,这个示例更像是一个创新示例,而不是 Backplay 的真实示例。实际的 Backplay 是将代理放在目标位置,然后向后工作。在这个示例中,我们几乎将代理放置在目标位置并向后工作。这个差异是微妙的,但到现在为止,希望你能理解,从训练的角度来看,这可能是有意义的。

好奇心学习

到目前为止,我们只考虑了代理在环境中可能获得的外部奖励。例如,走廊示例当代理到达目标时会给出 +1 的外部奖励,而当它达到错误的目标时会给予 -1 的外部奖励。然而,像我们这样的真实动物其实可以基于内在动机进行学习,或者使用内在的 reward 函数。一个很好的例子是一个婴儿(猫、人类,或者其他任何东西),它通过玩耍有明显的自然动机去保持好奇心。玩耍的好奇心为婴儿提供了一个内在或固有的奖励,但实际上,这一行为本身会带来负面的外部奖励。毕竟,婴儿在消耗能量,这是一个负面的外部奖励,但它依然会继续玩耍,从而学习更多关于环境的一般信息。这反过来使它能够探索更多的环境,并最终达到一些非常困难的目标,比如打猎或上班。

这种内部或内在奖励建模属于强化学习(RL)的一类子类别,称为动机强化学习(Motivated Reinforcement Learning)。正如你可以想象的,这种学习模式在游戏中有着巨大的应用前景,从创建 NPC 到构建更具可信度的对手,这些对手实际上会受到某种性格特征或情绪的驱动。试想一下,拥有一个能生气甚至表现出同情心的电脑对手?当然,我们离这个目标还很远,但在此期间,Unity 已经添加了一个内在奖励系统,用于模拟智能体的好奇心,这就是所谓的好奇心学习。

好奇心学习CL)最初由加利福尼亚大学伯克利分校的研究人员在一篇名为由**自监督预测驱动的好奇心探索的论文中提出,您可以在pathak22.github.io/noreward-rl/中找到该论文。论文进一步描述了一个使用正向和反向神经网络解决稀疏奖励问题的系统。研究人员将该系统称为内在好奇心模块ICM),旨在将其作为其他强化学习(RL)系统之上的一层或模块使用。这正是 Unity 所做的,他们将其作为模块添加到 ML-Agents 中。

Unity 的首席研究员 Arthur Juliani 博士在他们的实现上有一篇很棒的博客文章,您可以在blogs.unity3d.com/2018/06/26/solving-sparse-reward-tasks-with-curiosity/找到。

ICM 通过使用反向神经网络工作,该网络通过当前和下一个观察到的智能体状态进行训练。它使用编码器对两种状态之间的动作进行预测编码,当前状态和下一个状态。然后,正向网络在当前观察和动作的基础上进行训练,将其编码为下一个观察状态。接着,从反向和正向模型中分别计算实际和预测编码之间的差异。在这种情况下,差异越大,惊讶感越强,内在奖励也就越多。以下是从 Arthur Juliani 博士的博客中提取的图示,描述了这一过程如何工作:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/7c0c6c7f-8ad5-4fa4-a258-c0327d8e0ddf.png

好奇心学习模块的内部工作原理

该图表显示了两个模型和层的表示,分别是蓝色的正向和反向,蓝色线条表示网络流动,绿色框表示内在模型计算,而奖励输出则以绿色虚线的形式呈现。

好了,理论部分讲得差不多了,接下来是时候看看这个 CL 如何在实践中运作了。幸运的是,Unity 有一个非常完善的环境,展示了这个名为 Pyramids 的新模块。让我们打开 Unity 并按照接下来的练习查看这个环境的实际效果:

  1. 打开 Assets | ML-Agents | Examples | Pyramids | Scenes 文件夹中的 Pyramid 场景。

  2. 在层级视图窗口中选择 AreaPB(1)到 AreaPB(15),然后在检查器窗口中禁用这些对象。

  3. 离开玩家模式下的场景。第一次,我们希望你自己来体验这个场景并弄清楚目标。即使你已经阅读过博客或玩过这个场景,也请再试一次,不过这次需要思考应该设置哪些奖励函数。

  4. 在编辑器中按下播放按钮,并开始以玩家模式玩游戏。如果你之前没有玩过游戏或者不了解前提,不要感到惊讶,如果你花了一段时间才能解开这个谜题。

对于那些没有提前阅读或玩过的同学,下面是前提。场景开始时,代理被随机放置在一个有多个房间的区域,里面有石质金字塔,其中一个有开关。代理的目标是激活开关,然后生成一个沙箱金字塔,上面放着一个大金盒子。开关在被激活后会从红色变为绿色。金字塔出现后,代理需要把金字塔推倒并取回金盒子。这并不是最复杂的谜题,但确实需要一些探索和好奇心。

假设我们尝试用一组reward函数来模拟这种好奇心或探索的需求。我们将需要一个用于激活按钮的reward函数,一个用于进入房间的函数,一个用于推倒积木的函数,当然还有一个用于获得金盒子的函数。然后我们需要确定每个目标的价值,或许可以使用某种形式的逆向强化学习IRL)。然而,使用好奇心学习,我们可以只为获取盒子的最终目标创建奖励函数(+1),并可能设置一个小的负步长目标(.0001),然后利用内在的好奇心奖励让代理学习其余步骤。这个技巧相当巧妙,我们将在下一部分看到它是如何工作的。

好奇心内在模块正在运行

在我们理解金字塔任务的难度后,我们可以继续在接下来的练习中训练具有好奇心的代理:

  1. 在编辑器中打开金字塔场景。

  2. 在层次视图中选择 AreaRB | 代理对象。

  3. 将金字塔代理 | 大脑切换为 PyramidsLearning 大脑。

  4. 在层次视图中选择 Academy 对象。

  5. 启用 Academy | Pyramid Academy | 大脑 | 控制属性中的控制选项,如下截图所示:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/31a355e2-ae0d-4f6f-8635-5f620ace0450.png

设置 Academy 为控制模式

  1. 打开一个 Python 或 Anaconda 控制台,并为训练做准备。

  2. 打开位于ML-Agents/ml-agents/config文件夹中的trainer_config.yaml文件。

  3. 向下滚动到PyramidsLearning配置部分,如下所示:

      PyramidsLearning:
          use_curiosity: true
          summary_freq: 2000
          curiosity_strength: 0.01
 curiosity_enc_size: 256
          time_horizon: 128
          batch_size: 128
          buffer_size: 2048
          hidden_units: 512
          num_layers: 2
          beta: 1.0e-2
          max_steps: 5.0e5
          num_epoch: 3
  1. 这里有三个新的配置参数,已用粗体标出:

    • use_curiosity:将其设置为true以使用该模块,但默认情况下通常为false

    • curiosity_strength:这是代理对好奇心内在奖励与外在奖励的重视程度。

    • curiosity_enc_size:这是我们将网络压缩到的编码层的大小。如果你回想一下自编码器,你会发现 256 的大小相当大,但也要考虑你可能要编码的状态空间或观察空间的大小。

保持参数为已设置的值。

  1. 使用以下命令启动训练会话:
 mlagents-learn config/trainer_config.yaml --run-id=pyramids --train

虽然这次训练会话可能需要一些时间,但观察代理如何探索也许会很有趣。即使在当前的设置下,使用仅一个训练区域,你也许能够看到代理在几次迭代后解决这个难题。

由于 ICM 是一个模块,它可以快速激活到任何我们想要查看效果的其他示例中,这也是我们在下一部分将要做的。

在 Hallway/VisualHallway 上尝试 ICM

与我们训练的代理类似,我们通过试错学习得很好。这也是我们为何要不断练习那些非常困难的任务,比如跳舞、唱歌或演奏乐器。强化学习(RL)也不例外,要求从业者通过试验、错误和进一步探索的严峻考验中学习其中的技巧。因此,在接下来的练习中,我们将把 Backplay(课程学习)和好奇心学习结合在一起,应用到我们熟悉的 Hallway 场景中,看看它会带来什么效果,如下所示:

  1. 打开 Hallway 或 VisualHallway 场景(你可以选择其一),如我们上次所留,启用了课程学习并设置为模拟 Backplay。

  2. 打开trainer_config.yaml配置文件,位置在ML-Agents/ml-agents/config文件夹中。

  3. 向下滚动至HallwayLearningVisualHallwayLearning脑网络配置参数,并添加以下附加配置行:

HallwayLearning:
    use_curiosity: true
 curiosity_strength: 0.01
 curiosity_enc_size: 256
    use_recurrent: true
    sequence_length: 64
    num_layers: 2
    hidden_units: 128
    memory_size: 256
    beta: 1.0e-2
    gamma: 0.99
    num_epoch: 10
    buffer_size: 1024
    batch_size: 1000
    max_steps: 5.0e5
    summary_freq: 1000
    time_horizon: 64
  1. 这将启用本示例的好奇心模块。我们使用与上一个金字塔示例相同的好奇心设置。

  2. 确保这个示例已为我们在该部分中配置的课程 Backplay 做好准备。如果需要,回去复习那部分内容,并在继续之前将其能力添加到此示例中。

这可能需要你创建一个新的课程文件,使用与我们之前相同的参数。记住,课程文件需要与其所使用的脑网络名称相同。

  1. 打开一个准备好进行训练的 Python/Anaconda 窗口,并使用以下命令开始训练:
mlagents-learn config/trainer_config.yaml --curriculum=config/curricula/hallway/ --run-id=hallway_bp_cl --train
  1. 让训练运行直到完成,因为结果可能会很有趣,并展示叠加学习增强对外部和内部奖励的一些强大可能性。

这个练习展示了如何通过课程学习模拟 Backplay 以及好奇心学习为学习过程添加代理动机来运行一个代理。正如你可以想象的那样,内在奖励学习和整个动机强化学习领域可能会对我们的深度强化学习(DRL)带来一些有趣的进展和改进。

在下一节中,我们将回顾一些有助于你学习这些概念的练习。

练习

虽然你阅读本书的动机可能各不相同,但希望到目前为止你已经能够意识到亲自实践的价值。正如以往,我们呈现这些练习供你享受与学习,并希望你在完成它们时玩得开心:

  1. 选择另一个使用离散动作的示例场景,并编写相应的奖励函数。是的,这意味着你需要打开代码并查看它。

  2. 选择一个连续动作场景,并尝试为其编写奖励函数。虽然这可能有点困难,但如果你想要构建自己的控制训练智能体,这是必不可少的。

  3. 在我们探索的另一个离散动作示例中添加课程学习。决定如何将训练分解成不同的难度等级,并创建控制训练进程的参数。

  4. 在一个连续动作示例中添加课程学习。这更具挑战性,你可能希望先完成第二个练习。

  5. 在 Hallway 环境中实际实现 Backplay,通过将智能体从目标位置开始,并在智能体训练过程中,使用课程学习将其移动回期望的起始位置。

  6. 在另一个离散动作示例中实现 Backplay,并查看它对训练的影响。

  7. 在 VisualPyramids 示例中实施好奇心学习,并注意训练中的差异。

  8. 在一个连续动作示例中实施好奇心学习,并观察它对训练的效果。这是你预期的吗?

  9. 禁用 Pyramids 示例中的好奇心学习,观察这对智能体训练的影响。

  10. 想一想,如何将 Backplay 添加到 VisualPyramids 示例中。如果你真的实现了它,你将获得额外的积分。

如你所见,随着我们逐步深入本书,练习的难度也在增加。记住,即使只完成一两个练习,也会对你所掌握的知识产生重要影响。

摘要

在这一章,我们探讨了强化学习(RL)中的一个基础组成部分,那就是奖励。我们了解到,在构建训练环境时,最好定义一套奖励函数,让智能体遵循这些规则。通过理解这些方程式,我们能够更好地理解频繁或稀疏的奖励如何对训练产生负面影响。接着,我们介绍了几种方法,其中第一种叫做课程学习(Curriculum Learning),可以用来缓解或逐步推进智能体的外部奖励。之后,我们探讨了另一种技术,叫做反向播放(Backplay),它使用反向播放技术和课程训练来增强智能体的训练。最后,我们介绍了内在奖励或内在强化学习的概念。我们还了解到,最初开发的内在奖励系统是为了赋予智能体好奇心的动机。我们查看了如何在一些例子中应用好奇心学习,并通过课程学习将其与反向播放结合使用。

在下一章,我们将探索更多奖励辅助解决方案,形式为模仿学习和迁移学习,在这一章中,我们将学习如何将人类的游戏体验映射到一种叫做模仿学习或行为克隆的学习形式。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值