一、算法简介
深度蒙特卡洛算法是一种使用深度神经网络来进行蒙特卡洛估计的强化学习算法,它最早于2020年在《DouZero: Mastering DouDizhu with Self-Play Deep Reinforcement Learning》被提出用于解决斗地主问题。
深度蒙特卡洛算法使用深度网络拟合每个时刻,智能体状态和采取每种动作的价值函数,即Q value,所以其属于value base 类方法。
1.网络结构
以斗地主问题为例,编码网络由两部分组成,1.对(合法的)出牌动作的编码。2.对玩家手牌和历史出牌信息的编码。
出牌动作编码网络用于对出牌动作对应的矩阵进行特征提取,它的输入是一种出牌动作对应的矩阵,这种出牌动作可以来自当前智能体的所有合法的出牌组合,这种设计的优点有 1.使得模型不需要动作掩码就能对非法动作进行屏蔽,2.适用于于较大动作空间的情况,并且不会因为动作空间的增大而导致参数量明显增加。3.模型能对智能体做出的动作有更深的理解,因为动作也具有了丰富特征,而不仅仅一种one-hot向量。这种对动作的深刻理解有益于其对未曾见过的动作的估计的泛化。如3KKK的牌型很好,即使未见过动作3JJJ,其也能对动作价值有良好的评估。
玩家手牌和历史出牌信息的编码网络用于编码玩家当前持有的手牌,和其余玩家已经打出的手牌,记住已经打出的手牌对于斗地主之类部分可观测的马尔可夫决策过程有重要意义。
两种编码网络对信息进行提取后最终会合并输入一个全连接网络再次进行深层特征提取后最终输出一个形如 1x1 的值,由于编码网络中已经含有状态S和可能采取的动作A,故编码网络输出的值可以作为当前状态s下采取动作a的Q值。遍历所有合法的动作a,就可得agent在当前状态下采取任意动作的Q值。
2.算法更新方式
2.1采样
DMC算法采样类似于DQN,也使用epsilon-greedy方法:以epsilon 的概况v采取随机动作,以1-epsilon的概率采取Q值最大的动作。
2.2损失函数
不同于DQN的每一步获得奖励,自举的对Q值的估计,DMC算法只在每局结束时获得所有的奖励。DMC使用均方差 MSE loss对Q值进行更新。这使得DMC的Q值是无偏估计,但同时也有着高方差的问题,在原文中通过并行采样缓解。
二 使用DMC解决“吹牛”游戏
1.游戏规则
两个玩家、每人三个骰子、游戏开始后,两人同时投骰子,每个人只能看自己的点数。根据自己的点数,轮流喊出自己认为的两人骰子的最大公约数。如先手喊出 2个6,代表两人的六个骰子中至少有两个6点的骰子,后手要么喊出比先手更大的公约数,如3个6,4个5等,要么选择不相信,然后进入结算阶段,如果结算阶段发现两个人骰子中至少有2个6点骰子,则先手胜如果没有则后手胜利。
另外,点数为1的骰子可以作为任意点数参与结算。
2.状态空间和动作空间
动作空间很明确,从1个1 到6个6,外加一个不相信,共37个动作
状态空间,在未结算阶段,由自己的点数和历史动作组成,在结算阶段由自己和对方点数和历史动作组成。
3.环境代码
可以适配DMC算法的吹牛环境代码如下,实现了 reset step等方法,可以对动作和状态编码,根据结局胜负给双方正负奖励。
class BoastingEnv:
def __init__(self, objective):
self.env_id_ran = random.randint(0, 100)
self.objective = objective
self.last_action = None
self.agent_names = ["firsthand", "secondhand"] # AGENT_NAMES#['agent0','agent1']
self.acting_player_position = None
self.player_0 = [1, 5, 2]#[random.randint(0, 5) for i in range(3)]
self.player_1 = [0, 5, 1]#[random.randint(0, 5) for i in range(3)]
self.action_history = np.zeros((4, 6, 7)) # ((6 * 6 + 1, 2))
self.end_action = np.zeros((6, 7))
self.end_action[0, 6] = 1
# self.action_queue
self.infoset0 = {"player_position": "firsthand",
'pos_name': 'firsthand',
"legal_action": self.get_legal_action(),
"player_points": self.get_point_mat(self.obs2state(self.player_0)),
"other_points": np.zeros((6, 7)),
"card_play_action_seq": np.zeros((2, 6 * 7 * 2))}