2.1 剖析智能体
正如上章所述,RL的世界中包含很多实体:
- 智能体:主动行动的人或物。实际上,智能体只是实现了某些策略的代码片段而已。这个策略根据观察决定每一个时间点执行什么动作。
- 环境:某些世界的模型,它在智能体外部,负责提供观察并给予奖励。而且环境会根据智能体的动作改变自己的状态。
我们来探究一下如何在简单的情景下,用Python实现它们。先定义一个环境,限定交互步数,并且不管智能体执行任何动作,它都给智能体返回随机奖励。这种场景不是很有用,却能让我们聚焦于环境和智能体类中的某些方法。先从环境开始吧:
前面的代码展示了环境初始化内部状态。在示例场景下,状态就是一个计数器,记录智能体还能和环境交互的步数。
get_observation()
方法能给智能体返回当前环境的观察。它通常被实现为有关环境内部状态的某些函数。不知你是否对-> List[float]
感到好奇,它其实是Python的类型注解,是在Python 3.5版本引入的。在https://docs.python.org/3/library/typing.html的文档中可以查看更多内容。在示例中,观察向量总是0,因为环境根本就没有内部状态。
get_action()
方法允许智能体查询自己能执行的动作集。通常,智能体能执行的动作集不会随着时间变化,但是当环境发生变化的时候,某些动作可能会变得无法执行(例如在井字棋中,不是所有的位置能都执行所有动作)。而在我们这极其简单的例子中,智能体只能执行两个动作,它们被编码成了整数0和1。
前面的方法给予智能体片段结束的信号。就像第1章中所述,环境–智能体的交互序列被分成一系列步骤,称为片段。片段可以是有限的,比如国际象棋,也可以是无限的,比如旅行者2号的任务(一个著名的太空探测器,发射于40年前,目前已经探索到太阳系外了)。为了囊括两种场景,环境提供了一种检测片段何时结束的方法,通知智能体它无法再继续交互了。
action()
方法是环境的核心功能。它做两件事——处理智能体的动作以及返回该动作的奖励。在示例中,奖励是随机的,而动作被丢弃了。另外,该方法还会更新已经执行的步数,并拒绝继续执行已结束的片段。
现在该来看一下智能体的部分了,它更简单,只包含两个方法:构造函数以及在环境中执行一步的方法:
在构造函数中,我们初始化计数器,该计数器用来保存片段中智能体累积的总奖励:
step
函数接受环境实例作为参数,并允许智能体执行下列操作:
- 观察环境。
- 基于观察决定动作。
- 向环境提交动作。
- 获取当前步骤的奖励。
对于我们的例子,智能体比较愚笨,它在决定执行什么动作的时候会忽视得到的观察。取而代之的是,随机选择动作。最后还剩下胶水代码,它创建两个类并执行一次片段:
你可以在本书的GitHub库中找到上述代码,就在https://github.com/PacktPublishing/Deep-Reinforcement-Learning-Hands-On- Second-Edition的Chapter02/01_agent_anatomy.py
文件中。它没有外部依赖,只要稍微现代一点的Python版本就能运行。多次运行,智能体得到的奖励总数会是不同的。
前面那简单的代码就展示了RL模型的重要的基本概念。环境可以是极其复杂的物理模型,智能体也可以轻易地变成一个实现了最新RL算法的大型神经网络(NN),但是基本思想还是一致的——每一步,智能体都会从环境中得到观察,进行一番计算,最后选择要执行的动作。这个动作的结果就是奖励和新的观察。
你可能会问,如果基本思想是一样的,为什么还要从头开始实现呢?是否有人已经将其实现为一个通用库了?答案是肯定的,这样的框架已经存在了,但是在花时间讨论它们前,先把你的开发环境准备好吧。