为什么更轻的 Keras 模型 运行 与更大的原始模型具有相同的推理速度?

Why would a much lighter Keras model run at the same speed at inference as the much larger original model?

我用以下架构训练了一个 Keras 模型:

def make_model(input_shape, num_classes):
    inputs = keras.Input(shape=input_shape)
    # Image augmentation block
    x = inputs
    # Entry block
    x = layers.experimental.preprocessing.Rescaling(1.0 / 255)(x)
    x = layers.Conv2D(32, 3, strides=2, padding="same")(x)
    x = layers.BatchNormalization()(x)
    x = layers.Activation("relu")(x)

    x = layers.Conv2D(64, 3, padding="same")(x)
    x = layers.BatchNormalization()(x)
    x = layers.Activation("relu")(x)

    previous_block_activation = x  # Set aside residual

    for size in [128, 256, 512, 728]:
        x = layers.Activation("relu")(x) 
        x = layers.SeparableConv2D(size, 3, padding="same")(x)
        x = layers.BatchNormalization()(x)

        x = layers.Activation("relu")(x) 
        x = layers.SeparableConv2D(size, 3, padding="same")(x)
        x = layers.BatchNormalization()(x)

        x = layers.MaxPooling2D(3, strides=2, padding="same")(x)

        # Project residual
        residual = layers.Conv2D(size, 1, strides=2, padding="same")(
            previous_block_activation
        )
        x = layers.add([x, residual])  # Add back residual
        previous_block_activation = x  # Set aside next residual

    x = layers.SeparableConv2D(1024, 3, padding="same")(x)
    x = layers.BatchNormalization()(x)
    x = layers.Activation("relu")(x)

    x = layers.GlobalAveragePooling2D()(x)
    if num_classes == 2:
        activation = "sigmoid"
        units = 1
    else:
        activation = "softmax"
        units = num_classes

    x = layers.Dropout(0.5)(x)
    outputs = layers.Dense(units, activation=activation)(x)
    return keras.Model(inputs, outputs)

并且该模型有超过 200 万个可训练参数。

然后我训练了一个只有 300,000 的轻量级模型。可训练参数:

def make_model(input_shape, num_classes):
    inputs = keras.Input(shape=input_shape) 
    # Image augmentation block
    x = inputs
    # Entry block
    x = layers.experimental.preprocessing.Rescaling(1.0 / 255)(x)
    x = layers.Conv2D(64, kernel_size=(7, 7), activation=tf.keras.layers.LeakyReLU(alpha=0.01), padding = "same", input_shape=image_size + (3,))(x)
    x = layers.MaxPooling2D(pool_size=(2, 2))(x)
    x = layers.Conv2D(192, kernel_size=(3, 3), activation=tf.keras.layers.LeakyReLU(alpha=0.01), padding = "same", input_shape=image_size + (3,))(x)
    x = layers.Conv2D(128, kernel_size=(1, 1), activation=tf.keras.layers.LeakyReLU(alpha=0.01), padding = "same", input_shape=image_size + (3,))(x)
    x = layers.MaxPooling2D(pool_size=(2, 2))(x)
    x = layers.Conv2D(128, kernel_size=(3, 3), activation=tf.keras.layers.LeakyReLU(alpha=0.01), padding = "same", input_shape=image_size + (3,))(x)
    x = layers.MaxPooling2D(pool_size=(2, 2))(x)
    x = layers.Dropout(0.5)(x)

    x = layers.GlobalAveragePooling2D()(x)
    if num_classes == 2:
        activation = "sigmoid"
        units = 1
    else:
        activation = "softmax"
        units = num_classes

    x = layers.Dropout(0.5)(x)

    outputs = layers.Dense(units, activation=activation)(x)
    
    return keras.Model(inputs, outputs)

然而,最后一个模型(它更轻,甚至接受更小的输入尺寸)似乎以相同的速度 运行,每秒仅分类 2 张图像。因为它是一个较小的模型,速度不应该有差异吗?查看代码,是否有明显的理由说明情况并非如此?

我在两种情况下都使用相同的推理方法:

image_size = (180, 180)
batch_size = 32


model = keras.models.load_model('model_13.h5')

t_end = time.time() + 10

iters = 0

while time.time() < t_end:

    img = keras.preprocessing.image.load_img(
        "test2.jpg", target_size=image_size
    )


    img_array = image.img_to_array(img)

    #print(img_array.shape)

    img_array = tf.expand_dims(img_array, 0)  # Create batch axis


    predictions = model.predict(img_array)
    score = predictions[0]

    print(score)
    iters += 1

    if score < 0.5:
        print('Fire')
    else:
        print('No Fire')


print('TOTAL: ', iters)

参数的数量最多,表示模型训练或运行推理的速度。这可能取决于许多其他因素。

这里有一些示例,它们可能会影响您的模型的吞吐量:

  1. 激活函数:ReLu 激活比例如具有指数项的 ELU 或 GELU。不仅计算指数比线性数慢,而且梯度的计算也复杂得多,因为在 Relu 的情况下是常数,即激活的斜率(例如 1)。
  2. 用于您的数据的位精度。一些 HW 加速器可以在 float16 中比在 float32 中进行更快的计算,并且读取更少的位可以减少延迟。
  3. 有些层可能没有参数但执行固定计算。即使没有参数被添加到网络的权重中,仍然会执行计算。
  4. 您的训练硬件架构。可以比其他过滤器更有效地计算某些过滤器大小和批量大小。
  5. 有时计算硬件的速度不是瓶颈,用于加载和预处理数据的输入管道

如果不进行测试很难判断,但在您的特定示例中,我猜以下内容可能会减慢您的推理速度:

  1. 具有 7x7 转换的大感知域
  2. leaky_relu 比 relu
  3. 稍慢
  4. 瓶颈可能是您的数据输入管道,而不是推理速度。如果推理速度比数据准备快得多,则两个模型的速度可能看起来相同。但实际上硬件是空闲的,正在等待数据。

要了解发生了什么,您可以更改一些参数并评估速度,或者您可以通过使用 tensorboard 跟踪您的硬件来分析您的输入管道。这是一个小指南:https://www.tensorflow.org/tensorboard/tensorboard_profiling_keras

最好的, 萨沙