【问题标题】:Why replacing max pool by average pool using Keras APIs fails?为什么使用 Keras API 将最大池替换为平均池会失败?
【发布时间】:2019-06-27 18:14:48
【问题描述】:

我正在尝试使用 Keras API 将预训练网络中的最大池化层替换为平均池化层。不知何故,它对我不起作用。如果您能帮助我弄清楚如何实现它,我将不胜感激。

以下是我目前的解决方案:

def replace_max_by_average_pooling(model):

    input_layer, *other_layers = model.layers
    assert isinstance(input_layer, keras.layers.InputLayer)

    x = input_layer.output
    for layer in other_layers:
        if isinstance(layer, keras.layers.MaxPooling2D):
            layer = keras.layers.AveragePooling2D(
                pool_size=layer.pool_size,
                strides=layer.strides,
                padding=layer.padding,
                data_format=layer.data_format,
                name=f"{layer.name}_av",
            )
        x = layer(x)

    return keras.models.Model(inputs=input_layer.input, outputs=x)

当我尝试在 VGG 网络上使用此功能时:

vgg = keras.applications.vgg19.VGG19(include_top=False, weights="imagenet")
vgg_av = replace_max_by_average_pooling(vgg)

如果我打印摘要,它看起来不错:

_________________________________________________________________ 图层(类型)输出形状参数 #
==================================================== =============== input_1(InputLayer)(无,无,无,3)0
_________________________________________________________________ block1_conv1 (Conv2D)(无,无,无,64)1792
_________________________________________________________________ block1_conv2 (Conv2D)(无,无,无,64)36928
_________________________________________________________________ block1_pool_av(AveragePool(无,无,无,64)0
_________________________________________________________________ block2_conv1 (Conv2D)(无,无,无,128)73856
_________________________________________________________________ block2_conv2 (Conv2D)(无,无,无,128)147584
_________________________________________________________________ block2_pool_av (AveragePool (无,无,无,128) 0
_________________________________________________________________ block3_conv1 (Conv2D)(无,无,无,256)295168
...

但是,如果我尝试基于vgg_av 的几层构建一个新模型:

layer = vgg_av.get_layer("block3_conv1")
keras.models.Model(inputs=vgg_av.layers[0].input, outputs=layer.output).summary()

不知何故,平均池层再次被最大池层取代:

_________________________________________________________________ 图层(类型)输出形状参数 #
==================================================== =============== input_1(InputLayer)(无,无,无,3)0
_________________________________________________________________ block1_conv1 (Conv2D)(无,无,无,64)1792
_________________________________________________________________ block1_conv2 (Conv2D)(无,无,无,64)36928
_________________________________________________________________ block1_pool (MaxPooling2D)(无,无,无,64)0
_________________________________________________________________ block2_conv1 (Conv2D)(无,无,无,128)73856
_________________________________________________________________ block2_conv2 (Conv2D)(无,无,无,128)147584
_________________________________________________________________ block2_pool (MaxPooling2D)(无,无,无,128)0
_________________________________________________________________ block3_conv1 (Conv2D)(无,无,无,256)295168
==================================================== =============== 总参数:555,328 可训练参数:555,328 不可训练参数: 0


我做错了吗?为什么和在哪里?

我的猜测是,在这条线上x = layer(x) 新操作被添加到计算图中,使得新操作的名称为*name of an old operation*_1,当我调用vgg_av.get_layer("block3_conv1") 时,它仍然从vgg 获取子图。但是如果我在vgg_av 中打印图层名称,则名称与vgg 中的名称相同。为什么只有在我尝试获取图层子集时它才会失败?我想完全重建计算图,但也许我缺少一些 Keras API,或者我在概念上缺少一些东西。

【问题讨论】:

  • 知道“什么失败了”会很有趣...
  • @DanielMöller 好的,我更新了问题以使其更清楚。基本上,如果我基于来自vgg_av 的层子集构建一个新模型,我会再次获得最大池化层数

标签: python tensorflow keras


【解决方案1】:

原因是每当您重用一个层(您在使用平均池创建新分支时重用层)时,您都会在图中创建一个新节点。

原始模型仍然存在,并且所有层都使用索引 0 的节点,而您的新模型使用索引 1 的节点。

层应该有get_output_at(index) 或类似的方法,你将要从中获取输出的节点传递给它。根据我过去的经验,我猜想简单地layer.output 会带来一个错误,因为你有多个节点(但令人惊讶的是,代码接受了这一点——我猜是 keras 版本不同)。

因此,您应该通过以下方式实现目标:

layer = vgg_av.get_layer("block3_conv1")
output = layer.get_output_at(1)
keras.models.Model(inputs=vgg_av.layers[0].input, outputs=output).summary()

在调用最后一层之后,在replace_max_by_average_pooling 中计算新模型最后一层的.outputs 的数量应该是一个好主意,以防你将有更多这样的模型来自同一个原始模型(意味着更多节点)。


黑客建议

在 Keras 中保存和加载模型提供了一个系统(最初用于自定义层和自定义函数),您可以在其中定义 keras 应该为它不知道的类名和函数名使用什么。

加载模型是“使用保存的参数再次创建模型”。因此,如果您使用此系统“替换”现有名称,它应该可以在模型重建期间替换层。

custom_objects = {'MaxPooling2D': AveragePooling2D} 
vgg.save_model(filename)
vgg_ag = keras.models.load_model(filename, custom_objects = custom_objects)

如果这不起作用,您可以创建一个自定义函数,使用给定的参数返回一个平均池,例如:

def createAvgFromMax(**params):

    #study the params, choose what to keep and discard
    return AveragePooling2D(....)

还有custom_objects = { 'MaxPooling2D': createAvgFromMax }

【讨论】:

  • 很好,它确实有效!我不知道get_output_at!必须阅读更多关于它的信息。非常感谢您的帮助。在我接受答案之前,如果您能提供您的意见,是否有可能/是否有意义替换节点而不是创建新节点,我将不胜感激?
  • 我认为不可能替换节点。如果是,它应该非常复杂,并且需要深入研究源代码才能知道如何操作。
  • 好的,很好。我认为您建议跟踪计数我更新模型的次数可能是一种方法。再次感谢您!
  • 但是您可以使用 custom_objects = {'MaxPooling2D' : CustomClassThatCreatesAvgPoolingFromTheConfigParams}original_model.save(filename)new_model = keras.models.load_model(filename, custom_objects=custom_objects) 以肮脏的方式破解此模型。
  • 看起来很脏:)
猜你喜欢
  • 2017-01-23
  • 2019-07-24
  • 2020-02-29
  • 2019-06-16
  • 1970-01-01
  • 2020-04-21
  • 2017-06-16
  • 1970-01-01
  • 2019-09-08
相关资源
最近更新 更多