أساسيّات التّصنيف: تصنيف صور الملابس

إفتح المحتوى على موقع TensorFlow.org تفاعل مع المحتوى على Google Colab إطّلع على المصدر في Github تنزيل الدّفتر

نقوم في هذا الدّليل بتدريب نموذج شبكة عصبيّة لتصنيف الملابس، مثل الأحذية الرياضية و القمصان. لابأس إن لم تفهم كلّ التّفاصيل، فهذا درس تفاعليّ ذو نسق سريع يعطي نظرة عامّة عن كيفية اِستعمال Tensorflow في برنامج كامل و ستتوضّح التفاصيل أثناء تقدّمك.

يستخدم هذا الدليل واجهة برمجة التطبيقات tf.keras، و هي واجهة برمجة (API) عالية المستوى تمكّن من بناء و تدريب النماذج في Tensorflow بسهولة.

# TensorFlow and tf.keras
import tensorflow as tf
from tensorflow import keras

# Helper libraries
import numpy as np
import matplotlib.pyplot as plt

print(tf.__version__)
2.3.0

حمّل مجموعة بيانات Fashion MNIST

يستخدم هذا الدّليل مجموعة بيانات Fashion MNIST (أي MNIST الموضة). و هي مجموعة بيانات تحتوى على 70000 صورة ذات تدرّج رمادي (greyscale images). تنقسم الصّور إلى 10 أنواع من الملابس، سننظر فيها لاحقا. تظهر كل صورة نوعا واحدًا من الملابس بدقّة منخفضة (28 × 28 بكسل)، كما هو موضّح هنا:

Fashion MNIST sprite
Figure 1. Fashion-MNIST أمثلة من (by Zalando, MIT License).
 

تمّ تصميم Fashion MNIST لتكون بديلا مباشرا لمجموعة البيانات الكلاسيكية MNIST و التّي تعتبر المثال الأوّل، أو مثال "مرحبا يا عالم" ("Hello, word") لبرامج تعلّم الآلة في مجال رؤية الكمبيوتر. و تحتوى مجموعة بيانات MNIST على صور للأعداد من 0 إلى 9 مكتوبة بخط اليد من قِبَلِ العديد من الأشخاص حيث أنّ تنسيق الصور فيها مماثل لتنسيق الصور الموجودة في Fashion MNIST و التّي سنستخدمها في هذا الدّرس.

يستخدم هذا الدّليل Fashion MNIST لتنويع الأمثلة للمتعلّم، و لأنّ تصنيف الملابس هي مشكلة أكثر تحدّيا قليلا من مشكلة تصنيف الأرقام الموجودة في مجموعة بيانات MNIST الكلاسيكية. كِلتا مجموعتي البيانات صغيرتان نسبيًّا و يتمّ اِستخدمهما في الغالب للتحقّق من أنّ أيّ خوارزميّة بصدد التطوير تعمل كما هو متوقّع. فهما إذن نقطتا إنطلاق جيّدتان لاختبار البرامج و تصحيحها.

في التّالي، سنستخدم 60000 صورة لتدريب الشبكة العصبيّة و 10000 صورة لتقييم مدى دقّة الشبكة في تصنيف الصّور. يمكنك الوصول الى مجموعة بيانات Fashion MNIST مباشرة من Tensorflow. يمكنك القيام بذلك عن طريق استيراد و تحميل بيانات Fashion MNIST من Tensorflow كالتّالي:

fashion_mnist = keras.datasets.fashion_mnist

(train_images, train_labels), (test_images, test_labels) = fashion_mnist.load_data()
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/train-labels-idx1-ubyte.gz
32768/29515 [=================================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/train-images-idx3-ubyte.gz
26427392/26421880 [==============================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/t10k-labels-idx1-ubyte.gz
8192/5148 [===============================================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/t10k-images-idx3-ubyte.gz
4423680/4422102 [==============================] - 0s 0us/step

عمليّة تحميل البيانات السّابقة تُرجع أربعة صفائف NumPy:

  • تُكوِّن الصفيفتان train_images و train_labels مجموعة بيانات التّدريب (training set). أي البيانات التّي سيستخدمها النّموذج للتعلّم.
  • بعد التّدريب، يتمّ اختبار النموذج باستعمال مجموعة الاختبار (test set) و هي تتكوّن من الصفيفتان test_images و test_labels.

بعد تحميلها في الذّاكرة، ستكون الصّور متمثّلة في صفائف NumPy، أي NumPy arrays، ذات أبعاد 28x28، حيث ستكون قيمة كل بكسل عدَدًا طبيعيّا بين 0 و 255. بينما تكون التسميات (labels) متمثلة في صفيفة متكوّنة من أعداد صحيحة طبيعية تكون قيمة كل منها بين 0 و 9. يتوافق كلّ عدد مع فئة الملابس التّي تمثّلها الصورة:

Label Class Arabic Class
0 T-shirt/top تي شيرت
1 Trouser سِرْوال
2 Pullover كنزة صوفية
3 Dress فستان
4 Coat معطف
5 Sandal صندل
6 Shirt قميص
7 Sneaker حذاء رياضي
8 Bag حقيبة
9 Ankle boot حذاء الكاحل

يتمّ تعيين تسمية واحدة لكلّ صورة. بما أنّ أسماء الفئات (class names) لن تُضَمّنَ في مجموعة البيانات، سنقوم بتخزينها في المتغيّر (variable) المسمّى class_names حتّى نتمكّن من إستخدامها لاحقا عند رسم النتائج أو البيانات أو الصّور:

class_names = ['T-shirt/top', 'Trouser', 'Pullover', 'Dress', 'Coat',
               'Sandal', 'Shirt', 'Sneaker', 'Bag', 'Ankle boot']

استكشف البيانات

لنبدأ أوّلا باستكشاف شكل البيانات قبل القيام بتدريب النّموذج. في التّالي يمكننا أنّ نرى أنّ مجموعة بيانات التّدريب الموجودة في المتغيّر train_images تحتوى على 60000 صورة، حيث أنّ كلّ صورة متمثّلة في 28 × 28 بكسل:

train_images.shape
(60000, 28, 28)

و بطريقة مماثلة، يبلغ عدد التّسميات (labels) في مجموعة التّدريب 60000 و هي موجودة في المتغيّر train_labels :

len(train_labels)
60000

كل تسمية هي عدد صحيح طبيعي قيمته بين 0 و 9:

train_labels
array([9, 0, 0, ..., 3, 0, 5], dtype=uint8)

هناك 10000 صورة في مجموعة الإختبار (test set). هنا أيضا، كلّ صورة متمثّلة في 28 × 28 بكسل:

test_images.shape
(10000, 28, 28)

و تحتوى مجموعة الإختبار، أيضا، على 10000 تسمية:

len(test_labels)
10000

معالجة البيانات

يجب معالجة البيانات مسبقًا قبل القيام بتدريب نموذج الشبكة العصبيّة. إذا قمنا بفحص الصّورة الأولى في مجموعة التّدريب، سنرى أنّ قيمة كلّ بكسل هي بين 0 و 255:

plt.figure()
plt.imshow(train_images[0])
plt.colorbar()
plt.grid(False)
plt.show()

png

خفّض قيمة البكسلات من نطاقها الحاليّ من 0 إلى 255 إلي النّطاق من 0 إلى 1 قبل استخدمها لتدريب النّموذج. للقيام بذلك، قم بقسمة كلّ القيم على أكبر عدد في النّطاق الأوّل وهو 255. من المهمّ معالجة مجموعة التّدريب و مجموعة الإختبار بنفس الطّريقة:

train_images = train_images / 255.0

test_images = test_images / 255.0

للتأكّد من أنّ البيانات بالتّنسيق الصحيح و أنّنا جاهزون للبدء ببناء و تدريب الشبكة العصبيّة، فلنعرض أوّل 25 صورة من مجموعة التّدريب و نعرض إسم كلّ فئة تحت الصورة.

plt.figure(figsize=(10,10))
for i in range(25):
    plt.subplot(5,5,i+1)
    plt.xticks([])
    plt.yticks([])
    plt.grid(False)
    plt.imshow(train_images[i], cmap=plt.cm.binary)
    plt.xlabel(class_names[train_labels[i]])
plt.show()

png

بناء النّموذج

يتطلّب بناء الشبكة العصبيّة تعديل معلمات (parameters) طبقات النموذج، ثمّ تجميعه (compile the model).

تعديل طبقات النّموذج

تمثّل الطّبقة (layer) اللّبنة الأساسية لبناء الشبكة العصبيّة. تقوم الطبقات باستخراج التمثيلات (representations) من البيانات التّي يتمّ إدخالها فيها. مع أمل أن تكون هذه التمثيلات ذات معنى مساعد على حلّ المشكلة المطروحة.

معظم التعلّم العميق (deep learning) يتمثّل في ربط طبقات بسيطة معا. معظم هذه الطبقات، مثل tf.keras.layers.Dense، لديها معلمات (parameters) تُتَعلَّمُ أثناء عمليّة تدريب النّموذج.

model = keras.Sequential([
    keras.layers.Flatten(input_shape=(28, 28)),
    keras.layers.Dense(128, activation='relu'),
    keras.layers.Dense(10)
])

تُحوّل الطبقة الأولى من الشبكة، tf.keras.layers.Flatten، تنسيق الصور من صفيفة (array) ثنائية الأبعاد (28 × 28 بكسل) إلى صفيفة أحاديّة البعد (28 * 28 = 784 بكسل). فكّر في هذه الطبقة على أنّها تقوم بعمليّة تفريق الخطوط الأفقيّة للبكسلات في صورة ما، بعضها عن بعض ،ثمّ تقوم بتصفيفها في خطّ واحد. لا نحتاج لتعلّم أيّ معلمات (parameters) لضبط هذه الطبقة، فهدفها الوحيد هو إعادة تنسيق البيانات.

بعد تحويل صفيفة البكسلات، ذات البعدين، المكوّنة للصورة للحصول على صفيفة ذات بعد واحد، يتكوّن باقي الشبكة من تَسلسُل لطبقتين من نوع tf.keras.layers.Dense. و هي طبقات عصبيّة متّصلة بكثافة (densely connected) أو تسمّى أيضا متّصلة بالكامل (fully connected). أولّ طبقة متّصلة بالكامل (Dense layer) تتكوّن من 128 عقدة (خليّة عصبيّة). ثاني (و آخر) طبقة متّصلة بالكامل تُخرج صفيفة لوجيت (logits array) تتكوّن من 10 عناصر. بما أنّ كُلّ عقدة من الطبقة الأخيرة تهتمّ بفئة معيّنة من الملابس فإنّ كل قيمة من القيم الموجودة في صفيفة اللّوجيت تشير إلى احتمال أنّ الصورة الحاليّة تحتوى على الفئة التّي تُركّز عليها العقدة.

جمع النّموذج

قبل أن يصبح النموذج جاهزًا للتدريب، يحتاج إلى بعض الإعدادات الإضافية و التّي تتمّ إضافتها في خطوة تجميع النّموذج (model compiling) و التّي تقوم بها الدّالة model.compile. و هذه الإعدادات هي:

  • دالّة الخسارة (Loss function) - تقيس مدى دقّة النموذج أثناء التدريب. هدفك هو أن تبنِيَ نموذجا يقوم بتقليل قيمة هذه الدّالة في كُلّ خطوة من عمليّة التدريب حتى تقوم "بتوجيه" النموذج في الاتجاه الصحيح.
  • مُحسِّن (Optimizer) - هذه هي الطريقة التي يتم بها تحديث النموذج بناءً على البيانات التي يراها و دالّة الخسارة الخاصة به.
  • المقاييس (Metrics) - تُستخدم لمراقبة أداء النموذج عند التّدريب و الاختبار. يستخدم المثال التّالى مقياس الدّقة ، وهي تتمثّل في نسبة الصور المصنّفة بشكل صحيح.
model.compile(optimizer='adam',
              loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
              metrics=['accuracy'])

تدريب النموذج

تتطلّب عمليّة تدريب نموذج الشبكة العصبيّة الخطوات التّالية:

  1. لقّم بيانات التّدريب للنموذج. في هذا المثال، بيانات التدريب موجودة في الصفيفتان train_images و train_labels.
  2. يتعلّم النموذج العلاقة بين الصّور و التسميات (labels) و يربط بينهما.
  3. أطلب من النموذج القيام بالتنبؤ على مجموعة بيانات الاختبار. و التي هي موجودة في الصفيفة test_images.
  4. تحقّق إلى أيّ مدى تُطابق تنبؤات النموذج التسميات الحقيقية الموجودة في الصفيفة test_labels.

لقّم النموذج

لبدء عمليّة التدريب، نادي الدّالة model.fit و هي تسمّى كذلك (fit) لأنّها تقوم بتعديل معلمات النموذج حتّى يتناسب (fits) مع البيانات:

model.fit(train_images, train_labels, epochs=10)
Epoch 1/10
1875/1875 [==============================] - 3s 2ms/step - loss: 0.4978 - accuracy: 0.8264
Epoch 2/10
1875/1875 [==============================] - 3s 1ms/step - loss: 0.3760 - accuracy: 0.8642
Epoch 3/10
1875/1875 [==============================] - 3s 1ms/step - loss: 0.3375 - accuracy: 0.8769
Epoch 4/10
1875/1875 [==============================] - 3s 1ms/step - loss: 0.3144 - accuracy: 0.8849
Epoch 5/10
1875/1875 [==============================] - 3s 1ms/step - loss: 0.2949 - accuracy: 0.8917
Epoch 6/10
1875/1875 [==============================] - 3s 1ms/step - loss: 0.2806 - accuracy: 0.8966
Epoch 7/10
1875/1875 [==============================] - 3s 1ms/step - loss: 0.2679 - accuracy: 0.9002
Epoch 8/10
1875/1875 [==============================] - 3s 1ms/step - loss: 0.2575 - accuracy: 0.9040
Epoch 9/10
1875/1875 [==============================] - 3s 1ms/step - loss: 0.2472 - accuracy: 0.9071
Epoch 10/10
1875/1875 [==============================] - 3s 1ms/step - loss: 0.2387 - accuracy: 0.9108

<tensorflow.python.keras.callbacks.History at 0x7f200ef7e908>

أثناء تدريب النموذج، يتم عرض مقاييس الخسارة والدقة. بعد إتمام التدريب، يصل هذا النموذج إلى دقة تبلغ حوالي 0.91 (أو 91%) في بيانات التدريب.

تقييم الدقة

بعد ذلك، قارن أداء النموذج في مجموعة بيانات الاختبار:

test_loss, test_acc = model.evaluate(test_images,  test_labels, verbose=2)

print('\nTest accuracy:', test_acc)
313/313 - 0s - loss: 0.3273 - accuracy: 0.8858

Test accuracy: 0.8858000040054321

اِتّضح أنّ دقّة النموذج على مجموعة الاختبار أقلّ بقليل من دقّته على بيانات التدريب. هذه الفجوة بين دقّة التدريب و دقّة الاختبار تمثّل ما يسمّى ب overfitting، أي الإفراط في تعلّم كل ما في البيانات، حتّى الأخطاء و الشواذ الموجودة فيها. نقول بأنّ هناك إفراطًا في التعلّم عندما يكون أداء نموذج تعلّم آليّ (machine learning model) ما على بيانات لم يرها من قبل أثناء تعلّمه أسوء من أدائه على البيانات التّي رآها. النموذج المفرط في التعلّم يقوم بحفظ و تذكّر (memorizes) الضجيج و التفاصيل الموجودة في مجموعة بيانات التدريب إلى درجة تُضرّ بأداءه على بيانات جديدة. للإطلاع على المزيد من المعلومات عن هذا الموضوع، راجع المصادر التّالية:

القيام بالتنبؤات

يمكنك الآن القيام بتجربة النموذج على بعض الصور. تتمثّل مخرجات النموذج في صفيفة لوجيت (logits). أضف طبقة softmax لتحويل صفيفة اللوجيت إلى احتمالات يسهل تفسيرها.

probability_model = tf.keras.Sequential([model, 
                                         tf.keras.layers.Softmax()])
predictions = probability_model.predict(test_images)

هنا، قام النموذج بتنبّؤ التسمية المناسبة لكلّ صورة في مجموعة الاختبار. لنلقي نظرة على أوّل تنبؤ:

predictions[0]
array([1.4376285e-08, 1.0184440e-08, 1.0010436e-08, 3.1477956e-10,
       1.4307986e-07, 1.4289225e-03, 9.3553805e-09, 3.5396218e-03,
       8.1496279e-07, 9.9503040e-01], dtype=float32)

كُلّ تنبؤ هو عبارة عن صفيف متكوّن من 10 أرقام. و هي تمثّل مدى "ثقة" النموذج بأنّ الصورة تتوافق مع كل فئة من فئات الملابس العشر المختلفة. يمكنك معرفة أي تسمية تحتوى على أعلى قيمة ثقة هكذا:

np.argmax(predictions[0])
9

إذا، حسب النتيجة السابقة، فإنّ النموذج أكثر ثقة بأنّ هذه الصورة هي لحذاء كاحل، أي التمسية الموجودة في class_names[9]. و يأكّد فحص صفيفة تسميات الاختبار الموجود في test_labels بأنّ تصنيف النموذج صحيح:

test_labels[0]
9

ارسم الصورة مع توقّعات النموذج العشرة لتتوضح النتائج بشكل أفضل.

def plot_image(i, predictions_array, true_label, img):
  predictions_array, true_label, img = predictions_array, true_label[i], img[i]
  plt.grid(False)
  plt.xticks([])
  plt.yticks([])

  plt.imshow(img, cmap=plt.cm.binary)

  predicted_label = np.argmax(predictions_array)
  if predicted_label == true_label:
    color = 'blue'
  else:
    color = 'red'

  plt.xlabel("{} {:2.0f}% ({})".format(class_names[predicted_label],
                                100*np.max(predictions_array),
                                class_names[true_label]),
                                color=color)

def plot_value_array(i, predictions_array, true_label):
  predictions_array, true_label = predictions_array, true_label[i]
  plt.grid(False)
  plt.xticks(range(10))
  plt.yticks([])
  thisplot = plt.bar(range(10), predictions_array, color="#777777")
  plt.ylim([0, 1])
  predicted_label = np.argmax(predictions_array)

  thisplot[predicted_label].set_color('red')
  thisplot[true_label].set_color('blue')

تحقّق من التنبؤات

بعد إتمام تدريب النموذج، يمكنك استعماله للقيام بالتنبؤ بفئة الملابس الموجودة في بعض الصور.

لننظر في الصورة الأولى، الموجودة في المرتبة 0 في الصفيفة. و لنقارن تنبؤ النموذج على هذه الصورة بالتسمية الصحيحة الموجودة في test_labels. سوف نرسم التنبؤ الموافق للتسمية الصحيحة باللون الأزرق، و نرسم التنبؤات الخاطئة باللّون الأحمر. يمثّل العدد الموجود تحت الصورة النسبة (من 0 إلى 100) التي أعطاها النموذج للتسمية الأعلى ثقة.

i = 0
plt.figure(figsize=(6,3))
plt.subplot(1,2,1)
plot_image(i, predictions[i], test_labels, test_images)
plt.subplot(1,2,2)
plot_value_array(i, predictions[i],  test_labels)
plt.show()

png

i = 12
plt.figure(figsize=(6,3))
plt.subplot(1,2,1)
plot_image(i, predictions[i], test_labels, test_images)
plt.subplot(1,2,2)
plot_value_array(i, predictions[i],  test_labels)
plt.show()

png

لنقم برسم عدّة صور مع تنبؤاتها. لاحظ أنّ النموذج يمكن أن يكون على خطأ حتّى و إن كانت ثقته كبيرة في تنبؤه.

# Plot the first X test images, their predicted labels, and the true labels.
# Color correct predictions in blue and incorrect predictions in red.
num_rows = 5
num_cols = 3
num_images = num_rows*num_cols
plt.figure(figsize=(2*2*num_cols, 2*num_rows))
for i in range(num_images):
  plt.subplot(num_rows, 2*num_cols, 2*i+1)
  plot_image(i, predictions[i], test_labels, test_images)
  plt.subplot(num_rows, 2*num_cols, 2*i+2)
  plot_value_array(i, predictions[i], test_labels)
plt.tight_layout()
plt.show()

png

استعمل النموذج المدرّب

أخيرًا، استخدم النموذج المدرّب للتنبؤ بصورة واحدة.

# Grab an image from the test dataset.
img = test_images[1]

print(img.shape)
(28, 28)

تمّ تطوير حزمة tf.keras لتقوم بالتنبؤ بشكل فعّال على دفعة من البيانات في آن واحد. وفقا لذلك، رغم أنّك ستستعمل صورة واحدة في المثال التّالي، فأنت بحاجة إلى إضافتها إلى قائمة (list):

# Add the image to a batch where it's the only member.
img = (np.expand_dims(img,0))

print(img.shape)
(1, 28, 28)

الآن قم باستخدام النموذج للتنبؤ بالتسمية المناسبة لهذه الصورة:

predictions_single = probability_model.predict(img)

print(predictions_single)
[[2.8745531e-05 3.0691236e-14 9.9734110e-01 1.0378227e-11 2.3158297e-03
  1.3557792e-12 3.1429765e-04 3.7679918e-15 2.2482185e-09 4.1826926e-13]]

plot_value_array(1, predictions_single[0], test_labels)
_ = plt.xticks(range(10), class_names, rotation=45)

png

تقوم الوظيفة keras.Model.predict بإرجاع قائمة قوائم (a list of lists) حيث أن كُلّ صورة في الدفعة سيكون لها قائمتها الخاصّة. استخرج التنبؤات للصورة الوحيدة في الدفعة السابقة:

np.argmax(predictions_single[0])
2

و كما نرى يتنبأ النموذج بالتسمية كما هو متوقّع.

# MIT License
#
# Copyright (c) 2017 François Chollet
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.