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

资讯详情

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

哥大《计算机视觉第一原理》第十二讲:神经网络与视觉任务的第一性原理

哥大《计算机视觉第一原理》第十二讲:神经网络与视觉任务的第一性原理 哥大这套《计算机视觉第一原理》课程追到第Ⅻ讲时终于把镜头对准了神经网络。如果你一直在跟这个系列应该能感受到前面几讲是在搭地基相机成像、几何变换、光流、特征点、传统机器学习分类。到了神经网络这一讲前面那些知识并没有白学而是换了一种方式被重新组织起来。这一讲适合两类人一类是想系统理解视觉深度学习原理的初学者另一类是已经能跑通模型、但对网络设计逻辑还不踏实的开发者。最值得关注的地方不是它罗列了多少种层结构而是它用第一性原理的方式解释了为什么视觉任务需要神经网络、神经网络里的每个模块到底在解决什么问题。我最近重新把这一讲完整过了一遍又顺着课程思路在本地跑了一些小实验下面按我自己的学习路径拆开讲。我尽量把原理、环境、步骤和排查顺序都放在一起这样你既能看懂设计动机也能在上手时少绕路。1. 为什么视觉问题最终会走到神经网络1.1 视觉任务的核心矛盾像素太多语义太远一张 256×256 的彩色图片直接展开就是一个 196608 维的向量。每一个像素点只是数值和人眼能感知的“猫”“狗”“道路”“行人”之间隔着非常远的语义距离。传统视觉方法通常需要人工设计特征比如边缘、角点、纹理、颜色直方图、方向梯度直方图。这些特征在特定场景下有效但一旦遇到光照变化、遮挡、视角变化、背景杂乱手工特征就很难稳住。问题不在于某一种特征不好而在于“什么是好的特征”这件事本身很难用规则写清楚。神经网络换了一个思路不直接告诉计算机该看什么而是给它大量输入输出对让它自己从数据里学出适合当前任务的特征。这个思路听起来很自然但它要成立需要两个条件一是网络结构有足够的表达能力二是优化算法能把参数调到可用状态。这两个条件恰好就是这一讲的主线。1.2 传统特征工程与神经网络的根本差异传统方法的链条通常是图像预处理、特征提取、特征选择、分类器训练。每个环节都是独立模块你很难让“分类器认为重要的特征”反过来指导“特征提取器应该提取什么”。神经网络把这条链变成了一条可微分的通路。从输入像素到最终类别中间所有参数都可以通过反向传播统一更新。前一层提取到什么特征取决于后一层需要什么特征而不是由人工规则提前锁死。这就是“端到端”的含义也是计算机视觉在深度学习时代最大的变化。你不需要在每一层手工设计滤波器只需要设计网络结构和训练策略让数据自己说话。这个差异是理解这门课后续所有内容的基础。建议先记住这个判断神经网络不是一种“更复杂的分类器”而是一套“特征与决策联合学习”的框架。2. 学习这一讲之前建议先准备好的基础2.1 数学和编程底线计算机视觉第一原理这套课程的风格偏原理不是纯调库。所以你最好先有一些基础否则看公式时容易卡住。线代方面要熟悉矩阵乘法、向量、张量的基本形状变化。神经网络的前向传播本质上就是一连串矩阵乘法和非线性变换。微积分方面至少要理解导数和链式法则因为反向传播的核心就是链式法则。概率论方面损失函数里的交叉熵、softmax 都和概率分布有关。你不一定需要会推导所有公式但至少要知道“模型输出经过 softmax 后可以解释成概率”这件事。编程方面建议至少会 Python 和 NumPy。框架用 PyTorch 或 TensorFlow 都行但我在实际学习时更推荐 PyTorch因为它的计算图是动态的调试起来直观社区里视觉相关的示例也最多。有一个容易被忽视的点你不需要把所有数学都搞懂再上手。更好的方式是先跑通一个最小例子然后回头补数学。很多公式在你见过实际 tensor 形状后会突然变得好理解。2.2 运行环境和依赖这一讲的实验不需要特别高的硬件配置。单纯训练一个 MNIST 或 FashionMNIST 分类模型CPU 也能跑只是慢一点几分钟到十几分钟。如果你的机器有 NVIDIA 显卡建议装好 CUDA 版 PyTorch。具体装哪个版本要看你的驱动和 Python 版本训练前用下面这段代码确认 GPU 是否可用import torch print(torch.__version__) print(torch.cuda.is_available())如果torch.cuda.is_available()返回False先不要急着调模型检查 PyTorch 安装的 CUDA 版本和驱动是否匹配。这个问题经常出现在刚装完环境的时候而且报错信息不一定明确。依赖方面除了 PyTorch 还需要 torchvision用来加载常见数据集和图像变换。Matplotlib 可以用来画损失曲线。NumPy 是几乎所有数值操作的基础。磁盘空间不用太担心MNIST 整个数据集下载下来只有几十 MB。但如果你后面要跑 ImageNet 之类的数据集那就得提前规划硬盘了。3. 从第一性原理理解神经网络的关键部件3.1 感知机神经网络的最小单元神经网络的最小构件是感知机也叫神经元。它做的事很简单把输入向量和权重做内积加上偏置再通过一个非线性函数输出。公式写出来就是z W^T x b a activation(z)这里的偏置b经常被初学者忽略。它解决的问题是当输入全为零时神经元仍然可以有一个非零的基准输出。没有偏置所有通过原点的样本会失去区分能力。很多你看到的结构图里只画了权重和激活函数但实际实现时一定要带上偏置项。单个感知机只能解决线性可分问题比如二维平面上的“或”逻辑。但视觉数据几乎不会线性可分。要让网络有更强的表达能力就必须把大量神经元堆叠成多层结构并在每层之间加入非线性激活函数。3.2 激活函数没有它层数再多也没意义如果激活函数是线性的多层网络叠加之后仍然等价于一个线性变换。也就是说深层结构没有带来任何新的表达能力。这正是激活函数存在的根本原因给网络注入非线性。常见的激活函数有 Sigmoid、Tanh、ReLU以及后续的 LeakyReLU、GELU 等。视觉任务里最常用的仍然是 ReLUReLU(x) max(0, x)ReLU 计算简单反向传播时导数为 0 或 1能在一定程度上缓解梯度消失。但它也有个问题神经元一旦输出全部为负梯度可能一直为 0这个神经元就不会再更新了也就是“死神经元”。所以后面才出现了各种 ReLU 变体。学习这一部分时不要只记函数曲线要动手看一下不同激活函数下梯度的差异。比如在反向传播时Sigmoid 在两端梯度趋近于 0网络很难训练。这些现象影响了后来网络结构的设计取向。3.3 前馈传播、损失函数和反向传播前馈传播就是输入从输入层经过隐藏层到输出层逐层计算的过程。代码里写一个forward函数就是在做这件事。有了输出之后需要用损失函数衡量模型预测和真实标签之间的差距。分类任务最常用的是交叉熵损失。它的特点是当预测概率接近真实类别时损失小当模型把高概率分给错误类别时损失会明显增大。反向传播是训练的关键。它利用链式法则从输出层的损失出发逐层计算每个参数的梯度然后让优化器沿着梯度下降方向更新参数。这个过程在 PyTorch 里就是loss.backward()和optimizer.step()两行代码。很多初学者会把“反向传播”和“更新参数”混在一起。实际上反向传播只是计算梯度更新参数是优化器的事。如果梯度计算错了损失曲线会出现各种奇怪行为如果优化器配置错了梯度再准也更新不到合适位置。3.4 优化器与学习率优化器决定参数怎么更新。最简单的随机梯度下降SGD按固定学习率往负梯度方向走。后来出现的 Momentum、Adam、AdamW 都是在更新方式上做改进。学习率是最需要手工调的超参数之一。学习率太大损失会震荡甚至发散学习率太小训练速度很慢可能在有限时间内达不到理想精度。我建议把学习率理解为“每次更新的步长”。在视觉任务里2e-4 到 1e-3 经常是一个合理的初始区间但具体要看数据集、网络结构和优化器。第一次训练时优先保证损失能在前几十个迭代里明显下降再考虑精调。注意不要一上来就开网格搜索调学习率。先用一个 batch 跑一小段确认前向、反向、参数更新都没有问题再去做超参数实验。4. 从全连接走向卷积视觉任务为什么需要特殊结构4.1 全连接层的规模爆炸如果直接用全连接网络处理图像一个致命问题是参数量太大。拿 224×224 的彩色图片举例输入维度大约是 15 万。如果第一个隐藏层有 1000 个神经元那这一层光权重就有 1.5 亿个。这个规模在训练和推理时都不现实。更关键的问题是全连接层把一个像素位置和其他所有位置都建立连接但它没有考虑到图像本身的局部性。图像里相邻像素之间关系密切距离很远的像素通常关联不大。全连接层把这些关联全部混在一起既浪费参数又容易过拟合。4.2 卷积核、权值共享和局部感受野卷积神经网络的核心思路是每个卷积核只关注一个局部区域然后在整张图上滑动。这样做有两个直接好处。第一个好处是局部感受野。卷积核一次只看一个小窗口比如 3×3正好对应图像里的局部纹理、边缘等模式。多个卷积层堆叠后浅层看到的是边缘和颜色块深层看到的则是更抽象的物体部件和语义结构。第二个好处是权值共享。同一个卷积核在图片不同位置都使用同一套权重这让模型不需要为每个位置单独学习一套检测器。参数数量大幅减少同时还能保持平移等变性目标出现在不同位置时同一套卷积核都能响应。这个设计是不是很符合视觉直觉它之所以有效不是因为“卷积”听起来高级而是因为它把“图像有局部结构”这个先验知识直接编码进了网络结构里。4.3 池化与下采样池化层常见的是最大池化和平均池化。它的作用是在保留主要信息的同时降低空间尺寸。图像尺寸减小后后续卷积的感受野相对扩大计算量也降低。但池化不是越多越好。下采样太快空间细节会丢失对分割、检测这类需要精确定位的任务影响很大。这也是后来很多网络减少早期池化、改用步长卷积的原因。理解卷积网络时始终盯住两个指标特征图空间尺寸怎么变、通道数怎么变。只要把每一层的输入输出 shape 列出来网络结构的整体思路就清晰了。课程里画的网络结构图看起来很复杂拆成“卷积-激活-池化”的小块后就会好读很多。5. 用一个小型分类任务跑通全流程5.1 环境准备和数据集为了验证神经网络的基本流程我建议先用 MNIST 或 FashionMNIST 做实验。这两个数据集类别少、图片小、训练速度块非常适合理解原理。用 torchvision 加载时要做的预处理通常是把 PIL 图片转成 Tensor并做标准化。MNIST 的常用均值是 0.1307标准差是 0.3081。如果数据集换成 CIFAR-10均值和标准差就完全不同这一点不要拍脑袋写死。transform transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,)) ]) train_data datasets.MNIST( root./data, trainTrue, downloadTrue, transformtransform ) train_loader DataLoader(train_data, batch_size64, shuffleTrue)batch_size决定每次送入网络多少张图。64 是一个比较常见的起点。shuffleTrue能打乱训练样本顺序避免模型学到数据排列中的假规律。5.2 最小训练代码先用一个最简单的两层全连接网络跑通流程。这个网络结构并不追求精度目的是让你看清楚前向、损失、反向、更新这四步是怎么串联的。class VisionMLP(nn.Module): def __init__(self): super().__init__() self.net nn.Sequential( nn.Flatten(), nn.Linear(28 * 28, 128), nn.ReLU(), nn.Linear(128, 10) ) def forward(self, x): return self.net(x) model VisionMLP() loss_fn nn.CrossEntropyLoss() optimizer optim.SGD(model.parameters(), lr0.01) for epoch in range(3): for images, labels in train_loader: optimizer.zero_grad() outputs model(images) loss loss_fn(outputs, labels) loss.backward() optimizer.step() print(fepoch {epoch 1}, loss: {loss.item():.4f})这套代码有三个地方容易出错。第一optimizer.zero_grad()必须在loss.backward()之前调用否则 PyTorch 会累加梯度参数更新方向会被上一轮梯度污染。第二全连接层输入前必须先Flatten把(batch_size, 1, 28, 28)展平成(batch_size, 784)。忘记这一步会直接报维度错误或者更隐蔽地得到错误结果。第三CrossEntropyLoss期望模型输出原始 logits不要提前加 softmax。PyTorch 的交叉熵损失内部已经包含 softmax 计算过程。跑完 3 个 epoch 后你不需要追求高准确率重点看损失是否能从最初的 2.3 附近逐渐降到 1 以下。如果损失一直不降说明流程里还有问题。5.3 关键参数和判断标准训练视觉模型时有几个参数会直接影响结果batch_size控制每次更新使用的样本数。大了训练更稳定但更占显存小了梯度噪声大训练可能震荡。learning_rate控制更新步长。第一次实验先用固定学习率跑通后再尝试学习率衰减。epoch整个训练集被完整遍历的次数。MNIST 这种小数据集10 到 20 个 epoch 足够看到明显变化。hidden_dim隐藏层神经元个数。增加维度能提高表达能力但也会增加参数量和过拟合风险。判断训练是否正常不要只看最后一个 loss。更稳妥的方式是每轮结束后在验证集上计算准确率。如果训练损失在下降但验证准确率上不去说明模型可能过拟合如果训练损失和验证准确率都不动那往往不是训练轮数不够而是学习率、数据预处理或网络结构有问题。我一般会在第一次跑实验时额外打印每个 epoch 的训练损失并保存最后几个周期的模型权重。这样后面调参时能快速对比不用重新训练。torch.save(model.state_dict(), mlp_mnist.pth)6. 训练视觉模型最常见的坑和排查顺序6.1 损失不下降先看什么很多人在训练时遇到损失不降第一反应是换模型或调学习率。但实际上最耽误时间的问题往往出在更基础的地方。我的排查顺序是先看输入数据图片值有没有归一化标签有没有错位数据集加载后先用一小批样本可视化确认。再看代码逻辑zero_grad()有没有调用model.train()和model.eval()有没有用对验证阶段忘记关 dropout 和 BN 的更新会导致结果诡异。然后看学习率和初始化学习率太大可能导致 loss 变成 NaN学习率太小会导致 loss 下降极慢。最后才考虑网络结构层太深、激活函数选择不当、分支结构接错都会让梯度无法有效传播。如果 loss 在训练开始时几乎不下降先用一个非常小的数据集比如 16 张图试一下能不能把这批数据完全拟合。如果小数据都学不动问题基本出在代码或结构上而不是数据量不够。6.2 过拟合不是模型不行是训练策略没跟上神经网络参数量远大于样本数时很快就会记住训练集但在验证集上表现很差。这不是模型“笨”而是训练策略没有配合好。常用的解决手段按优先级排增加数据增强随机裁剪、水平翻转、颜色抖动、旋转。这个方法对视觉任务最有效。降低模型复杂度减少层数或隐藏通道数。加入正则化Dropout、权重衰减。提前停止当验证集 loss 开始上升时停止训练。一个常见误区是只加 Dropout、不调整学习率然后发现训练速度变慢。Dropout 在训练时随机丢弃神经元相当于训练了多个共享权重的子网络这会让收敛更慢。所以加了 Dropout 后学习率可能需要适当调大训练轮数也需要增加。验证集和测试集之间还有一个很容易犯的问题数据增强只应该在训练阶段使用验证和测试时只用基础预处理。如果不小心在验证阶段也做了随机裁剪那么每次评估结果都会有波动很难判断模型真实能力。6.3 显存、内存和批大小的取舍低显存环境最直接的限制不是模型能不能跑而是批大小能开多大。显存不足时程序会直接报CUDA out of memory这时不要急着换显卡可以先按顺序尝试减小batch_size比如从 64 减到 32 或 16。降低输入图片分辨率。用transforms.Resize缩小图片尺寸。检查代码里是否创建了多余的大张量。比如在循环里反复拼接历史特征这种情况会持续累积显存。关闭梯度保存推理阶段用torch.no_grad()。这些方法都能在现有硬件上把训练跑起来但要理解它们各有代价。batch_size减小后梯度噪声变大训练稳定性可能下降分辨率降低后模型能看到的空间细节变少精度可能受影响。所以不要盲目追求能跑而是要在能跑和结果可接受之间找到平衡。注意如果只是想验证网络结构是否合理CPU 跑小数据集完全够用。别把低配置默认成不能学习很多实验瓶颈其实是代码里某个循环写得太低效。7. 这门课学完之后下一步往哪走7.1 从基础结构延伸到现代网络把神经网络的基本原理想清楚之后再回头看视觉领域的现代结构会轻松很多。ResNet 解决的是深层网络退化问题核心是残差连接DenseNet 在 ResNet 基础上进一步做了特征复用EfficientNet 则把网络深度、宽度和分辨率放在一起进行联合缩放。还有一类是非卷积的视觉结构比如 Vision Transformer以及后来各种基于注意力机制的变体。它们改变了特征提取方式但基本的训练流程、损失函数、优化器逻辑仍然和这一讲里讲的一致。这也是为什么基础网络值得认真吃透。课程之外的常见扩展还有目标检测、语义分割、姿态估计。这些任务的基础编码器通常直接使用图像分类网络后面的检测头、分割头才是任务相关部分。所以先把分类网络的训练和调参练稳后面迁移到其他任务会顺手很多。7.2 建议自己动手做的三个实验学习神经网络只靠看课和读文章不够最好配合动手实验。我建议从下面三个实验开始每个实验都能验证一类问题。第一个实验用同一个数据集对比全连接网络和卷积神经网络的参数量、训练速度和准确率。这个实验能直观感受网络结构对视觉任务的影响也能理解为什么卷积网络会成为视觉任务的主流。第二个实验固定网络结构分别用不同学习率训练画出损失曲线。你很快会看到学习率太小、合适、太大三种情况在曲线上的表现差异。这个实验对后续所有调参都有帮助。第三个实验刻意用一个小训练集训练一个较大的网络观察过拟合现象再依次加入数据增强、Dropout 和权重衰减观察验证集准确率的变化。这个实验可以帮你建立“训练策略”和“泛化能力”之间的直觉。跑完这三个实验之后再回去看课程里关于反向传播、梯度消失、感受野这些细节你会发现很多当时觉得抽象的概念都变得具体了。我个人的建议是不要急着追求最新模型或高精度榜单。先把数据加载、模型定义、训练循环、评估流程这一整套工程闭环跑熟练再逐步碰更复杂的任务。很多看起来像模型能力不够的问题最后排查出来往往是数据预处理、标签对齐、日志监控这些配套环节没有做好。神经网络的原理并不复杂复杂的是在真实数据上把每一步都做对。
返回列表