【深度学习基础模型】深度残差网络(Deep Residual Networks, DRN)详细理解并附实现代码。
【深度学习基础模型】深度残差网络(Deep Residual Networks, DRN)详细理解并附实现代码。
文章目录
- 【深度学习基础模型】深度残差网络(Deep Residual Networks, DRN)详细理解并附实现代码。
- 1. 算法提出
- 2. 概述
- 3. 发展
- 4. 应用
- 5. 优缺点
- 6. Python代码实现
参考地址:https://www.asimovinstitute.org/neural-network-zoo/
论文地址:https://arxiv.org/pdf/1512.03385
欢迎宝子们点赞、关注、收藏!欢迎宝子们批评指正!
1. 算法提出
深度残差网络(DRN)最初由何凯明等人于2015年在论文“Deep Residual Learning for Image Recognition”中提出。该算法的核心思想是通过残差块(Residual Block)来解决深层神经网络训练中的退化问题。
传统神经网络在层数增加时,随着网络变深,训练误差反而会上升,这种现象被称为梯度消失/爆炸问题。DRN通过引入跳跃连接(Skip Connection),将前几层的输入直接传递到后几层,从而有效缓解了这个问题。
2. 概述
DRN的核心结构是残差块。一个典型的残差块包含一个跳跃连接,将输入直接加到输出上,如下所示:
y = F ( x ) + x y=F(x)+x y=F(x)+x
其中, x x x是残差块的输入, F ( x ) F(x) F(x)是经过几层非线性变换后的输出。通过将输入 x x x直接添加到输出 F ( x ) F(x) F(x),残差网络实际上是在学习一个残差函数。这种结构使得网络能够更容易训练,并且即使网络层数增加,网络也不会出现退化现象。
残差网络的优点在于:
- 更深的网络结构:传统前馈神经网络(Feedforward Neural Networks, FFNN)的层数通常在几层到几十层,而DRN可以扩展到上百层甚至更深(如ResNet-152)。
- 稳定的训练过程:通过引入跳跃连接,梯度可以更好地传播,从而缓解了梯度消失问题。
3. 发展
自2015年提出以来,残差网络成为了许多深度学习模型的基础架构。随着研究的深入,残差网络的变种也被提出,例如:
- ResNet:最早的残差网络版本,适用于图像分类等任务。
- ResNeXt:将残差块中的卷积运算拆分为多个并行的路径,提高了模型的可扩展性。
- DenseNet:一种变体,进一步增加了层之间的密集连接。
4. 应用
DRN被广泛应用于各种深度学习任务中,特别是在计算机视觉领域表现出色。典型的应用包括:
- 图像分类:ResNet在ImageNet分类任务中取得了极好的效果,常用于图像分类任务。
- 目标检测:许多目标检测模型(如Faster R-CNN)都基于残差网络作为主干结构。
- 语义分割:在语义分割任务中,残差网络作为特征提取器也广泛使用。
5. 优缺点
优点:
- 有效的深度学习:DRN能够有效训练非常深的网络(可达150层甚至更多),而不会出现明显的性能退化。
- 跳跃连接:通过跳跃连接,DRN能够更好地传播梯度,解决梯度消失问题,从而加快训练速度。
- 强大的表达能力:可以通过残差学习获得更高的模型表达能力,适用于复杂的学习任务。
缺点:
- 计算复杂性高:随着网络深度的增加,计算资源需求显著增加,训练时间可能较长。
- 模型可解释性差:深度模型的复杂性可能导致难以解释其内部机制和决策过程。
- 需要大量数据:有效训练深度残差网络通常需要大量标注数据,以防止过拟合。
6. Python代码实现
以下是一个使用深度残差网络进行图像分类的示例,基于PyTorch框架:
import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
# 定义残差块
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.relu = nn.ReLU(inplace=True)
self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=1, padding=1, bias=False)
self.bn2 = nn.BatchNorm2d(out_channels)
# 如果输入维度和输出维度不匹配,通过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):
out = self.relu(self.bn1(self.conv1(x)))
out = self.bn2(self.conv2(out))
out += self.shortcut(x) # 跳跃连接
out = self.relu(out)
return out
# 定义ResNet模型
class ResNet(nn.Module):
def __init__(self, block, num_blocks, num_classes=10):
super(ResNet, self).__init__()
self.in_channels = 64
self.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1, bias=False)
self.bn1 = nn.BatchNorm2d(64)
self.relu = nn.ReLU(inplace=True)
self.layer1 = self._make_layer(block, 64, num_blocks[0], stride=1)
self.layer2 = self._make_layer(block, 128, num_blocks[1], stride=2)
self.layer3 = self._make_layer(block, 256, num_blocks[2], stride=2)
self.layer4 = self._make_layer(block, 512, num_blocks[3], stride=2)
self.avg_pool = nn.AdaptiveAvgPool2d((1, 1))
self.fc = nn.Linear(512, num_classes)
def _make_layer(self, block, out_channels, num_blocks, stride):
layers = []
layers.append(block(self.in_channels, out_channels, stride))
self.in_channels = out_channels
for _ in range(1, num_blocks):
layers.append(block(self.in_channels, out_channels))
return nn.Sequential(*layers)
def forward(self, x):
out = self.relu(self.bn1(self.conv1(x)))
out = self.layer1(out)
out = self.layer2(out)
out = self.layer3(out)
out = self.layer4(out)
out = self.avg_pool(out)
out = out.view(out.size(0), -1)
out = self.fc(out)
return out
# 实例化ResNet18模型
def ResNet18():
return ResNet(ResidualBlock, [2, 2, 2, 2]) # 定义ResNet18结构
# 数据预处理
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),
])
# 加载CIFAR-10数据集
train_dataset = datasets.CIFAR10(root='./data', train=True, transform=transform, download=True)
test_dataset = datasets.CIFAR10(root='./data', train=False, transform=transform, download=True)
train_loader = DataLoader(train_dataset, batch_size=100, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=100, shuffle=False)
# 定义设备、损失函数和优化器
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = ResNet18().to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
# 训练模型
def train_model(num_epochs=5):
for epoch in range(num_epochs):
model.train()
for images, labels in train_loader:
images, labels = images.to(device), labels.to(device)
outputs = model(images)
loss = criterion(outputs, labels)
optimizer.zero_grad()
loss.backward()
optimizer.step()
print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}')
# 评估模型
def test_model():
model.eval()
correct = 0
total = 0
with torch.no_grad():
for images, labels in test_loader:
images, labels = images.to(device), labels.to(device)
outputs = model(images)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
print(f'测试集准确率: {100 * correct / total:.2f}%')
# 运行训练和测试
train_model(num_epochs=5)
test_model()
代码解释:
ResidualBlock
类:实现了残差块,其中包括卷积层、批量归一化(Batch Normalization)、ReLU激活函数和跳跃连接。通过跳跃连接,将输入直接加到输出中,以实现残差学习。ResNet
类:定义了ResNet模型结构,包括多个残差块的堆叠。_make_layer
方法用于构建每一层的残差块。- 数据预处理:使用
transforms.Compose
对CIFAR-10数据集进行转换,进行标准化处理。 - 模型训练:在
train_model
函数中,模型通过多轮训练,不断优化损失函数。 - 模型评估:在
test_model
函数中,模型评估在测试集上的性能,并输出准确率。
该代码实现了基于深度残差网络的图像分类任务,展示了DRN在实际应用中的有效性。