<think>我们正在实现深度残差网络ResNet的代码。ResNet的核心是残差块(Residual Block),它通过引入跨层连接(shortcut connection)来解决深度网络中的退化问题。根据引用[5],残差块包含一个主分支和一个捷径分支,主分支包含若干卷积层,而捷径分支可能包含一个恒等映射或一个卷积层(用于调整维度)。最终输出是主分支输出与捷径分支输出的和,然后通过激活函数。
我们将按照以下步骤实现:
1. 实现基本的残差块(Residual Block)
2. 构建ResNet网络,由多个残差块组成,并包含初始卷积层和最后的全连接层
3. 注意:ResNet有不同的深度版本(如ResNet18, ResNet34等),这里我们以ResNet18为例。
根据引用[2]和[5],残差块有两种类型:一种是不改变特征图尺寸的(同一组残差块内),另一种是改变特征图尺寸的(通常在每个残差块组的第一个块中,通过步长为2的卷积来下采样)。在改变尺寸时,捷径分支也需要通过1x1卷积调整维度。
我们将实现两种残差块:
- 基本块(BasicBlock):用于ResNet18和ResNet34,包含两个3x3卷积层。
- 瓶颈块(BottleneckBlock):用于更深的ResNet,但ResNet18使用基本块。
ResNet18的结构如下:
- 初始卷积层:7x7卷积,步长2,输出通道64,然后通过最大池化(3x3,步长2)
- 残差块组1:2个残差块,每个块包含两个3x3卷积,输出通道64(不改变特征图尺寸)
- 残差块组2:2个残差块,每个块第一个残差块进行下采样(步长2),输出通道128,第二个块不改变尺寸
- 残差块组3:2个残差块,同样第一个块下采样,输出通道256
- 残差块组4:2个残差块,同样第一个块下采样,输出通道512
- 全局平均池化,全连接层
注意:在每组第一个残差块中,当输入和输出的通道数不一致或者特征图尺寸变化时,捷径分支需要添加一个1x1卷积层来调整。
我们使用PyTorch实现。
步骤:
1. 定义BasicBlock
2. 定义ResNet网络结构,包含多个组(layer1, layer2, layer3, layer4)
3. 初始化网络
下面开始代码实现:</think>### 深度残差网络ResNet实现教程
ResNet的核心创新在于**残差块(Residual Block)**设计[^2],通过跨层连接解决深度网络退化问题。以下是PyTorch实现的关键步骤:
#### 1. 残差块实现
```python
import torch
import torch.nn as nn
class ResidualBlock(nn.Module):
def __init__(self, in_channels, out_channels, stride=1):
super().__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.relu = nn.ReLU(inplace=True)
# 捷径分支:当维度不匹配时使用1x1卷积调整
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):
identity = x
out = self.conv1(x)
out = self.bn1(out)
out = self.relu(out)
out = self.conv2(out)
out = self.bn2(out)
# 残差连接:主分支输出 + 捷径分支
out += self.shortcut(identity) # 关键残差连接[^5]
out = self.relu(out)
return out
```
#### 2. ResNet-18完整架构
```python
class ResNet18(nn.Module):
def __init__(self, num_classes=1000):
super().__init__()
# 初始卷积层
self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, bias=False)
self.bn1 = nn.BatchNorm2d(64)
self.relu = nn.ReLU(inplace=True)
self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
# 残差块组(4个阶段)
self.layer1 = self._make_layer(64, 64, stride=1, num_blocks=2) # 阶段1
self.layer2 = self._make_layer(64, 128, stride=2, num_blocks=2) # 阶段2
self.layer3 = self._make_layer(128, 256, stride=2, num_blocks=2) # 阶段3
self.layer4 = self._make_layer(256, 512, stride=2, num_blocks=2) # 阶段4
# 分类头
self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
self.fc = nn.Linear(512, num_classes)
def _make_layer(self, in_channels, out_channels, stride, num_blocks):
layers = []
# 每个阶段的首个块可能需要下采样
layers.append(ResidualBlock(in_channels, out_channels, stride))
# 后续块保持相同维度
for _ in range(1, num_blocks):
layers.append(ResidualBlock(out_channels, out_channels, stride=1))
return nn.Sequential(*layers)
def forward(self, x):
x = self.conv1(x)
x = self.bn1(x)
x = self.relu(x)
x = self.maxpool(x)
x = self.layer1(x)
x = self.layer2(x)
x = self.layer3(x)
x = self.layer4(x)
x = self.avgpool(x)
x = torch.flatten(x, 1)
x = self.fc(x)
return x
```
#### 3. 关键实现细节
1. **残差连接**:主路径输出与捷径分支相加(`out += shortcut(identity)`),这是解决梯度消失的核心[^5]
2. **维度匹配**:当通道数增加或特征图缩小时,捷径分支使用1×1卷积调整维度
3. **批归一化**:每个卷积层后使用BatchNorm加速收敛
4. **下采样策略**:每个阶段的首个残差块使用stride=2进行下采样
#### 4. 模型使用示例
```python
# 初始化模型
model = ResNet18(num_classes=1000)
# 训练配置(示例)
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.1, momentum=0.9, weight_decay=1e-4)
# 数据加载(需自定义数据集)
train_loader = ...
# 训练循环
for epoch in range(100):
for images, labels in train_loader:
outputs = model(images)
loss = criterion(outputs, labels)
optimizer.zero_grad()
loss.backward()
optimizer.step()
```
### 关键优势解析
1. **解决退化问题**:残差连接确保梯度可直接回传至浅层,缓解梯度消失[^2]
2. **保持特征完整性**:恒等映射保留原始特征信息
3. **计算高效**:额外计算仅涉及张量加法,GPU开销极小[^5]
4. **强迁移能力**:预训练模型广泛用于图像分类、目标检测等任务[^4]