作者: lukewood
建立日期 03/28/2023
上次修改日期 03/28/2023
說明:使用 KerasCV 訓練強大的影像分類器。
分類是為給定輸入影像預測類別標籤的過程。雖然分類是一個相對簡單的電腦視覺任務,但現代方法仍然由幾個複雜的組件構成。幸運的是,KerasCV 提供了建構常用組件的 API。
本指南將示範 KerasCV 如何以模組化方法解決三個複雜程度的影像分類問題
KerasCV 使用 Keras 3 來搭配 TensorFlow、PyTorch 或 Jax 使用。在以下指南中,我們將使用 jax
後端。本指南在 TensorFlow 或 PyTorch 後端中無需任何變更即可執行,只需更新下方的 KERAS_BACKEND
即可。
我們使用 Keras 的官方吉祥物 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://tensorflow.dev.org.tw/api_docs/python/tf/data) and its preprocessing functions
import tensorflow as tf
import tensorflow_datasets as tfds
讓我們從最簡單的 KerasCV API 開始:預先訓練好的分類器。在本範例中,我們將建構一個在 ImageNet 資料集上預先訓練好的分類器。我們將使用這個模型來解決歷史悠久的「貓或狗」問題。
KerasCV 中最高階的模組是「任務」。「任務」是由(通常是預先訓練好的)骨幹網路模型和特定任務層組成的 keras.Model
。以下是以 EfficientNetV2B0 骨幹網路為例,使用 keras_cv.models.ImageClassifier
的範例。
在建構影像分類流程時,EfficientNetV2B0 是一個很棒的起點模型。這種架構設法在使用 7M 的參數計數時,還能達到很高的準確度。如果 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
)
接下來,讓我們從分類器中取得一些預測
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))
喵嗚!
接下來,讓我們建構我們的模型。預設名稱中使用 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 ━━━━━[37m━━━━━━━━━━━━━━━ 15s 30ms/step - accuracy: 0.8433 - loss: 0.5113
Corrupt JPEG data: 99 extraneous bytes before marker 0xd9
254/727 ━━━━━━[37m━━━━━━━━━━━━━━ 14s 30ms/step - accuracy: 0.8535 - loss: 0.4941
Warning: unknown JFIF revision number 0.00
266/727 ━━━━━━━[37m━━━━━━━━━━━━━ 14s 30ms/step - accuracy: 0.8563 - loss: 0.4891
Corrupt JPEG data: 396 extraneous bytes before marker 0xd9
310/727 ━━━━━━━━[37m━━━━━━━━━━━━ 12s 30ms/step - accuracy: 0.8651 - loss: 0.4719
Corrupt JPEG data: 162 extraneous bytes before marker 0xd9
358/727 ━━━━━━━━━[37m━━━━━━━━━━━ 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 ━━━━━━━━━━[37m━━━━━━━━━━ 10s 30ms/step - accuracy: 0.8752 - loss: 0.4497
Corrupt JPEG data: 1403 extraneous bytes before marker 0xd9
534/727 ━━━━━━━━━━━━━━[37m━━━━━━ 5s 30ms/step - accuracy: 0.8921 - loss: 0.4056
Corrupt JPEG data: 214 extraneous bytes before marker 0xd9
636/727 ━━━━━━━━━━━━━━━━━[37m━━━ 2s 30ms/step - accuracy: 0.8993 - loss: 0.3837
Corrupt JPEG data: 2226 extraneous bytes before marker 0xd9
654/727 ━━━━━━━━━━━━━━━━━[37m━━━ 2s 30ms/step - accuracy: 0.9004 - loss: 0.3802
Corrupt JPEG data: 128 extraneous bytes before marker 0xd9
668/727 ━━━━━━━━━━━━━━━━━━[37m━━ 1s 30ms/step - accuracy: 0.9012 - loss: 0.3775
Corrupt JPEG data: 239 extraneous bytes before marker 0xd9
704/727 ━━━━━━━━━━━━━━━━━━━[37m━ 0s 30ms/step - accuracy: 0.9032 - loss: 0.3709
Corrupt JPEG data: 1153 extraneous bytes before marker 0xd9
712/727 ━━━━━━━━━━━━━━━━━━━[37m━ 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,
)
在我們之前的微調範例中,我們執行了靜態調整大小操作,並且沒有利用任何圖像增強。這是因為單次通過訓練集就足以獲得不錯的結果。當訓練解決更困難的任務時,您需要在資料管道中包含資料增強。
資料增強是一種使您的模型對輸入資料的變化(例如光線、裁剪和方向)具有穩健性的技術。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,
)
一半的圖像已被翻轉!
我們將使用的下一個增強是 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,
)
太棒了!我們現在正在處理一批密集的圖像。接下來,讓我們在我們的訓練集中包含一些基於空間和顏色的抖動。這將使我們能夠生成對光線閃爍、陰影等具有穩健性的分類器。
有無限多種方法可以透過改變顏色和空間特徵來增強圖像,但可能經過最多實戰測試的技術是 RandAugment
。RandAugment
實際上是由 10 種不同的增強功能組成:AutoContrast
、Equalize
、Solarize
、RandomColorJitter
、RandomContrast
、RandomBrightness
、ShearX
、ShearY
、TranslateX
和 TranslateY
。在推論時,會為每個圖像採樣 num_augmentations
個增強器,並為每個增強器採樣隨機的幅度因子。然後依序套用這些增強功能。
KerasCV 透過 augmentations_per_image
和 magnitude
參數,讓調整這些參數變得簡單!讓我們來試試看
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,
)
看起來很棒;但我們還沒做完!如果圖像缺少類別的一個關鍵特徵怎麼辦?例如,如果一片葉子擋住了貓耳朵的視線,但我們的分類器學會了僅僅透過觀察貓的耳朵來對貓進行分類,那該怎麼辦?
解決這個問題的一個簡單方法是使用 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,
)
雖然這在一定程度上解決了這個問題,但它可能會導致分類器對特徵邊界和由 cutout 造成的黑色像素區域產生反應。
CutMix
透過使用更複雜(也更有效)的技術來解決相同的問題。CutMix
不是用黑色像素替換 cutout 區域,而是用從訓練集中採樣的另一個圖像的區域替換這些區域!在替換之後,圖像的分類標籤會更新為原始圖像和混合圖像的類別標籤的混合。
這在實務中看起來像什麼?讓我們來看看
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,
)
讓我們先不要把它加到我們的增強器中——稍後會詳細說明!
接下來,讓我們來看看 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,
)
如果仔細觀察,您會發現圖像已經混合在一起。
我們沒有對每個圖像套用 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,
)
我們還需要調整評估集的大小,以獲得模型預期圖像大小的密集批次。在這種情況下,我們使用確定性的 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,
)
最後,讓我們解開數據集並準備將它們傳遞給 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)
排程看起來符合我們的預期。
接下來讓我們構造這個優化器
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 資料增強功能,您可以在幾百行程式碼中組裝訓練強大分類器所需的一切!
作為後續練習,請嘗試以下操作