1. 项目概述:用强化学习训练一个能通关《街霸2》的AI

如果你玩过《街头霸王II:特别冠军版》,一定对那个穿着红色道服、发着波动拳的隆(Ryu)和最终关底那个会放电的警察维加(Vega,国内俗称“警察”)印象深刻。当年在游戏厅里,不知道多少币都栽在了这个Boss手上。现在,有个叫“SFighterAI”的开源项目,目标就是用深度强化学习技术,训练一个AI智能体,让它学会仅凭游戏屏幕的像素信息,就能击败这个最终Boss。

这个项目的核心思路非常“极客”:它不依赖游戏内部的内存数据接口,也不预设任何连招指令。AI就像一个人坐在屏幕前的玩家,唯一的输入就是每一帧的RGB像素画面,然后它需要自己学会观察、判断,并输出对应的手柄按键(如上、下、左、右、拳、脚等),最终目标是在最高难度下战胜维加。项目作者已经训练出了一个模型,在特定的存档状态下,对第一回合的胜率能达到100%。当然,这背后涉及到一个机器学习中常见的问题——过拟合,我们后面会详细拆解。

对我而言,这类项目最吸引人的地方在于,它把前沿的AI研究和我们童年的游戏记忆结合了起来。你不再仅仅是“玩”游戏,而是在“教”一个AI如何玩,并观察它从零开始,从一个连移动都不会的“婴儿”,逐渐成长为一个能打出精妙连段、成功格挡并反击的“格斗家”。这个过程本身,就充满了工程和探索的乐趣。

2. 核心思路与技术选型解析

2.1 为什么选择深度强化学习?

要教会AI玩格斗游戏,传统的方法可能是穷举所有可能的按键组合,或者编写一套复杂的“如果-那么”规则。但格斗游戏的状态空间巨大,画面瞬息万变,规则系统很难覆盖所有情况。深度强化学习(Deep Reinforcement Learning, DRL)正好能解决这个问题。

你可以把DRL理解为一个“试错学习”的过程。AI(智能体)在游戏环境(环境)中不断尝试各种动作(如跳跃、出拳),环境会反馈给AI一个新的游戏画面(状态)和一个奖励值(奖励)。奖励是学习的指挥棒,比如击中对手奖励+1,被对手击中惩罚-1,赢得一回合奖励+100。AI的目标就是学习一套策略(神经网络),使得长期累积的奖励最大化。通过数百万次这样的试错,神经网络逐渐能从复杂的像素画面中提取出有用的特征(如对手的位置、出招前摇、自己的血量),并做出最优决策。

这个项目选择了**近端策略优化(Proximal Policy Optimization, PPO)**算法,它来自Stable-Baselines3库。PPO是目前DRL领域应用最广泛的算法之一,以其训练稳定、调参相对友好而著称。对于《街霸2》这种动作空间离散(有限的按键组合)、需要长时间序列决策的游戏,PPO是一个经过验证的可靠选择。

2.2 观测空间与动作空间的设计

这是DRL项目的两个核心设计,直接决定了AI能“看到”什么以及能“做”什么。

观测空间(Observation Space) :项目采用最“原始”的方式——屏幕像素。默认情况下,游戏屏幕会被预处理(如裁剪、灰度化、缩放)成一个形状为 [84, 84, 1] 的二维数组(即84x84分辨率的单通道灰度图像)。这样做的目的是大幅减少输入数据的维度,加快神经网络处理速度,同时保留基本的形状和运动信息。AI就靠这一帧帧的“小图片”来理解战局。

注意 :直接使用原始RGB三通道图片( [224, 320, 3] )在计算上是极其昂贵的,且大量颜色信息对判断战局可能并非必要。灰度化并降采样是平衡信息保留与计算效率的常见做法。

动作空间(Action Space) :项目使用了Gym Retro环境提供的“离散”动作空间。这意味着AI的每一个输出,对应着游戏手柄上一个特定的按键组合。例如,动作0可能代表“什么都不按”,动作1代表“按右”,动作2代表“按下+重拳”等等。Gym Retro已经为《街霸2》预定义了这些映射。AI的任务就是在每一帧,从这几十个可能的动作中选择一个执行。

2.3 奖励函数设计:教会AI“什么是好”

奖励函数是强化学习项目的灵魂,决定了AI的学习方向。一个糟糕的奖励函数可能会让AI学会一些匪夷所思的行为,比如一直躲在角落,或者不停地跳跃。

这个项目的奖励函数主要基于一个关键信息:双方的血量差值。它通过Gym Retro的集成工具,定位了游戏内存中存储“己方血量”和“敌方血量”的地址,并在 scenario.json 文件中进行了配置。这样,在每一步(或每N步)都能读取到实时血量。

一个基础的奖励设计可以是: 奖励 = (敌方上一步血量 - 敌方当前血量) - (己方上一步血量 - 己方当前血量) 简单说,就是“你对敌人造成的伤害”减去“你受到的伤害”。这鼓励AI积极进攻并减少受伤。

但仅仅这样是不够的。项目提到了参考一篇名为《Mitigating Cowardice for Reinforcement Learning》的论文,解决了“懦夫”问题。什么是“懦夫”问题?AI发现,只要远离对手,虽然赢不了,但也不会掉血,长期累积的惩罚反而比冲上去挨打要小,于是它就学会了满场逃跑,消极避战。

论文提出的“惩罚衰减”机制可能是这样运作的:如果AI在连续一段时间内没有对敌人造成伤害,那么它“没有造成伤害”这件事本身会开始累积一个小的负奖励(惩罚),鼓励它去尝试攻击。或者,也可以设置一个随时间衰减的“进攻奖励”,长时间不进攻,这个奖励就会消失,迫使AI采取行动。

实操心得 :设计奖励函数是门艺术。除了血量,还可以考虑加入“连击奖励”、“格挡奖励”、“胜利/失败的大额终局奖励”。但每增加一项,都需要仔细调整权重,避免奖励信号过于复杂或冲突。对于初学者,从简单的血量差开始,稳定后再尝试微调,是更稳妥的策略。

3. 环境搭建与实操步骤详解

纸上谈兵终觉浅,下面我们一步步把这个项目跑起来,看看AI是怎么训练的。整个过程在Windows 11上经过测试,其他系统可能略有不同。

3.1 基础环境配置

首先,我们需要一个干净的Python环境。强烈建议使用Anaconda来管理,可以避免很多包依赖冲突。

# 创建一个名为StreetFighterAI的Python 3.8.10环境
conda create -n StreetFighterAI python=3.8.10
# 激活这个环境
conda activate StreetFighterAI

接下来,进入项目的主目录,安装所有必需的Python库。这些库都写在 requirements.txt 文件里了。

# 假设你的项目克隆在 D:\Projects\street-fighter-ai
cd D:\Projects\street-fighter-ai\main
pip install -r requirements.txt

这里的关键库包括:

  • gym-retro :提供游戏模拟环境,让Python代码可以像玩家一样控制游戏。
  • stable-baselines3[extra] :包含了PPO等强化学习算法的实现。
  • opencv-python :用于对游戏屏幕图像进行预处理(缩放、灰度化)。
  • tensorboard :用于可视化训练过程中的各项指标。

3.2 游戏ROM与状态文件的准备

这是最关键也最容易出错的一步。Gym Retro本身不提供游戏ROM,你需要 合法地 拥有《Street Fighter II: Special Champion Edition (Europe)》的ROM文件。网络上有很多资源站,请确保你从合法渠道获取。

  1. 定位Gym Retro的游戏库目录 :运行项目提供的工具脚本。

    cd D:\Projects\street-fighter-ai
    python .\utils\print_game_lib_folder.py
    

    命令行会输出一个路径,例如 C:\Users\YourName\AppData\Local\Retro\retro\data\stable\SegaGenesis 。在文件管理器中打开这个路径。

  2. 集成游戏数据 :在这个 SegaGenesis 文件夹里,你应该能看到一个名为 StreetFighterIISpecialChampionEdition-Genesis 的文件夹。将本项目 data/ 文件夹下的四个文件:

    • Champion.Level12.RyuVsBison.state (最终关的存档状态文件)
    • data.json
    • metadata.json
    • scenario.json 复制进去, 覆盖 原有的文件(可能需要管理员权限)。这些 .json 文件包含了读取血量的内存地址等信息,是自定义奖励函数的基础。
  3. 放置ROM文件 :将你合法获取的ROM文件(通常是一个 .md .bin 文件)也复制到上述同一个文件夹内(即 StreetFighterIISpecialChampionEdition-Genesis ),并将其重命名为 rom.md 。这是Gym Retro要求的固定文件名。

踩坑记录 :最常见的问题就是ROM文件没放对位置,或者文件名不对。务必确保ROM文件就在那个游戏特定的文件夹内,且名为 rom.md 。另外, state 存档文件必须与ROM版本匹配,否则加载会失败。

3.3 辅助工具:状态录制与视频生成

  • 手动探索与状态录制 :如果你想自己录制其他关卡或角色的存档,可以使用Gym Retro Integration UI工具。将项目 data/ 文件夹下的 Gym Retro Integration.exe 复制到Gym Retro游戏库目录的 上两级目录 (即包含 retro 文件夹的目录)并运行。这个工具可以让你手动玩游戏,并随时保存游戏状态( .state 文件),还能探查和修改内存变量,对于调试非常有用。

  • 录制对战视频 :如果你想保存AI的精彩操作,需要安装FFmpeg。

    conda install ffmpeg
    

    之后在测试或训练代码中启用录像功能,就能生成MP4文件了。

4. 模型训练、测试与性能分析

环境准备好后,我们就可以开始“调教”AI了。

4.1 启动训练:见证AI的成长

训练是耗时最长的过程,在消费级GPU上,训练一个有效的模型也需要数小时甚至数天。

cd D:\Projects\street-fighter-ai\main
python train.py

运行 train.py 脚本,它会开始漫长的学习过程。在这个过程中,你应该关注几个关键点:

  1. 控制台输出 :会显示当前训练的步数(timesteps)、平均奖励(mean reward)、回合长度(episode length)等。平均奖励的整体上升趋势是学习有效的标志。
  2. Tensorboard可视化 :这是更重要的监控工具。在VSCode中可以直接打开集成的Tensorboard插件,或者用命令行启动:
    tensorboard --logdir=logs/
    
    然后在浏览器打开 http://localhost:6006 。你需要关注的曲线包括:
    • rollout/ep_rew_mean :每个回合的平均奖励,这是核心的成功指标。
    • rollout/ep_len_mean :每个回合的平均长度(帧数)。如果AI很快被打死,这个值会很低;如果AI一直逃跑拖延时间,这个值会很高。理想的曲线是随着奖励上升,长度也稳定在一个合理范围(代表能有效交战)。
    • losses/value_loss losses/policy_loss :价值损失和策略损失。它们应该随着训练波动下降,如果出现爆炸式增长(变成NaN或极大值),可能意味着学习率过高或网络结构有问题。

4.2 测试与评估:看看AI学得怎么样

项目提供了不同训练阶段的模型权重,存放在 main/trained_models/ 文件夹下。我们可以用 test.py 来直观地看看AI的表现。

cd D:\Projects\street-fighter-ai\main
python test.py

默认情况下,脚本会加载 ppo_ryu_2500000_steps_updated.zip 这个模型。这是一个泛化能力相对较好的模型,有较高的几率能通关最终关卡。你可以修改 test.py 中的 model_path 变量,来测试其他模型:

# 在test.py中修改这行
model_path = "./trained_models/ppo_ryu_7000000_steps_updated.zip"

根据项目描述,不同阶段模型的特性如下表所示:

模型文件 训练步数 性能特点 泛化能力
ppo_ryu_2000000_steps 200万 刚开始出现过拟合迹象,有一定泛化能力但实力不足。 中等
ppo_ryu_2500000_steps 250万 接近最终过拟合状态,不能稳赢第一回合但部分可泛化。通关最终关几率高。 较好(推荐测试)
ppo_ryu_3000000_steps 300万 接近最终过拟合状态,几乎能稳赢第一回合但泛化能力很差。
ppo_ryu_7000000_steps 700万 完全过拟合,能100%稳赢第一回合,但毫无泛化能力。 几乎为零

4.3 深入理解“过拟合”:AI成了“背板侠”

这里重点解释一下“过拟合”。在机器学习中,过拟合指的是模型在训练数据上表现极好,但在未见过的数据上表现很差。

在这个项目里,“训练数据”特指从 Champion.Level12.RyuVsBison.state 这个 特定存档状态 开始的无数局游戏。AI在这个固定的起点(双方位置、血量、能量槽固定)下,通过数百万次尝试,找到了一套能在这个 特定场景 下最大化奖励的策略。对于 ppo_ryu_7000000_steps 这个模型,它可能已经背下了开场后第几帧向左走一步,第几帧发一个波动拳,从而100%命中并形成连招。

但是,格斗游戏的魅力在于变化。如果稍微改变起点(比如警察开局就跳起来),或者换一个关卡、换一个对手,这个“背板”策略就完全失效了。这就是泛化能力差的表现。

ppo_ryu_2500000_steps 模型之所以被推荐,是因为它在“赢得当前对局”和“学习通用格斗策略”之间取得了更好的平衡。它可能没有找到那个100%必胜的固定套路,但它学会了更通用的技能,如“保持距离”、“抓对手出招硬直进行反击”、“使用防空技应对跳跃”等。因此,即使面对一些开局变化,它也有一定的应对能力,通关整个关卡的可能性反而更高。

实操心得 :判断模型是否过拟合,不能只看它在测试存档下的胜率。一个更科学的评估方法是: 使用多个不同的初始存档(例如不同关卡、不同对手、不同血量)来测试同一个模型 。如果它在所有存档下表现都稳定,说明泛化能力强;如果只在特定存档下无敌,在其他存档下像个“傻子”,那就是过拟合了。在资源有限的情况下,适时停止训练(早停法)是获得泛化能力更好模型的关键。

5. 自定义与进阶探索

如果你不满足于仅仅运行现成的项目,这里有一些方向可以深入探索:

5.1 调整超参数以优化训练

train.py 中定义了PPO算法的许多超参数。调整它们可以显著影响训练效率和最终模型质量。关键参数包括:

  • 学习率(learning_rate) :通常设置在1e-4到1e-5之间。太高会导致训练不稳定(损失爆炸),太低则学习速度慢。
  • 批次大小(batch_size) 时间步长(n_steps) n_steps 是每次收集多少步数据后才进行一次模型更新, batch_size 是每次更新时从这些数据中抽取的样本数。对于《街霸2》这类游戏, n_steps 可以设大一些(如2048或4096),以包含更完整的对战片段信息。
  • 伽马值(gamma) :折扣因子,范围0到1。它决定了AI对未来奖励的重视程度。越接近1,AI越有“长远眼光”。对于格斗游戏,可以设得较高(如0.99),因为一个成功的连招需要好几步动作的配合。
  • 奖励函数 :如前所述,这是最有魔力的部分。你可以尝试在 env.py 或自定义的回调函数中修改奖励计算逻辑。例如,给“成功格挡”一个正奖励,给“长时间不进攻”一个随时间增长的负奖励。

5.2 改进观测空间:给AI“更锐利的眼睛”

当前AI只看到了84x84的灰度图像,信息损失很大。可以考虑:

  1. 帧堆叠(Frame Stacking) :单张静态图片无法感知“速度”。常见的做法是将连续4帧画面堆叠在一起,作为一次观测输入给AI。这样AI就能判断对手是正在冲过来还是在后退。
  2. 特征提取 :与其给AI原始像素,不如先用一个预训练的卷积神经网络(CNN)对画面进行特征提取,再将提取出的高级特征(如角色轮廓、位置信息)输入给策略网络。这可以大幅降低需要学习的数据维度。
  3. 添加非视觉信息 :除了画面,可以直接将游戏内存中的结构化数据(如双方血量、能量槽、眩晕值、距离)作为额外输入向量,与图像特征融合。这相当于给了AI一个“游戏内HUD”,学习起来会更直接。

5.3 解决“过拟合”问题的实战策略

  1. 课程学习(Curriculum Learning) :不要一开始就让AI在最高难度下面对最终Boss。可以先从简单关卡、简单对手开始训练,等AI学会基本移动和攻击后,再逐步提高难度,最终挑战Boss。这模拟了人类的学习过程。
  2. 域随机化(Domain Randomization) :在训练过程中,随机化一些游戏条件。例如,随机化双方的开局位置、随机化Boss的初始行为模式、甚至随机化游戏背景的颜色(通过图像处理)。这强迫AI学习更本质、更鲁棒的特征,而不是记住固定的像素模式。
  3. 集成学习(Ensemble) :训练多个不同的模型,让它们“投票”决定采取什么动作。或者使用自博弈(Self-Play),让AI自己与自己不断对战进化,这能产生无限多样的对局情况,是解决过拟合、提升泛化能力的终极方法之一,但计算成本也最高。

6. 常见问题与故障排除实录

在复现和实验过程中,你大概率会遇到以下问题。这里记录了我的排查经验和解决方案。

6.1 环境配置与运行报错

问题1:运行 pip install -r requirements.txt 时,提示某些包(如 gym-retro )安装失败或版本冲突。

排查 :这通常是Python版本或操作系统环境导致的。首先确认你的Conda环境Python版本是否为3.8.*。Stable-Baselines3和Gym Retro对Python 3.9+的支持可能在某些版本上不稳定。

解决

  1. 尝试单独安装核心包并指定版本: pip install gym-retro==0.8.* stable-baselines3==1.8.*
  2. 如果涉及需要编译的包(如 box2d-py )在Windows上失败,可以搜索并安装预编译的wheel文件,或者安装Microsoft Visual C++ Build Tools。
  3. 终极方案:使用Docker。项目作者如果能提供一个Dockerfile,可以完美解决环境一致性问题。

问题2:运行 test.py train.py 时,出现 FileNotFoundError retro.core.InvalidStateError ,提示找不到ROM或state文件。

排查 :这是路径问题。99%的情况是ROM文件没放对位置或名字不对。

解决

  1. 再次核对 3.2节 的步骤。确保ROM文件在正确的游戏文件夹内( ...\StreetFighterIISpecialChampionEdition-Genesis\ ),并且名字是** rom.md **。
  2. 确保 .state .json 文件已经覆盖了原文件夹内的文件。
  3. 尝试在Python代码中打印出retro的数据路径进行确认。

问题3:训练开始后,Tensorboard中的奖励曲线不上升,或者AI的行为非常奇怪(如一直朝一个方向走、不停跳跃)。

排查 :奖励函数设计可能有问题,或者学习率设置不当。

解决

  1. 检查奖励值 :在 env.py step 函数中添加打印语句,输出每一步的奖励值。看看奖励是否在合理范围内(通常绝对值不超过10)。如果奖励值巨大或为NaN,说明计算有误。
  2. 可视化AI的视角 :修改测试代码,将预处理后的观测图像(那个84x84的灰度图)保存或显示出来。确认AI看到的画面是否是你期望的样子(角色是否在画面中,图像是否清晰)。
  3. 调低学习率 :如果策略损失剧烈波动,尝试将 learning_rate 降低一个数量级(例如从3e-4改为3e-5)。
  4. 简化问题 :先在一个极简的环境下测试,比如让AI学习“移动到画面中央”这样一个简单任务,确保整个训练流程是通的。

6.2 训练效率与性能优化

问题4:训练速度非常慢,每一步都要等很久。

排查 :强化学习训练本身就是计算密集型任务。慢可能是由于:

  • 图像预处理在CPU上进行。
  • 没有使用GPU进行神经网络运算。
  • n_steps 设置过大,导致每次更新前收集数据的时间很长。

解决

  1. 启用GPU :确保你的PyTorch(Stable-Baselines3的底层)是GPU版本。在Python中运行 import torch; print(torch.cuda.is_available()) ,如果输出 True ,则代码会自动利用GPU加速。
  2. 优化环境交互 :Gym Retro的环境模拟是主要瓶颈。可以尝试使用 VecFrameStack DummyVecEnv (Stable-Baselines3提供)创建多个环境并行运行,一次性收集更多数据,这是加速训练最有效的手段之一。
  3. 调整 n_steps :适当减小 n_steps (如从2048改为512),会增加模型更新的频率,可能加快初期学习速度,但可能会让梯度估计的方差变大。

问题5:模型似乎“忘记”了之前学到的技能,奖励曲线出现断崖式下跌。

排查 :这在强化学习中称为“灾难性遗忘”。当策略发生较大更新时,新的策略可能完全覆盖了旧的、有效的策略。

解决

  1. 调整PPO的 clip_range 参数 :这个参数限制了新旧策略之间的差异。适当减小 clip_range (如从0.2改为0.1),可以使更新更保守,避免策略突变。
  2. 使用策略蒸馏或保存检查点 :定期保存模型权重。如果发现模型性能严重下降,可以回滚到之前的检查点,并以更小的学习率继续训练。

从零开始构建一个能玩《街霸2》的AI,就像教一个孩子学习一项复杂的运动。你需要为他设计合适的训练环境(游戏模拟)、制定清晰的奖惩规则(奖励函数)、并耐心地陪伴他进行无数次练习(训练迭代)。这个过程充满了挑战,但当你看到AI从跌跌撞撞到能打出漂亮连招,最终战胜曾经让你头疼的Boss时,那种成就感是无与伦比的。这个项目提供了一个绝佳的起点,它不仅是一套可运行的代码,更是一个展示如何将深度强化学习应用于复杂决策问题的完整案例。你可以沿着它指出的方向,去探索智能体感知、决策的更多可能性,甚至尝试将其应用到其他游戏或更广泛的序列决策问题中去。

Logo

小龙虾开发者社区是 CSDN 旗下专注 OpenClaw 生态的官方阵地,聚焦技能开发、插件实践与部署教程,为开发者提供可直接落地的方案、工具与交流平台,助力高效构建与落地 AI 应用

更多推荐