Zobacz na TensorFlow.org | Uruchom w Google Colab | Wyświetl źródło na GitHub | Pobierz notatnik |
Ten samouczek pokazuje, jak klasyfikować obrazy kwiatów. Tworzy klasyfikator obrazu przy użyciu modelu tf.keras.Sequential
i ładuje dane przy użyciu tf.keras.utils.image_dataset_from_directory
. Zdobędziesz praktyczne doświadczenie z następującymi koncepcjami:
- Wydajne ładowanie zestawu danych z dysku.
- Identyfikowanie nadmiernego dopasowania i stosowanie technik w celu jego złagodzenia, w tym zwiększania i usuwania danych.
Ten samouczek jest zgodny z podstawowym przepływem pracy uczenia maszynowego:
- Zbadaj i zrozum dane
- Zbuduj potok wejściowy
- Zbuduj model
- Trenuj modelkę
- Przetestuj model
- Popraw model i powtórz proces
Importuj TensorFlow i inne biblioteki
import matplotlib.pyplot as plt
import numpy as np
import os
import PIL
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
from tensorflow.keras.models import Sequential
Pobierz i poznaj zbiór danych
Ten samouczek wykorzystuje zbiór danych zawierający około 3700 zdjęć kwiatów. Zestaw danych zawiera pięć podkatalogów, po jednym na klasę:
flower_photo/
daisy/
dandelion/
roses/
sunflowers/
tulips/
import pathlib
dataset_url = "https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz"
data_dir = tf.keras.utils.get_file('flower_photos', origin=dataset_url, untar=True)
data_dir = pathlib.Path(data_dir)
Po pobraniu powinieneś mieć teraz dostępną kopię zbioru danych. Łącznie jest 3670 obrazów:
image_count = len(list(data_dir.glob('*/*.jpg')))
print(image_count)
3670
Oto kilka róż:
roses = list(data_dir.glob('roses/*'))
PIL.Image.open(str(roses[0]))
PIL.Image.open(str(roses[1]))
I tulipany:
tulips = list(data_dir.glob('tulips/*'))
PIL.Image.open(str(tulips[0]))
PIL.Image.open(str(tulips[1]))
Załaduj dane za pomocą narzędzia Keras
Załadujmy te obrazy z dysku za pomocą przydatnego narzędzia tf.keras.utils.image_dataset_from_directory
. Spowoduje to przejście z katalogu obrazów na dysku do tf.data.Dataset
w zaledwie kilku wierszach kodu. Jeśli chcesz, możesz również napisać własny kod ładowania danych od podstaw, odwiedzając samouczek ładowania i wstępnego przetwarzania obrazów .
Utwórz zbiór danych
Zdefiniuj kilka parametrów modułu ładującego:
batch_size = 32
img_height = 180
img_width = 180
Dobrą praktyką jest używanie podziału walidacji podczas opracowywania modelu. Użyjmy 80% obrazów do szkolenia, a 20% do walidacji.
train_ds = tf.keras.utils.image_dataset_from_directory(
data_dir,
validation_split=0.2,
subset="training",
seed=123,
image_size=(img_height, img_width),
batch_size=batch_size)
Found 3670 files belonging to 5 classes. Using 2936 files for training.
val_ds = tf.keras.utils.image_dataset_from_directory(
data_dir,
validation_split=0.2,
subset="validation",
seed=123,
image_size=(img_height, img_width),
batch_size=batch_size)
Found 3670 files belonging to 5 classes. Using 734 files for validation.
Nazwy klas można znaleźć w atrybucie class_names
w tych zestawach danych. Odpowiadają one nazwom katalogów w kolejności alfabetycznej.
class_names = train_ds.class_names
print(class_names)
['daisy', 'dandelion', 'roses', 'sunflowers', 'tulips']
Wizualizuj dane
Oto pierwsze dziewięć obrazów ze zbioru danych treningowych:
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 10))
for images, labels in train_ds.take(1):
for i in range(9):
ax = plt.subplot(3, 3, i + 1)
plt.imshow(images[i].numpy().astype("uint8"))
plt.title(class_names[labels[i]])
plt.axis("off")
Wytrenujesz model przy użyciu tych zestawów danych, przekazując je za chwilę do Model.fit
. Jeśli chcesz, możesz również ręcznie iterować zestaw danych i pobierać partie obrazów:
for image_batch, labels_batch in train_ds:
print(image_batch.shape)
print(labels_batch.shape)
break
(32, 180, 180, 3) (32,)
image_batch
to tensor kształtu (32, 180, 180, 3)
. Jest to partia 32 obrazów o kształcie 180x180x3
(ostatni wymiar dotyczy kanałów kolorów RGB). label_batch
jest tensorem kształtu (32,)
, są to etykiety odpowiadające 32 obrazom.
Możesz wywołać .numpy()
na image_batch
i labels_batch
, aby przekonwertować je na numpy.ndarray
.
Skonfiguruj zbiór danych pod kątem wydajności
Upewnij się, że używasz buforowanego pobierania z wyprzedzeniem, aby móc uzyskiwać dane z dysku bez blokowania operacji we/wy. Oto dwie ważne metody, których powinieneś użyć podczas ładowania danych:
-
Dataset.cache
przechowuje obrazy w pamięci po ich załadowaniu z dysku w pierwszej epoce. Zapewni to, że zestaw danych nie stanie się wąskim gardłem podczas trenowania modelu. Jeśli zestaw danych jest zbyt duży, aby zmieścić się w pamięci, możesz również użyć tej metody do utworzenia wydajnej pamięci podręcznej na dysku. -
Dataset.prefetch
nakłada się na wstępne przetwarzanie danych i wykonywanie modelu podczas uczenia.
Zainteresowani czytelnicy mogą dowiedzieć się więcej o obu metodach, a także o buforowaniu danych na dysku w sekcji Pobieranie z wyprzedzeniem w przewodniku Lepsza wydajność z przewodnikiem po interfejsie API tf.data .
AUTOTUNE = tf.data.AUTOTUNE
train_ds = train_ds.cache().shuffle(1000).prefetch(buffer_size=AUTOTUNE)
val_ds = val_ds.cache().prefetch(buffer_size=AUTOTUNE)
Standaryzuj dane
Wartości kanałów RGB mieszczą się w zakresie [0, 255]
. Nie jest to idealne rozwiązanie dla sieci neuronowej; ogólnie powinieneś starać się, aby twoje wartości wejściowe były małe.
Tutaj ustandaryzujesz wartości tak, aby mieściły się w zakresie [0, 1]
, używając tf.keras.layers.Rescaling
:
normalization_layer = layers.Rescaling(1./255)
Istnieją dwa sposoby wykorzystania tej warstwy. Możesz zastosować go do zestawu danych, wywołując Dataset.map
:
normalized_ds = train_ds.map(lambda x, y: (normalization_layer(x), y))
image_batch, labels_batch = next(iter(normalized_ds))
first_image = image_batch[0]
# Notice the pixel values are now in `[0,1]`.
print(np.min(first_image), np.max(first_image))
0.0 1.0
Możesz też dołączyć warstwę do definicji modelu, co może uprościć wdrażanie. Użyjmy tutaj drugiego podejścia.
Stwórz model
Model sekwencyjny składa się z trzech bloków konwolucji ( tf.keras.layers.Conv2D
) z maksymalną warstwą puli ( tf.keras.layers.MaxPooling2D
) w każdym z nich. Istnieje w pełni połączona warstwa ( tf.keras.layers.Dense
) z 128 jednostkami na wierzchu, która jest aktywowana przez funkcję aktywacji ReLU ( 'relu'
). Ten model nie został dostrojony pod kątem wysokiej dokładności — celem tego samouczka jest pokazanie standardowego podejścia.
num_classes = len(class_names)
model = Sequential([
layers.Rescaling(1./255, input_shape=(img_height, img_width, 3)),
layers.Conv2D(16, 3, padding='same', activation='relu'),
layers.MaxPooling2D(),
layers.Conv2D(32, 3, padding='same', activation='relu'),
layers.MaxPooling2D(),
layers.Conv2D(64, 3, padding='same', activation='relu'),
layers.MaxPooling2D(),
layers.Flatten(),
layers.Dense(128, activation='relu'),
layers.Dense(num_classes)
])
Skompiluj model
Na potrzeby tego samouczka wybierz optymalizator tf.keras.optimizers.Adam
i funkcję utraty tf.keras.losses.SparseCategoricalCrossentropy
. Aby wyświetlić dokładność uczenia i walidacji dla każdej epoki uczenia, przekaż argument metrics
do Model.compile
.
model.compile(optimizer='adam',
loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
metrics=['accuracy'])
Podsumowanie modelu
Wyświetl wszystkie warstwy sieci za pomocą metody Model.summary
modelu:
model.summary()
Model: "sequential" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= rescaling_1 (Rescaling) (None, 180, 180, 3) 0 conv2d (Conv2D) (None, 180, 180, 16) 448 max_pooling2d (MaxPooling2D (None, 90, 90, 16) 0 ) conv2d_1 (Conv2D) (None, 90, 90, 32) 4640 max_pooling2d_1 (MaxPooling (None, 45, 45, 32) 0 2D) conv2d_2 (Conv2D) (None, 45, 45, 64) 18496 max_pooling2d_2 (MaxPooling (None, 22, 22, 64) 0 2D) flatten (Flatten) (None, 30976) 0 dense (Dense) (None, 128) 3965056 dense_1 (Dense) (None, 5) 645 ================================================================= Total params: 3,989,285 Trainable params: 3,989,285 Non-trainable params: 0 _________________________________________________________________
Trenuj modelkę
epochs=10
history = model.fit(
train_ds,
validation_data=val_ds,
epochs=epochs
)
Epoch 1/10 92/92 [==============================] - 3s 16ms/step - loss: 1.2769 - accuracy: 0.4489 - val_loss: 1.0457 - val_accuracy: 0.5804 Epoch 2/10 92/92 [==============================] - 1s 11ms/step - loss: 0.9386 - accuracy: 0.6328 - val_loss: 0.9665 - val_accuracy: 0.6158 Epoch 3/10 92/92 [==============================] - 1s 11ms/step - loss: 0.7390 - accuracy: 0.7200 - val_loss: 0.8768 - val_accuracy: 0.6540 Epoch 4/10 92/92 [==============================] - 1s 11ms/step - loss: 0.5649 - accuracy: 0.7963 - val_loss: 0.9258 - val_accuracy: 0.6540 Epoch 5/10 92/92 [==============================] - 1s 11ms/step - loss: 0.3662 - accuracy: 0.8733 - val_loss: 1.1734 - val_accuracy: 0.6267 Epoch 6/10 92/92 [==============================] - 1s 11ms/step - loss: 0.2169 - accuracy: 0.9343 - val_loss: 1.3728 - val_accuracy: 0.6499 Epoch 7/10 92/92 [==============================] - 1s 11ms/step - loss: 0.1191 - accuracy: 0.9629 - val_loss: 1.3791 - val_accuracy: 0.6471 Epoch 8/10 92/92 [==============================] - 1s 11ms/step - loss: 0.0497 - accuracy: 0.9871 - val_loss: 1.8002 - val_accuracy: 0.6390 Epoch 9/10 92/92 [==============================] - 1s 11ms/step - loss: 0.0372 - accuracy: 0.9922 - val_loss: 1.8545 - val_accuracy: 0.6390 Epoch 10/10 92/92 [==============================] - 1s 11ms/step - loss: 0.0715 - accuracy: 0.9813 - val_loss: 2.0656 - val_accuracy: 0.6049
Wizualizuj wyniki treningu
Utwórz wykresy strat i dokładności w zestawach uczących i walidacyjnych:
acc = history.history['accuracy']
val_acc = history.history['val_accuracy']
loss = history.history['loss']
val_loss = history.history['val_loss']
epochs_range = range(epochs)
plt.figure(figsize=(8, 8))
plt.subplot(1, 2, 1)
plt.plot(epochs_range, acc, label='Training Accuracy')
plt.plot(epochs_range, val_acc, label='Validation Accuracy')
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')
plt.subplot(1, 2, 2)
plt.plot(epochs_range, loss, label='Training Loss')
plt.plot(epochs_range, val_loss, label='Validation Loss')
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')
plt.show()
Wykresy pokazują, że dokładność uczenia i dokładność walidacji różnią się znacznie, a model osiągnął tylko około 60% dokładność w zbiorze walidacyjnym.
Sprawdźmy, co poszło nie tak i spróbujmy zwiększyć ogólną wydajność modelu.
Overfitting
Na powyższych wykresach dokładność uczenia wzrasta liniowo w czasie, podczas gdy dokładność walidacji zatrzymuje się na około 60% w procesie uczenia. Zauważalna jest również różnica w dokładności między treningiem a dokładnością walidacji — oznaka nadmiernego dopasowania .
Gdy istnieje niewielka liczba przykładów uczących, model czasami uczy się na podstawie szumów lub niechcianych szczegółów z przykładów uczących — w takim stopniu, że ma to negatywny wpływ na wydajność modelu w nowych przykładach. Zjawisko to znane jest jako overfitting. Oznacza to, że model będzie miał trudności z uogólnieniem na nowym zbiorze danych.
Istnieje wiele sposobów na walkę z nadmiernym dopasowaniem w procesie treningowym. W tym samouczku użyjesz rozszerzania danych i dodasz Dropout do swojego modelu.
Rozszerzanie danych
Overfitting zwykle występuje, gdy istnieje niewielka liczba przykładów treningowych. Rozszerzanie danych polega na generowaniu dodatkowych danych treningowych z istniejących przykładów poprzez powiększanie ich za pomocą losowych przekształceń, które dają wiarygodnie wyglądające obrazy. Pomaga to uwidocznić model w większej liczbie aspektów danych i lepiej uogólnić.
Zaimplementujesz rozszerzenie danych za pomocą następujących warstw przetwarzania wstępnego Keras: tf.keras.layers.RandomFlip
, tf.keras.layers.RandomRotation
i tf.keras.layers.RandomZoom
. Można je umieścić w modelu, podobnie jak inne warstwy, i uruchomić na GPU.
data_augmentation = keras.Sequential(
[
layers.RandomFlip("horizontal",
input_shape=(img_height,
img_width,
3)),
layers.RandomRotation(0.1),
layers.RandomZoom(0.1),
]
)
Zobrazujmy, jak wygląda kilka rozszerzonych przykładów, stosując kilkakrotne rozszerzenie danych do tego samego obrazu:
plt.figure(figsize=(10, 10))
for images, _ in train_ds.take(1):
for i in range(9):
augmented_images = data_augmentation(images)
ax = plt.subplot(3, 3, i + 1)
plt.imshow(augmented_images[0].numpy().astype("uint8"))
plt.axis("off")
Za chwilę użyjesz augmentacji danych, aby wytrenować model.
Spadkowicz
Inną techniką zmniejszania nadmiernego dopasowania jest wprowadzenie do sieci regularyzacji przerywania .
Kiedy zastosujesz dropout do warstwy, losowo (poprzez ustawienie aktywacji na zero) zostanie usunięta liczba jednostek wyjściowych z warstwy podczas procesu uczenia. Dropout przyjmuje jako wartość wejściową liczbę ułamkową w postaci takiej jak 0,1, 0,2, 0,4 itd. Oznacza to losowe usunięcie 10%, 20% lub 40% jednostek wyjściowych z nałożonej warstwy.
Stwórzmy nową sieć neuronową za pomocą tf.keras.layers.Dropout
przed trenowaniem jej za pomocą rozszerzonych obrazów:
model = Sequential([
data_augmentation,
layers.Rescaling(1./255),
layers.Conv2D(16, 3, padding='same', activation='relu'),
layers.MaxPooling2D(),
layers.Conv2D(32, 3, padding='same', activation='relu'),
layers.MaxPooling2D(),
layers.Conv2D(64, 3, padding='same', activation='relu'),
layers.MaxPooling2D(),
layers.Dropout(0.2),
layers.Flatten(),
layers.Dense(128, activation='relu'),
layers.Dense(num_classes)
])
Skompiluj i trenuj model
model.compile(optimizer='adam',
loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
metrics=['accuracy'])
model.summary()
Model: "sequential_2" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= sequential_1 (Sequential) (None, 180, 180, 3) 0 rescaling_2 (Rescaling) (None, 180, 180, 3) 0 conv2d_3 (Conv2D) (None, 180, 180, 16) 448 max_pooling2d_3 (MaxPooling (None, 90, 90, 16) 0 2D) conv2d_4 (Conv2D) (None, 90, 90, 32) 4640 max_pooling2d_4 (MaxPooling (None, 45, 45, 32) 0 2D) conv2d_5 (Conv2D) (None, 45, 45, 64) 18496 max_pooling2d_5 (MaxPooling (None, 22, 22, 64) 0 2D) dropout (Dropout) (None, 22, 22, 64) 0 flatten_1 (Flatten) (None, 30976) 0 dense_2 (Dense) (None, 128) 3965056 dense_3 (Dense) (None, 5) 645 ================================================================= Total params: 3,989,285 Trainable params: 3,989,285 Non-trainable params: 0 _________________________________________________________________
epochs = 15
history = model.fit(
train_ds,
validation_data=val_ds,
epochs=epochs
)
Epoch 1/15 92/92 [==============================] - 2s 14ms/step - loss: 1.3840 - accuracy: 0.3999 - val_loss: 1.0967 - val_accuracy: 0.5518 Epoch 2/15 92/92 [==============================] - 1s 12ms/step - loss: 1.1152 - accuracy: 0.5395 - val_loss: 1.1123 - val_accuracy: 0.5545 Epoch 3/15 92/92 [==============================] - 1s 12ms/step - loss: 1.0049 - accuracy: 0.6052 - val_loss: 0.9544 - val_accuracy: 0.6253 Epoch 4/15 92/92 [==============================] - 1s 12ms/step - loss: 0.9452 - accuracy: 0.6257 - val_loss: 0.9681 - val_accuracy: 0.6213 Epoch 5/15 92/92 [==============================] - 1s 12ms/step - loss: 0.8804 - accuracy: 0.6591 - val_loss: 0.8450 - val_accuracy: 0.6798 Epoch 6/15 92/92 [==============================] - 1s 12ms/step - loss: 0.8001 - accuracy: 0.6945 - val_loss: 0.8715 - val_accuracy: 0.6594 Epoch 7/15 92/92 [==============================] - 1s 12ms/step - loss: 0.7736 - accuracy: 0.6965 - val_loss: 0.8059 - val_accuracy: 0.6935 Epoch 8/15 92/92 [==============================] - 1s 12ms/step - loss: 0.7477 - accuracy: 0.7078 - val_loss: 0.8292 - val_accuracy: 0.6812 Epoch 9/15 92/92 [==============================] - 1s 12ms/step - loss: 0.7053 - accuracy: 0.7251 - val_loss: 0.7743 - val_accuracy: 0.6989 Epoch 10/15 92/92 [==============================] - 1s 12ms/step - loss: 0.6884 - accuracy: 0.7340 - val_loss: 0.7867 - val_accuracy: 0.6907 Epoch 11/15 92/92 [==============================] - 1s 12ms/step - loss: 0.6536 - accuracy: 0.7469 - val_loss: 0.7732 - val_accuracy: 0.6785 Epoch 12/15 92/92 [==============================] - 1s 12ms/step - loss: 0.6456 - accuracy: 0.7500 - val_loss: 0.7801 - val_accuracy: 0.6907 Epoch 13/15 92/92 [==============================] - 1s 12ms/step - loss: 0.5941 - accuracy: 0.7735 - val_loss: 0.7185 - val_accuracy: 0.7330 Epoch 14/15 92/92 [==============================] - 1s 12ms/step - loss: 0.5824 - accuracy: 0.7735 - val_loss: 0.7282 - val_accuracy: 0.7357 Epoch 15/15 92/92 [==============================] - 1s 12ms/step - loss: 0.5771 - accuracy: 0.7851 - val_loss: 0.7308 - val_accuracy: 0.7343
Wizualizuj wyniki treningu
Po zastosowaniu augmentacji danych i tf.keras.layers.Dropout
, przeuczenie jest mniejsze niż wcześniej, a dokładność treningu i walidacji jest ściślej wyrównana:
acc = history.history['accuracy']
val_acc = history.history['val_accuracy']
loss = history.history['loss']
val_loss = history.history['val_loss']
epochs_range = range(epochs)
plt.figure(figsize=(8, 8))
plt.subplot(1, 2, 1)
plt.plot(epochs_range, acc, label='Training Accuracy')
plt.plot(epochs_range, val_acc, label='Validation Accuracy')
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')
plt.subplot(1, 2, 2)
plt.plot(epochs_range, loss, label='Training Loss')
plt.plot(epochs_range, val_loss, label='Validation Loss')
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')
plt.show()
Przewiduj nowe dane
Na koniec użyjmy naszego modelu do sklasyfikowania obrazu, który nie został uwzględniony w zestawach szkoleniowych lub walidacyjnych.
sunflower_url = "https://storage.googleapis.com/download.tensorflow.org/example_images/592px-Red_sunflower.jpg"
sunflower_path = tf.keras.utils.get_file('Red_sunflower', origin=sunflower_url)
img = tf.keras.utils.load_img(
sunflower_path, target_size=(img_height, img_width)
)
img_array = tf.keras.utils.img_to_array(img)
img_array = tf.expand_dims(img_array, 0) # Create a batch
predictions = model.predict(img_array)
score = tf.nn.softmax(predictions[0])
print(
"This image most likely belongs to {} with a {:.2f} percent confidence."
.format(class_names[np.argmax(score)], 100 * np.max(score))
)
Downloading data from https://storage.googleapis.com/download.tensorflow.org/example_images/592px-Red_sunflower.jpg 122880/117948 [===============================] - 0s 0us/step 131072/117948 [=================================] - 0s 0us/step This image most likely belongs to sunflowers with a 89.13 percent confidence.