סיווג תמונה

הצג באתר TensorFlow.org הפעל בגוגל קולאב צפה במקור ב-GitHub הורד מחברת

מדריך זה מראה כיצד לסווג תמונות של פרחים. הוא יוצר מסיווג תמונה באמצעות מודל tf.keras.Sequential , וטוען נתונים באמצעות tf.keras.utils.image_dataset_from_directory . אתה תרכוש ניסיון מעשי עם המושגים הבאים:

  • טעינה יעילה של מערך נתונים מחוץ לדיסק.
  • זיהוי התאמת יתר ויישום טכניקות להפחתה, כולל הגדלת נתונים ונשירה.

מדריך זה עוקב אחר זרימת עבודה בסיסית של למידת מכונה:

  1. לבחון ולהבין נתונים
  2. בניית צינור קלט
  3. בנה את הדגם
  4. אימון הדגם
  5. בדוק את הדגם
  6. שפר את המודל וחזור על התהליך

ייבוא ​​TensorFlow וספריות אחרות

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

הורד וחקור את מערך הנתונים

מדריך זה משתמש במערך נתונים של כ-3,700 תמונות של פרחים. מערך הנתונים מכיל חמש ספריות משנה, אחת לכל מחלקה:

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)

לאחר ההורדה, כעת אמור להיות לך עותק של מערך הנתונים זמין. יש 3,670 תמונות בסך הכל:

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

הנה כמה ורדים:

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

png

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

png

וכמה צבעונים:

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

png

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

png

טען נתונים באמצעות כלי שירות Keras

בוא נטען את התמונות האלה מהדיסק באמצעות כלי השירות המועיל tf.keras.utils.image_dataset_from_directory . זה יעביר אותך מספריית תמונות בדיסק ל- tf.data.Dataset בכמה שורות קוד בלבד. אם תרצה, תוכל גם לכתוב את קוד טעינת הנתונים שלך מאפס על ידי ביקור במדריך הטעינה ועיבוד מקדים של תמונות .

צור מערך נתונים

הגדר כמה פרמטרים עבור המטען:

batch_size = 32
img_height = 180
img_width = 180

מומלץ להשתמש בפיצול אימות בעת פיתוח המודל שלך. בואו נשתמש ב-80% מהתמונות לאימון, ו-20% לאימות.

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.

אתה יכול למצוא את שמות המחלקות בתכונה class_names במערך הנתונים האלה. אלה תואמים את שמות הספריות בסדר אלפביתי.

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

דמיינו את הנתונים

להלן תשע התמונות הראשונות מתוך מערך ההדרכה:

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

אתה תאמן מודל באמצעות מערכי נתונים אלה על ידי העברתם אל Model.fit תוך רגע. אם תרצה, תוכל גם לחזור על מערך הנתונים באופן ידני ולאחזר קבוצות של תמונות:

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

ה- image_batch הוא טנסור של הצורה (32, 180, 180, 3) . זוהי אצווה של 32 תמונות 180x180x3 (הממד האחרון מתייחס לערוצי צבע RGB). ה- label_batch הוא טנסור של הצורה (32,) , אלו תוויות מתאימות ל-32 התמונות.

אתה יכול לקרוא .numpy() image_batch image_batch ו- labels_batch כדי להמיר אותם ל- numpy.ndarray .

הגדר את מערך הנתונים לביצועים

בוא נוודא להשתמש באחזור מראש מאוחסן כדי שתוכל להפיק נתונים מהדיסק מבלי שהקלט/פלט ייחסם. אלו הן שתי שיטות חשובות שבהן עליך להשתמש בעת טעינת נתונים:

  • Dataset.cache שומר את התמונות בזיכרון לאחר טעינתן מהדיסק במהלך התקופה הראשונה. זה יבטיח שמערך הנתונים לא יהפוך לצוואר בקבוק בזמן אימון המודל שלך. אם מערך הנתונים שלך גדול מכדי להתאים לזיכרון, אתה יכול גם להשתמש בשיטה זו כדי ליצור מטמון בעל ביצועים בדיסק.
  • Dataset.prefetch חופף לעיבוד מקדים של נתונים וביצוע מודלים תוך כדי אימון.

קוראים מעוניינים יכולים ללמוד עוד על שתי השיטות, כמו גם כיצד לשמר נתונים בדיסק בסעיף 'אחזור מראש ' של מדריך ביצועים טובים יותר עם tf.data API .

AUTOTUNE = tf.data.AUTOTUNE

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

תקן את הנתונים

ערכי ערוץ ה-RGB נמצאים בטווח [0, 255] . זה לא אידיאלי עבור רשת עצבית; באופן כללי, עליך לשאוף להקטין את ערכי הקלט שלך.

כאן, תתקן את הערכים כך שיהיו בטווח [0, 1] באמצעות tf.keras.layers.Rescaling :

normalization_layer = layers.Rescaling(1./255)

ישנן שתי דרכים להשתמש בשכבה זו. אתה יכול להחיל אותו על מערך הנתונים על ידי קריאה 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

לחלופין, אתה יכול לכלול את השכבה בתוך הגדרת המודל שלך, מה שיכול לפשט את הפריסה. בואו נשתמש בגישה השנייה כאן.

צור את הדגם

המודל Sequential מורכב משלושה בלוקים של קונבולציה ( tf.keras.layers.Conv2D ) עם שכבת בריכה מקסימלית ( tf.keras.layers.MaxPooling2D ) בכל אחד מהם. יש שכבה מחוברת לחלוטין ( tf.keras.layers.Dense ) עם 128 יחידות מעליה שמופעלת על ידי פונקציית הפעלה של ReLU ( 'relu' ). מודל זה לא כוונן לרמת דיוק גבוהה - המטרה של הדרכה זו היא להראות גישה סטנדרטית.

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

הרכיב את המודל

עבור הדרכה זו, בחר את פונקציית האובדן tf.keras.optimizers.Adam ו- tf.keras.losses.SparseCategoricalCrossentropy . כדי להציג את דיוק ההדרכה והאימות עבור כל עידן אימון, העבר את ארגומנט metrics אל Model.compile .

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

תקציר הדגם

הצג את כל שכבות הרשת בשיטת Model.summary של המודל:

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
_________________________________________________________________

אימון הדגם

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

דמיין את תוצאות האימון

צור עלילות של אובדן ודיוק על ערכות ההדרכה והאימות:

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

העלילות מראים שדיוק האימון ודיוק האימות מופחתים בפערים גדולים, והמודל השיג דיוק של כ-60% בלבד בערכת האימות.

בואו נבדוק מה השתבש וננסה להגביר את הביצועים הכוללים של הדגם.

התאמת יתר

בחלקות לעיל, דיוק האימון גדל באופן ליניארי עם הזמן, בעוד שדיוק האימות נעצר בסביבות 60% בתהליך האימון. כמו כן, ההבדל בדייקנות בין אימון לדיוק אימות מורגש - סימן להתאמת יתר .

כאשר יש מספר קטן של דוגמאות אימון, המודל לומד לפעמים מרעשים או פרטים לא רצויים מדוגמאות אימון - במידה שזה משפיע לרעה על ביצועי המודל בדוגמאות חדשות. תופעה זו ידועה בשם התאמה יתר. זה אומר שלמודל יהיה קשה להכליל על מערך נתונים חדש.

ישנן מספר דרכים להילחם בהתאמת יתר בתהליך האימון. במדריך זה, תשתמש בהגדלת נתונים ותוסיף Dropout למודל שלך.

הגדלת נתונים

התאמת יתר מתרחשת בדרך כלל כאשר יש מספר קטן של דוגמאות אימונים. הגדלת נתונים נוקטת בגישה של יצירת נתוני אימון נוספים מהדוגמאות הקיימות שלך על ידי הגדלתן באמצעות טרנספורמציות אקראיות שמניבות תמונות אמינות למראה. זה עוזר לחשוף את המודל ליותר היבטים של הנתונים ולהכליל טוב יותר.

אתה תטמיע הגדלת נתונים באמצעות שכבות העיבוד המקדמות של Keras הבאות: tf.keras.layers.RandomFlip , tf.keras.layers.RandomRotation ו- tf.keras.layers.RandomZoom . אלה יכולים להיכלל בתוך הדגם שלך כמו שכבות אחרות, ולהפעיל על ה-GPU.

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

בואו נראה איך נראות כמה דוגמאות מוגדלות על ידי החלת הגדלת נתונים על אותה תמונה מספר פעמים:

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

אתה תשתמש בהגדלת נתונים כדי להכשיר מודל בעוד רגע.

נשר

טכניקה נוספת להפחתת התאמת יתר היא הכנסת הסדרת נשירה לרשת.

כאשר אתה מחיל נשירה על שכבה, היא נושרת באופן אקראי (על ידי הגדרת ההפעלה לאפס) מספר יחידות פלט מהשכבה במהלך תהליך האימון. נשירה לוקחת מספר חלקי כערך הקלט שלו, בצורה כמו 0.1, 0.2, 0.4 וכו'. משמעות הדבר היא נשירה של 10%, 20% או 40% מיחידות הפלט באופן אקראי מהשכבה המיושמת.

בואו ניצור רשת עצבית חדשה עם tf.keras.layers.Dropout לפני אימון באמצעות התמונות המוגדלות:

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

הרכיבו והכשירו את המודל

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

דמיין את תוצאות האימון

לאחר החלת הגדלת נתונים ו- tf.keras.layers.Dropout , יש פחות התאמת יתר מבעבר, ודיוק ההדרכה והאימות מתואמים יותר:

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

חזה על נתונים חדשים

לבסוף, בואו נשתמש במודל שלנו כדי לסווג תמונה שלא נכללה בערכות ההדרכה או האימות.

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.