CNN输入通道数与输出通道数:卷积核、参数量与1×1卷积
发布时间:2026/9/30 17:36:58来源:尧图网络
1. 为什么通道数是CNN里最容易被跳过的一个参数我带过不少刚入门深度学习的同学发现一个挺普遍的现象讲到nn.Conv2d这个API多数人能把前三个参数背下来——输入通道、输出通道、卷积核大小但真被追问一句输出通道数为什么不能随便写往往就卡住了。输入通道数和输出通道数这两个参数看着只是两个整数实际上是整个卷积神经网络里连接数据形态与特征表达能力的枢纽。搞不清楚它们你在改网络、做迁移学习、处理灰度图或者多通道传感器数据的时候几乎必然会撞报错。这篇文章我就围绕这两个参数把它们的物理含义、数学来源、代码表现、以及我在实际项目里踩过的坑一次性讲透。它不是一篇纯理论推导而是给真正要动手写网络、调模型的人看的。无论你是刚看完李宏毅老师CNN那节课、对卷积核扫过图片这个画面有点印象的新手还是已经能跑通LeNet5、但改网络时总靠试错的老手这篇都值得从头顺一遍。我先给一个结论式的判断输入通道数由数据的物理含义决定你几乎没有自由度输出通道数由你想提取多少种特征决定完全是设计选择。这两句话是全文的骨架后面所有的细节都是在给这两句话做注释。1.1 从一张彩色照片说起三个矩阵叠在一起先回到最朴素的场景。一张普通的RGB图片在程序里不是一个二维数组而是三个二维数组叠在一起。红色一个通道、绿色一个通道、蓝色一个通道每个通道的数值范围是0到255。它在张量里的形状是(3, H, W)这里的3就是通道维度。灰度图不一样它只有一个亮度值形状是(1, H, W)。医学影像里常见的单模态CT切片、工业相机的黑白图像、音频转成的梅尔频谱图都可能是单通道。而遥感影像可能夸张到十几个波段高光谱图像甚至上百个波段那它的输入通道数就是几十上百。所以当你说我要用CNN处理这批数据第一个要想清楚的判断就是每个样本在通道维度上到底有几层。这不是你设计出来的是数据天生的。你把数据丢进网络之前它的通道数就已经定死了网络只能去适配它反过来不行。1.2 输入通道数和输出通道数谁定谁很多人第一次写nn.Conv2d(3, 64, 3)会以为3和64是同一类东西其实它俩的地位完全不对等。in_channels3告诉卷积层我等下收到的特征图有3层每个卷积核必须也做成3层厚才能和它对齐相乘。写错了直接报错没有商量空间。out_channels64告诉卷积层给我准备64个不同的卷积核每个都做成3层厚最后输出64层特征图。这是你主动决定的。一句话总结它们的关系输入通道数是被验证的约束输出通道数是被创造的容量。前者是适配后者是赋权。理解了这个不对等你再看那些网络结构图里的数字变化就不会觉得是一堆随机的数了。2. 卷积核的真实形状它根本不是图上画的那个小方块几乎所有入门教程画卷积核都是一个3×3的小方格在图片上滑动。这是为了好懂做的简化代价是很多人从此把卷积核理解成二维的了。真实的卷积核是三维的立方体这一点不搞清楚参数量的计算和通道的变换你永远只能死记。2.1 卷积核的厚度必须等于输入通道数假设输入是(3, 32, 32)的RGB图卷积核大小取3×3。那么一个卷积核的真实形状不是3×3而是3×3×3——宽3、高3、厚3。这个厚度3是被输入通道数逼出来的输入的每一层都要有一个对应的二维核去跟它做运算。如果你设置了64个输出通道那就意味着有64个这样的3×3×3立方体每个立方体负责生成一层输出。它们之间不共享权重各学各的模式。用PyTorch验证一下就很直观权重张量的形状永远是(out_channels, in_channels, kH, kW)import torch.nn as nn conv nn.Conv2d(in_channels3, out_channels64, kernel_size3) print(conv.weight.shape) # torch.Size([64, 3, 3, 3]) print(conv.bias.shape) # torch.Size([64])看到没第一个维度是输出通道第二个维度是输入通道后面两个才是核的空间尺寸。这个顺序非常重要很多人算参数量时把前两位写反结果差出一个数量级。2.2 一次卷积运算究竟在算什么把一个3×3×3的卷积核放到输入(3, 32, 32)的某个位置上运算过程是这样的在红通道上取一个3×3的窗口和卷积核的第一层做逐元素相乘再求和。在绿通道上取同一个位置的3×3窗口和卷积核的第二层做同样的事。在蓝通道上重复一次。把三个通道的结果全部加起来再加上一个偏置值。最后这一个数字就是输出特征图上对应位置的值。输入通道在这里是被求和掉的它不会出现在输出里。这就是为什么输入是3通道输出可以是64通道——3这个数字在一次卷积里被压没了输出通道数完全取决于你摆了多少个卷积核。我用一个生活化的类比你有三张不同角度拍的同一个人脸照片卷积核像一个评委它要综合这三张照片给一个总评分。三个角度看完得出一个分数。如果请64个评委每个评委关注不同的点鼻子、眼睛、轮廓、光影就得到64个分数最后拼成64维的评语。输入是3张照片输出是64个分数中间那张照片的数量被评委们消化掉了。2.3 偏置项和通道的对应关系nn.Conv2d默认开启biasTrue注意偏置的形状是(out_channels,)不是(in_channels,)。原因很简单一个输出通道对应一张输出特征图同一张特征图上的所有位置共享一个偏置。所以你有64个输出通道就有64个偏置值。这个细节在手动实现卷积或者做量化时会踩到我见过有人以为偏置和输入通道有关调试半天。3. 输出通道数的本质你到底想提取多少种模式现在说重点。为什么别人的网络第一层是64有的是32有的甚至是24这个数字背后到底在权衡什么我给你拆三层来看。3.1 每个输出通道是一张特征响应图把输出通道想象成一个侦探团队。每个侦探只盯着一类线索有的专门找边缘有的专门找角点有的对颜色敏感有的对纹理敏感。输入图片经过一个卷积层后输出的64层特征图就是64个侦探各自画的线索热力图。哪块区域出现了他关心的模式热力图上对应位置的值就大。这也解释了一个现象浅层的卷积核学到的东西往往是可以肉眼可视化的比如Gabor边缘、条纹深层的卷积核学到的东西人就很难看懂了因为它响应的是高度抽象的语义组合。通道数越往上通常越大也是因为高层需要区分的概念组合更多。3.2 参数量公式的完整推导参数量这块网上公式满天飞我干脆带你从张量形状推一遍推一次你就再也不用背了。权重形状是(out_channels, in_channels, kH, kW)那参数量就是这四个数相乘权重参数量 out_channels × in_channels × kH × kW 偏置参数量 out_channels 总参数量 out_channels × in_channels × kH × kW out_channels注意看输入通道数是在乘法里的。这意味着输入通道翻倍参数量也翻倍输出通道翻倍参数量同样翻倍。这一点很关键当你把一个网络第一层的输入从3通道改成1通道灰度图或者改成4通道多了一路深度信息参数量是实打实变化的。我拿几个常见配置算给你看配置inoutk权重参数量加偏置经典第一层364794089472换灰度图1647313632003×3小核版364317281792深层大通道256512311796481180160从表格能看出一个反直觉的事实卷积层的参数量主要被输入通道×输出通道这一对数字支配核大小的影响反而被平方削弱了。7×7的核换3×3参数量降到原来的一半不到但深层里256到512这种通道跳跃参数量直接上百万。这就是为什么现代网络普遍用堆叠的小卷积核替代单个大卷积核把省下来的预算花在通道数上。3.3 一个可以手算的小例子拿LeNet5练手最合适它足够小通道数变化也很典型。输入(1, 32, 32)C1层6个5×5卷积核输出(6, 28, 28)。参数量6 × 1 × 5 × 5 6 156。S2层2×2平均池化下采样到(6, 14, 14)池化层没有可学习参数。C3层16个5×5卷积核输出(16, 10, 10)。这里我提醒一句原始论文里C3并不是把6个通道全部连进去的而是用了一张部分连接表目的是打破对称性、减少参数。现在大家复现的时候经常直接写成全连接参数量就是16 × 6 × 5 × 5 16 2416。S4层池化到(16, 5, 5)。C5层120个5×5卷积核此时输入空间尺寸刚好是5×5输出(120, 1, 1)等价于全连接。参数量120 × 16 × 5 × 5 120 48120。看整个链路通道数一路从1涨到6、16、120空间尺寸一路从32缩到1。空间信息在收缩通道信息在膨胀这是几乎所有CNN共享的节奏。理解了这一点你看ResNet、VGG的结构图会很顺因为它们的通道变化套路是一样的。4. 1×1卷积不改尺寸只改通道数的搬运工1×1卷积是通道数理解的一个关键试金石。如果你能一句话说清它在干嘛说明你已经把通道数玩明白了。我先给结论1×1卷积的空间感受野只有一个像素它做的事本质上就是在通道维度上做加权求和也就是跨通道的信息融合与通道数变换。4.1 1×1卷积到底在运算什么设输入(64, 56, 56)用32个1×1卷积核。每个核的形状是(64, 1, 1)参数量64个。把这64个数和某个像素位置上的64个通道值一一相乘再求和得到一个数。32个核就得到32个数拼成(32, 56, 56)。你会发现空间尺寸56×56完全没变只有通道数从64变成了32。它做的事情等价于在每个像素位置上独立跑了一个全连接层输入64维输出32维权重矩阵形状(32, 64)共2048个参数。这也解释了为什么1×1卷积常被叫通道维度的全连接。为什么有用两个原因一是可以自由升降通道数控制计算量二是引入非线性因为在1×1卷积后面通常接ReLU等于给每个像素的通道向量做了一次非线性变换。不带非线性的1×1卷积就只是线性投影表达能力弱很多这点在做消融实验时特别明显我建议你亲自动手把ReLU去掉跑一次观察准确率的差别。4.2 它在ResNet瓶颈结构里的角色ResNet的瓶颈块Bottleneck是1×1卷积最经典的用法。一个瓶颈块是这样走的先用1×1把256通道压到64再用3×3在64通道上做真正的空间卷积最后用1×1把64通道升回256。整个过程通道数瘦身-工作-复原。为什么这么做算笔账就明白了。如果直接在256通道上做3×3卷积参数量是256 × 256 × 3 × 3 589824。而走瓶颈结构三段加起来是256×64 64×64×3×3 64×256 16384 36864 16384 69632只有原来的八分之一左右。用通道数的压缩换来了计算量的骤降同时保留了深度。这就是通道数作为一个可设计变量能带来的实际收益。提示瓶颈结构有个硬性要求1×1降维后的通道数必须能被分组整除且空间卷积的输入输出通道要一致否则残差相加时会因为形状不匹配报错。4.3 用代码验证通道变换import torch import torch.nn as nn x torch.randn(1, 64, 56, 56) conv1x1 nn.Conv2d(64, 32, kernel_size1, biasFalse) y conv1x1(x) print(y.shape) # torch.Size([1, 32, 56, 56]) print(conv1x1.weight.shape) # torch.Size([32, 64, 1, 1]) print(sum(p.numel() for p in conv1x1.parameters())) # 2048跑出来2048正好是32 × 64 × 1 × 1。这个数你可以自己口算验证比看任何公式都记得牢。5. 从LeNet5到现代网络通道数的设计直觉光会算参数量还不够真正做设计的时候你得有个凭感觉拍数的能力而这个感觉来自对通道数变化规律的观察。5.1 为什么通道数越深越大前面看LeNet5已经发现了通道数在往上走。VGG是64→128→256→512→512ResNet-50是64→256→512→1024→2048。为什么大家都默认通道要膨胀我的理解是这样浅层的特征种类少但空间分辨率高深层的特征种类多但空间分辨率低。浅层只需要识别边缘、颜色这种基础模式几十个通道就够到了深层网络要区分猫的耳朵狗的鼻子车的轮子这类高度复杂的组合需要成百上千个通道来承载这些概念的多样性。同时深层的空间尺寸已经缩到7×7甚至1×1了即便通道很多总计算量还是可控的。这里有个经验比例空间尺寸每减半一次通道数通常翻倍。比如56×56配64通道28×28配128通道14×14配256通道。这个规律不是铁律但很多网络不约而同地遵循它因为它让每一层的特征图总元素个数保持相对稳定计算负载比较均衡。5.2 全局平均池化通道数变类别数的最后一跳分类网络最后一步很有意思假设特征图是(2048, 7, 7)怎么变成10个类别现代做法是全局平均池化GAP对每个通道的7×7取平均得到2048维向量再接一个Linear(2048, 10)。这里发生的是通道数直接映射到类别数。每个通道相当于一个概念探测器池化把它的空间响应压缩成一个强度值最后的全连接层学习这些概念的加权组合。相比老式的Flatten大全连接GAP的参数少得多还不容易过拟合。这一步是通道数概念最优雅的应用之一值得单独体会。5.3 通道数和感受野是两回事我见过一个常见误解以为增加输出通道数就能让网络看得更广。不能。输出通道数影响的是能表达多少种特征感受野决定的是每个特征能看到多大范围。通道数再多一个3×3卷积核的空间视野还是3×3。要扩大视野得靠堆叠层数、用空洞卷积或者下采样。把这两个概念分清楚你在优化网络时会少走很多弯路。6. 我在通道数上踩过的真实坑这一节是我觉得最有价值的部分因为这些错误在文档里没人告诉你但几乎每个做CNN的人都会遇到。6.1 输入通道不匹配的经典报错报错长这样RuntimeError: Given groups1, weight of size [64, 3, 7, 7], expected input[1, 1, 224, 224] to have 3 channels, but got 1 channels instead第一次看到这个报错我也懵了一下。翻译一下就是你的卷积核是按3通道准备的但你喂进来的数据只有1通道。常见触发场景你用灰度图训练却加载了预训练的RGB网络权重。你数据加载时把维度顺序搞错了比如(H, W, C)没转成(C, H, W)。多模态任务里某个分支忘记把深度图也拼进去。排查方法我总结成三步先打印x.shape确认通道维位置再打印conv.weight.shape看期望通道最后核对数据预处理管线。十次报错里九次是数据预处理的问题不是网络写错了。6.2 灰度图、RGBA和单通道数据的处理拿到单通道数据想用预训练模型有两条路各有取舍。第一条路是把单通道复制成三通道x_gray torch.randn(1, 1, 224, 224) x_rgb x_gray.repeat(1, 3, 1, 1) # (1, 3, 224, 224)好处是能直接套预训练权重坏处是三个通道内容完全一样预训练模型学到的颜色相关特征成了冗余效果未必比从零训练好。第二条路是改网络第一层model.conv1 nn.Conv2d(1, 64, kernel_size7, stride2, padding3, biasFalse)注意这样丢了预训练的第一层权重通常是拿原来三通道权重在通道维求平均再填进去比随机初始化收敛快。具体用哪条路我的经验是数据量小就用复制法借力预训练数据量大就改第一层从头训。这个判断依据不是拍脑袋而是因为小数据下预训练特征的价值更大。6.3 3D卷积里的通道维度理解3D卷积是另一个容易绕晕的地方因为维度一下多了。nn.Conv3d的输入是(N, C, D, H, W)权重形状是(out_channels, in_channels, kD, kH, kW)。你会发现通道数的逻辑跟2D完全一样只是空间维度从两维变成三维。C永远是数据类型决定的比如视频的RGB是3多帧灰度序列是1输出的通道数永远是你摆了多少个3D核。我做过一个视频动作识别的项目输入是(1, 3, 16, 112, 112)16是时间帧数。有个同事以为16也要算进输入通道写成in_channels48跑不通还找不到原因。记住时间维度归空间维度管不归通道维度管。这个区分在处理3D数据时是第一课。6.4 迁移学习时改头改尾迁移学习改网络核心就是两处入口改输入通道出口改输出类别数。中间部分因为是特征提取器通道数一般不动。出口通常长这样in_features model.fc.in_features # 比如2048 model.fc nn.Linear(in_features, 你自己的类别数)这里我强调一个坑改完别忘了冻结策略。如果你打算只微调分类头那前面的卷积层要设requires_gradFalse否则整个网络都在更新小数据集上很容易过拟合。冻结的粒度和数据集大小直接相关这个在实操里比任何公式都重要。7. 一些能直接用上的工程判断最后分享几条我在实际项目里反复验证过的经验帮你把通道数的设计从玄学拉回到有依据。7.1 通道数尽量用32的倍数32、64、96、128、256、512这些数不是随便选的。现代GPU的内存对齐和并行计算更偏好32的倍数用33或100这种数字不会报错但可能会让某些算子走不到最优路径。这不是迷信是硬件友好的现实考量。我做过对比把一层从64改成66速度基本没变化但从64改成96吞吐反而提升因为利用更充分。总的原则是优先32的倍数其次16的倍数实在不行至少保证分组卷积能整除。7.2 分组和可分离卷积对通道数的硬约束分组卷积groupsg有个硬性要求输入通道数和输出通道数都必须能被g整除。深度可分离卷积里的深度卷积Depthwise本质上是groupsin_channels此时特别容易踩坑如果上一层输出是15通道深度卷积还能跑但如果你后面接了个groups4的普通卷积15除以4除不尽直接报错。# 深度卷积每个通道一个核 dw nn.Conv2d(32, 32, kernel_size3, padding1, groups32) # 分组卷积要求通道数能被组数整除 gw nn.Conv2d(64, 128, kernel_size1, groups4) # 64和128都能被4整除我在设计移动端网络时养成了一个习惯所有通道数都设成能被常用组数2、4、8、16整除的数这样后面加分组结构时不用回头改。7.3 通道数和显存的关系显存占用大头是中间的特征图。一个(16, 256, 56, 56)的特征图用float32存储是16 × 256 × 56 × 56 × 4字节约51MB。训练时因为要存激活值做反向传播实际占用会翻好几倍。通道数翻倍显存和计算量都翻倍所以在显存吃紧时砍通道数是比砍batch size更有效的降压手段虽然后者更常见。如果你要做推理优化可以考虑把中间通道数统一到8的倍数并用通道剪枝去
网站建设高端定制企业官网