尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

神经网络在计算机视觉中的第一性原理:从函数优化到工程实践

神经网络在计算机视觉中的第一性原理:从函数优化到工程实践 很多人接触计算机视觉里的神经网络第一反应就是卷积、ResNet、迁移学习、调参炼丹。但哥伦比亚大学这门《计算机视觉第一原理》系列讲到神经网络时并没有急着堆模型而是先回答了一个很朴素的问题神经网络到底在做什么如果这个问题答不清楚后面所有看似高级的操作都容易变成盲人摸象。我把这一讲反复看了两遍最大的感受是神经网络之所以能在计算机视觉领域占据统治地位不是因为它“模拟了人脑”而是因为它把视觉问题重新定义成了一个可微分的函数优化问题。一旦你用这个视角去看卷积、激活、损失函数、反向传播、学习率调度全都不是孤立的技巧而是同一套逻辑链条上的必要零件。这篇博客就沿着这个思路把神经网络在计算机视觉里的第一性原理拆开讲一遍。我不会复述课程原话而是把课程内容转成工程上可以落地理解的框架顺带给出一些实际训练模型时的经验。1. 先想清楚神经网络在计算机视觉里到底扮演什么角色1.1 它不是在复刻人脑而是在构造一个可学习的映射计算机视觉的传统路线是先设计特征提取器再训练分类器。比如边缘检测、颜色直方图、SIFT、HOG这些特征都是人类根据经验手工定出来的。问题是视觉世界太复杂手工特征永远只能覆盖一部分模式。到了复杂场景下特征设计本身就是最大的瓶颈。神经网络换了一个思路我不再告诉模型“什么是重要的特征”而是设计一个足够灵活的数学结构让它从数据里自己学出特征。这个数学结构本质上就是一个多层嵌套函数输入是像素值比如 224×224×3 的张量。中间经过一系列线性变换和非线性激活。输出是一个概率分布比如“猫 0.8狗 0.15鸟 0.05”。在哥大那门课里这个观点被反复强调神经网络就是一个从高维输入到语义输出的函数。它不是一个神秘黑箱而是一个带有大量可调参数的函数族。训练的过程就是根据数据不断调整参数让这个函数在训练集上输出正确标签并且希望它在没见过的新数据上也表现良好。理解这一点很重要。很多人把神经网络当成“模型”其实更准确的说法是“带有先验的函数逼近器”。它能不能解决你的视觉问题取决于你给它的结构是否匹配任务的结构。1.2 第一性原理损失函数才是训练的起点既然神经网络是函数那怎么评判函数好不好需要一个量化指标。这就是损失函数。在计算机视觉里常见损失函数有分类任务用交叉熵损失。回归任务用 L2 或 L1 损失。检测任务可能会用分类损失加回归损失的组合。分割任务通常用像素级交叉熵或 Dice 损失。但无论哪种损失它们都有一个共同点一定要可微。因为神经网络学参数的机制是反向传播而反向传播的第一步就是计算损失对输出层的梯度。如果损失函数不可导整个学习过程就断了。所以第一性原理的链条其实是这样的视觉任务被建模成一个函数逼近问题。函数逼近好坏用损失函数度量。损失函数可导梯度就可以从输出端传回输入端。用梯度下降更新每一层参数让损失越来越小。这就是神经网络的全部骨架。激活函数、卷积层、批归一化、残差连接都是在让这个骨架更稳定、更高效而不是推翻这套范式。2. 一次前向传播里图像是怎么变成答案的2.1 张量形状变化从像素到语义的“翻译”过程假设我们输入一张 32×32 的彩色图片形状是(3, 32, 32)。在 PyTorch 里通常是(batch, channel, height, width)也就是(N, 3, 32, 32)。神经网络要做的事情就是一步一步把这个张量“变窄变深”空间分辨率逐渐降低通道数逐渐增加直到最后一个向量能对应分类置信度。这个过程可以用一组常见操作描述输入 (N, 3, 32, 32) → 卷积ReLU池化 → (N, 32, 16, 16) → 卷积ReLU池化 → (N, 64, 8, 8) → 卷积ReLU池化 → (N, 128, 4, 4) → 展平 → (N, 2048) → 全连接层 → (N, 10) → Softmax → (N, 10)每一步都是在做一件事把图像中局部特征逐步组合成全局语义。一开始卷积核学到的是边缘、颜色块中间层学会纹理、局部形状深层学到的是物体部件甚至整个物体的抽象概念。这个“从局部到全局、从具体到抽象”的层级结构是卷积神经网络最核心的归纳偏置。所以你看神经网络前向传播时不要只是盯着代码要养成一种习惯每经过一层问一句“这个张量的 shape 为什么变了” 比如卷积后通道数为什么变多因为要提取不同类型的特征。池化后宽高为什么变小因为要用更大的感受野换取更紧凑的表示。最后为什么展平因为全连接层需要一维输入。这个习惯能帮你排除很多模型结构错误。2.2 激活函数没有非线性层数再多也是白堆如果神经网络只有线性变换那不管叠多少层最终都等价于一个线性变换。你在原图里画一条直线能区分的任务它勉强能做但像“猫和狗”这种非线性分割边界线性模型完全无能为力。所以每一层线性变换之后必须接一个非线性激活函数。常见的有ReLUmax(0, x)简单、稳定、计算快。Leaky ReLU给负区间一个小斜率防止神经元死亡。GELU / Swish在 Transformer 里更常见曲线更平滑。ReLU 是目前视觉模型里最常用的默认选择。它的梯度在正区间恒为 1在负区间为 0这种稀疏性有利于缓解梯度消失但也会带来“死亡 ReLU”问题。如果一个神经元的所有输入都落在负区间梯度就是 0这个神经元就再也无法更新了。解决方法是换 Leaky ReLU或者降低学习率或者加批归一化。从第一性原理看激活函数的价值不是“让输出变大变小”而是打破线性组合的天花板。没有它神经网络退化成一次矩阵乘法参数量再多也没有意义。2.3 反向传播真正传递信号的不是标签而是梯度前向传播得到损失之后就要更新参数。更新方向由梯度决定。反向传播利用链式法则从损失 L 开始依次求每一层参数的偏导数∂L/∂W_l ∂L/∂a_L · ∂a_L/∂a_{L-1} · ... · ∂a_l/∂W_l这个式子看起来复杂但在工程里就是一个自动微分过程。你不需要手推梯度但你得理解一个现象如果某几层梯度过小那么前面层的参数几乎更新不了模型就训练不动。这种情况叫做梯度消失。反过来如果梯度过大参数一步跳得太远loss 可能出现 NaN叫做梯度爆炸。所以你会发现训练神经网络时很多技巧都围绕“让梯度保持在一个健康范围”初始化方法Xavier、Kaiming是为了让梯度在反向传播时不要一开始就成倍放大或缩小。批归一化是为了让每层输入分布稳定间接缓解梯度消失。残差连接是为了给梯度提供一条“高速公路”让它能直接传到深层。梯度裁剪是为了防止梯度爆炸。理解了这些你就不再是机械调用nn.BatchNorm2d而是知道它的存在是为了维护梯度传播路径的健康。3. 卷积为什么是计算机视觉的主角3.1 全连接层的困局参数爆炸和平移敏感如果把一张 32×32×3 的图像直接展平成一维向量长度是 3072。假设第一个全连接层有 1024 个神经元那这一层的参数量就是 3072×1024 ≈ 314 万。这还只是一层。如果输入变成 224×224×3展平后是 150528 维再连一层 1024 个神经元参数量直接破亿。这样的模型不仅难以训练而且严重过拟合。更关键的问题是全连接层没有空间概念。它对每个像素一视同仁图像里的猫从左移到右网络要重新学习一遍。因为全连接层的连接方式是完全连接的不利用任何局部空间信息。而图像有个天然特性相邻像素之间相关性很强某一块局部特征通常对应某个语义概念比如“眼睛是圆形的”“车轮是圆形的”。这种局部性是不需要模型从零开始发现的我们应该把它设计进结构里。卷积就是干这个的。3.2 卷积层的归纳偏置局部连接、权值共享、平移等变性卷积神经网络和全连接网络最大的区别是它给网络强加了三类专业先验局部连接每个输出神经元只与输入的一个局部区域相连。这个区域叫感受野。权值共享一个卷积核在同一层所有位置滑动时参数完全一样。也就是说同一个特征检测器会在整张图上反复使用。平移等变性如果输入中的物体平移输出特征也按同样方式平移。这让模型对平移有一定鲁棒性。正是这三个先验使得卷积网络在图像任务上比全连接网络高效得多。参数量大幅下降但在图像识别上的表现却大幅提升。从 2012 年 AlexNet 在 ImageNet 上一战成名开始卷积网络迅速成为计算机视觉主力模型。后来虽然出现了 Vision Transformer但卷积的局部性和平移等变性仍然是极其有效的先验尤其是在中小规模数据集上。3.3 池化、步长和感受野控制空间信息怎么被压缩卷积网络里还有一个常用操作是池化比如MaxPool2d。它的作用是下采样也就是把特征图变小。池化的意义不只是降低计算量更关键的是扩大后续层的感受野。感受野可以理解为“输出特征上的一个点看到了输入图像上的多大区域”。不断池化后高层神经元可以看到更大的范围从而组合出全局信息。但池化也有代价空间分辨率降低细节丢失。所以在语义分割这类需要像素级输出的任务里池化后还需要上采样来恢复分辨率。这也是 U-Net 这类结构出现的原因。你要理解一个通用规律下采样是提取高阶语义上采样是恢复空间细节。任何视觉任务本质都是在“语义抽象”和“空间精度”之间找平衡。分类任务偏重前者分割任务两者都要检测任务处于中间。4. 把神经网络训练起来从最小流程到参数直觉4.1 先跑通一个最小可训练流程很多初学者拿到网络结构就直接跑大数据集结果 Loss 不降就不知道怎么调。我自己的习惯是先构造一个极小数据集比如每个类别 10 张图或者干脆用随机张量验证模型能不能在前向传播中跑通并且反向传播不报错。在 PyTorch 里一个最小训练流程长这样import torch import torch.nn as nn model SimpleCNN() optimizer torch.optim.Adam(model.parameters(), lr1e-3) criterion nn.CrossEntropyLoss() for step, (images, labels) in enumerate(train_loader): outputs model(images) # 前向 loss criterion(outputs, labels) # 计算损失 optimizer.zero_grad() # 清空梯度 loss.backward() # 反向传播 optimizer.step() # 更新参数这个循环是所有视觉训练的骨架。真正的复杂度都体现在如何准备数据、如何设计模型、如何调损失函数和优化器以及在训练过程中怎么监控和诊断。如果你连这个最小流程都没有完整跑通过不要急着上预训练模型。先用几行代码把训练闭环打通后面出的问题才能定位到具体环节。4.2 关键参数的几何直觉学习率学习率决定了一步更新多大。学习率太大损失会震荡甚至发散学习率太小训练进程慢可能长时间停在局部最优附近。实践中通常会先做学习率扫描从 1e-1、1e-2、1e-3、1e-4、1e-5 里粗筛一轮。也可以使用余弦退火调度或者热启动。Batch SizeBatch size 影响梯度估计的稳定性。越大梯度越平滑但显存占用越高泛化效果不一定更好。较小 batch 带来的随机性有时反而能逃离尖锐局部最小值。一个朴素经验是如果显存允许优先尝试 32 或 64如果 loss 震荡严重再考虑增大 batch 或者降低学习率。权重衰减权重衰减weight decay本质是 L2 正则化会抑制参数过大从而降低过拟合。在 Adam 优化器里weight decay 不是直接加到梯度里而是单独处理。你可以简单理解为让模型参数尽可能小。但这个参数不要设太大否则模型欠拟合。这里给一个通用配置起点不是标准答案而是方便你了解相对大小学习率1e-3Adam或 1e-2SGD momentum Batch Size32 / 64 权重衰减1e-4 / 5e-4 训练轮数先观察 loss 曲线决定调试时不要一次性改多个参数。一次只改一个记录曲线变化才能知道哪个参数在起作用。4.3 数据增强它不是“把数据变多”而是引入不变性先验在计算机视觉里同一个物体的图像可以有平移、旋转、缩放、亮度变化但语义不变。数据增强就是主动制造这些变化让模型学到对这类变化不敏感的特征。常见增强包括随机水平翻转随机裁剪随机旋转颜色抖动归一化数据增强不仅是为了扩充数据量更是在告诉模型这些变换不应该改变预测结果。它本质上是一种端到端的正则化约束。不过数据增强也要注意和任务匹配。比如 OCR 任务里你把文字垂直翻转可能就改变了语义医学图像里过度旋转也可能破坏解剖结构。增强策略要跟着任务语义走不能无脑叠加。5. 训练不收敛时从哪一层开始排查5.1 先看数据大多数问题出在输入和标签上Loss 不降、准确率忽高忽低、训练集上 loss 直接是 NaN这些现象背后最常见的原因往往不是网络结构而是数据。我自己的排查顺序是看输入图像shape 是否正确channel 是 RGB 还是 BGR像素是否归一化有没有全黑或全白样本。看标签是否从 0 开始连续编码和类别索引对不对应有无脏标签。看数据加载是否 shuffle每个 epoch 是否真的能遍历所有样本。看 batch 输出可视化一个 batch检查图像和标签是否匹配。一个很经典的坑是图片是 0-255 的 uint8直接喂给归一化层之前忘记除 255导致输入数值范围过大梯度爆炸loss 直接变成 Inf。另一个坑是标签从 1 开始而 CrossEntropyLoss 要求从 0 开始结果训练损失异常。如果数据没问题再看损失计算。检查是不是把 Softmax 和 CrossEntropyLoss 重复用了。PyTorch 的 CrossEntropyLoss 内部已经包含 LogSoftmax你不需要在模型最后一层再手动加 Softmax否则梯度会不稳定。5.2 看梯度和损失曲线不要等几小时后才发现没训练训练过程中至少要记录这几样东西训练损失。验证损失。训练准确率。验证准确率。每个 epoch 的平均梯度范数可选但很有效。梯度范数可以帮你判断是否发生梯度消失或爆炸如果梯度范数一直接近于 0而且 loss 几乎不动可能是初始化不合适、激活选择不当、或者学习率过低。如果梯度范数突然变成非常大的数然后 loss 变成 NaN很可能是梯度爆炸。可以加梯度裁剪或者降低学习率或者检查数据归一化。Loss 曲线也有很多种形状如果训练 loss 下降验证 loss 上升典型的过拟合。如果两个都不降先怀疑学习率和数据。如果 loss 前期下降很快后期停滞可能是模型容量不够或数据不够或陷入了局部最优。5.3 看模型结构维度、初始化、残差连接如果数据正常梯度也在传播但效果还是不行就得回头检查模型结构。常见问题有某个卷积层输出通道不对导致后面全连接层维度拼不上。解决办法是打印每一层输出的 shape。初始化方法不合适。ReLU 网络建议用 Kaiming 初始化Sigmoid/Tanh 建议用 Xavier。PyTorch 默认初始化已经做了不少处理但如果你自定义层最好显式设置初始化。网络太深且没有残差连接深层梯度容易消失。这时考虑增加残差结构。最后一层的输出维度和类别数不匹配。分类任务里输出节点数必须等于类别数。调试结构时最直接的方法是# 用随机数据做前向测试 x torch.randn(2, 3, 32, 32) out model(x) print(out.shape)如果这一步报错说明某个层维度没对上按报错信息逐层调整即可。5.4 看资源和生产环境单机调试和真实训练的差异最后一步是检查硬件和运行环境。比如显卡显存是否足够Batch Size 是否过大导致 OOM。如果多个程序共存显存可能被占满训练启动失败。CPU 和 GPU 版本是否一致PyTorch 和 CUDA 版本是否兼容。数据加载是否成了瓶颈num_workers设置是否合理。有一个容易忽略的坑本地验证用的图片路径和服务器不一致读不到图片文件但代码用 try-except 把错误吞掉了结果训练数据只有部分样本。所以日志一定要能反映每个 epoch 的样本数量如果中途样本数减少要立刻警觉。6. 真正的收获不是模型而是一套建模方式6.1 神经网络不是万能它有很强的先验假设很多人说神经网络是万能函数逼近器只要够大、数据够多什么都能学。这个说法在理论上成立但在实践中意义不大。因为真实任务的样本量永远是有限的计算资源也有限。神经网络是否有效取决于你选择的结构先验与任务匹配程度。图像是局部相关的所以卷积有效。序列是时序依赖的所以循环网络或 Transformer 有效。图数据是节点和边组成的所以图神经网络有效。这些模型都是把我们对数据结构的理解固化到网络结构里。理解这一点你就不会盲目跟风换模型。别人说 Transformer 强你就把自己的分割网络全换掉结果发现小数据集上还不如卷积网络稳定。这并不奇怪因为 Transformer 的归纳偏置更弱需要更多数据才能学出视觉结构。6.2 从课程到项目先小、后大、再工程化看过哥大这门课之后我给自己总结了一条路线适用于大多数想进入视觉领域的人先在小数据集上手工实现一遍全连接和卷积神经网络的前向、反向过程哪怕使用框架也要把核心概念用代码验证一遍。在标准数据集如 CIFAR-10 或选一个较小的自定义数据集上训练自己的模型把训练循环吃透。然后换用现成结构比如 ResNet、EfficientNet理解它为什么要这样做而不是直接调库。再进入真实项目此时要重点考虑数据分布、标注质量、类别不均衡、推理速度、模型部署。工程化不是课程的一部分但课程会给你一把尺子任何模型、任何损失函数、任何训练技巧都可以从第一性原理这个角度去衡量。它解决什么问题它引入了什么先验它的代价是什么6.3 适合谁学以及必要的心理准备这门课或者这一类“第一性原理”内容适合已经会写基础 Python、接触过 PyTorch 但总感觉理解不够深的人。如果你是个完全的新手连张量、梯度、数据集是什么都不清楚可能还是要先从更基础的入门教程开始。但它不一定适合纯粹只想快速出结果的人。如果你只是要跑通一个对象检测 demo直接读开源项目文档可能更快。而如果你想在视觉方向长期深耕能理解模型为什么失效、怎么针对性改进那这类从第一性原理出发的内容就非常有价值。我始终觉得神经网络最难的不是前向传播也不是反向传播的公式推导而是在遇到问题的时候能快速判断出问题最可能出在哪一层。这个判断力来自对基础机制的清晰理解而不是背下多少种模型结构。如果你开始学这门课或者准备补计算机视觉基础我建议你带着三个问题去听这一层更新参数时梯度是怎么传过来的这个模块加入网络它在信息流上改变了什么如果任务换一个数据类型这个设计还成立吗三个问题想明白你对神经网络的认知就不再是零零散散的层和函数而是一个能推理的完整体系。
返回列表