Klasyfikacja obrazu

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:

  1. Zbadaj i zrozum dane
  2. Zbuduj potok wejściowy
  3. Zbuduj model
  4. Trenuj modelkę
  5. Przetestuj model
  6. 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]))

png

PIL.Image.open(str(roses[1]))

png

I tulipany:

tulips = list(data_dir.glob('tulips/*'))
PIL.Image.open(str(tulips[0]))

png

PIL.Image.open(str(tulips[1]))

png

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")

png

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()

png

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")

png

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()

png

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.