从CNN结构图到手写代码:尺寸计算与PyTorch实现详解
发布时间:2026/9/19 14:09:37来源:尧图网络
简介一份以PPT形式呈现的卷积神经网络结构图面向机器学习初学者、深度学习者、算法工程师及需要绘制网络结构图的课件制作/论文汇报者用于快速理解CNN的层次组成和参数流动。资源共1个文件为pptx格式压缩包大小1.85MB已有706人学习下载。内容以图形化标注为主涵盖输入层、隐藏层、输出层等通用结构并具体展示重叠卷积CONV Overlapping、11×11与3×3卷积核、5×5/3×3最大池化、pad2、stride2/4、通道数变化、特征图尺寸由227×227逐层压缩以及FC全连接层和softmax输出等关键设计同时包含ML Visuals通用卷积操作示意补充SENet、RCNN等典型变体以及针对EEG时序/频谱图像设计的CNN变体结构图。读者可自由编辑PPT页面作为课程讲义、组会PPT或论文插图也可借助标注细节快速对照理解经典CNN前向传播与特征提取过程。1. 一张结构图能读出的信息量比你想象的要多网盘上下到的《卷积神经网络结构图PPT版本》里AlexNet、SENet、MLP 的画法其实是三种不同的符号系统AlexNet 靠“核大小 步长 pad”标注传递信息SENet 靠小幅分支表达通道注意力MLP 则退化成纯节点连线。如果没有参数化阅读习惯这些图就是一堆框和箭头复制成 PPT 也讲不出东西。这篇文章要解决的是一个问题如何从一张 CNN 结构图反推出每一层的确切输出尺寸、参数量级和可复现代码。适合两类人一是手上有结构图但只能画出框、算不出尺寸的读者二是想把论文网络画成 PPT 且不想画错的工程师。文中用到的结构图来自机器学习之心整理分享的 PPT 素材其内容覆盖经典卷积网络与注意力变体下面逐层拆。2. 卷积层、重叠池化与全连接读懂 AlexNet 风格图的参数标注2.1 图像处理为什么不用前馈神经网络而是用卷积先回答一个被问了无数遍的问题图像任务里为什么是卷积神经网络而不是普通的前馈神经网络前馈网络把输入拉平成向量后每个像素与下一层所有神经元全连接这带来两个无法回避的问题。第一是参数量爆炸一张 227×227 的 RGB 图像拉平成 154587 维第一层隐层设 1024 个神经元权重大小约 1.58 亿个单层参数就超过后面要讲的完整 CNN。第二是空间结构丢失图像中相邻像素的关系、边缘的连续性在拉平后完全不可见。卷积神经网络用两个机制解决上述问题局部连接与参数共享。局部连接指每个卷积核只覆盖输入的一个小窗口比如 3×3 或 11×11窗口内像素共同响应一个局部特征参数共享指同一个卷积核在整张图上滑动时权重不变。这样一组 96 个 11×11×3 卷积核参数量只有 96×11×11×3约 3.5 万比全连接层小数个数量级同时还能保留“猫耳朵在图像左上角还是右下角”的平移信息。这也是为什么结构图里第一层永远是 CONV 而不是 DENSE。不少人在看结构图时会忽略一个细节卷积层的输出特征图数量等于卷积核个数而不是核的某个维度。图里写“96 kernels”就意味着这层输出 96 张特征图下一层输入的通道数随之变成 96。很多 PPT 里只画 3 个矩形但真正画图时每一层框上的标签应该写“H×W×C”C 就是上一层的 kernel 数。2.2 手算经典首层227 到 55 的卷积与池化尺寸推演结构图中最经典的一段是 CONV 11×11、96 kernels、stride4后面接“Max POOL 5×5, pad2”“Max POOL 3×3, pad1, stride2”。这里需要注意原图把两个池化参数堆在同一行里实际对应的是卷积后的两个不同池化方案其中 3×3、stride2 才是 AlexNet 采用的池化设置。卷积输出尺寸的通用计算公式是S_out (S_in - K 2×P) / S 1S_in 是输入边长K 是核边长P 是 padS 是 stride。注意这里默认向下取整。把输入 227 代入S_out (227 - 11 2×0) / 4 1 216 / 4 1 55这里 pad 按图中未标注视为 0得到 55×55×96。若图上有 pad2 的标注结果变成 56×55 之外的尺寸因此我在校对 PPT 时习惯把 pad 显式写出来不给默认值留歧义。接下来是池化层。池化不算参数只改变尺寸。图中出现“5×5, pad2”和“3×3, pad1, stride2”两种若按重叠池化Overlapping Pooling算即池化窗口大于步长窗口会重叠。以 55 输入、3×3、pad1、stride2 为例S_out (55 - 3 2×1) / 2 1 28输出是 28×28而经典 AlexNet 中这一层为 27×27。差异在于经典网络并未给第一个池化层加 pad即 pad0 时S_out (55 - 3) / 2 1 27这个矛盾反而说明了一个常见问题PPT 结构图在传递参数时经常省略 pad或者为了对称美观把 pad 画为 1。真正写代码时以计算结果和经典网络结构为准而不是盲信图注。2.3 卷积核、池化与全连接逐层核对一张表看清尺寸流动继续向后读结构图。后续层标注为 CONV 3×3, 256 kernels、CONV 3×3, 384 kernels、CONV 3×3, 384 kernels、CONV 3×3, 256 kernels每层 pad1、stride1。按公式逐层计算并把“按公式推导结果”与“PPT 原图标注”放在同一张表里对比层配置输出尺寸推导原图风格标注Conv111×11, 96, stride4, pad0227→5555Pool13×3, stride2, pad155→2827 或 28取决于 padConv23×3, 256, stride1, pad128→2827Conv33×3, 384, stride1, pad128→2813 上层前的 384 特征图Pool23×3, stride2, pad128→1413FC1全连接40964096FC2全连接40964096Outsoftmax类别数1000从表里能明显看到如果严格按照图注的 pad1 一路算下来最终池化输出是 14×14而原图标注体系里出现了 13×13。13×13 只有在 pool 层 pad0 时成立。所以我的判断是这份 PPT 里的数值混合了两套规范一部分沿用 AlexNet 原始结构一部分是画图者对“重叠池化”的理解。老手拿到这种结构图不会去背 27 还是 28而是直接动手按上表核一遍然后修正与自己目标不一致的 pad 参数。2.4 重叠池化的实际作用与选择理由结构图中“Overlapping”这个词值得单独讲。重叠池化是指池化窗口的步长小于窗口边长例如 3×3 窗口、stride2窗口之间在行和列方向各重叠 1 个像素。相比非重叠池化如 2×2、stride2重叠池化的优势有二。一是抑制过拟合更明显因为相邻输出携带了更多互相重叠的信息等价于一种隐式的空间平滑二是信息保留更连续特征图上尖锐的极大值不会因为窗口刚好错开而被漏掉。代价是计算量增加。同样输入尺寸下stride2 的 3×3 池化输出尺寸大于 2×2 池化后续卷积层的特征图尺寸会偏大计算开销跟着上升。因此并不是所有网络都适合重叠池化。我的建议是如果分类目标对平移扰动不敏感用 2×2、stride2 即可如果检测或分割任务需要更精确的空间信息重叠池化更值得保留。3. SENet 模块结构图里那个小分支到底在做什么3.1 从“特征图多了”到“哪些通道重要”的注意力进化前面读的 AlexNet 风格图只关心卷积核怎么滑动。而原 PPT 中还有一组带 SENet 标注的结构3×3 卷积、32 或 64 或 128 kernels每若干层后接一个 SE 块再接 Max Pool。SENet 全称 Squeeze-and-Excitation Networks核心思想是在通道维度上做注意力重标定。普通卷积输出一个 H×W×C 的特征图所有通道被一视同仁地送入下一层SE 模块则先压缩每个通道为一个标量再学习一组权重把这组权重乘回原特征图让网络自己决定“哪些通道值得放大哪些通道应当抑制”。在 PPT 结构图里SENet 常被画成特征图旁边的窄条或分叉看起来并不起眼但它的作用机理值得手推一遍。假设上一层输出 X形状为 H×W×CSE 模块首先做 Squeezez_c (1 / (H × W)) × sum( X_c )对第 c 个通道的所有空间位置取平均得到一个长度为 C 的向量 z。这一步相当于把空间信息压缩成通道描述符。紧接着是 Excitations σ( W2 × ReLU( W1 × z ) )W1 是 C×(C/r) 的降维矩阵r 是缩减率通常取 16W2 是 (C/r)×C 的升维矩阵σ 是 Sigmoid。最终拿 s 与原始特征图逐通道相乘X_out_c s_c × X_c3.2 为什么中间要降维reduction16 的工程权衡Excitation 部分如果直接用一个 C×C 的权重矩阵学习通道关系参数量是 C 的平方。以结构图里 128 通道为例就是 16384 个参数占比不高但要放到 384 通道的 AlexNet 风格层中单层 SE 参数达到 14.7 万接近一个 3×3 卷积核组的规模。引入 r16 后128 通道的 SE 参数变为128 × (128/16) (128/16) × 128 1024 1024 2048384 通道则从 14.7 万降到 1.8 万。缩减率 r 是超参数常用值有 8、16、32。r 越小表示能力越强但参数量和过拟合风险上升r 越大模块越轻量但对通道关系的建模越粗。我的工程习惯是网络通道数在 64 以下时 r 取 8保证注意力有足够表达能力通道数在 128 以上时 r 取 16优先控制参数量增速。这个经验在处理 PPT 中那组 32→64→128 通道递进结构时尤其有效。值得提醒的是SE 不仅适用于 CNN 分类网络也适用于结构图中的全连接之前的特征精炼阶段。放在最后一组卷积后、全连接前SE 相当于对送入分类器的特征做一次通道级筛选在不少开源实现中能带来 1% 到 3% 的分类准确率提升。3.3 把 SE 模块插入既有网络需要关注哪些参数结合 PPT 中出现的“CONV 3×3, stride1, 32 kernels → Max POOL 2×2, stride2 → CONV 3×3, stride1, 64 kernels → SENet”这段结构实际插入 SE 时要注意三个点。第一SE 放在卷积之后、激活函数之前还是之后主流做法是放在激活函数之后、池化之前因为激活后的特征图已经完成了非线性变换通道注意力作用在其上更稳定。第二Squeeze 用的是全局平均池化而不是全局最大池化。平均池化保留整体响应强度最大池化只保留最强位置通道注意力更关心整体激活水平两者混用会改变语义。第三SE 不改变特征图空间尺寸只改变通道权重所以插入位置不会影响后续池化层的尺寸计算这对对着 PPT 手工推导的人来说是个好消息。4. 把 PPT 结构翻译成可运行的 PyTorch 实现4.1 先把结构写成配置字典而不是直接写模型面对结构图直接开敲 nn.Conv2d很容易在 kernel size 和 padding 上出错。我的习惯是先从图中提取结构化配置写成 Python 字典再让字典驱动模型搭建。这样每一层参数在代码里一目了然也方便后续对照 PPT 逐项检查。以原图中 32→64→128 通道的 SENet 风格分支为例先定义基础配置config { input_channels: 3, conv1: {kernel: 3, out_channels: 32, stride: 1, padding: 1}, pool1: {kernel: 2, stride: 2}, conv2: {kernel: 3, out_channels: 64, stride: 1, padding: 1}, pool2: {kernel: 2, stride: 2}, conv3: {kernel: 3, out_channels: 128, stride: 1, padding: 1}, se_reduction: 16, fc_hidden: 512, num_classes: 10, }这段配置把 PPT 里的“3×3, stride1, 32 kernels”这类标注直接结构化。kernel 和 padding 分开写而不是写死成元组目的是方便用 2.2 节公式验证输出尺寸。注意池化层不需要 padding 参数时留空即可不要强行补 0。4.2 SEBlock 与主干网络实现定义 SE 模块和主干网络。SE 模块的代码应该能和任意卷积层组合因此单独定义为一个 nn.Moduleimport torch import torch.nn as nn class SEBlock(nn.Module): def __init__(self, channels, reduction16): super().__init__() self.squeeze nn.AdaptiveAvgPool2d(1) hidden max(channels // reduction, 1) self.excitation nn.Sequential( nn.Linear(channels, hidden), nn.ReLU(inplaceTrue), nn.Linear(hidden, channels), nn.Sigmoid(), ) def forward(self, x): b, c, _, _ x.shape z self.squeeze(x).view(b, c) gate self.excitation(z).view(b, c, 1, 1) return x * gate这段代码对应 3.1 节的两个公式。AdaptiveAvgPool2d(1) 把任意 H×W 特征图压成 1×1等价于全局平均池化。Excitation 内部第一层 Linear 完成降维ReLU 引入非线性第二层 Linear 恢复通道数Sigmoid 把输出映射到 0 到 1 之间当作门控权重。forward 里先取 batch 和通道数再把池化后的向量送入门控网络最后 reshape 成 (1,1) 以便与特征图逐通道相乘。接着定义主干网络。注意第一个卷积层的输入通道是 3对应 RGB 图像最后一个全连接层的输出是类别数示例数据集中是 10class SimpleSENet(nn.Module): def __init__(self, cfg, in_channels3, num_classes10): super().__init__() self.features nn.Sequential( nn.Conv2d(in_channels, cfg[conv1][out_channels], cfg[conv1][kernel], cfg[conv1][stride], cfg[conv1][padding]), nn.ReLU(inplaceTrue), nn.MaxPool2d(cfg[pool1][kernel], cfg[pool1][stride]), nn.Conv2d(cfg[conv1][out_channels], cfg[conv2][out_channels], cfg[conv2][kernel], cfg[conv2][stride], cfg[conv2][padding]), nn.ReLU(inplaceTrue), SEBlock(cfg[conv2][out_channels], cfg[se_reduction]), nn.MaxPool2d(cfg[pool2][kernel], cfg[pool2][stride]), nn.Conv2d(cfg[conv2][out_channels], cfg[conv3][out_channels], cfg[conv3][kernel], cfg[conv3][stride], cfg[conv3][padding]), nn.ReLU(inplaceTrue), ) self.classifier nn.Sequential( nn.AdaptiveAvgPool2d(1), nn.Flatten(), nn.Linear(cfg[conv3][out_channels], cfg[fc_hidden]), nn.ReLU(inplaceTrue), nn.Linear(cfg[fc_hidden], cfg[num_classes]), ) def forward(self, x): return self.classifier(self.features(x))这段代码把 PPT 结构中的“卷积—池化—卷积—SE—池化—卷积—全连接”流程完整实现。池化放在 SE 之后因为 SE 不改变空间尺寸先放大有用通道再降采样信息保留效果比先池化再注意力更好。4.3 用一次前向传播验证尺寸流动结构图是否理解正确最有说服力的验证方式是打印每一层的输出形状。写一个辅助函数遍历各层并实时打印张量尺寸def inspect_shapes(net, input_tensor): x input_tensor print(input:, tuple(x.shape)) for name, module in net.features.named_children(): x module(x) print(f{name:12s} - {tuple(x.shape)}) out net.classifier(x) print(classifier -, tuple(out.shape)) return out net SimpleSENet(config) dummy torch.randn(1, 3, 32, 32) inspect_shapes(net, dummy)以 32×32 输入为例输出结果应当是Conv1 保持 32×32×32Pool1 降到 16×16×32Conv2 保持 16×16×64SE 不改变形状Pool2 降到 8×8×64Conv3 保持 8×8×128最后分类器通过全局平均池化输出 1×1×128 再进入全连接。如果打印结果中某一层尺寸与预期不符优先检查该层的 padding 和 stride 是否与结构图标注一致而不是改后续层参数。4.4 参数量估算结构图里没画出来的开销结构图不会画参数量但 PPT 汇报时一定有人问。用下面的代码统计可学习参数数量total_params sum(p.numel() for p in net.parameters()) trainable_params sum(p.numel() for p in net.parameters() if p.requires_grad) print(fTotal params: {total_params:,}) print(fTrainable params: {trainable_params:,})上述 32→64→128 的小型 SENet 参数量大约在几十万量级其中 SE 模块占比很小。如果换成第 2 节那种 96→256→384→256 的 AlexNet 风格结构全连接层参数会迅速膨胀到数千万。因此PPT 上画网络时建议顺手标注各层参数量而不是只标特征图尺寸这样观众才能理解为什么全连接层往往成为内存瓶颈。5. 反推 pad 参数用一个小函数快速定位 PPT 结构图里的标注错误拿到一张来源不明的 CNN 结构图最耗时的环节是核对每层尺寸是否自洽。结构图上通常标了 kernel 和 stride却经常漏标 pad。遇到这种情况我可以根据输入输出尺寸反推 pad快速判断是图错了还是自己理解错了。已知卷积输出尺寸公式反推 pad 的公式为p ((S_out - 1) × stride - S_in kernel) / 2写成可复用的 Python 函数def infer_pad(S_in, S_out, kernel, stride): p ((S_out - 1) * stride - S_in kernel) / 2 return int(p) if p 0 and p.is_integer() else None验证第 2 节中的首层卷积输入 227输出 55核 11步长 4代入得到 p0说明该层确实没有填充与经典结构一致。再验证池化层输入 55输出 27核 3步长 2得到 p0说明若图中标注 pad1 而实际输出标为 27两者必然有一处不成立。用这个函数扫描整张结构图能在几分钟内找出所有参数不自治的位置。使用时的判断顺序有讲究。第一步先用已知的 kernel、stride 和 S_in 算出理论输出与图中标注比对。第二步若不符用 infer_pad 反推 pad看结果是否为非负整数。第三步若 pad 反推为负数或小数说明核大小、步长、输出三者之间必有一个标错优先怀疑 stride因为它对输出尺寸影响最大也最容易被画图时忽略。第四步若 pad 反推成功但与原图标注不同就以反推值为准并在 PPT 中修正。这个技巧对多分支结构同样有效。SENet 分支中 SE 模块不改变尺寸因此可以把 SE 当作透明层跳过直接验证前后卷积的关系跳连结构则要分别验证主路径和捷径两个尺寸流再用 add 操作要求两者完全一致。把 infer_pad 放在模型搭建之前的校验步骤里比堆一堆 assert 语句更直观也比一个个手算高效得多。网络结构的阅读瓶颈从来不在记忆层名而在把“3×3、pad1、stride2”这类三元组还原成确定的输出尺寸跑一次逆推函数比盯着 PPT 看五分钟管用。本文还有配套的精品资源点击获取
网站建设高端定制企业官网