应用CFR实现德州扑克对战

857 字
4 分钟
应用CFR实现德州扑克对战

引言#

反事实后悔最小化(Counterfactual Regret Minimization, CFR)是求解不完全信息博弈纳什均衡的经典算法。本文先用最小博弈 Kuhn Poker 给出完整 CFR 实现,再给出在德州扑克中的工程化落地思路(抽象与对手建模)。

1. CFR 核心思想#

  • 信息集(Information Set):在不完全信息下,玩家对状态的可观测等价类
  • 策略:在每个信息集上对可行动作的概率分布 σ(I)
  • 后悔值:选择动作 a 与平均策略相比的损失
  • CFR 迭代:累计正后悔值 R⁺(I,a),按比例归一化得到新策略(Regret Matching)

2. Kuhn Poker 简介#

  • 牌:3 张(J, Q, K),每人一张,先手/后手
  • 行动:过/跟/加注(精简为 bet/pass + call)
  • 结算:摊牌比大小或对手弃牌

3. Python 实现(Kuhn Poker CFR)#

import random
from collections import defaultdict
CARDS = ['J', 'Q', 'K']
class KuhnCFR:
def __init__(self):
self.regret_sum = defaultdict(lambda: [0.0, 0.0]) # 每信息集两动作: [pass, bet]
self.strategy_sum = defaultdict(lambda: [0.0, 0.0])
def get_strategy(self, info_set):
regrets = self.regret_sum[info_set]
# Regret-Matching
positive = [max(r, 0.0) for r in regrets]
normalizer = sum(positive)
if normalizer > 0:
strategy = [r/normalizer for r in positive]
else:
strategy = [0.5, 0.5]
# 累加平均策略
for i in range(2):
self.strategy_sum[info_set][i] += strategy[i]
return strategy
def get_average_strategy(self, info_set):
s = self.strategy_sum[info_set]
total = sum(s)
if total > 0:
return [x/total for x in s]
return [0.5, 0.5]
def cfr(self, cards, history, p0, p1):
plays = len(history)
player = plays % 2
opponent = 1 - player
# 终止判断
if len(history) >= 2:
terminal_pass = history[-1] == 'p'
double_bet = history[-2:] == 'bb'
if terminal_pass:
# p 或 bp
if history == 'pp':
return 1 if cards[player] > cards[opponent] else -1
else:
return 1
elif double_bet:
return 2 if cards[player] > cards[opponent] else -2
# 信息集:牌 + 历史
info_set = cards[player] + history
strategy = self.get_strategy(info_set)
# 递归对手价值
if player == 0:
v_pass = -self.cfr(cards, history + 'p', p0*strategy[0], p1)
v_bet = -self.cfr(cards, history + 'b', p0*strategy[1], p1)
else:
v_pass = -self.cfr(cards, history + 'p', p0, p1*strategy[0])
v_bet = -self.cfr(cards, history + 'b', p0, p1*strategy[1])
v = strategy[0]*v_pass + strategy[1]*v_bet
# 反事实后悔
regrets = [v_pass - v, v_bet - v]
if player == 0:
reach = p1
else:
reach = p0
self.regret_sum[info_set][0] += reach * regrets[0]
self.regret_sum[info_set][1] += reach * regrets[1]
return v
def train(self, iterations=100000):
util = 0.0
deck = CARDS[:]
for _ in range(iterations):
random.shuffle(deck)
util += self.cfr(deck[:2], '', 1.0, 1.0)
print(f"Average game value for Player 0: {util/iterations:.4f}")
# 输出平均策略
strat = {I: self.get_average_strategy(I) for I in self.strategy_sum}
return strat
if __name__ == '__main__':
solver = KuhnCFR()
avg_strategy = solver.train(50000)
for I in sorted(avg_strategy.keys()):
s = avg_strategy[I]
print(I, { 'pass': round(s[0],3), 'bet': round(s[1],3) })

运行后,你将得到接近文献中的纳什近似策略。例如持 K 倾向下注、持 J 更倾向过牌等。

4. 扩展到德州扑克的工程实践#

CFR 在完整德州扑克上需要“抽象”以压缩庞大状态空间:

  • 信息集抽象:按公共牌纹理、权益(hand strength/board texture)、行动序列分桶
  • 行动抽象:将下注空间离散为固定档位(如 0.33pot/0.66pot/pot)
  • 公共牌抽样:Public Chance Sampling 或外部抽样(External Sampling CFR)
  • 策略表示:表格策略或函数逼近(NFSP/CFR+/Deep CFR 结合神经网络)
  • 加速:多进程自对弈,增量保存平均策略,CFR+ 使用正则化与截断加速收敛

实践建议:

  • 先在 Leduc Poker(小型公共牌博弈)上验证抽象与 CFR+ 实现
  • 逐步接入德扑回合(翻前/翻牌/转牌/河牌)与行动抽象
  • 评估用 exploitability 或与固定对手对战胜率衡量

5. 结语#

本文提供了可运行的 CFR 基础实现与向德州扑克落地的工程路线。下一步可引入 CFR+、外部抽样、函数逼近策略网络,实现规模化训练与对战。

🧠 文章思维导图

mindmap
引言#
1. CFR 核心思想#
2. Kuhn Poker 简介#
3. Python 实现(Kuhn Poker CFR)#
4. 扩展到德州扑克的工程实践#
5. 结语#

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!

打赏
应用CFR实现德州扑克对战
https://meiluosi.github.io/posts/2024-6-30-应用cfr实现德州扑克对战/
作者
枫语
发布于
2024-06-30
许可协议
CC BY-NC-SA 4.0

引用本文

@article{2024-6-30-应用cfr实现德州扑克对战,
  author  = {枫语},
  title   = {应用CFR实现德州扑克对战},
  journal = {枫语},
  year    = {2024},
  month   = {jun},
  url     = {https://meiluosi.github.io/posts/2024-6-30-%E5%BA%94%E7%94%A8cfr%E5%AE%9E%E7%8E%B0%E5%BE%B7%E5%B7%9E%E6%89%91%E5%85%8B%E5%AF%B9%E6%88%98/}
}

评论区

Profile Image of the Author
枫语
LLM 训练/推理 · 分布式系统 · RLHF
公告
LLM 训练/推理 · 分布式系统 · RLHF — 用 RL 的方法让大模型更聪明
音乐
封面

音乐

暂未播放

0:000:00
暂无歌词
分类
标签
站点统计
文章
33
分类
9
标签
15
总字数
75,837
运行时长
0
最后活动
0 天前
站点信息
构建平台
GitHub Actions
博客版本
v6.13.5
文章许可
CC BY-NC-SA 4.0

文章目录