ResNet (Residual Network) - 残差网络:深度卷积神经网络的突破

一、引言

在计算机视觉领域,图像识别一直是一个核心且具有挑战性的任务。随着深度学习的发展,卷积神经网络(CNN)在图像识别方面取得了显著的成果。然而,随着网络深度的增加,出现了梯度消失或梯度爆炸等问题,导致网络性能下降,这被称为 "退化问题"。ResNet(残差网络)的出现,为解决这一难题提供了有效的方案,它通过引入残差连接,使得深度卷积神经网络能够更好地学习图像特征,显著提高了图像识别的准确率,成为了深度学习领域的重要里程碑。

二、ResNet 的背景与发展历程

(一)深度学习与卷积神经网络的兴起

深度学习的兴起得益于大数据的发展和计算能力的提升。卷积神经网络作为深度学习的重要分支,因其在处理具有网格结构数据(如图像)方面的优势,成为图像识别的主流模型。早期的卷积神经网络如 LeNet-5 等,在简单的图像分类任务上取得了不错的效果,但随着任务复杂度的增加,网络深度的不足限制了其性能的进一步提升。

(二)深度网络的挑战 - 退化问题

当研究人员尝试增加网络深度时,发现网络性能并没有如预期般提升,反而出现了退化现象。即更深的网络在训练集和测试集上的准确率反而不如较浅的网络。这一现象引起了广泛关注,传统的观点认为,增加网络深度应该能够学习到更复杂的特征,从而提高性能,但实际情况并非如此。

(三)ResNet 的诞生

为了解决深度网络的退化问题,何恺明等人于 2015 年提出了 ResNet。ResNet 的核心思想是引入残差连接(Residual Connection),使得网络能够更容易地学习到恒等映射(Identity Mapping),从而缓解了梯度消失或梯度爆炸问题,使得深度网络的训练成为可能。ResNet 在当年的 ImageNet 大规模视觉识别挑战赛(ILSVRC)中取得了优异的成绩,引起了学术界和工业界的广泛关注,并迅速成为深度学习领域的研究热点之一。

三、ResNet 的基本原理

(一)残差块(Residual Block)

ResNet 的基本构建模块是残差块。一个简单的残差块由两部分组成:主路径和残差连接。主路径通常包含若干卷积层、批量归一化(Batch Normalization)层和激活函数(如 ReLU)。残差连接则是将输入直接连接到主路径的输出,然后将两者相加作为残差块的最终输出。

以下是一个使用 PyTorch 实现的简单残差块代码示例:

python 复制代码
import torch
import torch.nn as nn

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)

        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)
            )
        else:
            self.shortcut = nn.Identity()

    def forward(self, x):
        residual = x
        out = self.conv1(x)
        out = self.bn1(out)
        out = self.relu(out)
        out = self.conv2(out)
        out = self.bn2(out)
        out += self.shortcut(residual)
        out = self.relu(out)
        return out

在上述代码中,ResidualBlock类定义了一个残差块。conv1和conv2是卷积层,bn1和bn2是批量归一化层,relu是 ReLU 激活函数。shortcut表示残差连接,如果输入和输出的通道数或步长不一致,shortcut会通过一个卷积层和批量归一化层进行维度调整,否则直接使用nn.Identity()。在forward方法中,先计算主路径的输出,然后将其与残差连接的输出相加,并通过 ReLU 激活函数得到最终结果。

(二)残差学习的原理

假设我们要学习的函数为H(x),残差网络尝试学习的是残差函数
那么原始函数就可以表示为

这种学习方式使得网络更容易学习到恒等映射,因为如果残差函数为 0,那么网络就可以轻松地学习到恒等映射,而不需要对网络参数进行大幅调整。在深度网络中,这种残差学习的方式有助于梯度的传播,缓解了梯度消失或梯度爆炸问题,使得网络能够更好地训练。

例如,在一个非常深的网络中,假设某一层的梯度很小,如果没有残差连接,那么这个小梯度在反向传播过程中会不断衰减,导致前面的层难以训练。而有了残差连接,即使某一层的梯度很小,通过残差连接,前面层的梯度仍然可以通过残差路径得到一定程度的保留,从而使得网络能够更有效地训练。

四、ResNet 的网络架构

(一)不同深度的 ResNet 模型

ResNet 系列包括多种不同深度的模型,如 ResNet-18、ResNet-34、ResNet-50、ResNet-101 和 ResNet-152 等。这些模型的主要区别在于残差块的数量和网络的宽度(即通道数)。以下是 ResNet-18 的网络架构示例:

python 复制代码
class ResNet18(nn.Module):
    def __init__(self, num_classes=1000):
        super(ResNet18, self).__init__()
        self.in_channels = 64
        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)
        self.layer1 = self._make_layer(64, 2, stride=1)
        self.layer2 = self._make_layer(128, 2, stride=2)
        self.layer3 = self._make_layer(256, 2, stride=2)
        self.layer4 = self._make_layer(512, 2, stride=2)
        self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
        self.fc = nn.Linear(512, num_classes)

    def _make_layer(self, out_channels, num_blocks, stride):
        strides = [stride] + [1] * (num_blocks - 1)
        layers = []
        for stride in strides:
            layers.append(ResidualBlock(self.in_channels, out_channels, stride))
            self.in_channels = out_channels
        return nn.Sequential(*layers)

    def forward(self, x):
        out = self.conv1(x)
        out = self.bn1(out)
        out = self.relu(out)
        out = self.maxpool(out)
        out = self.layer1(out)
        out = self.layer2(out)
        out = self.layer3(out)
        out = self.layer4(out)
        out = self.avgpool(out)
        out = torch.flatten(out, 1)
        out = self.fc(out)
        return out

在上述代码中,ResNet18类定义了 ResNet-18 模型。conv1是第一个卷积层,后面跟着批量归一化层和 ReLU 激活函数,然后是一个最大池化层。_make_layer方法用于构建残差块组成的层,根据指定的输出通道数、残差块数量和步长来创建相应的残差块序列。layer1到layer4分别是不同阶段的残差层,最后通过自适应平均池化层将特征图转换为固定大小,再通过全连接层进行分类。

(二)瓶颈结构(Bottleneck Structure)

对于更深的 ResNet 模型(如 ResNet-50 及以上),为了减少计算量和参数数量,采用了瓶颈结构。瓶颈结构在残差块中增加了一个卷积层来降低通道数,然后通过11卷积层进行特征提取,最后再通过一个33卷积层恢复通道数。以下是一个使用瓶颈结构的残差块代码示例:

python 复制代码
class BottleneckBlock(nn.Module):
    expansion = 4

    def __init__(self, in_channels, out_channels, stride=1):
        super(BottleneckBlock, self).__init__()
        self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=1, bias=False)
        self.bn1 = nn.BatchNorm2d(out_channels)
        self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=stride, padding=1, bias=False)
        self.bn2 = nn.BatchNorm2d(out_channels)
        self.conv3 = nn.Conv2d(out_channels, out_channels * self.expansion, kernel_size=1, bias=False)
        self.bn3 = nn.BatchNorm2d(out_channels * self.expansion)

        if stride!= 1 or in_channels!= out_channels * self.expansion:
            self.shortcut = nn.Sequential(
                nn.Conv2d(in_channels, out_channels * self.expansion, kernel_size=1, stride=stride, bias=False),
                nn.BatchNorm2d(out_channels * self.expansion)
            )
        else:
            self.shortcut = nn.Identity()

        self.relu = nn.ReLU(inplace=True)

    def forward(self, x):
        residual = x
        out = self.conv1(x)
        out = self.bn1(out)
        out = self.relu(out)
        out = self.conv2(out)
        out = self.bn2(out)
        out = self.relu(out)
        out = self.conv3(out)
        out = self.bn3(out)
        out += self.shortcut(residual)
        out = self.relu(out)
        return out

在BottleneckBlock类中,expansion表示通道扩展倍数,这里设置为 4。conv1用于降低通道数,conv2进行主要的特征提取,conv3恢复通道数。通过这种方式,在不损失太多性能的前提下,大大减少了计算量和参数数量,使得更深的网络能够在实际应用中得以训练和使用。

五、ResNet 在图像识别中的应用

(一)ImageNet 数据集上的表现

ResNet 在 ImageNet 数据集上取得了非常出色的成绩。ImageNet 是一个大规模的图像数据集,包含了数百万张图像和上千个类别,是图像识别领域的重要基准数据集。ResNet-18、ResNet-34 等较浅的模型在 ImageNet 上的准确率已经超过了很多传统的卷积神经网络模型,而 ResNet-50、ResNet-101 和 ResNet-152 等更深的模型则进一步提高了准确率,在图像分类任务上达到了当时的先进水平。

以下是使用 ResNet-50 在 ImageNet 数据集上进行训练和测试的示例代码(使用 PyTorch 和 torchvision 库):

python 复制代码
import torch
import torchvision
import torchvision.transforms as transforms
import torch.nn as nn
import torch.optim as optim

# 数据预处理
transform_train = transforms.Compose([
    transforms.RandomResizedCrop(224),
    transforms.RandomHorizontalFlip(),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])

transform_test = transforms.Compose([
    transforms.Resize(256),
    transforms.CenterCrop(224),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])

# 加载训练集和测试集
trainset = torchvision.datasets.ImageNet(root='./data', split='train', transform=transform_train)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=128, shuffle=True, num_workers=4)

testset = torchvision.datasets.ImageNet(root='./data', split='val', transform=transform_test)
testloader = torch.utils.data.DataLoader(testset, batch_size=128, shuffle=False, num_workers=4)

# 定义ResNet-50模型
net = torchvision.models.resnet50(pretrained=False)
num_classes = 1000
net.fc = nn.Linear(net.fc.in_features, num_classes)

# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.01, momentum=0.9)

# 训练模型
def train(epoch):
    net.train()
    running_loss = 0.0
    for i, data in enumerate(trainloader, 0):
        inputs, labels = data
        optimizer.zero_grad()
        outputs = net(inputs)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()
        running_loss += loss.item()
        if i % 100 == 99:
            print('[%d, %5d] loss: %.3f' % (epoch + 1, i + 1, running_loss / 100))
            running_loss = 0.0

# 测试模型
def test():
    net.eval()
    correct = 0
    total = 0
    with torch.no_grad():
        for data in testloader:
            images, labels = data
            outputs = net(images)
            _, predicted = torch.max(outputs.data, 1)
            total += labels.size(0)
            correct += (predicted == labels).sum().item()
    print('Accuracy of the network on the test images: %d %%' % (100 * correct / total))

# 训练和测试循环
for epoch in range(10):
    train(epoch)
    test()

在上述代码中,首先定义了数据预处理的转换操作,包括随机裁剪、随机水平翻转、转换为张量以及标准化等。然后加载 ImageNet 数据集的训练集和测试集,并使用torchvision.models.resnet50创建 ResNet-50 模型,将最后一层全连接层修改为适应 ImageNet 的类别数。接着定义了损失函数(交叉熵损失)和优化器(随机梯度下降),并实现了训练和测试函数。在训练过程中,对每个批次的数据进行前向传播、计算损失、反向传播和参数更新。在测试过程中,计算模型在测试集上的准确率。

(二)其他图像识别任务中的应用

除了在 ImageNet 数据集上的图像分类任务,ResNet 还被广泛应用于其他图像识别任务,如目标检测、图像分割、人脸识别等。

目标检测:在目标检测任务中,ResNet 常作为骨干网络(Backbone Network)用于提取图像的特征。例如,在 Faster R-CNN、Mask R-CNN 等目标检测模型中,ResNet 可以提供丰富的语义特征,帮助模型准确地检测和定位图像中的目标。以下是使用 ResNet-50 作为骨干网络的 Faster R-CNN 模型的示例代码(使用 PyTorch 和 torchvision 库):

python 复制代码
import torchvision
from torchvision.models.detection import FasterRCNN
from torchvision.models.detection.rpn import AnchorGenerator

# 加载ResNet-50作为骨干网络
backbone = torchvision.models.resnet50(pretrained=True)
backbone = nn.Sequential(*list(backbone.children())[:-2])

# 定义锚点生成器
anchor_generator = AnchorGenerator(sizes=((32, 64, 128, 256, 512),), aspect_ratios=((0.5, 1.0, 2.0),))

# 创建Faster R-CNN模型
roi_pooler = torchvision.ops.MultiScaleRoIAlign(featmap_names=['0'], output_size=7, sampling_ratio=2)
model = FasterRCNN(backbone, num_classes=2, rpn_anchor_generator=anchor_generator, box_roi_pool=roi_pooler)

# 定义损失函数和优化器
params = [p for p in model.parameters() if p.requires_grad]
optimizer = torch.optim.SGD(params, lr=0.005, momentum=0.9, weight_decay=0.0005)
lr_scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=3, gamma=0.1)
相关推荐
沙漏AI机器人4 分钟前
【20250115】Nature子刊:柔性生物传感与深度学习结合的上肢运动增强外骨骼机器人...
人工智能·深度学习·机器人
吕小明么5 分钟前
腾讯AI Lab与上交大探索模型“过度”思考
人工智能·神经网络·算法·aigc·agi
Bug退退退1236 分钟前
UDP报文格式
网络·网络协议·udp
WoTrusSSL36 分钟前
SSL 与 TLS:简单解释主要区别
网络·安全·https·ssl
Logrus IT38 分钟前
俄语画外音的特点
人工智能·语音识别
浊酒南街1 小时前
SVM模型(理论知识2)
人工智能·机器学习·支持向量机
Fuweizn1 小时前
案例|富唯智能复合机器人CNC柔性上下料
人工智能·机器人·自动化
KeyPan1 小时前
【机器学习:三十三(二)、支持向量机(SVM)的核函数:概念、类型与应用】
人工智能·神经网络·算法·机器学习·支持向量机·数据挖掘
Robot2511 小时前
协作机器人公司切入人形机器人赛道,大有可为!
人工智能·科技·机器人·自动驾驶
机器学习小小白1 小时前
【机器学习实战】kaggle 欺诈检测---使用生成对抗网络(GAN)解决欺诈数据中正负样本极度不平衡问题
人工智能·python·深度学习·神经网络·机器学习·生成对抗网络·gan