深度学习camp-第J3周:DenseNet算法实战与解析
- 🍨 本文为🔗365天深度学习训练营 中的学习记录博客
- 🍖 原作者:K同学啊
我的环境
- 语言环境:Python 3.12
- 编译器:Jupyter Lab
- 深度学习环境:Pytorch 2.4.1 Torchvision 0.19.1
- 数据集:鸟类分类数据库(和之前的几周一样)
📌 本周任务:
●2.了解并研究 DenseNet与ResNetV 的区别
●3.改进思路是否可以迁移到其他地方呢(自由探索,虽然不强求但是请认真对待这个哦)
一、前言
在计算机视觉领域,卷积神经网络(CNN)已经成为最主流的方法,比如GoogLenet,VGG-16,Incepetion等模型。CNN史上的一个里程碑事件是ResNet模型的出现,ResNet可以训练出更深的CNN模型,从而实现更高的准确度。ResNet模型的核心是通过建立前面层与后面层之间的“短路连接”(shortcuts,skip connection),进而训练出更深的CNN网络。
今天我们要介绍的是DenseNet模型,它的基本思路与ResNet一致,但是它建立的是前面所有层与后面层的密集连接(dense connection),它的名称也是由此而来。DenseNet的另一大特色是通过特征在channel上的连接来实现特征重用(feature reuse)。这些特点让DenseNet在参数和计算成本更少的情形下实现比ResNet更优的性能,DenseNet也因此斩获CVPR 2017的最佳论文奖。
DenseNet(Densely Connected Convolutional Network)是一种深度神经网络架构,于2017年由Gao Huang等人提出。它是深度学习中经典的卷积神经网络模型之一,在图像分类、目标检测等领域表现优异。DenseNet通过高效的信息流传递和参数复用,实现了更高的性能和更少的参数量。

以下是对DenseNet的核心思想、结构和优点的详细介绍:
核心思想
DenseNet的关键思想是通过密集连接(Dense Connectivity),实现特征的高效利用。具体来说:
- 在传统的卷积网络中,层与层之间是线性连接的,数据从上一层流向下一层。而在DenseNet中,每一层都与之前所有层直接相连。
- 每一层的输出不仅作为下一层的输入,还会被直接传递给所有后续层。这样可以更好地保留前面层提取的特征,并减轻梯度消失问题。
网络结构
DenseNet的基本构成可以分为以下几个部分:
-
Dense Block
- 在一个Dense Block中,每一层都接收所有前面层的特征图作为输入,并将自己的输出传递给后续所有层。
- 对于第( l )层,其输入是所有之前层的特征的级联,公式为:
x l = H l ( [ x 0 , x 1 , … , x l − 1 ] ) x_l = H_l([x_0, x_1, \ldots, x_{l-1}]) xl=Hl([x0,x1,…,xl−1])
其中, H l H_l Hl表示第 l l l层的非线性变换(如卷积、ReLU等), [ x 0 , x 1 , … , x l − 1 ] [x_0, x_1, \ldots, x_{l-1}] [x0,x1,…,xl−1]表示特征级联。
-
Transition Layer
- 在Dense Blocks之间插入Transition Layer,用于降维和控制模型复杂度。
- Transition Layer通常由一个 1 × 1 1\times1 1×1卷积层和一个平均池化层(Average Pooling)组成。
-
Growth Rate
- DenseNet的一个重要超参数是增长率(Growth Rate, ( k )),表示每一层新增的特征图数量。
- Growth Rate较小时,模型更轻量化;较大时,模型更有表达能力。
-
全局池化和分类层
- 最终通过全局平均池化层(Global Average Pooling)和全连接层实现分类。
优点
-
参数高效
- DenseNet通过特征复用显著减少了参数量,因为不需要重复计算冗余特征。
-
缓解梯度消失
- DenseNet的直接连接机制使得梯度可以从后层更轻松地传递到前面层,改善了深度网络的训练。
-
特征复用
- 每一层的特征都会被后续层反复利用,显著提高了模型的表达能力。
-
更深的网络
- DenseNet的架构支持更深的网络,而不会带来过高的计算开销。
DenseNet的变体
DenseNet有多个变体,适应不同的任务和场景:
-
DenseNet-121、DenseNet-169、DenseNet-201、DenseNet-264
- 基于层数的不同,适用于从轻量级到更复杂的任务。
-
DenseNet在语义分割、目标检测中的扩展
- 如在DenseASPP等模型中,与空洞卷积结合,用于语义分割任务。
应用场景
- 图像分类:如ImageNet、CIFAR-10等数据集上表现优异。
- 目标检测:DenseNet作为特征提取器,广泛应用于Faster R-CNN等模型中。
- 医学影像分析:DenseNet因其高效的特征表达能力,常用于CT图像分类、病灶检测等任务。
DenseNet与ResNet的比较
| 特性 | DenseNet | ResNet |
|---|---|---|
| 连接方式 | 每层与之前所有层连接 | 仅与前一层的输出相加 |
| 参数量 | 更少 | 较多 |
| 特征复用 | 高效 | 较低 |
| 梯度流动 | 更顺畅 | 较好 |
| 适用场景 | 更深、更复杂的网络 | 多种场景,适用性广泛 |
二、设计理念
相比ResNet,DenseNet提出了一种更为激进的密集链接机制:即互相连接所有的层,具体来说就是每个层都会接受前面所有层作为其额外的输入。
图1展示了ResNet的残差连接机制,作为对比,后面图2为DenseNet的密集连接机制。可以看到,ResNet是每个层与前面的某层短路连接在一起,连接方式是元素相加,而DenseNet中,每个层都会与前面所有层在channel维度上concat在一起,元素叠加,并作为下一层的输入。
图1
图2
三、网络结构
CNN网络一般要经过Pooling或者Stride > 1的Conv来降低特征图的大小,而DenseNet的密集连接方式需要特征图大小保持一致。为了解决这个问题,DenseNet网络中使用DenseBlock + Transition的结构,其中DenseBlock是包含很多层的模块,每个层的特征图大小相同,层与层之间采用密集连接方式,Transition层是链接两个相邻的DenseBlock,通过Pooling使特征图的大小降低。

四、网络构建
1、Denselayer的构建
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils import model_zoo
class _DenseLayer(nn.Sequential):
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)
2、Denseblock的构建
class _DenseBlock(nn.Sequential):
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)
3、Transition的构建
class _Transition(nn.Sequential):
def __init__(self, num_input_features, num_output_features):
super(_Transition,self).__init__()
self.add_module('norm',nn.BatchNorm2d(num_input_features))
self.add_module('relu',nn.ReLU(inplace=True))
self.add_module('conv',nn.Conv2d(num_input_features, num_output_features, kernel_size=1,stride=1, bias=False))
self.add_module('pool',nn.AvgPool2d(2, stride=2))
4、完整网络构建
from collections import OrderedDict
class DenseNet(nn.Module):
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):
super(DenseNet, self).__init__()
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))
]))
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)
self.features.add_module('norm5', nn.BatchNorm2d(num_features))
self.features.add_module('relu5', nn.ReLU(inplace=True))
self.classifier = nn.Linear(num_features, num_classes)
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, kernel_size=features.size()[2:]).view(features.size(0), -1)
out = self.classifier(out)
return out
使用densenet121:
import re
from torch.utils import model_zoo
model_urls = {
'densenet121': 'https://download.pytorch.org/models/densenet121-a639ec97.pth',
}
def densenet121(pretrained=False, **kwargs):
model = DenseNet(num_init_features = 64, growth_rate = 32, block_config = (6, 12, 24, 16), **kwargs)
if pretrained:
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
我们可以查看网络:
import torchsummary
model = densenet121(pretrained=True).cuda()
x = (3, 224, 224)
torchsummary.summary(model,x)
代码输出:
----------------------------------------------------------------
Layer (type) Output Shape Param #
================================================================
Conv2d-1 [-1, 64, 112, 112] 9,408
BatchNorm2d-2 [-1, 64, 112, 112] 128
ReLU-3 [-1, 64, 112, 112] 0
MaxPool2d-4 [-1, 64, 56, 56] 0
BatchNorm2d-5 [-1, 64, 56, 56] 128
ReLU-6 [-1, 64, 56, 56] 0
Conv2d-7 [-1, 128, 56, 56] 8,192
BatchNorm2d-8 [-1, 128, 56, 56] 256
ReLU-9 [-1, 128, 56, 56] 0
Conv2d-10 [-1, 32, 56, 56] 36,864
BatchNorm2d-11 [-1, 96, 56, 56] 192
ReLU-12 [-1, 96, 56, 56] 0
Conv2d-13 [-1, 128, 56, 56] 12,288
BatchNorm2d-14 [-1, 128, 56, 56] 256
ReLU-15 [-1, 128, 56, 56] 0
Conv2d-16 [-1, 32, 56, 56] 36,864
BatchNorm2d-17 [-1, 128, 56, 56] 256
ReLU-18 [-1, 128, 56, 56] 0
Conv2d-19 [-1, 128, 56, 56] 16,384
BatchNorm2d-20 [-1, 128, 56, 56] 256
ReLU-21 [-1, 128, 56, 56] 0
Conv2d-22 [-1, 32, 56, 56] 36,864
BatchNorm2d-23 [-1, 160, 56, 56] 320
ReLU-24 [-1, 160, 56, 56] 0
Conv2d-25 [-1, 128, 56, 56] 20,480
BatchNorm2d-26 [-1, 128, 56, 56] 256
ReLU-27 [-1, 128, 56, 56] 0
Conv2d-28 [-1, 32, 56, 56] 36,864
BatchNorm2d-29 [-1, 192, 56, 56] 384
ReLU-30 [-1, 192, 56, 56] 0
Conv2d-31 [-1, 128, 56, 56] 24,576
BatchNorm2d-32 [-1, 128, 56, 56] 256
ReLU-33 [-1, 128, 56, 56] 0
Conv2d-34 [-1, 32, 56, 56] 36,864
BatchNorm2d-35 [-1, 224, 56, 56] 448
ReLU-36 [-1, 224, 56, 56] 0
Conv2d-37 [-1, 128, 56, 56] 28,672
BatchNorm2d-38 [-1, 128, 56, 56] 256
ReLU-39 [-1, 128, 56, 56] 0
Conv2d-40 [-1, 32, 56, 56] 36,864
BatchNorm2d-41 [-1, 256, 56, 56] 512
ReLU-42 [-1, 256, 56, 56] 0
Conv2d-43 [-1, 128, 56, 56] 32,768
AvgPool2d-44 [-1, 128, 28, 28] 0
BatchNorm2d-45 [-1, 128, 28, 28] 256
ReLU-46 [-1, 128, 28, 28] 0
Conv2d-47 [-1, 128, 28, 28] 16,384
BatchNorm2d-48 [-1, 128, 28, 28] 256
ReLU-49 [-1, 128, 28, 28] 0
Conv2d-50 [-1, 32, 28, 28] 36,864
BatchNorm2d-51 [-1, 160, 28, 28] 320
ReLU-52 [-1, 160, 28, 28] 0
Conv2d-53 [-1, 128, 28, 28] 20,480
BatchNorm2d-54 [-1, 128, 28, 28] 256
ReLU-55 [-1, 128, 28, 28] 0
Conv2d-56 [-1, 32, 28, 28] 36,864
BatchNorm2d-57 [-1, 192, 28, 28] 384
ReLU-58 [-1, 192, 28, 28] 0
Conv2d-59 [-1, 128, 28, 28] 24,576
BatchNorm2d-60 [-1, 128, 28, 28] 256
ReLU-61 [-1, 128, 28, 28] 0
Conv2d-62 [-1, 32, 28, 28] 36,864
BatchNorm2d-63 [-1, 224, 28, 28] 448
ReLU-64 [-1, 224, 28, 28] 0
Conv2d-65 [-1, 128, 28, 28] 28,672
BatchNorm2d-66 [-1, 128, 28, 28] 256
ReLU-67 [-1, 128, 28, 28] 0
Conv2d-68 [-1, 32, 28, 28] 36,864
BatchNorm2d-69 [-1, 256, 28, 28] 512
ReLU-70 [-1, 256, 28, 28] 0
Conv2d-71 [-1, 128, 28, 28] 32,768
BatchNorm2d-72 [-1, 128, 28, 28] 256
ReLU-73 [-1, 128, 28, 28] 0
Conv2d-74 [-1, 32, 28, 28] 36,864
BatchNorm2d-75 [-1, 288, 28, 28] 576
ReLU-76 [-1, 288, 28, 28] 0
Conv2d-77 [-1, 128, 28, 28] 36,864
BatchNorm2d-78 [-1, 128, 28, 28] 256
ReLU-79 [-1, 128, 28, 28] 0
Conv2d-80 [-1, 32, 28, 28] 36,864
BatchNorm2d-81 [-1, 320, 28, 28] 640
ReLU-82 [-1, 320, 28, 28] 0
Conv2d-83 [-1, 128, 28, 28] 40,960
BatchNorm2d-84 [-1, 128, 28, 28] 256
ReLU-85 [-1, 128, 28, 28] 0
Conv2d-86 [-1, 32, 28, 28] 36,864
BatchNorm2d-87 [-1, 352, 28, 28] 704
ReLU-88 [-1, 352, 28, 28] 0
Conv2d-89 [-1, 128, 28, 28] 45,056
BatchNorm2d-90 [-1, 128, 28, 28] 256
ReLU-91 [-1, 128, 28, 28] 0
Conv2d-92 [-1, 32, 28, 28] 36,864
BatchNorm2d-93 [-1, 384, 28, 28] 768
ReLU-94 [-1, 384, 28, 28] 0
Conv2d-95 [-1, 128, 28, 28] 49,152
BatchNorm2d-96 [-1, 128, 28, 28] 256
ReLU-97 [-1, 128, 28, 28] 0
Conv2d-98 [-1, 32, 28, 28] 36,864
BatchNorm2d-99 [-1, 416, 28, 28] 832
ReLU-100 [-1, 416, 28, 28] 0
Conv2d-101 [-1, 128, 28, 28] 53,248
BatchNorm2d-102 [-1, 128, 28, 28] 256
ReLU-103 [-1, 128, 28, 28] 0
Conv2d-104 [-1, 32, 28, 28] 36,864
BatchNorm2d-105 [-1, 448, 28, 28] 896
ReLU-106 [-1, 448, 28, 28] 0
Conv2d-107 [-1, 128, 28, 28] 57,344
BatchNorm2d-108 [-1, 128, 28, 28] 256
ReLU-109 [-1, 128, 28, 28] 0
Conv2d-110 [-1, 32, 28, 28] 36,864
BatchNorm2d-111 [-1, 480, 28, 28] 960
ReLU-112 [-1, 480, 28, 28] 0
Conv2d-113 [-1, 128, 28, 28] 61,440
BatchNorm2d-114 [-1, 128, 28, 28] 256
ReLU-115 [-1, 128, 28, 28] 0
Conv2d-116 [-1, 32, 28, 28] 36,864
BatchNorm2d-117 [-1, 512, 28, 28] 1,024
ReLU-118 [-1, 512, 28, 28] 0
Conv2d-119 [-1, 256, 28, 28] 131,072
AvgPool2d-120 [-1, 256, 14, 14] 0
BatchNorm2d-121 [-1, 256, 14, 14] 512
ReLU-122 [-1, 256, 14, 14] 0
Conv2d-123 [-1, 128, 14, 14] 32,768
BatchNorm2d-124 [-1, 128, 14, 14] 256
ReLU-125 [-1, 128, 14, 14] 0
Conv2d-126 [-1, 32, 14, 14] 36,864
BatchNorm2d-127 [-1, 288, 14, 14] 576
ReLU-128 [-1, 288, 14, 14] 0
Conv2d-129 [-1, 128, 14, 14] 36,864
BatchNorm2d-130 [-1, 128, 14, 14] 256
ReLU-131 [-1, 128, 14, 14] 0
Conv2d-132 [-1, 32, 14, 14] 36,864
BatchNorm2d-133 [-1, 320, 14, 14] 640
ReLU-134 [-1, 320, 14, 14] 0
Conv2d-135 [-1, 128, 14, 14] 40,960
BatchNorm2d-136 [-1, 128, 14, 14] 256
ReLU-137 [-1, 128, 14, 14] 0
Conv2d-138 [-1, 32, 14, 14] 36,864
BatchNorm2d-139 [-1, 352, 14, 14] 704
ReLU-140 [-1, 352, 14, 14] 0
Conv2d-141 [-1, 128, 14, 14] 45,056
BatchNorm2d-142 [-1, 128, 14, 14] 256
ReLU-143 [-1, 128, 14, 14] 0
Conv2d-144 [-1, 32, 14, 14] 36,864
BatchNorm2d-145 [-1, 384, 14, 14] 768
ReLU-146 [-1, 384, 14, 14] 0
Conv2d-147 [-1, 128, 14, 14] 49,152
BatchNorm2d-148 [-1, 128, 14, 14] 256
ReLU-149 [-1, 128, 14, 14] 0
Conv2d-150 [-1, 32, 14, 14] 36,864
BatchNorm2d-151 [-1, 416, 14, 14] 832
ReLU-152 [-1, 416, 14, 14] 0
Conv2d-153 [-1, 128, 14, 14] 53,248
BatchNorm2d-154 [-1, 128, 14, 14] 256
ReLU-155 [-1, 128, 14, 14] 0
Conv2d-156 [-1, 32, 14, 14] 36,864
BatchNorm2d-157 [-1, 448, 14, 14] 896
ReLU-158 [-1, 448, 14, 14] 0
Conv2d-159 [-1, 128, 14, 14] 57,344
BatchNorm2d-160 [-1, 128, 14, 14] 256
ReLU-161 [-1, 128, 14, 14] 0
Conv2d-162 [-1, 32, 14, 14] 36,864
BatchNorm2d-163 [-1, 480, 14, 14] 960
ReLU-164 [-1, 480, 14, 14] 0
Conv2d-165 [-1, 128, 14, 14] 61,440
BatchNorm2d-166 [-1, 128, 14, 14] 256
ReLU-167 [-1, 128, 14, 14] 0
Conv2d-168 [-1, 32, 14, 14] 36,864
BatchNorm2d-169 [-1, 512, 14, 14] 1,024
ReLU-170 [-1, 512, 14, 14] 0
Conv2d-171 [-1, 128, 14, 14] 65,536
BatchNorm2d-172 [-1, 128, 14, 14] 256
ReLU-173 [-1, 128, 14, 14] 0
Conv2d-174 [-1, 32, 14, 14] 36,864
BatchNorm2d-175 [-1, 544, 14, 14] 1,088
ReLU-176 [-1, 544, 14, 14] 0
Conv2d-177 [-1, 128, 14, 14] 69,632
BatchNorm2d-178 [-1, 128, 14, 14] 256
ReLU-179 [-1, 128, 14, 14] 0
Conv2d-180 [-1, 32, 14, 14] 36,864
BatchNorm2d-181 [-1, 576, 14, 14] 1,152
ReLU-182 [-1, 576, 14, 14] 0
Conv2d-183 [-1, 128, 14, 14] 73,728
BatchNorm2d-184 [-1, 128, 14, 14] 256
ReLU-185 [-1, 128, 14, 14] 0
Conv2d-186 [-1, 32, 14, 14] 36,864
BatchNorm2d-187 [-1, 608, 14, 14] 1,216
ReLU-188 [-1, 608, 14, 14] 0
Conv2d-189 [-1, 128, 14, 14] 77,824
BatchNorm2d-190 [-1, 128, 14, 14] 256
ReLU-191 [-1, 128, 14, 14] 0
Conv2d-192 [-1, 32, 14, 14] 36,864
BatchNorm2d-193 [-1, 640, 14, 14] 1,280
ReLU-194 [-1, 640, 14, 14] 0
Conv2d-195 [-1, 128, 14, 14] 81,920
BatchNorm2d-196 [-1, 128, 14, 14] 256
ReLU-197 [-1, 128, 14, 14] 0
Conv2d-198 [-1, 32, 14, 14] 36,864
BatchNorm2d-199 [-1, 672, 14, 14] 1,344
ReLU-200 [-1, 672, 14, 14] 0
Conv2d-201 [-1, 128, 14, 14] 86,016
BatchNorm2d-202 [-1, 128, 14, 14] 256
ReLU-203 [-1, 128, 14, 14] 0
Conv2d-204 [-1, 32, 14, 14] 36,864
BatchNorm2d-205 [-1, 704, 14, 14] 1,408
ReLU-206 [-1, 704, 14, 14] 0
Conv2d-207 [-1, 128, 14, 14] 90,112
BatchNorm2d-208 [-1, 128, 14, 14] 256
ReLU-209 [-1, 128, 14, 14] 0
Conv2d-210 [-1, 32, 14, 14] 36,864
BatchNorm2d-211 [-1, 736, 14, 14] 1,472
ReLU-212 [-1, 736, 14, 14] 0
Conv2d-213 [-1, 128, 14, 14] 94,208
BatchNorm2d-214 [-1, 128, 14, 14] 256
ReLU-215 [-1, 128, 14, 14] 0
Conv2d-216 [-1, 32, 14, 14] 36,864
BatchNorm2d-217 [-1, 768, 14, 14] 1,536
ReLU-218 [-1, 768, 14, 14] 0
Conv2d-219 [-1, 128, 14, 14] 98,304
BatchNorm2d-220 [-1, 128, 14, 14] 256
ReLU-221 [-1, 128, 14, 14] 0
Conv2d-222 [-1, 32, 14, 14] 36,864
BatchNorm2d-223 [-1, 800, 14, 14] 1,600
ReLU-224 [-1, 800, 14, 14] 0
Conv2d-225 [-1, 128, 14, 14] 102,400
BatchNorm2d-226 [-1, 128, 14, 14] 256
ReLU-227 [-1, 128, 14, 14] 0
Conv2d-228 [-1, 32, 14, 14] 36,864
BatchNorm2d-229 [-1, 832, 14, 14] 1,664
ReLU-230 [-1, 832, 14, 14] 0
Conv2d-231 [-1, 128, 14, 14] 106,496
BatchNorm2d-232 [-1, 128, 14, 14] 256
ReLU-233 [-1, 128, 14, 14] 0
Conv2d-234 [-1, 32, 14, 14] 36,864
BatchNorm2d-235 [-1, 864, 14, 14] 1,728
ReLU-236 [-1, 864, 14, 14] 0
Conv2d-237 [-1, 128, 14, 14] 110,592
BatchNorm2d-238 [-1, 128, 14, 14] 256
ReLU-239 [-1, 128, 14, 14] 0
Conv2d-240 [-1, 32, 14, 14] 36,864
BatchNorm2d-241 [-1, 896, 14, 14] 1,792
ReLU-242 [-1, 896, 14, 14] 0
Conv2d-243 [-1, 128, 14, 14] 114,688
BatchNorm2d-244 [-1, 128, 14, 14] 256
ReLU-245 [-1, 128, 14, 14] 0
Conv2d-246 [-1, 32, 14, 14] 36,864
BatchNorm2d-247 [-1, 928, 14, 14] 1,856
ReLU-248 [-1, 928, 14, 14] 0
Conv2d-249 [-1, 128, 14, 14] 118,784
BatchNorm2d-250 [-1, 128, 14, 14] 256
ReLU-251 [-1, 128, 14, 14] 0
Conv2d-252 [-1, 32, 14, 14] 36,864
BatchNorm2d-253 [-1, 960, 14, 14] 1,920
ReLU-254 [-1, 960, 14, 14] 0
Conv2d-255 [-1, 128, 14, 14] 122,880
BatchNorm2d-256 [-1, 128, 14, 14] 256
ReLU-257 [-1, 128, 14, 14] 0
Conv2d-258 [-1, 32, 14, 14] 36,864
BatchNorm2d-259 [-1, 992, 14, 14] 1,984
ReLU-260 [-1, 992, 14, 14] 0
Conv2d-261 [-1, 128, 14, 14] 126,976
BatchNorm2d-262 [-1, 128, 14, 14] 256
ReLU-263 [-1, 128, 14, 14] 0
Conv2d-264 [-1, 32, 14, 14] 36,864
BatchNorm2d-265 [-1, 1024, 14, 14] 2,048
ReLU-266 [-1, 1024, 14, 14] 0
Conv2d-267 [-1, 512, 14, 14] 524,288
AvgPool2d-268 [-1, 512, 7, 7] 0
BatchNorm2d-269 [-1, 512, 7, 7] 1,024
ReLU-270 [-1, 512, 7, 7] 0
Conv2d-271 [-1, 128, 7, 7] 65,536
BatchNorm2d-272 [-1, 128, 7, 7] 256
ReLU-273 [-1, 128, 7, 7] 0
Conv2d-274 [-1, 32, 7, 7] 36,864
BatchNorm2d-275 [-1, 544, 7, 7] 1,088
ReLU-276 [-1, 544, 7, 7] 0
Conv2d-277 [-1, 128, 7, 7] 69,632
BatchNorm2d-278 [-1, 128, 7, 7] 256
ReLU-279 [-1, 128, 7, 7] 0
Conv2d-280 [-1, 32, 7, 7] 36,864
BatchNorm2d-281 [-1, 576, 7, 7] 1,152
ReLU-282 [-1, 576, 7, 7] 0
Conv2d-283 [-1, 128, 7, 7] 73,728
BatchNorm2d-284 [-1, 128, 7, 7] 256
ReLU-285 [-1, 128, 7, 7] 0
Conv2d-286 [-1, 32, 7, 7] 36,864
BatchNorm2d-287 [-1, 608, 7, 7] 1,216
ReLU-288 [-1, 608, 7, 7] 0
Conv2d-289 [-1, 128, 7, 7] 77,824
BatchNorm2d-290 [-1, 128, 7, 7] 256
ReLU-291 [-1, 128, 7, 7] 0
Conv2d-292 [-1, 32, 7, 7] 36,864
BatchNorm2d-293 [-1, 640, 7, 7] 1,280
ReLU-294 [-1, 640, 7, 7] 0
Conv2d-295 [-1, 128, 7, 7] 81,920
BatchNorm2d-296 [-1, 128, 7, 7] 256
ReLU-297 [-1, 128, 7, 7] 0
Conv2d-298 [-1, 32, 7, 7] 36,864
BatchNorm2d-299 [-1, 672, 7, 7] 1,344
ReLU-300 [-1, 672, 7, 7] 0
Conv2d-301 [-1, 128, 7, 7] 86,016
BatchNorm2d-302 [-1, 128, 7, 7] 256
ReLU-303 [-1, 128, 7, 7] 0
Conv2d-304 [-1, 32, 7, 7] 36,864
BatchNorm2d-305 [-1, 704, 7, 7] 1,408
ReLU-306 [-1, 704, 7, 7] 0
Conv2d-307 [-1, 128, 7, 7] 90,112
BatchNorm2d-308 [-1, 128, 7, 7] 256
ReLU-309 [-1, 128, 7, 7] 0
Conv2d-310 [-1, 32, 7, 7] 36,864
BatchNorm2d-311 [-1, 736, 7, 7] 1,472
ReLU-312 [-1, 736, 7, 7] 0
Conv2d-313 [-1, 128, 7, 7] 94,208
BatchNorm2d-314 [-1, 128, 7, 7] 256
ReLU-315 [-1, 128, 7, 7] 0
Conv2d-316 [-1, 32, 7, 7] 36,864
BatchNorm2d-317 [-1, 768, 7, 7] 1,536
ReLU-318 [-1, 768, 7, 7] 0
Conv2d-319 [-1, 128, 7, 7] 98,304
BatchNorm2d-320 [-1, 128, 7, 7] 256
ReLU-321 [-1, 128, 7, 7] 0
Conv2d-322 [-1, 32, 7, 7] 36,864
BatchNorm2d-323 [-1, 800, 7, 7] 1,600
ReLU-324 [-1, 800, 7, 7] 0
Conv2d-325 [-1, 128, 7, 7] 102,400
BatchNorm2d-326 [-1, 128, 7, 7] 256
ReLU-327 [-1, 128, 7, 7] 0
Conv2d-328 [-1, 32, 7, 7] 36,864
BatchNorm2d-329 [-1, 832, 7, 7] 1,664
ReLU-330 [-1, 832, 7, 7] 0
Conv2d-331 [-1, 128, 7, 7] 106,496
BatchNorm2d-332 [-1, 128, 7, 7] 256
ReLU-333 [-1, 128, 7, 7] 0
Conv2d-334 [-1, 32, 7, 7] 36,864
BatchNorm2d-335 [-1, 864, 7, 7] 1,728
ReLU-336 [-1, 864, 7, 7] 0
Conv2d-337 [-1, 128, 7, 7] 110,592
BatchNorm2d-338 [-1, 128, 7, 7] 256
ReLU-339 [-1, 128, 7, 7] 0
Conv2d-340 [-1, 32, 7, 7] 36,864
BatchNorm2d-341 [-1, 896, 7, 7] 1,792
ReLU-342 [-1, 896, 7, 7] 0
Conv2d-343 [-1, 128, 7, 7] 114,688
BatchNorm2d-344 [-1, 128, 7, 7] 256
ReLU-345 [-1, 128, 7, 7] 0
Conv2d-346 [-1, 32, 7, 7] 36,864
BatchNorm2d-347 [-1, 928, 7, 7] 1,856
ReLU-348 [-1, 928, 7, 7] 0
Conv2d-349 [-1, 128, 7, 7] 118,784
BatchNorm2d-350 [-1, 128, 7, 7] 256
ReLU-351 [-1, 128, 7, 7] 0
Conv2d-352 [-1, 32, 7, 7] 36,864
BatchNorm2d-353 [-1, 960, 7, 7] 1,920
ReLU-354 [-1, 960, 7, 7] 0
Conv2d-355 [-1, 128, 7, 7] 122,880
BatchNorm2d-356 [-1, 128, 7, 7] 256
ReLU-357 [-1, 128, 7, 7] 0
Conv2d-358 [-1, 32, 7, 7] 36,864
BatchNorm2d-359 [-1, 992, 7, 7] 1,984
ReLU-360 [-1, 992, 7, 7] 0
Conv2d-361 [-1, 128, 7, 7] 126,976
BatchNorm2d-362 [-1, 128, 7, 7] 256
ReLU-363 [-1, 128, 7, 7] 0
Conv2d-364 [-1, 32, 7, 7] 36,864
BatchNorm2d-365 [-1, 1024, 7, 7] 2,048
ReLU-366 [-1, 1024, 7, 7] 0
Linear-367 [-1, 1000] 1,025,000
================================================================
Total params: 7,978,856
Trainable params: 7,978,856
Non-trainable params: 0
----------------------------------------------------------------
Input size (MB): 0.57
Forward/backward pass size (MB): 294.58
Params size (MB): 30.44
Estimated Total Size (MB): 325.59
----------------------------------------------------------------
剩下的和之前一样我们用于鸟类分类问题:
import os, PIL, pathlib
from torchvision import transforms, datasets
data_dir = './data/第十一周/bird_photos/'
data_dir = pathlib.Path(data_dir)
data_paths = list(data_dir.glob('*'))
classNames = [path.name for path in data_paths]
classNames
代码输出:
['Bananaquit', 'Black Skimmer', 'Black Throated Bushtiti', 'Cockatoo']
接下来是transform:
train_transforms = transforms.Compose([
transforms.RandomResizedCrop(224), # 随机裁剪到 224x224
transforms.RandomHorizontalFlip(), # 随机水平翻转
transforms.ToTensor(), # 转为 Tensor
transforms.Normalize(
mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225]) # 标准化
])
total_data = datasets.ImageFolder('./data/第十一周/bird_photos/', transform = train_transforms)
total_data
代码输出:
Dataset ImageFolder
Number of datapoints: 565
Root location: ./data/第十一周/bird_photos/
StandardTransform
Transform: Compose(
RandomResizedCrop(size=(224, 224), scale=(0.08, 1.0), ratio=(0.75, 1.3333), interpolation=bilinear, antialias=True)
RandomHorizontalFlip(p=0.5)
ToTensor()
Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
device
代码输出:
device(type='cuda')
接下来划分数据集:
train_size = int(0.7 * len(total_data)) #训练集的大小为总数据的70%
remain_size = len(total_data) - train_size #剩下的为测试集
train_dataset, remain_dataset = torch.utils.data.random_split(total_data, [train_size, remain_size]) #随机分配数据
test_size = int(0.6 * len(remain_dataset))
validate_size = len(remain_dataset) - test_size
test_dataset, validate_dataset = torch.utils.data.random_split(remain_dataset, [test_size, validate_size]) #随机分配数据
train_dataset, test_dataset, validate_dataset
代码输出:
(<torch.utils.data.dataset.Subset at 0x1aaa5696390>,
<torch.utils.data.dataset.Subset at 0x1aaa5694380>,
<torch.utils.data.dataset.Subset at 0x1aaa5696840>)
batch_size = 16
train_dl = torch.utils.data.DataLoader(
train_dataset,
batch_size = batch_size,
shuffle = True,
num_workers = 4
)
test_dl = torch.utils.data.DataLoader(
test_dataset,
batch_size = batch_size,
shuffle = False,
num_workers = 4
)
validate_dl = torch.utils.data.DataLoader(
validate_dataset,
batch_size = batch_size,
shuffle = False,
num_workers = 4
)
for x, y in test_dl:
print('shape of x[N, C, H, W]:', x.shape)
print('shape of y:',y.shape)
break
代码输出:
shape of x[N, C, H, W]: torch.Size([16, 3, 224, 224])
shape of y: torch.Size([16])
查看数据情况:
import numpy as np
import matplotlib.pyplot as plt
# 定义反归一化函数
def unnormalize(img, mean, std):
mean = np.array(mean)
std = np.array(std)
img = img * std + mean # 反归一化
return np.clip(img, 0, 1) # 限制值范围到 [0, 1]
plt.figure(figsize=(10, 5))
mean = [0.485, 0.456, 0.406]
std = [0.229, 0.224, 0.225]
for images, labels in test_dl: # 从 DataLoader 中获取一个批次
for i in range(8): # 显示前 8 张图片
ax = plt.subplot(2, 4, i + 1) # 创建 2 行 4 列的子图
# 反归一化并转换为 (H, W, C)
img = images[i].permute(1, 2, 0).numpy() # (C, H, W) -> (H, W, C)
img = unnormalize(img, mean, std) # 反归一化
# 显示图像
plt.imshow(img) # 显示图像,值范围应为 [0, 1]
plt.title(classNames[labels[i].item()]) # 使用类别名称作为标题
plt.axis("off") # 关闭坐标轴
break # 仅显示第一个批次
代码输出:

接下来正式训练:
def train(dataloader, model, loss_fn, optimizer):
size = len(dataloader.dataset)
num_batches = len(dataloader)
train_loss, train_acc = 0, 0
for x, y in dataloader:
x, y = x.to(device), y.to(device)
pred = model(x)
loss = loss_fn(pred, y)
#backward
optimizer.zero_grad()
loss.backward()
optimizer.step()
train_acc += (pred.argmax(1) == y).type(torch.float).sum().item()
train_loss += loss.item()
train_acc /= size
train_loss /= num_batches
return train_acc, train_loss
def test(dataloader, model, loss_fn):
size = len(dataloader.dataset)
num_batches = len(dataloader)
test_loss, test_acc = 0, 0
for x, y in dataloader:
x, y = x.to(device), y.to(device)
pred = model(x)
loss = loss_fn(pred, y)
test_acc += (pred.argmax(1) == y).type(torch.float).sum().item()
test_loss += loss.item()
test_acc /= size
test_loss /= num_batches
return test_acc, test_loss
#%%
import copy
from torch.optim.lr_scheduler import ReduceLROnPlateau
opt = torch.optim.Adam(model.parameters(), lr= 1e-4)
scheduler = ReduceLROnPlateau(opt, mode='min', factor=0.1, patience=5, verbose=True) # 当指标(如损失)连续 5 次没有改善时,将学习率乘以 0.1
loss_fn = nn.CrossEntropyLoss() # 交叉熵
epochs = 32
train_loss = []
train_acc = []
test_loss = []
test_acc = []
best_acc = 0 # 设置一个最佳准确率,作为最佳模型的判别指标
for epoch in range(epochs):
model.train()
epoch_train_acc, epoch_train_loss = train(train_dl, model, loss_fn, opt)
model.eval()
epoch_test_acc, epoch_test_loss = test(test_dl, model, loss_fn)
scheduler.step(epoch_test_loss)
if epoch_test_acc > best_acc:
best_acc = epoch_test_acc
best_model = copy.deepcopy(model)
train_acc.append(epoch_train_acc)
train_loss.append(epoch_train_loss)
test_acc.append(epoch_test_acc)
test_loss.append(epoch_test_loss)
# 获取当前的学习率
lr = opt.state_dict()['param_groups'][0]['lr']
template = ('Epoch:{:2d}, Train_acc:{:.1f}%, Train_loss:{:.3f}, Test_acc:{:.1f}%, Test_loss:{:.3f}, Lr:{:.2E}')
print(template.format(epoch+1, epoch_train_acc*100, epoch_train_loss,
epoch_test_acc*100, epoch_test_loss, lr))
# 保存最佳模型到文件中
PATH = './best_model.pth' # 保存的参数文件名
torch.save(best_model.state_dict(), PATH)
print('Done')
代码输出:
Epoch: 1, Train_acc:98.2%, Train_loss:0.072, Test_acc:99.0%, Test_loss:0.114, Lr:1.00E-04
Epoch: 2, Train_acc:98.2%, Train_loss:0.050, Test_acc:99.0%, Test_loss:0.026, Lr:1.00E-04
Epoch: 3, Train_acc:98.7%, Train_loss:0.038, Test_acc:99.0%, Test_loss:0.019, Lr:1.00E-04
Epoch: 4, Train_acc:98.0%, Train_loss:0.087, Test_acc:98.0%, Test_loss:0.068, Lr:1.00E-04
Epoch: 5, Train_acc:96.7%, Train_loss:0.078, Test_acc:100.0%, Test_loss:0.014, Lr:1.00E-04
Epoch: 6, Train_acc:99.0%, Train_loss:0.022, Test_acc:98.0%, Test_loss:0.022, Lr:1.00E-04
Epoch: 7, Train_acc:99.0%, Train_loss:0.030, Test_acc:100.0%, Test_loss:0.003, Lr:1.00E-04
Epoch: 8, Train_acc:99.0%, Train_loss:0.031, Test_acc:98.0%, Test_loss:0.034, Lr:1.00E-04
Epoch: 9, Train_acc:98.2%, Train_loss:0.043, Test_acc:99.0%, Test_loss:0.033, Lr:1.00E-04
Epoch:10, Train_acc:99.0%, Train_loss:0.033, Test_acc:97.1%, Test_loss:0.065, Lr:1.00E-04
Epoch:11, Train_acc:99.2%, Train_loss:0.040, Test_acc:98.0%, Test_loss:0.038, Lr:1.00E-04
Epoch:12, Train_acc:97.7%, Train_loss:0.064, Test_acc:97.1%, Test_loss:0.107, Lr:1.00E-04
Epoch:13, Train_acc:98.2%, Train_loss:0.055, Test_acc:100.0%, Test_loss:0.018, Lr:1.00E-05
Epoch:14, Train_acc:98.7%, Train_loss:0.029, Test_acc:99.0%, Test_loss:0.012, Lr:1.00E-05
Epoch:15, Train_acc:97.5%, Train_loss:0.074, Test_acc:99.0%, Test_loss:0.017, Lr:1.00E-05
Epoch:16, Train_acc:99.7%, Train_loss:0.010, Test_acc:99.0%, Test_loss:0.023, Lr:1.00E-05
Epoch:17, Train_acc:98.2%, Train_loss:0.042, Test_acc:99.0%, Test_loss:0.010, Lr:1.00E-05
Epoch:18, Train_acc:99.2%, Train_loss:0.023, Test_acc:96.1%, Test_loss:0.042, Lr:1.00E-05
Epoch:19, Train_acc:99.2%, Train_loss:0.028, Test_acc:99.0%, Test_loss:0.028, Lr:1.00E-06
Epoch:20, Train_acc:98.7%, Train_loss:0.025, Test_acc:100.0%, Test_loss:0.011, Lr:1.00E-06
Epoch:21, Train_acc:99.0%, Train_loss:0.020, Test_acc:97.1%, Test_loss:0.066, Lr:1.00E-06
Epoch:22, Train_acc:99.5%, Train_loss:0.021, Test_acc:98.0%, Test_loss:0.094, Lr:1.00E-06
Epoch:23, Train_acc:99.2%, Train_loss:0.028, Test_acc:100.0%, Test_loss:0.004, Lr:1.00E-06
Epoch:24, Train_acc:98.7%, Train_loss:0.031, Test_acc:98.0%, Test_loss:0.031, Lr:1.00E-06
Epoch:25, Train_acc:99.2%, Train_loss:0.025, Test_acc:95.1%, Test_loss:0.079, Lr:1.00E-07
Epoch:26, Train_acc:99.7%, Train_loss:0.012, Test_acc:98.0%, Test_loss:0.023, Lr:1.00E-07
Epoch:27, Train_acc:99.5%, Train_loss:0.021, Test_acc:98.0%, Test_loss:0.050, Lr:1.00E-07
Epoch:28, Train_acc:99.7%, Train_loss:0.021, Test_acc:99.0%, Test_loss:0.011, Lr:1.00E-07
Epoch:29, Train_acc:99.5%, Train_loss:0.019, Test_acc:99.0%, Test_loss:0.015, Lr:1.00E-07
Epoch:30, Train_acc:99.0%, Train_loss:0.034, Test_acc:99.0%, Test_loss:0.024, Lr:1.00E-07
Epoch:31, Train_acc:99.7%, Train_loss:0.012, Test_acc:99.0%, Test_loss:0.014, Lr:1.00E-08
Epoch:32, Train_acc:99.5%, Train_loss:0.018, Test_acc:99.0%, Test_loss:0.032, Lr:1.00E-08
Done
数据可视化:
epochs_range = range(epochs)
plt.figure(figsize=(12, 3))
plt.subplot(1, 2, 1)
plt.plot(epochs_range, train_acc, label='Training Accuracy')
plt.plot(epochs_range, test_acc, label='Test Accuracy')
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')
plt.subplot(1, 2, 2)
plt.plot(epochs_range, train_loss, label='Training Loss')
plt.plot(epochs_range, test_loss, label='Test Loss')
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')
plt.show()
代码输出:

对验证集的准确性预测:
plt.figure(figsize=(10, 5))
# 遍历验证数据集,取第一个批次
for images, labels in test_dl:
for i in range(8): # 只显示前 8 张图片
ax = plt.subplot(2, 4, i + 1)
# 显示图片
img = images[i].permute(1, 2, 0).numpy() # 转换为 (H, W, C)
img = unnormalize(img, mean, std) # 反归一化
plt.imshow(img) # 显示图像,值范围为 [0, 1]
# 增加一个维度用于模型预测
img_tensor = images[i].unsqueeze(0).to("cuda" if torch.cuda.is_available() else "cpu")
# 使用模型预测类别
best_model.eval() # 切换到评估模式
with torch.no_grad(): # 禁用梯度计算
predictions = best_model(img_tensor) # 预测结果
predicted_class_index = predictions.argmax(dim=1).item() # 获取预测类别索引
predicted_class = classNames[predicted_class_index] # 获取预测类别名称
# 获取真实类别名称
true_class = classNames[labels[i].item()]
# 设置标题为真实类别和预测类别
plt.title(f"T: {true_class}\nP: {predicted_class}")
plt.axis("off") # 隐藏坐标轴
# 打印真实类别和预测类别
print(f"Image {i+1}: True Label = {true_class}, Predicted Label = {predicted_class}")
break # 只处理第一个批次
代码输出:
Image 1: True Label = Black Skimmer, Predicted Label = Black Skimmer
Image 2: True Label = Cockatoo, Predicted Label = Cockatoo
Image 3: True Label = Black Skimmer, Predicted Label = Black Skimmer
Image 4: True Label = Cockatoo, Predicted Label = Cockatoo
Image 5: True Label = Black Skimmer, Predicted Label = Black Skimmer
Image 6: True Label = Cockatoo, Predicted Label = Cockatoo
Image 7: True Label = Bananaquit, Predicted Label = Bananaquit
Image 8: True Label = Bananaquit, Predicted Label = Bananaquit

验证集的总体正确率:
def validate(dataloader, model):
model.eval()
size = len(dataloader.dataset)
num_batches = len(dataloader)
validate_acc = 0
for x, y in dataloader:
x, y = x.to(device), y.to(device)
pred = model(x)
validate_acc += (pred.argmax(1) == y).type(torch.float).sum().item()
validate_acc /= size
return validate_acc
# 计算验证集准确率
validate_acc = validate(validate_dl, best_model)
print(f"Validation Accuracy: {validate_acc:.2%}")
代码输出:
Validation Accuracy: 92.65%
验证集的准确性达到92.65%
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐

所有评论(0)