使用 TensorFlow 创建 DenseNet 121

一、说明

本篇示意DenseNet如何在tensorflow上实现,DenseNet与ResNet有类似的地方,都有层与层的"短路"方式,但两者对层的短路后处理有所不同,本文遵照原始论文的技术路线,完整复原了DenseNet的全部网络。

图1:DenseNet中的各种块和层(来源:原始DenseNet论文)

二、DenseNet综述

DenseNet(密集卷积网络)是一种架构,专注于使深度学习网络更深入,但同时通过在层之间使用更短的连接来提高它们的训练效率。DenseNet 是一个卷积神经网络,其中每一层都连接到网络中更深的所有其他层,即第一层连接到第 2、3、4 层等,第二层连接到第 3、4、5 层等。这样做是为了在网络各层之间实现最大的信息流。为了保持前馈特性,每一层从前面的所有层获取输入,并将自己的特征图传递给它将要到达的所有层。与 Resnets 不同,它不是通过求和来组合特征,而是通过连接它们来组合特征。因此,"ith"层具有"i"输入,并且由其所有先前卷积块的特征图组成。它自己的特征图被传递到所有下一个"I-i"层。这在网络中引入了"(I(I+1)))/2"连接,而不是像传统深度学习架构中那样只是"I"连接。因此,与传统的卷积神经网络相比,它需要的参数更少,因为不需要学习不重要的特征图。

DenseNet由两个重要的块组成,而不是基本的卷积层和池化层。它们是密集块和过渡层。

接下来,我们看看所有这些块和层的外观,以及如何在 python 中实现它们。

图2:DenseNet121框架(来源:DenseNet原始论文,由作者编辑)

DenseNet从基本的卷积和池化层开始。然后有一个密集块,然后是一个过渡层,另一个密集块后跟一个过渡层,另一个密集块后跟一个过渡层,最后是一个密集块,然后是一个分类层。

第一个卷积块有 64 个大小为 7x7 的过滤器,步幅为 2。接下来是最大池化为 3x3 且步幅为 2 的 MaxPooling 层。这两行可以在 python 中用以下代码表示。

ax 复制代码
input = Input (input_shape)
x = Conv2D(64, 7, strides = 2, padding = 'same')(input)
x = MaxPool2D(3, strides = 2, padding = 'same')(x)

2.1 定义卷积块 ---

输入后的每个卷积块具有以下顺序:批处理归一化,然后是 ReLU 激活,然后是实际的 Conv2D 层。为了实现这一点,我们可以编写以下函数。

ax 复制代码
#batch norm + relu + conv
def bn_rl_conv(x,filters,kernel=1,strides=1):
        
    x = BatchNormalization()(x)
    x = ReLU()(x)
    x = Conv2D(filters, kernel, strides=strides,padding = 'same')(x)
    return x

图3.密集块(来源:DenseNet论文-作者编辑)

2.2 定义密集块 ---

如图 3 所示,每个密集块都有两个卷积,具有 1x1 和 3x3 大小的内核。在密集块 1 中,重复 6 次,在密集块 2 中重复 12 次,在密集块 3 中重复 24 次,最后在密集块 4 中重复 16 次。

在密集块中,每个 1x1 卷积都有 4 倍的滤波器数量。所以我们使用 4*过滤器,但 3x3 过滤器只存在一次。此外,我们必须将输入与输出张量连接起来。

每个块分别运行 6、12、24、16 次重复,使用 'for 循环'。

ax 复制代码
def dense_block(x, repetition):
        
   for _ in range(repetition):
        y = bn_rl_conv(x, 4*filters)
        y = bn_rl_conv(y, filters, 3)
        x = concatenate([y,x])
   return x

图4:过渡层(来源:DenseNet论文,作者编辑)

2.3 定义过渡层

--- 在过渡层中,我们将通道数减少到现有通道的一半。有一个 1x1 卷积层和一个 2x2 平均池化层,步幅为 2。bn_rl_conv,函数中已经设置了 1x1 的内核大小,因此我们不需要明确地再次定义它。

在过渡层中,我们必须将通道删除到现有通道的一半。我们有输入张量x,我们想找到有多少个通道,我们需要得到其中的一半。因此,我们可以使用 Keras 后端 (K) 获取张量 x 并返回一个维度为 x 的元组。而且,我们只需要该形状的最后一个数字,即过滤器的数量。所以我们加上 -1。最后,我们可以将这个数量的过滤器除以 2 以获得所需的结果。

ax 复制代码
def transition_layer(x):
        
    x = bn_rl_conv(x, K.int_shape(x)[-1] //2 )
    x = AvgPool2D(2, strides = 2, padding = 'same')(x)
    return x

因此,我们完成了定义密集块和过渡层的工作。现在我们需要将密集块和过渡层堆叠在一起。所以我们写了一个 for 循环来运行 6,12,24,16 次重复。因此,循环运行 4 次,每次使用 6、12、24 或 16 中的值之一。这样就完成了 4 个密集块和过渡层。

ax 复制代码
for repetition in [6,12,24,16]:
        
    d = dense_block(x, repetition)
    x = transition_layer(d)

最后,是GlobalAveragePooling,然后是最终的输出层。正如我们在上面的代码块中看到的,密集块由"d"定义,而在最后一层,在密集块 4 之后,没有过渡层 4,而是直接进入分类层。因此,"d"是应用GlobalAveragePooling的连接,而不是"x"。另一种选择是从上面的代码中删除"for"循环,并将层一个接一个地堆叠,而不使用最终的过渡层。

ax 复制代码
x = GlobalAveragePooling2D()(d)
output = Dense(n_classes, activation = 'softmax')(x)

现在我们已经将所有块放在一起,让我们将它们合并以查看整个DenseNet架构。

三、完整的 DenseNet 121 架构

ax 复制代码
import tensorflow as tf
from tensorflow.keras.layers import Input, Conv2D, BatchNormalization, Dense
from tensorflow.keras.layers import AvgPool2D, GlobalAveragePooling2D, MaxPool2D
from tensorflow.keras.models import Model
from tensorflow.keras.layers import ReLU, concatenate
import tensorflow.keras.backend as K
# Creating Densenet121
def densenet(input_shape, n_classes, filters = 32):
    
    #batch norm + relu + conv
    def bn_rl_conv(x,filters,kernel=1,strides=1):
        
        x = BatchNormalization()(x)
        x = ReLU()(x)
        x = Conv2D(filters, kernel, strides=strides,padding = 'same')(x)
        return x
    
    def dense_block(x, repetition):
        
        for _ in range(repetition):
            y = bn_rl_conv(x, 4*filters)
            y = bn_rl_conv(y, filters, 3)
            x = concatenate([y,x])
        return x
        
    def transition_layer(x):
        
        x = bn_rl_conv(x, K.int_shape(x)[-1] //2 )
        x = AvgPool2D(2, strides = 2, padding = 'same')(x)
        return x
    
    input = Input (input_shape)
    x = Conv2D(64, 7, strides = 2, padding = 'same')(input)
    x = MaxPool2D(3, strides = 2, padding = 'same')(x)
    
    for repetition in [6,12,24,16]:
        
        d = dense_block(x, repetition)
        x = transition_layer(d)
    x = GlobalAveragePooling2D()(d)
    output = Dense(n_classes, activation = 'softmax')(x)
    
    model = Model(input, output)
    return model
input_shape = 224, 224, 3
n_classes = 3
model = densenet(input_shape,n_classes)
model.summary()

输出:(假设 3 个最终类 --- 模型摘要的最后几行)

四、 查看体系结构关系图

可以使用以下代码。

ax 复制代码
from tensorflow.python.keras.utils.vis_utils import model_to_dot
from IPython.display import SVG
import pydot
import graphviz

SVG(model_to_dot(
    model, show_shapes=True, show_layer_names=True, rankdir='TB',
    expand_nested=False, dpi=60, subgraph=False
).create(prog='dot',format='svg'))

输出 --- 图表的前几个块

这就是我们如何实现DenseNet 121架构。

五、引用

  1. 黄高、刘壮、劳伦斯·范德马滕和基利安·温伯格,密集连接的卷积网络,arXiv 1608.06993 (2016)
    阿琼·萨卡尔

2 密网论文链接:https://arxiv.org/pdf/1608.06993.pdf

相关推荐
To_OC1 小时前
大模型蒸馏是啥?说白了就是大厨带徒弟的学问
人工智能·llm·agent
新手来了@click1 小时前
JAVA+AI 简化开发操作|文章被 AI Agent 技术社区收录分享
人工智能
GuWenyue2 小时前
Cursor黑盒拆解!1套LangChain.js手写Mini编程Agent,自动生成React项目,效率提升60%
前端·数据库·人工智能
GuWenyue2 小时前
传统Agent工具两大痛点!300行代码落地MCP跨语言工具,彻底解耦LLM与工具
前端·人工智能·算法
老云讲算力市场2 小时前
WAIC首日观察:国产算力与机器人加速落地,奇点算力迎来产业新机遇
人工智能·科技
糖果店的幽灵2 小时前
【DeepAgents 从入门到精通】Context Management 上下文管理
java·人工智能·后端·spring·中间件·langgraph·deepagents
小林ixn2 小时前
大模型随机说话的秘密:Temperature 和 Top K 深度解析,LangChain 实战调优
人工智能·langchain
ALINX技术博客3 小时前
ALINX 亮相 2026 WAIC 世界人工智能大会,展示 AI 视觉 FPGA+GPU 异构计算与电子后视镜解决方案
人工智能·ai·fpga·世界人工智能大会·电子后视镜
程序员老猫3 小时前
当 AI 能写 80% 的代码时,后端工程师的核心价值还剩什么?
人工智能
想会飞的蒲公英3 小时前
计算机怎样读取中文文本:编码、清洗与标准化
人工智能·python·自然语言处理