如何从Keras模型中删除前N层?

6

我希望能够从预训练的Keras模型中删除前N层。例如,EfficientNetB0 的前 3 层仅用于预处理:

import tensorflow as tf

efinet = tf.keras.applications.EfficientNetB0(weights=None, include_top=True)

print(efinet.layers[:3])
# [<tensorflow.python.keras.engine.input_layer.InputLayer at 0x7fa9a870e4d0>,
# <tensorflow.python.keras.layers.preprocessing.image_preprocessing.Rescaling at 0x7fa9a61343d0>,
# <tensorflow.python.keras.layers.preprocessing.normalization.Normalization at 0x7fa9a60d21d0>]

正如M.Innat所提到的,第一层是一个输入层(Input Layer),应该被保留或重新附加。我想要移除这些层,但像这样简单的方法会报错:

cut_input_model = return tf.keras.Model(
    inputs=[efinet.layers[3].input], 
    outputs=efinet.outputs
)

这将导致:
ValueError: Graph disconnected: cannot obtain value for tensor KerasTensor(...)

你建议采用什么方法来实现这个?


1
你想删除前三层! 但您也要删除规格图层,即输入层吗?这不是预处理层!您是否意识到了? - Innat
你说得对,我更新了我的问题。谢谢。 - sebastian-sz
3个回答

2
“Graph disconnected”错误的原因是您没有指定“Input”层。但这不是主要问题。有时,使用Sequential和Functional API从keras模型中删除中间层并不直接。
对于Sequential,相对而言应该很容易,而在功能模型中,您需要关注多输入块(例如multiply、add等)。例如:如果您想要从序列模型中删除某些中间层,您可以轻松地采用此解决方案。但对于功能模型(efficientnet),您不能这样做,因为它具有多输入内部块,并且您将遇到此错误:“ValueError:合并图层应在输入列表上调用”。因此,这需要更多的工作。这里是一个可能的方法来克服它。
在这里,我将展示一个简单的解决方案,但它可能不是通用的,在某些情况下也不安全。它基于this approach;使用pop方法。为什么使用它可能不安全!好的,让我们先加载模型。
func_model = tf.keras.applications.EfficientNetB0()

for i, l in enumerate(func_model.layers):
    print(l.name, l.output_shape)
    if i == 8: break

input_19 [(None, 224, 224, 3)]
rescaling_13 (None, 224, 224, 3)
normalization_13 (None, 224, 224, 3)
stem_conv_pad (None, 225, 225, 3)
stem_conv (None, 112, 112, 32)
stem_bn (None, 112, 112, 32)
stem_activation (None, 112, 112, 32)
block1a_dwconv (None, 112, 112, 32)
block1a_bn (None, 112, 112, 32)

接下来,使用 .pop 方法:
func_model._layers.pop(1) # remove rescaling
func_model._layers.pop(1) # remove normalization

for i, l in enumerate(func_model.layers):
    print(l.name, l.output_shape)
    if i == 8: break

input_22 [(None, 224, 224, 3)]
stem_conv_pad (None, 225, 225, 3)
stem_conv (None, 112, 112, 32)
stem_bn (None, 112, 112, 32)
stem_activation (None, 112, 112, 32)
block1a_dwconv (None, 112, 112, 32)
block1a_bn (None, 112, 112, 32)
block1a_activation (None, 112, 112, 32)
block1a_se_squeeze (None, 32)

1
感谢您提供详细的答案并提供额外的资源链接。这确实解决了问题。 - sebastian-sz

1

对我来说,@M.Innat的解决方案导致了一个不连通的图形,因为仅弹出层是不够的,需要在输入层和第一个卷积层之间建立连接(您可以使用Netron检查问题)。

唯一有效的解决方案是手动编辑模型的配置。

这是一个完整的脚本,可以删除Efficientnet-B1的预处理部分。已在TF2中进行了测试。

import tensorflow as tf

def split(model, start, end):
    confs = model.get_config()
    kept_layers = set()
    for i, l in enumerate(confs['layers']):
        if i == 0:
            confs['layers'][0]['config']['batch_input_shape'] = model.layers[start].input_shape
            if i != start:
                #confs['layers'][0]['name'] += str(random.randint(0, 100000000)) # rename the input layer to avoid conflicts on merge
                confs['layers'][0]['config']['name'] = confs['layers'][0]['name']
        elif i < start or i > end:
            continue
        kept_layers.add(l['name'])
    # filter layers
    layers = [l for l in confs['layers'] if l['name'] in kept_layers]
    layers[1]['inbound_nodes'][0][0][0] = layers[0]['name']
    # set conf
    confs['layers'] = layers
    confs['input_layers'][0][0] = layers[0]['name']
    confs['output_layers'][0][0] = layers[-1]['name']
    # create new model
    submodel = tf.keras.Model.from_config(confs)
    for l in submodel.layers:
        orig_l = model.get_layer(l.name)
        if orig_l is not None:
            l.set_weights(orig_l.get_weights())
    return submodel


model = tf.keras.applications.efficientnet.EfficientNetB1()

# first layer = 3, last layer = 341
new_model = split(model, 3, 341)
new_model.summary()
new_model.save("efficientnet_b1.h5")

脚本基于这个优秀的答案

0

我一直在尝试使用keras tensorflow VGGFace模型做同样的事情。经过大量实验,我发现这种方法是有效的。在这种情况下,除了最后一层之外,所有模型都被使用,并被替换为自定义嵌入层:

vgg_model = VGGFace(include_top=True, input_shape=(224, 224, 3)) # full VGG16 model
inputs = Input(shape=(224, 224, 3))
x = inputs
# Assemble all layers except for the last layer
for layer in vgg_model.layers[1:-2]:
  x = vgg_model.get_layer(layer.name)(x)
    
# Now add a new last layer that provides the 128 embeddings output
x = Dense(128, activation='softmax', use_bias=False, name='fc8x')(x)
# Create the custom model
custom_vgg_model = Model(inputs, x, name='custom_vggface')

与 layers[x] 或 pop() 不同,get_layer 获取实际层,允许将它们组装成新的输出层集。然后可以从中创建一个新模型。'for' 语句从 1 开始而不是 0,因为输入层已经由 'inputs' 定义。

此方法适用于顺序模型。不清楚它是否适用于更复杂的模型。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接