Press "Enter" to skip to content

使用离策略蒙特卡洛控制解决强化学习赛车道练习

Midjourney收费订阅生成的图片,符合一般商业条款[1]

介绍

在《强化学习导论第2版》(第112页)的章节“Off-policy Monte Carlo Control”中,作者给了我们一个有趣的练习:使用加权重要性采样的离策略蒙特卡洛方法找到在两个赛道上行驶的最快方式。这个练习非常全面,要求我们考虑并构建强化学习任务的几乎每个组成部分,比如环境、代理、奖励、动作、终止条件和算法。解决这个练习既有趣又有助于我们建立对算法和环境之间交互的深入理解,正确的情节任务定义的重要性以及值初始化对训练结果的影响。通过这篇文章,我希望与对强化学习感兴趣的每个人分享我对这个练习的理解和解决方案。

练习要求

如上所述,这个练习要求我们找到一种策略,使得赛车能够在起点和终点之间尽可能快地行驶,而不会碰到碎石或离开赛道。仔细阅读练习描述后,我列出了完成这个任务所必需的一些关键点:

  • 地图表示:在这个上下文中,地图实际上是带有(行索引,列索引)作为坐标的二维矩阵。每个单元格的值表示该单元格的状态;例如,我们可以用0表示碎石,用1表示赛道表面,用0.4表示起始区域,用0.8表示终点线。矩阵之外的任何行和列索引都可以视为超出边界。
  • 赛车表示:我们可以直接使用矩阵的坐标来表示赛车的位置;
  • 速度和控制:速度空间是离散的,由水平速度和垂直速度组成,可以表示为一个元组(行速度,列速度)。两个轴上的速度限制为(-5,5),每个轴上每步的增量为+1、0和-1;因此,每步中总共有9种可能的动作。从字面上看,速度不能同时为零,除了在起点线上,而垂直速度或行速度不能为负,因为我们不希望车子往回开到起点线。
  • 奖励和回合:在成功穿过终点线之前的每一步的奖励为-1。当车子离开赛道时,它将被重新设置到起始单元格之一。回合仅当车成功穿过终点线时才结束。
  • 起始状态:我们随机选择起点线上的起始单元格作为车子的起始位置;根据练习的描述,车子的初始速度为(0,0)。
  • 零加速挑战:作者提出了一个小的零加速挑战,即在每个时间步中,有0.1的概率动作不会生效,车子将保持其先前的速度。我们可以在训练中实现这个挑战,而不是将该特性添加到环境中。

解决这个练习分为两篇文章;在本篇文章中,我们将重点介绍如何构建一个赛车道环境。这个练习的文件结构如下所示:

|-- race_track_env|  |-- maps|  |  |-- build_tracks.py // 用于生成赛道地图的文件|  |  |-- track_a.npy // 赛道A的数据|  |  |-- track_b.npy // 赛道B的数据|  |-- race_track.py // 赛车道环境|-- exercise_5_12_racetrack.py // 这个练习的解决方案

此实现中使用的库如下:

python==3.9.16numpy==1.24.3matplotlib==3.7.1pygame==2.5.0

构建赛道地图

我们可以将赛道地图表示为具有不同值的二维矩阵,表示不同的赛道状态。为了忠于练习,我尝试通过手动分配矩阵值来构建与书中显示的相同的地图。这些地图将被保存为单独的.npy文件,以便环境可以在训练中读取文件,而不是在运行时生成它们。

这两张地图如下所示;浅色单元格表示碎石路面,深色单元格表示赛道表面,绿色单元格表示终点线,浅蓝色单元格表示起跑线。

图1 带有2D矩阵表示的赛道地图。来源:作者提供的图片

构建类似Gym的环境

有了准备好的赛道地图,我们现在开始创建一个类似Gym的环境,以便算法可以与之交互。这个健身房,之前是OpenAI健身房,现在属于Farama基金会,为测试强化学习算法提供了一个简单的接口;我们将使用gymnasium包来创建赛道环境。我们的环境将包括以下组件/特性:

  • env.nS:观察空间的形状,在本例中为(num_rows, num_cols, row_speed, col_speed)。行和列的数量在不同的地图中有所变化,但速度空间在赛道上是一致的。对于行速度,由于我们不希望汽车返回起跑线,行速度观察包括[-4, -3, -2, -1, 0](负值,因为我们期望汽车在地图上向上移动),总共有五个空间;列速度没有这样的限制,所以观察范围从-4到4,总共有九个空间。因此,在赛道示例中,观察空间的形状是(num_rows, num_cols, 5, 9)。
  • env.nA:可能的动作数量。在我们的实现中有9个可能的动作;我们还将在环境类中创建一个字典,将整数动作映射到(row_speed, col_speed)元组表示,以控制代理程序。
  • env.reset():当一个episode结束或汽车跑出赛道时,将车带回起始单元格的函数;
  • env.step(action):步骤函数使算法能够通过执行一个动作与环境进行交互,并返回一个包含(next_state, reward, is_terminated, is_truncated)元组的结果。
  • 状态检查函数:有两个私有函数用于检查汽车是否离开了赛道或穿过了终点线;
  • 渲染函数:从我的角度来看,渲染函数对于自定义环境是必不可少的;我总是通过渲染游戏空间和代理行为来检查环境是否已经正确构建,这比仅仅查看日志信息更直观。

有了这些特性,我们可以开始构建赛道环境。

检查环境

到目前为止,准备好了赛道环境所需的一切,我们可以测试/验证我们的环境设置。

首先,我们渲染出游戏进行检查,看看每个组件是否正常工作:

图2 代理程序在两个赛道上进行随机策略驾驶。来源:作者提供的GIF

然后,我们关闭渲染选项,使环境在后台运行,检查轨迹是否正确终止:

// 赛道a|   观察   | 奖励 | 终止 | 总奖励 ||  (1, 16, 0, 3)  |   -1   |    True    |    -4984     ||  (2, 16, 0, 2)  |   -1   |    True    |    -23376    ||  (3, 16, 0, 3)  |   -1   |    True    |    -14101    ||  (1, 16, 0, 3)  |   -1   |    True    |    -46467    |// 赛道b|   观察    | 奖励 | 终止 | 总奖励 ||  (1, 31, -2, 2)  |   -1   |    True    |    -3567     ||  (0, 31, -4, 4)  |   -1   |    True    |    -682      ||  (2, 31, -2, 1)  |   -1   |    True    |    -1387     ||  (3, 31, -1, 3)  |   -1   |    True    |    -2336     |

离策略蒙特卡洛控制算法

环境准备就绪后,我们可以准备实现带有加权重要性采样算法的离策略MC控制算法,如下所示:

图3 离策略蒙特卡洛控制算法。来源:作者使用LaTex编写的算法。

蒙特卡洛方法通过对样本回报进行平均来解决强化学习问题。在训练中,该方法基于给定策略生成轨迹,并从每个回合的尾部进行学习。离策略学习与在策略学习的区别在于,在策略方法使用一个策略进行决策和改进,而离策略方法使用不同的策略生成数据和策略改进。根据该书的作者所说,几乎所有的离策略方法都利用重要性采样来估计给定另一个分布下的期望值。[2]

以下几个部分将解释上述算法中出现的软策略和加权重要性采样的技巧,以及适当的值初始化对该任务的重要性。

目标策略和行为策略

该算法的离策略方法使用两个策略:

  • 目标策略:正在学习的策略。在该算法中,目标策略是贪婪且确定性的,这意味着在时间t上贪婪动作A的概率为1.0,而其他所有动作的概率都等于0.0。目标策略遵循广义策略迭代(GPI),在每一步之后进行改进。
  • 行为策略:用于生成行为的策略。该算法中的行为策略被定义为软策略,意味着给定状态下的所有动作都具有非零概率。我们将使用ε-贪婪策略作为行为策略。

在软策略中,一个动作的概率为:

使用离策略蒙特卡洛控制解决强化学习赛车道练习 四海 第4张

在生成行为以进行重要性采样时,我们还应返回该概率。行为策略的代码如下:

加权重要性采样

我们估计由目标策略生成的轨迹相对于行为策略生成的轨迹的相对概率,该概率的方程为:

使用离策略蒙特卡洛控制解决强化学习赛车道练习 四海 第5张

加权重要性采样的值函数为:

使用离策略蒙特卡洛控制解决强化学习赛车道练习 四海 第6张

我们可以重新构造方程以适应增量更新的形式,以适应具有周期性任务的形式:

使用离策略蒙特卡洛控制解决强化学习赛车道练习 四海 第7张

关于如何推导上述方程,有许多很好的例子,所以我们不再花时间进行推导。但我也想解释一下算法的最后几行的有趣技巧:

图4 离策略蒙特卡洛控制算法中的技巧。来源:作者的图片

这个技巧基于目标策略是确定性的设置。如果某个时间步骤上采取的动作与来自目标策略的动作不同,则相对于目标策略的动作的概率为0.0;因此,所有后续步骤将不再更新轨迹的状态-动作值函数。相反,如果该时间步骤上的动作与目标策略相同,则概率为1.0,并且继续进行动作值更新。

权重初始化

适当的权重初始化在成功解决这个练习中起着重要作用。首先让我们来看一下随机策略下两个赛道上的奖励:

// 赛道 a|   观测值   | 奖励 | 终止状态 | 总奖励 ||  (1, 16, 0, 3)  |   -1   |    True    |    -4984     ||  (2, 16, 0, 2)  |   -1   |    True    |    -23376    ||  (3, 16, 0, 3)  |   -1   |    True    |    -14101    ||  (1, 16, 0, 3)  |   -1   |    True    |    -46467    |// 赛道 b|   观测值    | 奖励 | 终止状态 | 总奖励 ||  (1, 31, -2, 2)  |   -1   |    True    |     -3567    ||  (0, 31, -4, 4)  |   -1   |    True    |     -682     ||  (2, 31, -2, 1)  |   -1   |    True    |     -1387    ||  (3, 31, -1, 3)  |   -1   |    True    |     -2336    |

在越过终点线之前的每个时间步骤的奖励都是-1。在训练的早期阶段,奖励将为负值较大;如果我们将状态-动作值初始化为0或从正态分布中随机选择值,例如均值为0,方差为1,那么该值可能被认为过于乐观,程序可能需要很长时间才能找到最优策略。

通过多次测试,我发现均值为-500,方差为1的正态分布可以用于更快地收敛;您可以尝试其他值,肯定会找到比这更好的初始值。

解决问题

有了上述思考,我们可以编写离策略蒙特卡洛控制函数,并使用它来解决赛车道练习。我们还将添加练习描述中提到的零加速挑战。

然后,我们在两个赛道上分别进行1,000,000次训练,包括不考虑零加速挑战和考虑零加速挑战,并使用以下代码对其进行评估:

通过绘制训练记录,我们发现在不考虑零加速的情况下,策略在第10,000个回合左右收敛;添加零加速使得在第100,000个回合之前收敛速度较慢且不稳定,但之后仍然收敛。

图5 赛道 A 的训练奖励历史。来源:作者提供的图像
图6 赛道 B 的训练奖励历史。来源:作者提供的图像

结果

最后,我们可以要求代理根据训练的策略来玩游戏,并绘制出可能的轨迹以进一步检查策略的正确性。

图7 基于训练的策略在两个赛道上行驶的代理。来源:作者提供的 Gif

赛道 a 的样本轨迹:

图8 赛道 A 的样本最佳轨迹。来源:作者提供的图像

赛道 b 的样本轨迹:

图9 赛道 B 的样本最佳轨迹。来源:作者提供的图像

到目前为止,我们已经解决了赛车场练习。这个实现可能还存在一些问题,欢迎您指出并在评论中讨论更好的解决方案。感谢阅读!

参考资料

[1] Midjourney 服务条款:https://docs.midjourney.com/docs/terms-of-service

[2] Sutton, Richard S., and Andrew G. Barto. 强化学习导论. MIT Press, 2018.

此练习的GitHub仓库:[链接];请查看“练习部分”。

Leave a Reply

Your email address will not be published. Required fields are marked *