طبقه بندی تصویر

مشاهده در TensorFlow.org در Google Colab اجرا شود مشاهده منبع در 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

مجموعه داده را دانلود و کاوش کنید

این آموزش از مجموعه داده ای از حدود 3700 عکس گل استفاده می کند. مجموعه داده شامل پنج زیرمجموعه است، یکی در هر کلاس:

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)

پس از دانلود، اکنون باید یک کپی از مجموعه داده در دسترس داشته باشید. در کل 3670 تصویر وجود دارد:

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 و labels_batch کنید تا آنها را به numpy.ndarray تبدیل کنید.

مجموعه داده را برای عملکرد پیکربندی کنید

بیایید مطمئن شویم که از واکشی اولیه بافر استفاده می کنیم تا بتوانید بدون مسدود شدن ورودی/خروجی، داده ها را از دیسک به دست آورید. این دو روش مهم هستند که باید هنگام بارگیری داده ها از آنها استفاده کنید:

  • Dataset.cache تصاویر را پس از بارگیری از دیسک در اولین دوره در حافظه نگه می دارد. این اطمینان حاصل می کند که مجموعه داده در حین آموزش مدل شما به یک گلوگاه تبدیل نمی شود. اگر مجموعه داده شما بیش از حد بزرگ است که نمی تواند در حافظه قرار بگیرد، می توانید از این روش برای ایجاد یک حافظه کش عملکردی روی دیسک نیز استفاده کنید.
  • Dataset.prefetch با پیش پردازش داده ها و اجرای مدل در حین آموزش همپوشانی دارد.

خوانندگان علاقه مند می توانند با راهنمای tf.data API اطلاعات بیشتری در مورد هر دو روش و همچنین نحوه کش کردن داده ها در دیسک در بخش Prefetching از عملکرد بهتر کسب کنند.

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] است. این برای یک شبکه عصبی ایده آل نیست. به طور کلی باید به دنبال کوچک کردن مقادیر ورودی خود باشید.

در اینجا، با استفاده از tf.keras.layers.Rescaling ، مقادیر را در محدوده [0, 1] استاندارد می‌کنید:

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

یا، می توانید لایه را در تعریف مدل خود قرار دهید، که می تواند استقرار را ساده کند. بیایید از رویکرد دوم در اینجا استفاده کنیم.

مدل را ایجاد کنید

مدل متوالی از سه بلوک کانولوشن ( 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 درصد متوقف می‌شود. همچنین، تفاوت در دقت بین دقت آموزشی و اعتبارسنجی قابل توجه است - نشانه ای از برازش بیش از حد.

هنگامی که تعداد کمی از نمونه های آموزشی وجود دارد، مدل گاهی اوقات از نویزها یا جزئیات ناخواسته از نمونه های آموزشی یاد می گیرد - تا حدی که بر عملکرد مدل در نمونه های جدید تأثیر منفی می گذارد. این پدیده به نام overfitting شناخته می شود. این بدان معناست که تعمیم مدل بر روی یک مجموعه داده جدید با مشکل مواجه خواهد شد.

راه های متعددی برای مبارزه با بیش از حد تناسب در فرآیند تمرین وجود دارد. در این آموزش، از افزایش داده ها استفاده می کنید و Dropout را به مدل خود اضافه می کنید.

افزایش داده ها

Overfitting معمولا زمانی اتفاق می افتد که تعداد کمی از نمونه های آموزشی وجود داشته باشد. تقویت داده رویکرد تولید داده‌های آموزشی اضافی از نمونه‌های موجود شما را با تقویت آن‌ها با استفاده از تبدیل‌های تصادفی که تصاویری با ظاهر باورپذیر به دست می‌دهد، اتخاذ می‌کند. این کمک می کند که مدل در معرض جنبه های بیشتری از داده ها قرار گیرد و بهتر تعمیم یابد.

با استفاده از لایه‌های پیش‌پردازش 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

شما از تقویت داده ها برای آموزش یک مدل در یک لحظه استفاده خواهید کرد.

ترک تحصیل

یکی دیگر از تکنیک‌های کاهش بیش از حد برازش، معرفی منظم‌سازی خروج از شبکه به شبکه است.

هنگامی که حذف را به یک لایه اعمال می کنید، به طور تصادفی (با تنظیم فعال سازی روی صفر) تعدادی از واحدهای خروجی از لایه در طول فرآیند آموزش حذف می شود. Dropout یک عدد کسری را به عنوان مقدار ورودی خود می گیرد، به شکل هایی مانند 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.