معاينة مختبر آمنة
Conv Nets TF
هذي معاينة منقّحة للقراءة فقط؛ ما فيه أي شيء يشتغل داخل الصفحة.
قراءة فقط
معاينة الدفتر
Conv Nets TF
> **ملاحظة بيئة التشغيل المدمجة:** هالمعاينة تستخدم عيّنة صغيرة وثابتة وآمنة من ناحية الحقوق عشان تكون النتايج قابلة للتكرار. النتايج بالحجم الكامل تحتاج مجموعة البيانات أو النموذج الموثّق بالدرس داخل بيئة خارجية معتمدة.
# الشبكات العصبية الالتفافية
شفنا قبل إن الشبكات العصبية ممتازة في التعامل مع الصور، وحتى البيرسيبترون أحادي الطبقة يقدر يتعرّف على الأرقام المكتوبة باليد من مجموعة البيانات MNIST بدقة معقولة. لكن MNIST حالة خاصة؛ كل الأرقام متمركزة داخل الصورة، وهذا يسهّل المهمة.
في التطبيقات الواقعية، نبي نتعرّف على الجسم مهما كان موقعه داخل الصورة. وتختلف الرؤية الحاسوبية عن **التصنيف** العام؛ لأننا إذا بغينا نلقى جسمًا معيّنًا، نمسح الصورة بحثًا عن **أنماط** محددة وتركيبات منها. مثلًا، إذا كنا نبحث عن قطة، قد نبدأ بخطوط أفقية تمثّل الشوارب، وبعدها يدلنا تركيب معيّن من الشوارب إن الصورة فعلًا لقطة. المهم وجود الأنماط ومواقعها النسبية، مب موقعها الدقيق داخل الصورة.
لاستخراج الأنماط، بنستخدم **مرشحات الالتفاف (Convolutional filters)**. لكن أولًا، خلونا نحمّل كل التبعيات والدوال اللي عرّفناها في الوحدات السابقة. وبنستورد بعد مكتبة المساعدة `tfcv`، اللي فيها دوال مفيدة ما نبي نكرر تعريفها داخل الدفتر، عشان يبقى الكود قصيرًا وواضحًا.
import tensorflow as tf
from tensorflow import keras
import matplotlib.pyplot as plt
import numpy as np
# Self-contained course computer-vision helpers.
from pathlib import Path
import glob
from PIL import Image
def plot_convolution(data, kernel, title=""):
fig, axes = plt.subplots(2, len(data) + 1, figsize=(10, 4))
fig.suptitle(title, fontsize=16)
tensor_kernel = np.asarray(kernel, dtype=np.float32)[:, :, None, None]
for index, image in enumerate(data):
axes[0, index].imshow(image, cmap="gray")
tensor_image = np.asarray(image, dtype=np.float32)[None, :, :, None]
convolved = tf.nn.conv2d(tensor_image, tensor_kernel, strides=1, padding="SAME")
axes[1, index].imshow(convolved[0, :, :, 0], cmap="gray")
axes[0, index].axis("off")
axes[1, index].axis("off")
axes[0, -1].imshow(kernel, cmap="gray")
axes[0, -1].axis("off")
axes[1, -1].axis("off")
plt.tight_layout()
plt.show()
def plot_results(history):
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
axes[0].set_title("Accuracy")
axes[1].set_title("Loss")
for key in ("accuracy", "val_accuracy"):
if key in history.history:
axes[0].plot(history.history[key], label=key)
for key in ("loss", "val_loss"):
if key in history.history:
axes[1].plot(history.history[key], label=key)
axes[0].legend()
axes[1].legend()
plt.show()
def display_dataset(dataset, labels=None, n=10, classes=None):
count = min(n, len(dataset))
fig, axes = plt.subplots(1, count, figsize=(1.8 * count, 3))
axes = np.atleast_1d(axes)
for index in range(count):
axes[index].imshow(dataset[index])
axes[index].axis("off")
if classes is not None and labels is not None:
label = int(np.asarray(labels[index]).reshape(-1)[0])
axes[index].set_title(classes[label])
plt.show()
def check_image(filename):
try:
with Image.open(filename) as image:
image.verify()
return True
except (OSError, SyntaxError):
return False
def check_image_dir(pattern):
invalid = [filename for filename in glob.glob(pattern) if not check_image(filename)]
if invalid:
names = ", ".join(Path(filename).name for filename in invalid[:5])
raise ValueError(f"Invalid images detected ({len(invalid)}): {names}")
return 0في هالمثال بنركّز على مجموعة بيانات MNIST اللي شفناها قبل وعلى **تصنيف الصور**، وهو نوع من **التصنيف**. بنبدأ بتحميل مجموعة البيانات باستخدام الدوال المضمنة في Keras.
# Bundled, deterministic handwritten-digits fixture.
from sklearn.datasets import load_digits as _course_load_digits
def _course_digits_load_data():
course_digits = _course_load_digits()
images = np.kron(
course_digits.images.astype(np.float32),
np.ones((3, 3), dtype=np.float32),
)
images = np.pad(images, ((0, 0), (2, 2), (2, 2))) * (255.0 / 16.0)
labels = np.asarray(course_digits.target, dtype=np.int64)
split = int(0.8 * len(images))
return (images[:split], labels[:split]), (images[split:], labels[split:])
(x_train,y_train),(x_test,y_test) = _course_digits_load_data()
x_train = x_train.astype(np.float32) / 255.0
x_test = x_test.astype(np.float32) / 255.0## مرشحات الالتفاف
مرشحات الالتفاف نوافذ صغيرة تمر على كل بكسل في الصورة وتحسب متوسطًا موزونًا للبكسلات المجاورة.
نعرّف هالمرشحات بمصفوفات من معاملات الأوزان. خلونا نشوف نتيجة تطبيق مرشحين مختلفين على أرقام MNIST المكتوبة باليد:
plot_convolution(x_train[:5],[[-1.,0.,1.],[-1.,0.,1.],[-1.,0.,1.]],'Vertical edge filter')
plot_convolution(x_train[:5],[[-1.,-1.,-1.],[0.,0.,0.],[1.,1.,1.]],'Horizontal edge filter')أول مرشح اسمه **مرشح الحافة العمودية**، ونعرّفه بالمصفوفة التالية:
$$
\left(
\begin{matrix}
-1 & 0 & 1 \cr
-1 & 0 & 1 \cr
-1 & 0 & 1 \cr
\end{matrix}
\right)
$$
إذا مر هالمرشح على منطقة بكسلات متقاربة القيم، يكون مجموع القيم 0. لكن إذا قابل حافة عمودية في الصورة، ينتج قيمة مرتفعة. عشان كذا تشوفون الحواف العمودية في الصور اللي فوق ممثّلة بقيم مرتفعة ومنخفضة، بينما تتلاشى الحواف الأفقية بسبب حساب المتوسط.
ويصير العكس مع مرشح الحافة الأفقية: تتضخم الخطوط الأفقية وتتلاشى العمودية.
في الرؤية الحاسوبية التقليدية، كنا نطبّق عدة مرشحات على الصورة لتوليد سمات، ثم تستخدم خوارزمية من تعلم الآلة هالسمات لبناء مصنّف. وهالمرشحات تشبه فعلًا تراكيب عصبية موجودة في جهاز الإبصار عند بعض الحيوانات.
> **وصف الشكل:** حُذف الأصل لأن حقوق إعادة استخدامه غير موثّقة.
أما في التعلم العميق، فنبني شبكات **تتعلّم** أفضل مرشحات الالتفاف لحل مسألة التصنيف. وعشان نسوي كذا، نضيف **طبقات الالتفاف**.
## طبقات الالتفاف
عشان نخلي أوزان طبقة الالتفاف قابلة للتدريب، نحتاج نحوّل تطبيق نافذة مرشح الالتفاف على الصورة إلى عمليات مصفوفية نقدر ندرّبها بالانتشار العكسي. نسوي هالشي بتحويل مصفوفي ذكي اسمه **im2col**.
افترضوا إن عندنا صورة صغيرة $\mathbf{x}$ ببكسلاتها التالية:
$$
\mathbf{x} = \left(
\begin{array}{ccccc}
a & b & c & d & e \\
f & g & h & i & j \\
k & l & m & n & o \\
p & q & r & s & t \\
u & v & w & x & y \\
\end{array}
\right)
$$
ونبي نطبّق مرشحَي التفاف بالأوزان التالية:
$$
W^{(i)} = \left(\begin{array}{ccc}
w^{(i)}_{00} & w^{(i)}_{01} & w^{(i)}_{02} \\
w^{(i)}_{10} & w^{(i)}_{11} & w^{(i)}_{12} \\
w^{(i)}_{20} & w^{(i)}_{21} & w^{(i)}_{22} \\
\end{array}\right)
$$
عند تطبيق الالتفاف، نحصل على أول بكسل في الناتج بضرب عناصر المصفوفة
$\left(\begin{array}{ccc}
a & b & c \\
f & g & h \\
k & l & m \\
\end{array}\right)$ في $W^{(i)}$ عنصرًا بعنصر. ونحصل على العنصر الثاني بضرب $\left(\begin{array}{ccc}
b & c & d \\
g & h & i \\
l & m & n \\
\end{array}\right)$ في $W^{(i)}$، ونكمل بالطريقة نفسها.
عشان نصيغ العملية رسميًا، نستخرج كل أجزاء الصورة الأصلية $x$ اللي مقاسها $3\times3$ إلى المصفوفة التالية:
$$
\mathrm{im2col}(x) = \left[
\begin{array}{cccccc}
a & b & \ldots & g & \ldots & m \\
b & c & \ldots & h & \ldots & n \\
c & d & \ldots & i & \ldots & o \\
f & g & \ldots & l & \ldots & r \\
g & h & \ldots & m & \ldots & s \\
h & i & \ldots & n & \ldots & t \\
k & l & \ldots & q & \ldots & w \\
l & m & \ldots & r & \ldots & x \\
m & n & \ldots & s & \ldots & y \\
\end{array}
\right]
$$
يمثّل كل عمود في هالمصفوفة منطقة فرعية مقاسها $3\times3$ من الصورة الأصلية. وللحصول على ناتج الالتفاف، نضرب هالمصفوفة في مصفوفة الأوزان:
$$
\mathbf{W} = \left[
\begin{array}{cccccccc}
w^{(0)}_{00} & w^{(0)}_{01} & w^{(0)}_{02} & w^{(0)}_{10} & w^{(0)}_{11} & \ldots & w^{(0)}_{21} & w^{(0)}_{22} \\
w^{(1)}_{00} & w^{(1)}_{01} & w^{(1)}_{02} & w^{(1)}_{10} & w^{(1)}_{11} & \ldots & w^{(1)}_{21} & w^{(1)}_{22} \\
\end{array}
\right]
$$
(كل صف في هالمصفوفة يحتوي أوزان المرشح رقم $i$ بعد فردها في صف واحد).
وبكذا نقدر نستبدل تطبيق مرشح الالتفاف على الصورة الأصلية بضرب مصفوفات، وهي عملية نعرف نتعامل معها بالانتشار العكسي:
$$
C(x) = W\times\mathbf{im2col}(x)
$$
نعرّف طبقات الالتفاف باستخدام الصنف `Conv2d`. ولازم نحدد التالي:
* `filters` — عدد المرشحات المستخدمة. بنستخدم 9 مرشحات مختلفة، وهذا يعطي الشبكة فرصًا كثيرة تكتشف أي المرشحات أنسب لهالسيناريو.
* `kernel_size` — مقاس النافذة المنزلقة. غالبًا نستخدم مرشحات 3x3 أو 5x5.
أبسط CNN تحتوي على طبقة التفاف واحدة. إذا كان مقاس الإدخال 28x28 وطبّقنا تسعة مرشحات مقاسها 5x5، نحصل على موتر مقاسه 24x24x9. البعد المكاني أصغر لأن فيه 24 موضعًا بس لنافذة طولها 5 داخل 28 بكسلًا.
بعد الالتفاف، نفرد الموتر 24x24x9 إلى متجه واحد حجمه 5184، ثم نضيف طبقة خطية تنتج 10 فئات. ونستخدم بعد دالة التنشيط `relu` بين الطبقات.
model = keras.models.Sequential([
keras.layers.Conv2D(filters=9, kernel_size=(5,5), input_shape=(28,28,1),activation='relu'),
keras.layers.Flatten(),
keras.layers.Dense(10)
])
model.compile(loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),metrics=['acc'])
model.summary()مثل ما تشوفون، فيها هالشبكة قرابة 50k من المعاملات القابلة للتدريب، مقابل قرابة 80k في الشبكات متعددة الطبقات كاملة الاتصال. وهالشي يساعدنا نحقق نتائج زينة حتى مع مجموعات بيانات أصغر؛ لأن الشبكات الالتفافية تعمّم بشكل أفضل.
> **ملاحظة:** في أغلب التطبيقات العملية نطبّق طبقات الالتفاف على صور ملونة. لذلك تتوقع طبقة `Conv2D` إدخالًا بالشكل $W\times H\times C$، بحيث يمثّل $W$ عرض الصورة، و$H$ ارتفاعها، و$C$ عدد قنوات الألوان. أما الصور الرمادية فتحتاج الشكل نفسه مع $C=1$.
لازم نعيد تشكيل البيانات قبل ما نبدأ التدريب:
x_train_c = np.expand_dims(x_train,3)
x_test_c = np.expand_dims(x_test,3)
hist = model.fit(x_train_c,y_train,validation_data=(x_test_c,y_test),epochs=2)plot_results(hist)واضح إننا قدرنا نوصل إلى دقة أعلى، وبعدد أقل من حقب التدريب، مقارنةً بالشبكات كاملة الاتصال في الوحدة السابقة. لكن التدريب نفسه يحتاج موارد أكثر، وقد يكون أبطأ على الأجهزة اللي ما فيها GPU.
## عرض طبقات الالتفاف
نقدر نعرض أوزان طبقات الالتفاف بعد تدريبها، عشان نفهم أكثر وش قاعد يصير:
fig,ax = plt.subplots(1,9)
l = model.layers[0].weights[0]
for i in range(9):
ax[i].imshow(l[...,0,i])
ax[i].axis('off')تلاحظون إن بعض المرشحات كأنها تتعرّف على ضربات مائلة، بينما يبدو بعضها عشوائيًا.
> **المهمة:** درّبوا الشبكة نفسها بمرشحات مقاسها 3x3 واعرضوها. هل تشوفون أنماطًا أوضح؟
## نماذج CNN متعددة الطبقات وطبقات التجميع
تبحث طبقة الالتفاف الأولى عن أنماط أولية، مثل الخطوط الأفقية أو العمودية. ونقدر نضيف فوقها طبقات التفاف ثانية تبحث عن أنماط أعلى مستوى، مثل الأشكال البسيطة. وبعدها تدمج طبقات إضافية هالأشكال لتكوّن أجزاء من الصورة، إلى أن نوصل للجسم النهائي اللي نبي نصنّفه.
ومع هالخطوات نقدر نستخدم حيلة ثانية: نقلّل المقاس المكاني للصورة. إذا اكتشفنا ضربة أفقية داخل نافذة منزلقة مقاسها 3x3، ما عاد يهمنا كثير في أي بكسل بالضبط ظهرت. لذلك نقدر «نصغّر» الصورة باستخدام إحدى **طبقات التجميع (Pooling layers)**:
* **التجميع بالمتوسط (Average Pooling)** يأخذ نافذة منزلقة، مثل 2x2 بكسل، ويحسب متوسط القيم داخلها.
* **التجميع بالأقصى (Max Pooling)** يستبدل النافذة بأكبر قيمة فيها. فكرته اكتشاف وجود نمط معيّن داخل النافذة المنزلقة.
عادةً تحتوي CNN على عدة طبقات التفاف، وبينها طبقات تجميع تقلّل أبعاد الصورة. ونزيد عدد المرشحات بعد؛ لأن الأنماط كلما صارت أعمق، زاد عدد التركيبات المهمة اللي نحتاج نبحث عنها.
> **وصف الشكل:** صورة توضح عدة طبقات التفاف وبينها طبقات تجميع.
وبسبب تناقص الأبعاد المكانية وزيادة أبعاد السمات والمرشحات، نسمّي هالبنية **البنية الهرمية**.
model = keras.models.Sequential([
keras.layers.Conv2D(filters=10, kernel_size=(5,5), input_shape=(28,28,1),activation='relu'),
keras.layers.MaxPooling2D(),
keras.layers.Conv2D(filters=20, kernel_size=(5,5), activation='relu'),
keras.layers.MaxPooling2D(),
keras.layers.Flatten(),
keras.layers.Dense(10)
])
model.compile(loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),metrics=['acc'])
model.summary()لاحظوا إن عدد المعاملات القابلة للتدريب، قرابة 8.5K، أقل بكثير من الحالات السابقة. السبب إن طبقات الالتفاف فيها معاملات قليلة عمومًا، وإن أبعاد الصورة تنخفض بشكل واضح قبل الطبقة الكثيفة الأخيرة. قلة المعاملات تفيد النموذج لأنها تقلّل فرط التكيّف حتى مع مجموعات البيانات الصغيرة.
hist = model.fit(x_train_c,y_train,validation_data=(x_test_c,y_test),epochs=2)plot_results(hist)المفروض تلاحظون إننا وصلنا لدقة أعلى من شبكة الطبقة الواحدة، وبعدد أقل من حقب التدريب؛ احتجنا 1 أو 2 فقط. هذا يعني إن البنية المتقدمة للشبكة تحتاج بيانات أقل بكثير عشان تفهم وش قاعد يصير وتستخرج أنماطًا عامة من الصور. لكن التدريب نفسه ياخذ وقتًا أطول ويحتاج GPU.
## تجربة صور حقيقية من مجموعة بيانات CIFAR-10
قد تبدو مسألة التعرّف على الأرقام المكتوبة باليد بسيطة، لكن الحين صرنا جاهزين لمهمة أجدّ. خلونا نستكشف مجموعة البيانات الأصعب لصور أجسام مختلفة اسمها [CIFAR-10](https://www.cs.toronto.edu/~kriz/cifar.html). تحتوي على 60k صورة بمقاس 32x32، مقسّمة إلى 10 فئات.
# Generated RGB patterns for deterministic, rights-safe execution.
def _course_rgb_load_data():
yy, xx = np.mgrid[:32, :32]
images, labels = [], []
for label in range(10):
base = np.zeros((32, 32, 3), dtype=np.float32)
base[..., label % 3] = ((xx * (label + 2) + yy * ((label % 4) + 1)) % 32) / 31.0
base[..., (label + 1) % 3] = ((yy + label * 3) % 32) / 31.0
for sample in range(24):
images.append(np.roll(base, shift=(sample % 5) - 2, axis=sample % 2))
labels.append(label)
images = (255.0 * np.asarray(images)).astype(np.float32)
labels = np.asarray(labels, dtype=np.int64).reshape(-1, 1)
split = 200
return (images[:split], labels[:split]), (images[split:], labels[split:])
(x_train,y_train),(x_test,y_test) = _course_rgb_load_data()
x_train = x_train.astype(np.float32) / 255.0
x_test = x_test.astype(np.float32) / 255.0
classes = ('plane', 'car', 'bird', 'cat',
'deer', 'dog', 'frog', 'horse', 'ship', 'truck')display_dataset(x_train,y_train,classes=classes)من البُنى المعروفة لمجموعة CIFAR-10 شبكة [LeNet](https://en.wikipedia.org/wiki/LeNet)، اللي اقترحها *Yann LeCun*. تتبع المبادئ نفسها اللي شرحناها فوق، والفرق الأساسي إن لها 3 قنوات لونية في الإدخال بدل 1.
model = keras.models.Sequential([
keras.layers.Conv2D(filters = 6, kernel_size = 5, strides = 1, activation = 'relu', input_shape = (32,32,3)),
keras.layers.MaxPooling2D(pool_size = 2, strides = 2),
keras.layers.Conv2D(filters = 16, kernel_size = 5, strides = 1, activation = 'relu'),
keras.layers.MaxPooling2D(pool_size = 2, strides = 2),
keras.layers.Flatten(),
keras.layers.Dense(120, activation = 'relu'),
keras.layers.Dense(84, activation = 'relu'),
keras.layers.Dense(10, activation = 'softmax')])
model.summary()تدريب هالشبكة بشكل صحيح بياخذ وقتًا ملحوظًا، والأفضل نشغّله على جهاز فيه GPU.
model.compile(optimizer = 'adam', loss = 'sparse_categorical_crossentropy', metrics = ['acc'])
hist = model.fit(x_train,y_train,validation_data=(x_test,y_test),epochs=2)plot_results(hist)قد تبدو الدقة اللي وصلنا لها بعد بضع حقب تدريب منخفضة. لكن تذكّروا إن التخمين العشوائي بيعطينا دقة 10% فقط، وإن المسألة أصعب بكثير من تصنيف أرقام MNIST. الوصول إلى دقة أعلى من 50% في وقت تدريب قصير كذا يُعد نتيجة زينة.
## أهم الخلاصات
تعلّمنا في هالوحدة الفكرة الأساسية وراء الشبكات العصبية المستخدمة في **الرؤية الحاسوبية**: الشبكات الالتفافية. البُنى المستخدمة فعليًا في مهام **التصنيف**، ومنها **تصنيف الصور** و**اكتشاف الأجسام** وحتى شبكات توليد الصور كلها مبنية على CNN، لكن بطبقات أكثر وبعض أساليب التدريب الإضافية.
حذفنا المخرجات وعدّادات التشغيل والودجات والمحتوى النشط وقت الاستيراد. شغّل الدفاتر بس في بيئة خارجية تثق فيها.
سجّل تطبيقك
التسجيل اختياري، يفيدك تتذكر وش طبّقت، ولا يمنع إكمال الدورة.