为什么一个“AI玩马里奥”的视频能吸引几百万播放?很多人的第一反应是游戏画面有趣,或者解说幽默。但真正值得技术人关注的,是这类视频背后那个非常通用的问题:如何让一个对游戏一无所知的模型,从零开始学会在一个实时交互环境中做出正确决策。
Code Bullet 的“AI 学习玩马力欧”系列,恰好把这个问题压缩成了一个可以观察的完整闭环。很多人看完视频觉得“AI 好厉害”,其实这里面的核心方法并不神秘,也不依赖动辄几千张显卡的大规模训练。它真正展示的是:用进化策略 + 一个很简单的神经网络,就能让 AI 从“原地乱跳”慢慢进化到“懂得避开敌人、踩上砖头往前走”。
这篇文章不会停留在“看视频喊厉害”的层面,而是把这类项目拆开讲清楚:它用了什么算法,为什么这样做能学出来,以及你自己在本地怎么用 Python 搭一个简化版。即使你没看过原视频,也不影响阅读;如果你正打算做游戏 AI、强化学习入门,或者只是好奇“AI 怎么学会玩游戏的”,这篇文章可以帮你省掉大量试错时间。
1. 这类项目真正值得学习的地方
先给一个判断:Code Bullet 的价值不在于做出了一个多么无敌的游戏 AI,而在于他用最小成本证明了“进化策略 + 简单神经网络”可以解决实时动作游戏的策略学习问题。
很多人容易把这类项目和 AlphaGo 混为一谈。AlphaGo 用的是深度强化学习、蒙特卡洛树搜索、海量自我对弈,工程复杂度非常高;而 Code Bullet 做马里奥用的是一种更朴素的方式——遗传算法/进化策略。它没有复杂的梯度反传,没有大规模分布式训练,甚至不需要一个可微分的环境,核心就是“生成一堆候选策略 → 让它们玩游戏 → 留下表现好的 → 让它们变异产生下一代”。
这带来的启发是:如果你只是想让 AI 学会玩某个游戏、控制某个仿真环境、优化某个不太好建模的黑盒问题,进化策略往往比强化学习更容易落地。它实现简单,对奖励函数的噪声容忍度高,也不容易陷入“梯度消失”或“奖励稀疏导致完全学不动”的困境。
当然,它也有明显局限:进化策略不擅长利用海量时序数据做精细决策,训练到后期往往不如 PPO 这类强化学习算法精细。所以本文的定位不是“哪种算法更强”,而是帮你理解“这一类 AI 学习视频背后到底发生了什么”。
对 CSDN 读者来说,最实际的收益有三点:
- 理解 AI 玩游戏的技术框架,包括状态、动作、奖励、策略网络这几个核心组件。
- 掌握一种可以本地复现的简化实现,代码不复杂,普通电脑也能跑。
- 学会判断“AI 学会了没有”,而不是只看视频里的剪辑效果。
2. Code Bullet 的做法与强化学习核心概念
2.1 游戏 AI 的四个基本组成
任何“AI 学会玩游戏”的项目,都绕不开四个东西:
- 状态(State):AI 每时每刻能看到什么。对马里奥来说,就是当前的游戏画面,以及马里奥的位置、速度、是否踩在地面上等信息。
- 动作(Action):AI 能做什么。马里奥的动作就是手柄上的按键组合:左移、右移、跳跃、加速跑等。
- 奖励(Reward):怎么判断“做得好不好”。马里奥每向右移动一定距离,就给出正向奖励;撞到敌人死亡或者超时,则给出负向信号。
- 策略(Policy):AI 根据当前状态决定下一个动作的规则。在 Code Bullet 这类项目里,策略就是一个神经网络:输入屏幕像素,输出动作。
这四个概念是所有游戏 AI 的通用语言。理解它们之后,再看任何 AI 玩游戏的视频,你都能快速拆解出“它到底在学什么”。
2.2 进化策略的核心思路
Code Bullet 的马里奥 AI,本质上采用了一种非常直观的优化思路:
- 初始化一个由若干个体组成的种群,每个个体携带一组随机的神经网络参数。
- 让每个个体分别玩一局游戏,记录它取得的分数(累计奖励)。
- 删除表现差的个体,保留表现好的个体。
- 让保留的个体互相“繁殖”,即对它们的参数进行交叉和变异,得到下一代种群。
- 重复上述过程,直到 AI 能顺利通关或达到预期表现。
这里的“变异”通常是在参数上加一点高斯噪声,让下一代在优秀参数附近小幅探索。这个思路和生物进化非常相似:没有哪个个体知道“跳跃”这个动作的意义,但那些“碰巧在合适位置按下跳跃键”的个体获得了更高奖励,因此它们的参数被保留下来,并在后代中得到放大。
用代码表达,进化策略的训练循环大概是这样的:
for generation in range(total_generations): # 1. 评估当前种群 fitness = [evaluate(individual) for individual in population] # 2. 按表现排序,选前 K 个 sorted_idx = np.argsort(fitness)[::-1] elites = [population[i] for i in sorted_idx[:elite_num]] # 3. 生成下一代:精英保留 + 变异 new_population = elites.copy() while len(new_population) < population_size: parent = random.choice(elites) child = mutate(parent) new_population.append(child) population = new_population这个流程最迷人的地方在于:它完全不需要知道游戏规则,也不需要梯度信息。只要你能让 AI 玩一局游戏并返回一个分数,就能启动优化。
2.3 进化策略和强化学习的区别
很多人会把进化策略和强化学习混在一起,这里做一个简单对比:
| 对比项 | 进化策略 | 强化学习(如 PPO) |
|---|---|---|
| 核心思想 | 种群竞争、变异择优 | 智能体与环境交互,通过奖励反馈更新策略 |
| 是否需要梯度 | 不需要 | 需要 |
| 实现复杂度 | 低,代码量小 | 较高,需要经验回放、策略更新等机制 |
| 对奖励噪声的容忍度 | 较高 | 一般 |
| 样本效率 | 低,需要大量环境交互 | 相对较高 |
| 适合场景 | 黑盒优化、奖励稀疏、环境难以建模 | 复杂策略、长时间序列决策 |
Code Bullet 选择进化策略,一个重要原因就是实现门槛低、调试直观。你不需要搭建复杂的训练框架,用一个普通 Python 脚本就能跑起来。对于很多独立开发者来说,这是非常友好的入门路径。
2.4 为什么马里奥是很好的测试环境
超级马里奥是一个非常适合 AI 学习的测试环境:
- 它需要精确的时序控制:起跳早了会撞到敌人,跳晚了会掉进坑里,AI 必须学会“什么时候按跳”这个节奏。
- 它的状态空间复杂但不极端:画面是二维像素,理解起来比 3D 游戏简单,但足够表达“识别障碍物、规划跳跃时机”等能力。
- 它的奖励信号天然存在:向右移动就有奖励,死了就结束,很容易定义。
正因为如此,超级马里奥成了游戏 AI 领域的“Hello World”。从学术界到独立开发者,大量研究都在这个环境上用强化学习、进化策略、模仿学习等方式验证算法效果。
3. 环境准备与依赖安装
如果你想本地复现一个简化版“AI 学玩马里奥”,建议先搭好环境。以下环境信息以目前社区常用的开源方案为例,版本号请以实际安装结果为准,本文重点演示通用思路。
3.1 推荐环境
- 操作系统:Windows 10/11、Ubuntu 20.04 或 macOS 均可
- Python 版本:3.8 及以上
- 主要依赖:
gym、gym-super-mario-bros、nes-py、numpy、torch(可选,如果只用多项式策略也可以不用) - 硬件要求:普通 CPU 即可运行,不需要独立显卡;用 GPU 可以加速,但并非必需
gym-super-mario-bros是 OpenAI Gym 生态中专门用于马里奥游戏的环境包,它封装了 NES 模拟器,提供了标准化的强化学习接口。Code Bullet 原版视频更多是自己编写的模拟器交互脚本,但从工程复现的角度看,使用这个开源环境更简单、更稳定。
3.2 安装步骤
首先创建一个干净的 Python 虚拟环境,避免依赖冲突:
python -m venv mario_ai_env # Windows 激活 mario_ai_env\Scripts\activate # Linux / macOS 激活 source mario_ai_env/bin/activate然后安装依赖:
pip install gym-super-mario-bros pip install nes-py pip install numpy pip install torch --index-url https://download.pytorch.org/whl/cpu如果只需要 CPU 环境,建议安装 CPU 版 PyTorch,体积更小。如果你不想引入 PyTorch,也可以直接用 NumPy 实现一个简单策略网络,后面的示例会提到这种替代方案。
3.3 验证安装
在 Python 中执行以下代码,确认环境能正常创建:
import gym import gym_super_mario_bros from gym_super_mario_bros.actions import SIMPLE_MOVEMENT env = gym.make("SuperMarioBros-1-1-v0", render_mode="rgb_array", apply_api_compatibility=True) print("动作空间:", env.action_space) print("观测空间:", env.observation_space) # 重置环境 state, info = env.reset() print("初始状态形状:", state.shape) env.close()如果能看到动作空间和观测空间的输出,说明环境安装成功。这里需要注意的是,gym和gym-super-mario-bros的版本兼容性偶尔会出问题,遇到报错时优先检查 gym 版本。
4. 核心流程拆解
4.1 数据流:AI 看什么、做什么
在强化学习和进化策略项目中,运行一局游戏的循环是固定的:
环境输出画面 → AI 根据画面生成动作 → 环境执行动作 → 返回新画面和奖励 → 循环用代码表达就是:
state, info = env.reset() done = False total_reward = 0 while not done: action = policy.predict(state) # AI 决策 next_state, reward, terminated, truncated, info = env.step(action) total_reward += reward state = next_state done = terminated or truncated这个循环是整个项目的灵魂。无论后面的算法多复杂,最终都是在调整policy.predict这一步的参数,让 total_reward 越来越大。
4.2 状态预处理:把原始画面变成 AI 能理解的数据
原始 NES 游戏画面分辨率不高,但直接作为神经网络输入仍然有两个问题:一是数据量偏大,二是画面包含大量无关信息(比如背景云朵、计分板)。
常用的做法是:
- 灰度化:把彩色画面转成灰度图,减少通道数量。
- 缩放:把画面缩放到更小的尺寸,比如 84x84,减少参数数量。
- 帧堆叠:把最近的几帧堆叠在一起作为状态,让 AI 感知运动方向和速度。因为单张图片看不出马里奥是向左跑还是向右跑,而连续几帧能表达运动趋势。
这些预处理步骤看起来简单,但对训练效果影响非常大。Code Bullet 视频中之所以能较快看到 AI 学会移动,很大一部分原因是状态表示足够简洁,让神经网络把有限的容量集中在了“马里奥的位置、障碍物的位置”这些关键信息上。
4.3 奖励塑形:告诉 AI 什么重要
马里奥环境自带的奖励信号主要是“向右移动的距离”。这个设定非常巧妙:AI 不需要理解“过关”“躲避敌人”这些高维概念,只需要知道“向右走是好的,停在原地是不好的”,就能通过简单策略获得正向反馈。
不过在本地复现时,你可能还需要额外处理超时奖励。如果 AI 在某一关卡停留太长时间,即使没有死亡,也应该给一个负向奖励或直接结束本局,否则 AI 可能学会“原地站着不动”这种零奖励的消极策略。
奖励塑形是游戏 AI 项目中最容易低估的部分。很多人在训练中遇到“AI 一直不学习”的问题,八成不是神经网络有问题,而是奖励信号设计得不够清晰。马里奥环境默认已经解决了这个问题,这也是我推荐新手从这里入手的原因。
4.4 策略网络:用最简单的结构表达决策
Code Bullet 原版视频中的神经网络结构并不复杂,完全可以理解成一个“像素输入 → 简单神经网络 → 动作输出”的映射。用 PyTorch 实现时,一个两到三层的全连接网络就足够处理 84x84 的输入了。
更关键的一点是,如果你的输入状态是 84x84x4(四帧堆叠),直接展平成 28224 维向量可能对 CPU 训练有些压力。实际项目中可以先用更小的分辨率,比如 42x42,或者只在训练刚开始时使用单帧输入,跑通流程后再逐步增加复杂度。
4.5 进化策略的训练主循环
这是整个项目的核心。为了便于理解,我给出一版完整的简化实现思路,你可以在本地运行后观察 AI 的表现逐渐变化。
5. 完整示例代码实现
下面提供一个可以在本地运行的简化版本。由于完整训练需要的时间较长,这里用一个小分辨率和较小的种群规模演示流程,实际训练时你可以逐步加大。
5.1 文件一:mario_env.py —— 环境与预处理
# 文件路径:mario_env.py import gym import gym_super_mario_bros from gym_super_mario_bros.actions import SIMPLE_MOVEMENT from gym.wrappers import GrayScaleObservation, ResizeObservation, FrameStack def create_mario_env() -> gym.Env: """创建马里奥环境,并做基础预处理。""" env = gym.make( "SuperMarioBros-1-1-v0", render_mode="rgb_array", apply_api_compatibility=True, ) # 使用简洁动作集,共 7 个动作 env = gym.wrappers.ActionWrapper(env, SIMPLE_MOVEMENT) # 灰度化、缩放、帧堆叠 env = GrayScaleObservation(env, keep_dim=False) env = ResizeObservation(env, (42, 42)) env = FrameStack(env, 4) return env这段代码的核心是GrayScaleObservation和FrameStack。灰度化把彩色画面转成单通道,缩放把分辨率降到 42x42,帧堆叠让 AI 看到最近四帧的运动信息。SIMPLE_MOVEMENT包含以下动作:什么都不做、向右走、向右跳、向右跑、向右跑跳、原地跳、向左走。
需要注意的是,不同版本的gym对ActionWrapper的用法略有差异。如果运行时报错,可以直接使用环境返回的原始 Discrete 动作空间,然后在后续代码中做一个动作映射。
5.2 文件二:policy.py —— 策略网络与参数管理
这里给出两个版本:一个是 PyTorch 版本,一个是 NumPy 版本。PyTorch 版本更接近真实项目,NumPy 版本不依赖深度学习框架,更容易理解参数是怎么变异的。
# 文件路径:policy.py import numpy as np class SimplePolicy: """基于 NumPy 的简单策略网络。 输入:展开后的像素状态向量 输出:每个动作的分数,取分数最大的动作作为决策 """ def __init__(self, input_dim: int, hidden_dim: int = 64, n_actions: int = 7): # 两层全连接网络:input_dim -> hidden_dim -> n_actions self.w1 = np.random.randn(input_dim, hidden_dim) * 0.01 self.b1 = np.zeros(hidden_dim) self.w2 = np.random.randn(hidden_dim, n_actions) * 0.01 self.b2 = np.zeros(n_actions) def predict(self, state: np.ndarray) -> int: """根据状态输出动作。""" x = state.flatten() h = np.maximum(0, x @ self.w1 + self.b1) # ReLU logits = h @ self.w2 + self.b2 return int(np.argmax(logits)) def get_params(self) -> list: """返回所有参数,用于变异。""" return [self.w1, self.b1, self.w2, self.b2] def set_params(self, params: list) -> None: """设置参数,用于把变异后的参数写回策略。""" self.w1, self.b1, self.w2, self.b2 = params def mutate_params(params: list, noise_scale: float = 0.05) -> list: """对参数做高斯变异,返回新参数。""" new_params = [] for p in params: noise = np.random.randn(*p.shape) * noise_scale new_params.append(p + noise) return new_params def crossover_params(params_a: list, params_b: list) -> list: """简单交叉:每个参数按 50% 概率来自父本或母本。""" child = [] for p_a, p_b in zip(params_a, params_b): mask = np.random.rand(*p_a.shape) > 0.5 child.append(np.where(mask, p_a, p_b)) return childSimplePolicy的predict方法就是一个核心的前向推理:把像素展平,经过一个隐藏层和一个输出层,输出每个动作的分数。这个策略内部没有设定任何游戏规则,它不知道跳跃是什么、敌人是什么,只在训练中通过参数变异慢慢学会“什么样的输入对应什么样的动作”。
5.3 文件三:train.py —— 进化策略训练主循环
# 文件路径:train.py import numpy as np from mario_env import create_mario_env from policy import SimplePolicy, mutate_params, crossover_params POPULATION_SIZE = 20 ELITE_NUM = 4 GENERATIONS = 100 MAX_STEPS = 2000 NOISE_SCALE = 0.05 def evaluate(env, policy, max_steps=MAX_STEPS): """让一个策略玩一局游戏,返回累计奖励。""" state, info = env.reset() total_reward = 0.0 done = False steps = 0 while not done and steps < max_steps: action = policy.predict(np.array(state)) next_state, reward, terminated, truncated, info = env.step(action) total_reward += reward state = next_state done = terminated or truncated steps += 1 return total_reward def main(): env = create_mario_env() state_shape = env.observation_space.shape # 计算输入维度:42 * 42 * 4 input_dim = int(np.prod(state_shape)) print(f"输入维度:{input_dim}, 观测空间:{state_shape}") # 初始化种群 population = [] for _ in range(POPULATION_SIZE): policy = SimplePolicy(input_dim=input_dim) population.append(policy) for generation in range(GENERATIONS): # 1. 评估所有个体 fitness = [] for policy in population: # 关闭渲染,训练阶段不需要画面,能明显加快速度 reward = evaluate(env, policy) fitness.append(reward) # 2. 选择精英 sorted_idx = np.argsort(fitness)[::-1] elites = [population[i] for i in sorted_idx[:ELITE_NUM]] best_fitness = fitness[sorted_idx[0]] print(f"Generation {generation}: best reward = {best_fitness:.2f}") # 3. 生成下一代 new_population = [SimplePolicy(input_dim=input_dim) for _ in range(POPULATION_SIZE)] for i in range(ELITE_NUM): new_population[i] = elites[i] # 精英直接保留 for i in range(ELITE_NUM, POPULATION_SIZE): # 从精英中随机选两个,交叉 + 变异 parent_a = elites[np.random.randint(ELITE_NUM)] parent_b = elites[np.random.randint(ELITE_NUM)] child_params = crossover_params( parent_a.get_params(), parent_b.get_params() ) child_params = mutate_params(child_params, NOISE_SCALE) new_population[i].set_params(child_params) population = new_population env.close() if __name__ == "__main__": main()这段代码完整实现了进化策略的训练流程,每一代的流程是:
- 让当前种群里的每个个体分别玩一局游戏,得到累计奖励。
- 按奖励排序,取前
ELITE_NUM个作为精英。 - 下一代由精英直接保留和“交叉 + 变异”产生的新个体组成。
从打印结果可以看到,随着迭代次数增加,best reward 通常会逐渐上升。最开始时,AI 可能一动不动就死在原地,最佳奖励很低;几十代之后,它会慢慢学会向右移动、跳跃,甚至能躲过一些敌人。
5.4 文件四:demo.py —— 让训练好的 AI 可视化玩游戏
训练完成后,你想看到 AI 的实际表现,可以单独跑一个演示脚本。这里假设你已经把训练好的最优策略保存下来了(建议在训练循环中把精英个体的参数保存成.npy文件)。
# 文件路径:demo.py import numpy as np from mario_env import create_mario_env from policy import SimplePolicy def load_policy(input_dim: int, params_path: str) -> SimplePolicy: """从 .npy 文件加载参数并生成策略。""" policy = SimplePolicy(input_dim=input_dim) w1 = np.load(f"{params_path}_w1.npy") b1 = np.load(f"{params_path}_b1.npy") w2 = np.load(f"{params_path}_w2.npy") b2 = np.load(f"{params_path}_b2.npy") policy.set_params([w1, b1, w2, b2]) return policy def main(): env = create_mario_env() state_shape = env.observation_space.shape input_dim = int(np.prod(state_shape)) policy = load_policy(input_dim, "best_policy") state, info = env.reset() total_reward = 0.0 done = False while not done: action = policy.predict(np.array(state)) state, reward, terminated, truncated, info = env.step(action) total_reward += reward done = terminated or truncated # 渲染当前画面 env.render() print(f"Demo finished, total reward = {total_reward:.2f}") env.close() if __name__ == "__main__": main()运行demo.py时,你会看到一个小窗口实时播放 AI 玩游戏的过程。如果训练效果不错,AI 会表现出明显的“向右移动”倾向,并在遇到障碍物时尝试跳跃。如果训练效果不好,AI 可能一直原地跳或者撞墙,这时候需要调整参数或加大种群规模。
5.5 保存最优策略
在训练循环中,每一代结束后应该把当前最优个体保存下来,避免 demo 时用的是随机策略。可以在train.py的 main 函数里加几行:
# 在每一代循环结束后保存最优个体 best_policy = elites[0] w1, b1, w2, b2 = best_policy.get_params() np.save("best_policy_w1.npy", w1) np.save("best_policy_b1.npy", b1) np.save("best_policy_w2.npy", w2) np.save("best_policy_b2.npy", b2)保存参数文件后,即使训练中断,也可以随时用demo.py查看当前最优策略的表现。
6. 运行验证与效果判断
6.1 如何判断训练是否有效
训练过程中,控制台会不断打印类似下面的信息:
Generation 0: best reward = 0.00 Generation 1: best reward = 1.50 Generation 2: best reward = 12.00 Generation 3: best reward = 45.00 ... Generation 50: best reward = 320.00你可以从两个角度判断训练是否有效:
- 最佳奖励曲线整体上升:虽然中间会有波动,但整体趋势向上说明进化策略在起作用。
- AI 的实际行为有变化:跑一次 demo,观察 AI 是否从一开始的原地不动,慢慢变成了“向右走”“跳跃”等行为。
需要注意的是,最佳奖励是单调不减的,因为每代都保留了精英个体;但每一代的最大值可能因为变异而产生波动。如果你看到最佳奖励长时间不增长,优先检查奖励信号是否正确返回、种群规模是否太小、变异噪声是否过大。
6.2 运行 demo 时的预期表现
训练 100 代后,AI 的表现取决于你设置的参数和环境随机性。一般预期会有以下几个阶段:
- 前几代:AI 基本不会动,或者只会在原地随机跳跃。
- 十代以后:AI 学会了持续向右移动,遇到砖块会撞上去不动。
- 几十代后:AI 在部分位置能成功跳过障碍物,但还不稳定。
- 训练后期:AI 能连贯地移动和跳跃,偶尔能通过第一个关卡部分路段。
这个“渐进式学习”的过程,正是这类视频最有观赏价值的部分。Code Bullet 的视频剪辑也保留了这种“从笨拙到熟练”的过程,给观众一种“AI 真的在进化”的直观感受。
6.3 训练失败的排查路径
如果运行完 100 代,AI 依然什么都不学,可以按以下顺序排查:
- 检查环境是否真的能返回奖励。单独跑一个脚本,手动让马里奥向右走,看
reward是否为正值。 - 检查输入维度是否正确。
policy.predict接收的是展平后的像素,如果 shape 不匹配会直接报错。 - 检查变异噪声是否太大。如果
NOISE_SCALE过大,精英参数会被完全打乱,下一代不保留优秀信息。 - 检查种群规模是否太小。至少 10 个个体以上,不然随机搜索的覆盖面不够。
- 检查
max_steps是否太短。如果每局只允许 200 步,AI 还没走出几步就被强制结束了,很难学到有效行为。
7. 常见问题与排查思路
本地复现这类项目时,遇到的问题通常集中在依赖安装、环境接口、训练效果三个方面。下面整理一份常见问题清单:
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
安装gym-super-mario-bros报错 | Python 版本或依赖冲突 | 查看错误信息中的依赖版本要求 | 使用 Python 3.8-3.10 创建新虚拟环境后重装 |
env.reset()返回数据格式不对 | gym 版本接口差异 | 打印返回值的类型和 shape | 加apply_api_compatibility=True,或升级 gym |
运行demo.py报错找不到渲染模块 | 环境中没有安装pygame或显示驱动 | 检查渲染依赖是否完整 | pip install pygame,或使用render_mode="rgb_array"并自己保存图像帧 |
| 训练时最佳奖励一直是 0 | 奖励信号未正确返回 | 单独跑一步env.step(1)查看 reward 值 | 确认动作映射正确,确认环境版本支持奖励返回 |
| AI 一直原地跳跃不往前走 | 神经网络没有学到“向右移动”动作 | 查看动作集中是否包含向右动作 | 增大变异噪声或增加训练代数,检查奖励是否以距离为主 |
| 训练速度非常慢 | 输入分辨率太大、帧堆叠过多 | 打印状态 shape 并估算计算量 | 把分辨率降到 42x42,或者减少帧堆叠数量 |
| 环境随机性导致结果不稳定 | 没有设置随机种子 | 在 main 函数中调用np.random.seed(0) | 固定 NumPy 和环境随机种子,方便复现 |
其中最容易忽略的是第二个问题。gym-super-mario-bros早期版本的接口和现在差别较大,很多教程的代码直接复制会报错。遇到问题优先看官方仓库的 README,而不是东拼西凑改代码。
8. 最佳实践与工程建议
把“AI 学玩马里奥”从玩具项目推向更工程化,需要关注下面几个方面。
8.1 设置随机种子
进化策略看似简单,但参数量一大,随机性就会非常明显。同样的代码,不同的随机种子可能得到完全不同的训练曲线。在实验阶段,建议固定三个位置的随机种子:
import random import numpy as np random.seed(0) np.random.seed(0)这能让你在调参时排除随机性的干扰,也能帮助定位“到底是算法问题还是运气问题”。
8.2 控制训练时间成本
CPU 上跑进化策略虽然比深度学习训练快很多,但依然需要耐心。一个经验值是:在 42x42 分辨率、单局 2000 步、种群 20 个、训练 100 代的情况下,每代大约需要跑 20 局游戏,总耗时可能在几十分钟到几小时之间,取决于 CPU 性能。
如果你的 CPU 性能一般,可以先把分辨率降到 32x32,把MAX_STEPS降到 1000,先验证代码能跑通,再逐步加大规模。
8.3 保存中间结果与断点续训
进化学策略训练不是直线上升的,可能某个中间代已经学得不错,但后续变异把它带偏了。建议每隔一定代数保存一次最优个体,形成检查点:
if generation % 10 == 0: np.save(f"checkpoints/gen_{generation}_w1.npy", w1) np.save(f"checkpoints/gen_{generation}_b1.npy", b1)这样即使训练过程中断,也可以从最近的检查点继续变异,而不必重新训练。
8.4 关注奖励塑形的可解释性
马里奥环境的默认奖励来自向右移动的距离,这是非常直观的。但在更复杂的自定义游戏或仿真项目中,你可能会遇到“AI 刷分”“AI 原地抖”“AI 卡在某个状态”等玩法漏洞。建议在每次实验时记录:
- 每个个体每局的平均奖励
- 每局的最大移动距离
- 每局的存活步数
三个指标放在一起看,才能判断 AI 是真的学会了策略,还是碰巧刷到了高分。
8.5 从进化策略平滑过渡到强化学习
如果你在跑通进化策略后想继续深入,一个自然的路线是:保持上面的环境和预处理代码不变,把“变异 + 选择”替换成 PPO 或 DQN 等强化学习算法。很多开源项目正是在这个基础上改的。你会发现,环境搭建和奖励设计在这两类方法中是通用的,唯一变的只是“如何利用奖励更新策略”这一步。
9. 总结与后续学习方向
这篇文章从 Code Bullet 的“AI 学习玩马力欧”视频切入,拆解了游戏 AI 的核心框架:状态、动作、奖励、策略,并重点讲解了他采用的进化策略为什么能高效地学会玩马里奥。
我给出的判断是:这类项目的学习价值不在“AI 通关”这种结果,而在于它用最少的工程成本,完整地展示了“从随机尝试到策略涌现”的过程。你不需要掌握复杂的强化学习理论,也可以写出一个能玩的游戏 AI,这对树立工程直觉非常有帮助。
如果你打算继续深入,这里有几条实践路径可以参考:
- 先跑通上面的简化代码:观察 AI 从完全不会到会向右移动的过程,建立对进化策略的直观感受。
- 替换成 PyTorch 版本:把
SimplePolicy改成torch.nn.Module,然后用同样的进化策略训练,比较两个版本的训练速度和效果。 - 改造奖励函数:比如加入“碰到敌人扣分”“踩到敌人加分”等自定义规则,观察 AI 行为如何变化。
- 尝试强化学习:在同一个马里奥环境上改用 PPO 或 DQN 算法,体验强化学习与进化策略的本质差异。
Code Bullet 视频的可贵之处在于,他把 AI 训练中的失败和迭代过程都展示了出来,那些“把 AI 骂骂咧咧”的剪辑本来就是技术故事的张力来源。而技术人真正要学的,是如何在面对一个看似简单的任务时,用最小可行方案把问题跑通,再用数据慢慢逼近理想效果。