Logo

بناء شبكة CNN لتصنيف الصور في مجموعة بيانات CIFAR-10

10 دقائق قراءة
شرائح الدرس
1 / 12

بناء شبكة CNN لتصنيف صور مجموعة بيانات CIFAR-10

الطبقات الالتفافية، أول شبكة CNN، وتضخيم البيانات

بناء شبكة CNN لتصنيف الصور في مجموعة بيانات CIFAR-10

سنعمل مع مجموعة بيانات CIFAR-10. وهي مجموعة بيانات معروفة لتصنيف الصور، تتكون من 60000 صورة ملونة بحجم 32×32 موزّعة على 10 فئات، بواقع 6000 صورة لكل فئة. توجد 50000 صورة للتدريب و10000 صورة للاختبار.

الفئات العشر هي:

  1. طائرة (airplane)
  2. سيارة (automobile)
  3. طائر (bird)
  4. قطة (cat)
  5. غزال (deer)
  6. كلب (dog)
  7. ضفدع (frog)
  8. حصان (horse)
  9. سفينة (ship)
  10. شاحنة (truck)

لمزيد من التفاصيل حول CIFAR-10، راجع: https://www.cs.toronto.edu/~kriz/cifar.html

للاطلاع على مجموعة من نتائج الأداء المنشورة على CIFAR-10، راجع: http://rodrigob.github.io/are_we_there_yet/build/classification_datasets_results.html


بناء الشبكات العصبية الالتفافية (CNN)

في هذا التمرين سنبني وندرّب أول شبكة عصبية التفافية (Convolutional Neural Network) لنا. في الجزء الأول، سنستعرض الطبقات المختلفة وكيفية ضبط إعداداتها. وفي الجزء الثاني، ستبني نموذجك الخاص، وتدرّبه، وتقارن أداءه.

import keras
from keras.datasets import cifar10
from keras.models import Sequential
from keras.layers import Dense, Dropout, Activation, Flatten
from keras.layers import Conv2D, MaxPooling2D
 
import matplotlib.pyplot as plt
%matplotlib inline
# The data, shuffled and split between train and test sets:
(x_train, y_train), (x_test, y_test) = cifar10.load_data()
print('x_train shape:', x_train.shape)
print(x_train.shape[0], 'train samples')
print(x_test.shape[0], 'test samples')
x_train shape: (50000, 32, 32, 3)
50000 train samples
10000 test samples
## Each image is a 32 x 32 x 3 numpy array
x_train[444].shape
(32, 32, 3)
## Let's look at one of the images
 
print(y_train[444])
plt.imshow(x_train[444]);
[9]
num_classes = 10
 
y_train = keras.utils.to_categorical(y_train, num_classes)
y_test = keras.utils.to_categorical(y_test, num_classes)
# now instead of classes described by an integer between 0-9 we have a vector with a 1 in the (Pythonic) 9th position
y_train[444]
array([0., 0., 0., 0., 0., 0., 0., 0., 0., 1.])
# As before, let's make everything float and scale
x_train = x_train.astype('float32')
x_test = x_test.astype('float32')
x_train /= 255
x_test /= 255

طبقات Keras الخاصة بشبكات CNN

  • في السابق بنينا شبكات عصبية باستخدام بشكل أساسي طبقات Dense، وActivation، وDropout.

  • هنا سنشرح كيفية استخدام بعض الطبقات المخصَّصة لشبكات CNN التي توفّرها Keras

Conv2D

keras.layers.convolutional.Conv2D(filters, kernel_size, strides=(1, 1), padding='valid', data_format=None, dilation_rate=(1, 1), activation=None, use_bias=True, kernel_initializer='glorot_uniform', bias_initializer='zeros', kernel_regularizer=None, bias_regularizer=None, activity_regularizer=None, kernel_constraint=None, bias_constraint=None, **kwargs)

شرح بعض المعاملات:

  • filters: عدد المرشِّحات (Filters) المستخدمة في كل موقع. بعبارة أخرى، عمق (Depth) المخرج.
  • kernel_size: زوج (x,y) يحدّد ارتفاع وعرض النواة (Kernel) المستخدمة
  • strides: زوج (x,y) يحدّد الخطوة (Stride) في كل بُعد. القيمة الافتراضية هي (1,1)
  • input_shape: مطلوب فقط للطبقة الأولى

لاحظ أن حجم المخرج يتحدَّد بناءً على kernel_size وstrides

MaxPooling2D

keras.layers.pooling.MaxPooling2D(pool_size=(2, 2), strides=None, padding='valid', data_format=None)

  • pool_size: حجم (x,y) للشبكة (Grid) التي سيُطبَّق عليها التجميع (Pooling).
  • strides: يُفترَض أنها تساوي pool_size ما لم يُحدَّد خلاف ذلك

Flatten

تحوّل مدخلها إلى متجه أحادي البُعد (لكل عيّنة). تُستخدَم عادةً عند الانتقال بين الطبقات الالتفافية (Convolutional) والطبقات متصلة بالكامل (Fully Connected).


أول شبكة CNN

سنبني أدناه أول شبكة CNN لنا. لأغراض العرض التوضيحي (كي تتدرب بسرعة)، فهي ليست عميقة جدًا ولديها عدد قليل نسبيًا من المعاملات. نستخدم خطوات (Strides) بقيمة 2 في أول طبقتين التفافيتين، مما يقلّل بسرعة أبعاد المخرج. بعد طبقة MaxPooling، نُسطِّح (Flatten) البيانات، ثم لدينا طبقة واحدة متصلة بالكامل قبل طبقة التصنيف النهائية.

# Let's build a CNN using Keras' Sequential capabilities
 
model_1 = Sequential()
 
 
## 5x5 convolution with 2x2 stride and 32 filters
model_1.add(Conv2D(32, kernel_size = (5, 5), strides = (2,2), padding='same',
                 input_shape=x_train.shape[1:]))
model_1.add(Activation('relu'))
 
## Another 5x5 convolution with 2x2 stride and 32 filters
model_1.add(Conv2D(32, (5, 5), strides = (2,2), activation="relu")) # another way to define activation
 
 
## 2x2 max pooling reduces to 3 x 3 x 32
model_1.add(MaxPooling2D(pool_size=(2, 2)))
model_1.add(Dropout(0.25))
 
## Flatten turns 3x3x32 into 288x1
model_1.add(Flatten())
model_1.add(Dense(512))
model_1.add(Activation('relu'))
model_1.add(Dropout(0.5))
model_1.add(Dense(num_classes))
model_1.add(Activation('softmax'))
 
model_1.summary()
/usr/local/lib/python3.11/dist-packages/keras/src/layers/convolutional/base_conv.py:107: UserWarning: Do not pass an `input_shape`/`input_dim` argument to a layer. When using Sequential models, prefer using an `Input(shape)` object as the first layer in the model instead.
  super().__init__(activity_regularizer=activity_regularizer, **kwargs)
Model: "sequential"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Layer (type)                    ┃ Output Shape           ┃       Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ conv2d (Conv2D)                 │ (None, 16, 16, 32)     │         2,432 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ activation (Activation)         │ (None, 16, 16, 32)     │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ conv2d_1 (Conv2D)               │ (None, 6, 6, 32)       │        25,632 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ max_pooling2d (MaxPooling2D)    │ (None, 3, 3, 32)       │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dropout (Dropout)               │ (None, 3, 3, 32)       │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ flatten (Flatten)               │ (None, 288)            │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense (Dense)                   │ (None, 512)            │       147,968 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ activation_1 (Activation)       │ (None, 512)            │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dropout_1 (Dropout)             │ (None, 512)            │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_1 (Dense)                 │ (None, 10)             │         5,130 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ activation_2 (Activation)       │ (None, 10)             │             0 │
└─────────────────────────────────┴────────────────────────┴───────────────┘
 Total params: 181,162 (707.66 KB)
 Trainable params: 181,162 (707.66 KB)
 Non-trainable params: 0 (0.00 B)

ما زال لدينا 181 ألف معامل (Parameters)، رغم أن هذا نموذج "صغير".

batch_size = 32
 
# initiate RMSprop optimizer
opt = keras.optimizers.RMSprop(learning_rate=0.0005)
 
# Let's train the model using RMSprop
model_1.compile(loss='categorical_crossentropy',
              optimizer='rmsprop',
              metrics=['accuracy'])
 
model_1.fit(x_train, y_train,
              batch_size=batch_size,
              epochs=15,
              validation_data=(x_test, y_test),
              shuffle=True)
Epoch 1/15
1563/1563 ━━━━━━━━━━━━━━━━━━━━ 13s 6ms/step - accuracy: 0.3136 - loss: 1.8675 - val_accuracy: 0.4506 - val_loss: 1.5152
Epoch 2/15
1563/1563 ━━━━━━━━━━━━━━━━━━━━ 6s 4ms/step - accuracy: 0.4739 - loss: 1.4538 - val_accuracy: 0.5385 - val_loss: 1.2852
Epoch 3/15
1563/1563 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.5191 - loss: 1.3548 - val_accuracy: 0.5606 - val_loss: 1.2484
Epoch 4/15
1563/1563 ━━━━━━━━━━━━━━━━━━━━ 6s 4ms/step - accuracy: 0.5435 - loss: 1.3027 - val_accuracy: 0.5620 - val_loss: 1.2574
Epoch 5/15
1563/1563 ━━━━━━━━━━━━━━━━━━━━ 6s 4ms/step - accuracy: 0.5592 - loss: 1.2648 - val_accuracy: 0.5616 - val_loss: 1.2314
Epoch 6/15
1563/1563 ━━━━━━━━━━━━━━━━━━━━ 6s 4ms/step - accuracy: 0.5674 - loss: 1.2424 - val_accuracy: 0.5452 - val_loss: 1.3458
Epoch 7/15
1563/1563 ━━━━━━━━━━━━━━━━━━━━ 10s 4ms/step - accuracy: 0.5718 - loss: 1.2406 - val_accuracy: 0.5917 - val_loss: 1.1967
Epoch 8/15
1563/1563 ━━━━━━━━━━━━━━━━━━━━ 5s 4ms/step - accuracy: 0.5748 - loss: 1.2402 - val_accuracy: 0.6114 - val_loss: 1.1200
Epoch 9/15
1563/1563 ━━━━━━━━━━━━━━━━━━━━ 6s 4ms/step - accuracy: 0.5711 - loss: 1.2464 - val_accuracy: 0.5790 - val_loss: 1.2071
Epoch 10/15
1563/1563 ━━━━━━━━━━━━━━━━━━━━ 10s 4ms/step - accuracy: 0.5820 - loss: 1.2403 - val_accuracy: 0.4886 - val_loss: 1.5045
Epoch 11/15
1563/1563 ━━━━━━━━━━━━━━━━━━━━ 6s 4ms/step - accuracy: 0.5753 - loss: 1.2391 - val_accuracy: 0.6053 - val_loss: 1.1649
Epoch 12/15
1563/1563 ━━━━━━━━━━━━━━━━━━━━ 10s 4ms/step - accuracy: 0.5779 - loss: 1.2530 - val_accuracy: 0.5396 - val_loss: 1.3305
Epoch 13/15
1563/1563 ━━━━━━━━━━━━━━━━━━━━ 6s 4ms/step - accuracy: 0.5763 - loss: 1.2622 - val_accuracy: 0.5561 - val_loss: 1.2769
Epoch 14/15
1563/1563 ━━━━━━━━━━━━━━━━━━━━ 6s 4ms/step - accuracy: 0.5788 - loss: 1.2512 - val_accuracy: 0.5679 - val_loss: 1.2698
Epoch 15/15
1563/1563 ━━━━━━━━━━━━━━━━━━━━ 6s 4ms/step - accuracy: 0.5731 - loss: 1.2575 - val_accuracy: 0.5863 - val_loss: 1.2251
<keras.src.callbacks.history.History at 0x7b0b01452b50>

تمرين

كان لنموذجنا السابق البنية التالية:

Conv -> Conv -> MaxPool -> (Flatten) -> Dense -> التصنيف النهائي

(مع دوال التنشيط ونسب الإسقاط - Dropouts المناسبة)

  1. ابنِ نموذجًا أكثر تعقيدًا بالنمط التالي:
  • Conv -> Conv -> MaxPool -> Conv -> Conv -> MaxPool -> (Flatten) -> Dense -> التصنيف النهائي

  • استخدم خطوات (Strides) بقيمة 1 لجميع الطبقات الالتفافية.

  1. كم عدد المعاملات (Parameters) التي يمتلكها نموذجك؟ كيف تقارَن بالنموذج السابق؟

  2. درّبه لمدة 5 حِقَب (Epochs). ماذا تلاحظ حول وقت التدريب، وأرقام الخسارة (Loss) والدقة (Accuracy) (على كل من مجموعتَي التدريب والتحقق)؟

  3. جرّب بُنى وأوقات تشغيل مختلفة، وانظر إلى أي درجة يمكن أن يصبح نموذجك دقيقًا.

# Please provide your solution here
# Create model_2 as mentioned in the exercise
 

تصنيف CIFAR-10 باستخدام زيادة البيانات (Data Augmentation)

تُستخدَم زيادة البيانات (Data Augmentation) لتوسيع مجموعة بيانات التدريب بشكل مصطنَع عن طريق إنشاء نسخ معدَّلة من الصور الموجودة.

تساعد هذه التعديلات نموذجك على أن يصبح أكثر متانة (Robust)، وأن يعمّم بشكل أفضل، ويتجنّب الإفراط في التخصيص (Overfitting).

بعد ذلك، سنعود إلى CIFAR-10 والشبكات التي بنيناها سابقًا. سنستخدم زيادة بيانات فورية (Real-Time) لمحاولة تحسين نتائجنا.

عندما تنتهي من مراجعة الدفتر (Notebook)، جرّب معاملات مختلفة لزيادة البيانات وانظر إذا كانت تُحسِّن (أو تُضعِف!) أداء المصنِّف الخاص بك.

from tensorflow.keras.preprocessing.image import ImageDataGenerator
 
datagen = ImageDataGenerator(
    rotation_range=20,  # randomly rotate images in the range (degrees, 0 to 180)
    width_shift_range=0.1,  # randomly shift images horizontally (fraction of total width)
    height_shift_range=0.1,  # randomly shift images vertically (fraction of total height)
    horizontal_flip=False,  # randomly flip images
    shear_range=0.1,   # Shear intensity (shear angle in counter-clockwise direction)
    zoom_range=0.1,    # Zoom in/out on images
    fill_mode='nearest'   # Strategy to fill in new pixels
    )
# Let's build a CNN using Keras' Sequential capabilities
model_1 = Sequential()
 
## 5x5 convolution with 2x2 stride and 32 filters
model_1.add(Conv2D(32, kernel_size = (5, 5), strides = (2,2), padding='same',
                 input_shape=x_train.shape[1:]))
model_1.add(Activation('relu'))
 
## Another 5x5 convolution with 2x2 stride and 32 filters
model_1.add(Conv2D(32, (5, 5), strides = (2,2), activation="relu")) # another way to define activation
 
 
## 2x2 max pooling reduces to 3 x 3 x 32
model_1.add(MaxPooling2D(pool_size=(2, 2)))
#model_1.add(Dropout(0.25))
 
## Flatten turns 3x3x32 into 288x1
model_1.add(Flatten())
model_1.add(Dense(512))
model_1.add(Activation('relu'))
#model_1.add(Dropout(0.5))
model_1.add(Dense(num_classes))
model_1.add(Activation('softmax'))
 
batch_size = 32
 
# Let's train the model using RMSprop
model_1.compile(loss='categorical_crossentropy',
                optimizer='rmsprop',
                metrics=['accuracy'])
 
# Fit the model on the batches generated by datagen.flow().
model_1.fit(
    datagen.flow(x_train, y_train, batch_size=batch_size),
    epochs=15,
    validation_data=(x_test, y_test)
)
Epoch 1/15
/usr/local/lib/python3.11/dist-packages/keras/src/trainers/data_adapters/py_dataset_adapter.py:121: UserWarning: Your `PyDataset` class should call `super().__init__(**kwargs)` in its constructor. `**kwargs` can include `workers`, `use_multiprocessing`, `max_queue_size`. Do not pass these arguments to `fit()`, as they will be ignored.
  self._warn_if_super_not_called()
1563/1563 ━━━━━━━━━━━━━━━━━━━━ 33s 20ms/step - accuracy: 0.3195 - loss: 1.8580 - val_accuracy: 0.5205 - val_loss: 1.3476
Epoch 2/15
1563/1563 ━━━━━━━━━━━━━━━━━━━━ 30s 19ms/step - accuracy: 0.4840 - loss: 1.4376 - val_accuracy: 0.5217 - val_loss: 1.3634
Epoch 3/15
1563/1563 ━━━━━━━━━━━━━━━━━━━━ 30s 19ms/step - accuracy: 0.5274 - loss: 1.3324 - val_accuracy: 0.5750 - val_loss: 1.1765
Epoch 4/15
1563/1563 ━━━━━━━━━━━━━━━━━━━━ 41s 19ms/step - accuracy: 0.5594 - loss: 1.2471 - val_accuracy: 0.6083 - val_loss: 1.1206
Epoch 5/15
1563/1563 ━━━━━━━━━━━━━━━━━━━━ 30s 19ms/step - accuracy: 0.5745 - loss: 1.2118 - val_accuracy: 0.6015 - val_loss: 1.1356
Epoch 6/15
1563/1563 ━━━━━━━━━━━━━━━━━━━━ 29s 19ms/step - accuracy: 0.5858 - loss: 1.1726 - val_accuracy: 0.6154 - val_loss: 1.1121
Epoch 7/15
1563/1563 ━━━━━━━━━━━━━━━━━━━━ 42s 19ms/step - accuracy: 0.6007 - loss: 1.1575 - val_accuracy: 0.6197 - val_loss: 1.0995
Epoch 8/15
1563/1563 ━━━━━━━━━━━━━━━━━━━━ 33s 21ms/step - accuracy: 0.6035 - loss: 1.1363 - val_accuracy: 0.6273 - val_loss: 1.0985
Epoch 9/15
1563/1563 ━━━━━━━━━━━━━━━━━━━━ 38s 19ms/step - accuracy: 0.6074 - loss: 1.1399 - val_accuracy: 0.6604 - val_loss: 0.9989
Epoch 10/15
1563/1563 ━━━━━━━━━━━━━━━━━━━━ 31s 20ms/step - accuracy: 0.6181 - loss: 1.1066 - val_accuracy: 0.6279 - val_loss: 1.1005
Epoch 11/15
1563/1563 ━━━━━━━━━━━━━━━━━━━━ 30s 19ms/step - accuracy: 0.6132 - loss: 1.1133 - val_accuracy: 0.6527 - val_loss: 1.0322
Epoch 12/15
1563/1563 ━━━━━━━━━━━━━━━━━━━━ 31s 20ms/step - accuracy: 0.6201 - loss: 1.1032 - val_accuracy: 0.6485 - val_loss: 1.0506
Epoch 13/15
1563/1563 ━━━━━━━━━━━━━━━━━━━━ 30s 19ms/step - accuracy: 0.6232 - loss: 1.1023 - val_accuracy: 0.6452 - val_loss: 1.0841
Epoch 14/15
1563/1563 ━━━━━━━━━━━━━━━━━━━━ 31s 20ms/step - accuracy: 0.6258 - loss: 1.0899 - val_accuracy: 0.6548 - val_loss: 1.0765
Epoch 15/15
1563/1563 ━━━━━━━━━━━━━━━━━━━━ 29s 19ms/step - accuracy: 0.6221 - loss: 1.1078 - val_accuracy: 0.6285 - val_loss: 1.0848
<keras.src.callbacks.history.History at 0x7b0b01450390>

كيف يقارَن الأداء بالتدريب غير المُعزَّز بزيادة البيانات؟

 

تمرين

دورك الآن

  1. جرّب أعلاه إعدادات مختلفة لمعاملات زيادة البيانات. هل يمكنك جعل النموذج يؤدي بشكل أفضل؟ هل يمكنك جعله يؤدي بشكل أسوأ؟