【问题标题】:Caffe to Keras conversion of grouped convolution分组卷积的 Caffe 到 Keras 的转换
【发布时间】:2019-02-09 20:55:43
【问题描述】:

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

这是Caffe中model的原始定义,我们称之为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 不同,但顺序不是这里的问题。

问题是 Keras 的最后一个卷积层与 Caffe 的最后一个卷积层在 3 维的值不同。见下文。


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,)])

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

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


问题:

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

非常感谢!

【问题讨论】:

    标签: python keras deep-learning caffe


    【解决方案1】:

    注意conv2 定义中的group: 2 字段。这意味着你有一个grouped 卷积(Caffe: What does the group param mean?)。从技术上讲,这意味着您有 两个 过滤器,每个过滤器形状为 (128, 48, 5, 5)。第一个将与前 48 个通道进行卷积并产生前 128 个输出,第二个用于剩余的输出。然而,Caffe 将这两个权重存储在一个 blob 中,这就是为什么它的形状是 (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 可能会很棘手。要获得绝对相同的结果,您必须遇到很多微妙的事情,例如卷积中的asymmetric paddingdifferent max-pooling behavior。这就是为什么如果你从 Caffe 导入权重,你可能无法用 batchnorm 替换 LRN 层。幸运的是,Keras 中有 LRN 的实现,例如 here

    【讨论】:

    • 是的,我昨天晚些时候发现的。从 Caffe 官方卷积的文档中,一旦我将组参数改回1,一切都很好。但我一直在寻找确切的实现,我在这里找到了它。我还担心填充和本地响应规范化,我之前使用过ZeroPadding2D,但不对称填充似乎有所不同。我注意到 Caffe 的padding 不会影响输出形状,你知道为什么吗?非常感谢您的大力帮助!我很感激!
    • pad Caffe 卷积层中的参数一定会影响输出形状,什么意思?在某些条件下(stride > 1, padding='same'),TensorFlow 中可能会发生不对称填充,为了避免这种情况,您可以将 tf.pad + Conv2D 与填充“有效”结合使用。例如,参见programtalk.com/vs2/python/12827/models/slim/nets/… 中的 conv2d_same 函数。
    • 这是另一种奇怪的行为吗?例如,我们将第一层conv1pad: 5参数设置为pad: 10,层的形状保持不变(96, 3, 11, 11)。这是正常行为吗?如果不是,我想这是另一个答案的问题。
    • 抱歉,我有点困惑,我读的是重量形状而不是实际的输出形状。填充确实会像通常那样影响输出。我想我会使用tf.pad"symmetric" 选项创建自己的图层。谢谢!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-11-24
    • 2015-03-21
    • 1970-01-01
    • 2019-08-18
    • 2018-02-10
    • 2016-11-13
    • 1970-01-01
    相关资源
    最近更新 更多