深度强化学习实战:从零构建通关《街霸2》的AI智能体
深度强化学习(Deep Reinforcement Learning, DRL)是机器学习的重要分支,它通过智能体与环境的持续交互,以试错和奖励机制来学习最优决策策略。其核心原理在于利用神经网络从高维原始输入(如像素图像)中提取特征,并输出动作以最大化长期累积奖励。这项技术的核心价值在于能够解决传统规则系统难以处理的复杂、高维状态空间的序列决策问题。在工程实践中,DRL已广泛应用于游戏AI、机器人
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文件。网络上有很多资源站,请确保你从合法渠道获取。
-
定位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。在文件管理器中打开这个路径。 -
集成游戏数据 :在这个
SegaGenesis文件夹里,你应该能看到一个名为StreetFighterIISpecialChampionEdition-Genesis的文件夹。将本项目data/文件夹下的四个文件:Champion.Level12.RyuVsBison.state(最终关的存档状态文件)data.jsonmetadata.jsonscenario.json复制进去, 覆盖 原有的文件(可能需要管理员权限)。这些.json文件包含了读取血量的内存地址等信息,是自定义奖励函数的基础。
-
放置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 脚本,它会开始漫长的学习过程。在这个过程中,你应该关注几个关键点:
- 控制台输出 :会显示当前训练的步数(timesteps)、平均奖励(mean reward)、回合长度(episode length)等。平均奖励的整体上升趋势是学习有效的标志。
- 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的灰度图像,信息损失很大。可以考虑:
- 帧堆叠(Frame Stacking) :单张静态图片无法感知“速度”。常见的做法是将连续4帧画面堆叠在一起,作为一次观测输入给AI。这样AI就能判断对手是正在冲过来还是在后退。
- 特征提取 :与其给AI原始像素,不如先用一个预训练的卷积神经网络(CNN)对画面进行特征提取,再将提取出的高级特征(如角色轮廓、位置信息)输入给策略网络。这可以大幅降低需要学习的数据维度。
- 添加非视觉信息 :除了画面,可以直接将游戏内存中的结构化数据(如双方血量、能量槽、眩晕值、距离)作为额外输入向量,与图像特征融合。这相当于给了AI一个“游戏内HUD”,学习起来会更直接。
5.3 解决“过拟合”问题的实战策略
- 课程学习(Curriculum Learning) :不要一开始就让AI在最高难度下面对最终Boss。可以先从简单关卡、简单对手开始训练,等AI学会基本移动和攻击后,再逐步提高难度,最终挑战Boss。这模拟了人类的学习过程。
- 域随机化(Domain Randomization) :在训练过程中,随机化一些游戏条件。例如,随机化双方的开局位置、随机化Boss的初始行为模式、甚至随机化游戏背景的颜色(通过图像处理)。这强迫AI学习更本质、更鲁棒的特征,而不是记住固定的像素模式。
- 集成学习(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+的支持可能在某些版本上不稳定。
解决 :
- 尝试单独安装核心包并指定版本:
pip install gym-retro==0.8.* stable-baselines3==1.8.*- 如果涉及需要编译的包(如
box2d-py)在Windows上失败,可以搜索并安装预编译的wheel文件,或者安装Microsoft Visual C++ Build Tools。- 终极方案:使用Docker。项目作者如果能提供一个Dockerfile,可以完美解决环境一致性问题。
问题2:运行 test.py 或 train.py 时,出现 FileNotFoundError 或 retro.core.InvalidStateError ,提示找不到ROM或state文件。
排查 :这是路径问题。99%的情况是ROM文件没放对位置或名字不对。
解决 :
- 再次核对 3.2节 的步骤。确保ROM文件在正确的游戏文件夹内(
...\StreetFighterIISpecialChampionEdition-Genesis\),并且名字是**rom.md**。- 确保
.state和.json文件已经覆盖了原文件夹内的文件。- 尝试在Python代码中打印出retro的数据路径进行确认。
问题3:训练开始后,Tensorboard中的奖励曲线不上升,或者AI的行为非常奇怪(如一直朝一个方向走、不停跳跃)。
排查 :奖励函数设计可能有问题,或者学习率设置不当。
解决 :
- 检查奖励值 :在
env.py的step函数中添加打印语句,输出每一步的奖励值。看看奖励是否在合理范围内(通常绝对值不超过10)。如果奖励值巨大或为NaN,说明计算有误。- 可视化AI的视角 :修改测试代码,将预处理后的观测图像(那个84x84的灰度图)保存或显示出来。确认AI看到的画面是否是你期望的样子(角色是否在画面中,图像是否清晰)。
- 调低学习率 :如果策略损失剧烈波动,尝试将
learning_rate降低一个数量级(例如从3e-4改为3e-5)。- 简化问题 :先在一个极简的环境下测试,比如让AI学习“移动到画面中央”这样一个简单任务,确保整个训练流程是通的。
6.2 训练效率与性能优化
问题4:训练速度非常慢,每一步都要等很久。
排查 :强化学习训练本身就是计算密集型任务。慢可能是由于:
- 图像预处理在CPU上进行。
- 没有使用GPU进行神经网络运算。
n_steps设置过大,导致每次更新前收集数据的时间很长。
解决 :
- 启用GPU :确保你的PyTorch(Stable-Baselines3的底层)是GPU版本。在Python中运行
import torch; print(torch.cuda.is_available()),如果输出True,则代码会自动利用GPU加速。- 优化环境交互 :Gym Retro的环境模拟是主要瓶颈。可以尝试使用
VecFrameStack和DummyVecEnv(Stable-Baselines3提供)创建多个环境并行运行,一次性收集更多数据,这是加速训练最有效的手段之一。- 调整
n_steps:适当减小n_steps(如从2048改为512),会增加模型更新的频率,可能加快初期学习速度,但可能会让梯度估计的方差变大。
问题5:模型似乎“忘记”了之前学到的技能,奖励曲线出现断崖式下跌。
排查 :这在强化学习中称为“灾难性遗忘”。当策略发生较大更新时,新的策略可能完全覆盖了旧的、有效的策略。
解决 :
- 调整PPO的
clip_range参数 :这个参数限制了新旧策略之间的差异。适当减小clip_range(如从0.2改为0.1),可以使更新更保守,避免策略突变。- 使用策略蒸馏或保存检查点 :定期保存模型权重。如果发现模型性能严重下降,可以回滚到之前的检查点,并以更小的学习率继续训练。
从零开始构建一个能玩《街霸2》的AI,就像教一个孩子学习一项复杂的运动。你需要为他设计合适的训练环境(游戏模拟)、制定清晰的奖惩规则(奖励函数)、并耐心地陪伴他进行无数次练习(训练迭代)。这个过程充满了挑战,但当你看到AI从跌跌撞撞到能打出漂亮连招,最终战胜曾经让你头疼的Boss时,那种成就感是无与伦比的。这个项目提供了一个绝佳的起点,它不仅是一套可运行的代码,更是一个展示如何将深度强化学习应用于复杂决策问题的完整案例。你可以沿着它指出的方向,去探索智能体感知、决策的更多可能性,甚至尝试将其应用到其他游戏或更广泛的序列决策问题中去。
更多推荐




所有评论(0)