从像素到决策:FlappyBird游戏AI的CNN网络设计与PyTorch实现
1. 从像素到决策为什么这个小游戏需要CNN而不是全连接网络很多人第一次给FlappyBird写AI逻辑时第一反应是状态就几个数字用全连接网络不就行了。我一开始也是这么想的用小鸟的y坐标、速度、到上下水管的距离这几个特征喂给一个三层MLP训练了几百轮结果小鸟在管道密集区域的表现一塌糊涂。后来把游戏画面直接截图存下来看才发现问题所在当小鸟处于两根管道之间的狭窄缝隙时几个标量特征几乎完全一样但画面里管道开口的上下位置、小鸟相对开口的偏移方向这些空间信息全被压扁了。这就是为什么最终选择CNN。FlappyBird虽然简单但它的状态本质上是一张二维图像——管道的位置、开口的大小、小鸟的姿态这些信息在空间上是有结构的。CNN的卷积核天然擅长捕捉这种局部空间模式比如管道边缘、开口上下沿这类特征用卷积层去提取比手工设计特征靠谱得多。具体到我们的场景输入是游戏画面的灰度图尺寸我选的是84×84。为什么不是原始分辨率因为原始画面里大量像素是纯色背景信息密度低直接输入会让网络参数爆炸训练慢且容易过拟合。缩放到84×84是强化学习领域的经典做法既保留了管道开口的关键空间关系又把输入维度控制在可接受范围。你可以理解为把一张大图压成一张小图但管道和小鸟的相对位置关系没丢。另一个关键决策是帧堆叠。单帧图像有个致命问题——看不出小鸟是在上升还是下降。速度这个信息在单张静态图里是缺失的。解决办法是把连续4帧画面叠在一起作为输入这样网络就能从帧间差异中推断出运动方向。这个技巧在Atari游戏AI里被验证过无数次FlappyBird同样适用。所以最终输入张量的形状是(4, 84, 84)4个通道分别对应最近4帧的灰度图。注意帧堆叠不是简单地把4张图拼在一起而是按时间顺序排列让卷积核能在通道维度上感知到时间变化。如果你把顺序打乱网络学到的运动信息就是错的。2. 网络骨架设计三层卷积加两层全连接的取舍逻辑确定了输入形式接下来是网络结构。我试过好几种配置最终定下来的是一个相对轻量的架构参数量控制在几十万级别在普通笔记本上也能跑得动。下面把每一层的设计理由拆开讲。2.1 卷积层的通道数与核大小选择第一层卷积我用的是32个3×3的核步长2。为什么步长是2而不是1因为84×84的输入如果步长1第一层输出的特征图还是很大后续计算量翻倍。步长2相当于在卷积的同时做了一次下采样把空间尺寸降到42×42计算效率高很多。3×3的核是经典选择感受野不大不小刚好能捕捉管道边缘这种局部特征。第二层卷积是64个3×3的核步长2输出降到21×21。通道数从32翻到64是因为随着空间尺寸减小我们需要更多的通道来保留信息容量。你可以这样理解空间维度在压缩但特征维度在扩展总信息量不至于丢太多。第三层卷积是64个3×3的核步长1输出保持21×21。这一层不再下采样目的是在已有的特征图上做更精细的组合把低阶的边缘特征组合成高阶的管道开口形状这类模式。三层卷积之后特征图的形状是(64, 21, 21)。这个尺寸已经足够小可以展平后接全连接层了。2.2 全连接层的维度压缩与输出设计展平后得到64×21×2128224维的向量。直接从这个维度接到输出层参数太多所以中间加一层256维的全连接做过渡。256这个数字是试出来的——太小了表达能力不够太大了训练慢且容易过拟合。实测256在FlappyBird这个任务上收敛速度和最终表现都比较平衡。输出层就两个神经元对应两个动作不拍翅膀和拍翅膀。用Softmax激活输出两个动作的概率分布。训练时用交叉熵损失推理时选概率大的那个动作执行。整个网络的结构可以总结成下面这张表层类型配置输出形状输入-4帧84×84灰度图(4, 84, 84)1卷积32核 3×3 步长2 ReLU(32, 42, 42)2卷积64核 3×3 步长2 ReLU(64, 21, 21)3卷积64核 3×3 步长1 ReLU(64, 21, 21)4展平-282245全连接256 ReLU2566全连接2 Softmax2提示卷积层后面我习惯加ReLU但没加池化层。因为步长2的卷积已经起到了下采样作用再加池化会让特征图缩得太快管道开口的细节可能就丢了。2.3 参数量估算与显存占用算一下参数量心里有个数。第一层卷积32×4×3×3321184。第二层64×32×3×36418496。第三层64×64×3×36436928。全连接第一层28224×256256≈722万。全连接第二层256×22514。总参数量大约730万其中绝大部分集中在展平后的全连接层。这个参数量在GPU上训练毫无压力在CPU上单次前向传播大概几毫秒对于FlappyBird这种实时性要求不高的游戏完全够用。如果你想让网络更轻量可以把全连接层的256降到128参数量直接减半实测表现下降不明显。3. 用PyTorch把网络搭出来代码逐段拆解与形状验证理论说完了直接上代码。我用的是PyTorch版本1.10以上都行。下面这段代码你可以直接复制到项目里用每一行我都加了注释说明意图。import torch import torch.nn as nn import torch.nn.functional as F class FlappyCNN(nn.Module): def __init__(self, input_channels4, num_actions2): super(FlappyCNN, self).__init__() # 第一层卷积4通道输入32通道输出3x3核步长2 self.conv1 nn.Conv2d(input_channels, 32, kernel_size3, stride2, padding1) # 第二层卷积32通道输入64通道输出3x3核步长2 self.conv2 nn.Conv2d(32, 64, kernel_size3, stride2, padding1) # 第三层卷积64通道输入64通道输出3x3核步长1 self.conv3 nn.Conv2d(64, 64, kernel_size3, stride1, padding1) # 全连接层展平后28224维降到256维 self.fc1 nn.Linear(64 * 21 * 21, 256) # 输出层256维降到动作数 self.fc2 nn.Linear(256, num_actions) def forward(self, x): # x形状(batch, 4, 84, 84) x F.relu(self.conv1(x)) # - (batch, 32, 42, 42) x F.relu(self.conv2(x)) # - (batch, 64, 21, 21) x F.relu(self.conv3(x)) # - (batch, 64, 21, 21) x x.view(x.size(0), -1) # 展平 - (batch, 28224) x F.relu(self.fc1(x)) # - (batch, 256) x self.fc2(x) # - (batch, 2) return x写完代码第一件事不是训练而是验证形状对不对。我见过太多人因为形状不匹配报错排查半天发现是某个卷积层的padding忘了加。下面这段验证代码建议你每次改完网络结构都跑一遍。# 形状验证 model FlappyCNN() dummy_input torch.randn(1, 4, 84, 84) # 模拟一个batch的输入 output model(dummy_input) print(f输入形状: {dummy_input.shape}) print(f输出形状: {output.shape}) # 预期输出: torch.Size([1, 2])如果输出是[1, 2]说明网络结构没问题。如果报错大概率是某个层的输入输出维度对不上从报错信息里找到是哪一层检查它的输入通道数是否等于上一层的输出通道数。注意padding1在3×3核、步长2的情况下输出尺寸计算公式是(输入尺寸2×padding-核大小)/步长1。以第一层为例(842-3)/2142.5向下取整得42。所以输出是42×42和代码注释一致。如果你不加padding输出会变成41×41后续全连接层的输入维度就要跟着改。4. 输入预处理的三个关键细节灰度化、缩放与归一化网络搭好了但直接把游戏截图喂进去是不行的。原始画面是RGB三通道尺寸也不一定是84×84像素值范围是0到255。这三项都需要处理。4.1 灰度化为什么丢掉颜色信息反而更好FlappyBird的画面颜色很简单背景是浅蓝管道是绿色小鸟是黄色。这些颜色信息对判断管道位置有帮助吗几乎没有。管道的位置和开口大小完全由形状决定颜色只是视觉装饰。把RGB三通道压成单通道灰度图输入数据量直接降到三分之一训练速度提升明显而任务表现没有下降。灰度化的公式是Gray 0.299×R 0.587×G 0.114×B。这个权重不是随便定的它来自人眼对不同颜色敏感度的研究。绿色权重最高因为人眼对绿色最敏感。虽然我们的网络不是人眼但用这个经典公式做灰度化实测效果稳定。def rgb_to_gray(frame): # frame形状(H, W, 3)值范围0-255 gray 0.299 * frame[:, :, 0] 0.587 * frame[:, :, 1] 0.114 * frame[:, :, 2] return gray.astype(np.uint8)4.2 缩放84×84不是随便选的原始游戏画面分辨率因设备而异我测试的环境是288×512。直接缩放到84×84长宽比会变形。但FlappyBird的场景里管道是垂直的小鸟的水平位置基本不变所以水平方向的压缩对任务影响很小。垂直方向从512压到84管道开口的高度信息被压缩了大约6倍但仍然保留了足够的区分度。缩放用OpenCV的cv2.resize就行插值方式选INTER_AREA它在缩小图像时效果比默认的INTER_LINEAR更平滑不容易产生锯齿。import cv2 resized cv2.resize(gray, (84, 84), interpolationcv2.INTER_AREA)4.3 归一化把像素值压到0到1之间原始像素值是0到255的整数直接输入网络会导致梯度爆炸。归一化到0到1之间是最简单的做法除以255就行。也有人用均值方差归一化但在FlappyBird这个场景下除以255已经足够没必要搞复杂。normalized resized / 255.0预处理完的单帧是84×84的浮点数组。接下来要做帧堆叠把最近4帧拼成一个(4, 84, 84)的张量。这里有个细节游戏刚开始时没有4帧历史我的做法是用第一帧重复填充保证输入形状始终一致。from collections import deque class FrameStack: def __init__(self, k4): self.k k self.frames deque(maxlenk) def reset(self, frame): for _ in range(self.k): self.frames.append(frame) return self._get_obs() def step(self, frame): self.frames.append(frame) return self._get_obs() def _get_obs(self): return np.stack(self.frames, axis0) # 形状(4, 84, 84)提示帧堆叠的顺序很重要。deque的append是从右边加np.stack按顺序堆叠所以最早的一帧在最前面最新的一帧在最后面。这个顺序要和训练时保持一致否则推理时网络看到的时间方向就反了。5. 训练前的最后一步动作空间与损失函数的对应关系网络输出两个值对应两个动作。但这两个值不是概率是未归一化的logits。训练时用CrossEntropyLoss它内部会自动做Softmax所以网络输出层不需要加Softmax激活。这一点很多人容易搞混在输出层加了Softmax再用CrossEntropyLoss相当于做了两次Softmax梯度会出问题。# 正确做法输出层不加Softmax self.fc2 nn.Linear(256, num_actions) # 损失函数 criterion nn.CrossEntropyLoss() # 训练时 logits model(state) # 形状(batch, 2) loss criterion(logits, action_labels) # action_labels是0或1推理时选动作对logits做Softmax得到概率然后选概率大的那个。或者更简单直接比较两个logits的大小大的对应动作就是最优动作省去Softmax计算。with torch.no_grad(): logits model(state) action torch.argmax(logits, dim1).item()动作空间的设计还有一个选择是二分类还是三分类。有些实现会把不拍翅膀拆成什么都不做和向下加速两个动作变成三分类。我试过效果没有明显提升反而增加了训练复杂度。FlappyBird的物理规则很简单不拍翅膀就是自由落体没必要单独设一个向下加速的动作。6. 踩过的坑从形状报错到梯度消失的完整排查记录这部分是我在实际搭建过程中遇到的问题和解决过程你大概率也会遇到。6.1 形状不匹配展平维度算错第一次跑训练时报错说全连接层的输入维度是28224但我写的是64*21*2128224看起来没错。仔细检查发现我在第三层卷积后加了一个F.max_pool2d把21×21变成了10×10但全连接层还是按21×21算的。去掉池化层后问题解决。排查这类问题的技巧在forward里每经过一层就打印一次形状或者用torchsummary库自动输出每层的形状和参数量。from torchsummary import summary summary(model, (4, 84, 84))6.2 梯度消失全连接层太深我一开始在全连接部分加了三层256→128→64→2。训练时发现前几轮loss几乎不降后来把中间层减到一层256loss正常下降。原因是全连接层太多梯度在反向传播时逐层衰减到了卷积层几乎为零卷积核的参数基本没更新。解决办法有两个一是减少全连接层数二是加BatchNorm。我选了前者因为FlappyBird任务不复杂不需要太深的网络。6.3 过拟合训练集表现好但实际游戏死得快有段时间发现模型在收集的训练数据上准确率很高但实际玩游戏时小鸟很快就撞管道。原因是训练数据分布不均匀——大部分帧是小鸟在管道中间安全飞行的状态撞管道前的危险状态样本很少。网络学会了大多数时候不拍翅膀这个偷懒策略因为训练数据里不拍翅膀的样本占多数。解决办法是在数据收集阶段增加危险状态的采样权重或者用强化学习的思路让AI自己探索而不是纯监督学习。这部分涉及AI逻辑的训练策略后面单独展开。7. 网络搭好之后下一步该做什么CNN网络只是AI逻辑的一部分它负责从画面中提取特征并输出动作决策。但网络本身不会玩游戏它需要训练。训练数据的来源、损失的计算方式、探索与利用的平衡这些是接下来要解决的问题。我目前的进展是网络结构已经稳定输入输出形状验证通过单次前向传播在CPU上耗时约3毫秒完全满足实时推理需求。下一步计划是用游戏引擎自动收集人类玩家的操作数据作为初始训练集然后用强化学习做微调。如果你也在做类似的项目建议先把网络结构跑通用随机权重测试一下推理速度确认没有性能瓶颈之后再开始收集数据。网络结构改来改去很浪费时间定下来之后就不要再动了把精力放在训练策略上。

相关新闻

ETL设计实战:ODS层数据质量管控与清洗流水线构建

ETL设计实战:ODS层数据质量管控与清洗流水线构建

简介:本资源是一份面向数据工程师、BI开发人员及ETL初学者的系统性实践指南,聚焦ETL核心环节——数据抽取、清洗与转换的设计原理与落地要点。文档深入剖析ODS层数据抽取策略(含同构/异构数据库链接、文件类数据导入、增量更新机制&#xff0…

2026/10/17 12:55:10 阅读更多 →
算法竞赛入门经典例题答案拆解:从WA到AC的完整链路与避坑指南

算法竞赛入门经典例题答案拆解:从WA到AC的完整链路与避坑指南

简介:这份资源是《算法竞赛入门经典(第二版)》例题答案与配套代码合集,面向准备ACM/ICPC等算法竞赛的初学者与进阶选手,帮助读者对照书中例题检验思路、补齐实现细节。压缩包共224个文件,以191个cpp源码为主…

2026/10/17 12:55:10 阅读更多 →
RoboDojo榜首Astra:Physical RSI闭环如何让机器人自我进化

RoboDojo榜首Astra:Physical RSI闭环如何让机器人自我进化

1. 从RoboDojo榜单说起:一个三个月团队凭什么压过巨头RoboDojo这个榜单在机器人圈子里不算大众,但在做具身智能和机器人策略评测的人眼里,它的分量不轻。它不像某些跑分榜那样只看单一任务的完成率,而是把机器人放进一整套连续任务…

2026/10/17 12:55:10 阅读更多 →

最新新闻

软件系统运维方案模板实战:从5大模块到落地避坑指南

软件系统运维方案模板实战:从5大模块到落地避坑指南

简介:软件系统运维方案模板是一份面向运维团队、项目管理者及信息化部门的Word文档,旨在解决系统日常维护、故障应对与规范化管理缺失等问题。压缩包内包含1个docx文件,大小约119KB,当前已有63人学习下载。模板依据完整运维体系编…

2026/10/17 13:46:46 阅读更多 →
Kumo Tabular vs XGBoost:表格基础模型如何挑战传统机器学习?

Kumo Tabular vs XGBoost:表格基础模型如何挑战传统机器学习?

人工智能基础模型机器学习 【免费下载链接】Kumo-Tabular 项目地址: https://ai.gitcode.com/hf_mirrors/nvidia/Kumo-Tabular 点击查看 免费下载 Kumo Tabular 是 NVIDIA 发布的预训练表格基础模型(tabular foundation model),一…

2026/10/17 13:46:46 阅读更多 →
C语言typedef完全指南:别名、函数指针与const细节一次搞懂

C语言typedef完全指南:别名、函数指针与const细节一次搞懂

C语言中的typedef,很多初学者把它当“给类型起小名”,用熟了感觉也就那么回事。真正到了维护老项目、读第三方源码、或者做嵌入式跨平台开发的时候,才发现typedef的水远比想象的深:函数指针要不要typedef、结构体要不要typedef、t…

2026/10/17 13:46:46 阅读更多 →
从手工到自动化:用OpenClaw Skill与glab改造GitLab内部协作流程的TaoToken实践

从手工到自动化:用OpenClaw Skill与glab改造GitLab内部协作流程的TaoToken实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 13:46:46 阅读更多 →
北大清华阿里联合发布OmniEcho:让具身智能体真正听懂空间声音

北大清华阿里联合发布OmniEcho:让具身智能体真正听懂空间声音

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 13:46:46 阅读更多 →
AI时代程序员必看:脱离大模型,你还能独立工作吗?Vibe Coding挑战与应对

AI时代程序员必看:脱离大模型,你还能独立工作吗?Vibe Coding挑战与应对

本文探讨了Vibe Coding(氛围编码)如何借助AI改变编程方式,引发对程序员核心能力的思考。文章指出,AI擅长生成代码,但复杂系统判断、风险识别仍需人类智慧。强调基础知识和底层原理的重要性,提出程序员应驾驭…

2026/10/17 13:45:46 阅读更多 →

日新闻

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

简介:《DeepSeek使用方法.pdf》是一份面向DeepSeek入门及进阶用户的完整使用指南,重点围绕V3与R1两款模型的能力差异与适用场景展开,帮助读者根据任务类型与成本选择合适的模型。资源共1个PDF文件,压缩包大小约311KB,篇…

2026/10/17 0:00:25 阅读更多 →
200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

简介:这是一份聚焦DeepSeek提示词优化的《200Deepseek润色指令》PDF文档,共收录两百余条可直接套用的润色指令,涵盖语法修正、风格统一、逻辑梳理、表达精炼等维度,并针对商务写作、学术论文、广告文案等场景给出差异化建议。文档…

2026/10/17 0:00:25 阅读更多 →
OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

简介:本资源是一份基于OpenCV的本科课程设计完整报告,面向计算机视觉初学者、图像处理学习者及高校通信/信息类专业学生,解决物体智能计数与检测结果结构化记录的实际问题。项目覆盖图像预处理、轮廓检测、边界框绘制、轨迹栏交互式参数调节、…

2026/10/17 0:00:25 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/16 21:35:51 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/17 1:44:59 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/16 15:04:24 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 21:54:38 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 3:49:43 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 14:23:36 阅读更多 →