引用来自ShangtongZhang的代码chapter05/blackjack.py
实现了基于Monte Carlo方法的三种算法:
1、基于Monte Carlo方法的策略预测,根据给出的策略计算state-value
2、使用exploring starts的训练方法,得出state-action-value,以及对应的优化policy π
3、使用off-policy的重要取样方法预测state-value,并和期望值对比
问题描述
blackjack是一个常见的赌场游戏,规则如下:
牌组和牌值:总的牌堆由7到8组牌去掉大小王组成,所以不用考虑牌数量问题;其中2-10是原值,J-K当做10使用,A分两种情况,可以等于1 or 11。
规则:游戏开始,发牌的兄弟给玩家和庄家各发2张牌,玩家的牌是亮出来的明牌,庄家的是一张明牌一张暗牌,如果玩家当场数值和到21了(natural),即拿到了一张A和一张10(或者J-K),如果庄家没有达到21,那么判庄家输(这里不考虑赌金什么的),反正则平局;如果玩家没有到21,可以选择继续要牌(hit)或者放弃要牌(strick or stand),如果超过21了就叫做爆牌(go bust),玩家就被判负;如果玩家strick1了,就轮到庄家发牌了,庄家一般会按照这样的方法来决策(也可以不这样,这里是一种规定吧):如果牌值<17,就hit,在17-21之间stand,如果庄家goes bust,庄家就被判负;如果庄家stand了,就比较双方的总牌值,大的一方为胜方。
这里使用policy:玩家hit当牌值<20,20-21就stand。
monte carlo算法本身不难理解,但是这个blackjack问题可以说是目前接触到的最复杂的强化学习问题了。首先是state的理解,state=[usable_ace_player, player_sum, dealer_card1],其中usable_ace_player指的是player是否将A用作11,player_sum指的是Player牌组总值,dealer_card1指的是庄家亮出的明牌。
算法中使用了很多blackjack游戏的游戏技巧,比如上面提到的一些策略,还有state的选取,增加了理解难度。
所以这个问题主要理解monte carlo算法工作原理,具体的技巧可以选择性忽略。
引入模块,并定义action常量,hit=继续抽,stand=停止
1 | import numpy as np |
为player定义policy,属于游戏技巧,也是本例的一个参考,通过最终学到的policy和这里的POLICY_PLAYER对比来比较算法的优劣
1 | # policy for player |
两个待定函数,这里的target_policy_player和behavior_policy_player的工作方式是理解off-policy Monte Carlo算法的关键
1 | # use for off-policy method |
为庄家定义policy,属于游戏技巧
1 | # policy for dealer |
Monte Carlo方法的关键一步,通过模拟游戏来获得sample
1 | # get a new card |
使用on-policy策略训(yu)练(ce)
算法伪代码:
其实给的target_policy才是last boss,这里只是使用Monte Carlo的方法来预测了一下,看看结果是不是符合的。
这里有一个比较有意思的地方感觉可以讨论一下:
算法的最后一部分,也就是循环的最小部分,即对value的更新,因为blackjack问题本身就是state不重复的,所以first-visit和every-visit是一样的;其次就是G,这个G是被累加了,因为这里S_t是按照索引减小的方向移动的,而且注意符号,这里累加了R_{t+1},这个形式和上一章的MDP问题中的表达式是一致的,关于这里R的索引是n还是n+1,我觉得也有必要提一下。因为在第二章Bandit问题里,R是用的n:
而且这里对value的更新也是从前往后的,state是有限的;
而第4章DP的时候,R使用的就变成n+1了:
而且也变成反向更新了(虽然形式上仍是正向写的code,但是事实上最先确定的是最终的state-value)
仔细看的话会发现,其实这两个问题还是差别挺大的,首先说下bandit问题把。Bandit问题是通过学习找到最优的平均收益,所以n控制的试验次数,所以其实本质上它只计算了一个value值,就是最终目标是为了收敛到最优的action,即找到reward期望最大的action。
MDP问题中的t代表的是state随step出现的时刻,即使一般问题可能不满足MDP的马尔科夫性,但是对于一个普遍的多状态序列决策问题,t时刻的state对应的value肯定是其后时刻states的value的一种和的形式(通常会考虑discount如果问题是continuing task)。所以两个问题本质就是不同的,不能混淆。
再谈谈另一个问题,就是伪算法最里层计算state-value的时候,计算顺序是沿着t减小的方向的,因为这样有利于迭代,其实也是一种DP的思路。但是这个blackjack问题有它的特殊性,就是每一步action的reward是设为0的,只有最后的结果才是有reward的。这样设计是有道理的,避免达到次优点嘛。
看到这里我不禁想到,Monte Carlo方法和MDP其实原理是相通的,都利用的DP的思路来解决问题,我记得在MDP那一章也讲过这个问题,在原书46页:
1 | # Monte Carlo Sample with On-Policy |
绘制图表
1 | def figure_5_1(): |
100%|██████████| 10000/10000 [00:00<00:00, 51795.15it/s]
100%|██████████| 500000/500000 [00:08<00:00, 55705.87it/s]
使用exploring-start训练
算法的伪代码如下:
注意其中几个要点:
在Monte Carlo模拟的时候,即play函数里,使用的player_policy是greey的;
初始状态是explore的,并保证每个初始state和action组合都有概率出现
1 | # Monte Carlo with Exploring Starts |
绘制图表
1 | def figure_5_2(): |
100%|██████████| 500000/500000 [00:31<00:00, 15708.47it/s]
使用off-policy策略训练(预测)
通过计算Ordinary Importance Sampling 和 Weighted Importance Sampling并和target_policy训练的结果比对,分别计算均方误差来测试算法的性能。
重点是两个重要取样的意义和计算:
重要取样(Importance Sampling)的推导是通过MDP得到的,它是一种off-policy方法中通过behavior policy来训练target policy的重要途径,其中一个重要的概念就是重要取样比率(importance-sampling ratio),它的定义式是target policy下的状态轨迹的π(a|s)和behavior policy下的状态轨迹的b(a|s)的比值。具体推导如下:
首先定义通过behavior policy取样得到的states-chain的联合概率,这里的进一步计算使用了马尔科夫独立性假设:
然后给出重要取样比率的定义:
其中的状态转移概率p被消去了,可以证明这个结论虽然是通过马尔科夫独立性推出的,却具有一般性。
好了,现在我们基本理解了重要采样比率的概念,那么它到底有什么用那?下面的公式解释了这个问题(具体推导不清楚QAQ):
这个值是联系episode的return和target policy π下的v_π(s)的关键!
所以很直观的从这个期望公式引出ordinary importance sampling:
但是有一个问题,就是如果重要采样因子有可能是方差无限的,这时我们近似得到的state-value就会发散而无法收敛,这个问题很严重,随后也会通过代码来解释。
所以通过对采样因子做归一化,引出了Weighted importance sampling的概念:
和前者相比,虽然weighted importance sampling是有偏的(bias),因为它不是直接从上面的期望公式来的,但是经过多次迭代bias会趋于0。但是如果采样因子的方差不是有限的,
ordinary importance sampling就很有可能无法收敛,也就是说它的方差(variance)是高于weighted importance sampling的,所以总的来说后者更常用。
1 | # Monte Carlo Sample with Off-Policy |
绘制图表
1 | def figure_5_3(): |
100%|██████████| 100/100 [00:20<00:00, 4.87it/s]