原理与四框架实现指南)
人工智能深度学习机器学习教程【免费下载链接】d2l-zh《动手学深度学习》面向中文读者、能运行、可讨论。中英文版被70多个国家的500多所大学用于教学。项目地址https://gitcode.com/GitHub_Trending/d2/d2l-zh点击查看免费下载本篇技术指南以《动手学深度学习》d2l-zh仓库的 densenet.md 为骨架系统讲解稠密连接网络DenseNet从数学动机到工程实现的全过程从 ResNet 的恒等映射思维出发推导 DenseNet 的通道维连结思想并给出 MXNet、PyTorch、TensorFlow、Paddle 四个框架下稠密块、过渡层与完整 DenseNet 模型的逐行可运行代码最后以 Fashion-MNIST 数据集上的训练实验验证网络行为。读完本文你将掌握 DenseNet 的核心组件设计、增长率与通道数演算规则以及如何用仓库提供的d2l工具包快速训练验证。从 ResNet 到 DenseNet为什么要连结而不是相加回顾任意函数的泰勒展开式Taylor expansion在 $x$ 接近 0 时它把函数分解成越来越高阶的项$$f(x) f(0) f(0) x \frac{f(0)}{2!} x^2 \frac{f(0)}{3!} x^3 \ldots.$$同样的思想也被 ResNet 继承——ResNet 将函数展开为$$f(\mathbf{x}) \mathbf{x} g(\mathbf{x}).$$也就是说ResNet 把 $f$ 分解为两部分一个简单的线性项 $\mathbf{x}$ 和一个复杂的非线性项 $g(\mathbf{x})$其具体推导见仓库的 残差网络ResNet 一节。DenseNet稠密连接网络:cite:Huang.Liu.Van-Der-Maaten.ea.2017在某种程度上是 ResNet 的逻辑扩展如果我们不满足于只把 $f$ 展开成两项而是想捕捉超过两项的信息呢一种方案便是 DenseNet。如上图所示ResNet 与 DenseNet 的关键区别在于跨层连接的运算方式DenseNet 的输出是连接concatenation图中用 $[,]$ 表示而不是 ResNet 的简单相加。因此在应用越来越复杂的函数序列后我们执行从 $\mathbf{x}$ 到其展开式的映射$$\mathbf{x} \to \left[ \mathbf{x}, f_1(\mathbf{x}), f_2([\mathbf{x}, f_1(\mathbf{x})]), f_3([\mathbf{x}, f_1(\mathbf{x}), f_2([\mathbf{x}, f_1(\mathbf{x})])]), \ldots\right].$$最后将这些展开式结合到多层感知机中再次减少特征的数量。实现起来非常简单不需要添加术语而是将它们连接起来。DenseNet 的名字源于变量之间的稠密连接——最后一层与之前的所有层紧密相连依赖图变得相当稠密稠密连接如图示稠密网络主要由两部分构成稠密块dense block定义如何连接输入和输出过渡层transition layer控制通道数量使其不会太复杂。稠密块Dense Block基础卷积块BN-ReLU-Conv 架构DenseNet 使用了 ResNet 改良版的批量规范化、激活和卷积BatchNorm-ReLU-Conv架构参见 ResNet 练习。四个框架下的实现如下from d2l import mxnet as d2l from mxnet import np, npx from mxnet.gluon import nn npx.set_np() def conv_block(num_channels): blk nn.Sequential() blk.add(nn.BatchNorm(), nn.Activation(relu), nn.Conv2D(num_channels, kernel_size3, padding1)) return blk#tab pytorch from d2l import torch as d2l import torch from torch import nn def conv_block(input_channels, num_channels): return nn.Sequential( nn.BatchNorm2d(input_channels), nn.ReLU(), nn.Conv2d(input_channels, num_channels, kernel_size3, padding1))#tab tensorflow from d2l import tensorflow as d2l import tensorflow as tf class ConvBlock(tf.keras.layers.Layer): def __init__(self, num_channels): super(ConvBlock, self).__init__() self.bn tf.keras.layers.BatchNormalization() self.relu tf.keras.layers.ReLU() self.conv tf.keras.layers.Conv2D( filtersnum_channels, kernel_size(3, 3), paddingsame) self.listLayers [self.bn, self.relu, self.conv] def call(self, x): y x for layer in self.listLayers.layers: y layer(y) y tf.keras.layers.concatenate([x,y], axis-1) return y#tab paddle from d2l import paddle as d2l import warnings warnings.filterwarnings(ignore) import paddle import paddle.nn as nn def conv_block(input_channels, num_channels): return nn.Sequential( nn.BatchNorm2D(input_channels), nn.ReLU(), nn.Conv2D(input_channels, num_channels, kernel_size3, padding1))注意 TensorFlow 版本的ConvBlock在call中已经直接完成了concatenate([x, y], axis-1)的连结操作这是因为 TensorFlow 的通道维在最后一维channels-last 布局与 MXNet/PyTorch/Paddle 的axis1channels-first 布局相对应。稠密块逐块连结输入与输出一个稠密块由多个卷积块组成每个卷积块使用相同数量的输出通道。在前向传播中我们将每个卷积块的输入和输出在通道维上连结concatenateclass DenseBlock(nn.Block): def __init__(self, num_convs, num_channels, **kwargs): super().__init__(**kwargs) self.net nn.Sequential() for _ in range(num_convs): self.net.add(conv_block(num_channels)) def forward(self, X): for blk in self.net: Y blk(X) # 连接通道维度上每个块的输入和输出 X np.concatenate((X, Y), axis1) return X#tab pytorch class DenseBlock(nn.Module): def __init__(self, num_convs, input_channels, num_channels): super(DenseBlock, self).__init__() layer [] for i in range(num_convs): layer.append(conv_block( num_channels * i input_channels, num_channels)) self.net nn.Sequential(*layer) def forward(self, X): for blk in self.net: Y blk(X) # 连接通道维度上每个块的输入和输出 X torch.cat((X, Y), dim1) return X#tab tensorflow class DenseBlock(tf.keras.layers.Layer): def __init__(self, num_convs, num_channels): super(DenseBlock, self).__init__() self.listLayers [] for _ in range(num_convs): self.listLayers.append(ConvBlock(num_channels)) def call(self, x): for layer in self.listLayers.layers: x layer(x) return x#tab paddle class DenseBlock(nn.Layer): def __init__(self, num_convs, input_channels, num_channels): super(DenseBlock, self).__init__() layer [] for i in range(num_convs): layer.append( conv_block(num_channels * i input_channels, num_channels)) self.net nn.Sequential(*layer) def forward(self, X): for blk in self.net: Y blk(X) # 连接通道维度上每个块的输入和输出 X paddle.concat(x[X, Y], axis1) return X从实现细节可以看出框架间的差异PyTorch/Paddle 版本的DenseBlock在构建层列表时就显式地累加输入通道数第 $i$ 个卷积块的输入通道为num_channels * i input_channels而 MXNet/TensorFlow 版本利用框架的惰性通道推断无需手工计算。增长率Growth Rate与通道数演算下面验证稠密块的通道增长行为。定义一个有 2 个卷积块、输出通道数为 10 的DenseBlock使用通道数为 3 的输入将会得到通道数为 $32\times 1023$ 的输出blk DenseBlock(2, 10) blk.initialize() X np.random.uniform(size(4, 3, 8, 8)) Y blk(X) Y.shape#tab pytorch blk DenseBlock(2, 3, 10) X torch.randn(4, 3, 8, 8) Y blk(X) Y.shape#tab tensorflow blk DenseBlock(2, 10) X tf.random.uniform((4, 8, 8, 3)) Y blk(X) Y.shape#tab paddle blk DenseBlock(2, 3, 10) X paddle.randn([4, 3, 8, 8]) Y blk(X) Y.shape各框架运行后均得到(4, 23, 8, 8)TensorFlow 为 channels-last 布局的(4, 8, 8, 23)。这里的 10 即增长率growth rate卷积块的通道数控制了输出通道数相对于输入通道数的增长量。在 DenseNet 论文中增长率通常取 12、24 或 32 等较小值因为特征被反复复用不需要为每一层学习冗余的表示。过渡层Transition Layer由于每个稠密块都会带来通道数的增加使用过多则会过于复杂化模型。过渡层用于控制模型复杂度它通过 $1\times 1$ 卷积层来减小通道数并使用步幅为 2 的平均汇聚层减半高和宽从而进一步降低模型复杂度def transition_block(num_channels): blk nn.Sequential() blk.add(nn.BatchNorm(), nn.Activation(relu), nn.Conv2D(num_channels, kernel_size1), nn.AvgPool2D(pool_size2, strides2)) return blk#tab pytorch def transition_block(input_channels, num_channels): return nn.Sequential( nn.BatchNorm2d(input_channels), nn.ReLU(), nn.Conv2d(input_channels, num_channels, kernel_size1), nn.AvgPool2d(kernel_size2, stride2))#tab tensorflow class TransitionBlock(tf.keras.layers.Layer): def __init__(self, num_channels, **kwargs): super(TransitionBlock, self).__init__(**kwargs) self.batch_norm tf.keras.layers.BatchNormalization() self.relu tf.keras.layers.ReLU() self.conv tf.keras.layers.Conv2D(num_channels, kernel_size1) self.avg_pool tf.keras.layers.AvgPool2D(pool_size2, strides2) def call(self, x): x self.batch_norm(x) x self.relu(x) x self.conv(x) return self.avg_pool(x)#tab paddle def transition_block(input_channels, num_channels): return nn.Sequential( nn.BatchNorm2D(input_channels), nn.ReLU(), nn.Conv2D(input_channels, num_channels, kernel_size1), nn.AvgPool2D(kernel_size2, stride2))对上例稠密块的输出23 通道使用通道数为 10 的过渡层输出的通道数减为 10高和宽均减半blk transition_block(10) blk.initialize() blk(Y).shape#tab pytorch, paddle blk transition_block(23, 10) blk(Y).shape#tab tensorflow blk TransitionBlock(10) blk(Y).shape运行后得到(4, 10, 4, 4)TensorFlow 为(4, 4, 4, 10)即通道数 23 → 10空间尺寸 8×8 → 4×4。这里为什么要用平均汇聚层而不是最大汇聚层因为过渡层的作用是压缩信息、平滑地降低空间分辨率与通道冗余平均汇聚能保留整体特征响应而最大汇聚只保留局部最强的单一激活在信息压缩场景下平均汇聚对特征传递更温和。构建完整的 DenseNet 模型与 ResNet 的构造过程类似DenseNet 整体由四段构成入口卷积块 4 个稠密块含过渡层 全局汇聚与输出层。第一步入口卷积与最大汇聚DenseNet 首先使用同 ResNet 一样的单卷积层和最大汇聚层卷积 64 通道、7×7 核、步幅 2、padding 3net nn.Sequential() net.add(nn.Conv2D(64, kernel_size7, strides2, padding3), nn.BatchNorm(), nn.Activation(relu), nn.MaxPool2D(pool_size3, strides2, padding1))#tab pytorch b1 nn.Sequential( nn.Conv2d(1, 64, kernel_size7, stride2, padding3), nn.BatchNorm2d(64), nn.ReLU(), nn.MaxPool2d(kernel_size3, stride2, padding1))#tab tensorflow def block_1(): return tf.keras.Sequential([ tf.keras.layers.Conv2D(64, kernel_size7, strides2, paddingsame), tf.keras.layers.BatchNormalization(), tf.keras.layers.ReLU(), tf.keras.layers.MaxPool2D(pool_size3, strides2, paddingsame)])#tab paddle b1 nn.Sequential( nn.Conv2D(1, 64, kernel_size7, stride2, padding3), nn.BatchNorm2D(64), nn.ReLU(), nn.MaxPool2D(kernel_size3, stride2, padding1))第二步堆叠 4 个稠密块与过渡层类似于 ResNet 使用的 4 个残差块模块DenseNet 使用 4 个稠密块。与 ResNet 类似我们可以设置每个稠密块使用多少个卷积层这里设成 4从而与 ResNet-18 保持一致见 ResNet。稠密块里的卷积层通道数即增长率设为 32所以每个稠密块将增加 $4 \times 32 128$ 个通道。在每个模块之间ResNet 通过步幅为 2 的残差块减小高和宽DenseNet 则使用过渡层来减半高和宽并减半通道数# num_channels为当前的通道数 num_channels, growth_rate 64, 32 num_convs_in_dense_blocks [4, 4, 4, 4] for i, num_convs in enumerate(num_convs_in_dense_blocks): net.add(DenseBlock(num_convs, growth_rate)) # 上一个稠密块的输出通道数 num_channels num_convs * growth_rate # 在稠密块之间添加一个转换层使通道数量减半 if i ! len(num_convs_in_dense_blocks) - 1: num_channels // 2 net.add(transition_block(num_channels))#tab pytorch # num_channels为当前的通道数 num_channels, growth_rate 64, 32 num_convs_in_dense_blocks [4, 4, 4, 4] blks [] for i, num_convs in enumerate(num_convs_in_dense_blocks): blks.append(DenseBlock(num_convs, num_channels, growth_rate)) # 上一个稠密块的输出通道数 num_channels num_convs * growth_rate # 在稠密块之间添加一个转换层使通道数量减半 if i ! len(num_convs_in_dense_blocks) - 1: blks.append(transition_block(num_channels, num_channels // 2)) num_channels num_channels // 2#tab tensorflow def block_2(): net block_1() # num_channels为当前的通道数 num_channels, growth_rate 64, 32 num_convs_in_dense_blocks [4, 4, 4, 4] for i, num_convs in enumerate(num_convs_in_dense_blocks): net.add(DenseBlock(num_convs, growth_rate)) # 上一个稠密块的输出通道数 num_channels num_convs * growth_rate # 在稠密块之间添加一个转换层使通道数量减半 if i ! len(num_convs_in_dense_blocks) - 1: num_channels // 2 net.add(TransitionBlock(num_channels)) return net#tab paddle # num_channels为当前的通道数 num_channels, growth_rate 64, 32 num_convs_in_dense_blocks [4, 4, 4, 4] blks [] for i, num_convs in enumerate(num_convs_in_dense_blocks): blks.append(DenseBlock(num_convs, num_channels, growth_rate)) # 上一个稠密块的输出通道数 num_channels num_convs * growth_rate # 在稠密块之间添加一个转换层使通道数量减半 if i ! len(num_convs_in_dense_blocks) - 1: blks.append(transition_block(num_channels, num_channels // 2)) num_channels num_channels // 2这段循环的通道数演算值得细读初始num_channels 64每个稠密块之后num_channels num_convs * growth_rate即 128随后除了最后一个稠密块外过渡层又把通道数整除 2。以 PyTorch 版本为例实际通道序列为64 → 192 → 96 → 224 → 112 → 240 → 120 → 248最后一个稠密块后不做减半最终以 248 通道进入输出层。第三步全局汇聚与输出层与 ResNet 类似最后接上全局汇聚层和全连接层来输出结果net.add(nn.BatchNorm(), nn.Activation(relu), nn.GlobalAvgPool2D(), nn.Dense(10))#tab pytorch net nn.Sequential( b1, *blks, nn.BatchNorm2d(num_channels), nn.ReLU(), nn.AdaptiveAvgPool2d((1, 1)), nn.Flatten(), nn.Linear(num_channels, 10))#tab tensorflow def net(): net block_2() net.add(tf.keras.layers.BatchNormalization()) net.add(tf.keras.layers.ReLU()) net.add(tf.keras.layers.GlobalAvgPool2D()) net.add(tf.keras.layers.Flatten()) net.add(tf.keras.layers.Dense(10)) return net#tab paddle net nn.Sequential( b1, *blks, nn.BatchNorm2D(num_channels), nn.ReLU(), nn.AdaptiveMaxPool2D((1, 1)), nn.Flatten(), nn.Linear(num_channels, 10))全局汇聚PyTorch 用AdaptiveAvgPool2d((1,1))Paddle 的示例中使用了AdaptiveMaxPool2D把每个通道的特征图压缩为 1×1从而与输入尺寸无关地接入全连接层因为 DenseNet 的最后一层稠密连接了前面所有层全连接层之前必须通过这种聚合把可变大小的稠密特征张量固定下来。训练模型在 Fashion-MNIST 上验证由于 DenseNet 是较深的网络本节将输入高和宽从 224 降到 96 来简化计算学习率设为 0.1ResNet 一节中为 0.05稠密连接让梯度流动更充分可以采用稍大的学习率#tab all lr, num_epochs, batch_size 0.1, 10, 256 train_iter, test_iter d2l.load_data_fashion_mnist(batch_size, resize96) d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())这里依赖的d2l工具函数都可以在仓库的 d2l 目录源码中找到load_data_fashion_mnist如 d2l/torch.py下载并加载 Fashion-MNIST 数据集支持resize参数把输入图像调整到指定尺寸返回训练与测试DataLoadertrain_ch6如 d2l/torch.py第 6 章的通用训练流程内部完成 Xavier 初始化、SGD 优化器、交叉熵损失、逐 epoch 的训练/测试评估与动画可视化最后打印 loss、train acc、test acc 与吞吐量try_gpu如 d2l/torch.py存在 GPU 时返回cuda:i设备否则回退到 CPU。小结在跨层连接上不同于 ResNet 中将输入与输出相加稠密连接网络DenseNet在通道维上连结输入与输出特征被显式地复用最后一层与之前所有层稠密相连。DenseNet 的主要构建模块是稠密块决定输入输出如何连结和过渡层通过 $1\times 1$ 卷积减半通道数、步幅 2 平均汇聚减半高宽控制网络复杂度。在构建 DenseNet 时我们需要通过添加过渡层来控制网络的维数从而再次减少通道的数量增长率growth rate直接决定了每个稠密块通道数的增长速度。练习与进阶思考为什么过渡层使用平均汇聚层而不是最大汇聚层从信息压缩的语义出发分析两种汇聚操作的差异。DenseNet 的优点之一是模型参数比 ResNet 小为什么结合特征复用与每层只需学习少量新特征增长率两个角度作答。DenseNet 被诟病的问题是内存或显存消耗过多真的是这样吗把输入形状换成 $224 \times 224$观察实际的显存消耗变化是否有另一种方法减少显存消耗需要改变框架吗提示考虑计算与存储的重新调度如梯度检查点技术实现 DenseNet 论文:cite:Huang.Liu.Van-Der-Maaten.ea.2017表 1 所示的不同 DenseNet 版本DenseNet-121/169/201/264 等注意在 ImageNet 上使用的 bottleneck 与 compression 设置。应用 DenseNet 的思想设计一个基于多层感知机的模型并将其应用于 Kaggle 房价预测 任务中即把通道维连结思想移植到稠密连接的 MLP 上。如果你想系统了解 DenseNet 的前置知识可先阅读仓库的 残差网络ResNet 章节本文全部代码四框架版本可在 chapter_convolutional-modern/densenet.md 中直接运行配合 d2l 目录下的工具包即可完成端到端实验。赞分享人工智能深度学习机器学习教程【免费下载链接】d2l-zh《动手学深度学习》面向中文读者、能运行、可讨论。中英文版被70多个国家的500多所大学用于教学。项目地址https://gitcode.com/GitHub_Trending/d2/d2l-zh点击查看免费下载相关推荐动手学深度学习DenseNet 稠密连接网络原理与多框架实现详解动手学深度学习DenseNet 稠密连接网络原理与多框架实现详解 DenseNetdense convolutional network是《动手学深度学习人工智能深度学习机器学习教程深入理解DenseNet稠密连接网络的原理与实现深入理解DenseNet稠密连接网络的原理与实现 引言 在深度学习领域卷积神经网络 CNN 架构的创新一直是推动计算机视觉进步的关键力量。ResNet通过残人工智能深度学习机器学习教程深入理解PaddlePaddle中的稠密连接网络(DenseNet)深入理解PaddlePaddle中的稠密连接网络 DenseNet 引言 在深度学习领域卷积神经网络 CNN 架构的创新一直是推动计算机视觉任务性能提升的关键文档教程人工智能深度学习上一篇ZeroOmega终极指南如何轻松管理浏览器代理的完整解决方案下一篇eSearch滚动截屏三步出长图横向竖向任意方向都能拼创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考