当你听到“强化学习”时,你首先想到的是什么?最常见的想法是 – 太复杂而且数学太多。但我在此向您保证,这是一个非常迷人的研究领域 – 我的目标是将我的文章中的这些技术分解为易于理解的概念。
你一定听说过OpenAI和DeepMind。这是两个领先的人工智能组织,他们在这一领域取得了重大进展。OpenAI机器人团队能够击败Dota 2中的业余游戏玩家团队,这是一款非常受欢迎且复杂的战斗竞技场游戏。
您认为使用动态编程为Dota 2这样复杂的东西构建机器人是否可行?
不幸的是,这是不行的。有太多的状态(千百万计),收集DOTA 2的所有细节是一项不可能完成的任务。这样我们就需要认识强化学习领域或更具体地说是无模型学习的领域。
在本文中,我们将尝试理解Monte Carlo learning(蒙特卡罗学习)的基础知识,当没有先验信息的环境且所有信息基本上由经验收集时使用。我们将在Python中使用OpenAI Gym工具包来实现此方法。
如果您是这个领域的初学者或需要快速了解一些基本的强化学习术语,我强烈建议您阅读以下文章,以真正最大限度地从这篇文章中学习:
目录
- Model-Based 和 Model-Free Learning
- 蒙特卡罗方法 – 示例
- 蒙特卡洛强化学习
- Monte Carlo Prediction
- Monte Carlo Control
4.使用Python的OpenAI Gym工具包
Model-Based 和 Model-Free Learning
我们知道动态编程用于解决被用来解决环境中的底层模型预先已知(或者更精确地说,基于模型的学习)的模型。强化学习就是学习玩游戏的经验。然而,在所有动态编程算法中,我们实际上是在玩游戏/体验环境。我们有一个完整的环境模型,其中包括所有状态转换概率。
然而,正如我们在引言中提到的大多数现实生活中,从一个状态到另一个状态(或所谓的环境模型)的转换概率事先是未知的。这项任务甚至不必遵循马尔可夫性(Markov property)。
假设我们想训练一个机器人来学习下棋。考虑将国际象棋环境转换为MDP(Markov Decision Processes马可夫决策过程)。
现在,根据片段的位置,这个环境将有许多状态(超过10 50),以及大量可能的操作。这种环境的模型几乎不可能设计出来!
一种可能的解决方案可能是在每场比赛结束时重复进行完整的国际象棋比赛并获得积极的胜利奖励,以及对失败的负面奖励。这被称为从经验中学习(learning from experience)。
蒙特卡罗方法 – 示例
通常蒙特卡罗方法可以粗略地分成两类:一类是所求解的问题本身具有内在的随机性,借助计算机的运算能力可以直接模拟这种随机的过程。
另一种类型是所求解问题可以转化为某种随机分布的特征数,通过随机抽样的方法,以随机事件出现的频率估计其概率,或者以抽样的数字特征估算随机变量的数字特征,并将其作为问题的解。
让我们做一个有趣的练习,我们将尝试使用笔和纸来找出pi的值。让我们绘制一个单位长度的正方形,并绘制一个单位长度为半径的1/4圆。现在,我们有一个机器助手C3PO。它的任务是在正方形上随机放置尽可能多的点3000次,结果如下图所示:
每次将圆点置于圆圈内时,C3PO都需要计数。因此,pi的值将由下式给出:
其中N是点放入圆圈内的次数。正如你所看到的,我们没有做任何事情,除了计算落在圆圈内的随机点,然后用一个比率来近似pi的值。
蒙特卡洛强化学习
用于强化学习的蒙特卡罗方法直接从经验事件中学习,而没有任何MDP过渡的先验知识。这里,随机成分是回报或奖励。
需要注意的是,它只能应用于偶发性MDP,为什么?
原因是在我们计算任何回报之前,一点小插曲必须终止。在这里,我们不会在每个动作之后进行更新,而是在每一片段之后进行更新。它使用最简单的想法 – 值是每个状态的所有样本轨迹的平均回报。
回顾本文讨论的多臂老虎机( multi-armed bandits)的想法,每个状态都是一个单独的多臂老虎机问题,其想法是立即为所有多臂老虎机提供最佳表现。
与动态编程类似,有一个策略评估(找到给定随机策略的值函数)和策略改进步骤(找到最优策略)。我们将在接下来的两节中介绍这两个步骤。
蒙特卡洛策略评估
这里的目标再次是从策略pi下的经验片段中学习价值函数vpi。回想一下,回报是总折扣奖励:
S1,A1,R2,… .Sk~pi
还记得值函数是预期的回报:
我们知道,只需将样本相加并除以样本总数,我们就可以估算出任何预期值:
- i – 片段指数
- s – 状态指数
问题是我们如何获得这些样本回报?为此,我们需要释放一些片段并生成它们。
每一集我们都会有一系列的状态和奖励。从这些奖励中,我们可以根据定义计算回报,这只是所有未来奖励的总和。
First Visit Monte Carlo:在一个片段中 s 第一次被访问的平均回报率。
以下是该算法如何工作的分步视图:
- 初始化策略,状态值函数
- 首先根据当前策略生成片段
- 跟踪通过该片段遇到的状态
- 在2.a中选择一个状态
- 将首次出现此状态后收到的回报添加到列表中
- 所有回报的平均值
- 将状态值设置为计算的平均值
- 重复步骤3
- 重复2-4直到满意为止
Every visit Monte Carlo:在某一片段中中每次访问 s 的平均回报。
对于这个算法,我们只需将步骤#3.a更改为“将每次出现此状态后收到的返回添加到列表中”。
让我们考虑一个简单的例子来进一步理解这个概念。假设我们有一个环境,2个状态 – A和B.假设我们观察了2个样本集:
A + 3 => A表示从状态A到状态A的转换,奖励+3。让我们使用两种方法找出值函数:
增量平均值
将均值回报转换为增量更新是很方便的,这样每个片段都可以更新均值,我们可以理解每段的进度。在解决多臂老虎机问题时,我们已经学会了这一点。
我们在片段之后逐步更新v(s)。对于每个状态St,返回Gt:
在非固定问题中,跟踪运行平均值是有用的,即忘记旧片段:
V(S t)←V(S t)+α(G t – V(S t))
Monte Carlo Control
与动态编程类似,一旦我们拥有随机策略的值函数,仍然存在的重要任务是使用蒙特卡罗找到最优策略。
回想一下DP中的策略改进公式需要环境模型,如下面的等式所示:
该等式通过找到最大化奖励总和的动作来找出最优策略。然而,这里的一个主要警告是它使用转换概率,这在无模型学习的情况下是未知的。
由于我们不知道状态转移概率p(s’,r / s,a),我们不能像DP一样进行前瞻搜索。因此,所有信息都是通过玩游戏或探索环境的经验获得的。
通过使策略相对于当前值函数的policy greedy来完成策略改进。在这种情况下,我们有一个action-value函数,因此不需要模型来构造greedy policy。
如果没有正确探索大多数行动,greedy policy(如上所述)将始终倾向于采取某种行动。有两种解决方案:
蒙特卡洛探索开始
在该算法中,所有状态动作对都具有非零概率的起始对。这将确保每一片段将agent带到新的状态,因此,对环境的探索更多。
蒙特卡洛与epsilon-soft
如果环境有一个单一起点(例如国际象棋游戏)怎么办?在这种情况下,探索开始不是正确的选择。回想一下,在一个多臂老虎机问题中,我们讨论了 epsilon-greedy approach.。
确保持续探索的最简单的想法以非零概率1尝试所有动作 – epsilon选择最大化动作值函数的动作并且用概率epsilon随机选择动作。
现在我们了解了蒙特卡罗控制和预测的基础知识,让我们在Python中实现该算法。我们将从流行的OpenAI Gym工具包导入冻结湖泊环境。
Python中的蒙特卡罗实现
Frozen Lake Environment
代理控制角色在网格世界中的移动。网格的一些瓷砖是可行走的,而其他瓷砖则导致药剂掉入水中。另外,代理的移动方向是不确定的,并且仅部分地取决于所选择的方向。Agent因找到目标图块的可行走路径而获得奖励。
使用如下网格描述表面:
(S:起点,安全),(F:冻结表面,安全),(H:洞,落入你的厄运),(G:目标)
我们的想法是通过仅在冰面上行走并避开所有洞来从起点到达目标。有关OpenAI Gym的安装详细信息和文档,请访问此 链接。
首先,我们将定义一些辅助函数来设置蒙特卡罗算法。
创造环境
import gym import numpy as np import operator from IPython.display import clear_output from time import sleep import random import itertools import tqdm tqdm.monitor_interval = 0
随机政策的功能
def create_random_policy(env): policy = {} for key in range(0, env.observation_space.n): current_end = 0 p = {} for action in range(0, env.action_space.n): p[action] = 1 / env.action_space.n policy[key] = p return policy
用于存储状态动作值的字典
def create_state_action_dictionary(env, policy): Q = {} for key in policy.keys(): Q[key] = {a: 0.0 for a in range(0, env.action_space.n)} return Q
功能播放片段
def run_game(env, policy, display=True): env.reset() episode = [] finished = False while not finished: s = env.env.s if display: clear_output(True) env.render() sleep(1) timestep = [] timestep.append(s) n = random.uniform(0, sum(policy[s].values())) top_range = 0 for prob in policy[s].items(): top_range += prob[1] if n < top_range: action = prob[0] break state, reward, finished, info = env.step(action) timestep.append(action) timestep.append(reward) episode.append(timestep) if display: clear_output(True) env.render() sleep(1) return episode
用于测试策略和输出胜率的功能
def test_policy(policy, env): wins = 0 r = 100 for i in range(r): w = run_game(env, policy, display=False)[-1][-1] if w == 1: wins += 1 return wins / r
首次访问蒙特卡洛预测和控制
def monte_carlo_e_soft(env, episodes=100, policy=None, epsilon=0.01): if not policy: policy = create_random_policy(env) # Create an empty dictionary to store state action values Q = create_state_action_dictionary(env, policy) # Empty dictionary for storing rewards for each state-action pair returns = {} # 3. for _ in range(episodes): # Looping through episodes G = 0 # Store cumulative reward in G (initialized at 0) episode = run_game(env=env, policy=policy, display=False) # Store state, action and value respectively # for loop through reversed indices of episode array. # The logic behind it being reversed is that the eventual reward would be at the end. # So we have to go back from the last timestep to the first one propagating result from the future. for i in reversed(range(0, len(episode))): s_t, a_t, r_t = episode[i] state_action = (s_t, a_t) G += r_t # Increment total reward by reward on current timestep if not state_action in [(x[0], x[1]) for x in episode[0:i]]: # if returns.get(state_action): returns[state_action].append(G) else: returns[state_action] = [G] Q[s_t][a_t] = sum(returns[state_action]) / len(returns[state_action]) # Average reward across episodes Q_list = list(map(lambda x: x[1], Q[s_t].items())) # Finding the action with maximum value indices = [i for i, x in enumerate(Q_list) if x == max(Q_list)] max_Q = random.choice(indices) A_star = max_Q # 14. for a in policy[s_t].items(): # Update action probability for s_t in policy if a[0] == A_star: policy[s_t][a[0]] = 1 - epsilon + (epsilon / abs(sum(policy[s_t].values()))) else: policy[s_t][a[0]] = (epsilon / abs(sum(policy[s_t].values()))) return policy
现在,是时候运行这个算法来解决8×8冻湖环境并检查奖励:
在运行50,000片段时,我们得到0.9分。随着更多的片段,它最终达到了最优策略。