P13:DenseNet算法实战与解析

()本周任务:
1.请根据本文 Pytorch 代码,编写出相应的 TensorFlow 代码(建议使用上周的数据测试一下模型是
否构建正确)
2.了解并研究 DenseNet与ResNetV 的区别

一、DenseNet121与DenseNet

  • DenseNet(Densely Connected Convolutional Networks)是一种卷积神经网络架构,由Gao Huang等人于2017年提出。其核心思想是密集连接(dense connection),即每一层都与前面所有层直接连接,从而促进特征的重用和梯度的传播。
  • DenseNet121是DenseNet家族中的一个特定模型,其中“121”表示网络的总层数。它采用了密集连接策略,具有以下特点:
    • 密集连接:在一个Dense Block内,第i层的输入不仅仅是第i−1层的输出,还包括第i−2层、第i−3层等所有之前层的输出。
    • 参数效率:由于特征在网络中得以重复使用,DenseNet相较于其他深度网络模型(如VGG或ResNet)通常需要更少的参数来达到相同(或更好)的性能。
    • 特征复用与强化:密集连接方式也促进了梯度的反向传播,使得网络更容易训练。同时,低层特征能被直接传播到输出层,因此被更好地强化和利用。
      DenseNet(Densely Connected Convolutional Network)是一种高效的卷积神经网络架构,以下是其密集链接、参数效率、特征复用与强化的具体介绍:

二、特点介绍

密集链接

  • 定义:在DenseNet中,密集链接指的是网络中的每一层都与其他所有层直接连接。具体来说,第(l)层的输入不仅包括上一层(l-1)的输出,还包括前面所有层(1,2,\cdots,l-2)的输出。这种连接方式打破了传统卷积神经网络中层层递进的连接模式,使得信息能够在网络中更高效地流动。
  • 作用机制
    • 缓解梯度消失问题:在传统的深度神经网络中,随着网络深度的增加,梯度在反向传播过程中容易出现消失或爆炸的情况,导致训练困难。而DenseNet的密集链接使得梯度能够更直接地从后面的层传播到前面的层,为梯度提供了更多的传播路径,从而有效地缓解了梯度消失问题,使得网络能够更容易地进行训练。
    • 促进特征传播:每一层都能直接获取前面所有层的特征信息,这意味着浅层的特征能够更直接地传递到深层,避免了在传递过程中可能出现的信息丢失或衰减。同时,深层的特征也能反馈给浅层,实现了不同层次特征的融合与交互,有助于网络学习到更丰富、更复杂的特征。

参数效率

  • 减少参数数量:DenseNet通过密集链接的方式,在实现高性能的同时,能够减少模型的参数数量。这是因为密集链接使得网络中的每一层都可以重用前面层学习到的特征,避免了重复学习相同或相似的特征,从而大大减少了模型参数的冗余。
  • 计算资源高效利用:由于参数数量的减少,DenseNet在计算过程中所需的内存和计算量也相应降低。在训练和推理过程中,能够更高效地利用计算资源,提高计算速度,降低能耗。这使得DenseNet在资源受限的环境中,如移动设备、嵌入式系统等,具有更好的适用性和优势。

特征复用与强化

  • 特征复用
    • 底层特征利用:在图像识别等任务中,底层卷积层通常能够学习到图像的一些基本特征,如边缘、纹理等。DenseNet的密集链接使得这些底层特征可以被后面的多个层直接复用,避免了在不同层中重复学习这些基本特征,提高了特征利用的效率。
    • 中间层特征共享:中间层学习到的特征往往具有更丰富的语义信息,既包含了一定的底层细节,又具有一定的抽象语义。这些特征在密集链接的作用下,可以被后续的多个层共享和利用,进一步增强了网络对不同层次特征的综合利用能力。
  • 特征强化
    • 多尺度特征融合:通过密集链接,不同尺度和层次的特征能够被融合在一起。例如,浅层的低分辨率、细节丰富的特征与深层的高分辨率、语义更强的特征可以相互补充,形成更全面、更具代表性的特征表示。这种多尺度特征的融合能够强化特征的表达能力,使得网络对不同大小、不同语义的目标都能有更好的识别和理解能力。
    • 特征非线性增强:每一层的输出都是基于前面所有层的特征进行非线性变换得到的。这种非线性变换能够对特征进行进一步的提炼和强化,使得网络能够学习到更复杂、更高级的特征关系。通过不断地对特征进行非线性增强,DenseNet能够生成更具判别力的特征表示,从而提高模型的分类或其他任务的性能。

DenseNet与ResNet的对比

  • 连接方式:DenseNet中每一层都与其前面的所有层密集连接,而ResNet中每一层仅与其前一层进行残差连接。
  • 参数效率:DenseNet由于特征复用,参数效率更高,而ResNet的参数效率相对较低。
  • 特征复用:DenseNet实现了高度的特征复用,所有前面层的输出都用作每一层的输入,而ResNet仅前一层的输出被用于下一层。
  • 梯度流动:DenseNet由于密集连接,梯度流动更容易,而ResNet通过残差连接改善梯度流动,但相对于DenseNet可能较弱。
  • 过拟合抑制:DenseNet在数据集较小的情况下具有更强的过拟合抑制能力,而ResNet相对较弱。
  • 计算复杂度:DenseNet的计算复杂度一般来说更低,尽管有更多的连接,而ResNet的计算复杂度一般来说更高,尤其是在深层网络中。
  • 网络深度:DenseNet可以构建更深的网络且更容易训练,而ResNet虽然也可以构建很深的网络,但通常需要更仔细的设计。

网络结构

  • DenseNet:由多个Dense Block组成,每个Dense Block包含若干层,每一层的输出都被传递到下一层中,同时每一层的输入也会被直接连接到后续所有的层中。在Dense Block之间有Transition Layer进行降维和减通道数,最后通过全局池化层和分类器层进行分类。
  • DenseNet121:具体结构包括以下几个部分:
    • Initial Block:包含一个7x7的卷积层和一个3x3的最大池化层。
    • Dense Blocks:包含4个Dense Block,每个Dense Block之间由Transition Layer连接。每个Dense Block包含若干层,每层包含批量归一化、ReLU激活函数和3x3卷积层。
    • Transition Layers:包含批量归一化、1x1卷积层和2x2平均池化层,用于减少特征图的尺寸和通道数。
    • Final Layer:包含全局平均池化层和一个全连接层,用于最终的分类。

三、ResNetV2代码实现

1. 库函数

import matplotlib.pyplot as plt
from torchvision import transforms, datasets
import os, PIL, pathlib
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader, random_split
import numpy as np

2.数据导入

# 设置设备
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

# 支持中文
plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号

# 数据集路径
data_dir = "./data/day_eight/bird_photos"
data_dir = pathlib.Path(data_dir)

# 图片总数
image_count = len(list(data_dir.glob('*/*')))
print("图片总数为:", image_count)
图片总数为: 565

3.获取数据路径和类别名称

data_paths = list(data_dir.glob('*'))
classeNames = [str(path).split("\\")[3] for path in data_paths]
print(classeNames)
['Bananaquit', 'Black Skimmer', 'Black Throated Bushtiti', 'Cockatoo']

4.数据预处理

# 定义批量大小和图像尺寸
batch_size = 8
img_height = 224
img_width = 224

# 定义数据预处理转换
train_transforms = transforms.Compose([
    transforms.Resize([224, 224]),
    transforms.ToTensor(),
    transforms.Normalize(
        mean=[0.485, 0.456, 0.406],
        std=[0.229, 0.224, 0.225]
    )
])

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

# 加载数据集
total_data = datasets.ImageFolder(data_dir, transform=train_transforms)

# 划分训练集和验证集
train_size = int(0.8 * len(total_data))
val_size = len(total_data) - train_size
train_dataset, val_dataset = random_split(total_data, [train_size, val_size])

# 创建数据加载器
train_ds = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
val_ds = DataLoader(val_dataset, batch_size=batch_size, shuffle=False)

5.可视化数据集

plt.figure(figsize=(10, 5))
plt.suptitle("数据集")

images, labels = next(iter(train_ds))
for i in range(8):
    ax = plt.subplot(2, 4, i + 1)
    image = images[i].permute(1, 2, 0).cpu().numpy()
    # 反归一化
    mean = np.array([0.485, 0.456, 0.406])
    std = np.array([0.229, 0.224, 0.225])
    image = std * image + mean
    image = np.clip(image, 0, 1)
    plt.imshow(image)
    plt.title(classeNames[labels[i].item()])
    plt.axis("off")

plt.show()

在这里插入图片描述

5.模型定义

# 定义 DenseNet 相关类和函数
model_urls = {
    'densenet121': 'https://download.pytorch.org/models/densenet121-a639ec97.pth',
    # 可以根据需要添加其他 DenseNet 模型的预训练权重链接
}

class _DenseLayer(nn.Sequential):
    """Basic unit of DenseBlock (using bottleneck layer) """
    def __init__(self, num_input_features, growth_rate, bn_size, drop_rate):
        super(_DenseLayer, self).__init__()
        self.add_module("norm1", nn.BatchNorm2d(num_input_features))
        self.add_module("relu1", nn.ReLU(inplace=True))
        self.add_module("conv1", nn.Conv2d(num_input_features, bn_size*growth_rate,
                                           kernel_size=1, stride=1, bias=False))
        self.add_module("norm2", nn.BatchNorm2d(bn_size*growth_rate))
        self.add_module("relu2", nn.ReLU(inplace=True))
        self.add_module("conv2", nn.Conv2d(bn_size*growth_rate, growth_rate,
                                           kernel_size=3, stride=1, padding=1, bias=False))
        self.drop_rate = drop_rate

    def forward(self, x):
        new_features = super(_DenseLayer, self).forward(x)
        if self.drop_rate > 0:
            new_features = F.dropout(new_features, p=self.drop_rate, training=self.training)
        return torch.cat([x, new_features], 1)

class _DenseBlock(nn.Sequential):
    """DenseBlock"""
    def __init__(self, num_layers, num_input_features, bn_size, growth_rate, drop_rate):
        super(_DenseBlock, self).__init__()
        for i in range(num_layers):
            layer = _DenseLayer(num_input_features+i*growth_rate, growth_rate, bn_size,
                                drop_rate)
            self.add_module("denselayer%d" % (i+1,), layer)

class _Transition(nn.Sequential):
    """Transition layer between two adjacent DenseBlock"""
    def __init__(self, num_input_feature, num_output_features):
        super(_Transition, self).__init__()
        self.add_module("norm", nn.BatchNorm2d(num_input_feature))
        self.add_module("relu", nn.ReLU(inplace=True))
        self.add_module("conv", nn.Conv2d(num_input_feature, num_output_features,
                                          kernel_size=1, stride=1, bias=False))
        self.add_module("pool", nn.AvgPool2d(2, stride=2))


class DenseNet(nn.Module):
    "DenseNet-BC model"
    def __init__(self, growth_rate=32, block_config=(6, 12, 24, 16), num_init_features=64,
                 bn_size=4, compression_rate=0.5, drop_rate=0, num_classes=1000):
        """
        :param growth_rate: (int) number of filters used in DenseLayer, `k` in the paper
        :param block_config: (list of 4 ints) number of layers in each DenseBlock
        :param num_init_features: (int) number of filters in the first Conv2d
        :param bn_size: (int) the factor using in the bottleneck layer
        :param compression_rate: (float) the compression rate used in Transition Layer
        :param drop_rate: (float) the drop rate after each DenseLayer
        :param num_classes: (int) number of classes for classification
        """
        super(DenseNet, self).__init__()
        # first Conv2d
        self.features = nn.Sequential(OrderedDict([
            ("conv0", nn.Conv2d(3, num_init_features, kernel_size=7, stride=2, padding=3, bias=False)),
            ("norm0", nn.BatchNorm2d(num_init_features)),
            ("relu0", nn.ReLU(inplace=True)),
            ("pool0", nn.MaxPool2d(3, stride=2, padding=1))
        ]))

        # DenseBlock
        num_features = num_init_features
        for i, num_layers in enumerate(block_config):
            block = _DenseBlock(num_layers, num_features, bn_size, growth_rate, drop_rate)
            self.features.add_module("denseblock%d" % (i + 1), block)
            num_features += num_layers*growth_rate
            if i != len(block_config) - 1:
                transition = _Transition(num_features, int(num_features*compression_rate))
                self.features.add_module("transition%d" % (i + 1), transition)
                num_features = int(num_features * compression_rate)

        # final bn+ReLU
        self.features.add_module("norm5", nn.BatchNorm2d(num_features))
        self.features.add_module("relu5", nn.ReLU(inplace=True))

        # classification layer
        self.classifier = nn.Linear(num_features, num_classes)

        # params initialization
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                nn.init.kaiming_normal_(m.weight)
            elif isinstance(m, nn.BatchNorm2d):
                nn.init.constant_(m.bias, 0)
                nn.init.constant_(m.weight, 1)
            elif isinstance(m, nn.Linear):
                nn.init.constant_(m.bias, 0)

    def forward(self, x):
        features = self.features(x)
        out = F.avg_pool2d(features, 7, stride=1).view(features.size(0), -1)
        out = self.classifier(out)
        return out

def densenet121(pretrained=False, **kwargs):
    """DenseNet121"""
    model = DenseNet(num_init_features=64, growth_rate=32, block_config=(6, 12, 24, 16),
                     **kwargs)

    if pretrained:
        # '.'s are no longer allowed in module names, but pervious _DenseLayer
        # has keys 'norm.1', 'relu.1', 'conv.1', 'norm.2', 'relu.2', 'conv.2'.
        # They are also in the checkpoints in model_urls. This pattern is used
        # to find such keys.
        pattern = re.compile(
            r'^(.*denselayer\d+\.(?:norm|relu|conv))\.((?:[12])\.(?:weight|bias|running_mean|running_var))$')
        state_dict = model_zoo.load_url(model_urls['densenet121'])
        for key in list(state_dict.keys()):
            res = pattern.match(key)
            if res:
                new_key = res.group(1) + res.group(2)
                state_dict[new_key] = state_dict[key]
                del state_dict[key]
        model.load_state_dict(state_dict)
    return model


6.模型训练

# 创建模型实例
num_classes = len(classeNames)
model = densenet121(pretrained=False, num_classes=num_classes).to(device)
# 使用 torchsummary 打印模型结构
print(summary(model, input_size=(3, 224, 224)))
----------------------------------------------------------------
        Layer (type)               Output Shape         Param #
================================================================
            Conv2d-1         [-1, 64, 112, 112]           9,408
       BatchNorm2d-2         [-1, 64, 112, 112]             128
             		 ···						   ···
             		 ···						   ···
             		 ···						   ···
            ReLU-366           [-1, 1024, 7, 7]               0
          Linear-367                    [-1, 4]           4,100
================================================================
Total params: 6,957,956
Trainable params: 6,957,956
Non-trainable params: 0
# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters())

# 训练参数
epochs = 10
train_losses = []
val_losses = []
train_accuracies = []
val_accuracies = []

# 训练循环
for epoch in range(epochs):
    model.train()
    train_loss = 0
    train_correct = 0
    train_total = 0
    for images, labels in train_ds:
        images, labels = images.to(device), labels.to(device)
        optimizer.zero_grad()
        outputs = model(images)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()

        train_loss += loss.item()
        _, predicted = torch.max(outputs.data, 1)
        train_total += labels.size(0)
        train_correct += (predicted == labels).sum().item()

    train_loss /= len(train_ds)
    train_accuracy = 100 * train_correct / train_total
    train_losses.append(train_loss)
    train_accuracies.append(train_accuracy)

    model.eval()
    val_loss = 0
    val_correct = 0
    val_total = 0
    with torch.no_grad():
        for images, labels in val_ds:
            images, labels = images.to(device), labels.to(device)
            outputs = model(images)
            loss = criterion(outputs, labels)

            val_loss += loss.item()
            _, predicted = torch.max(outputs.data, 1)
            val_total += labels.size(0)
            val_correct += (predicted == labels).sum().item()

    val_loss /= len(val_ds)
    val_accuracy = 100 * val_correct / val_total
    val_losses.append(val_loss)
    val_accuracies.append(val_accuracy)

    print(f'Epoch {epoch + 1}/{epochs}, Train Loss: {train_loss:.4f}, Train Acc: {train_accuracy:.2f}%, Val Loss: {val_loss:.4f}, Val Acc: {val_accuracy:.2f}%')


7. 绘制结果

# 绘制训练和验证曲线
epochs_range = range(epochs)

plt.figure(figsize=(12, 4))
plt.subplot(1, 2, 1)
plt.suptitle("微信公众号:K同学啊")

plt.plot(epochs_range, train_accuracies, label='Training Accuracy')
plt.plot(epochs_range, val_accuracies, label='Validation Accuracy')
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')

plt.subplot(1, 2, 2)
plt.plot(epochs_range, train_losses, label='Training Loss')
plt.plot(epochs_range, val_losses, label='Validation Loss')
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')
plt.show()
Epoch 1/10, Train Loss: 1.0186, Train Acc: 58.63%, Val Loss: 1.5051, Val Acc: 59.29%
Epoch 2/10, Train Loss: 0.7674, Train Acc: 66.81%, Val Loss: 0.6617, Val Acc: 77.88%
Epoch 3/10, Train Loss: 0.6746, Train Acc: 77.88%, Val Loss: 0.6344, Val Acc: 76.11%
Epoch 4/10, Train Loss: 0.6340, Train Acc: 78.10%, Val Loss: 0.4998, Val Acc: 87.61%
Epoch 5/10, Train Loss: 0.6296, Train Acc: 78.54%, Val Loss: 0.9036, Val Acc: 73.45%
Epoch 6/10, Train Loss: 0.5383, Train Acc: 81.86%, Val Loss: 0.2479, Val Acc: 91.15%
Epoch 7/10, Train Loss: 0.5220, Train Acc: 80.75%, Val Loss: 1.1503, Val Acc: 61.06%
Epoch 8/10, Train Loss: 0.4167, Train Acc: 86.50%, Val Loss: 0.4137, Val Acc: 88.50%
Epoch 9/10, Train Loss: 0.5516, Train Acc: 79.20%, Val Loss: 0.5084, Val Acc: 85.84%
Epoch 10/10, Train Loss: 0.3977, Train Acc: 86.28%, Val Loss: 0.4362, Val Acc: 89.38%

在这里插入图片描述

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值