1. 先搞清楚一件事参数量和计算量不是一回事我最早整理这份卷积核参数计算公式是因为一次模型文件大小对不上账。当时我在做一个轻量级分类网络层结构很简单卷积核就是常规的3×3和1×1按理说参数量应该很好算。结果模型导出之后文件大小比我手算的多了不少我一度怀疑是框架在导出时往模型里塞了什么额外结构。排查了一圈才发现问题不在框架在于我把某个分组卷积当成普通卷积来算了输入通道和输出通道在分组情况下被我先入为主地代错了。从那天起卷积核参数计算对我来说就不再是一行公式的事而是一套需要区分场景、逐项拆解的速查体系。很多人看到这个问题会觉得简单毕竟卷积核参数不就是核的宽乘高、再乘输入通道、再乘输出通道最后加个偏置嘛。但实际一深入分组卷积怎么算、深度可分离卷积为什么要分成两段算、空洞卷积参数到底变不变、全连接层和1×1卷积在参数上是什么关系每一处都有小坑。这篇文章就把我日常真正在用的那套速查方法完整写出来。适合这么几种人看刚入门CNN不久、想把手算参数量和实际模型对上的学习者需要快速评估网络体积、在边缘设备上判断模型能不能跑得动的工程师以及准备面试时想把这部分基础概念彻底理清的人。1.1 我在模型大小对不上账之后才开始整理这份速查先说说那次翻车经历的具体情况。我用的是PyTorch框架网络结构里有一段深度可分离卷积我图省事直接套用了标准卷积的参数公式把通道数整个代进去算。当时想的是3×3核、128输入、128输出也就是3×3×128×128再加上偏置算出来一个不小的数字。但实际上深度可分离卷积是拆成深度卷积和逐点卷积两层的参数应该分开算再相加我一口咬定的标准公式在结构上就套错了。结果模型文件大概比我手算的少了差不多7倍这个差距很明显也正是因为差距过大我才意识到公式出了问题。所以后来我把什么结构套什么公式整理成了固定表格不仅仅是记公式本身也记录公式适用的前置条件。这是我最想强调的一点卷积核参数计算从来不是一个公式走天下而是按结构分支走多条路径边界条件比公式本身更容易出错。1.2 参数量、显存占用、FLOPs三者之间的关系在往下展开公式之前必须先把三个概念分清楚否则后面很容易把参数计算和性能估算搅到一起。参数量Parameters衡量的是模型存储维度指的是这个模型里所有可训练权重的总数。它只跟网络结构有关跟输入分辨率无关——你用320×320还是640×640输入参数量是一样的。显存占用是模型运行时占的内存除了参数量本身还包括中间特征图、梯度、优化器状态等它跟输入分辨率强相关所以只能由参数量粗略推导不能直接画等号。FLOPs浮点运算次数衡量的是单次前向推理的计算量它既依赖网络参数又依赖输入特征图的空间尺寸。我见过不少朋友拿着参数量公式去估FLOPs方向是对的但少了一半的乘法加法转换估出来的数字往往会差2倍左右。这篇文章后面会专门写一套从参数量推导FLOPs的快速估算法但那套方法之所以能用前提是你已经分清了这三个概念。先把概念理清再进入公式部分你会顺很多。提示如果你只是在做模型选型先看参数量如果你在评估推理延迟重点看FLOPs如果你在算训练显存参数量和FLOPs都要看但还得额外考虑batch size和优化器类型。2. 标准卷积核参数公式一个词一个词拆开看标准卷积是所有卷积结构里最基础的形态也是判断其他衍生结构时的参照系。它的参数来源非常直观一批卷积核在输入特征图上滑动提取特征每个核负责产生一张输出特征图。2.1 公式本身和最容易忽略的偏置项标准卷积的参数量计算公式写成这样P (K_h × K_w × C_in) × C_out C_out逐项拆开看K_h 和 K_w 是卷积核的高和宽比如3×3卷积核这两个数都是3C_in 是输入特征图的通道数C_out 是输出通道数也就是这一层用了多少个卷积核最后的 C_out 是偏置项每个卷积核配一个偏置所以有几个输出通道就有几个偏置。括号里的 K_h × K_w × C_in 是一个卷积核需要的权值数量乘以 C_out 就是全部卷积核的权值再补上偏置得到总参数量。最容易被人忽略的其实是那个加 C_out。有些教程写的是 P K_h × K_w × C_in × C_out在偏置普遍存在的现代网络里这个写法严格来说不完整会少算一笔账。偏置本身数量不大在小网络里可能只占总参数的0.1%都不到但如果你在做一个有几十上百层卷积的网络逐层累加下来的偏置误差也不算小最后和实际模型对比时就会发现差了几个K。2.2 三个经典例子3×3、1×1、5×5我在速查本上固定放了三个算例分别对应小核、中等核、以及特殊的1×1核。这三个例子一旦记住基本可以覆盖日常开发里绝大多数卷积配置。第一个3×3卷积输入是RGB图片C_in 3输出64个通道。代入公式P 3 × 3 × 3 × 64 64 1,728 64 1,792第二个5×5卷积输入256通道输出256通道P 5 × 5 × 256 × 256 256 1,638,400 256 1,638,656这个数字可以直观看出一个现象卷积核尺寸稍微变大参数量就迅速上涨因为核尺寸是平方级影响。第三个1×1卷积输入512通道输出128通道P 1 × 1 × 512 × 128 128 65,536 128 65,6641×1卷积的参数公式和全连接层是一样的这个特殊点后面会展开讲。这里先记住它没有空间感受野只在通道维度上做混合所以参数量计算中 K_h × K_w 直接就是1。2.3 没有偏置的卷积怎么算卷积层可以配置 bias False这在很多轻量化网络里很常见。没有偏置时公式就直接砍掉最后加的那一项P K_h × K_w × C_in × C_out还是用刚才第一个例子不启用偏置的话3×3卷积、3通道输入、64输出参数量就是1,728而不是1,792。这里有个容易被忽略的细节有些算子融合方案比如卷积和批归一化融合会把偏置吸收进前面的缩放系数里这时模型文件里的参数计数不含偏置导出但推理算子里仍然有等效偏置。如果你拿手算结果和框架打印的numel对不上可以看看偏置开关是不是被框架层自动处理了。3. 输出特征图尺寸公式和参数计算配套使用既然标题是卷积核参数计算看起来跟输出特征图尺寸没关系。但实际工程里这两条公式几乎总是成对出现。原因很简单参数计算只告诉你模型有多重输出尺寸计算才告诉你这层卷积之后特征图变成了多大而下一层卷积的输入通道和空间尺寸都依赖这个结果。你算一个多层的网络总参数量时几乎必然走到输出尺寸这道工序。3.1 公式和边界情况处理输出特征图尺寸的标准公式是H_out (H_in 2P - K) / S 1 W_out (W_in 2P - K) / S 1其中 H_in、W_in 是输入高和宽P 是填充paddingK 是卷积核尺寸S 是步长。如果填充在左右或上下不对称Padding 要分开算但绝大多数框架里都是对称填充。注意这个公式在 S 大于1时经常出现除不尽的边界情况。很多深度学习框架默认采用向下取整floor也就是余数直接舍弃。比如输入是14×14卷积核3×3步长2填充1代入算一下H_out (14 2 × 1 - 3) / 2 1 13 / 2 1 6 1 7这里 13/2 向下取整得到6输出是7。如果你用的是向上取整结果会变成8。框架之间的差异就在这里PyTorch里默认是floorTensorFlow里如果显式配置了ceil_mode True才会变成向上取整。做手算对比时最好先确认你用的框架在哪一种模式。3.2 步长、填充对参数量的间接影响步长和填充不会直接改写卷积核参数公式因为卷积核本身还是那个尺寸、那个通道数。但它们会通过改变输出特征图的H_out和W_out间接影响下一层卷积的输入空间尺寸进而影响下一层的C_in和整体结构。打个比方你用步长2的3×3卷积替换掉步长1的卷积参数量完全不变但特征图尺寸直接减半后续层接进来的计算量会大幅下降。这也是为什么很多轻量化网络喜欢用步长卷积做下采样而不是池化——下采样效果类似但参数上不增加额外开销网络还能保持整体可训练。我在实际搭网络时一定会先手算每层的输入输出尺寸再决定下一层的填充和步长。一旦某一层尺寸不符合预期后续层的参数公式虽然还成立但整个网络的通道配比会错位模型初始化阶段就会报维度不匹配错误。这类错误排查起来非常麻烦不如在设计阶段就逐层核对。提示推荐把输出尺寸公式写在参数公式旁边每次算完参数顺手把特征图尺寸也算一下两步一起做能省掉很多后面调试维度的时间。4. 分组卷积、深度可分离卷积、空洞卷积衍生家族的参数速查标准卷积公式只是基础实际网络里大量使用各种衍生卷积。这一节是整篇文章的速查重点我把每个衍生结构的公式和适用场景都列清楚并配上算例。4.1 分组卷积参数减少G倍别算错除法分组卷积把输入通道分成G组每组拥有独立的一批卷积核。直观理解是原来一个卷积核要跨全部C_in通道做卷积现在只在 C_in/G 个通道上做之后各组结果在通道维度拼接起来。分组卷积的参数量公式是P (K_h × K_w × (C_in / G)) × C_out C_out注意是把C_in除以G不是把C_out除以G这个顺序错一点点结果就会天差地别。从公式看G越大参数越少G等于C_in时就变成了极端情况——每组只有一个输入通道卷积只在单通道上做空间卷积这就是深度卷积是深度可分离卷积的一半。举一个具体算例。输入256通道输出512通道约等于将通道翻倍卷积核3×3分组数G 8P 3 × 3 × (256 / 8) × 512 512 3 × 3 × 32 × 512 512 147,456 512 147,968如果同一配置不做分组P 3 × 3 × 256 × 512 512 1,179,648 512 1,180,160可以看出分组卷积的参数量正好大约是普通卷积的1/8和G值一致。分组卷积在实际网络中出现得非常多最典型的是某个轻量化网络结构里的通道混洗操作里面就广泛用了分组卷积来控制参数规模。手算这类网络时最容易踩的坑就是把输入通道和输出通道混在一起想成总共256通道分成8组每组32通道然后顺手把输出也除8了。记住输出通道数 C_out 是整个卷积层决定的输出规模不用分组再分一次。4.2 深度可分离卷积DW和PW分开算深度可分离卷积由两部分组成深度卷积Depthwise Convolution简称DW和逐点卷积Pointwise Convolution简称PW。深度卷积的每个卷积核只作用在一个输入通道上相当于分组数等于输入通道数的分组卷积。它的参数公式P_dw K_h × K_w × C_in C_in有偏置逐点卷积本质是1×1卷积作用是在深度卷积之后把各通道信息结合起来。它的参数公式P_pw 1 × 1 × C_in × C_out C_out所以深度可分离卷积的总参数量P_dsconv P_dw P_pw用一个完整的算例来演示。输入128通道输出128通道使用3×3卷积核。深度卷积部分P_dw 3 × 3 × 128 128 1,152 128 1,280逐点卷积部分P_pw 1 × 1 × 128 × 128 128 16,384 128 16,512总参数量P_dsconv 1,280 16,512 17,792而标准3×3卷积输入128、输出128P_standard 3 × 3 × 128 × 128 128 147,456 128 147,584深度可分离卷积的参数大约是标准卷积的12%差距正好在8倍量级附近。这也是为什么它成为移动端网络里的常客。我在实战中总结出的经验是算这一类网络时最好先分别写出DW层和PW层的参数再相加不要合并成一步。合并过程中你很容易漏掉DW层的偏置或者把PW层的C_in误写成上一层的输出尺寸。分开算虽然显得啰嗦但多写两步能避免后面全盘对不上账。4.3 空洞卷积与转置卷积参数不变别被输出尺寸迷惑空洞卷积Dilated Convolution通过在卷积核元素之间插入空洞扩大感受野但它没有新增任何可学习参数。它的参数公式和标准卷积完全一样。区别在于等效卷积核尺寸变了K_eff (K - 1) × dilation 1这会影响输出特征图的尺寸计算但不会影响参数量。比如3×3卷积核dilation 2等效核尺寸是K_eff (3 - 1) × 2 1 5感受野范围是5×5但参数量还是9个权值加1个偏置。这个感受野变大但参数不涨的特性让空洞卷积特别适合在语义分割任务里用既能保证感受野足够大又不让模型体积失控。转置卷积Transposed Convolution/Deconvolution的情况也类似它的参数量公式还是标准卷积那一套因为权重维度并没有变化只是计算方向相当于正向卷积的反向。转置卷积的输出尺寸公式不同常规写法是H_out (H_in - 1) × S - 2P K但参数这件事它跟标准卷积完全一致。换句话说你看到反卷积这个名字时千万不要以为它参数会翻倍它本质上只是在信号的流向上做了反转可学习权重的数量并没有增加。加上常见的注意力结构比如Squeeze-and-Excitation模块里的两个全连接层实际上也在用1×1卷积或全连接参数公式可以直接套之前的标准公式来算不需要额外记忆。5. 从参数量到模型大小存储换算与FLOPs估算参数量算出来之后下一步通常是估算模型文件大小。这个换算比很多人以为的要简单但精度问题容易踩坑。5.1 不同精度下参数占用的字节数模型参数在磁盘上的存储大小取决于精度。最常用的精度和字节数对应关系如下精度每个参数占字节数备注FP324 bytes训练默认精度FP162 bytes推理常用可减少一半存储BF162 bytes训练加速常用动态范围更大INT81 byte量化推理压缩最明显假设某个模型总参数量是25M用FP32存文件大小大约是 25 × 4 100MB用FP16存直接降到50MB量化到INT8就只有25MB左右。这个换算方法很实用每次我在评估一个预训练模型能不能塞进目标设备时脑子里就是跑一遍这个乘法。注意实际模型文件往往还包含优化器状态、元数据等信息所以文件大小会比纯参数量算出来的略大。但在模型导出后做纯推理时优化器状态会被剥离这个时候参数计算和文件大小的对账关系就比较准了。5.2 从参数量估算FLOPsFLOPs衡量的是模型做一次前向推理需要的浮点运算次数。它的估算公式在卷积层里可以写成FLOPs 2 × K_h × K_w × C_in × H_out × W_out × C_out乘2是因为一次卷积要做一次乘法和一次加法。很多论文里直接报的是MACs乘加次数也就是不加2那倍的K_h×K_w×C_in×H_out×W_out×C_out。这两者容易混淆看论文的数字时先搞清楚它说的是FLOPs还是MACs。还是用那个3×3卷积的例子输入3通道输出64通道假设输入图像224×224步长1填充1那么输出也是224×224MACs 3 × 3 × 3 × 224 × 224 × 64 86,671,360 FLOPs 2 × 86,671,360 173,342,720这个数字说明FLOPs和参数量的关系不是简单的倍数因为H_out和W_out会跟着输入分辨率走。所以不要试图用一个固定的系数从Paras直接换算FLOPs。正确做法是先确定输出特征图尺寸再代入公式计算。5.3 一个简洁的FLOPs速查公式我日常速查时其实不一项一项算而是用这个简写公式FLOPs_conv 2 × Paras_per_kernel × H_out × W_out × C_out其中 Paras_per_kernel K_h × K_w × C_in也就是每个卷积核的权值数。这样就把前面算参数时的中间结果直接复用了减少重复劳动。对于全连接层FLOPs计算和卷积层类似只是没有空间维度FLOPs_fc 2 × C_in × C_out算一个例子全连接层从4096降到1024MACs是 4096×10244,194,304FLOPs是8,388,608。提示参数量以KB/MB衡量FLOPs以百万M或十亿G为单位二者不能放在同一个数量级里比较。我在网上看到过不少参数量50M和FLOPs 50M搞混的讨论抓住单位就能避开这种坑。6. 完整手算一遍一个分类小网络的参数全流程前面公式拆了很多这一节把它们合起来使用从零手算一个很小的分类网络逐步算出每一层参数再累加得到总参数量。这就像把速查表从头到尾实际跑一遍后面你换成自己的网络时替换数字就行。6.1 网络结构定义假设网络输入是 3×32×32 的RGB图像比如某个小型图像分类任务。网络结构如下Conv1: 3×3卷积3通道输入16通道输出padding1步长1带偏置ReLU MaxPool: 2×2池化步长2Conv2: 3×3卷积16通道输入32通道输出padding1步长1带偏置ReLU MaxPool: 2×2池化步长2全局平均池化Global Average Pooling全连接层输出10类池化不会引入任何可学习参数这一点先记住遇到池化直接跳过参数计算。6.2 逐层手算过程先看Conv1P_conv1 3 × 3 × 3 × 16 16 432 16 448同时算输出尺寸输入32×32padding1卷积核3步长1H_out1 (32 2×1 - 3) / 1 1 32所以Conv1输出 16×32×32。池化后变成 16×16×16。Conv2P_conv2 3 × 3 × 16 × 32 32 4,608 32 4,640输出尺寸同样保持16padding1步长1池化后变成 32×8×8。全连接层输入维度是 32×8×8 2048输出维度10P_fc 2048 × 10 10 20,480 10 20,490总参数量P_total 448 4,640 20,490 25,578换算FP32文件大小25,578 × 4 102,312 bytes约等于100KB。这个模型非常小说明手算得和实际框架打印出来的数字一致时基本可以确认公式没问题。6.3 与框架实际测算结果对比的思路如果你用PyTorch跑这么一段代码可以拿到实际参数量def count_parameters(model): return sum(p.numel() for p in model.parameters()) model YourSmallCNN() total count_parameters(model) print(fTotal parameters: {total})注意如果模型启用了偏置parameters()里会把权重和偏置都包含进去。你要同时打印每一层的参数形状for name, param in model.named_parameters(): print(name, param.shape, param.numel())这样就能和手算的每一层数值逐项核对。我遇到这种情况时先看Conv1是不是448再看Conv2是不是4,640最后看全连接是不是20,490。哪一层对不上问题就出在哪一层不需要整个模型重新查。我自己反而建议初学者先在纸上把每层参数都算出来再跑代码确认因为手算过程中你会发现很多对维度理解的偏差而这些偏差在代码里往往会被框架自动抹平反而不容易暴露。7. 踩坑复盘与速查技巧我整理成了一张表这一节把我在实际项目里遇到的问题和最终沉淀的速查方式分享出来。每一条都是实实在在的对账经验。7.1 三个最容易出错的位置第一个坑是偏置到底有没有。很多预训练模型的卷积层确实不带偏置因为后面接了批归一化层批归一化的可学习参数可以覆盖偏置的功能。你算参数时如果给这一层加了偏置最后模型总参数就会比实际多出C_out个。解决方法很简单看模型定义里conv层的bias参数是True还是False然后决定公式里加不加末尾的C_out。第二个坑是分组卷积的通道除法。分组卷积的C_in除以G这个操作我见过反着除的、也见过把C_out也一起除的结果都和真实数量差得离谱。判断自己有没有算错可以先看分组数G是不是整数地除尽C_in如果除出来不是整数那公式一定代错了。第三个坑是深度可分离卷积的合并式求法。有些人觉得反正DW加PW最终等价于一种卷积图省事直接拿标准公式算结果偏差巨大原因就是没有把C_in和C_out分开对待。我现在的习惯是深度可分离卷积永远列两行先算DW再算PW最后求和不写合并式。表格速查放这里平时不确定就瞄一眼卷积类型参数量公式备注标准卷积(K_h×K_w×C_in)×C_out C_out无偏置则去掉C_out分组卷积(K_h×K_w×(C_in/G))×C_out C_outG为分组数深度卷积K_h×K_w×C_in C_in即分组数C_in的分组卷积逐点卷积1×1×C_in×C_out C_out本质为1×1卷积深度可分离DW参数 PW参数绝不可合并成一行空洞卷积同标准卷积感受野变大但参数不变转置卷积同标准卷积参数不因反向而翻倍7.2 我的速查方式把公式记成一句话说一个我常用的记忆方式。我记标准卷积公式时脑子里只记一句话一个卷积核有多少参数就看他能接触到多少输入值。一个核滑过一个输出位置时要对输入特征图上的一个 K_h×K_w×C_in 的小块做加权求和这一个小块里的元素数量就是单个卷积核的权值数。有了这句话其他变体都很容易推导分组卷积只是让每个核只接触1/G的通道深度卷积是让每个核只接触一个通道1×1卷积是让每个核只接触一个像素。输出尺寸公式我也有个记法有效窗口尺寸减掉之后除以步长加回自己。先把填充后的尺寸想清楚再把卷积核想象成一个在图上滑动的窗口步长决定你隔多远取一个值公式就顺带背下来了。这套速查方式陪我过了好几个不同框架的项目从PyTorch到TensorFlow、从MobileNet系列到轻量语义分割网络手算结果和框架打印数值都能对得上。最后说一句个人习惯遇到不确定的结构我不会靠记忆硬算而是把这一层单独拉出来跑一个小测试打印参数形状和数量再回头比对公式。宁可多花两分钟写一段三行的确认代码也不想让一个偏置项的错误藏在几十个卷积层里等最后模型训练起来才发现。这个习惯帮我省下的排查时间远比当初整理这张速查表花的时间多。