强化学习笔记专栏传送
上一篇:强化学习RL学习笔记2-概述(2)
下一篇:强化学习RL学习笔记4-马尔可夫决策过程(MDP)(1)
前言
强化学习(Reinforcement Learning, RL),又称再励学习、评价学习或增强学习,是机器学习的范式和方法论之一,用于描述和解决智能体(agent)在与环境的交互过程中通过学习策略以达成回报最大化或实现特定目标的问题 。
本文是笔者对强化学习的一点学习记录,成文于笔者刚开始接触强化学习期间,主要内容参考LeeDeepRL-Notes,学习期间很多概念和理论框架还很不成熟,若文中存在错误欢迎批评指正,也欢迎广大学习者沟通交流、共同进步。
Experiment with Reinforcement Learning
1.gym
OpenAI Gym 是一个环境仿真库,里面包含了很多现有的环境。针对不同的场景,可以选择不同的环境:
- 离散控制场景(输出的动作是可数的,比如 Pong 游戏中输出的向上或向下动作):一般使用 Atari 环境评估
- 连续控制场景(输出的动作是不可数的,比如机器人走路时不仅有方向,还要角度,角度就是不可数的,是一个连续的量 ):一般使用 mujoco 环境评估
Gym Retro 是对 Gym 环境的进一步扩展,包含了更多的游戏。
可以通过 pip 来安装 Gym:
pip install gym
作为例子,看一下 CartPole 的这个环境。对于这个环境,有两个动作,Cart 往左移还是往右移。这里得到了观测:
- 这个车当前的位置
- Cart 当前往左往右移的速度
- 这个杆的角度以及杆的最高点的速度
https://gym.openai.com/videos/2019-10-21–mqt8Qj1mwo/CartPole-v1/original.mp4
如果 observation 越详细,就可以更好地描述当前这个所有的状态。这里有 reward 的定义,如果能多保留一步,你就会得到一个奖励,所以你需要在尽可能多的时间存活来得到更多的奖励。当这个杆的角度大于某一个角度(没能保持平衡)或者这个车已经出到外面的时候,游戏就结束了,你就输了。所以这个 agent 的目的就是为了控制木棍,让它尽可能地保持平衡以及尽可能保持在这个环境的中央。
import gym # 导入 Gym 的 Python 接口环境包
env = gym.make('CartPole-v0') # 构建实验环境
env.reset() # 重置一个 episode
for _ in range(1000):
env.render() # 显示图形界面
action = env.action_space.sample() # 从动作空间中随机选取一个动作
env.step(action) # 用于提交动作,括号内是具体的动作
env.close() # 关闭环境
执行这段代码时,机器人会完全无视那根本该立起来的杆子,驾驶着小车朝某个方向一通跑,直到不见踪影。这是因为还没开始训练机器人。
env.action_space.sample() 值为 1 或者 0,含义是在该游戏的所有动作空间里随机选择一个作为输出。在这个例子中,意思就是,动作只有两个:0 和 1,一左一右。
env.step()这个方法的作用不止于提交动作,还有四个返回值,分别是observation、reward、done、info。
- observation(object)是状态信息,是在游戏中观测到的屏幕像素值或者盘面状态描述信息。
- reward(float)是奖励值,即 action 提交以后能够获得的奖励值。这个奖励值因游戏的不同而不同,但总体原则是,对完成游戏有帮助的动作会获得比较高的奖励值。
- done(boolean)表示游戏是否已经完成。如果完成了,就需要重置游戏并开始一个新的 episode。
- info(dict)是一些比较原始的用于诊断和调试的信息,或许对训练有帮助。不过,OpenAI团队在评价你提交的机器人时,是不允许使用这些信息的。
在每个训练中都要使用的返回值有 observation、reward、done。但 observation 的结构会由于游戏的不同而发生变化。以 CartPole-v0 小游戏为例,我们修改下代码:
import gym
env = gym.make('CartPole-v0')
env.reset()
for _ in range(1000):
env.render()
action = env.action_space.sample()
observation, reward, done, info = env.step(action)
print(observation)
env.close()
输出:
[ 0.04554792 0.22763931 -0.01155967 -0.29054901]
[ 0.05010071 0.42292417 -0.01737065 -0.58685519]
[ 0.05855919 0.61828504 -0.02910775 -0.884959 ]
…
从输出可以看出这是一个四维的 Observation。在其他游戏中会有维度很多的情况。
env.step()完成了一个完整的 S → A → R → S ′ S \to A \to R \to S' S→A→R→S′过程。我们只要不断观测这样的过程,并让机器在其中用相应的算法完成训练,就能得到一个高质量的强化学习模型。
想要查看当前 Gym 库已经注册了哪些环境,可以使用以下代码:
from gym import envs
env_specs = envs.registry.all()
envs_ids = [env_spec.id for env_spec in env_specs]
print(envs_ids)
输出形式为:
[‘Copy-v0’, ‘RepeatCopy-v0’, ‘ReversedAddition-v0’, ‘ReversedAddition3-v0’, ‘DuplicatedInput-v0’, ‘Reverse-v0’, ‘CartPole-v0’, ‘CartPole-v1’, ‘MountainCar-v0’, ‘MountainCarContinuous-v0’,…]
每个环境都定义了自己的观测空间和动作空间。环境 env 的观测空间用env.observation_space表示,动作空间用 env.action_space 表示。观测空间和动作空间既可以是离散空间(即取值是有限个离散的值),也可以是连续空间(即取值是连续的)。在 Gym 库中,离散空间一般用gym.spaces.Discrete类表示,连续空间用gym.spaces.Box类表示。
例如,环境’MountainCar-v0’的观测空间是Box(2,),表示观测可以用 2 个 float 值表示;环境’MountainCar-v0’的动作空间是Dicrete(3),表示动作取值自{0,1,2}。对于离散空间,gym.spaces.Discrete类实例的成员 n 表示有几个可能的取值;对于连续空间,Box类实例的成员 low 和 high 表示每个浮点数的取值范围。
2.MountainCar-v0 Example
通过一个例子来学习如何与 Gym 库进行交互。我们选取**小车上山(MountainCar-v0)**作为例子。
首先看看这个任务的观测空间和动作空间:
import gym
env = gym.make('MountainCar-v0')
print('观测空间 = {}'.format(env.observation_space))
print('动作空间 = {}'.format(env.action_space))
print('观测范围 = {} ~ {}'.format(env.observation_space.low,
env.observation_space.high))
print('动作数 = {}'.format(env.action_space.n))
输出:
观测空间 = Box(-1.2000000476837158, 0.6000000238418579, (2,), float32)
动作空间 = Discrete(3)
观测范围 = [-1.2 -0.07] ~ [0.6 0.07]
动作数 = 3
由输出可知,观测空间是形状为 (2,) 的浮点型 np.array,动作空间是取 {0,1,2} 的 int 型数值。
接下来考虑智能体。智能体往往是我们自己实现的。我们可以实现一个智能体类:BespokeAgent类,代码如下所示:
class BespokeAgent:
def __init__(self, env):
pass
def decide(self, observation): # 决策
position, velocity = observation
lb = min(-0.09 * (position + 0.25) ** 2 + 0.03,
0.3 * (position + 0.9) ** 4 - 0.008)
ub = -0.07 * (position + 0.38) ** 2 + 0.07
if lb < velocity < ub:
action = 2
else:
action = 0
return action # 返回动作
def learn(self, *args): # 学习
pass
agent = BespokeAgent(env)
智能体的 decide() 方法实现了决策功能,而 learn() 方法实现了学习功能。BespokeAgent类是一个比较简单的类,它只能根据给定的数学表达式进行决策,并且不能有效学习。所以它并不是一个真正意义上的强化学习智能体类。但是,用于演示智能体和环境的交互已经足够了。
接下来我们试图让智能体与环境交互,代码如下所示:
def play_montecarlo(env, agent, render=False, train=False):
episode_reward = 0. # 记录回合总奖励,初始化为0
observation = env.reset() # 重置游戏环境,开始新回合
while True: # 不断循环,直到回合结束
if render: # 判断是否显示
env.render() # 显示图形界面,图形界面可以用 env.close() 语句关闭
action = agent.decide(observation)
next_observation, reward, done, _ = env.step(action) # 执行动作
episode_reward += reward # 收集回合奖励
if train: # 判断是否训练智能体
agent.learn(observation, action, reward, done) # 学习
if done: # 回合结束,跳出循环
break
observation = next_observation
return episode_reward # 返回回合总奖励
上面代码中的 play_montecarlo 函数可以让智能体和环境交互一个回合。这个函数有 4 个参数:
- env 是环境类
- agent 是智能体类
- render是 bool 类型变量,指示在运行过程中是否要图形化显示。如果函数参数 render为 True,那么在交互过程中会调用 env.render() 以显示图形化界面,而这个界面可以通过调用 env.close() 关闭。
- train是 bool 类型的变量,指示在运行过程中是否训练智能体。在训练过程中应当设置为 True,以调用 agent.learn() 函数;在测试过程中应当设置为 False,使得智能体不变。
这个函数有一个返回值 episode_reward,是 float 类型的数值,表示智能体与环境交互一个回合的回合总奖励。
接下来,我们使用下列代码让智能体和环境交互一个回合,并在交互过程中图形化显示,可用 env.close() 语句关闭图形化界面。
env.seed(0) # 设置随机数种子,只是为了让结果可以精确复现,一般情况下可删去
episode_reward = play_montecarlo(env, agent, render=True)
print('回合奖励 = {}'.format(episode_reward))
env.close() # 此语句可关闭图形界面
输出:
回合奖励 = -105.0
为了系统评估智能体的性能,下列代码求出了连续交互 100 回合的平均回合奖励:
import numpy as np
episode_rewards = [play_montecarlo(env, agent) for _ in range(100)]
print('平均回合奖励 = {}'.format(np.mean(episode_rewards)))
输出:
平均回合奖励 = -105.55
小车上山环境有一个参考的回合奖励值 -110,如果当连续 100 个回合的平均回合奖励大于 -110,则认为这个任务被解决了。BespokeAgent 类对应的策略的平均回合奖励大概就在 -110 左右。
测试 agent 在 Gym 库中某个任务的性能时,学术界一般最关心 100 个回合的平均回合奖励。至于为什么是 100 个回合而不是其他回合数(比如 128 个回合),完全是习惯使然,没有什么特别的原因。对于有些环境,还会指定一个参考的回合奖励值,当连续 100 个回合的奖励大于指定的值时,就认为这个任务被解决了。但是,并不是所有的任务都指定了这样的值。对于没有指定值的任务,就无所谓任务被解决了或者没有被解决。
总结一下 Gym 的用法:使用 env=gym.make(环境名) 取出环境,使用 env.reset()初始化环境,使用env.step(动作)执行一步环境,使用 env.render()显示环境,使用 env.close() 关闭环境。