Keras3通过使用 EfficientNet 进行微调进行图像分类
作者: Yixing Fu
创建日期: 2020/06/30
最后修改时间: 2023/07/10
描述: 使用在 imagenet 上预先训练的权重的 EfficientNet 进行 Stanford Dogs 分类。
简介:什么是 EfficientNet
EfficientNet 于 2019 年在 Tan 和 Le中首次推出,是最有效的模型之一(即需要最少的 FLOPS 进行推理) 在两者上都达到了最先进的精度 ImageNet 和常见的图像分类迁移学习任务。
最小的基本模型类似于 MnasNet,它 以明显较小的模型达到接近 SOTA。通过引入启发式方式 扩展模型时,EfficientNet 提供了一系列模型(B0 到 B7),这些模型表示 在各种秤上将效率和准确性良好结合。这样的缩放 启发式方法(化合物缩放,详细信息参见 Tan 和 Le,2019 年)允许 以效率为导向的基础模型 (B0) 在各种规模上超越模型,同时避免 超参数的广泛网格搜索。
该模型的最新更新摘要可在此处获得,其中各种 增强方案和半监督学习方法进一步应用于 提高模型的 ImageNet 性能。可以使用模型的这些扩展 在不更改模型架构的情况下更新权重。
EfficientNet 的 B0 到 B7 变体
(本节提供了有关 “compound scaling” 的一些详细信息,可以跳过 如果您只对使用模型感兴趣)
根据原始论文,人们可能有 印象中 EfficientNet 是由任意 选择 中的比例因子作为论文的方程 (3)。然而,分辨率的选择, 深度和宽度也受到许多因素的限制:
- 分辨率:不能被 8、16 等整除的分辨率会导致边界附近出现零填充 浪费计算资源的层。这尤其适用于较小的 变体,因此 B0 和 B1 的输入分辨率选择为 224 和 240.
- 深度和宽度:EfficientNet 的构建块要求通道大小 8 的倍数。
- 资源限制:当深度 宽度仍然可以增加。在这种情况下,增加深度和/或 width 但保持分辨率仍可以提高性能。
因此,EfficientNet 模型的每个变体的深度、宽度和分辨率 经过精心挑选并被证明会产生良好的结果,尽管它们可能会显着 off 从 compound scaling 公式中。 因此,keras 实现(详见下文)仅提供这 8 个模型,即 B0 到 B7、 而不是允许任意选择宽度/深度/分辨率参数。
EfficientNet 的 Keras 实现
自 v2.3 起,Keras 随附了 EfficientNet B0 到 B7 的实现。自 使用 EfficientNetB0 对 ImageNet 中的 1000 类图像进行分类,运行:
from tensorflow.keras.applications import EfficientNetB0
model = EfficientNetB0(weights='imagenet')
此模型采用 shape 为 的输入图像,并且输入数据应位于 范围。归一化作为模型的一部分包含在内。(224, 224, 3)
[0, 255]
因为在 ImageNet 上训练 EfficientNet 需要大量的资源,并且 不属于模型架构本身的几种技术。因此,Keras 默认情况下,implementation 会加载通过使用 AutoAugment 进行训练而获得的预训练权重。
对于 B0 到 B7 基础模型,输入形状不同。下面是输入形状的列表 每个模型的预期值:
基本模型 | 分辨率 |
---|---|
高效网 B0 | 224 |
高效网 B1 | 240 |
高效网络 B2 | 260 |
高效网络 B3 | 300 |
高效网络 B4 | 380 |
高效网络 B5 | 456 |
高效网络 B6 | 528 |
高效网 B7 | 600 |
当模型用于迁移学习时,Keras 实现 提供删除顶部图层的选项:
model = EfficientNetB0(include_top=False, weights='imagenet')
此选项不包括将 1280 个要素置于倒数第二个图层的最后一个图层 层转换为 1000 个 ImageNet 类的预测。将顶层替换为自定义图层 layers 允许在迁移学习工作流程中使用 EfficientNet 作为特征提取器。Dense
模型构造函数中值得注意的另一个参数是哪些控件 负责随机深度的 dropout 率。 此参数用作微调中额外正则化的切换,但不用作 影响加载的权重。例如,当需要更强的正则化时,请尝试:drop_connect_rate
model = EfficientNetB0(weights='imagenet', drop_connect_rate=0.4)
默认值为 0.2。
示例:Stanford Dogs 的 EfficientNetB0。
EfficientNet 能够执行广泛的图像分类任务。 这使其成为迁移学习的良好模型。 作为端到端示例,我们将展示在 Stanford Dogs 数据集上使用预训练的 EfficientNetB0。
设置和数据加载
import numpy as np
import tensorflow_datasets as tfds
import tensorflow as tf # For tf.data
import matplotlib.pyplot as plt
import keras
from keras import layers
from keras.applications import EfficientNetB0
# IMG_SIZE is determined by EfficientNet model choice
IMG_SIZE = 224
BATCH_SIZE = 64
加载数据
在这里,我们从 tensorflow_datasets(以下简称 TFDS)加载数据。 Stanford Dogs 数据集在 TFDS 作为 stanford_dogs。 它包含 20,580 张图像,属于 120 个犬种类别 (12000 个用于训练,8580 个用于测试)。
通过简单地更改以下内容,您也可以尝试使用这款笔记本 TFDS 中的其他数据集,例如 cifar10、cifar100、food101、 等。当图像远小于 EfficientNet 输入的大小时, 我们可以简单地对输入图像进行上采样。Tan 和 Le,2019 年已经表明,迁移学习 即使输入图像仍然很小,结果也更适合提高分辨率。dataset_name
dataset_name = "stanford_dogs"
(ds_train, ds_test), ds_info = tfds.load(
dataset_name, split=["train", "test"], with_info=True, as_supervised=True
)
NUM_CLASSES = ds_info.features["label"].num_classes
当数据集包含不同大小的图像时,我们需要将它们的大小调整为 共享大小。Stanford Dogs 数据集仅包含至少 200x200 的图像 pixels 的大小。在这里,我们将图像大小调整为 EfficientNet 所需的输入大小。
size = (IMG_SIZE, IMG_SIZE)
ds_train = ds_train.map(lambda image, label: (tf.image.resize(image, size), label))
ds_test = ds_test.map(lambda image, label: (tf.image.resize(image, size), label))
可视化数据
以下代码显示了前 9 张图像及其标签。
def format_label(label):
string_label = label_info.int2str(label)
return string_label.split("-")[1]
label_info = ds_info.features["label"]
for i, (image, label) in enumerate(ds_train.take(9)):
ax = plt.subplot(3, 3, i + 1)
plt.imshow(image.numpy().astype("uint8"))
plt.title("{}".format(format_label(label)))
plt.axis("off")
数据增强
我们可以使用预处理层 API 进行图像增广。
img_augmentation_layers = [
layers.RandomRotation(factor=0.15),
layers.RandomTranslation(height_factor=0.1, width_factor=0.1),
layers.RandomFlip(),
layers.RandomContrast(factor=0.1),
]
def img_augmentation(images):
for layer in img_augmentation_layers:
images = layer(images)
return images
此模型对象可以用作 我们稍后构建的模型,并作为函数进行预处理 数据。将它们用作函数 很容易可视化增强的图像。这里我们绘制了 9 个示例 的增强结果。Sequential
for image, label in ds_train.take(1):
for i in range(9):
ax = plt.subplot(3, 3, i + 1)
aug_img = img_augmentation(np.expand_dims(image.numpy(), axis=0))
aug_img = np.array(aug_img)
plt.imshow(aug_img[0].astype("uint8"))
plt.title("{}".format(format_label(label)))
plt.axis("off")
准备输入
一旦我们验证了输入数据和增强工作正常, 我们准备用于训练的数据集。输入数据的大小将调整为 uniform 。标签放入 one-hot (又名 categorical) 编码。数据集已批处理。IMG_SIZE
注意:在某些情况下可能会有所改善 性能,但取决于环境和使用的特定数据集。 有关数据管道性能的更多信息,请参阅本指南。prefetch
AUTOTUNE
# One-hot / categorical encoding
def input_preprocess_train(image, label):
image = img_augmentation(image)
label = tf.one_hot(label, NUM_CLASSES)
return image, label
def input_preprocess_test(image, label):
label = tf.one_hot(label, NUM_CLASSES)
return image, label
ds_train = ds_train.map(input_preprocess_train, num_parallel_calls=tf.data.AUTOTUNE)
ds_train = ds_train.batch(batch_size=BATCH_SIZE, drop_remainder=True)
ds_train = ds_train.prefetch(tf.data.AUTOTUNE)
ds_test = ds_test.map(input_preprocess_test, num_parallel_calls=tf.data.AUTOTUNE)
ds_test = ds_test.batch(batch_size=BATCH_SIZE, drop_remainder=True)
从头开始训练模型
我们构建一个具有 120 个输出类的 EfficientNetB0,它是从头开始初始化的:
注意:精度会非常缓慢地增加,并且可能会过拟合。
model = EfficientNetB0(
include_top=True,
weights=None,
classes=NUM_CLASSES,
input_shape=(IMG_SIZE, IMG_SIZE, 3),
)
model.compile(optimizer="adam", loss="categorical_crossentropy",<