ResNet 显着改变了如何在深度网络中参数化函数的观点。DenseNet(密集卷积网络)在某种程度上是对此的逻辑延伸 (Huang et al. , 2017)。DenseNet 的特点是每一层都连接到所有前面的层的连接模式和连接操作(而不是 ResNet 中的加法运算符)以保留和重用早期层的特征。要了解如何得出它,让我们稍微绕道数学。
8.7.1. 从 ResNet 到 DenseNet
回忆一下函数的泰勒展开式。对于这一点x=0 它可以写成
关键是它将函数分解为越来越高阶的项。同样,ResNet 将函数分解为
也就是说,ResNet分解f分为一个简单的线性项和一个更复杂的非线性项。如果我们想捕获(不一定要添加)两个术语以外的信息怎么办?一种这样的解决方案是 DenseNet (Huang等人,2017 年)。
如图 8.7.1所示,ResNet 和 DenseNet 的主要区别在于后者的输出是 连接的(表示为[,]) 而不是添加。结果,我们从x在应用越来越复杂的函数序列后,它的值:
最后,将所有这些功能组合在 MLP 中,再次减少特征数量。就实现而言,这非常简单:我们不是添加术语,而是将它们连接起来。DenseNet 这个名字源于变量之间的依赖图变得非常密集这一事实。这种链的最后一层与前面的所有层紧密相连。密集连接如图 8.7.2所示 。
构成 DenseNet 的主要组件是密集块和 过渡层。前者定义输入和输出如何连接,而后者控制通道的数量,使其不会太大,因为扩展 x→[x,f1(x),f2([x,f1(x)]),…] 可以是相当高维的。
8.7.2. 密集块
DenseNet 使用改进的 ResNet 的“批量归一化、激活和卷积”结构(参见第 8.6 节中的练习 )。首先,我们实现这个卷积块结构。
class ConvBlock(nn.Module):
num_channels: int
training: bool = True
@nn.compact
def __call__(self, X):
Y = nn.relu(nn.BatchNorm(not self.training)(X))
Y = nn.Conv(self.num_channels, kernel_size=(3, 3), padding=(1, 1))(Y)
Y = jnp.