Monte Carlo CFR

在上一篇文章中,我们讲了经典的 CFR 算法的原理及实现。但是,它也有两个致命的缺陷,导致无法应用到实际的复杂博弈中:

  • 它需要遍历整个游戏树

  • 它需要知道对手的策略,这在实际情况下很难满足

本文将介绍基于 Monte Carlo 的改进算法。全部代码可以参考我的 github 项目:cfr-kuhn.

1 Monte Carlo CFR

核心思想是,在避免遍历整个游戏树的前提下,每个 information set 的每个 action 的 counterfactual regret 期望值保持不变。

\mathcal{Q} = \{Q_1, Q_2, ..., Q_r \} 是所有 terminal node Z 的子集,其中的每一个元素 Q_j 我们称为 block,每个 block 可能包含多个 terminal node 。令 q_j > 0 是选择 Q_j 的概率,满足 \sum_{j=1}^r q_j = 1。每次迭代,我们都会从这些 blocks 中采样一个并且只考虑在该 block 中的 terminal node 。

回忆 CFR 经典版的 counterfactual value 计算公式:

v_i(\sigma, I) = \sum_{z \in Z_I } \pi_{-i}^{\sigma} (z[I]) \pi ^{\sigma} (z[I], z) u_i (z)

在 MCCFR 中,对于 block Q_j 的 sampled counterfactual value 可以表示为:

v_i(\sigma, I | j) = \sum_{z \in Q_j \cap Z_I} \frac{1}{q(z)} \pi_{-i}^\sigma (z[I]) \pi^\sigma (z[I], z) u_i(z)

其中:

  • z 是 terminal node
  • z_I 表示 information set I 可以到达的 z 的全集
  • z[I] 表示某个可以到达 z 的 information set
  • i 表示玩家 id
  • j 表示 block id
  • q(z) 表示 z 所在 block 被选中的概率之和(一个 terminal node 可以被分到多个 block 中)

我们可以证明 counterfactual value 和 sampled counterfactual value 期望是相等的。

这就是 MCCFR 的基本思想了。事实上,CFR 可以看作 MCCFR 在 \mathcal{Q} = \{Z\}q_1 = 1.0 的特例。

此外,我们可以将每一个 block 选为 chance node 的一个分支。以 Kuhn Poker 为例,我们对于手牌 1,2,3 可以建立 3 个 block: \{ Q_1, Q_2, Q_3 \},分别包含手牌为 1,2,3 的所有 terminal nodes。从而,每个 block 的概率也自然为 \frac{1}{3}, \frac{1}{3}, \frac{1}{3}。这就是 chance-sampled CFR。

1.1 Outcome-Sampling MCCFR

在 outcome-sampling MCCFR 中,我们把每个 terminal node 作为一个 block。每次迭代,我们抽取一个 terminal node 并更新它的前缀 information set。一个 terminal node 出现概率越高,我们采样概率也越高。因此我们必须得到每个 terminal node j 出现的概率作为 q_j。如何得到呢?

在 CFR 中,我们在计算 regret 的时候,会计算到达每个 history 的概率。对于 terminal node j,这个概率其实就是采样概率 q_j

CFR 算法包含了两个方向的遍历:

  • 前向遍历。用于计算每个玩家从原点到达当前 history 的概率 \pi_i^{\sigma}(h)
  • 后向遍历。用于计算每个玩家从当前 history 进行到 terminal node 的概率 \pi_i^{\sigma}(h,z),在此过程中,还会计算 sampled counterfactual regrets。

后向遍历中计算 sampled counterfactual regrets 会分为两种情况。

其一是选择了能通向当前 terminal node z 的 action。即,在information set I 采取了行动 a,此时计算方式为:

\begin{align} r(I, a) &= v_i(\sigma_{I \rightarrow a}, I) - v_i(\sigma, I) \\ &= \frac{\pi_{-i}^\sigma (z[I]) \pi^\sigma (z[I]a, z) u_i(z)}{q(z)} - \frac{\pi_{-i}^\sigma (z[I]) \pi^\sigma (z[I], z) u_i(z)}{q(z)} \\ &= \frac{\pi_{-i}^\sigma (z[I]) \pi^\sigma (z[I] a, z) u_i(z)}{q(z)} ( 1 - \sigma(a|z[I])) \end{align}

上式比较难以理解的是:

\pi^\sigma (z[I] a, z) \cdot \sigma(a|z[I]) = \pi^\sigma (z[I], z)

结合定义,\pi^\sigma (z[I] a, z) 是从 I + a 到达 z 的概率,相比 \pi^\sigma (z[I], z) 多前进了一步(选择行动 a),在 I 选择 a 的概率是 \sigma(a|z[I]),因此,如果从 Iz 的概率为 p,由于 I + a 将选择 a 的概率由原本的 \sigma(a|z[I]) 变成了 1.0,它到 z 的概率就变成了 p / \sigma(a|z[I])

另一种情况是选择了其他行动,此时 I + a 不是 z 的前缀。此时的 sampled counterfactual value 是:

\begin{align} r(I, a) &= 0 - v_i(\sigma, I) \\ &= - \frac{\pi_{-i}^\sigma (z[I]) \pi^\sigma (z[I] a, z) u_i(z)}{q(z)} \sigma(a|z[I]) \end{align}

选取 0 是因为该动作的后悔值更新不归 z 管。必然有别的 terminal node 的前缀是 I + a,当采样到这些 terminal node 的时候自然会更新。

我们令:

w_I = \frac{\pi_{-i}^\sigma (z[I]) \pi^\sigma (z[I] a, z) u_i(z)}{q(z)}

由于在 outcome sampling 中,每个 terminal node z 都对应一个 block Q,因此 q(z) 就是选中 Q 的概率。理论上,为了保证采样的真实性,这个概率应该与所有玩家遵循策略 \sigma 进行游戏到达 z 的概率相同,即

q(z) = \pi^\sigma(z)

根据定义,\pi_{-i}^\sigma (z) 是对手到达 z 的概率,在计算时,假设玩家 i 以 1.0 的概率选择动作,而 \pi_i^\sigma (z) 相反。两者的乘积就是从局外旁观者看来到达 z 的概率。因此有:

\begin{align} q(z) &= \pi^\sigma(z) \\ &= \pi^\sigma(z[I]) \cdot \pi^\sigma(z[I], z) \\ &= \pi_i^\sigma (z[I]) \cdot \pi_{-i}^\sigma (z[I]) \cdot \pi^\sigma(z[I], z) \end{align}

带入得到:

\begin{align} w_I &= \frac{\pi_{-i}^\sigma (z[I]) \pi^\sigma (z[I] a, z) u_i(z)}{q(z)} \\ &= \frac{\pi^\sigma (z[I] a, z) u_i(z)}{ \pi_i^\sigma (z[I]) \cdot \pi^\sigma(z[I], z)} \\ &= \frac{\pi_i^\sigma(z[I]a, z) u_i(z)}{\pi_i^\sigma(z)} \\ &= \frac{u_i(z)}{ \pi_i^\sigma (z[I]) \cdot \sigma(a|z[I])} \end{align}

注意在这个表达式中,我们不再需要对手的信息 (只有带下标 i 的部分了)。我们在化简中刻意消去了对手 -i 相关的信息。

每次迭代累计后悔值会增加:

r(I,a) = \begin{cases} w_I \cdot (1 - \sigma(a|z[I]) & \text{if} ~ z(z[I]a) \sqsubset z \\ -w_I \cdot \sigma(a|z[I]) & \text{otherwise} \end{cases}

这样计算可以保证累计后悔值的期望是与传统 cfr 相同的。

每次迭代,每个动作的累计 strategy 增加:

s(I, a) = \pi_i^\sigma(z[I]) \cdot \sigma(a|z[I])

注:论文中还增加了 t - c_I 权重,即本次 iteration 和上次访问 I 的 iteration 之差,但是我加上权重后无法得到正确结果,尚需进一步研究。

1.2 实现细节

相比 cfr,mccfr 的不同主要有:

  1. 无需对手(-i)的信息
  2. 无需遍历所有行动
  3. 计算后悔值的方法不同,需要增加一个基于采样概率的权重。
  4. 需要引入 epsilon 保证探索所有 action
    fn mccfr(
        &mut self,
        history: kuhn::ActionHistory,
        cards: &Vec<i32>,
        reach_probs: HashMap<i32, f64>,
    ) -> f64 {
        // current active player
        let player_id = (history.0.len() % 2) as i32;
        let opponent_id = 1 - player_id;

        let maybe_payoff = kuhn::get_payoff(&history, cards);
        if maybe_payoff.is_some() {
            let payoff = match player_id {
                0 => maybe_payoff.unwrap(),
                1 => -maybe_payoff.unwrap(),
                _ => panic!("unexpected player id {}", player_id),
            };
            return payoff as f64;
        }

        // not the terminal node
        let info_set = InformationSet {
            action_history: history.clone(),
            hand_card: cards[player_id as usize],
        };

        if self.cfr_info.contains_key(&info_set) == false {
            self.cfr_info.insert(info_set.clone(), CfrNode::new(0.06));
        }

        let action_probs = self
            .cfr_info
            .get(&info_set)
            .unwrap()
            .get_action_probability();

        let chosen_action_id = sample(&action_probs);
        let chosen_action = kuhn::Action::from_int(chosen_action_id);
        let chosen_action_prob = action_probs[chosen_action_id as usize];
        let mut next_history = history.clone();
        next_history.0.push(chosen_action);
        // modify reach prob for SELF (not opponent)
        // update history probability
        let mut next_reach_probs = reach_probs.clone();
        *next_reach_probs.get_mut(&player_id).unwrap() *= chosen_action_prob;
        // recursive call
        // final payoff of the terminal node
        let final_payoff = -self.mccfr(next_history, cards, next_reach_probs);

        // update regret value
        let node = self.cfr_info.get_mut(&info_set).unwrap();
        for (action_id, action_prob) in action_probs.iter().enumerate() {
            let action = kuhn::Action::from_int(action_id);
            // reach probability of SELF (not opponent)
            let weight = final_payoff / reach_probs[&player_id] / action_prob;
            if action == chosen_action {
                node.cum_regrets[action_id] += weight * (1.0 - action_prob);
            } else {
                node.cum_regrets[action_id] += -weight * action_prob;
            }
        }

        // update strategy
        for (action_id, action_prob) in action_probs.iter().enumerate() {
            node.cum_strategy[action_id] += action_prob * reach_probs[&player_id];
        }

        return final_payoff;
    }

1.3 结果

Explore with epsilon=0.06. Average payoff = -0.05138

History Check Probability Bet Probability
1 0.805 0.195
1C 0.68 0.32
1B 0.97 0.03
1CB 0.97 0.03
2 0.97 0.03
2C 0.94 0.05
2B 0.56 0.44
2CB 0.47 0.53
3 0.422 0.578
3C 0.03 0.97
3B 0.03 0.97
3CB 0.03 0.97

We get 0.03 here because we choose epsilon = 0.06 and we have 2 actions to choose from.

Reference

  1. Monte Carlo Counterfactual Regret Minimization
  2. MCCFR technical report
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容