分组卷积的Caffe到Keras的转换



我试图从一个非常简单的Caffe模型中提取权重,并将其解释为功能齐全的Keras模型。

这是Caffe中模型的原始定义,我们称之为simple.prototxt:

input: "im_data"
input_shape {
dim: 1
dim: 3
dim: 1280
dim: 1280
}
layer {
name: "conv1"
type: "Convolution"
bottom: "im_data"
top: "conv1"
param {
lr_mult: 1
decay_mult: 1
}
param {
lr_mult: 2
decay_mult: 0
}
convolution_param {
num_output: 96
kernel_size: 11
pad: 5
stride: 4
}
}
layer {
name: "relu1"
type: "ReLU"
bottom: "conv1"
top: "conv1"
}
layer {
name: "pool1"
type: "Pooling"
bottom: "conv1"
top: "pool1"
pooling_param {
pool: MAX
kernel_size: 3
pad: 0
stride: 2
}
}
layer {
name: "norm1"
type: "LRN"
bottom: "pool1"
top: "norm1"
lrn_param {
local_size: 5
alpha: 0.0001
beta: 0.75
}
}
layer {
name: "conv2"
type: "Convolution"
bottom: "norm1"
top: "conv2"
param {
lr_mult: 1
decay_mult: 1
}
param {
lr_mult: 2
decay_mult: 0
}
convolution_param {
num_output: 256
kernel_size: 5
pad: 2
group: 2
}
}
layer {
name: "relu2"
type: "ReLU"
bottom: "conv2"
top: "conv2"
}

Caffe中的层定义可能看起来很复杂,但它只需要一个维度为1280x1280x3的图像,将其传递到卷积层,然后最大值将其汇集并传递到最终的卷积层。

以下是它在Keras中的实现,它要简单得多:

from keras.models import Model
from keras.layers import Input, BatchNormalization, 
from keras.activations import relu, softmax
im_data = Input(shape=(1280, 1280, 3),
dtype='float32',
name='im_data')
conv1 = Conv2D(filters=96,
kernel_size=11,
strides=(4, 4),
activation=relu,
padding='same',
name='conv1')(im_data)
pooling1 = MaxPooling2D(pool_size=(3, 3),
strides=(2, 2),
padding='same',
name='pooling1')(conv1)
normalized1 = BatchNormalization()(pooling1)  # https://stats.stackexchange.com/questions/145768/importance-of-local-response-normalization-in-cnn
conv2 = Conv2D(filters=256,
kernel_size=5,
activation=relu,
padding='same',
name='conv2')(normalized1)
model = Model(inputs=[im_data], outputs=conv2)  

问题:

尽管两个模型在每一层中似乎都有相似的参数,但问题是它们的权重形状不相等。我知道Caffe的形状顺序与Keras不同,但这里不关心点餐。

问题是,与Caffe中的最后一个卷积层相比,Keras的最后一层卷积层在三维中具有不同的值。请参见下文。


咖啡的重量形状

>>> net = caffe.net('simple.prototxt', 'premade_weights.caffemodel', caffe.TEST)
>>> for i in range(len(net.layers)):
...     if len(net.layers[i].blobs) != 0:  # if layer has no weights
...         print(("name", net._layer_names[i]))
...         print("weight_shapes", [v.data.shape for v in net.layers[i].blobs])
('name', 'conv1')
('weight_shapes', [(96, 3, 11, 11), (96,)])
('name', 'conv2')
('weight_shapes', [(256, 48, 5, 5), (256,)])

角蛋白的重量形状:

>>> for layer in model.layers:
...     if len(layer.get_weights()) != 0:
...         print(("name", layer.name))
...         print(("weight_shapes", [w.shape for w in layer.get_weights()]))  
('name', 'conv1')
('weight_shapes', [(11, 11, 3, 96), (96,)])
('name', 'conv2')
('weight_shapes', [(5, 5, 96, 256), (256,)])

这似乎是一种奇怪的行为。正如您所看到的,Caffe和Keras中的conv1形状是相等的(忽略顺序)。但在Caffe中,conv2形状是[(256, 48, 5, 5), (256,)]),而在Keras的'conv2'形状是[(5, 5, 96, 256), (256,)]注意,即48*2=96

此外,注意conv2层直接在最大池化层之后,因此Keras中的最大池化可能有问题。


问题:

我是否正确解释了从Caffe到Keras的模型定义?特别是最大池化层及其参数?

非常感谢!

注意conv2定义中的group: 2字段。这意味着你得到了一个分组卷积(Caffe:分组参数是什么意思?)。从技术上讲,这意味着您有两个过滤器,每个过滤器的形状都是(128, 48, 5, 5)。第一个将与前48个通道卷积并产生前128个输出,第二个用于剩余的输出。然而,Caffe将两个权重存储在一个斑点中,这就是为什么它的形状是(128x2, 48, 5, 5)

KerasConv2D层中没有这样的参数,但广泛采用的解决方法是用Lambda层分割输入特征图,用两个不同的卷积层处理它们,然后合并回单个特征图。

from keras.layers import Concatenate
normalized1_1 = Lambda(lambda x: x[:, :, :, :48])(normalized1)
normalized1_2 = Lambda(lambda x: x[:, :, :, 48:])(normalized1)
conv2_1 = Conv2D(filters=128,
kernel_size=5,
activation=relu,
padding='same',
name='conv2_1')(normalized1_1)
conv2_2 = Conv2D(filters=128,
kernel_size=5,
activation=relu,
padding='same',
name='conv2_2')(normalized1_2)
conv2 = Concatenate(name='conv_2_merge')([conv2_1, conv2_2])

我没有检查代码的正确性,但想法一定是这样的。

关于你的任务:将网络从Caffe转换为Keras可能很棘手。要获得完全相同的结果,您必须遇到许多微妙的事情,如卷积中的不对称填充或不同的最大池行为。这就是为什么如果你从Caffe导入权重,你可能无法用batchnorm替换LRN层。幸运的是,在Keras中有LRN的实现,例如这里。

最新更新