Python 分组卷积的Caffe到Keras转换

Python 分组卷积的Caffe到Keras转换,python,keras,deep-learning,caffe,Python,Keras,Deep Learning,Caffe,我试图从一个非常简单的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 {

我试图从一个非常简单的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
的图像,将其传递到卷积层,然后max将其合并并传递到最终卷积层

下面是它在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的最后一个卷积层在三维中具有不同的值。见下文


Caffe的重量形状

>>> 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,)])
Keras的重量形状

>>> 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,)])
这似乎是一种奇怪的行为。如您所见,
conv1
Caffe中的shape和Keras是相等的(忽略顺序)。但是在Caffe
conv2
中,形状是
[(256,48,5,5),(256,)]
,而在Keras中,“conv2”的形状是
[(5,5,96,256),(256,)]
注意
48*2=96

另外,请注意
conv2
层直接位于最大池层之后,因此Keras中的最大池层可能有问题


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


多谢各位

注意您的
conv2
定义中的
group:2
字段。这意味着这里有一个卷积()。从技术上讲,这意味着您有两个过滤器,每个过滤器的形状
(128、48、5、5)
。第一个将与前48个通道进行卷积,并产生前128个输出,第二个用于剩余的输出。但是,Caffe将这两个权重存储在一个块中,这就是为什么它的形状是
(128x2,48,5,5)

Keras
Conv2D
层中没有此类参数,但广泛采用的解决方法是将输入特征图拆分为
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的实现。

是的,我昨天晚些时候发现了这一点。从Caffe中官方卷积的文档来看,一旦我将组参数改回
1
,一切都很好。但我一直在寻找精确的实现,我在这里找到了它。我还关心填充和局部响应规范化,之前我使用了
zeroppadding2d
,但不对称填充似乎有所不同。我注意到Caffe的
填充
不会影响输出形状,你知道为什么吗?非常感谢你的帮助!我很感激pad
参数必须影响输出形状,这是什么意思?在某些情况下(跨步>1,padding='same'),TensorFlow中可能会发生不对称填充,为了避免这种情况,可以将tf.pad+Conv2D与padding'valid'结合使用。请参见示例中的conv2d_相同函数。这是另一种奇怪的行为吗?例如,让我们为第一层设置
pad:5
参数
conv1
,并将其设置为
pad:10
,层的形状保持不变
(96,3,11,11)
。这是正常的行为吗?如果不是,我想这是另一个问题的答案。我很抱歉,我有点困惑,我读的是重量形状,而不是实际的输出形状。填充确实会像正常情况一样影响输出。我想我会用
tf.pad
对称选项创建自己的图层。非常感谢。