تصنيف الصورة

عرض على TensorFlow.org تشغيل في Google Colab عرض المصدر على جيثب تحميل دفتر

يوضح هذا البرنامج التعليمي كيفية تصنيف صور الزهور. يقوم بإنشاء مصنف صور باستخدام نموذج 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]))

بي إن جي

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

بي إن جي

وبعض الزنبق:

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

بي إن جي

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

بي إن جي

تحميل البيانات باستخدام أداة 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")

بي إن جي

ستقوم بتدريب نموذج باستخدام مجموعات البيانات هذه عن طريق تمريرها إلى 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 .

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

أو يمكنك تضمين الطبقة داخل تعريف النموذج الخاص بك ، مما يسهل عملية النشر. دعنا نستخدم الطريقة الثانية هنا.

قم بإنشاء النموذج

يتكون النموذج التسلسلي من ثلاث كتل التفاف ( 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 Optimizer و 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()

بي إن جي

تُظهر المخططات أن دقة التدريب ودقة التحقق من الصحة متوقفة عن الهوامش الكبيرة ، وقد حقق النموذج حوالي 60 ٪ فقط من الدقة في مجموعة التحقق من الصحة.

دعنا نفحص الخطأ الذي حدث ونحاول زيادة الأداء العام للنموذج.

تجهيز

في المخططات أعلاه ، تزداد دقة التدريب خطيًا بمرور الوقت ، بينما تتوقف دقة التحقق عن الصحة بنسبة 60٪ في عملية التدريب. أيضًا ، الفرق في الدقة بين دقة التدريب والتحقق من الصحة ملحوظًا - علامة على فرط التجهيز .

عندما يكون هناك عدد قليل من أمثلة التدريب ، يتعلم النموذج أحيانًا من الضوضاء أو التفاصيل غير المرغوب فيها من أمثلة التدريب - إلى حد يؤثر سلبًا على أداء النموذج في الأمثلة الجديدة. تُعرف هذه الظاهرة باسم overfitting. هذا يعني أن النموذج سيواجه صعوبة في التعميم على مجموعة بيانات جديدة.

هناك طرق متعددة لمحاربة فرط التجهيز في عملية التدريب. في هذا البرنامج التعليمي ، ستستخدم زيادة البيانات وإضافة Dropout إلى نموذجك.

زيادة البيانات

يحدث فرط الملاءمة بشكل عام عندما يكون هناك عدد قليل من أمثلة التدريب. تعتمد زيادة البيانات على نهج إنشاء بيانات تدريب إضافية من الأمثلة الموجودة لديك عن طريق زيادتها باستخدام تحويلات عشوائية تنتج صورًا ذات مظهر معقول. يساعد هذا في عرض النموذج لمزيد من جوانب البيانات والتعميم بشكل أفضل.

ستقوم بتنفيذ زيادة البيانات باستخدام طبقات معالجة Keras التالية: tf.keras.layers.RandomFlip و tf.keras.layers.RandomRotation و tf.keras.layers.RandomZoom . يمكن تضمينها داخل نموذجك مثل الطبقات الأخرى ، وتشغيلها على وحدة معالجة الرسومات.

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

بي إن جي

ستستخدم زيادة البيانات لتدريب نموذج في لحظة.

أوقع

أسلوب آخر لتقليل فرط التخصيص هو إدخال تنظيم التسرب إلى الشبكة.

عندما تقوم بتطبيق التسرب على طبقة ، فإنها تسقط عشوائيًا (عن طريق ضبط التنشيط على الصفر) عددًا من وحدات الإخراج من الطبقة أثناء عملية التدريب. يأخذ التسرب رقمًا كسريًا كقيمة إدخال ، في شكل مثل 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()

بي إن جي

توقع البيانات الجديدة

أخيرًا ، دعنا نستخدم نموذجنا لتصنيف صورة لم يتم تضمينها في مجموعات التدريب أو التحقق من الصحة.

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.