用Python玩转二十一点:蒙特卡洛方法实战指南(附完整代码)

张开发
2026/4/12 18:51:52 15 分钟阅读

分享文章

用Python玩转二十一点:蒙特卡洛方法实战指南(附完整代码)
用Python玩转二十一点蒙特卡洛方法实战指南附完整代码二十一点作为经典的赌场游戏其决策过程与强化学习中的序列决策问题高度契合。本文将带你用Python从零实现蒙特卡洛强化学习算法通过实战掌握价值预测与策略优化的核心技巧。我们将使用Gym库构建游戏环境并可视化不同策略下的胜率变化——这可能是你见过最直观的强化学习入门项目。1. 环境搭建与游戏规则解析在开始编写算法前我们需要先理解二十一点的游戏规则。这个游戏的胜负判定看似简单玩家和庄家轮流要牌谁的点数更接近21点谁就获胜。但其中的状态空间和动作选择却构成了一个典型的马尔可夫决策过程。安装必要的Python环境只需两行命令pip install gym numpy matplotlib二十一点的状态由三个关键要素构成玩家当前点数总和12-21点庄家明牌点数A-10是否有可用A可计为11点的A游戏中的动作空间只有两种选择要牌Hit获得一张新牌停牌Stand结束当前回合让我们用Gym库快速搭建游戏环境import gym env gym.make(Blackjack-v1) state env.reset() # 初始化游戏状态 print(f初始状态: {state}) # 示例输出: (15, 5, False)游戏状态示例解析15玩家当前点数5庄家明牌点数False没有可用A2. 蒙特卡洛预测算法实现蒙特卡洛方法的核心思想是通过大量随机采样来估计状态价值。与需要环境模型的动态规划不同它直接从游戏经验中学习。2.1 首次访问型MC预测我们首先实现经典的首次访问型算法它只计算每个状态在一局游戏中第一次出现时的回报from collections import defaultdict def mc_prediction(policy, env, num_episodes): returns_sum defaultdict(float) returns_count defaultdict(float) V defaultdict(float) for episode in range(num_episodes): # 生成一局游戏记录 episode_history [] state env.reset() while True: action policy(state) next_state, reward, done, _ env.step(action) episode_history.append((state, action, reward)) if done: break state next_state # 反向计算回报 G 0 visited_states set() for t in reversed(range(len(episode_history))): state, _, reward episode_history[t] G G reward if state not in visited_states: returns_sum[state] G returns_count[state] 1 V[state] returns_sum[state] / returns_count[state] visited_states.add(state) return V2.2 定义基础策略我们先测试一个简单策略当玩家点数≥18时停牌否则要牌def basic_policy(state): player_score, _, _ state return 0 if player_score 18 else 1 # 0停牌, 1要牌运行100万局游戏进行价值估计value_estimates mc_prediction(basic_policy, env, num_episodes1000000)2.3 可视化价值函数将三维状态空间投影到二维平面更直观import numpy as np import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D def plot_value_function(V, title): fig plt.figure(figsize(12, 6)) ax fig.add_subplot(111, projection3d) # 准备网格数据 player_range np.arange(12, 22) dealer_range np.arange(1, 11) X, Y np.meshgrid(player_range, dealer_range) # 提取有/无可用A的价值 Z_ace np.zeros_like(X) Z_no_ace np.zeros_like(X) for i in range(X.shape[0]): for j in range(X.shape[1]): Z_ace[i,j] V.get((X[i,j], Y[i,j], True), 0) Z_no_ace[i,j] V.get((X[i,j], Y[i,j], False), 0) # 绘制有可用A的情况 ax.plot_surface(X, Y, Z_ace, cmapviridis) ax.set_xlabel(Player Sum) ax.set_ylabel(Dealer Showing) ax.set_zlabel(Value) ax.set_title(f{title} (Usable Ace)) plt.show()3. 蒙特卡洛控制算法进阶预测算法告诉我们当前策略的价值而控制算法则能优化策略本身。我们实现三种经典方法3.1 试探性出发蒙特卡洛MCES这种方法通过在每局开始时随机选择初始状态和动作来保证充分探索def mces_control(env, num_episodes): Q defaultdict(float) returns_count defaultdict(float) policy defaultdict(int) for episode in range(num_episodes): # 随机初始状态和动作 state env.reset() action env.action_space.sample() # 生成完整对局 episode_history [] while True: next_state, reward, done, _ env.step(action) episode_history.append((state, action, reward)) if done: break state next_state action policy[state] if state in policy else env.action_space.sample() # 更新动作价值 G 0 visited set() for t in reversed(range(len(episode_history))): state, action, reward episode_history[t] G G reward if (state, action) not in visited: returns_count[(state, action)] 1 Q[(state, action)] (G - Q[(state, action)]) / returns_count[(state, action)] # 策略改进 policy[state] max([(Q[(state, a)], a) for a in [0, 1]], keylambda x: x[0])[1] visited.add((state, action)) return policy, Q3.2 ε-贪心同轨策略更实用的方法是在每一步都以ε概率随机探索def epsilon_greedy_policy(Q, state, epsilon0.1): if random.random() epsilon: return env.action_space.sample() return max([(Q.get((state, a), 0), a) for a in [0, 1]], keylambda x: x[0])[1]3.3 离轨策略与重要度采样当行为策略与目标策略不同时需要使用重要度采样比来修正def off_policy_mc(env, num_episodes): Q defaultdict(float) C defaultdict(float) policy defaultdict(int) for _ in range(num_episodes): # 行为策略生成对局完全随机 episode [] state env.reset() while True: action random.randint(0, 1) next_state, reward, done, _ env.step(action) episode.append((state, action, reward)) if done: break state next_state # 重要度采样更新 G 0 W 1.0 for t in reversed(range(len(episode))): state, action, reward episode[t] G G reward C[(state, action)] W Q[(state, action)] (W / C[(state, action)]) * (G - Q[(state, action)]) # 策略改进 policy[state] max([(Q.get((state, a), 0), a) for a in [0, 1]], keylambda x: x[0])[1] # 如果动作不是目标策略选择的提前终止 if action ! policy[state]: break W W * 2 # 行为策略随机概率为0.5倒数为2 return policy, Q4. 策略分析与优化实战经过500万局训练后我们得到最优策略的热力图import seaborn as sns def plot_policy(policy, title): # 准备策略矩阵 policy_matrix np.zeros((10, 10)) for player in range(12, 22): for dealer in range(1, 11): for ace in [True, False]: policy_matrix[player-12, dealer-1] policy.get((player, dealer, ace), 0) plt.figure(figsize(10, 6)) sns.heatmap(policy_matrix, annotTrue, fmtd, cmapYlOrRd, xticklabelsrange(1, 11), yticklabelsrange(12, 22)) plt.title(title) plt.xlabel(Dealer Showing) plt.ylabel(Player Sum) plt.show()观察最优策略我们会发现一些反直觉的决策点当玩家手牌为17点且庄家明牌为A时最优策略竟是要牌有可用A时在玩家点数13-17点间的决策更激进无可用A时对庄家弱牌2-6点可以更冒险这些发现验证了蒙特卡洛方法的价值——它能发现人类直觉可能忽略的微妙策略。

更多文章