用NEAT进化算法训练AI玩超级马力欧:从零到跳跃的完整实践

📅 发布时间:2026/9/2 13:06:26
用NEAT进化算法训练AI玩超级马力欧:从零到跳跃的完整实践 这次我们来看一个很有“实验感”的 AI 项目让 AI 自己学习玩《超级马力欧》。标题里的 Code Bullet 指的是原作者那套持续用进化算法挑战游戏关卡的视频系列而“中配”则说明你看到的是中文解说的汉化版本。这类项目之所以值得技术同学关注不是因为它用了多前沿的模型而是因为它把“游戏环境 神经网络 进化策略”这条链路完整地跑通了非常适合用来理解遗传算法、适应度函数设计和智能体训练中的各种经典问题。文章会按三条线展开第一Code Bullet 那期演示背后的算法思路到底是什么为什么 AI 能从“原地乱跳”慢慢变得“会跳障碍”第二怎么用 Python 把类似环境搭起来我会给出一套基于 NEAT 进化策略的可复现训练框架包含环境安装、配置文件和训练脚本第三训练过程中最容易踩的坑包括依赖版本、ROM 缺失、奖励函数设计不合理、训练速度过慢、换一关就失效等。整个过程不需要高端显卡CPU 就能跑适合在周末动手复现一遍。先说清楚一个预期管理问题这不是一个“双击运行”的工具型项目而是一个偏算法教育和技术演示的内容项目。如果你只是看视频那什么都不用装理解思路就行如果你想动手复现“AI 玩马力欧”的效果那就必须准备好 Python 环境、模拟器和训练脚本下面这套流程可以直接照着走。1. 核心能力速览能力项说明项目主题用进化算法 / 神经网络驱动 AI 学习游玩《超级马力欧》项目来源Code Bullet 的 AI 游戏实验系列本标题为中文配音版核心任务让 AI 通过多代进化学会向右推进、跳跃障碍、躲避敌人技术路线游戏模拟器环境 NEAT 神经进化或类似进化策略输入形式游戏画面帧可降采样为灰度图或特征向量输出形式离散动作例如右移、跳跃、加速及组合动作开发语言Python关键依赖gym-super-mario-bros、nes-py、NEAT-Python、numpy硬件门槛CPU 可运行GPU 非必需是否支持 API原生不提供 HTTP API可自行封装训练/推理函数批量能力种群内多个个体可并行试玩训练过程天然具备批量模拟特征适合场景算法学习、游戏 AI 实验、神经进化研究、教学演示不适合场景产品化、商业服务、要求稳定输出的生产环境从这张表就能看出这个项目的核心价值不在“工具”而在“配方”。它把游戏测试环境、神经网络拓扑、进化算子三者组合起来让 AI 在一次次失败中自动调整行为。看视频时你看到的是“AI 变聪明了”实际上你看到的是一套完整的搜索过程只是这个搜索发生在动作序列空间里。注意一点Code Bullet 的演示更接近“玩得好不好看”的实验风格不是那种追求通关率的生产级强化学习工程。复现时不必追求和原视频完全一致的曲线关键是观察适应度是否逐步上升、智能体行为是否从随机变得有方向性。2. 适用场景与使用边界2.1 适合谁使用这个项目最适合三类人。第一类是正在学机器学习基础算法的开发者对遗传算法、NEAT、强化学习只有概念没有实践正好借一个可视化程度高的游戏场景把算法跑起来。第二类是游戏 AI 方向的学生或研究员想快速验证不同奖励函数对智能体行为的影响。第三类是技术内容创作者想复现 Code Bullet 风格的“AI 进化”视频需要一个可记录、可回放、可展示的训练流程。从工程角度看它也适合作为“第一个完整 AI 训练项目”来练手。因为游戏环境由现成库提供神经网络规模不大训练时间可控反馈直观。你能在几分钟内看到随机策略是什么水平也能在几小时后看到进化带来的提升这种即时反馈对建立训练直觉非常重要。2.2 不适合什么场景它不适合作为生产级游戏 AI 方案。原视频和这类型实验的定位是演示和教学不会像商用项目那样考虑稳定性、泛化能力和复杂场景适配。如果你要做一个“能通关所有关卡”的马力欧 AI或者要把它接到真实业务里做决策控制NEAT 加简单像素输入远远不够应该转向 DQN、PPO 等深度强化学习方案并且需要更完善的训练基础设施。另外它也不适合完全没有 Python 基础的人。虽然复现门槛不高但你至少要会创建虚拟环境、安装依赖、运行脚本、读取训练日志。否则遇到环境报错时会比较劝退。2.3 版权与合规边界马力欧是任天堂的经典作品游戏素材和 ROM 都有自己的版权边界。复现这类项目时要注意ROM 文件应来自合法渠道例如你自己拥有的正版卡带备份或者改用开源、自制、明确允许研究的 2D 平台游戏环境。不要传播盗版 ROM不要用这个项目去做任何商业变现或绕过游戏本身的安全机制。另外如果你会把训练过程录制成视频发布建议在简介里标明“思路参考 Code Bullet仅供学习交流”不要把别人实验的算法思路包装成完全原创的闭门成果。对原作者的尊重也是技术社区的基本礼仪。3. 算法原理进化算法如何驱动马力欧跑起来3.1 整体链路这个项目的基本链路并不复杂游戏模拟器不断输出当前画面的像素帧AI 把像素帧预处理成小尺寸输入送入一个神经网络神经网络输出一个离散动作例如“右移”“跳跃”“右移 跳跃”游戏环境根据动作更新画面并返回新的观测训练框架根据智能体推进了多远来打分这个分数就是适应度。适应度高的个体保留下来适应度低的个体被淘汰。然后通过交叉、变异、新增节点等方式产生下一代重复这一过程几十代甚至几百代。Code Bullet 那类视频里常见的“笨拙跑步 → 学会跳跃 → 能过第一个障碍 → 突然卡在某处”的过程本质上就是适应度曲线在爬坡过程中的可视化表现。这种思路和“监督学习”完全不一样。监督学习需要给定正确动作作为标签而这个项目里没有任何人告诉 AI 哪一步应该跳AI 只能靠自己试错和进化压力去发现“跳跃是有用的”。这也解释了为什么第一代 AI 看起来那么蠢它完全是在随机生成的动作空间里乱撞。3.2 神经网络的输入与输出输入最简单的方式是直接把游戏画面作为感知。但原始画面通常是 240×256 的 RGB 图像直接展开就是十几万个维度不仅训练慢还容易让 NEAT 的拓扑变得非常大。常见做法是把画面转成灰度图再压缩到 16×16 或 32×32 的小尺寸最后归一化成 0 到 1 之间的向量。这样输入维度只有几百神经网络规模小很多进化也更容易收敛。输出是动作空间。Code Bullet 这类实验通常不把“左移”加进动作集因为目标是向右前进左移只会浪费时间。常用的动作集RIGHT_ONLY包括原地不动、右移、右移 跳跃、右移 加速、右移 跳跃 加速等组合。动作集越小智能体越容易学到有效策略这也是降低问题难度的关键手段。3.3 适应度函数的常见设计适应度函数的本质是告诉进化算法“什么样的行为更好”。在马力欧场景里最直观的指标是 x 坐标的推进量也就是角色从左到右走了多远。可以把每一帧的 x 增量累加起来作为适应度这样 AI 只要一直往右走适应度就会增加。但只靠 x 推进量会带来一个问题AI 可能学会了“站在原地反复按右”或者跑到第一个坑前面疯狂跳跃却始终无法落地。更稳的方案是组合指标例如 x 推进量加通关奖励再减去时间惩罚。还有一些实验会给“高度变化”加一点奖励鼓励 AI 尝试跳跃因为完全不跳的个体很容易卡死。这个设计空间很大复现时可以自己调。3.4 进化循环与 NEATNEAT 的全称是 NeuroEvolution of Augmenting Topologies它的特点在于不仅优化神经网络的权重还会优化网络结构本身。初始个体可能只有一个很浅的网络结构很简单随着进化算法可以通过“新增节点”“新增连接”逐步增加网络复杂度让网络从“只会执行固定动作”进化到“能根据画面判断是否跳跃”。每一次进化循环包括四个步骤用当前种群里的每个个体分别玩一局游戏记录每人的适应度按照适应度排序保留表现好的个体通过交叉、变异生成新个体把新个体作为下一代继续试玩。整个循环不依赖梯度也不依赖大量带标签数据这让它非常容易在 CPU 上跑起来。3.5 局限与“AI 幻觉”式问题这类进化策略有一个很明显的局限模型很容易过拟合当前关卡。AI 可能在 1-1 关学会了某个固定节奏看起来很有策略但一旦换到 1-2 关障碍物布局变了它立刻就崩溃。这其实是一种“AI 幻觉”的体现它并不是真正理解了“遇到坑要跳”的抽象规则而是记住了特定像素模式和动作的对应关系。这种问题在复现时几乎一定会遇到。判断一个智能体是真学会还是背板最好的办法是换一关测试。如果换一个地图适应度断崖式下降说明模型没有泛化能力只是记住了局部模式。理解这一点比单纯看它“通关了”更有价值。4. 环境准备与前置条件开始复现之前先检查本机环境。下面是一套通用检查清单具体版本以你所用依赖的官方文档为准。项目建议配置操作系统Windows 10/11、macOS、Linux 均可Python 版本3.8 到 3.11 之间的干净环境CPU无特殊要求2 核以上即可GPU非必需可选内存8GB 以上更稳妥磁盘至少预留 3GB 给虚拟环境和依赖游戏 ROM确认来源合法或使用允许研究的自制关卡有一个容易忽略的点是 Python 版本。gym-super-mario-bros和nes-py这类老牌环境库对 Python 新版本的支持有时会滞后如果你用的是最新 Python 版本安装时可能遇到编译或依赖解析错误。建议直接用 Anaconda 或 venv 创建一个独立的 3.9/3.10 环境避免污染系统 Python。这个项目不需要 Web 服务所以不存在端口冲突问题。但如果你习惯在 Jupyter Notebook 里跑实验注意 8888 端口如果之后接入 TensorBoard 观察训练曲线则注意 6006 端口。这些都是训练辅助工具层面的问题和 AI 游戏环境本身无关。5. 安装部署与启动方式下面是一套常见的本地复现流程。请注意这里使用的是通用组件和公开库用来搭建一个“NEAT 训练 AI 玩超级马力欧”的最小工程不代表原视频源码原样。5.1 创建独立虚拟环境建议先用 venv 隔离依赖python -m venv mario-ai # Linux/macOS source mario-ai/bin/activate # Windows mario-ai\Scripts\activate激活后确认 python 和 pip 指向新环境python --version pip --version5.2 安装依赖pip install --upgrade pip pip install gym gym-super-mario-bros nes-py neat-python numpy如果后续要用图像缩放预处理可以再装 opencvpip install opencv-python不同版本的 gym 和 gym-super-mario-bros 在 API 上有差异如果安装时出现依赖冲突优先锁定版本并查看官方 README。不要一次性安装最新版的所有依赖那样更容易出问题。5.3 验证游戏环境是否可用先写一个最小脚本确认模拟器能正常加载并输出画面from nes_py.wrappers import JoypadSpace import gym_super_mario_bros from gym_super_mario_bros.actions import RIGHT_ONLY env gym_super_mario_bros.make(SuperMarioBros-1-1-v0) env JoypadSpace(env, RIGHT_ONLY) obs env.reset() print(画面形状:, obs.shape) print(动作空间:, env.action_space) env.close()如果输出结果是类似(240, 256, 3)的画面形状和一个Discrete(5)的动作空间说明环境正常。如果这里报 ROM 缺失或模拟器初始化失败先不要进入训练环节。5.4 NEAT 配置文件NEAT-Python 依赖一个配置文件来控制种群大小、结构变异概率、激活函数等参数。下面是一份最小可运行的示例配置[NEAT] fitness_criterion max fitness_threshold 5000 pop_size 30 reset_on_extinction True [DefaultGenome] num_inputs 256 num_hidden 0 num_outputs 5 initial_connection partial_direct 0.5 activation_default tanh activation_options tanh mutate_add_node_prob 0.05 mutate_add_conn_prob 0.05 mutate_weight_step 0.3 [DefaultStagnation] species_fitness_func max max_stagnation 15关键字段解释num_inputs要和预处理后的输入维度一致本文示例把画面压缩成 16×16 灰度图所以是 256num_outputs对应RIGHT_ONLY的 5 个动作pop_size是每一代同时试玩的个体数量调大会更慢调小更容易陷入局部最优。5.5 训练主脚本下面脚本把“画面预处理、单局适应度评估、NEAT 进化循环”串起来import pickle import numpy as np import neat import cv2 from nes_py.wrappers import JoypadSpace import gym_super_mario_bros from gym_super_mario_bros.actions import RIGHT_ONLY def preprocess(obs): # 转灰度、压缩到 16x16、拉平并归一化 gray cv2.cvtColor(obs, cv2.COLOR_RGB2GRAY) small cv2.resize(gray, (16, 16)) return (small.flatten() / 255.0).tolist() def eval_genome(genome, config): net neat.nn.FeedForwardNetwork.create(genome, config) env gym_super_mario_bros.make(SuperMarioBros-1-1-v0) env JoypadSpace(env, RIGHT_ONLY) obs env.reset() total_fitness 0.0 prev_x 0 for _ in range(500): inputs preprocess(obs) action int(np.argmax(net.activate(inputs))) obs, reward, done, info env.step(action) x info.get(x_pos, 0) total_fitness max(0, x - prev_x) prev_x x if done: break env.close() return total_fitness config neat.Config( neat.DefaultGenome, neat.DefaultReproduction, neat.DefaultSpeciesSet, neat.DefaultStagnation, config-feedforward.txt ) pop neat.Population(config) pop.add_reporter(neat.StdOutReporter(True)) pop.add_reporter(neat.StatisticsReporter()) winner pop.run(eval_genome, 50) with open(winner.pkl, wb) as f: pickle.dump(winner, f) print(训练完成最优个体已保存到 winner.pkl)这里要注意几个细节。动作集如果换成SIMPLE_MOVEMENTnum_outputs要改成 7单局 500 步是一个示例上限实际应结合关卡长度调整info中字段名在不同版本中可能略有差异最好在第一次运行时打印一下info看看有哪些键避免脚本因为字段缺失中断。6. 功能测试与效果验证由于这不是 Web 工具“功能测试”变成了“训练管线的分阶段验证”。建议按从简到繁的顺序做。6.1 测试环境连通性运行 5.3 的最小脚本确认画面能加载、动作空间正确。这个测试不涉及任何 AI 逻辑只是排除环境和依赖问题。如果连这一步都过不了后面所有训练都无从谈起。6.2 随机策略基线在训练 NEAT 之前先做一个随机策略基线。具体做法是不加载任何神经网络完全用随机动作跑 100 局记录每一局的最大 x 坐标和存活时长。这个基线能告诉你“完全不学的情况下AI 能走多远”。判断标准很简单随机策略通常跑不远可能卡在第一个柱子或坑前。如果随机策略都能跑很后面说明关卡太简单后续很难看出进化效果。6.3 小规模 NEAT 验证把pop_size调成 10代数调成 5先跑一次小规模实验。这个阶段不看通关效果只看两件事程序是否能完整跑完每代结束后是否打印出适应度统计。如果小规模实验能跑通就意味着训练链路没有断。此时再加大种群和代数训练成功率会高很多。6.4 正式训练与观察把pop_size调到 30 到 50代数调到 50 到 100。训练过程中定时观察标准输出的适应度信息。通常你会看到最高适应度在某几代出现跃升也可能长时间停滞。停滞不是 bug很可能说明当前种群陷入了局部最优需要调整变异概率或奖励函数。判断训练是否有效的标准不是“是否通关”而是“最高适应度是否比随机基线高”。哪怕 AI 只是多跳过一个障碍也已经说明进化压力在起作用。6.5 回放最优个体训练结束后从winner.pkl加载最优个体关闭进化单独跑一局观察行为def play(genome, config, max_steps2000): net neat.nn.FeedForwardNetwork.create(genome, config) env gym_super_mario_bros.make(SuperMarioBros-1-1-v0) env JoypadSpace(env, RIGHT_ONLY) obs env.reset() done False step 0 while not done and step max_steps: env.render() action int(np.argmax(net.activate(preprocess(obs)))) obs, reward, done, info env.step(action) step 1 env.close() with open(winner.pkl, rb) as f: winner pickle.load(f) play(winner, config)回放时重点观察三件事智能体是否会在某个固定位置反复卡住跳跃动作是否总是过早或过晚是否出现了“往左走”等无效行为。这些现象会反馈到奖励函数的设计调整上。7. 接口 API 与批量任务原项目并不提供 HTTP API它本质上是“游戏环境 训练脚本”的组合。不过工程上完全可以把它抽象成一条可复用的训练管线下面给出两种常见的工程化方向。第一种是批量模拟。NEAT 的种群天然支持批量试玩每个个体都要跑一局这本身就是一批任务。如果你想加快速度可以参考多进程思路把种群分成几组在多个 CPU 进程里并发评估。但要注意gym-super-mario-bros的模拟器实例会占用不少内存并发数不宜过高否则会拖垮本机。from concurrent.futures import ProcessPoolExecutor def evaluate_population(population, config, workers4): results {} with ProcessPoolExecutor(max_workersworkers) as pool: futures { pool.submit(eval_genome, genome, config): genome for genome in population } for future in futures: genome futures[future] results[genome] future.result() return results这是一个通用并行思路NEAT-Python 官方并未内置这套并行接口需要自己根据训练脚本调整。实际使用时还要考虑 pickle 序列化、重复创建环境的开销小规模种群时串行可能反而更快。第二种是导出策略权重供下游使用。训练好的winner.pkl保存的是一个 NEAT 基因组你可以把它做成一个“决策函数”来复用def load_agent(path): with open(path, rb) as f: genome pickle.load(f) return genome之后不管是做视频回放、单局评估还是把动作预测接入其他可视化工具都只需要加载这个文件即可。如果你确实想让别人通过 HTTP 调用能力可以基于 FastAPI 再包一层“输入画面帧 → 输出动作”的接口但这已经超出了原项目的范围需要自己实现服务封装和并发控制。8. 资源占用与性能观察这类 CPU 训练的负荷主要集中在模拟器和画面预处理上。NEAT 的神经网络本身非常小计算量不大真正耗时的是每一代里几十个个体的完整游戏模拟。如果不关闭渲染画面渲染也会占用不少 CPU。建议训练时把env.render()关掉只在回放时打开。内存方面进程里同时存在多个环境实例时会明显上涨具体数值取决于并发数和模拟器实现。如果发现内存占用过高优先降低并发数、减小种群规模或者缩短单局步数上限。GPU 在这个项目里不是必需品。如果你完全走 NEAT 路线显卡基本用不上。但如果你把这个项目升级为深度强化学习方案例如用 DQN 或 PPO那么显存占用会和网络结构、输入分辨率、并行环境数量强相关需要单独做监控。观察资源占用可以用系统自带工具。Windows 下看任务管理器里的 CPU 和内存macOS 看活动监视器Linux 用top或htop。如果训练脚本里用了 PyTorch 等深度学习库再用nvidia-smi观察显存。关键是建立“先看训练日志、再看系统资源”的排查习惯不要一卡顿就怀疑代码。9. 常见问题与排查方法问题现象可能原因排查方式解决方案安装 gym-super-mario-bros 失败Python 版本过新或依赖冲突查看 pip 报错中的包名回到 Python 3.8-3.11 环境逐项安装依赖首次运行报 ROM 缺失游戏 ROM 未放入指定目录查看环境初始化日志按官方文档放入合法 ROM或改用自制关卡NEAT 跑了很多代没有进展适应度函数太粗糙或动作空间过大打印每代最高适应度缩小动作集增大 x 推进奖励智能体原地卡住学会了按右但不会跳回放单局轨迹增加跳跃相关奖励或加入高度变化指标单局训练很慢渲染开启、单局步数过长、种群太大看 CPU 占用和日志关闭渲染、降低步数上限、调小种群换一关就完全失败模型过拟合当前关卡多关卡交叉测试训练时引入多个关卡增加障碍随机性画面花屏或闪退模拟器后端渲染问题查看 nes-py 报错日志升级依赖或使用无头模式训练训练中断后进程残留环境循环中没有 close查看任务管理器用 try/finally 包裹环境生命周期NEAT 不进化本身不是“代码跑挂了”更可能是搜索空间设置问题。优先检查配置里的pop_size、变异概率和适应度函数。很多时候给一点很小的“跳跃倾向”奖励就能打破长期停滞。10. 最佳实践与使用建议第一次复现的时候强烈建议先跑小规模实验。把种群调到 10代数调到 5完整跑通一遍后再加量。这样可以避免在“环境是否正常”都还没确认时就启动一个需要跑几小时的训练任务。代码和配置要用版本管理。NEAT 配置、训练脚本、结果目录、模型文件分开整理固定随机种子确保实验结果可复现。比如在训练脚本开头加random.seed(42)和numpy.random.seed(42)便于下次对比不同奖励函数的效果。project/ ├── config-feedforward.txt ├── train.py ├── play.py ├── checkpoints/ ├── logs/ └── outputs/批量实验时记录每次实验的配置和日志不要只保存最后的winner.pkl。这样一来如果某次训练效果特别好你能知道是哪个奖励函数、哪个变异概率导致的如果某次训练失败也能快速对比历史参数。合规方面再强调一次模拟器和 ROM 相关文件要注意来源合法性不要传播盗版训练出来的智能体行为、录屏内容如果要公开发布加上“学习实验”的说明。不要用这个项目去绕过游戏限制或者对他人账号、设备造成影响。11. 总结与下一步这个项目最值得尝试的地方是它用最小成本展示了一条完整的智能体训练链路。你不必非得跑出通关效果只要看到适应度曲线从随机基线开始缓慢上升就已经理解了进化算法的核心机制。最先应该验证的是游戏环境能否跑通紧接着是随机策略基线。这两个基础测试几乎决定后续训练是否顺利。最容易踩的坑是依赖版本和奖励函数依赖版本会导致环境根本起不来奖励函数设计不当则会让训练“看似在跑实际上没有学习”。下一步可以尝试的方向有很多把输入从 16×16 灰度图换成带色彩信息的特征图观察对障碍物识别的影响把 NEAT 换成 DQN 或 PPO对比两类算法在相同关卡上的适应度曲线加入 LSTM 让智能体保留对过去几步动作的记忆或者把训练环境扩展到多个关卡观察模型的泛化能力是否提升。建议先把这套流程收藏备用周末找一个完整的时间段从环境检查开始一步步跑到模型回放。亲手看一次 AI 从乱撞到跳过第一个障碍比读十篇算法原理更能建立直观认识。