Eine Frage haben? Verbinden Sie sich mit der Community im TensorFlow Forum Visit Forum

Bildklassifizierung

Auf TensorFlow.org ansehen In Google Colab ausführen Quelle auf GitHub anzeigen Notizbuch herunterladen

Dieses Tutorial zeigt, wie Sie Bilder von Blumen klassifizieren. Es erzeugt ein Bild Klassifikator ein mit keras.Sequential Modell und lädt Daten mit preprocessing.image_dataset_from_directory . Sie sammeln praktische Erfahrungen mit folgenden Konzepten:

  • Effizientes Laden eines Datensatzes von der Festplatte.
  • Identifizieren von Überanpassung und Anwendung von Techniken, um diese zu mindern, einschließlich Datenerweiterung und Dropout.

Dieses Tutorial folgt einem grundlegenden Machine-Learning-Workflow:

  1. Daten untersuchen und verstehen
  2. Erstellen Sie eine Eingabepipeline
  3. Baue das Modell
  4. Trainiere das Modell
  5. Testen Sie das Modell
  6. Verbessern Sie das Modell und wiederholen Sie den Vorgang

TensorFlow und andere Bibliotheken importieren

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

Laden Sie den Datensatz herunter und erkunden Sie ihn

Dieses Tutorial verwendet einen Datensatz von etwa 3.700 Blumenfotos. Der Datensatz enthält 5 Unterverzeichnisse, eines pro Klasse:

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)

Nach dem Download sollten Sie nun eine Kopie des Datensatzes zur Verfügung haben. Es gibt insgesamt 3.670 Bilder:

image_count = len(list(data_dir.glob('*/*.jpg')))
print(image_count)
3670

Hier einige Rosen:

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

png

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

png

Und einige Tulpen:

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

png

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

png

Laden mit keras.preprocessing

Laden wir diese Bilder mit dem hilfreichen Dienstprogramm image_dataset_from_directory von der Festplatte. Dies führt Sie in nur wenigen Codezeilen von einem Verzeichnis mit Bildern auf der Festplatte zu einemtf.data.Dataset . Wenn Sie möchten, können Sie auch Ihren eigenen Code zum Laden von Daten von Grund auf neu schreiben, indem Sie das Tutorial zum Laden von Bildern besuchen.

Erstellen Sie einen Datensatz

Definieren Sie einige Parameter für den Loader:

batch_size = 32
img_height = 180
img_width = 180

Es empfiehlt sich, bei der Entwicklung Ihres Modells einen Validierungssplit zu verwenden. Lassen Sie uns 80 % der Bilder für das Training und 20 % für die Validierung verwenden.

train_ds = tf.keras.preprocessing.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.preprocessing.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.

Sie finden die Klassennamen im Attribut class_names dieser Datensätze. Diese entsprechen den Verzeichnisnamen in alphabetischer Reihenfolge.

class_names = train_ds.class_names
print(class_names)
['daisy', 'dandelion', 'roses', 'sunflowers', 'tulips']

Visualisieren Sie die Daten

Hier sind die ersten 9 Bilder aus dem Trainingsdatensatz.

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

Sie trainieren ein Modell mit diesen Datensätzen, indem Sie sie gleich an model.fit . Wenn Sie möchten, können Sie den Datensatz auch manuell durchlaufen und Bildstapel abrufen:

for image_batch, labels_batch in train_ds:
  print(image_batch.shape)
  print(labels_batch.shape)
  break
(32, 180, 180, 3)
(32,)

Der image_batch ist ein Tensor der Form (32, 180, 180, 3) . Dies ist ein Stapel von 32 Bildern der Form 180x180x3 (die letzte Dimension bezieht sich auf die RGB-Farbkanäle). Der label_batch ist ein Tensor der Form (32,) , dies sind entsprechende Labels zu den 32 Bildern.

Sie können .numpy() für die Tensoren image_batch und labels_batch , um sie in ein numpy.ndarray zu konvertieren.

Konfigurieren Sie das Dataset für die Leistung

Stellen Sie sicher, dass Sie gepuffertes Prefetching verwenden, damit Sie Daten von der Festplatte abrufen können, ohne dass die E/A blockiert wird. Dies sind zwei wichtige Methoden, die Sie beim Laden von Daten verwenden sollten.

Dataset.cache() behält die Bilder im Speicher, nachdem sie während der ersten Epoche von der Festplatte geladen wurden. Dadurch wird sichergestellt, dass das Dataset beim Trainieren Ihres Modells nicht zu einem Engpass wird. Wenn Ihr Dataset zu groß ist, um in den Arbeitsspeicher zu passen, können Sie mit dieser Methode auch einen leistungsfähigen Cache auf der Festplatte erstellen.

Dataset.prefetch() überlappt die Datenvorverarbeitung und die Modellausführung während des Trainings.

Interessierte Leser können mehr über beide Methoden sowie das Zwischenspeichern von Daten auf der Festplatte im Leitfaden zur Datenleistung erfahren.

AUTOTUNE = tf.data.AUTOTUNE

train_ds = train_ds.cache().shuffle(1000).prefetch(buffer_size=AUTOTUNE)
val_ds = val_ds.cache().prefetch(buffer_size=AUTOTUNE)

Standardisieren Sie die Daten

Die RGB-Kanalwerte liegen im Bereich [0, 255] . Dies ist für ein neuronales Netzwerk nicht ideal; Im Allgemeinen sollten Sie versuchen, Ihre Eingabewerte klein zu halten. Hier werden Sie Werte im Bereich [0, 1] standardisieren [0, 1] indem Sie eine Rescaling-Ebene verwenden.

normalization_layer = layers.experimental.preprocessing.Rescaling(1./255)

Es gibt zwei Möglichkeiten, diese Ebene zu verwenden. Sie können es auf das Dataset anwenden, indem Sie map aufrufen:

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 pixels values are now in `[0,1]`.
print(np.min(first_image), np.max(first_image))
0.0 0.9997713

Oder Sie können den Layer in Ihre Modelldefinition einschließen, was die Bereitstellung vereinfachen kann. Lassen Sie uns hier den zweiten Ansatz verwenden.

Modell erstellen

Das Modell besteht aus drei Faltungsblöcken mit jeweils einer Max-Pool-Schicht. Darüber gibt es eine voll verbundene Ebene mit 128 Einheiten, die durch eine relu Aktivierungsfunktion aktiviert wird. Dieses Modell wurde nicht auf hohe Genauigkeit abgestimmt, das Ziel dieses Tutorials ist es, einen Standardansatz zu zeigen.

num_classes = 5

model = Sequential([
  layers.experimental.preprocessing.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)
])

Kompilieren Sie das Modell

Wählen Sie für dieses Tutorial die Funktionen optimizers.Adam Optimizer und losses.SparseCategoricalCrossentropy loss. Um die Trainings- und Validierungsgenauigkeit für jede Trainingsepoche anzuzeigen, übergeben Sie das metrics Argument.

model.compile(optimizer='adam',
              loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
              metrics=['accuracy'])

Modellzusammenfassung

Zeigen Sie alle Schichten des Netzwerks mit der summary des Modells an:

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 (MaxPooling2 (None, 45, 45, 32)        0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 45, 45, 64)        18496     
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 22, 22, 64)        0         
_________________________________________________________________
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
_________________________________________________________________

Trainiere das Modell

epochs=10
history = model.fit(
  train_ds,
  validation_data=val_ds,
  epochs=epochs
)
Epoch 1/10
92/92 [==============================] - 3s 16ms/step - loss: 1.4412 - accuracy: 0.3784 - val_loss: 1.1290 - val_accuracy: 0.5409
Epoch 2/10
92/92 [==============================] - 1s 10ms/step - loss: 1.0614 - accuracy: 0.5841 - val_loss: 1.0058 - val_accuracy: 0.6131
Epoch 3/10
92/92 [==============================] - 1s 10ms/step - loss: 0.8999 - accuracy: 0.6560 - val_loss: 0.9920 - val_accuracy: 0.6104
Epoch 4/10
92/92 [==============================] - 1s 10ms/step - loss: 0.7416 - accuracy: 0.7153 - val_loss: 0.9279 - val_accuracy: 0.6458
Epoch 5/10
92/92 [==============================] - 1s 10ms/step - loss: 0.5618 - accuracy: 0.7844 - val_loss: 1.0019 - val_accuracy: 0.6322
Epoch 6/10
92/92 [==============================] - 1s 10ms/step - loss: 0.3950 - accuracy: 0.8634 - val_loss: 1.0232 - val_accuracy: 0.6553
Epoch 7/10
92/92 [==============================] - 1s 10ms/step - loss: 0.2228 - accuracy: 0.9268 - val_loss: 1.2722 - val_accuracy: 0.6444
Epoch 8/10
92/92 [==============================] - 1s 10ms/step - loss: 0.1188 - accuracy: 0.9687 - val_loss: 1.4410 - val_accuracy: 0.6567
Epoch 9/10
92/92 [==============================] - 1s 10ms/step - loss: 0.0737 - accuracy: 0.9802 - val_loss: 1.6363 - val_accuracy: 0.6444
Epoch 10/10
92/92 [==============================] - 1s 10ms/step - loss: 0.0566 - accuracy: 0.9847 - val_loss: 1.9752 - val_accuracy: 0.6390

Trainingsergebnisse visualisieren

Erstellen Sie Verlust- und Genauigkeitsdiagramme für die Trainings- und Validierungssätze.

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

Wie Sie in den Diagrammen sehen können, weichen die Trainingsgenauigkeit und die Validierungsgenauigkeit stark voneinander ab und das Modell hat beim Validierungssatz nur eine Genauigkeit von etwa 60 % erreicht.

Schauen wir uns an, was schief gelaufen ist, und versuchen wir, die Gesamtleistung des Modells zu erhöhen.

Überanpassung

In den obigen Diagrammen nimmt die Trainingsgenauigkeit im Laufe der Zeit linear zu, während die Validierungsgenauigkeit während des Trainingsprozesses um etwa 60 % stagniert. Außerdem ist der Genauigkeitsunterschied zwischen Trainings- und Validierungsgenauigkeit spürbar – ein Zeichen für eine Überanpassung .

Bei einer geringen Anzahl von Trainingsbeispielen lernt das Modell manchmal aus Störgeräuschen oder unerwünschten Details von Trainingsbeispielen – in einem Ausmaß, das sich negativ auf die Leistung des Modells bei neuen Beispielen auswirkt. Dieses Phänomen wird als Überanpassung bezeichnet. Dies bedeutet, dass es dem Modell schwer fallen wird, einen neuen Datensatz zu verallgemeinern.

Es gibt mehrere Möglichkeiten, Overfitting im Trainingsprozess zu bekämpfen. In diesem Tutorial verwenden Sie die Datenerweiterung und fügen Dropout zu Ihrem Modell hinzu.

Datenerweiterung

Overfitting tritt im Allgemeinen auf, wenn es eine kleine Anzahl von Trainingsbeispielen gibt. Die Datenerweiterung verfolgt den Ansatz, zusätzliche Trainingsdaten aus Ihren vorhandenen Beispielen zu generieren, indem Sie diese mit zufälligen Transformationen erweitern, die glaubwürdig aussehende Bilder liefern. Dies trägt dazu bei, das Modell für mehr Aspekte der Daten zugänglich zu machen und besser zu verallgemeinern.

Sie implementieren die tf.keras.layers.experimental.preprocessing mit den Layern aus tf.keras.layers.experimental.preprocessing . Diese können wie andere Ebenen in Ihr Modell eingebunden und auf der GPU ausgeführt werden.

data_augmentation = keras.Sequential(
  [
    layers.experimental.preprocessing.RandomFlip("horizontal", 
                                                 input_shape=(img_height, 
                                                              img_width,
                                                              3)),
    layers.experimental.preprocessing.RandomRotation(0.1),
    layers.experimental.preprocessing.RandomZoom(0.1),
  ]
)

Lassen Sie uns visualisieren, wie einige erweiterte Beispiele aussehen, indem Sie die Datenerweiterung mehrmals auf dasselbe Bild anwenden:

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

Sie werden die Datenerweiterung verwenden, um ein Modell in Kürze zu trainieren.

Ausfallen

Eine andere Technik, um Overfitting zu reduzieren, besteht darin, Dropout in das Netzwerk einzuführen, eine Form der Regularisierung .

Wenn Sie Dropout auf eine Ebene anwenden, wird während des Trainingsvorgangs eine Anzahl von Ausgabeeinheiten aus der Ebene zufällig (indem die Aktivierung auf Null gesetzt wird) ausgelassen. Dropout verwendet als Eingabewert eine Bruchzahl in der Form 0,1, 0,2, 0,4 usw. Dies bedeutet, dass 10 %, 20 % oder 40 % der Ausgabeeinheiten zufällig aus der angewendeten Ebene fallen.

Lassen Sie uns ein neues neuronales Netzwerk mit " layers.Dropout und es dann mit Augmented Images trainieren.

model = Sequential([
  data_augmentation,
  layers.experimental.preprocessing.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)
])

Kompilieren und trainieren Sie das Modell

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 (MaxPooling2 (None, 90, 90, 16)        0         
_________________________________________________________________
conv2d_4 (Conv2D)            (None, 90, 90, 32)        4640      
_________________________________________________________________
max_pooling2d_4 (MaxPooling2 (None, 45, 45, 32)        0         
_________________________________________________________________
conv2d_5 (Conv2D)            (None, 45, 45, 64)        18496     
_________________________________________________________________
max_pooling2d_5 (MaxPooling2 (None, 22, 22, 64)        0         
_________________________________________________________________
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 13ms/step - loss: 1.2685 - accuracy: 0.4465 - val_loss: 1.0464 - val_accuracy: 0.5899
Epoch 2/15
92/92 [==============================] - 1s 11ms/step - loss: 1.0195 - accuracy: 0.5964 - val_loss: 0.9466 - val_accuracy: 0.6008
Epoch 3/15
92/92 [==============================] - 1s 11ms/step - loss: 0.9184 - accuracy: 0.6356 - val_loss: 0.8412 - val_accuracy: 0.6689
Epoch 4/15
92/92 [==============================] - 1s 11ms/step - loss: 0.8497 - accuracy: 0.6768 - val_loss: 0.9339 - val_accuracy: 0.6444
Epoch 5/15
92/92 [==============================] - 1s 11ms/step - loss: 0.8180 - accuracy: 0.6781 - val_loss: 0.8309 - val_accuracy: 0.6689
Epoch 6/15
92/92 [==============================] - 1s 11ms/step - loss: 0.7424 - accuracy: 0.7105 - val_loss: 0.7765 - val_accuracy: 0.6962
Epoch 7/15
92/92 [==============================] - 1s 11ms/step - loss: 0.7157 - accuracy: 0.7251 - val_loss: 0.7451 - val_accuracy: 0.7016
Epoch 8/15
92/92 [==============================] - 1s 11ms/step - loss: 0.6764 - accuracy: 0.7476 - val_loss: 0.9703 - val_accuracy: 0.6485
Epoch 9/15
92/92 [==============================] - 1s 11ms/step - loss: 0.6667 - accuracy: 0.7439 - val_loss: 0.7249 - val_accuracy: 0.6962
Epoch 10/15
92/92 [==============================] - 1s 11ms/step - loss: 0.6282 - accuracy: 0.7619 - val_loss: 0.7187 - val_accuracy: 0.7071
Epoch 11/15
92/92 [==============================] - 1s 11ms/step - loss: 0.5816 - accuracy: 0.7793 - val_loss: 0.7107 - val_accuracy: 0.7275
Epoch 12/15
92/92 [==============================] - 1s 11ms/step - loss: 0.5570 - accuracy: 0.7813 - val_loss: 0.6945 - val_accuracy: 0.7493
Epoch 13/15
92/92 [==============================] - 1s 11ms/step - loss: 0.5396 - accuracy: 0.7939 - val_loss: 0.6713 - val_accuracy: 0.7302
Epoch 14/15
92/92 [==============================] - 1s 11ms/step - loss: 0.5194 - accuracy: 0.7936 - val_loss: 0.6771 - val_accuracy: 0.7371
Epoch 15/15
92/92 [==============================] - 1s 11ms/step - loss: 0.4930 - accuracy: 0.8096 - val_loss: 0.6705 - val_accuracy: 0.7384

Trainingsergebnisse visualisieren

Nach der Anwendung von Data Augmentation und Dropout gibt es weniger Overfitting als zuvor und die Genauigkeit von Training und Validierung ist besser aufeinander abgestimmt.

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

Vorhersage auf neue Daten

Lassen Sie uns schließlich unser Modell verwenden, um ein Bild zu klassifizieren, das nicht in den Trainings- oder Validierungssätzen enthalten war.

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 = keras.preprocessing.image.load_img(
    sunflower_path, target_size=(img_height, img_width)
)
img_array = keras.preprocessing.image.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
This image most likely belongs to sunflowers with a 99.36 percent confidence.