☰
反向传播与ResNet:计算机视觉入门的两大核心技术
2026/9/26 15:21:44 网站建设 项目流程

为什么反向传播和ResNet是计算机视觉绕不过的两根柱子

很多刚开始学计算机视觉的同学,会有一个特别普遍的困惑:网上的教程东一榔头西一棒子,今天看到一个CNN结构图,明天又看到一个反向传播公式,后天打开一篇ResNet论文,发现里面的跳跃连接看得懂,但不知道为什么加了它网络就能训练得更深。碎片化学习最致命的地方在于:你不知道每个知识点在整个技术栈里处于什么位置,也不知道它们之间是怎么衔接的。

这篇文章想帮你做一件事:把“神经网络如何学习”这条主线一次性捋清楚。从最基础的前向传播和损失函数开始,讲到反向传播如何把误差传回去,再讲到卷积神经网络CNN为什么适合处理图像,最后落回到ResNet——它解决的到底是什么问题,残差结构又是怎么起作用的。读完你会有一个完整的技术认知链,而不是又收藏了一堆零散概念。

我的一个明确判断是:计算机视觉入门阶段,你把反向传播、CNN卷积原理、ResNet残差学习这三块吃透,后面再看YOLO、Transformer、SAM这些新模型,理解成本会直线下降,因为它们都是在这些底层认知上长出来的。


1. 这篇文章真正要解决的问题

先说一个真实场景。假设你正在做一个图像分类的小项目,数据集是手写数字或者猫狗图片。你从网上找了一段PyTorch代码,里面定义了一个卷积神经网络,跑起来准确率还不错。但这时如果有人问你:

  • 网络里的卷积核到底在干什么?
  • loss下降是因为哪个参数被更新了?
  • 为什么网络层数加到50层反而训练不动,加了残差连接就好了?

你大概率会卡壳。这不是因为你笨,而是因为很多教程默认你已经懂了这些基础。但实际上,反向传播的链式法则、卷积的局部连接和权值共享、残差结构的梯度传播路径,这三件事之间有一条清晰的逻辑线。

本文要解决的就是这条逻辑线。具体来说,你会搞清楚:

  1. 神经网络前向传播是怎么计算预测值的,损失函数如何量化预测和真实值的差距。
  2. 反向传播通过链式法则,把误差从输出层逐层传回输入层,每个参数的梯度是怎么算出来的。
  3. CNN的卷积层、池化层和全连接层在图像任务中分别承担什么角色,为什么卷积比全连接更适合图像。
  4. 网络加深后为什么会出现退化问题,ResNet用残差学习绕开了什么障碍。

适合的读者:刚入门深度学习、想系统补基础的同学;调过模型但没细看源码的开发者;准备面试需要把CV知识串起来的人。


2. 神经网络基础:前向传播、损失函数与梯度下降

2.1 一个神经元在做什么

神经网络最基本的单元是神经元,它的计算可以拆成两步。第一步是加权求和:把输入和对应的权重相乘,再加上偏置项。第二步是非线性变换:把加权求和的结果通过一个激活函数,比如ReLU或者Sigmoid。

用数学公式表达就是:

z = w1 * x1 + w2 * x2 + ... + wn * xn + b a = activation(z)

这里w是权重,b是偏置,activation是激活函数。权重决定了每个输入特征的重要程度,偏置可以让神经元的输出平移。激活函数的作用是引入非线性——如果没有这一步,不管堆多少层网络,本质上都等价于一层线性变换,那“深度”就没有意义了。

实际项目中,一个网络往往包含很多层,每层又有几十到几千个神经元。数据从输入层进入,经过每一层的加权求和和激活函数,最后在输出层得到一个预测结果。这个过程叫作前向传播。

2.2 损失函数:怎么判断模型好坏

网络给出预测后,我们需要一个指标来衡量预测值和真实值的差距,这个指标就是损失函数。分类任务最常用的是交叉熵损失,回归任务常用均方误差。

交叉熵损失的一个直观理解是:如果模型对正确类别的预测概率越接近1,损失就越小;越接近0,损失就越大。均方误差则直接计算预测值和真实值的平方差的平均值。

训练目标由此变成:找到一组参数w和b,使损失函数的值最小。这是一个优化问题,而梯度下降就是解决这个问题最核心的方法。

2.3 梯度下降是怎么更新参数的

梯度下降的思想非常朴素。想象你站在一座山的某个位置,要在天黑前下到山谷最低点。你并不知道整座山的样子,但你能感觉到脚下的坡度。最笨也最有效的方法就是:每次都沿着坡度最陡的方向往下走一步。在数学上,这个“最陡的方向”就是损失函数对参数的梯度。

参数更新公式为:

w_new = w_old - learning_rate * gradient_w b_new = b_old - learning_rate * gradient_b

learning_rate是学习率,它决定了每一步走多大。学习率太大,可能跳过最低点甚至震荡发散;学习率太小,训练会非常缓慢。这就是为什么调参首先要关注学习率。

但这里引出一个关键问题:怎么高效地计算出每一层参数的梯度?这就是反向传播要回答的。


3. 反向传播算法:误差是怎么从输出层传回输入层的

3.1 链式法则与梯度传递

反向传播的核心数学工具是微积分里的链式法则。它做的事情可以用一句话概括:从损失函数出发,把误差对每一层参数求偏导,从输出层往输入层逐层传递。

举个例子。假设一个简单网络有两层线性变换:

z1 = W1 * x + b1 a1 = activation(z1) z2 = W2 * a1 + b2 pred = activation(z2) loss = L(pred, y)

我们要计算loss对W1的梯度。由于W1并不直接影响loss,它先影响z1,z1影响a1,a1影响z2,z2影响pred,pred才影响loss。链式法则告诉我们:

d(loss)/d(W1) = d(loss)/d(pred) * d(pred)/d(z2) * d(z2)/d(a1) * d(a1)/d(z1) * d(z1)/d(W1)

每一层只需要知道两样东西:上一层传回来的梯度(累积误差信号),以及本层输出对输入的偏导(局部梯度)。两者相乘,就是本层参数需要的梯度,同时再往下一层传。这就是“反向传播”名字的由来——误差信号从后向前流。

3.2 为什么说反向传播是深度学习最关键的工程算法

从工程角度看,如果没有反向传播和自动微分,训练神经网络将停留在几乎不可能的阶段。假设一个网络有1000万个参数,用数值微分的方法计算梯度,需要对每个参数都做一次前向传播,成本高得不可接受。而反向传播只需要一次前向传播加一次反向传播,就能得到所有参数的梯度,计算效率提升了几个数量级。

现代深度学习框架如PyTorch、TensorFlow,都实现了自动微分机制。你只需定义网络结构和前向传播过程,框架会自动构建计算图,并在调用backward()时自动完成反向传播。

3.3 最小示例:用numpy手动实现反向传播

为了让你真正理解,这里用一个最小例子演示手动实现。这个例子不依赖任何深度学习框架,只用numpy完成一个单隐层网络的前向传播和反向传播。

import numpy as np def sigmoid(x): return 1 / (1 + np.exp(-x)) def sigmoid_derivative(x): s = sigmoid(x) return s * (1 - s) # 输入:两个样本,每个样本两个特征 X = np.array([[0, 0], [1, 1]], dtype=np.float64) y = np.array([[0], [1]], dtype=np.float64) # 标签:0 和 1 # 初始化参数 np.random.seed(0) input_dim = 2 hidden_dim = 4 output_dim = 1 W1 = np.random.randn(input_dim, hidden_dim) b1 = np.zeros((1, hidden_dim)) W2 = np.random.randn(hidden_dim, output_dim) b2 = np.zeros((1, output_dim)) learning_rate = 0.5 # 前向传播 z1 = X.dot(W1) + b1 a1 = sigmoid(z1) z2 = a1.dot(W2) + b2 pred = sigmoid(z2) # 损失(均方误差) loss = np.mean((pred - y) ** 2) print("初始损失:", loss) # 反向传播 d_pred = 2 * (pred - y) / len(y) # 损失对pred的梯度 d_z2 = d_pred * sigmoid_derivative(z2) # pred对z2的梯度 d_W2 = a1.T.dot(d_z2) d_b2 = np.sum(d_z2, axis=0, keepdims=True) d_a1 = d_z2.dot(W2.T) # z2对a1的梯度 d_z1 = d_a1 * sigmoid_derivative(z1) d_W1 = X.T.dot(d_z1) d_b1 = np.sum(d_z1, axis=0, keepdims=True) # 更新参数 W1 -= learning_rate * d_W1 b1 -= learning_rate * d_b1 W2 -= learning_rate * d_W2 b2 -= learning_rate * d_b2 # 更新后的前向传播 z1 = X.dot(W1) + b1 a1 = sigmoid(z1) z2 = a1.dot(W2) + b2 pred = sigmoid(z2) loss = np.mean((pred - y) ** 2) print("更新后损失:", loss)

这段代码的关键逻辑在反向传播部分。你能清楚看到,从d_pred开始,每一行都在利用上一段计算的梯度和本层的局部导数,一步步传回去。如果你的任务更复杂,这段逻辑可以套用到CNN上,只是卷积层的局部梯度计算换成了卷积和转置卷积的运算。

3.4 常见误区:偏置项也会梯度消失吗

很多初学者在写反向传播时,只关注权重的梯度,忽略偏置。实际上偏置也必须更新,它对损失的影响同样通过链式法则传递。从上面代码可以看到,d_b1和d_b2通过对梯度在样本维度上求和得到。因为偏置是逐层共享的,它的梯度等于所有样本在该层梯度之和。


4. 卷积神经网络CNN:为什么卷积更适合图像

4.1 全连接层的致命问题

如果把一张224x224的RGB图片直接拉平成向量,会得到约15万个像素值。如果第一层全连接层有1000个神经元,那这一层的权重参数就有1.5亿个。这个量级的参数量不仅训练极慢,还很容易过拟合,而且完全没有利用图像的空间结构。

图像的核心特征是局部性:一个像素通常只和它附近的像素相关,而这个相关性在空间上是平移不变的——猫的耳朵不管出现在图片左上角还是右下角,它的纹理特征本质上是一样的。全连接层无法建模这种局部性和权重共享。

4.2 卷积操作的三个关键设计

卷积神经网络用三个设计解决了全连接的问题:

第一个是局部连接。卷积核只和输入图片上的一个小窗口做内积运算,而不是和整个图片连接。这样每个神经元只关注局部区域,大大减少了参数量。

第二个是权值共享。同一个卷积核会在整张输入图上滑动,不管滑动到哪里,用的都是同一套权重。这意味着网络学会的某个局部特征(比如边缘、角点),可以在图片的任意位置被检测到,同时参数量进一步减少。

第三个是平移不变性。因为同样的卷积核在图片各处滑动,所以同一个特征不管出现在哪里,都能被同一个检测器捕捉到。这对图像分类是很有价值的——我们通常希望无论目标在画面哪个位置,都能识别出来。

4.3 卷积层、池化层、全连接层的分工

一个典型的CNN由三类层交替组成:

卷积层负责提取特征。低层的卷积核学到的是边缘、颜色块、纹理这类低级特征,高层的卷积核会组合出物体的部件甚至整个物体。这就是为什么CNN有“层次化特征学习”的说法。

池化层负责降采样。最常用的是最大池化,取窗口中最大的值作为输出。它的作用是减少空间尺寸,从而减少计算量,同时让特征具有一定程度的平移鲁棒性——目标在窗口内稍微移动,最大值可能变化不大。

全连接层通常放在网络的最后部分,把卷积层提取到的高层特征展平,然后映射到类别得分上。

用一句话总结CNN的分工:卷积层负责“看什么”,池化层负责“缩小视野范围”,全连接层负责“做决策”。

4.4 CNN前向传播与反向传播的对应关系

理解CNN的反向传播有一个要点:卷积操作本质上也是线性运算,它的反向传播和全连接层一样遵循链式法则,只是梯度传播的方式变成了“卷积的转置运算”。

虽然我们不建议手写CNN的反向传播(现代框架已经做得很完善),但理解这个对应关系有助于你排查梯度问题。比如,当你发现某一层的卷积核梯度为全零,最可能的原因是上层的梯度信号已经消失,而不是卷积核本身有问题。


5. 从CNN到现代视觉模型:CNN不再只是线性堆叠

很多入门者以为CNN就是“卷几层、池化几次、拉平、全连接”,直到去看现代模型才发现完全不是这么简单。从ResNet开始,CNN在结构设计上经历了几个重要的升级。

第一个升级是Batch Normalization的引入。它做的事是对每一层输入做归一化,让数据分布更稳定。之前训练深层网络时,每层输入数据的分布随训练变化,导致要反复调整学习率;BN层显著缓解了这个问题。需要注意,在PyTorch里,BN层默认在训练和推理模式下行为不同,这也是初学者易踩坑的细节之一,通常模型评估要调用model.eval()。

第二个升级是残差连接。这是ResNet的核心理念,也是本文后半部分的重点。

第三个升级是更丰富的分支结构。后来的Inception模块用多个不同尺寸的卷积核并行提取特征;DenseNet让每层的输出都作为之后所有层的输入;EfficientNet则通过复合缩放方式平衡深度、宽度和分辨率。

理解这些演进的主线,你会发现CNN并不是一个固定的“模板”,而是“特征提取 + 结构优化”的设计思路。ResNet提供了一个极其有效的通用组件,让网络可以做得非常深而不退化,这也是为什么它今天仍然被广泛用作骨干网络。


6. 从CNN到ResNet:网络加深为什么反而不work

6.1 什么是退化问题

按照直觉,网络越深,表达能力越强,效果应该越好。但实验发现,在ResNet之前的时代,直接把网络从20层加到56层,训练误差和测试误差反而都变大了。这不是过拟合,因为训练误差本身就增大了。

这个现象被称为“退化问题”。它的诡异之处在于:一个56层的网络理论上完全能模拟20层网络的行为(前20层照抄,后面36层退化为恒等映射),但实际优化过程却无法找到这样的参数组合。换句话说,深层网络不是表达能力不够,而是“难以优化”。

6.2 梯度消失与梯度爆炸

一个更深层的原因是梯度在反向传播中的不稳定性。网络层数一多,梯度要从输出层一路传回输入层,每一层都要乘上该层权重的乘积。如果权重矩阵的特征值普遍大于1,梯度会指数级增长,产生梯度爆炸;如果普遍小于1,梯度就会指数级衰减,导致前面的层几乎收不到更新信号,这就是梯度消失。

虽然ReLU激活函数和BN层已经缓解了梯度消失,但深层网络训练仍然不够稳定。梯度穿过多层结构时,信息还是会大量丢失。

6.3 ResNet的核心思想:让网络去学“残差”

ResNet的解决办法非常巧妙。它把网络要学习的映射从原来的F(x)改成了H(x) = F(x) + x。其中x是跳跃连接直接传过来的输入,F(x)是卷积等操作学到的残差部分。

这看起来只是数学形式的小改动,但实际效果巨大。如果网络某个阶段的最优解本来就是恒等映射,即H(x)=x,那么它只需要把F(x)学到0就行。相比直接让堆叠的非线性层去拟合恒等映射,把残差压到0要容易得多。

从梯度传播的角度看,跳跃连接让梯度多了一条从后层直接传到前层的“捷径”。即使中间的卷积层梯度很小,跳跃连接也能保证前层至少能收到来自后层的梯度信号,这就缓解了梯度消失。

6.4 残差块结构

一个典型的ResNet残差块有两个版本。

BasicBlock(用于ResNet18/34):

输入 x -> [3x3 Conv -> BN -> ReLU] -> [3x3 Conv -> BN] -> 加上x -> ReLU -> 输出

Bottleneck(用于ResNet50及更深网络):

输入 x -> [1x1 Conv降维] -> [3x3 Conv] -> [1x1 Conv升维] -> 加上x -> ReLU -> 输出

Bottleneck的设计思路是:先用1x1卷积降低通道数,减少3x3卷积的计算量,再用1x1卷积恢复通道数。这样在加深网络的同时控制了计算成本。

当输入和输出的通道数或空间尺寸不一致时,跳跃连接上还需要加一个1x1卷积(或下采样操作)来匹配维度。


7. 完整示例:用PyTorch搭建并训练一个CNN模型

7.1 环境准备

建议使用Python 3.8及以上版本,PyTorch 1.10及以上版本。如果没有GPU环境,用CPU也可以跑通下面的例子,只是训练时间稍长。本示例使用MNIST手写数字数据集,输入为28x28的灰度图片。

安装依赖:

pip install torch torchvision

7.2 定义CNN模型

下面代码定义了一个简单的CNN,并在其中演示了残差块的思想。

import torch import torch.nn as nn import torch.optim as optim import torch.nn.functional as F from torch.utils.data import DataLoader from torchvision import datasets, transforms # 残差块(适用于28x28小图,通道数较少) class ResidualBlock(nn.Module): def __init__(self, in_channels, out_channels, stride=1): super(ResidualBlock, self).__init__() self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1, bias=False) self.bn1 = nn.BatchNorm2d(out_channels) self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=1, padding=1, bias=False) self.bn2 = nn.BatchNorm2d(out_channels) # 当通道数或尺寸变化时,对短接线做调整 self.shortcut = nn.Sequential() if stride != 1 or in_channels != out_channels: self.shortcut = nn.Sequential( nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=stride, bias=False), nn.BatchNorm2d(out_channels) ) def forward(self, x): out = F.relu(self.bn1(self.conv1(x))) out = self.bn2(self.conv2(out)) out = out + self.shortcut(x) out = F.relu(out) return out # 简单CNN分类模型 class SimpleCNN(nn.Module): def __init__(self, num_classes=10): super(SimpleCNN, self).__init__() self.conv1 = nn.Conv2d(1, 32, kernel_size=3, stride=1, padding=1) self.layer1 = ResidualBlock(32, 32) self.conv2 = nn.Conv2d(32, 64, kernel_size=3, stride=2, padding=1) self.layer2 = ResidualBlock(64, 64) self.pool = nn.AdaptiveAvgPool2d((1, 1)) self.fc = nn.Linear(64, num_classes) def forward(self, x): x = F.relu(self.conv1(x)) x = self.layer1(x) x = self.layer2(x) x = self.pool(x) x = x.view(x.size(0), -1) x = self.fc(x) return x model = SimpleCNN(num_classes=10) print(model)

7.3 训练代码

下面代码完成数据加载、训练和验证。

device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model = model.to(device) # 数据加载 transform = transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,)) ]) train_dataset = datasets.MNIST(root="./data", train=True, download=True, transform=transform) test_dataset = datasets.MNIST(root="./data", train=False, download=True, transform=transform) train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True) test_loader = DataLoader(test_dataset, batch_size=256, shuffle=False) criterion = nn.CrossEntropyLoss() optimizer = optim.Adam(model.parameters(), lr=0.001) def train_one_epoch(model, loader, criterion, optimizer, device): model.train() total_loss = 0 correct = 0 total = 0 for images, labels in loader: images, labels = images.to(device), labels.to(device) outputs = model(images) loss = criterion(outputs, labels) optimizer.zero_grad() loss.backward() optimizer.step() total_loss += loss.item() * images.size(0) _, predicted = outputs.max(1) total += labels.size(0) correct += predicted.eq(labels).sum().item() return total_loss / total, correct / total def evaluate(model, loader, device): model.eval() correct = 0 total = 0 with torch.no_grad(): for images, labels in loader: images, labels = images.to(device), labels.to(device) outputs = model(images) _, predicted = outputs.max(1) total += labels.size(0) correct += predicted.eq(labels).sum().item() return correct / total num_epochs = 5 for epoch in range(num_epochs): train_loss, train_acc = train_one_epoch(model, train_loader, criterion, optimizer, device) test_acc = evaluate(model, test_loader, device) print(f"Epoch [{epoch+1}/{num_epochs}], Loss: {train_loss:.4f}, Train Acc: {train_acc:.4f}, Test Acc: {test_acc:.4f}")

7.4 代码逻辑解释

这个示例有两个关键点。

第一,ResidualBlock中引入了短接线。当输入和输出通道数一致且stride为1时,短接线直接原样传递;否则通过1x1卷积调整维度和尺寸。这正符合ResNet原论文中的设计。

第二,训练时调用loss.backward(),PyTorch会自动完成反向传播。理解前面手动实现的梯度计算后,你应该能想象到这个调用背后发生了什么:误差从交叉熵损失出发,经过全连接层、池化层、残差块,逐层回到卷积层和输入。

7.5 运行结果与验证

运行后,预期每个epoch输出类似:

Epoch [1/5], Loss: 0.2312, Train Acc: 0.9321, Test Acc: 0.9542 Epoch [2/5], Loss: 0.0895, Train Acc: 0.9733, Test Acc: 0.9771 Epoch [3/5], Loss: 0.0654, Train Acc: 0.9808, Test Acc: 0.9820

MNIST是一个相对简单的数据集,即使很浅的网络也能达到不错的效果。如果训练准确率低,优先检查学习率;如果loss为NaN,优先检查是否使用了未经归一化的数据。


8. 常见问题与排查思路

8.1 常见问题表格

问题现象可能原因排查方式解决方案
loss一直不下降学习率过大或过小打印初始loss和梯度范数调整学习率,尝试1e-3到1e-5区间
loss变成NaN数据出现NaN或学习率过大检查输入数据和前几次迭代的loss归一化数据,降低学习率,增加梯度裁剪
训练集准确率高但测试集低过拟合对比训练和测试准确率差距增加数据增强、正则化或Dropout
网络加深后效果变差退化问题或梯度信号问题对比浅层和深层网络的训练loss引入残差连接或BN层
显存不足batch size过大或输入尺寸过大观察显存使用量降低batch size或减小输入尺寸
模型eval结果和训练时差异大BN层和Dropout行为变化确认是否调用model.eval()训练用model.train(),评估用model.eval()

8.2 排查梯度问题的通用思路

当网络训练异常时,建议按以下顺序排查:

第一,打印每一层的梯度范数,确认梯度是否为全零或非常大。如果梯度为全零,说明梯度可能消失;如果非常大,说明梯度爆炸。PyTorch中,在loss.backward()后,可以用model.layer1.conv1.weight.grad.norm()来查看梯度。

第二,用一个小batch的过拟合测试。只拿几个样本训练,如果loss能降到接近0,说明模型有学习能力,问题出在数据或训练策略上;如果loss降不动,说明模型结构本身有问题。

第三,检查数据预处理。很多训练失败不是模型问题,而是输入没有归一化,或者数据标签错位。


9. 最佳实践与工程建议

9.1 先跑通最小模型,再逐步加深

我的建议是:不要一上来就复现ResNet152。先写一个只有两三层的CNN,在MNIST或CIFAR-10这种小数据集上跑通,确认数据加载、训练循环、评估流程都没问题,然后在此基础上加深网络、引入残差块。每一步改动都单独验证。这能最大限度地减少“出了bug不知道是哪里改出来的”的痛苦。

9.2 理解框架API背后的原理

用PyTorch时,nn.Conv2d的padding、stride、dilation这些参数,背后对应的都是卷积运算的几何意义。不要满足于“调参跑通”,建议你用numpy实现一次最简单的卷积前向传输,然后再用PyTorch实现同样的操作并对比结果。这样你对卷积的理解会发生质变。

9.3 数据增强与迁移学习

实际项目中,MNIST这种干净数据集很少见。如果你的任务数据量不大,建议优先使用ImageNet或相关领域的预训练模型做迁移学习,只微调最后几层。PyTorch官方提供了很多预训练模型,可以在torchvision.models里直接加载。ResNet的预训练模型尤其常用,这也是为什么ResNet至今仍是视觉任务的通用骨干网络。

9.4 正确看待“调参”和“架构设计”的关系

很多新手执着于调学习率、调batch size。但真正影响模型上限的,往往是数据处理是否合理、模型结构是否匹配任务、损失函数是否合适。花在数据清洗和模型设计上的时间,回报通常远高于盲目调参。学习这些基础知识时,一定要带着“设计者的视角”去思考:如果我来设计一个图像分类网络,我会怎么选卷积核大小?要不要残差连接?这个决策背后的权衡是什么?


10. 总结与后续学习路径

这篇文章从一条主线把计算机视觉的几个核心基础串了起来:前向传播让数据通过网络得到预测,损失函数量化预测误差,反向传播把误差信号从输出层传回每一层并计算梯度,CNN用局部连接和权值共享把“图像空间结构”纳入模型设计,而ResNet通过残差学习解决了深层网络难以优化的问题。

建议你按下面的路径继续深入学习:

第一步,动手复现一次手动反向传播(用numpy即可),体会链式法则和梯度传递的过程。第二步,把本文的CNN示例跑通,在MNIST上观察卷积核输出特征图。第三步,在CIFAR-10上把ResNet18跑起来,对比它和普通深度CNN的收敛区别——这一步能让你直观感受残差结构的效果。第四步,再看现代模型,比如Vision Transformer(ViT)和检测模型YOLO的骨干设计,你会发现它们的基础概念绝大多数都在这里出现过。

有一点要提醒你:深度学习基础知识的价值不在“记住公式”,而在“理解设计动机”。遇到一个新的网络结构,先问三个问题:它解决了什么问题?它带来了什么代价?它为什么这样设计?带着这些问题去读论文和源码,效率会远高于盲目收集资料。本文提到的ResNet,就是这条方法论最典型的样本。建议收藏本文,作为你深入学习计算机视觉时的一个路线参照。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询