From 0da710a549424925f2b41598d0a6f5ffb78a53d4 Mon Sep 17 00:00:00 2001 From: swartz-k <49771587+swartz-k@users.noreply.github.com> Date: Wed, 13 Oct 2021 04:45:49 +0800 Subject: [PATCH] [zh-cn] Chapter 8 (#397) Signed-off-by: swartz-k <9215868@gmail.com> --- .../1-QLearning/translations/README.zh-cn.md | 320 ++++++++++++++++ .../translations/assignment.zh-cn.md | 27 ++ .../2-Gym/translations/README.zh-cn.md | 341 ++++++++++++++++++ .../2-Gym/translations/assignment.zh-cn.md | 47 +++ 4 files changed, 735 insertions(+) create mode 100644 8-Reinforcement/1-QLearning/translations/README.zh-cn.md create mode 100644 8-Reinforcement/1-QLearning/translations/assignment.zh-cn.md create mode 100644 8-Reinforcement/2-Gym/translations/README.zh-cn.md create mode 100644 8-Reinforcement/2-Gym/translations/assignment.zh-cn.md diff --git a/8-Reinforcement/1-QLearning/translations/README.zh-cn.md b/8-Reinforcement/1-QLearning/translations/README.zh-cn.md new file mode 100644 index 00000000..735d78b2 --- /dev/null +++ b/8-Reinforcement/1-QLearning/translations/README.zh-cn.md @@ -0,0 +1,320 @@ +# 强化学习和 Q-Learning 介绍 + +![机器学习中的强化学习总结](../../../sketchnotes/ml-reinforcement.png) +> 作者 [Tomomi Imura](https://www.twitter.com/girlie_mac) + +强化学习涉及三个重要概念:代理、一些状态和每个状态的一组动作。通过在指定状态下执行一个动作,代理会得到奖励。想象一下电脑游戏超级马里奥。你是马里奥,你在一个游戏关卡中,站在悬崖边上。在你上面是一枚硬币。你是马里奥,在游戏关卡里,在特定位置......这就是你的状态。向右移动一步(一个动作)会让跌下悬崖,而且会得到一个低分。然而,按下跳跃按钮会让你活下来并得分。这是一个积极的结果,它会给你一个积极、正向的分数。 + +通过使用强化学习和模拟器(游戏),你可以学习如何玩游戏以最大化奖励,既能够活下去还可以获得尽可能多的积分。 + +[![强化学习简介](https://img.youtube.com/vi/lDq_en8RNOo/0.jpg)](https://www.youtube.com/watch?v=lDq_en8RNOo) + +> 🎥 点击上图观看 Dmitry 讨论强化学习 + +## [课前测验](https://white-water-09ec41f0f.azurestaticapps.net/quiz/45/) + +## 先决条件和设置 + +在本课中,我们将用 Python 代码做一些试验。你应该能够在你的计算机上或云中的某个地方运行本课程中的 Jupyter Notebook 代码。 + +你可以打开[课本笔记本](../notebook.ipynb) 并通过学习本课进行编译、运行。 + +> **注意:** 如果你是从云端打开此代码,你还需要获取笔记本代码中使用的 [`rlboard.py`](../rlboard.py) 文件。将其添加到与笔记本相同的目录中。 + +## 介绍 + +在本课中,我们将探索 **[彼得与狼](https://en.wikipedia.org/wiki/Peter_and_the_Wolf)** 的世界,其灵感来自俄罗斯作曲家 [Sergei Prokofiev](https://en.wikipedia.org/wiki/Sergei_Prokofiev)的音乐童话。我们将使用**强化学习**让彼得探索他的环境,收集美味的苹果并避免遇到狼。 + +**强化学习**(RL)是一种学习技术,它允许我们通过运行许多实验来学习**代理**在某些**环境**中的最佳行为。这种环境中的代理应该有一些**目标**,由**奖励函数**定义。 + +## 环境 + +为简单起见,让我们将 Peter 的世界视为一个大小为 `width` x `height` 的方板,如下所示: + +![彼得的环境](../images/environment.png) + +该板中的每个单元格可以是: + +* **地面**,彼得和其他生物可以在上面行走。 +* **水**,不能在上面行走。 +* **树**或**草**,可以休息的地方。 +* **苹果**,代表彼得希望找到用来喂饱自己的食物。 +* **狼**,这是危险的,应该避免遇到。 + +有一个单独的 Python 模块 [`rlboard.py`](../rlboard.py),其中包含在此环境中工作的代码。因为这段代码对于理解我们的概念并不重要,我们将导入该模块并使用它来创建示例板(代码块 1): + +```python +from rlboard import * + +width, height = 8,8 +m = Board(width,height) +m.randomize(seed=13) +m.plot() +``` + +这段代码会打印一张类似于上面的环境图片。 + +## 行动和策略 + +在我们的例子中,彼得的目标是找到苹果,同时避开狼和其他障碍物。为此,他可以四处走动,直到找到一个苹果。 + +因此,在任何位置,他都可以选择以下动作之一:向上、向下、向左和向右。 + +我们将这些动作定义为字典,并将它们映射到相应的坐标变化对。例如,向右移动 (`R`) 将对应于一对 `(1,0)`。(代码块 2): + +```python +actions = { "U" : (0,-1), "D" : (0,1), "L" : (-1,0), "R" : (1,0) } +action_idx = { a : i for i,a in enumerate(actions.keys()) } +``` + +综上所述,本场景的策略和目标如下: + +- 我们的代理(彼得)的**策略**由一个函数定义,它返回任何给定状态下的动作。在我们的例子中,问题的状态由棋盘表示,包括玩家的当前位置。 + +- **目标**,强化学习的目的是学习一个好的策略,使我们能够有效地解决问题。但是,作为基准,让我们考虑称为 **随机走动** 的最简单策略。 + +## 随机走动 + +让我们首先通过实施随机走动策略来解决我们的问题。通过随机走动,我们从允许的动作中随机选择下一个动作,直到我们找到苹果(代码块 3)。 + +1. 使用以下代码实现随机走动: + + ```python + def random_policy(m): + return random.choice(list(actions)) + + def walk(m,policy,start_position=None): + n = 0 # number of steps + # set initial position + if start_position: + m.human = start_position + else: + m.random_start() + while True: + if m.at() == Board.Cell.apple: + return n # success! + if m.at() in [Board.Cell.wolf, Board.Cell.water]: + return -1 # eaten by wolf or drowned + while True: + a = actions[policy(m)] + new_pos = m.move_pos(m.human,a) + if m.is_valid(new_pos) and m.at(new_pos)!=Board.Cell.water: + m.move(a) # do the actual move + break + n+=1 + + walk(m,random_policy) + ``` + + 对 `walk` 的调用应返回相应路径的长度,该长度可能因每次运行而不同。 + +1. 多次运行该实验(例如 100 次),并打印结果统计信息(代码块 4): + + ```python + def print_statistics(policy): + s,w,n = 0,0,0 + for _ in range(100): + z = walk(m,policy) + if z<0: + w+=1 + else: + s += z + n += 1 + print(f"Average path length = {s/n}, eaten by wolf: {w} times") + + print_statistics(random_policy) + ``` + + 请注意,一条路径的平均长度约为 30-40 步,考虑到到最近苹果的平均距离约为 5-6 步,这一数字相当大。 + + 你还可以看到 Peter 在随机走动过程中的运动情况: + + ![彼得的随机走动](../images/random_walk.gif) + +## 奖励函数 + +为了使我们的策略更加智能,我们需要了解哪些动作比其他动作 "更好"。为此,我们需要定义我们的目标。 + +可以根据**奖励函数**来定义目标,该函数将为每个状态返回一些分数值。数字越大,奖励函数越好。(代码块 5) + +```python +move_reward = -0.1 +goal_reward = 10 +end_reward = -10 + +def reward(m,pos=None): + pos = pos 或 m.human + if not m.is_valid(pos): + return end_reward + x = m.at(pos) + if x==Board.Cell.water or x == Board.Cell.wolf: + return end_reward + if x==Board.Cell.apple: + return goal_reward + return move_reward +``` + +关于奖励函数的一个有趣的事情是,在大多数情况下,*我们只在游戏结束时才得到实质性的奖励*。这意味着我们的算法应该以某种方式记住最终导致积极奖励的"好"步骤,并增加它们的重要性。同样,所有导致不良结果的举动都应该被阻拦。 + +## Q-Learning + +我们将在这里讨论的一种叫做 **Q-Learning** 的算法。在该算法中,策略由称为 **Q-Table** 的函数(或数据结构)定义。它记录了给定状态下每个动作的"优点"。 + +之所以称为 Q-Table,是因为表格或多维数组通常表示起来很方便。由于我们的棋盘尺寸为 "width"x"height",我们可以使用形状为 "width"x"height"x"len(actions) 的 numpy 数组来表示 Q-Table:(代码块6) + +```python +Q = np.ones((width,height,len(actions)),dtype=np.float)*1.0/len(actions) +``` + +请注意,我们将 Q-Table 的所有值初始化为一个相等的值,在我们的例子中为 "-0.25"。这对应于"随机走动"策略,因为每个状态中的所有移动都同样好。我们可以将 Q-Table 传递给 `plot` 函数,以便在板上可视化表格:`m.plot(Q)`。 + +![彼得的环境](../images/env_init.png) + +在每个单元格的中心有一个"箭头",表示首选的移动方向。由于所有方向都相等,因此显示一个点。 + +现在我们需要运行这个程序,探索我们的环境,并学习更好的 Q-Table 值分布,这将使我们能够更快地找到通往苹果的路径。 + +## Q-Learning 的本质:贝尔曼方程 + +一旦我们开始移动,每个动作都会有相应的奖励,即理论上我们可以根据最高的即时奖励来选择下一个动作。但是,在大多数情况,此举不会实现我们到达苹果的目标,因此我们无法立即决定哪个方向更好。 + +> 请记住,重要的不是直接结果,而是我们将在模拟结束时获得的最终结果。 + +为了解释这种延迟奖励,我们需要使用**[动态规划](https://en.wikipedia.org/wiki/Dynamic_programming)** 的原则,它允许我们递归地思考问题。 + +假设我们现在处于状态 *s*,并且我们想要移动到下一个状态 *s'*。通过这样做,我们将收到由奖励函数定义的即时奖励 *r(s,a)*,以及一些未来的奖励。如果我们假设我们的 Q-Table 正确反映了每个动作的“吸引力”,那么在状态 *s'* 我们将选择对应于 *Q(s',a')* 最大值的动作 *a*。因此,我们可以在状态 *s* 获得的最佳未来奖励将被定义为 `max`a'*Q(s',a')*(这里的最大值是在状态 *s'* 时所有可能的动作 *a'* 上计算的)。 + +这给出了 **Bellman 公式**,用于计算状态 *s* 的 Q-Table 值,给定动作 *a*: + + + +这里 γ 是所谓的**折扣因子**,它决定了你应该在多大程度上更喜欢当前的奖励而不是未来的奖励,反之亦然。 + +## 学习算法 + +鉴于上面的等式,我们现在可以为我们的学习算法编写伪代码: + +* 用相同的数字为所有状态和动作初始化 Q-Table Q +* 设置学习率α ← 1 +* 多次重复模拟 + 1. 随机位置开始 + 1. 重复 + 1. 在状态 *s* 选择一个动作 *a* + 2.通过移动到新状态 *s'* 来执行动作 + 3.如果我们遇到游戏结束的情况,或者总奖励太少——退出模拟 + 4. 计算新状态下的奖励 *r* + 5. 根据 Bellman 方程更新 Q-Function: *Q(s,a)* ← *(1-α)Q(s,a)+α(r+γ maxa'Q( s',a'))* + 6. *s* ← *s'* + 7. 更新总奖励并减少 α。 + +## 利用与探索 + +在上面的算法中,我们没有指定在步骤 2.1 中我们应该如何选择一个动作。如果我们随机选择动作,我们会随机**探索**环境,我们很可能会经常死亡以及探索我们通常不会去的区域。另一种方法是**利用**我们已经知道的 Q-Table 值,从而在状态 *s* 选择最佳动作(具有更高的 Q-Table 值)。然而,这将阻止我们探索其他状态,而且我们可能找不到最佳解决方案。 + +因此,最好的方法是在探索和开发之间取得平衡。这可以通过选择状态 *s* 的动作来完成,概率与 Q 表中的值成正比。一开始,当 Q-Table 值都相同时,它将对应于随机选择,但是随着我们对环境的了解越来越多,我们将更有可能遵循最佳路线,同时允许智能体偶尔选择未探索的路径。 + +## Python 实现 + +我们现在准备实现学习算法。在我们这样做之前,我们还需要一些函数来将 Q-Table 中的任意数字转换为相应动作的概率向量。 + +1. 创建一个函数 `probs()`: + + ```python + def probs(v,eps=1e-4): + v = vv.min()+eps + v = v/v.sum() + return v + ``` + + 我们向原始向量添加了一些 `eps`,以避免在初始情况下被 0 除,此时向量的所有分量都相同。 + +通过 5000 次实验运行他们的学习算法,也称为 **epochs**:(代码块 8) + +```python + for epoch in range(5000): + + # Pick initial point + m.random_start() + + # Start travelling + n=0 + cum_reward = 0 + while True: + x,y = m.human + v = probs(Q[x,y]) + a = random.choices(list(actions),weights=v)[0] + dpos = actions[a] + m.move(dpos,check_correctness=False) # we allow player to move outside the board, which terminates episode + r = reward(m) + cum_reward += r + if r==end_reward or cum_reward < -1000: + lpath.append(n) + break + alpha = np.exp(-n / 10e5) + gamma = 0.5 + ai = action_idx[a] + Q[x,y,ai] = (1 - alpha) * Q[x,y,ai] + alpha * (r + gamma * Q[x+dpos[0], y+dpos[1]].max()) + n+=1 +``` + +执行此算法后,应使用定义每个步骤不同动作的吸引力的值更新 Q-Table 。我们可以尝试通过在每个单元格上绘制一个向量来可视化 Q-Table,该向量将指向所需的移动方向。为简单起见,我们画一个小圆圈而不是箭头。 + + + +## 检查策略 + +由于 Q-Table 列出了每个状态下每个动作的"吸引力",因此很容易使用它来定义我们世界中的高效导航。在最简单的情况下,我们可以选择最高 Q-Table 值对应的 action:(代码块9) + +```python +def qpolicy_strict(m): + x,y = m.human + v = probs(Q[x,y]) + a = list(actions)[np.argmax(v)] + return a + +walk(m,qpolicy_strict) +``` + +> 如果你多次尝试上面的代码,你可能会注意到它有时会"挂起",你需要按笔记本中的 STOP 按钮来中断它。发生这种情况是因为可能存在两种状态在最佳 Q 值方面"指向"彼此的情况,在这种情况下,代理最终会在这些状态之间无限期地移动。 + +## 🚀挑战 + +> **任务 1:** 修改 `walk` 函数,将路径的最大长度限制为一定的步数(比如 100),并时不时地观察上面的代码返回值。 + +> **任务 2:** 修改 `walk` 函数,使其不会回到之前已经去过的地方。这将防止 `walk` 循环,但是,代理仍然可能最终"被困"在它无法逃脱的位置。 + +## 导航 + +更好的导航策略是我们在训练期间使用的,它结合了利用和探索。在这个策略中,我们将以一定的概率选择每个动作,与 Q-Table 中的值成比例。这种策略可能仍会导致代理返回到它已经探索过的位置,但是,正如你从下面的代码中看到的,它会导致到达所需位置的平均路径非常短(请记住,`print_statistics` 运行模拟100次):(代码块10) + +```python +def qpolicy(m): + x,y = m.human + v = probs(Q[x,y]) + a = random.choices(list(actions),weights=v)[0] + return a + +print_statistics(qpolicy) +``` + +运行此代码后,你应该获得比以前小得多的平均路径长度,范围为 3-6。 + +## 调查学习过程 + +正如我们已经提到的,学习过程是探索和探索有关问题空间结构的知识之间的平衡。我们已经看到学习的结果(帮助代理找到到达目标的短路径的能力)有所改善,但观察平均路径长度在学习过程中的表现也很有趣: + + + +学习内容可以概括为: + +- **平均路径长度增加**。我们在这里看到的是,起初,平均路径长度增加。这可能是因为当我们对环境一无所知时,我们很可能会陷入糟糕的状态,水或狼。随着我们学习更多并开始使用这些知识,我们可以更长时间地探索环境,但我们仍然不知道苹果在哪里。 + +- **随着我们了解更多,路径长度减少**。一旦我们学习得足够多,代理就更容易实现目标,路径长度开始减少。然而,我们仍然对探索持开放态度,因此我们经常偏离最佳路径,并探索新的选择,使路径比最佳路径更长。 + +- **长度突然增加**。我们在这张图上还观察到,在某个时刻,长度突然增加。这表明该过程的随机性,我们可以在某个时候通过用新值覆盖 Q-Table 系数来"破坏" Q-Table 系数。理想情况下,这应该通过降低学习率来最小化(例如,在训练结束时,我们只调整 Q-Table 很小的一个小值)。 + +总的来说,重要的是要记住学习过程的成功和质量在很大程度上取决于参数,例如学习率、学习率衰减和折扣因子。这些通常称为**超参数**,以区别于我们在训练期间优化的**参数**(例如,Q-Table 系数)。寻找最佳超参数值的过程称为**超参数优化**,它值得一个单独的话题来介绍。 + +## [课后测验](https://white-water-09ec41f0f.azurestaticapps.net/quiz/46/) + +## 作业[一个更真实的世界](assignment.zh-cn.md) diff --git a/8-Reinforcement/1-QLearning/translations/assignment.zh-cn.md b/8-Reinforcement/1-QLearning/translations/assignment.zh-cn.md new file mode 100644 index 00000000..3fedd2ad --- /dev/null +++ b/8-Reinforcement/1-QLearning/translations/assignment.zh-cn.md @@ -0,0 +1,27 @@ +# 一个更真实的世界 + +我们假想,彼得几乎可以一直走动而不会感到疲倦或饥饿。但在一个更真实的世界里,我们需要时不时地坐下来休息,也要吃东西。让我们加入以下规则让我们的世界更真实: + +1. 从一个地方走到另一个地方,彼得失去了**能量**并获得了一些**疲惫**。 +2. 彼得可以通过吃苹果来获得更多的能量。 +3. 彼得可以通过在树下或草地上休息来消除疲惫(即走进有树和草的棋盘位置——绿色的格子) +4. 彼得需要找到并杀死狼 +5. 为了杀死狼,彼得需要有一定级别的能量和疲惫,否则他会输掉这场战斗。 + +## 说明 + +使用原始 [notebook.ipynb](../notebook.ipynb) 笔记本作为解决方案的起点。 + +根据游戏规则修改上面的奖励函数,运行强化学习算法来学习赢得游戏的最佳策略,并在游戏赢/输的数量上将你的算法和随机走动算法进行对比。 + +> **注意**:在你的新世界中,状态更加复杂,除了人体位置还包括疲惫和能量水平。你可以选择将状态表示为一个元组(Board、energy、fatigue),或者为状态定义一个类(你可能还想从 `Board` 派生它),甚至在 [rlboard.py](../rlboard.py)中修改`Board`的源码。 + +在你的解决方案中,请保留负责随机走动策略的代码,并在最后将你的算法与随机走动算法进行比较。 + +> **注意**:你可能需要调整超参数才能使其工作,尤其是 epoch 数。因为游戏的成功(与狼搏斗)是一个罕见的事件,你需要更长的训练时间。 + +## 评判标准 + +| 标准 | 优秀 | 中规中矩 | 仍需努力 | +| -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | +| |笔记本上有新世界规则的定义、Q-Learning 算法和一些文字解释。与随机游走相比,Q-Learning 能够显著改善结果 | 介绍了 Notebook,实现了 Q-Learning 并与随机走动算法相比提高了结果,但不显著;或者 notebook 的文档不完善,代码结构不合理 | 一些试图重新定义世界规则的尝试,但 Q-Learning 算法不起作用,或者奖励函数没有完全定义 | diff --git a/8-Reinforcement/2-Gym/translations/README.zh-cn.md b/8-Reinforcement/2-Gym/translations/README.zh-cn.md new file mode 100644 index 00000000..16d993f8 --- /dev/null +++ b/8-Reinforcement/2-Gym/translations/README.zh-cn.md @@ -0,0 +1,341 @@ +# CartPole Skating + +我们在上一课中一直在解决的问题可能看起来像一个玩具问题,并不真正适用于现实生活场景。事实并非如此,因为许多现实世界的问题也有这种情况——包括下国际象棋或围棋。它们很相似,因为我们也有一个具有给定规则和**离散状态**的板。 +https://white-water-09ec41f0f.azurestaticapps.net/ + +## [课前测验](https://white-water-09ec41f0f.azurestaticapps.net/quiz/47/) + +## 介绍 + +在本课中,我们将把 Q-Learning 的相同原理应用到具有**连续状态**的问题,比如由一个或多个实数给出的状态。我们将处理以下问题: + +> **问题**:如果彼得想要逃离狼群,他需要能够移动得更快。我们将看到彼得如何使用 Q-Learning 学习滑冰,特别是保持平衡。 + +![大逃亡!](../images/escape.png) + +> 彼得和他的朋友们发挥创意来逃离狼!图片来自 [Jen Looper](https://twitter.com/jenlooper) + +我们将使用称为 **CartPole** 问题的简化版版本。在这个世界中,我们有一个可以左右移动的水平滑块,目标是平衡滑块顶部的垂直杆。 + +a cartpole + +## 先决条件 + +在本课中,我们将使用一个名为 **OpenAI Gym** 的库来模拟不同的 **环境**。你可以在本地(例如从 Visual Studio Code)运行本课程的代码,在这种情况下,模拟将在新窗口中打开。在线运行代码时,你可能需要对代码进行一些调整,如 [此处](https://towardsdatascience.com/rendering-openai-gym-envs-on-binder-and-google-colab-536f99391cc7)。 + +## OpenAI Gym + +在上一课中,游戏规则和状态是由我们自己定义的"Board"类给出的。这里我们将使用一个特殊的**模拟环境**,它将模拟平衡杆后面的物理规则。训练强化学习算法最流行的模拟环境之一称为 [Gym](https://gym.openai.com/),由 [OpenAI](https://openai.com/) 维护。通过使用这个模拟环境,我们可以创建不同的**环境**,从推车模拟到 Atari 游戏。 + +> **注意**:你可以在 [此处](https://gym.openai.com/envs/#classic_control) 查看 OpenAI Gym 提供的其他环境。 + +首先,让我们安装 gym 并导入所需的库(代码块 1): + +```python +import sys +!{sys.executable} -m pip install gym + +import gym +import matplotlib.pyplot as plt +import numpy as np +import random +``` + +## 练习 - 初始化一个推车环境 + +为了解决车杆平衡问题,我们需要初始化相应的环境。每个环境都有以下内容: + +- **观察空间**,定义了我们从环境中接收到的信息结构。对于 cartpole 问题,我们接收杆的位置、速度和其他一些值。 + +- **动作空间**,定义可能的动作。在我们的例子中,动作空间是离散的,由两个动作组成 - **left** 和 **right**。(代码块 2) + +1. 要初始化,请键入以下代码: + + ```python + env = gym.make("CartPole-v1") + print(env.action_space) + print(env.observation_space) + print(env.action_space.sample()) + ``` + +要查看环境如何工作,让我们运行 100 个步骤的简短模拟。在每一步,我们提供一个要采取的行动——在这个模拟中,我们只是从 "action_space" 中随机选择一个行动。 + +1. 运行下面的代码,看看它会导致什么。 + + ✅ 请记住,最好在本地 Python 安装上运行此代码!(代码块 3) + + ```python + env.reset() + + for i in range(100): + env.render() + env.step(env.action_space.sample()) + env.close() + ``` + + 你应该会看到与此图像类似的内容: + + ![非平衡cartpole](../images/cartpole-nobalance.gif) + +2. 在模拟过程中,我们需要通过观察来决定如何行动。事实上,step 函数返回当前观察值、奖励函数和指示是否继续模拟有意义的完成标志:(代码块 4) + + ```python + env.reset() + + done = False + while not done: + env.render() + obs, rew, done, info = env.step(env.action_space.sample()) + print(f"{obs} -> {rew}") + env.close() + ``` + + 你最终会在笔记本输出中看到类似的内容: + + ```text + [ 0.03403272 -0.24301182 0.02669811 0.2895829 ] -> 1.0 + [ 0.02917248 -0.04828055 0.03248977 0.00543839] -> 1.0 + [ 0.02820687 0.14636075 0.03259854 -0.27681916] -> 1.0 + [ 0.03113408 0.34100283 0.02706215 -0.55904489] -> 1.0 + [ 0.03795414 0.53573468 0.01588125 -0.84308041] -> 1.0 + ... + [ 0.17299878 0.15868546 -0.20754175 -0.55975453] -> 1.0 + [ 0.17617249 0.35602306 -0.21873684 -0.90998894] -> 1.0 + ``` + + 在模拟的每一步返回的观察向量包含以下值: + - 推车的位置 + - 推车速度 + - 杆的角度 + - 杆的转速 + +1. 获取这些数字的最小值和最大值:(代码块 5) + + ```python + print(env.observation_space.low) + print(env.observation_space.high) + ``` + + 你可能还注意到,每个模拟步骤的奖励值始终为 1。这是因为我们的目标是尽可能长时间地生存,即在最长的时间内将杆保持在合理的垂直位置。 + + ✅ 事实上,如果我们设法在 100 次连续试验中获得 195 的平均奖励,则认为 CartPole 问题已解决。 + +## 状态离散化 + +在 Q-Learning 中,我们需要构建 Q-Table 来定义在每个状态下要做什么。为了能够做到这一点,我们需要状态 **discreet**,更准确地说,它应该包含有限数量的离散值。因此,我们需要以某种方式**离散**我们的观察,将它们映射到一组有限的状态。 + +我们有几种方法可以做到这一点: + +- **拆分装箱**。如果我们知道某个值的区间,我们可以把这个区间分成若干个**bins**,然后用它所属的箱子序号替换这个值。这可以使用 [`digitize`](https://numpy.org/doc/stable/reference/generated/numpy.digitize.html) 方法来完成。在这种情况下,我们将精确地知道状态大小,因为它取决于我们选择的箱子数量。 + +✅ 我们可以使用线性插值将值带入某个有限区间(例如,从 -20 到 20),然后通过四舍五入将数字转换为整数。这使我们对状态大小的控制减弱了一点,尤其是当我们不知道输入值的确切范围时。例如,在我们的例子中,4 个值中有 2 个值的值没有上限/下限,这可能会导致无限数量的状态。 + +在我们的示例中,我们将采用第二种方法。稍后你可能会注意到,尽管有未定义的上限/下限,但这些值很少采用某些有限区间之外的值,因此具有极值的状态将非常罕见。 + +1. 这是一个函数,它将从我们的模型中获取观察结果并生成一个包含 4 个整数值的元组:(代码块 6) + + ```python + def discretize(x): + return tuple((x/np.array([0.25, 0.25, 0.01, 0.1])).astype(np.int)) + ``` + +2. 让我们也探索另一种使用 bins 的离散化方法:(代码块 7) + + ```python + def create_bins(i,num): + return np.arange(num+1)*(i[1]-i[0])/num+i[0] + + print("Sample bins for interval (-5,5) with 10 bins\n",create_bins((-5,5),10)) + + ints = [(-5,5),(-2,2),(-0.5,0.5),(-2,2)] # 每个参数的值间隔 + nbins = [20,20,10,10] # 每个参数的 bin 数量 + bins = [create_bins(ints[i],nbins[i]) for i in range(4)] + + def discretize_bins(x): + return tuple(np.digitize(x[i],bins[i]) for i in range(4)) + ``` + +3. 现在让我们运行一个简短的模拟并观察那些离散的环境值。随意尝试 `discretize` 和 `discretize_bins` ,看看是否有区别。 + + ✅ discretize_bins 返回 bin 编号,从 0 开始。因此,对于0附近 的输入变量值,它返回区间 (10) 中间的数字。在离散化中,我们不关心输出值的范围,允许它们为负,因此状态值不会移位,0 对应于 0。(代码块 8) + + ```python + env.reset() + + done = False + while not done: + #env.render() + obs, rew, done, info = env.step(env.action_space.sample()) + #print(discretize_bins(obs)) + print(discretize(obs)) + env.close() + ``` + + ✅ 如果你想查看环境如何执行,请取消以 env.render 开头行的注释。或者你可以在后台执行它,这样会更快。我们将在 Q-Learning 过程中使用这种"隐形"执行。 + +## Q-Table 结构 + +在我们上一课中,状态是从 0 到 8 的一对简单数字,因此用形状为 8x8x2 的 numpy 张量来表示 Q-Table 很方便。如果我们使用 bins 离散化,我们的状态向量的大小也是已知的,所以我们可以使用相同的方法,用一个形状为 20x20x10x10x2 的数组来表示状态(这里 2 是动作空间的维度,第一个维度对应于我们选择用于观察空间中的每个参数的箱)。 + +然而,有时不知道观察空间的精确尺寸。在 `discretize` 函数的情况下,我们可能永远无法确定我们的状态是否保持在某些限制内,因为一些原始值不受约束。因此,我们将使用稍微不同的方法并用字典表示 Q-Table。 + +1. 使用 *(state,action)* 对作为字典键,该值将对应于 Q-Table 条目值。(代码块 9) + + ```python + Q = {} + actions = (0,1) + + def qvalues(state): + return [Q.get((state,a),0) for a in actions] + ``` + + 这里我们还定义了一个函数 `qvalues()`,它返回对应于所有可能操作的给定状态的 Q-Table 值列表。如果该条目不存在于 Q-Table 中,我们将返回 0 作为默认值。 + +## 让我们开始 Q-Learning + +现在我们准备教彼得平衡! + +1. 首先,让我们设置一些超参数:(代码块 10) + + ```python + # hyperparameters + alpha = 0.3 + gamma = 0.9 + epsilon = 0.90 + ``` + + 这里,`alpha` 是**学习率**,它定义了我们应该在每一步调整 Q-Table 的当前值的程度。在上一课中,我们从 1 开始,然后在训练期间将 `alpha` 减小到较低的值。在这个例子中,为了简单起见,我们将保持它不变,你可以稍后尝试调整 `alpha` 值。 + + `gamma` 是**折扣因素**,它表明我们应该在多大程度上优先考虑未来的奖励而不是当前的奖励。 + + `epsilon` 是**探索/开发因素**,它决定了我们是否应该更喜欢探索而不是开发,反之亦然。在我们的算法中,我们将在 `epsilon` 百分比的情况下根据 Q-Table 值选择下一个动作,在剩余的情况下,我们将执行随机动作。这将使我们能够探索我们以前从未见过的搜索空间区域。 + + ✅ 在平衡方面 - 选择随机动作(探索)将作为朝着错误方向的随机一拳,杆子必须学习如何从这些"错误"中恢复平衡 + +### 改进算法 + +我们还可以对上一课的算法进行两项改进: + +- **计算平均累积奖励**,经过多次模拟。我们每 5000 次迭代打印一次进度,并计算这段时间内累积奖励的平均值。这意味着如果我们得到超过 195 分——我们可以认为问题已经解决,甚至比要求的质量更高。 + +- **计算最大平均累积结果**,`Qmax`,我们将存储与该结果对应的Q-Table。当你运行训练时,你会注意到有时平均累积结果开始下降,我们希望保留与训练期间观察到的最佳模型相对应的 Q-Table 值。 + +1. 在 `rewards` 向量处收集每次模拟的所有累积奖励,用于进一步绘图。(代码块 11) + + ```python + def probs(v,eps=1e-4): + v = vv.min()+eps + v = v/v.sum() + return v + + Qmax = 0 + cum_rewards = [] + rewards = [] + for epoch in range(100000): + obs = env.reset() + done = False + cum_reward=0 + # == do the simulation == + while not done: + s = discretize(obs) + if random.random() Qmax: + Qmax = np.average(cum_rewards) + Qbest = Q + cum_rewards=[] + ``` + +你可能会从这些结果中注意到: + +- **接近我们的目标**。我们非常接近实现在连续 100 多次模拟运行中获得 195 个累积奖励的目标,或者我们可能真的实现了!即使我们得到更小的数字,我们仍然不知道,因为我们平均超过 5000 次运行,而在正式标准中只需要 100 次运行。 + +- **奖励开始下降**。有时奖励开始下降,这意味着我们可以"破坏" Q-Table 中已经学习到的值,这些值会使情况变得更糟。 + +如果我们绘制训练进度图,则这种观察会更加清晰可见。 + +## 绘制训练进度 + +在训练期间,我们将每次迭代的累积奖励值收集到`rewards`向量中。以下是我们根据迭代次数绘制它时的样子: + +```python +plt.plot(reawrd) +``` + +![原始进度](../images/train_progress_raw.png) + +从这张图中,无法说明任何事情,因为由于随机训练过程的性​​质,训练课程的长度差异很大。为了更好地理解这个图,我们可以计算一系列实验的 **running average**,假设为 100。这可以使用 `np.convolve` 方便地完成:(代码块 12) + +```python +def running_average(x,window): + return np.convolve(x,np.ones(window)/window,mode='valid') + +plt.plot(running_average(rewards,100)) +``` + +![训练进度](../images/train_progress_runav.png) + +## 不同的超参数 + +为了让学习更稳定,在训练期间调整我们的一些超参数是有意义的。特别是: + +- **对于学习率**,`alpha`,我们可以从接近 1 的值开始,然后不断减小参数。随着时间的推移,我们将在 Q-Table 中获得良好的概率值,因此我们应该稍微调整它们,而不是用新值完全覆盖。 + +- **增加epsilon**。我们可能希望缓慢增加`epsilon`,以便探索更少,开发更多。从`epsilon`的较低值开始,然后上升到接近 1 可能是有意义的。 + +> **任务 1**:玩转超参数值,看看是否可以获得更高的累积奖励。你超过195了吗? + +> **任务 2**:要正式解决问题,你需要在 100 次连续运行中获得 195 的平均奖励。在培训期间衡量并确保你已经正式解决了问题! + +## 在行动中看到结果 + +实际看看受过训练的模型的行为会很有趣。让我们运行模拟并遵循与训练期间相同的动作选择策略,根据 Q-Table 中的概率分布进行采样:(代码块 13) + +```python +obs = env.reset() +done = False +while not done: + s = discretize(obs) + env.render() + v = probs(np.array(qvalues(s))) + a = random.choices(actions,weights=v)[0] + obs,_,done,_ = env.step(a) +env.close() +``` + +你应该会看到如下内容: + +![平衡车杆](../images/cartpole-balance.gif) + +--- + +## 🚀挑战 + +> **任务 3**:在这里,我们使用的是 Q-Table 的最终副本,它可能不是最好的。请记住,我们已将性能最佳的 Q-Table 存储到 `Qbest` 变量中!通过将`Qbest`复制到`Q`来尝试使用性能最佳的 Q-Table 的相同示例,看看你是否注意到差异。 + +> **任务 4**:这里我们不是在每一步选择最佳动作,而是用相应的概率分布进行采样。始终选择具有最高 Q-Table 值的最佳动作是否更有意义?这可以通过使用 `np.argmax` 函数找出对应于较高 Q-Table 值的动作编号来完成。实施这个策略,看看它是否能改善平衡。 + +## [课后测验](https://white-water-09ec41f0f.azurestaticapps.net/quiz/48/) + +## 作业:[训练山地车](assignment.zh-cn.md) + +## 结论 + +我们现在已经学会了如何训练智能体以取得良好的结果,只需为它们提供一个定义游戏所需状态的奖励函数,并为它们提供智能探索搜索空间的机会。我们已经成功地将 Q-Learning 算法应用于离散和连续环境的情况,但具有离散动作。 + +研究动作状态也是连续的情况以及观察空间复杂得多的情况也很重要,例如来自 Atari 游戏屏幕的图像。在那些问题中,我们往往需要使用更强大的机器学习技术,比如神经网络,才能取得好的效果。这些更高级的主题在我们即将推出的更高级 AI 课程的主题中。 diff --git a/8-Reinforcement/2-Gym/translations/assignment.zh-cn.md b/8-Reinforcement/2-Gym/translations/assignment.zh-cn.md new file mode 100644 index 00000000..c7d20bea --- /dev/null +++ b/8-Reinforcement/2-Gym/translations/assignment.zh-cn.md @@ -0,0 +1,47 @@ +# 连续山地车 + +[OpenAI Gym](http://gym.openai.com) 的设计方式是所有环境都提供相同的 API - 即相同的方法 `reset`、`step` 和 `render`,以及相同的抽象**动作空间**和**观察空间**。因此,应该可以通过最少的代码更改使相同的强化学习算法适应不同的环境。 + + +## 山地车环境 + +[山地车环境](https://gym.openai.com/envs/MountainCar-v0/) 包含一辆卡在山谷中的汽车: + + + +目标是通过在每一步执行以下任一操作来走出山谷并得到旗帜: + +| 值 | 含义 | +|---|---| +| 0 | 向左加速 | +| 1 | 不加速| +| 2 | 向右加速| + +然而,这个问题的主要技巧是,汽车的引擎不够强大,无法一次性翻越这座山。因此,成功的唯一方法是来回驱动以积聚动力。 + +观察空间仅包含两个值: + +| 数量 | 观察 | 最小 | 最大 | +|-----|--------------|-----|-----| +| 0 | 车位置 | -1.2| 0.6 | +| 1 | 车速度 | -0.07 | 0.07 | + +山地车的奖励系统相当棘手: + +- 如果山地车到达山顶的标志(位置 = 0.5)则奖励 0。 +- 如果代理的位置小于 0.5,则奖励 -1。 + +如果汽车位置大于 0.5 或步骤长度大于 200,则游戏终止。 + + +## 说明 + +调整我们的强化学习算法来解决山地车问题。从现有的 [notebook.ipynb](../notebook.ipynb)代码开始,替换新环境,更改状态离散化函数,并尝试使现有算法以最少的代码修改进行训练。通过调整超参数来优化结果。 + +> 注意:可能需要调整超参数以使算法收敛。 + +## 评判标准 + +| 标准 | 优秀 | 中规中矩 | 仍需努力 | +| -------- | --------- | -------- | ----------------- | +| | Q-Learning 算法成功地改编自 CartPole 示例,代码修改最少,能够解决 200 步以下得到旗帜的问题。 | 一种新的 Q-Learning 算法已从 Internet 上采用,但有据可查;或采用现有算法,但未达到预期效果 | 学生无法成功采用任何算法,但已经迈出了解决问题的实质性步骤(实现了状态离散化、Q-Table 数据结构等)| \ No newline at end of file