基础阶段(六)——蒙特卡罗(MC)方法

提示:转载请注明出处,若本文无意侵犯到您的合法权益,请及时与作者联系。


一、为什么要用蒙特卡罗方法?

在之前的学习中我们学习了一种有限MDP问题DP算法,我们已经提到这种算法是基于模型(Model-based)的算法,在实际问题中并不多见。

1.1 为什么DP算法是一种基于模型的算法?

这是因为Agent在计算价值函数时使用到了状态转移概率\large P^{a}_{s{s}'},我们可以简单理解为Agent已经知道了环境的特点,它在思考哪个动作最有利时已经知道了自己采取哪个动作环境会如何变化,即Agent已经是“知己知彼”的神人,但是再很多情况下,我们训练的Agent是一个从零开始的什么都不懂的小萌新,所以这种DP算法具备较大的局限性。

既然不能根据状态转移概率\large P^{a}_{s{s}'}来求解价值函数,那么我们就得引入一个新的求解方法。这就需要回到价值函数的定义

\large \LARGE V^{\pi }(s)= E_{\pi } [ G_{t}|s_{t}= s]

我们已经了解到,价值函数本质就是长期回报G期望值

那么如何求期望呢?在不知晓概率的情况下,就得通过多次样本采样的方法,

理论上我们需要进行无数次采样才能得到这个价值,但我们都知道现实中无法做到无数次求解。

所以这种采样求得的值永远只是真值的一个近似值,近似求解在现实的工程中是可行的,至于有多近似,就取决于这个采样次数优化算法。为此我们介绍今天的主角——蒙特卡罗(Monte Carlo)方法,简称MC方法

1.2 什么是蒙特卡罗方法?

“蒙特卡罗”是一个术语,名字来源于世界著名的赌城蒙特卡罗,而蒙特卡罗方法又叫统计模拟方法,它使用随机数(或伪随机数)来解决计算的问题,是一类重要的数值计算方法。举个简单例子:

假设我们需要计算一个不规则图形的面积,那么图形的不规则程度和分析性计算(比如积分)的复杂程度是成正比的。而采用蒙特卡罗方法是怎么计算的呢?首先你把图形放到一个已知面积的方框内,然后假想你有一些豆子,把豆子均匀地朝这个方框内撒,散好后数这个图形之中有多少颗豆子,再根据图形内外豆子的比例来计算面积。当你的豆子越小,撒的越多的时候,结果就越精确。

 在强化学习中我们用“蒙特卡罗”特指那些对完整的回报取均值的算法。

MC方法是一种基于经验的方法,即它只需要Agent可以生成状态转移的的一些训练样本,它最基本的核心思想就是:

求解某个特定状态的价值,就是对所有经过这个状态之后产生的回报的求解均值。随着越来越多的回报被观察到,平均值就会收敛于期望值。

强化学习中的蒙特卡罗方法定义在episode task上,所谓的episode task就是指不管采取哪种策略π,都会在有限时间内到达终止状态并获得回报的任务。比如玩棋类游戏,在有限步数以后总能达到输赢或者平局的结果并获得相应回报。

这里我将episode task理解为回合,如果我之后的叙述中出现了回合,可以尝试当作episode task

1.3 DP算法与MC方法的异同点?

相同点:

DP算法和MC方法都使用到了广义策略迭代(GPI)的算法思想:即通过策略评估策略改进两种机制不断逼近最优策略,策略评估评估出当前策略的动作价值,策略改进依据评估出的动作价值来改进当前策略。这也是我们先要学习DP算法的重要原因。

不同点:

DP算法需要环境生成所有可能转移的概率分布,需要Agent掌握环境的变化特性,即使Agent不去环境中测试也能大致估计出一个最优策略;MC算法则是一种基于经验的算法,它需要Agent不断地在环境中进行测试来生成大量数据从而生成最优策略。

DP算法中的价值函数更新是一种自举思想的应用,即当前状态的价值依赖于后继状态的价值;MC算法中的每个状态的价值计算都是独立的,计算一个状态的价值与状态个数无关。

综上所述,MC方法相比于DP算法具有如下优势:

一是MC方法不需要描述环境动态特性的模型,可以直接通过与环境的交互来学习最优策略。

二是MC方法可以通过数据仿真或者采样模型来学习,获取多幕序列数据是较为简单的。

二是某个状态的价值计算不依赖于其他状态。

二、蒙特卡罗方法

前面我们已经提到,MC方法和DP算法都使用了广义策略迭代的思想,即策略优化分为策略评估和策略改进两部分。

1.蒙特卡罗策略预测(估计)

DP算法中使用状态价值函数直接迭代计算当前状态的价值,在MC方法中我们只需简单的将从这个状态开始得到的多个回报平均即可。这是因为一个状态就是累计回报的期望,使用累计回报的均值就可以得到一个较好的近似值。

下面我们结合代码来说明如何进行MC方法的策略评估。

1.1 获取某个episode的交互样本序列

前面我们已经了解到MC方法是一种episode task的算法,可以简单理解为它是一种回合制的,一个回合可以理解为玩了一局游戏,在每个回合中,它都会生成一组包含了每个时刻的基本信息(状态、动作、回报)的样本序列:

\large S_{0},A_{0},R_{1},S_{1},A_{1},R_{2},...,S_{T-1},A_{T-1},R_{T}

现在我们考虑如何用一个函数的代码描述上述的过程,假设我们有一个策略policy,该策略可以确定某个状态s下采取的动作a。

我们要实现一个函数,输入相应的policy,输出一个交互样本序列,这个交互样本序列的长度由两种情况确定:

1、Agent与环境的交互满足终止条件(环境给出提示,Agent直接接受到该信息即可)

2、Agent与环境一直没有达到终止条件则设置一个最大序列长度。

在每一个时刻,Agent根据策略policy和state获取action,将action传给环境,得到该时刻的后继状态、奖励和是否终止。

将当前状态state、采取的action和后继状态next_state作为一个数组存储到交互样本序列数组中。

该代码较为简单,直接给出,如下:

# 演示按照某个策略policy进行max_length次交互的回合
    def draw_one_episode(self, policy, max_length=1000):
        episode = [] # 该回合被定义一个数组,用来存储三元组(s,a,r)样本交互序列
        state = self.env.reset() # 重新初始化环境,返还一个随机初始化的状态
        # 进行1000次状态交互
        for t in range(max_length):
            action = policy(state) #按照策略policy,输入一个state得到一个动作action
            next_state, reward, done, _ = self.env.step(action) # 获取下一个状态,奖励,回合是否终止
            episode.append((state, action, reward))
            if done:
                break
            state = next_state
        return episode

需要注意到的是,上述代码中的初始状态state是由环境reset后给出的,这个state是随机获取的,至于为什么随机后面将给出答案。

1.1 首次访问和每次访问

现在我们已经结合代码知道了某个episode的交互序列:

\large S_{0},A_{0},R_{1},S_{1},A_{1},R_{2},...,S_{T-1},A_{T-1},R_{T}

在上述序列中,每次状态S的出现都称为对s的一次访问。在上述序列中,同一个状态s可能会被多次访问。

第一次访问s称为它的首次访问,s的首次访问算法用所有回合中的所有首次访问的累计回报的平均值来估计价值。

每次访问s称为它的每次访问,s的每次访问算法用所有回合中的所有访问的累计回报的平均值来估算价值。

这两种MC评估算法非常相似但却有着不同的理论基础。这里我们先结合代码谈论下MC首次访问的评估算法。

现在我们的任务是,给定交互序列数组episode,计算其中某个当前状态s的累计回报:

# 计算某个回合episode内某个状态s的累计回报
    def accumulate_state_reward_from_episode(self, s, episode):
        first_occur_idx = next(i for i, x in enumerate(episode) if x[0] == s) # 获取第一次出现状态s的索引
        reward = sum([x[2] * (DISCOUNT_FACTOR ** i)
                      for i, x in enumerate(episode[first_occur_idx:])])# 计算s时刻的累计回报
        return reward

上述python代码中使用了一些编程技巧等,这里不做讨论,算法思想比较简单,接着计算某个给定交互序列数组episode的其中某个当前状态s和动作a的累计回报

# 计算某个回合episode内某个状态的累计回报
    def accumulate_state_action_reward_from_episode(self, s, a, episode):
        first_occur_idx = next(i for i, x in enumerate(episode)
                               if x[0] == s and x[1] == a)
        reward = sum([x[2] * (DISCOUNT_FACTOR ** i)
                      for i, x in enumerate(episode[first_occur_idx:])])
        return reward

 上述两段代码的区别就是在数组episode中首次访问的时刻的条件不一样,前者只需要s符合即可,后者需要s和a都符合。

 现在我们就可以计算所有状态的价值了,当然这里区别,上述的每个样本序列episode中不一定访问到了所有的状态,对于一个特定的episode,我们只能计算其中访问过的状态的价值,这并不一定就是所有状态空间的价值。所以我们需要尽可能多的episode来覆盖所有的状态空间。

def estimate_state_value_first_visit(self, policy, num_episodes=100):
        state2reward = np.zeros(self.env.nS) #存储所有状态中每个状态的所有episode的累计回报
        state2count = np.zeros(self.env.nS) #存储所有状态的每个状态的所有episode的出现次数
        V = np.zeros(self.env.nS) # 状态个数的全零数组
        # 进行指定次数回合的训练
        for ith in range(1, num_episodes + 1):
            # 计算policy策略下模拟生成的样本序列
            episode = self.draw_one_episode(policy)
            # 生成当前episode里所有访问的状态的集合
            states_in_episode = set([x[0] for x in episode])
            # 计算访问状态集合的每个状态的状态价值:
            for s in states_in_episode:
                reward = self.accumulate_state_reward_from_episode(s, episode)
                state2reward[s] += reward
                state2count[s] += 1.0
                V[s] = state2reward[s] / state2count[s]
        return V

在上述代码中,我们使用了三个数组来分别存储所有状态中的每个状态的所有episode中的累计回报、出现次数和均值。

相应的求解动作价值的代码如下:

# 评估某个策略policy进行num_episodes次训练的首次访问的动作价值
    def estimate_state_action_value_first_visit(self, policy, num_episodes=100000):
        sa2reward = np.zeros((self.env.nS, self.env.nA))
        sa2count = np.zeros((self.env.nS, self.env.nA))
        Q = np.zeros((self.env.nS, self.env.nA))
        
        for ith in range(1, num_episodes + 1):
            episode = self.draw_one_episode(policy)
            
            sa_in_episode = set([(x[0], x[1]) for x in episode])
            for s, a in sa_in_episode:
                reward = self.accumulate_state_action_reward_from_episode(s, a, episode)
                sa2reward[s][a] += reward
                sa2count[s][a] += 1.0
                Q[s][a] = sa2reward[s][a] / sa2count[s][a]
        return Q

1.3 MC方法使用动作价值

在学习DP算法的过程中,我们学习了状态价值和动作状态价值(简称动作价值)。但是在MC方法一般偏向于使用动作价值。这是为什么呢?

因为在有模型的强化学习任务中,我们仅仅靠状态价值就可以确定一个策略:我们只需要简单地看到在选取某个动作后,后继状态和当前状态的状态价值之和最大,那么这个策略就是最优的。

但是在无模型的强化学习任务中,我们后继状态是不够稳定的,我们并没有精确的概率来得知后继状态的价值,我们必须使用更加精确的描述:在当前状态下采取某个动作的价值。

MC算法一般使用动作价值函数来进行策略评估和策略改进。

2、蒙特卡罗策略改进(学习)

经过策略评估后我们就得到了当前状态的价值,然后我们就要进行策略改进,也叫做策略学习。

策略改进就是在当前价值函数上贪心地选择动作,由于我们有动作价值函数,所以在贪心的时候完全不需要使用任何的模型信息。

常见的策略改进方式就是贪心策略:对于任意一个状态\large s\in S,必定选取对应动作价值最大的动作

\large \pi (s)=argmax q(s,a)

3、蒙特卡罗策略控制(迭代)

现在我们已经学习了策略评估和策略改进的方法,现在我们要将它们综合起来利用GPI的思想获得最优策略。

我们已经知道,想要使MC算法能够求解出最优策略,必须满足两个假设:

1、试探性出发。

2、在进行策略评估时有无限个episode的样本序列进行采样。

为了在实际工程化中满足上述两个条件,我们可以给出以下解决方案:

为了满足试探性出发假设,一般就是探索性初始化,即指定的“状态-动作”二元组作为起点开始一个episode的采样,同时保证所有的“状态-动作”二元组都有非零概率作为起点。

为了满足无限样本序列假设,我们需要在每个episode结束后就进行策略评估,然后在该episode访问到的每一个状态上进行策略改进,即逐个episode的进行评估与改进,与之前的值迭代思想相似。

试探性出发假设是比较难满足的,很多情况下我们还要考虑很难满足试探性出发假设的情况,这时我们一般有在线学习/同轨学习(On-Policy)离线学习/离轨学习(Off-Policy)两种方式。


参考资料

增强学习(四) ----- 蒙特卡罗方法(Monte Carlo Methods)

R.Sutton et al. Reinforcement learning: An introduction, 1998

猜你喜欢

转载自blog.csdn.net/qq_41959920/article/details/108955093
今日推荐