انتقل إلى المحتوى الرئيسي

الوحدة 2 — الواجهة التتابعية: أوّل نموذج في أسطر قليلة

أرَت الوحدة الأولى اللبنات على مستواها المنخفض. وتُغلّف Keras هذه اللبنات في واجهة تسع فيها الشبكة الكاملة عشرة أسطر. والواجهة التتابعية أبسط الواجهات الثلاث، وتُغطّي وحدها نصف الحاجات الفعلية.

رصّ من الطبقات، مدخل واحد ومخرج واحد

يُكدِّس Sequential الطبقات بالترتيب. مخرج كلّ طبقة يصير مدخل التي تليها، دون استثناء.

from tensorflow import keras
from tensorflow.keras import layers

model = keras.Sequential([
keras.Input(shape=(28, 28)),
layers.Flatten(),
layers.Dense(128, activation="relu"),
layers.Dropout(0.2),
layers.Dense(10, activation="softmax"),
])

اتُّخذت في هذا المقطع أربعة قرارات دفعةً واحدة. أُعلن شكل المدخل صراحةً، فيبني Keras الأوزان فورًا بدل انتظار أوّل دفعة بيانات. تُسطِّح Flatten الصورة إلى شعاع من 784 قيمة. الطبقة المخفيّة ذات 128 عصبونًا بتنشيط ReLU تُطبّق درس الوحدة 2 من الدورة 07. ومخرج softmax على عشرة عصبونات يقابل عشرة أصناف متنافية.

أعلن دائمًا شكل المدخل

بدون keras.Input يبقى النموذج غير مبنيّ: أوزانه لم توجد بعد، ويفشل model.summary()، ويظلّ عدد المعاملات مجهولًا. ينتظر Keras أوّل دفعة ليستنبط الأبعاد. هذا يعمل، لكنّنا نخسر الفحص الفوري للأشكال، وهو أفضل لحظة لاكتشاف خطأ معماري.

التصريف اختيار لثلاثة أشياء

النموذج المبنيّ لا يعرف بعدُ كيف يتعلّم. تُلحِق به compile مُحسِّنًا وخسارة ومقاييس.

model.compile(
optimizer=keras.optimizers.Adam(learning_rate=1e-3),
loss="sparse_categorical_crossentropy",
metrics=["accuracy"],
)

اختيار الخسارة ليس حرًّا: بل يتبع صيغة العلامات وتنشيط المخرج.

العلاماتالمخرجالخسارة
أعداد صحيحة، صنف واحد لكلّ مشاهدةsoftmaxsparse_categorical_crossentropy
أشعّة ذات واحد وحيدsoftmaxcategorical_crossentropy
0 أو 1sigmoid بعصبون واحدbinary_crossentropy
عدّة علامات في آنٍsigmoid لكلّ عصبونbinary_crossentropy
قيمة مستمرّةخطّيmse أو huber

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

الخسارة والمقياس لا يؤدّيان الدور نفسه

الخسارة هي ما يُقلّله المُحسِّن؛ ولا بدّ أن تكون قابلة للاشتقاق. أمّا المقياس فوظيفته إخبارك، ويجوز ألّا يكون قابلًا للاشتقاق — كالدقّة مثلًا. مقياس في مكان loss يرفع خطأ؛ وخسارة في مكان metrics تعمل لكنّها لا تُعلِّم شيئًا.

قراءة الملخّص قبل التدريب

يستحقّ model.summary() نظرة متأنّية قبل كلّ تدريب.

Layer (type)          Output Shape       Param #
=====================================================
flatten (Flatten) (None, 784) 0
dense (Dense) (None, 128) 100480
dropout (Dropout) (None, 128) 0
dense_1 (Dense) (None, 10) 1290
=====================================================
Total params: 101,770

يُفرَض عليك فحصان. أوّلًا الأشكال: كلّ Output Shape ينبغي أن يوافق قصدك، ولا بدّ من وجود None في مقدّمة كلّ سطر. ثمّ عدد المعاملات الذي يُعاد حسابه ذهنيًا: طبقة Dense من 784 مدخلًا إلى 128 مخرجًا لها 784×128+128=100480784 \times 128 + 128 = 100\,480 معاملًا، أوزان زائد انحياز. أيّ فارق مع حسابك يشير إلى طبقة سيّئة التحجيم.

لاحظ أنّ Flatten وDropout لا تحملان أيّ معامل: هما تحويلان لا شيء فيهما يُتعلَّم.

التدريب والمتابعة

history = model.fit(
x_train, y_train,
epochs=20,
batch_size=32,
validation_split=0.2,
verbose=2,
)

يحجز validation_split=0.2 آخر 20٪ من البيانات دون خلطها. فإن كان جدولك مرتّبًا حسب الصنف لن تحوي هذه المصادقة إلّا جزءًا من الأصناف، وتصير النتائج غير قابلة للتفسير. اخلط البيانات قبل ذلك، أو قدّم validation_data صراحةً.

يحتفظ كائن history بالقيم لكلّ حقبة، وهو ما يُرسَم لقراءة منحنيات التعلّم التي دُرست في الوحدة 9 من الدورة 07:

import matplotlib.pyplot as plt

plt.plot(history.history["loss"], label="التدريب")
plt.plot(history.history["val_loss"], label="المصادقة")
plt.legend()

التقييم والتوقّع، ولا ينبغي الخلط بينهما

loss, accuracy = model.evaluate(x_test, y_test)
probabilities = model.predict(x_test)
classes = probabilities.argmax(axis=1)

يحتاج evaluate إلى العلامات ويُعيد النقاط. بينما لا يحتاجها predict ويُعيد مخارج الشبكة الخام — وهي هنا احتمالات، لا أصناف. ونسيان argmax مصدر كلاسيكي لنتائج غير مفهومة.

تفصيل خفيّ لكنّه مهمّ: Dropout فعّال أثناء fit ومعطَّل أثناء evaluate وpredict. ويُدير Keras هذا التبديل تلقائيًا. ولهذا قد تبدو خسارة التدريب المعروضة أعلى من خسارة المصادقة في أوّل الحقب، دون أن يكون في ذلك شيء غير طبيعيّ.

متى تعجز الواجهة التتابعية

تفترض الواجهة التتابعية سلسلة خطّية. وتصير عاجزةً حال ابتعاد المعمارية عن هذا المخطّط:

  • مدخلان مختلفان في طبيعتهما، صورة ونصّ مثلًا؛
  • مخرجان، كتصنيف وانحدار في آنٍ واحد؛
  • تفرّع يلتفّ حول طبقات، وهو بعينه تعريف الاتّصال المتبقّي؛
  • طبقة تُطبَّق مرّتين على الأوزان نفسها، كما في المعماريات السياميّة.

هذه الحالات الأربع ليست غريبة: بل تُغطّي معظم المعماريات الحديثة. وهذا ما يُبرّر وجود الوحدة التالية.

في الخلاصة

  • يُكدِّس Sequential الطبقات في سلسلة خطّية؛ وإعلان keras.Input يبني الأوزان فورًا ويجعل أخطاء الأشكال مرئيّة على الفور.
  • يُثبّت compile المُحسِّن والخسارة والمقاييس؛ وتُستنبط الخسارة من صيغة العلامات، ولاحقة sparse_ تعني ببساطة أنّ العلامات أعداد صحيحة.
  • يُعاد قراءة model.summary() قبل كلّ تدريب: الأشكال ينبغي أن توافق القصد، وعدد المعاملات ينبغي أن يُعاد إيجاده يدويًا.
  • يُعيد predict احتمالات لا أصنافًا؛ وDropout فعّال أثناء التدريب ومُعطَّل أثناء التقييم، وهذا ما يُفسّر منحنيات تبدو مقلوبة ظاهريًّا.

الوحدة التالية: الواجهة الوظيفية، التي تُزيل الحدود الأربعة المذكورة أعلاه.