4

我想从预训练的 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(...)

推荐的方法是什么?

4

4 回答 4

2

出现错误的原因Graph disconnected是因为您没有指定Input图层。但这不是这里的主要问题。有时使用API从模型中移除中间层keras并不简单。SequentialFunctional

对于顺序,它应该比较容易,而在功能模型中,您需要关心多输入块(例如multiplyadd等)。例如:如果您想在顺序模型中删除一些中间层,您可以轻松地调整此解决方案。但是对于函数模型(efficientnet),你不能因为多输入内部块,你会遇到这个错误:ValueError: A merged layer should be called on a list of inputs. 所以这需要更多的工作AFAIK,这是一种可能的方法来克服它。


在这里,我将为您的案例展示一个简单的解决方法,但它可能不通用,在某些情况下也不安全。即基于这种方法;使用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)
于 2021-04-20T13:51:03.717 回答
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")

该脚本基于这个很好的答案

于 2021-11-26T11:42:48.340 回答
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' 定义。

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

于 2021-06-30T13:19:59.873 回答
0

以下代码加载模型,删除最后一层并添加一个新层作为最后一层

old_model = keras.models.load_model("old_model.h5")
new_model= keras.models.Sequential(old_model.layers[:-1])
new_model.add(keras.layers.Dense(5, activation="sigmoid"))

您可以使用类似的方法切掉第一层并只选择最后一层。

old_model.layers[N:]

有比这更好的方法。创建模型的克隆,复制其权重,然后添加新层并训练您的网络

clone_of_old_model = keras.models.clone_model(old_model)
clone_of_old_model.set_weights(old_model.get_weights())

我说这种方法更好的原因是,如果你使用第一段代码,新模型的训练可能会影响旧模型。

现在您可以将这两部分组合起来创建一个新模型。

请记住编译您的模型,因为您将冻结和解冻图层。

于 2021-04-20T10:11:21.173 回答