开发者指南 / KerasCV / 使用 KerasCV 进行图像分类

使用 KerasCV 进行图像分类

作者: lukewood
创建日期 03/28/2023
最后修改日期 03/28/2023
描述:使用 KerasCV 训练强大的图像分类器。

在 Colab 中查看 GitHub 源代码

图像分类是指根据给定的输入图像预测其类别标签的过程。虽然图像分类是一项相对简单的计算机视觉任务,但现代方法仍然由多个复杂的组件构成。幸运的是,KerasCV 提供了构建常用组件的 API。

本指南演示了 KerasCV 在三个复杂度级别上解决图像分类问题的模块化方法

  • 使用预训练分类器进行推理
  • 微调预训练主干网络
  • 从头开始训练图像分类器

KerasCV 使用 Keras 3 与 TensorFlow、PyTorch 或 Jax 中的任何一个一起工作。在下面的指南中,我们将使用 jax 后端。本指南无需任何更改即可在 TensorFlow 或 PyTorch 后端运行,只需更新下面的 KERAS_BACKEND 即可。

我们使用 Keras 官方吉祥物 Professor Keras 作为材料复杂度的视觉参考

!pip install -q --upgrade keras-cv
!pip install -q --upgrade keras  # Upgrade to Keras 3.
import os

os.environ["KERAS_BACKEND"] = "jax"  # @param ["tensorflow", "jax", "torch"]

import json
import math
import numpy as np

import keras
from keras import losses
from keras import ops
from keras import optimizers
from keras.optimizers import schedules
from keras import metrics

import keras_cv

# Import tensorflow for [`tf.data`](https://tensorflowcn.cn/api_docs/python/tf/data) and its preprocessing functions
import tensorflow as tf
import tensorflow_datasets as tfds

使用预训练分类器进行推理

让我们从最简单的 KerasCV API 开始:预训练分类器。在本例中,我们将构建一个在 ImageNet 数据集上预训练的分类器。我们将使用此模型来解决古老的“猫或狗”问题。

KerasCV 中的最高级模块是任务任务是一个包含(通常是预训练的)主干模型和特定于任务的层的 keras.Model。以下是如何使用 keras_cv.models.ImageClassifier 以及 EfficientNetV2B0 主干网络的示例。

EfficientNetV2B0 是构建图像分类管道时的绝佳起始模型。该架构设法实现了高精度,同时使用了 700 万的参数量。如果 EfficientNetV2B0 对于您希望解决的任务不够强大,请务必查看 KerasCV 其他可用的主干网络

classifier = keras_cv.models.ImageClassifier.from_preset(
    "efficientnetv2_b0_imagenet_classifier"
)

您可能会注意到与旧的 keras.applications API 略有不同;在旧 API 中,您将使用 EfficientNetV2B0(weights="imagenet") 来构建类。虽然旧 API 非常适合分类,但它并没有有效地扩展到需要复杂架构的其他用例,例如目标检测和语义分割。

现在我们的分类器已构建完成,让我们将其应用于这张可爱的猫图片!

filepath = keras.utils.get_file(origin="https://i.imgur.com/9i63gLN.jpg")
image = keras.utils.load_img(filepath)
image = np.array(image)
keras_cv.visualization.plot_image_gallery(
    np.array([image]), rows=1, cols=1, value_range=(0, 255), show=True, scale=4
)

png

接下来,让我们从分类器中获取一些预测结果

predictions = classifier.predict(np.expand_dims(image, axis=0))
 1/1 ━━━━━━━━━━━━━━━━━━━━ 4s 4s/step

预测结果以 softmax 归一化的类别排序形式给出。我们可以使用简单的 argsort 函数找到顶级类的索引

top_classes = predictions[0].argsort(axis=-1)

为了解码类别映射,我们可以构建一个从类别索引到 ImageNet 类别名称的映射。为了方便起见,我已经将 ImageNet 类别映射存储在一个 GitHub gist 中。让我们现在下载并加载它。

classes = keras.utils.get_file(
    origin="https://gist.githubusercontent.com/LukeWood/62eebcd5c5c4a4d0e0b7845780f76d55/raw/fde63e5e4c09e2fa0a3436680f436bdcb8325aac/ImagenetClassnames.json"
)
with open(classes, "rb") as f:
    classes = json.load(f)
Downloading data from https://gist.githubusercontent.com/LukeWood/62eebcd5c5c4a4d0e0b7845780f76d55/raw/fde63e5e4c09e2fa0a3436680f436bdcb8325aac/ImagenetClassnames.json
 33567/33567 ━━━━━━━━━━━━━━━━━━━━ 0s 0us/step

现在我们可以通过索引简单地查找类别名称

top_two = [classes[str(i)] for i in top_classes[-2:]]
print("Top two classes are:", top_two)
Top two classes are: ['Egyptian cat', 'velvet']

太好了!这两个结果似乎都是正确的!但是,其中一个类别是“天鹅绒”。我们试图对猫和狗进行分类。我们不关心天鹅绒毯子!

理想情况下,我们希望有一个分类器,它只执行确定图像是否为猫或狗的计算,并将所有资源都用于此任务。这可以通过微调我们自己的分类器来解决。

微调预训练分类器

当特定于我们任务的标记图像可用时,微调自定义分类器可以提高性能。如果我们想要训练一个猫与狗分类器,使用明确标记的猫与狗数据应该比通用分类器表现更好!对于许多任务,没有相关的预训练模型可用(例如,对特定于您的应用程序的图像进行分类)。

首先,让我们从加载一些数据开始

BATCH_SIZE = 32
IMAGE_SIZE = (224, 224)
AUTOTUNE = tf.data.AUTOTUNE
tfds.disable_progress_bar()

data, dataset_info = tfds.load("cats_vs_dogs", with_info=True, as_supervised=True)
train_steps_per_epoch = dataset_info.splits["train"].num_examples // BATCH_SIZE
train_dataset = data["train"]

num_classes = dataset_info.features["label"].num_classes

resizing = keras_cv.layers.Resizing(
    IMAGE_SIZE[0], IMAGE_SIZE[1], crop_to_aspect_ratio=True
)


def preprocess_inputs(image, label):
    image = tf.cast(image, tf.float32)
    # Staticly resize images as we only iterate the dataset once.
    return resizing(image), tf.one_hot(label, num_classes)


# Shuffle the dataset to increase diversity of batches.
# 10*BATCH_SIZE follows the assumption that bigger machines can handle bigger
# shuffle buffers.
train_dataset = train_dataset.shuffle(
    10 * BATCH_SIZE, reshuffle_each_iteration=True
).map(preprocess_inputs, num_parallel_calls=AUTOTUNE)
train_dataset = train_dataset.batch(BATCH_SIZE)

images = next(iter(train_dataset.take(1)))[0]
keras_cv.visualization.plot_image_gallery(images, value_range=(0, 255))

png

png

喵!

接下来让我们构建我们的模型。在预设名称中使用 imagenet 表示主干网络是在 ImageNet 数据集上预训练的。预训练的主干网络通过利用从可能大得多的数据集提取的模式来从我们的标记示例中提取更多信息。

接下来让我们组合我们的分类器

model = keras_cv.models.ImageClassifier.from_preset(
    "efficientnetv2_b0_imagenet", num_classes=2
)
model.compile(
    loss="categorical_crossentropy",
    optimizer=keras.optimizers.SGD(learning_rate=0.01),
    metrics=["accuracy"],
)
Downloading data from https://storage.googleapis.com/keras-cv/models/efficientnetv2b0/imagenet/classification-v0-notop.h5
 24029184/24029184 ━━━━━━━━━━━━━━━━━━━━ 1s 0us/step

这里我们的分类器只是一个简单的 keras.Sequential。剩下的就是调用 model.fit()

model.fit(train_dataset)
 216/727 ━━━━━━━━━━━━━━━━━━━━  15s 30ms/step - accuracy: 0.8433 - loss: 0.5113

Corrupt JPEG data: 99 extraneous bytes before marker 0xd9

 254/727 ━━━━━━━━━━━━━━━━━━━━  14s 30ms/step - accuracy: 0.8535 - loss: 0.4941

Warning: unknown JFIF revision number 0.00

 266/727 ━━━━━━━━━━━━━━━━━━━━  14s 30ms/step - accuracy: 0.8563 - loss: 0.4891

Corrupt JPEG data: 396 extraneous bytes before marker 0xd9

 310/727 ━━━━━━━━━━━━━━━━━━━━  12s 30ms/step - accuracy: 0.8651 - loss: 0.4719

Corrupt JPEG data: 162 extraneous bytes before marker 0xd9

 358/727 ━━━━━━━━━━━━━━━━━━━━  11s 30ms/step - accuracy: 0.8729 - loss: 0.4550

Corrupt JPEG data: 252 extraneous bytes before marker 0xd9
Corrupt JPEG data: 65 extraneous bytes before marker 0xd9

 374/727 ━━━━━━━━━━━━━━━━━━━━  10s 30ms/step - accuracy: 0.8752 - loss: 0.4497

Corrupt JPEG data: 1403 extraneous bytes before marker 0xd9

 534/727 ━━━━━━━━━━━━━━━━━━━━  5s 30ms/step - accuracy: 0.8921 - loss: 0.4056

Corrupt JPEG data: 214 extraneous bytes before marker 0xd9

 636/727 ━━━━━━━━━━━━━━━━━━━━  2s 30ms/step - accuracy: 0.8993 - loss: 0.3837

Corrupt JPEG data: 2226 extraneous bytes before marker 0xd9

 654/727 ━━━━━━━━━━━━━━━━━━━━  2s 30ms/step - accuracy: 0.9004 - loss: 0.3802

Corrupt JPEG data: 128 extraneous bytes before marker 0xd9

 668/727 ━━━━━━━━━━━━━━━━━━━━  1s 30ms/step - accuracy: 0.9012 - loss: 0.3775

Corrupt JPEG data: 239 extraneous bytes before marker 0xd9

 704/727 ━━━━━━━━━━━━━━━━━━━━  0s 30ms/step - accuracy: 0.9032 - loss: 0.3709

Corrupt JPEG data: 1153 extraneous bytes before marker 0xd9

 712/727 ━━━━━━━━━━━━━━━━━━━━  0s 30ms/step - accuracy: 0.9036 - loss: 0.3695

Corrupt JPEG data: 228 extraneous bytes before marker 0xd9

 727/727 ━━━━━━━━━━━━━━━━━━━━ 69s 62ms/step - accuracy: 0.9045 - loss: 0.3667

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

让我们看看微调后我们的模型表现如何

predictions = model.predict(np.expand_dims(image, axis=0))

classes = {0: "cat", 1: "dog"}
print("Top class is:", classes[predictions[0].argmax()])
 1/1 ━━━━━━━━━━━━━━━━━━━━ 3s 3s/step
Top class is: cat

太棒了——看起来模型正确地对图像进行了分类。

从头开始训练分类器

现在我们已经对分类有了初步了解,让我们完成最后一个任务:从头开始训练一个分类模型!图像分类的标准基准是 ImageNet 数据集,但是由于许可证限制,在本教程中我们将使用 CalTech 101 图像分类数据集。虽然在本指南中我们使用了更简单的 CalTech 101 数据集,但相同的训练模板可用于 ImageNet 以实现接近最先进的分数。

让我们从解决数据加载问题开始

NUM_CLASSES = 101
# Change epochs to 100~ to fully train.
EPOCHS = 1


def package_inputs(image, label):
    return {"images": image, "labels": tf.one_hot(label, NUM_CLASSES)}


train_ds, eval_ds = tfds.load(
    "caltech101", split=["train", "test"], as_supervised="true"
)
train_ds = train_ds.map(package_inputs, num_parallel_calls=tf.data.AUTOTUNE)
eval_ds = eval_ds.map(package_inputs, num_parallel_calls=tf.data.AUTOTUNE)

train_ds = train_ds.shuffle(BATCH_SIZE * 16)
 Downloading and preparing dataset 125.64 MiB (download: 125.64 MiB, generated: 132.86 MiB, total: 258.50 MiB) to /usr/local/google/home/rameshsampath/tensorflow_datasets/caltech101/3.0.1...
 Dataset caltech101 downloaded and prepared to /usr/local/google/home/rameshsampath/tensorflow_datasets/caltech101/3.0.1. Subsequent calls will reuse this data.

CalTech101 数据集中的每个图像的大小都不同,因此我们使用 ragged_batch() API 将它们批处理在一起,同时保留每个图像的形状信息。

train_ds = train_ds.ragged_batch(BATCH_SIZE)
eval_ds = eval_ds.ragged_batch(BATCH_SIZE)

batch = next(iter(train_ds.take(1)))
image_batch = batch["images"]
label_batch = batch["labels"]

keras_cv.visualization.plot_image_gallery(
    image_batch.to_tensor(),
    rows=3,
    cols=3,
    value_range=(0, 255),
    show=True,
)

png


数据增强

在我们之前的微调示例中,我们执行了静态调整大小操作,并且没有使用任何图像增强。这是因为对训练集进行一次遍历足以获得不错的结果。当训练以解决更困难的任务时,您需要在数据管道中包含数据增强。

数据增强是一种使模型对输入数据变化(如光照、裁剪和方向)具有鲁棒性的技术。KerasCV 在 keras_cv.layers API 中包含了一些最有用的增强方法。创建最佳增强管道是一门艺术,但在本指南的这一部分,我们将提供一些有关分类最佳实践的提示。

需要注意的一个问题是,在使用图像数据增强时,必须注意不要使增强后的数据分布与原始数据分布相差太远。目标是防止过拟合并提高泛化能力,但完全超出数据分布的样本只会给训练过程增加噪声。

我们将使用的第一个增强方法是 RandomFlip。此增强方法的行为或多或少与您的预期相同:它要么翻转图像,要么不翻转。虽然此增强方法在 CalTech101 和 ImageNet 中很有用,但需要注意的是,它不应用于数据分布不是垂直镜像不变的任务。此类数据集的一个示例是 MNIST 手写数字。沿垂直轴翻转 6 将使数字看起来更像 7 而不是 6,但标签仍然显示为 6

random_flip = keras_cv.layers.RandomFlip()
augmenters = [random_flip]

image_batch = random_flip(image_batch)
keras_cv.visualization.plot_image_gallery(
    image_batch.to_tensor(),
    rows=3,
    cols=3,
    value_range=(0, 255),
    show=True,
)

png

一半的图像已被翻转!

我们将使用的下一个增强方法是 RandomCropAndResize。此操作选择图像的随机子集,然后将其调整为提供的目标大小。通过使用此增强方法,我们强制我们的分类器变得空间不变。此外,此层接受一个 aspect_ratio_factor,可用于扭曲图像的长宽比。虽然这可以提高模型性能,但应谨慎使用。长宽比扭曲很容易使样本偏离训练集的原始数据分布。请记住——数据增强的目标是生成更多与训练集数据分布一致的训练样本!

RandomCropAndResize 也可以处理 tf.RaggedTensor 输入。在 CalTech101 图像数据集中,图像具有各种不同的尺寸。因此,它们不能轻易地批处理成密集的训练批次。幸运的是,RandomCropAndResize 为您处理了 Ragged 到 Dense 的转换过程!

让我们在增强方法集中添加 RandomCropAndResize

crop_and_resize = keras_cv.layers.RandomCropAndResize(
    target_size=IMAGE_SIZE,
    crop_area_factor=(0.8, 1.0),
    aspect_ratio_factor=(0.9, 1.1),
)
augmenters += [crop_and_resize]

image_batch = crop_and_resize(image_batch)
keras_cv.visualization.plot_image_gallery(
    image_batch,
    rows=3,
    cols=3,
    value_range=(0, 255),
    show=True,
)

png

太好了!我们现在正在使用一批密集图像。接下来,让我们在训练集中加入一些空间和颜色抖动。这将使我们能够生成一个对光线闪烁、阴影等具有鲁棒性的分类器。

通过改变颜色和空间特征来增强图像的方法数不胜数,但也许最经受考验的技术是 RandAugmentRandAugment 实际上是一组 10 种不同的增强方法:AutoContrastEqualizeSolarizeRandomColorJitterRandomContrastRandomBrightnessShearXShearYTranslateXTranslateY。在推理时,将为每个图像采样 num_augmentations 个增强器,并为每个增强器采样随机幅度因子。然后依次应用这些增强方法。

KerasCV 使用 augmentations_per_imagemagnitude 参数使调整这些参数变得容易!让我们试一试

rand_augment = keras_cv.layers.RandAugment(
    augmentations_per_image=3,
    value_range=(0, 255),
    magnitude=0.3,
    magnitude_stddev=0.2,
    rate=1.0,
)
augmenters += [rand_augment]

image_batch = rand_augment(image_batch)
keras_cv.visualization.plot_image_gallery(
    image_batch,
    rows=3,
    cols=3,
    value_range=(0, 255),
    show=True,
)

png

看起来不错;但我们还没有完成!如果图像缺少某个类别的关键特征怎么办?例如,如果一片叶子挡住了猫耳朵的视野,但我们的分类器只是通过观察猫的耳朵来学习分类猫的呢?

解决此问题的一种简单方法是使用 RandomCutout,它会随机删除图像的子部分

random_cutout = keras_cv.layers.RandomCutout(width_factor=0.4, height_factor=0.4)
keras_cv.visualization.plot_image_gallery(
    random_cutout(image_batch),
    rows=3,
    cols=3,
    value_range=(0, 255),
    show=True,
)

png

虽然这种方法在一定程度上解决了问题,但它可能导致分类器对特征之间的边界以及裁剪造成的黑色像素区域产生响应。

CutMix 使用更复杂(也更有效)的技术解决了相同的问题。CutMix 并没有用黑色像素替换裁剪区域,而是用来自训练集中其他图像的区域替换这些区域!替换完成后,图像的分类标签会更新为原始图像和混合图像的类别标签的混合。

在实践中,这看起来是什么样子的?让我们来看看。

cut_mix = keras_cv.layers.CutMix()
# CutMix needs to modify both images and labels
inputs = {"images": image_batch, "labels": label_batch}

keras_cv.visualization.plot_image_gallery(
    cut_mix(inputs)["images"],
    rows=3,
    cols=3,
    value_range=(0, 255),
    show=True,
)

png

让我们先不要将其添加到我们的增强器中 - 稍后会详细介绍!

接下来,让我们看看MixUp()。不幸的是,虽然MixUp()在经验上已被证明可以显著提高训练模型的鲁棒性和泛化能力,但其背后的原因尚不清楚……不过,一点炼金术也没什么坏处!

MixUp() 的工作原理是从批次中采样两张图像,然后将它们的像素强度以及它们的分类标签混合在一起。

让我们看看它的实际效果。

mix_up = keras_cv.layers.MixUp()
# MixUp needs to modify both images and labels
inputs = {"images": image_batch, "labels": label_batch}

keras_cv.visualization.plot_image_gallery(
    mix_up(inputs)["images"],
    rows=3,
    cols=3,
    value_range=(0, 255),
    show=True,
)

png

如果你仔细观察,你会发现图像已经混合在一起了。

我们不是将CutMix()MixUp() 应用于每张图像,而是选择其中一个应用于每个批次。这可以通过keras_cv.layers.RandomChoice()来实现。

cut_mix_or_mix_up = keras_cv.layers.RandomChoice([cut_mix, mix_up], batchwise=True)
augmenters += [cut_mix_or_mix_up]

现在让我们将最终的增强器应用于训练数据。

def create_augmenter_fn(augmenters):
    def augmenter_fn(inputs):
        for augmenter in augmenters:
            inputs = augmenter(inputs)
        return inputs

    return augmenter_fn


augmenter_fn = create_augmenter_fn(augmenters)
train_ds = train_ds.map(augmenter_fn, num_parallel_calls=tf.data.AUTOTUNE)

image_batch = next(iter(train_ds.take(1)))["images"]
keras_cv.visualization.plot_image_gallery(
    image_batch,
    rows=3,
    cols=3,
    value_range=(0, 255),
    show=True,
)

png

我们还需要调整评估集的大小,以便获得模型期望的图像大小的密集批次。在这种情况下,我们使用确定性的keras_cv.layers.Resizing来避免为我们的评估指标添加噪声。

inference_resizing = keras_cv.layers.Resizing(
    IMAGE_SIZE[0], IMAGE_SIZE[1], crop_to_aspect_ratio=True
)
eval_ds = eval_ds.map(inference_resizing, num_parallel_calls=tf.data.AUTOTUNE)

image_batch = next(iter(eval_ds.take(1)))["images"]
keras_cv.visualization.plot_image_gallery(
    image_batch,
    rows=3,
    cols=3,
    value_range=(0, 255),
    show=True,
)

png

最后,让我们解包数据集并准备将其传递给model.fit(),它接受一个(images, labels)元组。

def unpackage_dict(inputs):
    return inputs["images"], inputs["labels"]


train_ds = train_ds.map(unpackage_dict, num_parallel_calls=tf.data.AUTOTUNE)
eval_ds = eval_ds.map(unpackage_dict, num_parallel_calls=tf.data.AUTOTUNE)

数据增强是训练现代分类器最困难的部分。恭喜你坚持到了现在!


优化器调整

为了获得最佳性能,我们需要使用学习率调度而不是单个学习率。虽然我们不会详细介绍这里使用的带预热阶段的余弦衰减,但你可以在这里了解更多信息

def lr_warmup_cosine_decay(
    global_step,
    warmup_steps,
    hold=0,
    total_steps=0,
    start_lr=0.0,
    target_lr=1e-2,
):
    # Cosine decay
    learning_rate = (
        0.5
        * target_lr
        * (
            1
            + ops.cos(
                math.pi
                * ops.convert_to_tensor(
                    global_step - warmup_steps - hold, dtype="float32"
                )
                / ops.convert_to_tensor(
                    total_steps - warmup_steps - hold, dtype="float32"
                )
            )
        )
    )

    warmup_lr = target_lr * (global_step / warmup_steps)

    if hold > 0:
        learning_rate = ops.where(
            global_step > warmup_steps + hold, learning_rate, target_lr
        )

    learning_rate = ops.where(global_step < warmup_steps, warmup_lr, learning_rate)
    return learning_rate


class WarmUpCosineDecay(schedules.LearningRateSchedule):
    def __init__(self, warmup_steps, total_steps, hold, start_lr=0.0, target_lr=1e-2):
        super().__init__()
        self.start_lr = start_lr
        self.target_lr = target_lr
        self.warmup_steps = warmup_steps
        self.total_steps = total_steps
        self.hold = hold

    def __call__(self, step):
        lr = lr_warmup_cosine_decay(
            global_step=step,
            total_steps=self.total_steps,
            warmup_steps=self.warmup_steps,
            start_lr=self.start_lr,
            target_lr=self.target_lr,
            hold=self.hold,
        )

        return ops.where(step > self.total_steps, 0.0, lr)

WarmUpCosineDecay schedule

调度程序看起来与我们的预期一致。

接下来让我们构建这个优化器。

total_images = 9000
total_steps = (total_images // BATCH_SIZE) * EPOCHS
warmup_steps = int(0.1 * total_steps)
hold_steps = int(0.45 * total_steps)
schedule = WarmUpCosineDecay(
    start_lr=0.05,
    target_lr=1e-2,
    warmup_steps=warmup_steps,
    total_steps=total_steps,
    hold=hold_steps,
)
optimizer = optimizers.SGD(
    weight_decay=5e-4,
    learning_rate=schedule,
    momentum=0.9,
)

最后,我们现在可以构建我们的模型并调用fit()keras_cv.models.EfficientNetV2B0Backbone()keras_cv.models.EfficientNetV2Backbone.from_preset('efficientnetv2_b0') 的一个便捷别名。请注意,此预设不带任何预训练权重。

backbone = keras_cv.models.EfficientNetV2B0Backbone()
model = keras.Sequential(
    [
        backbone,
        keras.layers.GlobalMaxPooling2D(),
        keras.layers.Dropout(rate=0.5),
        keras.layers.Dense(101, activation="softmax"),
    ]
)

由于 MixUp() 和 CutMix() 生成的标签在某种程度上是人工的,因此我们采用标签平滑来防止模型过度拟合这些增强过程产生的伪影。

loss = losses.CategoricalCrossentropy(label_smoothing=0.1)

让我们编译我们的模型。

model.compile(
    loss=loss,
    optimizer=optimizer,
    metrics=[
        metrics.CategoricalAccuracy(),
        metrics.TopKCategoricalAccuracy(k=5),
    ],
)

最后调用 fit()。

model.fit(
    train_ds,
    epochs=EPOCHS,
    validation_data=eval_ds,
)
 96/96 ━━━━━━━━━━━━━━━━━━━━ 65s 462ms/step - categorical_accuracy: 0.0068 - loss: 6.6096 - top_k_categorical_accuracy: 0.0497 - val_categorical_accuracy: 0.0122 - val_loss: 4.7151 - val_top_k_categorical_accuracy: 0.1596

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

恭喜!你现在知道如何在 KerasCV 中从头开始训练一个强大的图像分类器了。根据你的应用程序中标记数据的可用性,从头开始训练可能比使用迁移学习以及上面讨论的数据增强更强大,也可能不更强大。对于较小的数据集,预训练模型通常可以产生较高的准确率并更快地收敛。


结论

虽然图像分类可能是计算机视觉中最简单的问题,但现代环境中包含许多复杂的组件。幸运的是,KerasCV 提供了强大的、生产级的 API,使在代码中一行就能组合大多数这些组件成为可能。通过使用 KerasCV 的ImageClassifier API、预训练权重和 KerasCV 数据增强,你可以在几百行代码中组装训练强大分类器所需的一切!

作为后续练习,请尝试以下操作:

  • 在你自己的数据集上微调 KerasCV 分类器。
  • 了解更多关于KerasCV 的数据增强
  • 查看我们如何在ImageNet上训练我们的模型。