ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

李宏毅机器学习笔记:CNN与RNN原理、组合与实战排查

李宏毅机器学习笔记:CNN与RNN原理、组合与实战排查 李宏毅机器学习笔记我陆陆续续整理到第三篇了前两篇基本都在跟回归、梯度下降、损失函数这些“基本功”较劲这次终于轮到两个绕不开的名字CNN卷积神经网络和RNN循环神经网络。这两个结构在课程里是分开讲的但我在实际项目里发现把它们放在一起理解反而更容易看清一件事——神经网络的设计从来不是“堆层数”而是把我们对数据的先验认知翻译成网络的连接方式和参数共享规则。CNN把“图像的空间局部性”写进了卷积核RNN把“序列的时间依赖性”写进了隐藏状态的循环箭头本质上都是在用结构换样本效率。这篇笔记适合谁看如果你刚学完全连接网络正在困惑“为什么图像任务非得用CNN直接堆前馈网络不行吗”或者你能背出LSTM有四个门但一到自己搭模型就不知道门控到底在防什么再或者你正在做课程作业、期末复习、面试准备需要一条能把原理、公式、代码和踩坑经验串起来的线索——那这篇应该能省下你不少翻资料的时间。我会尽量少写“正确的废话”多写参数怎么算、结构怎么选、训练崩了先查什么这些都是我调模型时真金白银换来的。1. 为什么图像任务绕不开CNN从前馈网络的三个死结说起很多教程上来就画卷积核滑动窗口但我觉得更有价值的切入点是先回答那个热搜问题图像处理为啥用CNN不用前馈神经网络把这个“为什么”想透卷积、池化、权值共享这些设计就不是需要死记的知识点而是自然而然推导出来的结果。1.1 参数量爆炸一张图就能压垮全连接层先算笔账。假设输入是一张 224×224 的彩色图展平后就是 224×224×3 150528 个输入维度。如果第一层隐藏层只放 1000 个神经元光这一层的权重矩阵就是 150528×1000 ≈ 1.5 亿个参数按 float32 算光权重就要占掉 600MB 显存还没算梯度和优化器状态。这已经是一个中等规模模型的全部预算了而我们才刚过第一层。更麻烦的不是显存是统计效率。1.5 亿个参数意味着模型有极强的记忆能力但图像数据里每个像素的独立信息量其实很低——相邻像素高度相关一团肉色的像素集合大概率是猫脸的一部分单独看某一个像素几乎说明不了任何问题。用海量参数去拟合高度冗余的输入结果就是训练集上表现很好换一批光照、角度略有差异的图就崩了也就是过拟合。我在早期做过一个很小的对比实验同样识别 10 类灰度小图全连接网络和简单 CNN 参数量差了将近 40 倍测试集准确率却是 CNN 高出一大截。那次之后我才真正接受“结构先验比参数数量更重要”这件事。1.2 图像天然携带的两个先验局部性与平移不变性李宏毅课程里把 CNN 的合理性总结成三句话我认为这几句值得反复琢磨。第一句是一张图里某些关键的模式往往比整张图小得多。识别一只猫不需要看完整张 224×224 的图看耳朵、眼睛、胡须附近的局部区域就够了。这意味着神经元没有必要连接全部输入只连接一个局部窗口就能提取到有效特征。第二句是同样的模式可能出现在图像的不同位置。猫的耳朵在左上角和在右下角本质是同一个模式应该用同一组参数去检测。这就是参数共享的动机——检测器不应该关心自己滑动到了哪里。第三句是对图像做下采样通常不会改变物体的类别。把一张猫的图缩小一半你还是能认出它是猫。这直接对应池化层的设计它让网络对小幅平移更宽容同时降低后续计算量。这三个先验合起来就解释了 CNN 为什么能用远少于全连接的参数达到更好的效果它不是从零开始学而是被结构“暗示”了应该往哪个方向学。1.3 CNN的设计哲学把先验写进网络结构里顺着上面的思路卷积层其实就是“局部连接 权值共享”的合体。一个 3×3 的卷积核在 224×224 的图上滑动它只连接输入的 9 个位置这是局部滑过整张图时用的是同一组权重这是共享。于是这一层的参数量从理论上的上亿直接降到 9×C_in×C_out 这个量级对于单通道输入到 32 通道输出也就 288 个参数。池化层则负责把“下采样不改变类别”这个假设落实成操作同时给网络带来一定的平移鲁棒性。值得注意的是这种鲁棒性是有限度的池化只对小范围平移有效对大角度旋转、尺度剧变基本无能为力所以后来才有了数据增强和各种注意力机制来补这块短板。理解了这一层你就会明白为什么数据量小的时候 CNN 比全连接更“抗饿”也会明白为什么把图像直接展平送进 MLP 通常是新手最容易犯的结构性错误。2. 卷积、池化、感受野把CNN的每一层拆开看搞懂了动机接下来就是实打实的机制。这部分我尽量把公式和直觉绑在一起讲因为只背公式很容易在调模型时翻车——比如卷积输出尺寸算错导致张量对不上是新手最常见的报错来源之一。2.1 卷积核到底在干什么从手工特征到自动学习你可以把卷积核想象成一个“模板匹配器”。一个 3×3 的核在图像上滑动每个位置做逐元素相乘再求和得到的数值就代表“这个局部区域和模板有多像”。如果核是边缘检测的形状输出的特征图上亮的地方就是原图里存在这类边缘的位置。手工设计特征的时代人们靠 Sobel、拉普拉斯这类固定核来提取边缘和纹理。CNN 的价值在于这些核的数值不再由人设定而是通过反向传播自动学出来。实际训练好的浅层卷积核可视化之后往往真的长得像边缘检测器、颜色斑点检测器这说明网络确实学到了人类视觉早期的类似特征。这里有个我踩过的细节坑卷积核的数量输出通道数决定这一层能提取多少种不同的模式。通道太少会欠拟合通道太多在小数据集上容易过拟合且显存吃紧。我一般从 32 或 64 起步根据数据集复杂度上下调整而不是一上来就堆到 512。2.2 步长、填充与输出尺寸一个必须背熟的公式卷积输出的空间尺寸由四个量决定输入尺寸 I、卷积核大小 K、填充 P、步长 S。公式是O floor((I 2P - K) / S) 1以 I32、K3、P1、S1 为例O (32 2 - 3)/1 1 32尺寸不变。这就是所谓的 same padding也是 3×3 卷积最常用的配置因为它能保持分辨率方便堆叠多层。如果改成 P0O 30每层缩小 2。反过来推也很有用当你想把特征图缩到目标尺寸时可以先定好想要的 O再反推 K、P、S 的组合。我在做检测类项目时经常这样定下采样节奏避免最后一层输出尺寸和标注框的坐标对不上。下面这张表是我平时参考的常用配置目标核大小步长填充输出尺寸变化保持分辨率311不变减半分辨率321约减半大幅压缩5 或 72计算得出按公式通道融合110空间不变通道可调2.3 池化层的取舍Max还是Average池化常见的有最大池化和平均池化两种。最大池化取窗口内的最大值突出最强烈的响应适合保留纹理和边缘这类显著性特征平均池化取均值输出更平滑对噪声更宽容但容易把弱响应也抹平。我的经验是分类主干网络里用最大池化更常见因为分类关心的是“有没有这个特征”用最大值保留最强证据更合理而在一些需要输出平滑特征的场合比如风格迁移或者最后的全局池化层平均池化会更合适。现在不少现代架构干脆用步长为 2 的卷积来替代池化好处是下采样过程本身也可学习而不是固定的取最大值规则。这个选择取决于你的数据和任务没有绝对优劣。但要注意池化层没有可训练参数用它替换卷积会减少参数量但降低表达能力反过来也要付出代价。2.4 感受野的递推计算别让深层神经元“看得太窄”感受野指的是输出特征图上一个点对应原始输入上多大的区域。这个量很关键如果感受野小于目标物体网络在理论上就不可能看到完整目标。递推公式是RF_i RF_{i-1} (K_i - 1) × 前面所有层步长的乘积我举个实际例子。三层 3×3 卷积步长都是 1那么感受野分别是 3、5、7。也就是说三层 3×3 叠加感受野等价于一层 7×7但参数量更少、非线性更多这就是“小核堆叠优于大核”的原因。如果中间插入步长为 2 的池化感受野会加速增长。这也是深层网络能在最后几个阶段看到整张图的原因。调试时如果发现模型对小目标效果很差一个常见原因就是下采样太激进深层特征图上的感受野虽然大但分辨率已经低到小目标只剩几个像素了。注意感受野只是理论上界实际有效感受野通常远小于计算值尤其在深层堆叠之后。所以不要过度依赖公式还是要靠可视化和消融实验来验证。3. 从LeNet到ResNetCNN骨架的演进与选型思路了解单个组件之后接下来要解决的是“怎么把这些层组织成一个能用的网络”。这部分我会按时间线梳理几条演进线索但重点不是背诵历史而是理解每一代架构到底在解决上一代的什么问题这样你自己设计网络时才有判断依据。3.1 演进主线从浅层堆叠到深层残差LeNet 是最早的成功范式之一几层卷积加池化再接全连接参数量小适合手写数字这类简单任务。AlexNet 把这个思路放大用 ReLU 替代 Sigmoid 缓解梯度消失用 Dropout 抑制过拟合再加数据增强在复杂图像分类上取得突破。VGG 的关键贡献是证明了“用小卷积核反复堆叠”这条路的有效性结构非常规整3×3 卷积加池化重复到很深。但它的全连接层参数巨大部署时显存和速度都不友好所以后来被全局平均池化替代。GoogLeNet 走了另一条路用多分支并行结构在同一层里用不同尺度的卷积核同时提取特征再拼接兼顾了多尺度表达和计算效率。ResNet 则直接针对“网络越深反而越差”这个退化现象引入跨层恒等连接让梯度可以绕过若干层直接回传。这个设计后来几乎成了深度网络的默认配置。3.2 1×1卷积和瓶颈结构省算力的实用技巧1×1 卷积初看很反直觉——一个 1×1 的窗口能提取什么空间特征答案是它根本不负责空间信息而是负责通道维度的信息融合和降维。它把每个像素位置上所有通道的值做一次线性组合实际上是一个作用在通道维度的全连接层但保留了空间结构。瓶颈结构的典型用法是先用 1×1 卷积把通道数压到原来的四分之一再在低维空间里做 3×3 卷积最后用 1×1 卷积把通道数恢复。这样虽然层数变多但计算量反而大幅下降参数量也随之减少。我在显存有限时经常用这招性价比很高。3.3 残差连接真正解决的问题很多人把残差连接简单解释成“防止梯度消失”这只说对了一半。更准确的说法是它让网络至少能学到恒等映射保证加深层数后性能不会比浅层差。如果新增的层没学到东西输出等于输入性能持平一旦学到有效特征性能就提升。这样就消除了“深了必然退化”的障碍。实现上非常简单一行加法out relu(bn(conv(x)) x) # 通道数和尺寸必须一致才能直接相加但要注意只有当输入输出通道数和空间尺寸一致时才能直接相加否则需要用 1×1 卷积或步长卷积做投影对齐。这个细节在动手实现时很容易漏报错通常比较隐晦。3.4 骨干网络选型不要盲目追新选骨干网络时我会看三个维度数据规模、部署环境和任务类型。数据量小的时候用预训练模型做迁移学习是最省事的路径冻结浅层、只训练后面几层通常几百张图就能出不错效果。数据量大、算力充足再考虑从头训练。移动端部署要考虑参数量和推理速度轻量级架构更合适服务端则优先保证精度。检测和分割任务还需要骨干输出多尺度特征所以像特征金字塔这类结构往往要额外加上。实操心得迁移学习时学习率要比从头训练小一个量级左右因为预训练权重已经接近局部最优大步长很容易把学到的东西“冲散”。我一般先用较小学习率训练最后几层稳定后再解冻更多层做微调。4. 序列建模为什么需要RNN把“记忆”加进网络图像讲完话题转到序列。CNN 处理的是有空间结构的数据而文本、语音、传感器时序这些数据的特点是顺序敏感——“我不喜欢你”和“你我不喜欢”包含的字完全一样意思完全不同。前馈网络对顺序一无所知这就是 RNN 存在的理由。4.1 序列任务的共同需求上下文依赖语言模型里预测下一个词需要看前文语音识别里同一帧的发音要结合前后音素才能确定股票和传感器数据里当前值往往与前若干时刻相关。这类任务有一个共同点输入长度可变且输出依赖历史信息。处理可变长度有两种朴素方案一是把所有序列补齐到固定长度送进 MLP二是用滑动窗口只看固定窗口的历史。前者浪费计算且丢失位置信息后者人为切断了长期依赖。RNN 的思路是让网络带一个内部状态随着时间步不断更新把历史压缩进隐藏向量里。4.2 展开图与参数共享时间维度上的同一个网络RNN 的核心公式可以写成h_t tanh(W_hh · h_{t-1} W_xh · x_t b_h) y_t W_hy · h_t b_y关键点在于无论序列多长W_hh、W_xh、W_hy 都是同一组参数。把 RNN 按时间展开看起来像是很深的网络但每一层用的权重完全相同。这带来两个好处参数量不随序列长度增长而且某个位置学到的模式可以泛化到其他位置。这其实和 CNN 的权值共享是同一个思想只是共享的维度不同CNN 在空间上共享RNN 在时间上共享。理解了这一点两者就不再是两个割裂的知识点。4.3 BPTT与梯度消失为什么长依赖那么难学训练 RNN 用的是时间反向传播本质上还是链式法则只是要沿着时间轴累积梯度。问题出在累积过程中每经过一个时间步梯度都要乘上权重矩阵和激活函数导数的组合。如果这个组合的谱半径长期小于 1梯度会随步数指数衰减传回远处时几乎为零反之如果大于 1就会指数爆炸。这就解释了 RNN 的经典困境理论上它能记住很久之前的信息实际上只能有效利用十几步以内的依赖。表现出的现象是模型能学会“前面出现过某个词所以下一个词应该是这个”这种短期模式但对跨越几十个词的语法依赖束手无策。梯度爆炸相对好处理做梯度裁剪就行torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm5.0)梯度消失就麻烦得多因为不是数值问题而是信息丢失问题只能靠改变结构来解决于是 LSTM 登场了。4.4 LSTM的门控机制让信息有选择地通过LSTM 引入了一个细胞状态相当于一条“信息高速公路”通过三个门控制信息的写入、保留和输出。遗忘门决定上一时刻的细胞状态保留多少输入门决定当前候选信息写入多少输出门决定暴露多少给隐藏状态。它的美妙之处在于细胞状态的更新是加法形式c_t f_t ⊙ c_{t-1} i_t ⊙ c̃_t加法的梯度传回去是常数项不像乘法那样连乘衰减所以长距离梯度更容易保住。这就是 LSTM 能记住长期依赖的根本原因而不是因为“门很多所以很强大”这种模糊说法。GRU 是简化版把遗忘门和输入门合并成更新门去掉独立的细胞状态参数更少、训练更快在中小规模数据上经常和 LSTM 打平。我的经验是先用 GRU 试效果不够再换 LSTM能省不少训练时间。4.5 双向与多层几个容易忽略的配置双向 RNN 同时从前往后和从后往前处理序列把两个方向的隐藏状态拼接。对于分类、标注这类能看完整句子的任务双向往往明显优于单向。但要注意实时流式场景下不能用双向因为反向那一遍需要未来的输入。堆多层 RNN 能提升表达能力但层数一多训练不稳定和过拟合会同时出现。我一般 1 到 2 层起步配合 Dropout 和层归一化很少直接堆到 4 层以上。5. CNN与RNN的组合方式与替代方案单看这两个结构一个是空间专家一个是序列专家但它们经常出现在同一个系统里而且各自都有现代替代方案。这部分聊聊组合方式和选型判断。5.1 两者本质差异的对照维度CNNRNN处理的数据结构网格状如图像、频谱序列如文本、语音、时序参数共享维度空间卷积核滑动时间循环权重复用核心假设局部性、平移不变顺序依赖、状态可压缩主要瓶颈感受野与分辨率权衡长依赖梯度消失常见替代注意力、视觉Transformer自注意力、时序卷积这张表我在复习时经常拿出来过一遍基本能覆盖两者最关键的区别。需要强调的是“替代”不等于淘汰很多场景下卷积的归纳偏置依然更省数据尤其在样本量不大的工业检测任务里CNN 仍然比 Transformer 更稳。5.2 经典组合图像描述生成与视频理解图像描述任务是最典型的组合案例用 CNN 当编码器提取图像特征把最后一层的特征向量作为初始状态喂给 RNN 解码器让它一个词一个词地生成描述。CNN 负责“看图”RNN 负责“说话”分工非常清晰。视频理解也类似先用 CNN 逐帧提取空间特征再用 RNN 建模帧与帧之间的时序关系。这种做法在早期视频分类里非常常见后来逐渐被 3D 卷积和时空注意力替代但理解这个组合对掌握多模态思路很有帮助。实现时有个细节要注意编码器输出的特征维度往往和解码器的隐藏维度对不上需要加一个线性投影层。同时训练时通常用教师强制把真实前一个词作为输入推理时则用模型自己生成的词这种训练和推理的差异会导致误差累积序列越长越明显实际部署时要留意。5.3 自注意力的位置理解它与RNN的关系自注意力机制可以看作是对 RNN 长依赖问题的一种更彻底的解法。RNN 需要一步步传递信息路径长度随距离线性增长自注意力让每个位置直接和所有位置交互路径长度变成常数梯度传播不再依赖时间步累积。它的代价是计算量随序列长度平方增长而且丢掉了 RNN 天然的顺序归纳偏置需要额外加位置编码来补。李宏毅课程里对自注意力和Transformer讲得非常透如果你已经理解了 RNN 在梯度上的困境再去看自注意力会顺畅很多——它就是来解决这个困境的。我的建议是先扎实掌握 CNN 和 RNN再学注意力。跳过前面直接上手 Transformer很容易变成只会调库、不懂为什么这么设计的状态。6. 实战中的常见问题与排查技巧理论讲得再清楚真正跑起来还是会遇到各种报错和异常。这部分整理我遇到过的典型问题按“现象—原因—处理”的方式列出来方便你对照排查。6.1 张量尺寸对不上最常见也最好解决的报错新手最先遇到的通常是维度不匹配。常见原因有三个卷积输出尺寸算错导致后面全连接层输入维度不匹配残差相加时通道数或尺寸不一致序列任务里 batch 维度和时间维度顺序搞混。排查方法很直接打印每一层输出的 shape和公式手算结果对照。我习惯在搭建时先写一个假输入跑一遍前向确认 shape 链路通畅再开始训练能省下大量调试时间。6.2 损失不下降从数据到学习率的排查顺序损失一直不降我会按这个顺序查先看数据标签有没有错位尤其是自己写的 Dataset 类索引错位非常常见。再看输入是否做了归一化图像没归一化会导致训练初期极不稳定。检查学习率是否过大过大会表现为损失剧烈震荡甚至变成 NaN。检查损失函数和输出层是否匹配比如多分类用了 Sigmoid 却配交叉熵。最后才怀疑模型结构因为结构问题其实很少见。这个顺序的经验是数据问题占七成超参数占两成结构问题占一成。新手往往反过来一上来就改网络结构浪费时间。6.3 过拟合与欠拟合的判别与应对现象训练集表现验证集表现应对方向欠拟合差差增大模型、减少正则、训练更久过拟合很好差数据增强、Dropout、权重衰减正常好接近训练保持继续观察数据泄漏极好极好但线上崩检查划分逻辑过拟合在 CNN 上尤其常见于小数据集。数据增强的性价比通常高于调结构随机裁剪、翻转、颜色抖动这些手段能显著提升泛化。RNN 上的过拟合则更多表现为对训练语料的模板记忆用 Dropout 加在层间和嵌入层上效果明显。6.4 显存不够时的几个实用手段显存报错是绕不过的问题我常用的手段按优先级排序减小批量大小是最直接的办法但要注意批量太小会让批归一化统计不稳用混合精度训练能省下可观显存现代框架支持良好用梯度累积模拟大批量把多个小批次的梯度累加后再更新再不行就换轻量骨干或做梯度检查点。注意梯度累积时学习率要和有效批量大小匹配否则相当于变相改了优化步长训练动态会跟着变。6.5 序列任务里的几个隐蔽坑RNN 相关的坑更隐蔽。变长序列需要填充但填充位置如果在计算损失时没有掩码会污染梯度模型会去学怎么预测填充符号。序列长度差异很大时按长度分桶能显著提速并减少无效计算。另一个坑是隐藏状态的初始化。每个批次开始时如果忘记把隐藏状态置零或分离计算图梯度会跨批次累积训练看起来正常但结果很怪。这类问题不会报错只能靠对训练流程的清晰理解来避免。7. 我个人的一些使用体会如果只能留一句话给后来者我会说先搞清楚数据的结构再选网络的连接方式。图像有空间局部性所以用卷积序列有顺序依赖所以用循环如果数据既有空间结构又有长程依赖那注意力会是更合适的起点。这个判断顺序比记住任何公式都重要。调参这块我踩过最大的坑是过早追求复杂结构。有段时间我迷信深网络和花哨模块后来发现把数据清洗做扎实、把基线模型跑稳、把学习率调度调对提升往往比换结构更大。基线永远是第一步CNN 或 RNN 的简单版本往往就能给你一个可以对比的可靠数字。最后一个实用建议把每次实验的配置、数据版本和结果都记下来哪怕只是随手写在一个文档里。模型训练的随机性很大不记录的话两周后你完全不记得当时那个提升是怎么来的也很难判断是真的改进还是运气。
返回列表