vgg16
我们的特征提取网络是用vgg16作为主干网络的,只用前面的13层,最后3层全连接层不要。为什么不用其他的呢,比如resnet101,这个更深理论上当然更好啦,但是实际上训练时间和复杂性也提高啦,等简单的用好了,后面可以换嘛。先看看vgg16的结构吧,我网上找了一张比较清晰的图:
很清晰吧,感谢这位知乎的拉普拉斯同学。我们看到vgg16用了很多的3x3的卷积,为什么不用5x5,7x7这种呢,其实你可以自己推推其实5x5的卷积等于两个3x3的效果,然后7x7的卷积等于三个3x3的效果,但是参数可就少了很多,比如5x5是25个参数,不算偏置,两个3x3也就18个参数。另外因为用了padding,所以卷积尺寸不变,尺寸就交给池化了,池化每次尺寸减半。而主干网络只用了前面五端卷积层,总共13个卷积层,4个池化层,为什么叫vgg16呢,因为用了16个参数层,后面还有3个全连接呢,池化层是没有参数的。
vgg.py
我们来看看源码vgg.py
的主干网络函数nn_base
:
'''
vgg主干网络定义 名字不能乱,最后加载参数是根据名字的
只要全连接之前的,最后一层池化也不要
'''
def nn_base(input_tensor=None, trainable=False):
# Determine proper input shape
# Theano的通道在最前面,tensorflow的通道在最后
if K.image_dim_ordering() == 'th':
input_shape = (3, None, None)
else:
input_shape = (None, None, 3)
# 防止没有输入形状
if input_tensor is None:
img_input = Input(shape=input_shape)
else:
# 如果不是keras张量的话,会封装一层,然后多一个维度,就是样本的批量数
if not K.is_keras_tensor(input_tensor):
img_input = Input(tensor=input_tensor, shape=input_shape)
else:
img_input = input_tensor
# 这个貌似还没用到
if K.image_dim_ordering() == 'tf':
bn_axis = 3
else:
bn_axis = 1
# Block 1
x = Conv2D(64, (3, 3), activation='relu', padding='same', name='block1_conv1')(img_input)
x = Conv2D(64, (3, 3), activation='relu', padding='same', name='block1_conv2')(x)
x = MaxPooling2D((2, 2), strides=(2, 2), name='block1_pool')(x)
# Block 2
x = Conv2D(128, (3, 3), activation='relu', padding='same', name='block2_conv1')(x)
x = Conv2D(128, (3, 3), activation='relu', padding='same', name='block2_conv2')(x)
x = MaxPooling2D((2, 2), strides=(2, 2), name='block2_pool')(x)
# Block 3
x = Conv2D(256, (3, 3), activation='relu', padding='same', name='block3_conv1')(x)
x = Conv2D(256, (3, 3), activation='relu', padding='same', name='block3_conv2')(x)
x = Conv2D(256, (3, 3), activation='relu', padding='same', name='block3_conv3')(x)
x = MaxPooling2D((2, 2), strides=(2, 2), name='block3_pool')(x)
# Block 4
x = Conv2D(512, (3, 3), activation='relu', padding='same', name='block4_conv1')(x)
x = Conv2D(512, (3, 3), activation='relu', padding='same', name='block4_conv2')(x)
x = Conv2D(512, (3, 3), activation='relu', padding='same', name='block4_conv3')(x)
x = MaxPooling2D((2, 2), strides=(2, 2), name='block4_pool')(x)
# Block 5
x = Conv2D(512, (3, 3), activation='relu', padding='same', name='block5_conv1')(x)
x = Conv2D(512, (3, 3), activation='relu', padding='same', name='block5_conv2')(x)
x = Conv2D(512, (3, 3), activation='relu', padding='same', name='block5_conv3')(x)
# x = MaxPooling2D((2, 2), strides=(2, 2), name='block5_pool')(x)
return x
再来看看vgg.py
的RPN网络函数rpn
:
# RPN网络
def rpn(base_layers, num_anchors):
# 再进行3x3的卷积,结果分别给分类和回归
x = Conv2D(512, (3, 3), padding='same', activation='relu', kernel_initializer='normal', name='rpn_conv1')(base_layers) # (?, ?, ?, 512)
# 滤波器个数就是特征的维度 用1x1整合512个通道信息, 生成9个通道,9个锚框,本来应该是18个类别的概率,直接考虑前景,不考虑背景,所以就9个概率
x_class = Conv2D(num_anchors, (1, 1), activation='sigmoid', kernel_initializer='uniform', name='rpn_out_class')(x) # (?, ?, ?, 9)
'''
回归主要是要训练出4个值,平移系数和缩放数,即平移(tx,ty),缩放(th,tw),9个锚框,所以是9x4=36 ,这里的激活函数是线性函数,等于在做线性回归,当预测和真实框宽高相近的时候,
我们可以认为是线性变换,tw=log(Gw/Pw)=log((Gw+Pw−Pw)/Pw)=log(1+(Gw−Pw)/Pw) 利用高数中等价无穷小的定义 ln(1+x)=x 当x趋向于0,
也就是(Gw−Pw)/Pw很小的时候,即Gw和Pw很相近的时候,tw=(Gw−Pw)/Pw是线性变换
'''
x_regr = Conv2D(num_anchors * 4, (1, 1), activation='linear', kernel_initializer='zero', name='rpn_out_regress')(x) # (?, ?, ?, 36)
return [x_class, x_regr, base_layers]
第一个卷积是让他输出是否是背景的分类概率,因为是二分类,激活函数sigmoid
就够了,然后用1x1的卷积进行降维,降到锚框数量的维度,也就是9维,其实每一个特征图上的点有9个通道,每个通道都是一个概率值,是前面512个通道整合起来的。1x1卷积的作用很大,既可以做升维降维,也可以做通道的信息整合。
第二个卷积是降维到36个维度,因为每个锚框包含4个回归梯度嘛,所以4x9=36,要36个值,如果标注框和锚框之间的差距很小的话,可以近似为线性回归问题,所以激活函数是线性的,当然你可以改成其他的,我没试过,不知道效果怎么样,你改好了记得通知我下啊嘿嘿。这个问题可以看下这篇文章,或许会更好理解。
还要注意的就是每个层的名字不能乱命名,加载模型参数的时候是按名字加载的。
最后的分类和回归网络先不说,因为涉及到ROIPooling层的操作,之前会涉及IOU,NMS等处理,后面慢慢讲。
好了,今天就到这里了,希望对学习理解有帮助,大神看见勿喷,仅为自己的学习理解,能力有限,请多包涵,部分图片来自网络,侵删。