代码示例 / 快速Keras食谱 / 自定义Conv2D层的卷积操作

自定义Conv2D层的卷积操作

作者: lukewood
创建日期: 2021年11月3日
最后修改日期: 2021年11月3日
描述: 该示例展示了如何使用Conv.convolution_op() API实现自定义卷积层。

在Colab中查看 GitHub源代码


介绍

有时您可能需要实现像Conv1DConv2D这样的卷积层的自定义版本。 Keras使您可以在不从头开始实现整个层的情况下实现这一点:您可以重用 大部分基本卷积层,只需通过 convolution_op()方法自定义卷积操作。

该方法在Keras 2.7中引入。因此,在使用 convolution_op() API之前,请确保您正在运行Keras版本2.7.0或更高版本。


一个简单的StandardizedConv2D实现

使用Conv.convolution_op() API有两种方法。第一种方法 是重写卷积层子类中的convolution_op()方法。 采用这种方法,我们可以快速实现一个 StandardizedConv2D,如下所示。

import os

os.environ["KERAS_BACKEND"] = "tensorflow"

import tensorflow as tf
import keras
from keras import layers
import numpy as np


class StandardizedConv2DWithOverride(layers.Conv2D):
    def convolution_op(self, inputs, kernel):
        mean, var = tf.nn.moments(kernel, axes=[0, 1, 2], keepdims=True)
        return tf.nn.conv2d(
            inputs,
            (kernel - mean) / tf.sqrt(var + 1e-10),
            padding="VALID",
            strides=list(self.strides),
            name=self.__class__.__name__,
        )

使用Conv.convolution_op() API的另一种方法是直接从 卷积层子类的call()方法中调用convolution_op()方法。 使用这种方法实现的可比较类如下所示。

class StandardizedConv2DWithCall(layers.Conv2D):
    def call(self, inputs):
        mean, var = tf.nn.moments(self.kernel, axes=[0, 1, 2], keepdims=True)
        result = self.convolution_op(
            inputs, (self.kernel - mean) / tf.sqrt(var + 1e-10)
        )
        if self.use_bias:
            result = result + self.bias
        return result

示例用法

这两个层可以作为Conv2D的直接替代品。以下 演示在MNIST数据集上执行分类。

# 模型/数据参数
num_classes = 10
input_shape = (28, 28, 1)

# 数据,分为训练集和测试集
(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()

# 将图像缩放到[0, 1]范围
x_train = x_train.astype("float32") / 255
x_test = x_test.astype("float32") / 255
# 确保图像形状为(28, 28, 1)
x_train = np.expand_dims(x_train, -1)
x_test = np.expand_dims(x_test, -1)
print("x_train shape:", x_train.shape)
print(x_train.shape[0], "训练样本")
print(x_test.shape[0], "测试样本")

# 将类向量转换为二进制类矩阵
y_train = keras.utils.to_categorical(y_train, num_classes)
y_test = keras.utils.to_categorical(y_test, num_classes)

model = keras.Sequential(
    [
        keras.layers.Input(shape=input_shape),
        StandardizedConv2DWithCall(32, kernel_size=(3, 3), activation="relu"),
        layers.MaxPooling2D(pool_size=(2, 2)),
        StandardizedConv2DWithOverride(64, kernel_size=(3, 3), activation="relu"),
        layers.MaxPooling2D(pool_size=(2, 2)),
        layers.Flatten(),
        layers.Dropout(0.5),
        layers.Dense(num_classes, activation="softmax"),
    ]
)

model.summary()
x_train shape: (60000, 28, 28, 1)
60000 训练样本
10000 测试样本
模型: "sequential"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ 层 (类型)                      输出形状                  参数 # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ standardized_conv2d_with_call   │ (, 26, 26, 32)        │        320 │
│ (标准化卷积2D(带调用))    │                           │            │
├─────────────────────────────────┼───────────────────────────┼────────────┤
│ max_pooling2d (最大池化2D)    │ (, 13, 13, 32)        │          0 │
├─────────────────────────────────┼───────────────────────────┼────────────┤
│ standardized_conv2d_with_overr… │ (, 11, 11, 64)        │     18,496 │
│ (标准化卷积2D(覆盖) │                           │            │
├─────────────────────────────────┼───────────────────────────┼────────────┤
│ max_pooling2d_1 (最大池化2D)  │ (, 5, 5, 64)          │          0 │
├─────────────────────────────────┼───────────────────────────┼────────────┤
│ flatten (展平)               │ (, 1600)              │          0 │
├─────────────────────────────────┼───────────────────────────┼────────────┤
│ dropout (丢弃)               │ (, 1600)              │          0 │
├─────────────────────────────────┼───────────────────────────┼────────────┤
│ dense (全连接层)                   │ (, 10)                │     16,010 │
└─────────────────────────────────┴───────────────────────────┴────────────┘
 总参数: 34,826 (136.04 KB)
 可训练参数: 34,826 (136.04 KB)
 非可训练参数: 0 (0.00 B)
batch_size = 128
epochs = 5

model.compile(loss="categorical_crossentropy", optimizer="adam", metrics=["accuracy"])

model.fit(x_train, y_train, batch_size=batch_size, epochs=5, validation_split=0.1)
第 1 轮/共 5 轮
  64/422 ━━━━━━━━━━━━━━━━━━━━  0s 2ms/步 - 准确率: 0.4439 - 损失: 13.1274

警告: 在调用 absl::InitializeLog() 之前的所有日志消息都写入 STDERR
I0000 00:00:1699557098.952525   26800 device_compiler.h:187] 使用 XLA 编译集群! 该行在进程生命周期内最多记录一次。

 422/422 ━━━━━━━━━━━━━━━━━━━━ 10s 14ms/步 - 准确率: 0.7277 - 损失: 4.5649 - 验证准确率: 0.9690 - 验证损失: 0.1140
第 2 轮/共 5 轮
 422/422 ━━━━━━━━━━━━━━━━━━━━ 2s 3ms/步 - 准确率: 0.9311 - 损失: 0.2493 - 验证准确率: 0.9798 - 验证损失: 0.0795
第 3 轮/共 5 轮
 422/422 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/步 - 准确率: 0.9531 - 损失: 0.1655 - 验证准确率: 0.9838 - 验证损失: 0.0610
第 4 轮/共 5 轮
 422/422 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/步 - 准确率: 0.9652 - 损失: 0.1201 - 验证准确率: 0.9847 - 验证损失: 0.0577
第 5 轮/共 5 轮
 422/422 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/步 - 准确率: 0.9687 - 损失: 0.1059 - 验证准确率: 0.9870 - 验证损失: 0.0525

<keras.src.callbacks.history.History at 0x7fed258da200>

结论

Conv.convolution_op() API 提供了一种简便易懂的方式来实现自定义卷积层。使用此 API 的 StandardizedConvolution 实现非常简洁,仅由四行代码组成。