用强化学习实现二十一点AI:从MDP建模到Q-learning实战
简介本资源是一套基于Python实现的强化学习教学实践项目面向机器学习初学者与算法爱好者聚焦于经典二十一点Blackjack游戏场景系统演示蒙特卡洛MC、SARSA及线性函数近似LFA三种主流强化学习算法的完整落地流程。压缩包共8个文件含6个核心Python模块如environment.py建模游戏环境、rl_algorithms.py封装算法逻辑、plotting.py可视化策略效果、1份README说明文档及1个预训练的MC值函数pickle文件整体仅14KB轻量易读、结构清晰便于逐模块理解状态转移、策略迭代与价值评估机制。已有1007人学习下载读者可直接运行main.py一键执行全部算法对比实验并通过内置绘图函数直观观察不同方法在胜率、收敛速度与策略稳定性上的差异同时获得可复用的环境抽象框架与策略设计模板。1. 为什么用强化学习打二十一点不是炫技而是练真功夫你有没有试过写一个“会思考”的二十一点玩家不是查表、不是固定策略而是让它在成千上万局对局中自己摸索出“什么时候该要牌、什么时候该停牌、什么时候该加倍”——这恰恰是强化学习最本源的用武之地在没有监督信号的环境中靠奖励反馈塑造行为策略。这个项目不是为了解决赌场盈利问题而是把 Blackjack 当作一个经典、可控、可复现的马尔可夫决策过程MDP沙盒状态空间小但结构清晰玩家点数庄家明牌是否含A动作集明确hit/stand/double奖励定义直接赢1、输−1、平局0。它适合本科生做毕设——代码量适中、数学门槛可控、结果可量化也适合刚入门RL的工程师练手——不碰图像、不调超参、不啃论文专注把Q-learning、SARSA、Monte Carlo这些基础算法从公式落到env.step(action)和agent.update()里。整套实现全部基于 Python零外部依赖仅需 numpy matplotlib所有环境建模、策略迭代、值函数可视化都封装在可读性优先的模块中答辩时能讲清每行关键逻辑部署时能单文件跑通全链路。2. 环境建模与状态-动作设计从纸面规则到可计算MDP2.1 二十一点规则的工程化翻译为什么状态不能只用“总点数”教科书里说“玩家点数≤16就hit”但这在RL里是毒药——它把状态压缩成单一标量抹杀了“软17”含A且算作11时总点数为17和“硬17”不含A或A只能算1的本质差异。真实决策依赖三元组状态player_sum当前手牌点数A按11或1动态计算取最大合法值dealer_showing庄家明牌2–11J/Q/K统一为10usable_ace布尔值表示玩家手牌中是否存在可算作11的A提示usable_aceTrue时玩家实际点数 player_sum但若再抽一张大牌导致爆牌A可自动降为1点数变为player_sum - 10。这个机制让状态空间从“点数17”分裂为两个策略迥异的状态必须显式编码。def _get_state(self, player_hand, dealer_hand): player_sum self._calculate_hand_value(player_hand) dealer_showing dealer_hand[0].value # 明牌 usable_ace self._has_usable_ace(player_hand) return (player_sum, dealer_showing, usable_ace)这段代码背后是三次关键判断_calculate_hand_value()需遍历手牌并动态决定A的计分方式_has_usable_ace()要验证A存在且未被强制降为1_get_state()最终返回元组而非字典——因为Q-table索引需要确定性哈希元组天然支持。2.2 动作空间与奖励函数如何让AI“痛恨”爆牌但“敢赌”加倍动作集定义为{0: stand, 1: hit, 2: double}但double有硬性约束仅当初始两张牌时允许且执行后强制抽一张牌并结束回合。这要求环境在step()中校验动作合法性def step(self, action): if action 2: # double if len(self.player_hand) ! 2: return self.state, -1, True, {} # 非法动作扣分并终止 self.player_hand.append(self._draw_card()) done self._is_busted(self.player_hand) reward self._get_reward() * 2 # 加倍奖励/惩罚 return self.state, reward, done, {} # ... 其他动作逻辑奖励设计遵循“即时反馈终局结算”双层结构每步无即时奖励避免稀释终局信号终局奖励赢1输−1平局0爆牌−1比输更重强化风险意识double成功时奖励×2让算法理解“高风险动作需匹配高回报预期”这种设计迫使Agent学习区分“安全hit”如软13对庄家2和“危险double”如硬11对庄家10而不是盲目追求高奖励。2.3 环境重置与随机种子为什么每次实验都要env.seed(42)Blackjack环境看似简单但隐藏着两个随机源牌堆洗牌顺序影响抽牌序列庄家补牌策略固定规则但起始牌随机若不固定随机种子同一策略在不同运行中胜率波动可能达±5%导致算法对比失效。项目中所有实验均采用env.seed(42)初始化并在训练循环外显式调用env BlackjackEnv() env.seed(42) # 必须在创建后立即调用 np.random.seed(42) # 同步numpy随机数这是血泪经验某次调试SARSA时胜率忽高忽低排查3小时才发现漏了env.seed()而np.random.seed()无法控制环境内部的random模块实例。3. 算法实现对比Q-learning、SARSA与Monte Carlo的落地差异3.1 Q-learning离线策略的“后悔药”机制Q-learning的核心是用贪婪策略更新但用ε-greedy策略采样——即“我实际怎么走”和“我理论上最优怎么走”是两回事。这带来关键优势即使当前策略很烂比如总是hit也能通过观察“如果我站在那里选最优动作会得到什么”来修正Q值。更新公式为$$Q(s,a) \leftarrow Q(s,a) \alpha [r \gamma \max_{a} Q(s,a) - Q(s,a)]$$实现时需注意两点max_a Q(s,a)必须在s的所有合法动作中取最大而非全动作空间double在s可能非法学习率α需随训练步数衰减否则后期Q值震荡。项目中采用α max(0.01, 0.5 * (0.999 ** episode))# Q-learning update核心逻辑 next_q_max np.max([q_table[next_state][a] for a in env.get_legal_actions(next_state)]) q_table[state][action] alpha * ( reward gamma * next_q_max - q_table[state][action] )3.2 SARSA在线策略的“边走边学”SARSA与Q-learning仅差一个符号它用实际执行的下一个动作a替代max_a Q(s,a)公式为$$Q(s,a) \leftarrow Q(s,a) \alpha [r \gamma Q(s,a) - Q(s,a)]$$这意味着SARSA的更新完全依赖当前策略ε-greedy的采样结果。当ε较大时它更保守——因为更新依据的是“我大概率会选的动作”而非“理论上最好的动作”。项目中SARSA的ε衰减比Q-learning更慢ε 0.95 ** episode以维持探索强度避免过早收敛到次优策略。3.3 Monte Carlo无模型的“终局复盘”MC不依赖状态转移只靠完整回合轨迹更新。其优势在于无需γ折扣即可处理终局奖励Blackjack天然满足但代价是方差大、收敛慢。项目采用首次访问MCFirst-Visit MC对每个状态-动作对只使用该回合第一次出现时的回报更新。# MC episode记录与更新 episode [] # [(state, action, reward), ...] # ... 游戏结束后 G 0 for state, action, reward in reversed(episode): G reward gamma * G if (state, action) not in seen_pairs: returns[(state, action)].append(G) q_table[state][action] np.mean(returns[(state, action)]) seen_pairs.add((state, action))关键细节G从终局反向累加确保每步的G是“从该状态开始直到游戏结束的总回报”seen_pairs去重保证首次访问。3.4 算法性能对比为什么Q-learning在10k局内就碾压MC在相同超参α0.1, γ0.9, ε初值0.9下三者胜率曲线呈现典型分层算法1k局胜率5k局胜率10k局胜率收敛稳定性Q-learning42.3%44.8%45.7%高波动0.5%SARSA41.1%44.2%45.1%中波动1.2%Monte Carlo38.5%42.6%43.9%低波动2.8%原因直指算法本质Q-learning的max操作提供强引导快速逼近最优策略边界SARSA因遵循当前策略更新受ε扰动收敛略慢但策略更鲁棒MC需等待整局结束才能更新早期大量无效轨迹如爆牌拉低平均回报且无法利用中间状态价值注意这不是贬低MC。若将Blackjack扩展为“多副牌洗牌频率可调”MC的无模型特性反而成为优势——它不需要预知状态转移概率而Q-learning/SARSA的γ和α需针对新环境重新调优。4. 策略可视化与评估从Q-table到可解释决策树4.1 Q-table热力图看懂AI的“直觉”原始Q-table是三维数组状态×动作直接阅读困难。项目提供plot_q_values()函数将player_sum12–21和dealer_showing2–11作为坐标轴绘制三个子图分别显示stand/hit/double的Q值def plot_q_values(q_table, titleQ-value Heatmap): fig, axes plt.subplots(1, 3, figsize(15, 4)) for i, action_name in enumerate([stand, hit, double]): # 提取该动作下所有状态的Q值reshape为10x10矩阵 values np.array([[q_table[(p,d,False)][i] for d in range(2,12)] for p in range(12,22)]) im axes[i].imshow(values, cmapRdBu_r, aspectauto) axes[i].set_title(f{action_name} Q-value) axes[i].set_xlabel(Dealer Showing) axes[i].set_ylabel(Player Sum) plt.colorbar(im, axaxes[i])这张图揭示了算法学到的深层规律stand热力图中玩家点数≥17时Q值普遍高于其他动作尤其对庄家弱牌hit热力图在player_sum12对庄家2–6时出现深红色高Q值印证“庄家易爆玩家应激进”double热力图仅在player_sum10/11且庄家明牌≤6时亮起符合经典策略4.2 策略矩阵生成把Q-table编译成if-else规则为验证算法可解释性项目提供extract_policy_matrix()函数将Q-table转换为二维策略矩阵行player_sum列dealer_showing值为最优动作编号def extract_policy_matrix(q_table): policy np.zeros((10, 10), dtypeint) # 10x10: sum12-21, dealer2-11 for p_idx, player_sum in enumerate(range(12, 22)): for d_idx, dealer_showing in enumerate(range(2, 12)): state (player_sum, dealer_showing, False) if state not in q_table: # 处理不可达状态如sum12含usable_ace state (player_sum, dealer_showing, True) policy[p_idx, d_idx] np.argmax(q_table[state]) return policy输出矩阵可直接与经典Blackjack策略表比对。例如当player_sum16, dealer_showing10时Q-learning策略矩阵输出0stand而MC可能输出1hit——这暴露了MC因样本不足对“硬16对庄家10”这一高风险场景学习不充分。4.3 多维度评估协议不止看胜率单一胜率指标易掩盖问题。项目定义四维评估体系维度计算方式合格阈值诊断意义胜率赢局数 / 总局数≥45.0%基础性能爆牌率玩家爆牌局数 / 总局数≤15.0%风险控制能力double利用率double动作次数 / 总动作数8–12%对高回报机会的把握策略一致性连续100局中相同状态选择相同动作的比例≥95%收敛稳定性评估脚本evaluate_agent.py自动运行10k局并输出四维报告。某次调试中发现爆牌率高达22%追查发现_calculate_hand_value()未正确处理A的双重计分修复后爆牌率降至13.2%胜率反升0.8%——证明风控与收益正相关。5. 避坑指南强化学习新手最容易翻车的5个点5.1 状态编码遗漏usable_ace让AI永远学不会软17现象训练10k局后player_sum17, dealer_showing6时仍频繁hit胜率卡在42%不上升原因状态只用(player_sum, dealer_showing)二元组soft 17和hard 17被映射到同一状态但最优动作相反soft17应hithard17应stand解决强制加入usable_ace布尔维度状态空间从10×10100增至10×10×2200虽增大内存但策略精度跃升5.2double动作未做合法性校验导致Q值污染现象Q-table中double动作的Q值异常高10但实际执行时频繁报错原因step()函数未拦截非初始手牌的double请求直接进入抽牌逻辑引发IndexError或错误奖励解决在step()开头添加if action2 and len(self.player_hand)!2: return ...并返回负奖励让Q值自然衰减5.3 学习率α未衰减Q值在收敛后持续震荡现象胜率曲线在45%附近锯齿状波动10k局后标准差仍1.5%原因固定α0.1使后期更新幅度过大微小奖励扰动即可推翻已学策略解决采用指数衰减α max(0.01, 0.5 * (0.999 ** episode))确保10k局后α≈0.01更新趋于平滑5.4 Monte Carlo未做首次访问检查导致Q值高估现象MC策略在player_sum12时过度hit胜率低于Q-learning 2个百分点原因同一状态在单局中多次出现如反复hit未爆若对每次出现都更新Q值会重复计入同一局的高回报造成乐观偏差解决维护seen_states集合仅对每局中首次出现的状态-动作对更新5.5 评估时未重置环境seed测试结果不可复现现象同一模型两次评估胜率相差3%答辩时被质疑结果可靠性原因评估脚本未调用env.seed(42)每次运行抽牌序列不同解决在evaluate_agent.py开头显式设置env.seed(42)和np.random.seed(42)并注明“所有评估基于固定随机种子”6. 答辩与验收如何让教授/导师一眼看懂你的工作量6.1 答辩PPT的黄金结构3页讲清技术深度不要从“什么是强化学习”开始。直接切入第一页问题定义页用一张图展示Blackjack MDP三要素状态节点画三个圆圈标注player_sum/dealer_showing/usable_ace旁注“共200个可达状态”动作边从状态引出三条带标签箭头stand/hit/double在double旁加小字“仅初始手牌允许”奖励标注在终局状态旁写1/-1/0爆牌状态旁加红色−1第二页放算法对比热力图并排三张图标题分别为“Q-learning最优动作”、“SARSA最优动作”、“经典策略表”用红框标出三者一致的区域如硬20必stand和分歧点如软13对庄家2结论写“Q-learning与经典策略吻合度92%SARSA因保守性在5个边缘状态选择不同符合理论预期”。第三页是工程实现亮点列三点——“环境完全解耦BlackjackEnv类独立于算法可无缝替换为其他MDP”“Q-table自动维度管理支持任意状态空间无需手动reshape”“评估协议标准化四维指标覆盖性能、风控、策略质量”提示教授最怕看到“调了100个参数”。你要强调“所有超参均有理论依据”γ0.9来自Blackjack终局奖励无折扣需求ε初值0.9参考Sutton《RL》第5章推荐值α衰减率0.999经网格搜索在收敛速度与稳定性间取得平衡。6.2 代码审查清单答辩前必须自测的7个断言把以下检查写成test_robustness.py答辩前运行一次检查项断言代码不通过意味着状态空间完备性assert len(q_table) 200遗漏usable_aceTrue状态double动作拦截env.reset(); _, r, _, _ env.step(2); assert r 0未校验非法doubleQ值范围合理性assert all(-2 q 2 for q in q_table.values())奖励未归一化或γ过大策略矩阵无空洞assert not np.isnan(policy).any()不可达状态未设默认动作评估可复现性assert evaluate_agent(1000) evaluate_agent(1000)随机种子未固化动作合法性for s in states: assert len(env.get_legal_actions(s)) 0环境建模逻辑错误胜率基线达标assert evaluate_agent(10000)[0] 44.5核心算法未生效6.3 从那以后我每次交毕设都强制走一遍“三阶验证”第一阶环境验证——用env.render()人工玩10局确认规则如A计分、爆牌判定、庄家补牌100%符合描述第二阶算法验证——关闭学习用ε0的贪婪策略跑1000局胜率应≈42.2%理论随机策略基准否则环境或奖励有误第三阶收敛验证——训练曲线必须呈现“前期陡升→中期平缓→后期微调”三段式若10k局后胜率仍在爬升说明α衰减太慢或训练不足。这套流程让我避开过两次致命翻车一次是环境里庄家补牌逻辑少判了一条while dealer_sum 17另一次是MC更新时忘了G的反向累加顺序。现在我的习惯是——只要新增一行核心代码就立刻跑一遍三阶验证。希望帮到你。本文还有配套的精品资源点击获取