استخدام الشبكات العصبونية الالتفافية مع صور معقدة

1. قبل البدء

في هذا الدرس التطبيقي حول الترميز، ستستخدم الالتفافات لتصنيف صور الخيول والبشر. ستستخدم TensorFlow في هذا الدرس التطبيقي لإنشاء شبكة CNN مدرّبة على التعرّف على صور الخيول والبشر وتصنيفها.

المتطلبات الأساسية

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

ما ستتعرّف عليه

  • طريقة تدريب أجهزة الكمبيوتر للتعرّف على الميزات في صورة لا يكون الموضوع واضحًا فيها

العناصر التي سيتم إنشاؤها

  • شبكة عصبونية خرقية يمكنها التمييز بين صور الخيول وصور البشر

المتطلبات اللازمة

يمكنك العثور على الرمز لبقية الدرس التطبيقي حول الترميز قيد التشغيل في Colab.

ويجب أيضًا تثبيت TensorFlow والمكتبات التي ثبّتها في الدرس التطبيقي السابق.

2. البدء: الحصول على البيانات

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

الخطوة الأولى: تنزيل البيانات:

!wget --no-check-certificate https://storage.googleapis.com/laurencemoroney-blog.appspot.com/horse-or-human.zip  -O /tmp/horse-or-human.zip

سوف يستخدم رمز Python التالي مكتبة نظام التشغيل لاستخدام مكتبات نظام التشغيل، ما يتيح لك الوصول إلى نظام الملفات ومكتبة الملفات بتنسيق ZIP، ما يتيح لك فك ضغط البيانات.

import os
import zipfile
 
local_zip = '/tmp/horse-or-human.zip'
zip_ref = zipfile.ZipFile(local_zip, 'r')
zip_ref.extractall('/tmp/horse-or-human')
zip_ref.close()

يتم استخراج محتوى ملف ZIP إلى الدليل الأساسي /tmp/horse-or-human الذي يتضمّن خيولًا وأدلة فرعية بشرية.

باختصار، تمثّل مجموعة التدريب البيانات التي تُستخدَم للإشارة إلى نموذج الشبكة العصبونية على النحو التالي:

3- استخدام برنامج GenGenerator لتصنيف البيانات وإعدادها

أنت لا تصنّف الصور بشكل صريح على أنها خيول أو بشر.

سترى لاحقًا شيئًا يُسمى ImageDataGenerator قيد الاستخدام. فهو يقرأ الصور من الأدلة الفرعية ويصنّفها تلقائيًا من اسم هذا الدليل الفرعي. على سبيل المثال، يتوفر لديك دليل تدريب يحتوي على دليل الخيول ودليل البشر. سيعمل ImageDataGenerator على تصنيف الصور بشكل مناسب لك، ما يقلّل من خطوة الترميز.

حدِّد كل دليل من تلك الأدلة.

# Directory with our training horse pictures
train_horse_dir = os.path.join('/tmp/horse-or-human/horses')
 
# Directory with our training human pictures
train_human_dir = os.path.join('/tmp/horse-or-human/humans')

الآن، تعرّف على شكل أسماء الملفات في أدلة تدريب الخيول والبشر.

train_horse_names = os.listdir(train_horse_dir)
print(train_horse_names[:10])
train_human_names = os.listdir(train_human_dir)
print(train_human_names[:10])

ابحث عن العدد الإجمالي لصور الخيول والإنسان في الدلائل:

print('total training horse images:', len(os.listdir(train_horse_dir)))
print('total training human images:', len(os.listdir(train_human_dir)))

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

ألقِ نظرة على بعض الصور للتعرّف بشكل أفضل على شكلها.

أولاً، اضبط معلَمات matplot:

%matplotlib inline
 
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
 
# Parameters for our graph; we'll output images in a 4x4 configuration
nrows = 4
ncols = 4
 
# Index for iterating over images
pic_index = 0

وِدِلْوَقْتِي، يُمْكِنْ عَرْضْ دَفْعَة مِنْ ثَمَانِي صُوَرْ خِيَلْ وَثَمَانِي صُوَرْ شَخْصِيَّة. يمكنك إعادة تشغيل الخلية للاطّلاع على مجموعة جديدة في كل مرة.

# Set up matplotlib fig, and size it to fit 4x4 pics
fig = plt.gcf()
fig.set_size_inches(ncols * 4, nrows * 4)
 
pic_index += 8
next_horse_pix = [os.path.join(train_horse_dir, fname) 
                for fname in train_horse_names[pic_index-8:pic_index]]
next_human_pix = [os.path.join(train_human_dir, fname) 
                for fname in train_human_names[pic_index-8:pic_index]]
 
for i, img_path in enumerate(next_horse_pix+next_human_pix):
  # Set up subplot; subplot indices start at 1
  sp = plt.subplot(nrows, ncols, i + 1)
  sp.axis('Off') # Don't show axes (or gridlines)
 
  img = mpimg.imread(img_path)
  plt.imshow(img)
 
plt.show()
 

في ما يلي بعض الأمثلة على الصور التي تعرض خيولًا وبشرًا بأوضاع واتجاهات مختلفة:

6b6ebbc6e694ccd2.png

5. تعريف النموذج

ابدأ في تحديد النموذج.

يمكنك البدء باستيراد TensorFlow:

import tensorflow as tf

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

ملاحظة: بما أنّك تواجه مشكلة في التصنيف من فئة واحدة (مشكلة في تصنيف ثنائي) سيؤدي هذا إلى إنهاء شبكتك من خلال تفعيل sigmoid بحيث تصبح مخرجات الشبكة مقياسًا واحدًا بين 0 و1، مع ترميز احتمالية أن الصورة الحالية من الفئة 1 (بدلاً من الفئة 0).

model = tf.keras.models.Sequential([
    # Note the input shape is the desired size of the image 300x300 with 3 bytes color
    # This is the first convolution
    tf.keras.layers.Conv2D(16, (3,3), activation='relu', input_shape=(300, 300, 3)),
    tf.keras.layers.MaxPooling2D(2, 2),
    # The second convolution
    tf.keras.layers.Conv2D(32, (3,3), activation='relu'),
    tf.keras.layers.MaxPooling2D(2,2),
    # The third convolution
    tf.keras.layers.Conv2D(64, (3,3), activation='relu'),
    tf.keras.layers.MaxPooling2D(2,2),
    # The fourth convolution
    tf.keras.layers.Conv2D(64, (3,3), activation='relu'),
    tf.keras.layers.MaxPooling2D(2,2),
    # The fifth convolution
    tf.keras.layers.Conv2D(64, (3,3), activation='relu'),
    tf.keras.layers.MaxPooling2D(2,2),
    # Flatten the results to feed into a DNN
    tf.keras.layers.Flatten(),
    # 512 neuron hidden layer
    tf.keras.layers.Dense(512, activation='relu'),
    # Only 1 output neuron. It will contain a value from 0-1 where 0 for 1 class ('horses') and 1 for the other ('humans')
    tf.keras.layers.Dense(1, activation='sigmoid')
])

يؤدي طلب طريقة model.summary() إلى طباعة ملخّص للشبكة.

model.summary()

ويمكنك الاطّلاع على النتائج هنا:

Layer (type)                 Output Shape              Param #   
=================================================================
conv2d (Conv2D)              (None, 298, 298, 16)      448       
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 149, 149, 16)      0         
_________________________________________________________________
conv2d_1 (Conv2D)            (None, 147, 147, 32)      4640      
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 73, 73, 32)        0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 71, 71, 64)        18496     
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 35, 35, 64)        0         
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 33, 33, 64)        36928     
_________________________________________________________________
max_pooling2d_3 (MaxPooling2 (None, 16, 16, 64)        0         
_________________________________________________________________
conv2d_4 (Conv2D)            (None, 14, 14, 64)        36928     
_________________________________________________________________
max_pooling2d_4 (MaxPooling2 (None, 7, 7, 64)          0         
_________________________________________________________________
flatten (Flatten)            (None, 3136)              0         
_________________________________________________________________
dense (Dense)                (None, 512)               1606144   
_________________________________________________________________
dense_1 (Dense)              (None, 1)                 513       
=================================================================
Total params: 1,704,097
Trainable params: 1,704,097
Non-trainable params: 0

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

6- تجميع النموذج

بعد ذلك، يمكنك ضبط مواصفات نموذج التدريب. درِّب النموذج الذي يتضمّن الفقدان binary_crossentropy لأنّه يمثّل مشكلة في التصنيف الثنائي وكان التفعيل النهائي هو دعمًا مماثلاً. (لتذكير حول مقاييس الفقدان، يُرجى الاطّلاع على التنازل في تعلّم الآلة.) استخدم مُحسِّن rmsprop بنسبة تعلم 0.001. أثناء التدريب، راقِب دقة التصنيف.

from tensorflow.keras.optimizers import RMSprop
 
model.compile(loss='binary_crossentropy',
              optimizer=RMSprop(lr=0.001),
              metrics=['acc'])

7- تدريب النموذج من المولدات الكهربائية

يمكنك إعداد أدوات إنشاء بيانات تقرأ الصور في مجلدات المصدر، وحوِّلها إلى أدوات عائمة 32 أو تغذية، (مع علاماتها) إلى شبكتك.

سيكون لديك مولد واحد لصور التدريب وواحد لصور التحقق. ستؤدي المولدات التابعة لك إلى تجميع مجموعات من الصور بحجم 300 × 300 وتصنيفاتها (برنامج ثنائي).

كما ذكرنا سابقًا، يجب عادةً تسوية البيانات التي تنتقل إلى الشبكات العصبونية بطريقة تجعلها أكثر قابلية للمعالجة من قِبل الشبكة. (ليس من الشائع تغذية وحدات البكسل الأولية إلى CNN.) في تجربتك، ستعالج الصور مسبقًا من خلال تسوية قيم البكسل لتكون في النطاق [0، 1] (في الأصل تكون جميع القيم في النطاق [0، 255]).

في Keras، يمكن إجراء ذلك عبر صف keras.preprocessing.image.ImageDataGenerator باستخدام المعلّمة تغيير الحجم. تسمح لك هذه الفئة من ImageDataGenerator بإنشاء مولدات دُفعات الصور المعزّزة (وتصنيفاتها) من خلال .flow(data, التصنيفات) أو .flow_from_directory(directory). ويمكن بعد ذلك استخدام أدوات الإنشاء هذه مع طرق نماذج Keras التي تقبل أدوات إنشاء البيانات كإدخالات: fit_generator وevaluate_generator وpredict_generator.

from tensorflow.keras.preprocessing.image import ImageDataGenerator
 
# All images will be rescaled by 1./255
train_datagen = ImageDataGenerator(rescale=1./255)
 
# Flow training images in batches of 128 using train_datagen generator
train_generator = train_datagen.flow_from_directory(
        '/tmp/horse-or-human/',  # This is the source directory for training images
        target_size=(300, 300),  # All images will be resized to 150x150
        batch_size=128,
        # Since we use binary_crossentropy loss, we need binary labels
        class_mode='binary')

8- إجراء التدريب

القطار لمدة 15 حقبة. (قد يستغرق ذلك بضع دقائق).

history = model.fit(
      train_generator,
      steps_per_epoch=8,  
      epochs=15,
      verbose=1)

سجِّل القيم لكل حقبة.

تمثّل الخسارة والدقة مؤشرًا رائعًا على مدى تقدّم التدريب. يعتمد ذلك على تخمين تصنيف بيانات التدريب ثم قياسها مقابل التصنيف المعروف، مع حساب النتيجة. الدقة هي الجزء من التخمينات الصحيحة.

Epoch 1/15
9/9 [==============================] - 9s 1s/step - loss: 0.8662 - acc: 0.5151
Epoch 2/15
9/9 [==============================] - 8s 927ms/step - loss: 0.7212 - acc: 0.5969
Epoch 3/15
9/9 [==============================] - 8s 921ms/step - loss: 0.6612 - acc: 0.6592
Epoch 4/15
9/9 [==============================] - 8s 925ms/step - loss: 0.3135 - acc: 0.8481
Epoch 5/15
9/9 [==============================] - 8s 919ms/step - loss: 0.4640 - acc: 0.8530
Epoch 6/15
9/9 [==============================] - 8s 896ms/step - loss: 0.2306 - acc: 0.9231
Epoch 7/15
9/9 [==============================] - 8s 915ms/step - loss: 0.1464 - acc: 0.9396
Epoch 8/15
9/9 [==============================] - 8s 935ms/step - loss: 0.2663 - acc: 0.8919
Epoch 9/15
9/9 [==============================] - 8s 883ms/step - loss: 0.0772 - acc: 0.9698
Epoch 10/15
9/9 [==============================] - 9s 951ms/step - loss: 0.0403 - acc: 0.9805
Epoch 11/15
9/9 [==============================] - 8s 891ms/step - loss: 0.2618 - acc: 0.9075
Epoch 12/15
9/9 [==============================] - 8s 902ms/step - loss: 0.0434 - acc: 0.9873
Epoch 13/15
9/9 [==============================] - 8s 904ms/step - loss: 0.0187 - acc: 0.9932
Epoch 14/15
9/9 [==============================] - 9s 951ms/step - loss: 0.0974 - acc: 0.9649
Epoch 15/15
9/9 [==============================] - 8s 877ms/step - loss: 0.2859 - acc: 0.9338

9- اختبار النموذج

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

يمكنك تنزيل الصور من الإنترنت إلى نظام الملفات لتجربتها. تجدر الإشارة إلى أنّ الشبكة قد تخطئ كثيرًا رغم أنّ دقة التدريب تزيد عن 99%.

ويرجع ذلك إلى ما يُسمّى التعامُد، ما يعني أن الشبكة العصبونية مدرَّبة ببيانات محدودة للغاية (هناك 500 صورة تقريبًا لكل صف دراسي). لذلك، من المفيد جدًا تمييز الصور التي تشبه تلك التي في مجموعة التدريب، ولكنها قد تفشل بشكل كبير في الصور غير الموجودة في مجموعة التدريب.

هذه هي نقطة البيانات التي تثبت أنه كلما زادت البيانات التي تتدرب عليها، كانت شبكتك النهائية أفضل!

هناك العديد من الأساليب التي يمكن استخدامها لتحسين تدريبك، على الرغم من البيانات المحدودة، بما في ذلك شيء يسمى زيادة الصور، ولكن هذا خارج نطاق هذا الدرس التطبيقي حول الترميز.

import numpy as np
from google.colab import files
from keras.preprocessing import image
 
uploaded = files.upload()
 
for fn in uploaded.keys():
 
  # predicting images
  path = '/content/' + fn
  img = image.load_img(path, target_size=(300, 300))
  x = image.img_to_array(img)
  x = np.expand_dims(x, axis=0)
 
  images = np.vstack([x])
  classes = model.predict(images, batch_size=10)
  print(classes[0])
  if classes[0]>0.5:
    print(fn + " is a human")
  else:
    print(fn + " is a horse")

مثلاً، لنفترض أنك تريد الاختبار باستخدام هذه الصورة:

9e07a57ff3be7a82.jpeg

في ما يلي ما ينتجه التعاون:

77b678e70b00862a.png

وعلى الرغم من أنها رسم كرتوني، فإنها لا تزال تصنِّف بشكل صحيح.

كما يتم تصنيف الصورة التالية بشكل صحيح:

c9213173d9f3d83c.jpeg

f2844da737a1a2f2.png

يمكنك تجربة بعض الصور الخاصة بك واستكشافها.

10- عرض تمثيلات تمثيلية بصرية

للتعرّف على نوع الميزات التي تعلّمتها محطة CNN، يمكنك تصوير كيفية تحويل إدخال البيانات أثناء مروره على CNN.

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

import numpy as np
import random
from tensorflow.keras.preprocessing.image import img_to_array, load_img
 
# Let's define a new Model that will take an image as input, and will output
# intermediate representations for all layers in the previous model after
# the first.
successive_outputs = [layer.output for layer in model.layers[1:]]
#visualization_model = Model(img_input, successive_outputs)
visualization_model = tf.keras.models.Model(inputs = model.input, outputs = successive_outputs)
# Let's prepare a random input image from the training set.
horse_img_files = [os.path.join(train_horse_dir, f) for f in train_horse_names]
human_img_files = [os.path.join(train_human_dir, f) for f in train_human_names]
img_path = random.choice(horse_img_files + human_img_files)
 
img = load_img(img_path, target_size=(300, 300))  # this is a PIL image
x = img_to_array(img)  # Numpy array with shape (150, 150, 3)
x = x.reshape((1,) + x.shape)  # Numpy array with shape (1, 150, 150, 3)
 
# Rescale by 1/255
x /= 255
 
# Let's run our image through our network, thus obtaining all
# intermediate representations for this image.
successive_feature_maps = visualization_model.predict(x)
 
# These are the names of the layers, so can have them as part of our plot
layer_names = [layer.name for layer in model.layers]
 
# Now let's display our representations
for layer_name, feature_map in zip(layer_names, successive_feature_maps):
  if len(feature_map.shape) == 4:
    # Just do this for the conv / maxpool layers, not the fully-connected layers
    n_features = feature_map.shape[-1]  # number of features in feature map
    # The feature map has shape (1, size, size, n_features)
    size = feature_map.shape[1]
    # We will tile our images in this matrix
    display_grid = np.zeros((size, size * n_features))
    for i in range(n_features):
      # Postprocess the feature to make it visually palatable
      x = feature_map[0, :, :, i]
      x -= x.mean()
      if x.std()>0:
        x /= x.std()
      x *= 64
      x += 128
      x = np.clip(x, 0, 255).astype('uint8')
      # We'll tile each filter into this big horizontal grid
      display_grid[:, i * size : (i + 1) * size] = x
    # Display the grid
    scale = 20. / n_features
    plt.figure(figsize=(scale * n_features, scale))
    plt.title(layer_name)
    plt.grid(False)
    plt.imshow(display_grid, aspect='auto', cmap='viridis')

إليك أمثلة على النتائج:

e078d1bc9662c93f.png

كما ترى، تنتقل من وحدات البكسل الأولية للصور إلى تمثيلات تجريدية ومكثفة بشكل متزايد. تبدأ التمثيلات الأولية في تسليط الضوء على ما تهتم به الشبكة، كما أنها تعرض عددًا أقل أو أقل من الميزات التي يتم &تنشيطها. أَكْتَرْ مَضْبُوطْ عَلَى صِفْرْ مُمَيَّزْ بِاسْمْ بَرَانَةْ. التنوع في تمثيل البيانات هو ميزة رئيسية في التعلّم المتعمّق.

وتحتوي هذه التمثيلات على معلومات أقل بشكل متزايد عن وحدات البكسل الأصلية للصورة، ولكنها تزيد بشكلٍ متزايد من المعلومات المتعلقة بفئة الصورة. ويمكنك اعتبار شبكة CNN (أو شبكة الشبكة بشكل عام) مسارًا مقطرًا للمعلومات.

11- تهانينا

لقد تعلمت كيفية استخدام شبكات CNN لتحسين الصور المعقدة. للتعرّف على كيفية تحسين نماذج الرؤية الحاسوبية بشكل أكبر، يُرجى المتابعة إلى استخدام الشبكات العصبونية الاصطناعية (CNNs) التي تحتوي على مجموعات بيانات كبيرة لتجنُّب الزيادة في العرض.