Logo

نموذج Keras التسلسلي (Sequential)

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

نموذج Keras التسلسلي (Sequential)

بناء مكدس بسيط من الطبقات، وفحصه، وتدريبه

المؤلف: fchollet
تاريخ الإنشاء: 2020/04/12
آخر تعديل: 2023/06/25
الوصف: دليل كامل للنموذج التسلسلي (Sequential).

الإعداد

import keras
from keras import layers
from keras import ops

متى تستخدم نموذجًا تسلسليًا (Sequential)

يناسب النموذج Sequential حالة تكديس بسيط من الطبقات (Layers) حيث تمتلك كل طبقة مُدخلاً واحدًا بالضبط ومُخرجًا واحدًا بالضبط (Tensor).

توضيحيًا، النموذج Sequential التالي:

# Define Sequential model with 3 layers
model = keras.Sequential(
    [
        keras.Input(shape=(3,1)),
        layers.Dense(2, activation="relu", name="layer1"),
        layers.Dense(3, activation="relu", name="layer2"),
        layers.Dense(4, name="layer3"),
    ]
)

النموذج التسلسلي غير مناسب عندما:

  • يمتلك نموذجك عدة مُدخلات أو عدة مُخرجات
  • تمتلك أي طبقة من طبقاتك عدة مُدخلات أو عدة مُخرجات
  • تحتاج إلى مشاركة الطبقات (Layer sharing)
  • تريد بنية غير خطية (Non-linear topology) (مثل اتصال متبقٍ - Residual connection، أو نموذج متعدد الفروع)

إنشاء نموذج تسلسلي

يمكنك إنشاء نموذج Sequential بتمرير قائمة من الطبقات إلى باني (Constructor) الصنف Sequential:

model = keras.Sequential(
    [
        layers.Dense(2, activation="relu"),
        layers.Dense(3, activation="relu"),
        layers.Dense(4),
    ]
)

يمكن الوصول إلى طبقاته عبر الخاصية layers:

model.layers
[<Dense name=dense, built=False>,
 <Dense name=dense_1, built=False>,
 <Dense name=dense_2, built=False>]

يمكنك أيضًا إنشاء نموذج Sequential بشكل تدريجي عبر الدالة add():

model = keras.Sequential()
model.add(layers.Dense(2, activation="relu"))
model.add(layers.Dense(3, activation="relu"))
model.add(layers.Dense(4))

لاحظ أنه توجد أيضًا دالة pop() مقابلة لإزالة الطبقات: يتصرّف النموذج التسلسلي تمامًا مثل قائمة من الطبقات.

model.pop()
print(len(model.layers))  # 2
2

لاحظ أيضًا أن باني Sequential يقبل معامل name، تمامًا مثل أي طبقة أو نموذج في Keras. هذا مفيد لتوسيم رسومات TensorBoard البيانية بأسماء ذات دلالة واضحة.

model = keras.Sequential(name="my_sequential")
model.add(layers.Dense(2, activation="relu", name="layer1"))
model.add(layers.Dense(3, activation="relu", name="layer2"))
model.add(layers.Dense(4, name="layer3"))

تحديد شكل المُدخل مسبقًا

بشكل عام، تحتاج جميع الطبقات في Keras إلى معرفة شكل (Shape) مُدخلاتها حتى تتمكن من إنشاء أوزانها (Weights). لذا عندما تنشئ طبقة كهذه، لا تمتلك في البداية أي أوزان:

layer = layers.Dense(3)
layer.weights  # Empty
[]

تُنشئ الطبقة أوزانها في أول مرة تُستدعى فيها على مُدخل، لأن شكل الأوزان يعتمد على شكل المُدخلات:

# Call layer on a test input
x = ops.ones((1, 4)) # creates a tensor of shape (1, 4) filled with ones.
y = layer(x)
layer.weights  # Now it has weights, of shape (4, 3) and (3,)
[<Variable path=dense_6/kernel, shape=(4, 3), dtype=float32, value=[[ 0.772823    0.559764   -0.4932681 ]
  [ 0.01619363 -0.34241778  0.6054685 ]
  [ 0.7129122   0.34047163 -0.08443046]
  [-0.6911168  -0.6273356   0.03513336]]>,
 <Variable path=dense_6/bias, shape=(3,), dtype=float32, value=[0. 0. 0.]>]

بطبيعة الحال، ينطبق هذا أيضًا على النماذج التسلسلية. عندما تُنشئ نموذجًا تسلسليًا دون تحديد شكل المُدخل، فإنه لا يكون "مبنيًا" (Built): لا يمتلك أوزانًا (واستدعاء model.weights ينتج عنه خطأ يوضّح ذلك بالضبط). تُنشَأ الأوزان عندما يرى النموذج بعض بيانات المُدخل لأول مرة:

model = keras.Sequential(
    [
        #keras.Input(shape=(4,)), # if this removed, No weights defined at this stage!
        layers.Dense(2, activation="relu"),
        layers.Dense(3, activation="relu"),
        layers.Dense(4),
    ]
)
 
model.weights
[]
# Call the model on a test input
# performs a forward pass of the input x through the model.
x = ops.ones((1, 4))
y = model(x)
# Now we have weights becuase input shape is defined
model.weights
[<Variable path=sequential_11/dense_31/kernel, shape=(4, 2), dtype=float32, value=[[ 0.5186703  -0.89275837]
  [-0.32034516  0.22481966]
  [ 0.1135633  -0.88960075]
  [-0.47233725  0.6958945 ]]>,
 <Variable path=sequential_11/dense_31/bias, shape=(2,), dtype=float32, value=[0. 0.]>,
 <Variable path=sequential_11/dense_32/kernel, shape=(2, 3), dtype=float32, value=[[ 0.10157859  0.19588745  0.05911839]
  [ 0.4548638   0.4834206  -1.0363262 ]]>,
 <Variable path=sequential_11/dense_32/bias, shape=(3,), dtype=float32, value=[0. 0. 0.]>,
 <Variable path=sequential_11/dense_33/kernel, shape=(3, 4), dtype=float32, value=[[ 0.08667135  0.63437176  0.64811933 -0.4640239 ]
  [ 0.4898951  -0.38968176  0.40150666 -0.8447258 ]
  [-0.09795547  0.2846216  -0.6438199  -0.68790245]]>,
 <Variable path=sequential_11/dense_33/bias, shape=(4,), dtype=float32, value=[0. 0. 0. 0.]>]
y
<tf.Tensor: shape=(1, 4), dtype=float32, numpy=
array([[-0.06730284, -0.033949  , -0.05837943,  0.04649062]],
      dtype=float32)>

بمجرد أن يكون النموذج "مبنيًا"، يمكنك استدعاء دالته summary() لعرض محتواه:

model.summary()
Model: "sequential"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓
┃ Layer (type)                         ┃ Output Shape                ┃         Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━┩
│ dense (Dense)                        │ (1, 2)                      │              10 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dense_1 (Dense)                      │ (1, 3)                      │               9 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dense_2 (Dense)                      │ (1, 4)                      │              16 │
└──────────────────────────────────────┴─────────────────────────────┴─────────────────┘
 Total params: 35 (140.00 B)
 Trainable params: 35 (140.00 B)
 Non-trainable params: 0 (0.00 B)

مع ذلك، يمكن أن يكون مفيدًا جدًا عند بناء نموذج تسلسلي بشكل تدريجي أن تتمكن من عرض ملخص النموذج حتى الآن، بما في ذلك شكل المُخرج الحالي. في هذه الحالة، يجب أن تبدأ نموذجك بتمرير كائن Input إلى نموذجك، حتى يعرف شكل مُدخله منذ البداية:

model = keras.Sequential()
model.add(keras.Input(shape=(4,)))
model.add(layers.Dense(2, activation="relu"))
 
model.summary()

لاحظ أن كائن Input لا يظهر كجزء من model.layers، لأنه ليس طبقة:

model.layers

النماذج المبنية بشكل مُدخل مُحدَّد مسبقًا كهذه تمتلك دائمًا أوزانًا (حتى قبل رؤية أي بيانات) ولها دائمًا شكل مُخرج محدَّد.

  • بشكل عام، من الممارسات الجيدة الموصى بها تحديد شكل المُدخل لنموذج Sequential مسبقًا إذا كنت تعرفه.

  • عند بناء بنية Sequential جديدة، من المفيد تكديس الطبقات تدريجيًا باستخدام add() وطباعة ملخصات النموذج بشكل متكرر. لكي تتمكن من مراقبة كيفية عمل تكديس الطبقات.

التدريب والتقييم والاستدلال (Training, evaluation, and inference)

يعمل التدريب والتقييم والاستدلال بنفس الطريقة تمامًا للنماذج المبنية باستخدام الواجهة الوظيفية (Functional API) كما هو الحال في النماذج Sequential.

يوفّر الصنف Model حلقة تدريب مدمجة (الدالة fit()) وحلقة تقييم مدمجة (الدالة evaluate()). لاحظ أنه يمكنك تخصيص هاتين الحلقتين بسهولة لتنفيذ روتينات تدريب خاصة بك.

هنا، حمِّل بيانات صور MNIST، وأعد تشكيلها إلى متجهات (Vectors)، ودرّب النموذج على البيانات (مع مراقبة الأداء على تقسيم تحقق - Validation split)، ثم قيّم النموذج على بيانات الاختبار:

model = keras.Sequential()
model.add(keras.Input(shape=(784,)))
model.add(layers.Dense(64, activation="relu"))
model.add(layers.Dense(64, activation="relu"))
model.add(layers.Dense(10, activation="softmax"))
 
model.summary()
Model: "sequential_12"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Layer (type)                    ┃ Output Shape           ┃       Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ dense_34 (Dense)                │ (None, 64)             │        50,240 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_35 (Dense)                │ (None, 64)             │         4,160 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_36 (Dense)                │ (None, 10)             │           650 │
└─────────────────────────────────┴────────────────────────┴───────────────┘
 Total params: 55,050 (215.04 KB)
 Trainable params: 55,050 (215.04 KB)
 Non-trainable params: 0 (0.00 B)

إليك ما يبدو عليه مسار العمل النموذجي من البداية إلى النهاية، ويتكوّن من:

  • التدريب
  • التحقق (Validation) على مجموعة محجوزة (Holdout set) مُولَّدة من بيانات التدريب الأصلية
  • التقييم على بيانات الاختبار

سنستخدم بيانات MNIST لهذا المثال.

(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()
 
# Preprocess the data (these are NumPy arrays)
x_train = x_train.reshape(60000, 784).astype("float32") / 255
x_test = x_test.reshape(10000, 784).astype("float32") / 255
 
y_train = y_train.astype("float32")
y_test = y_test.astype("float32")
 
# Reserve 10,000 samples for validation
x_val = x_train[-10000:]
y_val = y_train[-10000:]
x_train = x_train[:-10000]
y_train = y_train[:-10000]
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz
11490434/11490434 ━━━━━━━━━━━━━━━━━━━━ 0s 0us/step

الدالة compile(): تحديد دالة الخسارة والمقاييس والمُحسِّن

لتدريب نموذج باستخدام fit()، تحتاج إلى تحديد دالة خسارة (Loss function)، ومُحسِّن (Optimizer)، و اختياريًا، بعض المقاييس (Metrics) لمراقبتها.

تُمرِّر هذه إلى النموذج كمعاملات لدالة compile():

يتوفّر العديد من المُحسِّنات ودوال الخسارة والمقاييس المدمجة

يجب علينا تحديد إعدادات التدريب (المُحسِّن، دالة الخسارة، المقاييس):

  • استخدم SparseCategoricalCrossentropy وSparseCategoricalAccuracy عندما تكون تصنيفاتك (Labels) مُقدَّمة كأعداد صحيحة (مثل [0, 1, 2, ...]) بدلاً من متجهات ترميز أحادي (One-hot encoded).

  • استخدم categorical_crossentropy وcategorical_accuracy، عند التصنيف متعدد الفئات بتصنيفات ترميز أحادي [0, 0, 1], [1, 0, 0]

  • استخدم binary_crossentropy وBinaryAccuracy، عند التصنيف الثنائي [0, 1, 1, 0]

model.compile(
    optimizer=keras.optimizers.RMSprop(learning_rate=1e-3),  # Optimizer
    # Loss function to minimize
    loss=keras.losses.SparseCategoricalCrossentropy(),
    # List of metrics to monitor
    metrics=[keras.metrics.SparseCategoricalAccuracy()],
)

الآن، لنراجع كل جزء من مسار العمل هذا بالتفصيل.

يجب أن يكون المعامل metrics قائمة -- يمكن أن يمتلك نموذجك أي عدد من المقاييس.

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

لاحظ أنه إذا كنت راضيًا عن الإعدادات الافتراضية، يمكن في كثير من الحالات تحديد المُحسِّن ودالة الخسارة والمقاييس عبر معرِّفات نصية (String identifiers) كاختصار:

model.compile(
    optimizer="rmsprop",
    loss="sparse_categorical_crossentropy",
    metrics=["sparse_categorical_accuracy"],
)

نستدعي fit()، التي ستدرّب النموذج بتقطيع البيانات إلى "دفعات" (Batches) بحجم batch_size، والتكرار بشكل متكرر عبر مجموعة البيانات كاملة لعدد معين من epochs (الدورات التدريبية).

print("Fit model on training data")
history = model.fit(
    x_train,
    y_train,
    batch_size=64,
    epochs=2,
    # We pass some validation for
    # monitoring validation loss and metrics
    # at the end of each epoch
    validation_data=(x_val, y_val),
)
Fit model on training data
Epoch 1/2
782/782 ━━━━━━━━━━━━━━━━━━━━ 6s 5ms/step - loss: 0.5874 - sparse_categorical_accuracy: 0.8398 - val_loss: 0.1857 - val_sparse_categorical_accuracy: 0.9482
Epoch 2/2
782/782 ━━━━━━━━━━━━━━━━━━━━ 7s 3ms/step - loss: 0.1720 - sparse_categorical_accuracy: 0.9499 - val_loss: 0.1634 - val_sparse_categorical_accuracy: 0.9500

المعامل الوصف

x : بيانات المُدخل (مصفوفة NumPy، أو Tensor، أو Dataset)

y : تصنيفات الهدف (بنفس تنسيق مُخرج النموذج)

batch_size : عدد العيّنات لكل تحديث تدرّج (Gradient) (افتراضيًا: 32)

epochs : عدد المرات التي يمر فيها التدريب على كامل مجموعة البيانات

validation_data : زوج (val_x, val_y) لتقييم النموذج في نهاية كل دورة تدريبية (Epoch)

shuffle : هل يتم خلط بيانات التدريب قبل كل دورة تدريبية (افتراضيًا: True)

verbose : 0 = صامت، 1 = شريط تقدّم، 2 = سطر واحد لكل دورة تدريبية

يحتفظ الكائن history المُعاد بسجل لقيم الخسارة وقيم المقاييس أثناء التدريب:

print(history.history)

نقيّم النموذج على بيانات الاختبار عبر evaluate():

# Evaluate the model on the test data using `evaluate`
print("Evaluate on test data")
results = model.evaluate(x_test, y_test, batch_size=128)
print("test loss, test acc:", results)
 
# Generate predictions (probabilities -- the output of the last layer)
# on new data using `predict`
print("Generate predictions for 3 samples")
predictions = model.predict(x_test[:3])
print("predictions shape:", predictions.shape)

تمرين: جرّب مُحسِّنات مختلفة (Optimizers)

  • SGD() (مع أو بدون زخم - Momentum)
  • Adam()
  • إلخ.

زِد عدد batch_size، وepochs، ولاحظ الفرق

دراسة ذاتية: واجهة Keras الوظيفية (Functional API)

المؤلف: fchollet
تاريخ الإنشاء: 2019/03/01
آخر تعديل: 2023/06/25
الوصف: دليل كامل للواجهة الوظيفية (Functional API).

مقدمة

واجهة Keras الوظيفية (Functional API) هي طريقة لإنشاء نماذج أكثر مرونة من واجهة keras.Sequential. يمكن للواجهة الوظيفية التعامل مع نماذج ذات بنية غير خطية (Non-linear topology)، وطبقات مشتركة، وحتى عدة مُدخلات أو مُخرجات.

الفكرة الأساسية هي أن نموذج التعلّم العميق عادةً ما يكون رسمًا بيانيًا موجَّهًا غير دوري (DAG - Directed Acyclic Graph) من الطبقات. لذا فإن الواجهة الوظيفية طريقة لبناء رسوم بيانية من الطبقات.

انظر إلى النموذج التالي:

``` (input: 784-dimensional vectors) ↧ [Dense (64 units, relu activation)] ↧ [Dense (64 units, relu activation)] ↧ [Dense (10 units, softmax activation)] ↧ (output: logits of a probability distribution over 10 classes) ```

هذا رسم بياني أساسي بثلاث طبقات. لبناء هذا النموذج باستخدام الواجهة الوظيفية، ابدأ بإنشاء عقدة مُدخل (Input node):

inputs = keras.Input(shape=(784,))

يُحدَّد شكل البيانات كمتجه ذي 784 بُعدًا. يُحذَف حجم الدُّفعة (Batch size) دائمًا لأنه يُحدَّد فقط شكل كل عيّنة.

فإذا كان لديك، على سبيل المثال، مُدخل صورة بشكل (32, 32, 3)، فستستخدم:

# Just for demonstration purposes.
img_inputs = keras.Input(shape=(32, 32, 3))

يحتوي inputs المُعاد على معلومات حول شكل بيانات المُدخل ونوعها (dtype) التي تُغذّي بها نموذجك. إليك الشكل:

inputs.shape

إليك نوع البيانات (dtype):

inputs.dtype

تُنشئ عقدة جديدة في الرسم البياني للطبقات باستدعاء طبقة على كائن inputs هذا:

dense = layers.Dense(64, activation="relu")
x = dense(inputs)

يشبه إجراء "استدعاء الطبقة" رسم سهم من "المُدخلات" إلى هذه الطبقة التي أنشأتها. أنت "تُمرِّر" المُدخلات إلى الطبقة dense، وتحصل على x كمُخرج.

لنضِف بضع طبقات أخرى إلى الرسم البياني للطبقات:

x = layers.Dense(64, activation="relu")(x)
outputs = layers.Dense(10)(x)

عند هذه النقطة، يمكنك إنشاء Model بتحديد مُدخلاته ومُخرجاته في الرسم البياني للطبقات:

model = keras.Model(inputs=inputs, outputs=outputs, name="mnist_model")

لنطّلع على شكل ملخص النموذج:

model.summary()

يمكنك أيضًا رسم النموذج كرسم بياني:

keras.utils.plot_model(model, "my_first_model.png")

واختياريًا، عرض أشكال المُدخل والمُخرج لكل طبقة في الرسم البياني المرسوم:

keras.utils.plot_model(model, "my_first_model_with_shape_info.png", show_shapes=True)

هذا الشكل والكود متطابقان تقريبًا. في نسخة الكود، تُستبدَل أسهم الاتصال بعملية الاستدعاء.

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