资源 | 跟着Sutton经典教材学强化学习中的蒙特卡罗方法(代码实例)

用动态规划去解决强化学习的相关问题基本够了,但还是有很多限制。比如,你知道现实世界问题的状态转移概率吗?你能从任意状态随机开始吗?你的MDP是有限的吗?

好消息是,蒙特卡罗方法能解决以上问题!蒙特卡罗是一种估计复杂的概率分布的经典方法。本文部分内容取自Sutton的经典教材《强化学习》,并提供了额外的解释和例子。

初探蒙特卡罗

蒙特卡罗模拟以摩纳哥的著名赌场命名,因为机会和随机结果是建模技术的核心,它们与轮盘赌,骰子和老虎机等游戏非常相似。

相比于动态规划,蒙特卡罗方法以一种全新的方式看待问题,它提出了这个问题:我需要从环境中拿走多少样本去鉴别好的策略和坏的策略?

这一次,我们将再引入回报的概念,它是长期的预期收益:

7a9e899a2c0e492cc7248ec15acd32daf5951fb4

有时,如果环节不收敛,那么我们使用折扣因子:

1fa2e13d0688056a698483d6e5a5753c75b00a0a

我们将这些回报Gt和可能的At联系起来试图推导出:

89b3952d1e8b4f9d2d41f82793e3011c1029341f

根据大数定律,当N逼近∞时,我们能够得到准确的期望。我们记i次模拟下标为i。

现在,如果这是一个马尔科夫决策过程(MDP)(99%的强化学习问题都是),那么我们知道它展现出了强马尔科夫性质,也即:

d5d45a6a708195e068fb343e284a03cfe939bba0

有了这些,我们可以很容易推导得到这样一个事实,即期望中的是完全无关的,从现在开始,我们将使

G s指示从某个状态开始的回报(移动那个状态到t=0)。

解决值函数的一种经典方式是对第一次s的发生的回报进行采样,也叫首次访问蒙特卡罗预测。那么一个找到最优V的一个有效的算法如下:

pi=init_pi()

returns=defaultdict(list)


for i in range(NUM_ITER):


 episode=generate_episode(pi)#(1)

 G=np.zeros(|S|)

 prev_reward=0

 for (state,reward) in reversed(episode):

 reward+=GAMMA*prev_reward

 #breaking up replaces s eventually,

 #so we get first-visit reward.

 G[s]=reward

 prev_reward=reward

 for state in STATES:

 returns[state].append(state)

 V={state:np.mean(ret) for state, ret in returns.items()}

另一种方法是每次访问蒙特卡罗预测,也就是你在每个环节中每一次发生s的回报都进行采样。在两种情况下,估计均成平方收敛于期望。

在蒙特卡罗方法的背景下,策略迭代的核心问题是,正如我们之前说过的,如何确保探索和开采?

一种补救大状态空间探索的方法是,明确我们从一个特定的状态开始并采取特定的行动,对所有可能性采用轮循方式对它们的回报采样。这假定我们可以从任何状态出发,在每一环节的开始采取所有可能的行动,这在很多情况下不是一个合理的假设。然而对于像21点纸牌游戏这样的问题,这是完全合理的,这意味着我们可以很容易地解决我们的问题。

在以下代码中,我们只需要对我们之前的代码(1)做一个快速的补丁:

# Before(Start at some arbitrary s_0,a_0)

episode=generate_episode(pi)

# After(Start at some specifics s,a)

episode=generate_episode(pi,s,a)

# loop through s, a at every iteration.

在线策略ε-贪婪策略

如果我们不能假设我们可以从任何状态开始并采取任意行动那怎么办呢?好吧,那么,只要我们不要太贪婪或者探索所有的状态无穷次,我们仍然可以保证收敛,对吗?

以上是在线策略方法的主要属性之一,在线策略方法试图去改善当前运行试验的策略,与此同时,离线策略方法试图改善一种不同于正在运行试验的策略的策略。

说到这里,我们要规则化“不要太贪婪”。一种简答的方法是使用我们之前学过的k臂老虎机-ε-贪婪方法。回顾一下,我们以ε的概率从一个给定状态下所有行动的均匀分布中挑选,以1-ε的概率我们选argmaxtq(s,a)行动。

现在我们问:对于蒙特卡罗方法,这是否收敛到最优π*?答案是它会收敛,但不是收敛到那个策略。

我们从q和一个ε-贪婪策略π(s)开始:

a7dcf16974184e8f169ab7b99918cbeaad64d37f

再一次,我们得到这个结论ε-贪婪策略,跟其他贪婪策略一样,对于Vπ执行单调的改进。如果我们回退到所有的时间步,那么我们得到:

36e4fdbc80cd2d02e5fd450da3d3817d5f37d372

这就是我们想要的收敛性。

然而,我们需要去发现这个策略实际上收敛到什么。显然,即使最优策略是确定性的,我们的策略也被迫是随机的,不能保证收敛到π*。然而,我们可以修订我们的问题:

假定不是我们的策略保持以概率ε的随机性一致选择行动,而是环境不管我们的策略的规定随机选择一个行动,那么,我们能够确保一个最优解。证明的大纲在(1)中显示,如果等式成立,那么我们π=π,因此我们有Vπ=Vπ于环境,这个等式在随机性下是最优的。

离线策略:重要性采样

让我们介绍一些新的术语!

  • π是我们的目标策略。我们正努力优化它的预期回报。

  • b是我们的行为策略。我们使b产π以后会用到的数据。

  • π(a|s)>0⇒b(a|s)>0 ∀a∈A。这是收敛的概念。

离线策略方法通常有2个或者多个智能体,其中一个智能体产生另一个智能体需要的数据,我们分别叫它们行为策略和目标策略。离线策略方法比在线策略方法更异想天开,就像神经网络之于线性模型更异想天开。离线策略方法往往更强大,其代价是产生更高的方差模型和较慢的收敛性。

现在,让我们讨论重要性采样。

重要性采样回答了这个问题:“给定Eπ[G],Eπ[G]是什么?”换句话说,你怎样使用从b的采样中获得的信息去决定π的期望结果。

一种你能想到的直观方法就是:“如果b选择a很多,π选a很多,那么b的行为对于决π的行为是很重要的!”,相反:“如果b选择a很多,π不曾选择a,那么b在a上的行为π在a上的行为没有什么重要性”,很有道理,对吧?

所以这差不多就知道重要性采样的比率是什么概念了。给定一个轨迹3eb8bffe92dbcb07bb782ff858f59d7fb074ed26,在给定策略π的条件下,这条准确的轨迹发生的概率为:

53f64fc683417f1f01eb5f8c395f0fe2028ba88b

π和b之间的比率是:

2c2c959c62c680f327d0492bc439323f45c50cf3

普通的重要性采样

现在,有很多方法可以利用e61130d61882bc6287aac37eda7bf1022c993f59给我们一个Eπ[G]的很好的估计。最基础的方法是利用普通的重要性采样。假定我们采样了N个环节:

1be29bf3a73322154e14b0c4eda70a1324d99cfa

定义S的第一次到达时间为:

18203ef8e161d2039bb955e034758710badc5a23

我们想要估计Vπ(s),那么我们可以使用经验均值去通过首次访问方法估计值函数:

d424669d0d42c5dd81e097efa0c78e06277e5f34

当然,这可以很容易地推广到每次访问方法,但是我想用最简单的形式来表达我的意思。这说明我们需要不同的方式来衡量每一环节的收益,因为对于π更容易发生的轨迹相比那些永远不会发生的需要赋予更多的权重。

这种重要性采样的方法是一种无偏估计量,但它存在极大的方差问题。假定第k个环节的重要性比率

a63aac261e699211b903206107a22caa9850bb88

是1000,这个数很大,但是确实可能发生。这是不是意味着奖励也要1000倍甚至更多呢?如果我们只有一个环节,我们的估计是准确的。从长远来看,因为我们有一个乘法关系,导致比率可能会爆炸式增长或者消失。这对于估计来说,有点令人担忧。

加权重要性采样

为了减小方差,一种简单直观的方法就是减少估计的大小,通过除以重要比率的大小的总和(有点像柔性最大激活函数):

6ac1a58399ccd9540ce0294895a8ec87c7f33468

这叫做加权重要性采样,它是一种有偏估计(偏差渐进趋于0),但是减小了方差。在此之前,我们能够得到一个普通估计量的病态无界方差,但是这里的每个元素的最大权值都是1,通过此限制了方差有界。Sutton建议,在实践中,总是使用加权重要性采样比较好。

增值实现

与许多其它采样技术一样,我们可以逐步实现它。假设我们使用上一节的加权重要性采样方法,那么我们可以得到一些如下形式的采样算法:

6f639aa92f66cfe79ab89608aadec719720bac04

其中Wk可以是我们的权重。

我们想基于Nn来构造Nn+1,这是可行的。记Cne4171a011b1e64c4921ef76d918e70c50c37343f,我们会以如下方式持续更新这个计算和:

61a9bc7557385959f32d31719c13774ab050c720

Cn的更新规则非常明显:

9d16f660b59736809719c8da76e8e6c477e3dffb

现在Vn是我们的值函数,但是一个非常相似的类比也可以应用到我们的行为Qn

当我们更新值函数的时候,我们也能更新我们的策略π,我们能够用虽旧但是很好用argmaxtq(s,a)来更新π。

折扣意识重要性采样

到目前为止,我们已经计算了回报,并采样了回报得到了我们的估计。然而我们忽视了G的内部结构。它真的只是折扣奖励的求和,我们未能将它纳入到比率中ρ。折扣意识重要性采样将γ建模为终止的概率。环节的概率在一些时间步t终止,因此必须是一个几何分布geo(γ):

5261ab5d04647f032b27d45f78d41ddbc2283533

全部回报可以认为是对随机变量Rt求期望:

e31d6f8103d1e43f6962589f86c46b78e1f2d32a

可以构造一个任意的裂项求和如下:

c8df9736a4f2aa6e4b017a569177fb2bdd3a0e04

以此类推,我们可以看到,令k从x处开始,那么我们有γx

2e11b59bbca90010829ba2632af4492ec7d2d717

将上式代入G得到:

36e210739c83b945472e3e7e414cd3e1290d25df

这将导致Rt项中的等效系数1,γ,γ2等。这就意味着,我们现在能够分解Gt,并且在重要性采样比率中使用折扣。

现在,回忆我们之前得到的:

c0d82609c6be7cdc61b06aaa4c78996f37d8352c

如果我们扩展G,我们会有:

5908b2e8edc3cd86d9cfa1f8d32b6bffb8c7969f

注意我们是怎样在所有的回报中使用相同的比率的。一些回报,Gt.t+1,被整个轨迹的重要性比率相乘,这在模型假设:γ是终止概率下是不正确的。直观上,我们想要给Gt.t+1Pt.t+1,这很容易:

6feb5b63aab86e0666f6f4880c332e25bdf414f8

啊,好多了!这样,每个部分回报都有他们正确的比率,这极大解决了无界方差问题。


单个奖励重要性采样

另一种缓解p和它的方差问题的方式,我们可以将G分解为各个奖励,然后做一些分析,让我们研究一下Pt.T-1Gt.T

7f39594eb4a10bbd5bb3dc77db812337521da023

对于每一项,我们有Pt.T-1γkRt+k+1。扩展p,我们发现:

f921bf142d58589bae870db0622c6ef88033bc71

在没有常数γk的情况下求期望:

1a42975839bb08e7bbad38325cf094b8b58aea8f

记住E(AB)=E(A)E(B)当且仅当它们是独立的。显然根据马尔科夫性质,任意π(Ai|Si)和b(Ai|Si)都是独立于Rt+k+1,(如果i≥t+k+1),且(b也一样)。由此我们能够将它们分离出来,从而得到:

bb7dd6ca167e8d921581d6e66b91397ea4623dcd

所以我们可以完全忽略后半部分,从而得到:

739cb6296741f79d818758854cce9a1baadaca93

这是什么意思呢?我们完全可以用期望来表示最初的和:

fc8beaaa19df09ff106caafc9dfad64e16018089

这又一次将减少我们估计量的偏差。

Python中的在线策略模型

因为蒙特卡罗方法通常都是相似的结构。我在Python中创建了一个离散蒙特卡罗类,可以用来插入和运行。

代码下载:

https://github.com/OneRaynyDay/MonteCarloEngine



"""


General purpose Monte Carlo model for training on-policy methods.

"""


from copy import deepcopy


import numpy as np






class FiniteMCModel:


 def __init__(self, state_space, action_space, gamma=1.0, epsilon=0.1):


 """MCModel takes in state_space and action_space (finite) 


 Arguments

 ---------

 state_space: int OR list[observation], where observation is any hashable type from env's obs.

 action_space: int OR list[action], where action is any hashable type from env's actions.

 gamma: float, discounting factor.

 epsilon: float, epsilon-greedy parameter.

 If the parameter is an int, then we generate a list, and otherwise we generate a dictionary.

 >>> m = FiniteMCModel(2,3,epsilon=0)

 >>> m.Q

 [[0, 0, 0], [0, 0, 0]]

 >>> m.Q[0][1] = 1

 >>> m.Q

 [[0, 1, 0], [0, 0, 0]]

 >>> m.pi(1, 0)

 1

 >>> m.pi(1, 1)

 0

 >>> d = m.generate_returns([(0,0,0), (0,1,1), (1,0,1)])

 >>> assert(d == {(1, 0): 1, (0, 1): 2, (0, 0): 2})

 >>> m.choose_action(m.pi, 1)

 0

 """


 self.gamma = gamma


 self.epsilon = epsilon

 self.Q = None

 if isinstance(action_space, int):


 self.action_space = np.arange(action_space)


 actions = [0]*action_space


 # Action representation


 self._act_rep = "list"


 else:


 self.action_space = action_space


 actions = {k:0 for k in action_space}


 self._act_rep = "dict"


 if isinstance(state_space, int):


 self.state_space = np.arange(state_space)


 self.Q = [deepcopy(actions) for _ in range(state_space)]


 else:


 self.state_space = state_space


 self.Q = {k:deepcopy(actions) for k in state_space}


 


 # Frequency of state/action.

 self.Ql = deepcopy(self.Q)

 def pi(self, action, state):

 """pi(a,s,A,V) := pi(a|s)


 We take the argmax_a of Q(s,a).

 q[s] = [q(s,0), q(s,1), ...]

 """


 if self._act_rep == "list":


 if action == np.argmax(self.Q[state]):


 return 1


 return 0


 elif self._act_rep == "dict":


 if action == max(self.Q[state], key=self.Q[state].get):


 return 1


 return 0


 def b(self, action, state):


 """b(a,s,A) := b(a|s) 


 Sometimes you can only use a subset of the action space

 given the state.





 Randomly selects an action from a uniform distribution.


 """


 return self.epsilon/len(self.action_space) + (1-self.epsilon) * self.pi(action, state)


 def generate_returns(self, ep):


 """Backup on returns per time period in an epoch


 Arguments

 ---------

 ep: [(observation, action, reward)], an episode trajectory in chronological order.

 """


 G = {} # return on state


 C = 0 # cumulative reward


 for tpl in reversed(ep):


 observation, action, reward = tpl


 G[(observation, action)] = C = reward + self.gamma*C

 return G


 def choose_action(self, policy, state):


 """Uses specified policy to select an action randomly given the state.


 Arguments

 ---------

 policy: function, can be self.pi, or self.b, or another custom policy.

 state: observation of the environment.

 """


 probs = [policy(a, state) for a in self.action_space]


 return np.random.choice(self.action_space, p=probs)


 def update_Q(self, ep):


 """Performs a action-value update.


 Arguments

 ---------

 ep: [(observation, action, reward)], an episode trajectory in chronological order.

 """


 # Generate returns, return ratio


 G = self.generate_returns(ep)


 for s in G:


 state, action = s


 q = self.Q[state][action]

 self.Ql[state][action] += 1


 N = self.Ql[state][action]


 self.Q[state][action] = q * N/(N+1) + G[s]/(N+1)


 def score(self, env, policy, n_samples=1000):


 """Evaluates a specific policy with regards to the env.


 Arguments

 ---------

 env: an openai gym env, or anything that follows the api.

 policy: a function, could be self.pi, self.b, etc.

 """


 rewards = []


 for _ in range(n_samples):


 observation = env.reset()


 cum_rewards = 0


 while True:


 action = self.choose_action(policy, observation)


 observation, reward, done, _ = env.step(action)

 cum_rewards += reward

 if done:


 rewards.append(cum_rewards)


 break


 return np.mean(rewards)


if __name__ == "__main__":


 import doctest


 doctest.testmod()

如果你想在不同的库中使用它,可以自己尝试一下。

举例:21点纸牌游戏

在这个例子中,我们使用OpenAI的gym库。在这里,我们使用一个衰减的ε-贪婪策略去解决21点纸牌游戏:

import gym

env = gym.make("Blackjack-v0")





# The typical imports


import gym


import numpy as np


import matplotlib.pyplot as plt


from mc import FiniteMCModel as MC






eps = 1000000


S = [(x, y, z) for x in range(4,22) for y in range(1,11) for z in [True,False]]


A = 2


m = MC(S, A, epsilon=1)


for i in range(1, eps+1):


 ep = []


 observation = env.reset()

 while True:


 # Choosing behavior policy

 action = m.choose_action(m.b, observation)





 # Run simulation

 next_observation, reward, done, _ = env.step(action)

 ep.append((observation, action, reward))

 observation = next_observation

 if done:


 break






 m.update_Q(ep)

 # Decaying epsilon, reach optimal policy

 m.epsilon = max((eps-i)/eps, 0.1)






print("Final expected returns : {}".format(m.score(env, m.pi, n_samples=10000)))






# plot a 3D wireframe like in the example mplot3d/wire3d_demo


X = np.arange(4, 21)


Y = np.arange(1, 10)


Z = np.array([np.array([m.Q[(x, y, False)][0] for x in X]) for y in Y])


X, Y = np.meshgrid(X, Y)





from mpl_toolkits.mplot3d.axes3d import Axes3D






fig = plt.figure()

ax = fig.add_subplot(111, projection='3d')


ax.plot_wireframe(X, Y, Z, rstride=1, cstride=1)


ax.set_xlabel("Player's Hand")


ax.set_ylabel("Dealer's Hand")


ax.set_zlabel("Return")


plt.savefig("blackjackpolicy.png")


plt.show()

当没有可用的A时,我们得到一个非常漂亮的图形,(网络中Z为Flase)。

9033ab9ee36cf0746a566290de81bc8d34e91eda

我也写了一个模型的快速的离线策略版本,还没有润色过,因为我只想得到一个性能基准,这是结果:

Iterations: 100/1k/10k/100k/1million.

Tested on 10k samples for expected returns.

On-policy : greedy


-0.1636


-0.1063


-0.0648


-0.0458


-0.0312


On-policy : eps-greedy with eps=0.3


-0.2152


-0.1774


-0.1248


-0.1268


-0.1148


Off-policy weighted importance sampling:


-0.2393


-0.1347


-0.1176


-0.0813


-0.072

举例:悬崖行走

对代码的更改实际上非常小,因为正如我所说的,蒙特卡罗采样是与环境无关的,我们修改了这部分代码(除去绘图部分):

# Before: Blackjack-v0

env = gym.make("CliffWalking-v0")

# Before: [(x, y, z) for x in range(4,22) for y in range(1,11) for z in [True,False]]

S = 4*12

# Before: 2

A = 4

们运行gym库得到了Eπ[G]为-17.0。还不错!悬崖行走问题是一幅一些区域为悬崖一些区域为平台的地图。如果你在平台上行走,你的每一步将获得-1的奖励,如果你从悬崖上摔下来,将得到-100的奖励,将你降落在悬崖上时,你会回到起点,不管地图多大,每一个环节-17.0似乎是近乎最优的策略。

99638a90b0a4babe4fe6ddf2bcbe742985e107d6

我们可以看到,蒙特卡罗方法对于计算任意行为和观察空间具有诡异的概率分布的任务的最优值函数和行为值是一种很好的技术。在未来,我们会考虑蒙特卡罗方法更好的变体,但是这也是强化学习基础知识中的一块伟大的基石。


原文发布时间为:2018-06-12

本文作者:Ray Zhang

本文来自云栖社区合作伙伴“大数据文摘”,了解相关信息可以关注“大数据文摘”。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值