资讯动态

从卷积先验到变种卷积:CNN实现与排错指南

发布时间:2026/10/29 21:04:38 来源:尧图企业网站定制
简介这份CNN卷积神经网络详解PPT面向计算机视觉初学者与需要梳理深度学习知识体系的开发者围绕传统LBP等手工特征到端到端CNN特征提取的演进系统讲解卷积层、池化层、全连接层、激活函数及卷积尺寸计算并延伸到转置卷积、空洞卷积、深度可分离卷积、组卷积、可变形卷积和3D卷积等常见变体同时补充非线性激活、优化算法与正则化等训练必备要素。压缩包仅含1个pptx文件大小约7.58MB页面按“CNN从哪里来、细节实现、代码实战、CNN到哪里去”组织适合课堂讲授、自学复盘与技术分享。该内容已有6671人学习读者可借助目录框架快速建立CNN知识地图结合代码实战理解特征提取与分类流程并对照变体卷积和训练要点查漏补缺便于从概念到实现形成完整闭环。1. 卷积只是一个滑动的点积真正的门槛在它押中的三个先验CNN 卷积神经网络里最容易被高估的部分恰恰是“卷积”这个运算本身——十行 numpy 就能写完本质是一次滑窗点积。可把同样的参数量丢给全连接层去做图像分类准确率会掉一大截。差的那一截来自结构里被硬编码进网络的三个先验局部连接、权重共享、层次堆叠。这套课件从 LBP 这类手工算子讲起一路走到转置卷积、空洞卷积、深度可分离卷积主线其实只有一条——每加一个设计都是在回答“我要把什么先验塞给网络代价是什么”。看清这条线论文里那些眼花缭乱的变体就不那么玄学了而是能一眼看出它在补哪块短板。2. 从 LBP 到端到端特征这件事什么时候交给了梯度2.1 LBP 的工作机制以及它为什么只停在“中阶特征”LBP 的思路很朴素不看一个像素的绝对灰度只看它和周围八个邻居的相对大小。以中心像素为阈值邻居亮于它记 1暗于它记 0八个 bit 顺时针拼成一个 0 到 255 的整数。这个整数就是该位置局部纹理的“编号”把整张图的编号统计成直方图就得到一个图像描述子。它有效的原因值得多想一层阈值是中心像素自己所以整幅图整体变亮或变暗光照的单调变化不会改变任何一个 bit 的取值这就天然获得了光照不变性。代价是它把灰度信息彻底扔了只保留序关系。import numpy as np def lbp8(img): 8 邻域、3x3 固定窗口的原始 LBP返回比输入小一圈的编码图 h, w img.shape code np.zeros((h - 2, w - 2), dtypenp.uint8) # 8 个邻居相对中心的偏移按顺时针排第 k 个邻居占第 k 位 offsets [(-1,-1), (-1,0), (-1,1), (0,1), (1,1), (1,0), (1,-1), (0,-1)] center img[1:-1, 1:-1] for k, (dy, dx) in enumerate(offsets): neighbor img[1dy:h-1dy, 1dx:w-1dx] # 邻居 中心 记 1左移到第 k 位 code | ((neighbor center).astype(np.uint8) k) return code这段代码里最关键的一行是 k它把八个独立的布尔比较压成一个整数等价于给每个像素分配了一个“局部纹理模板编号”。参数只有两个——采样点数 P8 和半径 R1而这恰恰是它最大的约束。想描述更大的结构就得把 P 和 R 放大等价模式uniform pattern的种类数会随 P 迅速膨胀而每种模式对应什么语义没有任何机制去告诉它。课件里那句“只能手动设定特征的模式吗”问的就是这个人的先验写死在算子里任务变了算子不会跟着变。2.2 端到端把“设计特征”换成了“设计可学习性”传统流程是两段式的先手工算特征再拿特征喂 SVM 之类的分类器。这个结构有个致命处——两段之间是断开的。分类器分错了错误传不回特征提取器因为特征提取器里根本没有可调参数。LBP 的半径、采样点数、直方图分箱数只能靠人在验证集上试。端到端的改动看起来只是“把两段接起来”但性质完全不同卷积核里的每一个数都是可学习参数分类损失对它们的偏导可以一路回传。网络在做的其实是——在参数空间里搜索那组最能降低分类损失的模板。人不再回答“边缘长什么样”只回答“第一层是 3×3、通道数是 64”。对比项手工特征 分类器端到端 CNN特征来源人工设计算子梯度反传学出来的卷积核能否针对任务优化不能只能换算子能损失函数直接定义好坏参数量几乎为 0与通道数、核大小相关泛化依赖算子本身的通用性数据量 先验强度表里最后一行是很多人踩坑的地方端到端不是免费的它把“设计特征”的工作量换成了“喂数据和调结构”的工作量。数据量不够时手工算子的强先验反而更稳这也是小样本场景里 LBP、HOG 至今还活着的原因。2.3 三个先验局部连接、权重共享、层次堆叠局部连接的动机很直接图像相邻像素高度相关一个像素和它 200 像素外的点几乎没关系。全连接层硬把这种稀疏关系拉成稠密既浪费参数又破坏了空间结构。3×3 核在 224×224 的图上只连 9 个输入这个假设在自然图像上几乎总是成立。权重共享解决的是平移问题。同一个核在整张图上滑动意味着网络假设“猫的耳朵出现在左上角还是右下角用的是同一个检测器”。副作用是参数量与图像尺寸彻底解耦输入变成 448×448卷积层的参数一个都不增加只增加计算量。层次堆叠则来自一个经验观察——浅层学到的是边缘、颜色梯度中层是纹理和简单部件深层才是语义。这不是设计出来的更多是被深度逼出来的单层 3×3 核只看得到 9 个像素想看到整只猫只能一层层把感受野叠上去。拿数字说话。224×224×3 的输入直接全连接到 1000 维参数量是 1.5 亿换成一个 3×3×3、64 通道的卷积层参数量是 1728。四个数量级的差距就是这三个先验值多少钱。2.4 先验的另一面每条假设都会在某个任务上失效局部连接假设了“有用的模式是局部的”遇到需要全局统计的任务比如判断整张图的风格就得靠堆深度硬凑感受野。权重共享假设了“同一种模式出现在哪里都一样”遇到位置本身就是信息的任务比如关键点回归的绝对坐标就得额外编码位置。层次堆叠假设了“语义由局部到全局组合”遇到长程依赖比如两处相距很远的对称结构就力不从心。后面几章的变种卷积几乎都在拆这三个假设中的某一个空洞卷积拆的是“感受野必须靠加深”深度可分离卷积拆的是“跨通道必须有全连接”可变形卷积拆的是“采样点必须在固定网格上”。3. 卷积层的实现细节一次点积、一张特征图、一条尺寸公式3.1 卷积核到底在算什么互相关与模板匹配先把一个常见的误解澄清掉深度学习框架里的 conv2d 严格来说是互相关核不翻转。数学上的卷积要先翻转核再滑动但因为核的权重是学出来的翻不翻转对表达能力没有任何影响工程上就省了这一步。单通道的计算是输出 (i, j) 位置的值 输入窗口与核逐元素相乘再求和。多通道时核的深度必须等于输入通道数各通道乘积全部相加得到一个标量。所以一个核无论输入多少通道输出的都是一张单通道特征图想要 64 张特征图就得有 64 个核。import numpy as np def conv2d(x, w, b, stride1, pad0): x: (C_in, H, W) w: (C_out, C_in, kh, kw) b: (C_out,) C_in, H, W x.shape C_out, _, kh, kw w.shape xp np.pad(x, ((0, 0), (pad, pad), (pad, pad))) # 输出尺寸向下取整1 是因为首个窗口不需要滑动 Ho (H 2 * pad - kh) // stride 1 Wo (W 2 * pad - kw) // stride 1 out np.zeros((C_out, Ho, Wo)) for oc in range(C_out): for i in range(Ho): for j in range(Wo): hs, ws i * stride, j * stride patch xp[:, hs:hs kh, ws:ws kw] # (C_in, kh, kw) # 展平后做一次内积这就是一次点积的全部含义 out[oc, i, j] np.sum(patch * w[oc]) b[oc] return outnp.sum(patch * w[oc])这一句是整段代码的核心参与内积的维度是C_in * kh * kw。真实框架不会写成这样四重循环它会用 im2col 把每个滑窗展平成矩阵的一列再一次性交给 BLAS 做矩阵乘。理解 im2col 的意义在于它解释了为什么卷积的显存占用常常远大于参数量——展开后的矩阵尺寸是C_in * kh * kw * Ho * Wo是原始输入的kh * kw倍这也是后面显存排查章节要回头看的点。3.2 输出尺寸公式是怎么推出来的Ho (H 2P - K) // S 1这个公式在面试里被问烂了但推导过程比结论有用。填充后可用长度是H 2P。核长度 K最后一个合法窗口的起点必须满足start K H 2P也就是start H 2P - K。起点从 0 开始、步长 S能取的起点个数就是floor((H 2P - K) / S) 1。那个 1 是因为起点 0 也算一个窗口手算时漏掉它是最常见的翻车方式。空洞卷积要把 K 换成有效核尺寸K_eff d * (K - 1) 1。d2、K3 时 K_eff5感受野等效 5×5但参数还是 9 个。参数作用对输出尺寸的影响主要代价padding P在边界补零输出变大PK//2 且 S1 时尺寸不变边界引入虚假零值stride S滑窗步长输出约缩小为 1/S信息丢失可能出现混叠dilation d核内插空输出缩小等效核变大为 d(K-1)1网格效应局部不连续kernel K核尺寸输出变小参数按 K² 增长这四个参数不是自由的改 S 通常要同步改 K改 d 通常要保持d K否则核中间的空洞比有效区域还大采样会退化成一堆孤立的点。3.3 参数量和计算量两个完全不同的账参数量C_out * (C_in * K * K 1)只和通道数、核大小有关和输入分辨率无关。计算量乘加次数Ho * Wo * C_out * C_in * K * K和输出分辨率成正比。这个区别决定了很多工程决策。想在移动端减参数量砍通道数、换小核就够想减推理延迟光砍参数没用必须砍输出分辨率或者用深度可分离卷积因为延迟主要由计算量决定。见过不少模型参数量只有几 MB推理却慢得离谱原因就是早期层保持了高分辨率、通道数又给得很足计算量全堆在那里。3.4 为什么两个 3×3 胜过一个 5×5两个 3×3 卷积堆叠感受野等于 5×5参数量是2 * 9 18而单个 5×5 是 25。三个 3×3 叠出 7×7 感受野参数 27单个 7×7 要 49。越往上差距越大这是 VGG 式结构的基本动机。但参数少只是表面理由。更关键的是中间多了一次非线性激活——两个 3×3 之间存在一个 ReLU整个映射的表示能力比单个 5×5 的线性卷积更强。反过来说如果两个卷积之间没有激活函数那它们完全可以被一个等价的 5×5 核合并堆叠就白堆了。这也是排查不收敛时要先看激活函数的原因。3.5 池化与激活卷积层之外真正决定能不能训起来的部分池化层做的是降采样。Max Pooling 取窗口最大值反向传播时梯度只回传给窗口内取到最大值的那一个位置其余位置梯度为 0Average Pooling 把梯度平均分给窗口内每个位置。这个差别在多任务里很关键需要精确位置的任务分割、检测用 max pooling 会导致梯度稀疏位置信息也更难恢复。池化的收益是三重叠加的特征图变小、计算量下降、感受野按比例扩大、局部平移不变性增强。代价同样明确——它扔掉了窗口内的位置细节对小目标和密集排列的物体不友好。所以现代分割网络里池化层越来越少更多用 stride2 的卷积或者空洞卷积来替代因为卷积至少还能学。激活函数的存在理由更硬卷积是线性算子两个线性算子复合还是线性的堆一百层卷积不加激活等价于一个巨大的线性滤波器拟合能力和一层没区别。ReLU 之所以赢过 sigmoid核心在导数——sigmoid 在饱和区导数趋近于 0梯度一层层乘下去就消失了ReLU 正半轴导数恒为 1梯度能原样传回去。代价是负半轴导数为 0神经元可能“死掉”这才有了 LeakyReLU 之类的补丁。4. 变种卷积每一种都在补标准卷积的某块短板4.1 转置卷积把尺寸反推回去以及棋盘格是怎么来的转置卷积常被叫成“反卷积”这个叫法误导性很强——它不是卷积的逆运算不会还原出原来的输入它只是让输出尺寸变大的一种上采样卷积。名字里的“转置”来自矩阵视角如果标准卷积的前向可以写成输入向量左乘一个稀疏矩阵 W那转置卷积就是左乘 W 的转置。尺寸关系是H_out (H_in - 1) * S - 2P K output_padding。注意H_in - 1而不是H_in因为输出尺寸主要由“输入元素之间插入的间隔”决定首尾元素的贡献是固定的。def deconv_out_size(h_in, k, stride1, pad0, out_pad0): # 与 ConvTranspose2d 的尺寸公式一致 return (h_in - 1) * stride - 2 * pad k out_pad # 常见的 2 倍上采样h_in16 - 32 print(deconv_out_size(16, k4, stride2, pad1)) # 32 print(deconv_out_size(16, k3, stride2, pad1, out_pad1)) # 32两种配置都能把 16 变成 32但性质不同。k4、S2 时核尺寸能被步长整除每个输出位置收到的贡献次数相同k3、S2 时不能整除某些输出位置收到 2 次贡献相邻位置只收到 1 次累加后强度不均就形成了规律的棋盘格纹理。这就是转置卷积最经典的问题现象是生成或分割结果上出现规则网格纹原因是核尺寸与步长不整除导致重叠不均解决办法是让 K 能被 S 整除S2 就用 K4或者干脆先做最近邻/双线性插值放大再接一个普通 3×3 卷积修细节。后一种做法在实践中更稳也更容易调。4.2 空洞卷积不增加参数地扩大感受野标准卷积要扩大感受野只有三条路加大核、加深网络、加池化。三条路都会带来额外代价。空洞卷积提供第四条在核元素之间插零让同样的 9 个参数覆盖更大的区域。K3、d2 时9 个采样点分布在 5×5 的范围内有效核 5×5。代价是网格效应。d2 时采样点之间隔着一个像素那个像素永远不参与这一层的计算。单层还好多层连续使用同样的 dilation会出现大量“从未被采样”的像素对细小目标和密集预测任务影响明显。常见的补救是混合不同 dilation比如 1、2、5 交替让采样点相互覆盖或者在深层用普通卷积收尾。d K这条约束不是随便定的。d K 时有效区域内会出现大片连续的未采样区核实际上退化成了几个孤立的点采样卷积的局部性假设被彻底破坏。4.3 深度可分离卷积与组卷积从通道维度砍计算量标准卷积的计算量是Ho * Wo * C_in * C_out * K * K其中C_in * C_out这一项在通道数大时占绝对主导。深度可分离卷积把它拆成两步第一步 depthwise每个输入通道配一个独立的 K×K 核只在自己通道上滑动计算量Ho * Wo * C_in * K * K第二步 pointwise用 1×1 卷积把C_in个通道线性组合成C_out个计算量Ho * Wo * C_in * C_out。def sep_conv_cost(h, w, c_in, c_out, k3): std h * w * c_in * c_out * k * k # 标准卷积 dw h * w * c_in * k * k # 逐通道卷积通道间不交互 pw h * w * c_in * c_out # 1x1 卷积只做通道混合 return std, dw pw, (dw pw) / std std, sep, ratio sep_conv_cost(56, 56, 128, 256, k3) print(f标准 {std/1e6:.1f}M 可分离 {sep/1e6:.1f}M 占比 {ratio:.3f}) # 标准 2312.0M 可分离 267.4M 占比 0.116理论比值是1/C_out 1/K²。K3、C_out256 时约等于 1/9实测 0.116 与它吻合。这个省法有个隐含前提depthwise 那一步各通道之间完全没有信息交换表达能力被削弱所以 pointwise 那一层不能省而且通道数往往要给得比标准卷积更宽来补偿。组卷积是同一个思路的连续版本把通道切成 g 组组内做标准卷积组间不通信。g1 退化成标准卷积gC_in 就是 depthwise。它最初是为了把模型切到多张卡上训练后来发现精度损失可以接受就成了压缩手段。有意思的是 1×1 卷积可以看成g C_in的极端情形只不过它是把每组的单个通道重新组合所以跨通道能力还在。4.4 可变形卷积与 3D 卷积把几何先验也交给网络可变形卷积把“采样点必须在固定网格上”这条假设也拆了。它在标准网格上额外学一组偏移量(Δx, Δy)实际采样位置变成p p_n Δp。由于偏移是小数采样要用双线性插值。偏移量本身由一个平行的小卷积分支预测出来初始化为 0 时整个模块退化为标准卷积这个初始化设计很关键——它保证了训练初期不会因为随机偏移把特征图搅乱。代价有两块额外参数和显存偏移分支的卷积核以及不规则的内存访问模式——双线性插值要在四个非连续地址取值GPU 上很难向量化实测推理速度通常比标准卷积慢加速比不如理论计算量看起来那么漂亮。3D 卷积把核从(C_in, C_out, kh, kw)扩成(C_in, C_out, kt, kh, kw)多出的时间维用来处理视频或体数据。参数和计算量都按kt线性增长输入是 16 帧时一个 3D 卷积的开销是 2D 的 16 倍左右。所以主流做法是浅层用 2D 卷积提空间特征深层或少量层用 3D 卷积建时间关系或者把 3D 拆成空间 2D 加时间 1D。5. 排查清单尺寸对不上、不收敛、显存爆掉各有各的根5.1 尺寸类公式算出来的输出是负数现象很直白要么是框架抛 shape mismatch要么是手算的 Ho 出现负数或 0。原因通常是三种padding 给小了H 2P - K_eff变成负值stride 给大了可用长度不够滑一步用了空洞卷积却忘了把 K 换成d*(K-1)1按原核大小算出来的尺寸比实际大一圈。解决顺序建议固定成先写出K_eff再套公式最后检查整除方向。公式里的除法必须是向下取整不是四舍五入——差一个像素就会在后续的拼接concat或残差相加时报错而错误位置往往离真正的原因隔了好几层看 traceback 很难定位。提示残差结构要求主分支和 shortcut 的输出尺寸、通道数都一致改动 stride 或通道数时两边必须同步改否则会在相加那一步炸掉。5.2 不收敛类loss 卡在某个值一动不动现象是 loss 从第一个 epoch 就停在 0.69 附近二分类的随机水平或者完全不降或者几步之后直接变 NaN。最常见的三个原因按出现频率排忘了加激活函数多层卷积退化成单个线性变换模型容量和逻辑回归差不多学习率过大第一步就把权重推到饱和区梯度全变成 0数据标签错位比如图片按文件名排序读、标签按另一个列表顺序读shuffle 之后整批全错这种情况下 loss 会稳定在一个略高于随机的高位。定位方法我一般这么走先拿 20 到 50 张图关掉所有正则化和数据增强让模型去死记硬背。如果连这几张都过拟合不到 100%问题一定在模型结构、损失函数或标签管线上和数据量、泛化毫无关系。这一步能省掉大量瞎调超参的时间。5.3 显存类参数量明明很小却 OOM现象是模型文件只有几 MB一开训就报显存不足或者把 batch 从 8 调到 4 能跑、调到 6 又炸。原因是显存的大头从来不是参数而是激活值。前向时要保存每一层的输出供反向使用占用和batch * 通道数 * 高 * 宽 * 层数成正比。im2col 还会额外放大展开后的矩阵是输入尺寸的K*K倍3×3 卷积就是 9 倍这一块临时内存经常被忽略。还有一个隐蔽的坑验证或推理时忘了包torch.no_grad()计算图会一直累积跑几十个 batch 后显存越用越多最后炸在一个看起来毫不相关的位置。排查时先看显存是不是随时间线性增长是的话基本就是这个原因。5.4 过拟合与“好得不像话”的准确率现象是验证集准确率高得离谱或者训练集 99%、测试集掉到 60%。前者通常是数据泄漏同一张原图裁出的多个 patch 被随机划分到了训练集和验证集模型在验证集上看到的其实是训练时见过的内容。解决办法是按原图 ID 或采集批次划分不是按裁剪后的小图划分。后者常见于预处理不一致——训练时做了归一化、验证时忘了或者 BatchNorm 在 eval 模式下用的是没校准好的 running stats。BatchNorm 这一条值得单独说训练时统计量是每个 batch 现算的推理时用滑动平均。如果训练轮数太少滑动平均还没收敛直接切 eval 会看到精度断崖式下跌。稳妥做法是切 eval 之前先拿一批训练数据跑几次前向让统计量稳定下来。5.5 转置卷积的棋盘格和它在分割任务里的连锁反应现象是生成图像或分割掩码上出现规则的网格状明暗纹理放大看像马赛克。原因在 4.1 已经说过K 不能被 S 整除时不同输出位置收到的累加次数不同重叠少的区域强度偏低形成周期性图案。分割任务里这个问题的危害被放大——边界附近的预测会沿着网格抖动IoU 掉得比肉眼看到的更明显。解决就两条路让 K 能被 S 整除S2 配 K4或者用“插值上采样 3×3 卷积”替代转置卷积。后者多了几个参数但输出平滑、没有周期性伪影在需要精细边界的任务里更值得。6. 感受野与精度的取舍加深、加宽还是回头改数据模型不work的时候第一反应往往是加层、加通道但真正该先算的是感受野够不够。有效感受野的粗算方式是逐层累加每层增加(K_eff - 1) * 之前所有层的 stride 乘积。一个 224 输入的骨干经过四五个 stride2 的下采样后深层的理论感受野早就超过整张图了但实际有效感受野远小于理论值——高斯分布式的权重让边缘像素的贡献微乎其微。所以判断标准不是“理论感受野大于目标尺寸”而是“目标尺寸落在有效感受野的中心区域”。加深和加宽的取舍也有个大致边界。加深提升的是特征抽象层级和感受野但梯度路径变长需要残差连接和归一化兜底加宽提升的是单层能表达的模式种类数并行度好、训练更稳但参数量和显存增长是平方级的。经验上在数据量中等几万张的场景里先把深度堆到能覆盖目标尺度再考虑加宽比反过来划算得多。深度可分离卷积这类结构本质上就是用“加宽但每层更便宜”换“加深但每层更贵”。真正值得先动的是数据。我踩过最贵的一次坑是花了很久调结构和学习率最后发现验证集划分时把同一段视频的相邻帧拆散了模型其实在背帧。修正划分方式之后同一个网络结构的指标直接上了一个台阶之前调的参数全白费。从那以后我每次开新任务都强制先走一遍三件事打印每个类的样本数和划分来源、拿几十张图做单批过拟合验证、把预处理管线固化成同一个函数给训练和推理共用。这三步不做完调参调到天亮也大概率是在给数据问题打补丁。这套课件把 CNN 从手工特征到各种变种卷积的链路串成了一条线适合对着源码一页页往回推——先想清楚每一层在押什么先验、代价是什么再去看实现里那些参数为什么这么设。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑