前面笔者写了一篇关于resnet的文章
https://blog.csdn.net/weixin_44106928/article/details/101627948
当时看完resnet的论文,理解到设计思想,真的感觉到作者何凯明的牛逼之处
巧妙了运用短连接的方式将输入输出add到一起,减轻了深度神经网络的梯度消失,弥散的问题
而后几年,DenseNet参考了resnet的网络结构,将相加变成了concatenate连结,以DenseBlock为基础块,减少了参数量,并成功引起学术界注意
这里引用论文中的Densenet结构图片
每一个模块进行相关操作后,与输入(input)进行连接,再作为新的输入,进入到下一个DenseBlock
这个结构是不是很熟悉?
它与Resnet的resBlock结构很相似
下面我来介绍一下DenseBlock的设计
首先 简单介绍下预处理
也就是常规的卷积,激活,批量归一化
然后输入进一个个denseBlock里面
denseblock首先采用的是Resnet改良版的 批量归一化,激活,卷积的 新结构
第一个卷积先进行的是1x1 的卷积,第二次则进行的是3x3的卷积,然后与输入相连接
注意这里concatenate区别于Resnet的add
连结的意思是将两个相同长度,宽度的特征图合并在一起,生成一个新的图,通道数是两个特征图通道数之和
而ResBlock里的Add,是将特征图的值相加,这就需要保证两个特征图的各个维度相同
以下是ADD的图
可以看到最后两个相加的张量都是(256, 256, 16)
回来我们继续说Densenet的结构
论文里面定义了一个growth生长率这个参数
也就是经过了growth个DenseBlock后
我们使用步幅为2,1x1卷积块进行通道数上的降维,并且用 平均池化层对张量的长,宽进行降维
我们通过1x1的卷积将通道数从600降到了300
然后再用平均池化将256, 256 降维到128, 128
经过这样一个大结构后
最后我们使用全局平均池化层,然后接上你需要分类的全连接层
整体的结构就完成
接下来贴上总结构图
采用与Resnet相似的结构,使用短连接的拓扑结构,减缓梯度弥散情况
通过1x1卷积核减少维数,避免传统池化层对特征的忽略
强调特征复用,将前一级的输入相连接,构成新的特征图
4.参数减少,我们在Resnet下,如果做大量的卷积核卷积,如从256个卷积核逐步减少到32个卷积核
所耗费的参数也是很大的,笔者最近搭的一个Resnet网络参数训练量高达一千万
而表面上Densenet的维数也很高
但其实它每一个模块卷积的数量并不大
可以看到虽然后面输入维数很高,但它始终是保持一个48的卷积核在进行卷积,所以实质上,在每个DenseBlock里面,相加的通道数都是48 48 48
DenseBlock就不是很强调卷积的深度,它更强调通过每一次卷积得到的特征图复用
DenseNet的网络更窄,这也使得参数相较于Resnet有明显的减少
我写的代码参考了一些博主的
用的keras版本较老
里面有个方法是plot_ model,是将模型可视化,如果没有安装也可以注释掉
首先是conv_bl;ock.py文件
import keras
from keras import backend as K
from keras.utils.vis_utils import plot_model
from keras.models import *
from keras.layers import *
def DenseLayer(x, nb_filters, bn_size=4, alpha=0.0, drop_rate=0.2):
x = BatchNormalization()(x)
x = LeakyReLU(alpha=alpha)(x)
x = Conv2D(bn_size*nb_filters, (1, 1), strides=(1, 1), padding='SAME')(x)
x = BatchNormalization()(x)
x = LeakyReLU(alpha=alpha)(x)
x = Conv2D(bn_size * nb_filters, (3, 3), strides=(1, 1), padding='SAME')(x)
if drop_rate:
x = Dropout(drop_rate)(x)
return x
def DenseBlock(x, nb_layers, growth_rate, drop_rate=0.2):
for ii in range(nb_layers):
conv = DenseLayer(x, nb_filters=growth_rate, drop_rate=drop_rate)
x = Concatenate()([x, conv])
return x
def TransitonLayer(x, compression=0.5, alpha=0.0, is_max=0):
# 这是is_max只是为了定义使用最大池化层还是平均池化层
nb_filters = int(x.shape.as_list()[-1]*compression)
# 这里是将通道维数 乘一个压缩率,这里是0.5
# 比如我原本通道数有600,那么经过后面的1x1 卷积输出后通道压缩为一半也就是300
x = BatchNormalization()(x)
x = LeakyReLU(alpha=alpha)(x)
x = Conv2D(nb_filters, (1, 1), strides=(1, 1), padding='SAME')(x)
if is_max!=0: x = MaxPool2D(pool_size=(2, 2), strides=2)(x)
else: x = AveragePooling2D(pool_size=(2, 2), strides=2)(x)
return x
然后是我们主程序
NetStruct.py
from conv_block import *
BATCH_SIZE = 5 # 批处理图片个数 默认为50 前期测试
EPOCHS = 10 # DEFAULT 10
IMAGE_SIZE = 1024 # 输入的是256 x 256
NUM_CHANNEL = 1 # 通道为3,默认是灰度图
NUM_LABELS = 2
MODEL_DIR = './model/train_demo/'
MODEL_FORMAT = '.h5'
HISTORY_DIR = './history/train_demo/'
HISTORY_FORMAT = '.history'
# 模型网络结构文件
MODEL_VIS_FILE = 'My_net_classfication' + '.png'
# 定义增长率
growth_rate = 12
input = Input(shape=(256, 256, 3))
x = BatchNormalization()(input)
x = LeakyReLU(alpha=0.1)(x)
x = Conv2D(growth_rate*2, (3, 3), strides=1, padding='SAME')(x)
x = DenseBlock(x, 12, growth_rate, drop_rate=0.2)
x = TransitonLayer(x)
x = DenseBlock(x, 12 , growth_rate, drop_rate=0.2)
x = TransitonLayer(x)
x = DenseBlock(x, 12 , growth_rate, drop_rate=0.2)
x = BatchNormalization(axis=3)(x)
x = GlobalAveragePooling2D()(x)
x = Dense(10, activation='softmax')(x)
model = Model(input, x)
model.summary()
plot_model(model, to_file=MODEL_VIS_FILE, show_shapes=True)
这里我没有添加实际的数据,只是简单的以一个shape来进行模型的搭建