نماذج مسبقة التدريب ونقل التعلم
- 10 دقائق
تدريب شبكات CNN قد يستغرق وقتا كبيرا ويتطلب ذلك كمية كبيرة من البيانات. يقضى معظم الوقت في التجربة لإيجاد أفضل الفلاتر منخفضة المستوى التي تحتاجها الشبكة لاستخراج الأنماط من الصور. يطرح سؤال طبيعي - هل يمكننا استخدام شبكة عصبية مدربة على مجموعة بيانات واحدة وتكييفها لتصنيف صور مختلفة دون الحاجة إلى عملية تدريب كاملة؟
يسمى هذا النهج التعلم الانتقالي، لأننا ننقل بعض المعرفة من نموذج شبكة عصبية إلى آخر. في التعلم التحويلي، نبدأ عادة بنموذج مدرب مسبقا، تم تدريبه على مجموعة بيانات صور كبيرة مثل ImageNet. تقوم هذه النماذج بالفعل بعمل جيد في استخراج ميزات مختلفة من الصور العامة، وفي كثير من الحالات بناء مصنف فوق تلك الميزات المستخرجة يمكن أن يعطي نتيجة جيدة.
import tensorflow as tf
import keras
import matplotlib.pyplot as plt
import numpy as np
import os
import glob
from PIL import Image
مجموعة بيانات القطط مقابل الكلاب
في هذه الوحدة، سنحل مشكلة واقعية تتعلق بتصنيف صور القطط والكلاب. لهذا السبب، سنستخدم مجموعة بيانات Kaggle Cats vs. Dogs، والتي يمكن تحميلها أيضا من مايكروسوفت.
دعونا نحمل هذه المجموعة ونستخرجها إلى data الدليل:
import urllib.request
import zipfile
dataset_url = 'https://download.microsoft.com/download/3/E/1/3E1C3F21-ECDB-4869-8368-6DEBA77B919F/kagglecatsanddogs_5340.zip'
data_dir = 'data'
os.makedirs(data_dir, exist_ok=True)
zip_path = os.path.join(data_dir, 'kagglecatsanddogs_5340.zip')
if not os.path.exists(zip_path):
urllib.request.urlretrieve(dataset_url, zip_path)
if not os.path.exists(os.path.join(data_dir, 'PetImages')):
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
zip_ref.extractall(data_dir)
قد تحتوي مجموعة البيانات على بعض ملفات الصور التالفة. دعونا نحدد دالة مساعدة للتحقق منها وإزالتها قبل التحميل:
def check_image(fn):
try:
im = Image.open(fn)
im.verify()
return True
except (IOError, SyntaxError):
return False
def check_image_dir(dir_path):
for fn in glob.glob(dir_path):
if not check_image(fn):
print(f"Corrupt image: {fn}")
os.remove(fn)
# Remove any corrupt images from the dataset
check_image_dir('data/PetImages/Cat/*.jpg')
check_image_dir('data/PetImages/Dog/*.jpg')
تحميل مجموعة البيانات
في الأمثلة السابقة، كنا نحمل مجموعات بيانات مدمجة في Keras. الآن سنستخدم مجموعة البيانات الخاصة بنا، والتي نحتاج إلى تحميلها من دليل صور. يتضمن كيراس دالة image_dataset_from_directory مساعدة يمكنها إنشاء من tf.data.Dataset دليل صور منظمة في مجلدات فرعية حسب الفئة.
data_dir = 'data/PetImages'
batch_size = 32
ds_train = keras.utils.image_dataset_from_directory(
data_dir,
validation_split=0.2,
subset='training',
seed=13,
image_size=(224, 224),
batch_size=batch_size
)
ds_test = keras.utils.image_dataset_from_directory(
data_dir,
validation_split=0.2,
subset='validation',
seed=13,
image_size=(224, 224),
batch_size=batch_size
)
ملحوظة
نستخدم نفس seed القيمة عند إنشاء تقسيم التدريب والتحقق لضمان عدم التداخل بين المجموعتين الفرعيتين.
يمكننا التحقق من أسماء الفئات التي تم استنتاجها تلقائيا من هيكل الدليل:
# Expected output: ['Cat', 'Dog']
ds_train.class_names
دعونا نحدد مساعدا لعرض العينات من مجموعة بياناتنا (هذه نسخة جديدة من display_dataset المخصصة للبيانات المجمعة):
def display_dataset(images, labels, classes=None, cols=8):
n = len(images)
rows = (n + cols - 1) // cols
fig, axes = plt.subplots(rows, cols, figsize=(cols * 1.5, rows * 1.5))
axes = axes.flatten() if n > 1 else [axes]
for i, ax in enumerate(axes):
if i < n:
ax.imshow(images[i])
label = int(labels[i][0]) if labels[i].ndim > 0 else int(labels[i])
title = classes[label] if classes else str(label)
ax.set_title(title, fontsize=8)
ax.axis('off')
plt.tight_layout()
plt.show()
توفر مجموعة البيانات دفعات من الصور والتسميات. تحتوي كل دفعة على 32 صورة بحجم 224×224 مع 3 قنوات لونية، وملصقات مقابلة لها:
for x, y in ds_train:
print(f"Training batch shape: features={x.shape}, labels={y.shape}")
x_sample, y_sample = x, y
break
# Expected output: Training batch shape: features=(32, 224, 224, 3), labels=(32,)
display_dataset(x_sample.numpy().astype(np.uint8), np.expand_dims(y_sample, 1), classes=ds_train.class_names)
ملحوظة
قيم بكسل الصورة تتراوح بين 0-255. تتطلب بعض النماذج أن يتم تكبير المدخلات إلى 0-1 أو معالجتها مسبقا باستخدام دالة خاصة بالنموذج. لدى VGG-16 وظيفته الخاصة preprocess_input التي نستخدمها لاحقا.
النماذج المدربة مسبقا
هناك العديد من الشبكات العصبية المدربة مسبقا لتصنيف الصور تم تدريبها على مجموعة بيانات ImageNet، التي تحتوي على أكثر من 14 مليون صورة عبر 1000 فئة. واحدة من أشهر البنى هي VGG-16، التي تحقق دقة جيدة مع سهولة الفهم. لنحمل نموذج VGG-16 بأوزان مدربة مسبقا:
vgg = keras.applications.VGG16()
دعونا نجرب استخدام هذه الشبكة المدربة مسبقا لتصنيف إحدى صورنا. تم تدريب شبكة VGG-16 على ImageNet، الذي يتضمن فئات لسلالات مختلفة من الكلاب والقطط:
inp = keras.applications.vgg16.preprocess_input(x_sample[:1])
res = vgg(inp)
# tf.argmax returns the index of the highest-probability class
print(f"Most probable class = {tf.argmax(res, 1)}")
# decode_predictions maps class indices to human-readable labels
keras.applications.vgg16.decode_predictions(res.numpy())
تقوم الدالة preprocess_input بمقياس قيم البكسلات بشكل مناسب لنموذج VGG-16. تعيد الدالة decode_predictions أفضل 5 فئات محتملة في ImageNet مع درجات الثقة الخاصة بها.
لنرى بنية VGG-16:
# Shows all layers including convolutional blocks and final Dense classifier
vgg.summary()
حسابات وحدة معالجة الرسومات
تتطلب الشبكات العصبية العميقة قوة حسابية كبيرة للتدريب. استخدام وحدة معالجة الرسوميات يمكن أن يسرع عملية التدريب بشكل كبير. دعونا نتحقق مما إذا كان هناك بطاقة رسومات متوفرة:
# Lists available GPU devices; an empty list means CPU-only
tf.config.list_physical_devices('GPU')
استخراج ميزات VGG
إذا أردنا استخدام VGG-16 لاستخراج الميزات من صورنا، نحتاج إلى النموذج بدون طبقات التصنيف النهائية. يمكننا القيام بذلك بتحديد include_top=False:
vgg = keras.applications.VGG16(include_top=False)
inp = keras.applications.vgg16.preprocess_input(x_sample[:1])
res = vgg(inp)
# The output is a 7x7 grid of 512 feature maps
print(f"Shape after applying VGG-16: {res[0].shape}")
plt.figure(figsize=(15, 3))
plt.imshow(res[0].numpy().reshape(-1, 512))
متجه الميزة الناتج له شكل 7×7×512 = 25088 قيمة. يمثل هذا الميزات عالية المستوى التي تعلم VGG-16 استخراجها من الصورة. يمكننا حساب هذه الميزات يدويا لكامل مجموعة البيانات لدينا ثم تدريب مصنف فوقه:
التحذير
نستخدم .take(25) ما يلي .take(10) لتحديد حجم مجموعة البيانات من أجل تدريب أسرع في هذا المثال. كل دفعة تحتوي على 32 صورة، لذا نحن نستخدم فقط 800 صورة تدريبية و320 صورة اختبار. تعكس دقة ~90% المبلغ عنها هنا هذه المجموعة الصغيرة وقد لا تعمم على مجموعة البيانات الكاملة. للاستخدام الإنتاجي، تدرب على مجموعة البيانات الكاملة.
def preprocess(x, y):
return keras.applications.vgg16.preprocess_input(x), y
ds_features_train = ds_train.take(25).map(preprocess).map(lambda x, y: (vgg(x), y)).cache()
ds_features_test = ds_test.take(10).map(preprocess).map(lambda x, y: (vgg(x), y)).cache()
for x, y in ds_features_train:
# Expected output: (32, 7, 7, 512) (32,)
print(x.shape, y.shape)
break
ملحوظة
نستدعي .cache() بعد استخراج الميزات بحيث يعمل نموذج VGG-16 مرة واحدة فقط في كل دفعة بدلا من كل عصر.
الآن يمكننا بناء مصنف بسيط على الميزات المستخرجة. نظرا لأن ميزات VGG غنية بالمعلومات، حتى طبقة كثيفة واحدة يمكن أن تحقق نتائج جيدة:
model = keras.Sequential([
keras.layers.Input(shape=(7, 7, 512)),
keras.layers.Flatten(),
keras.layers.Dense(1, activation='sigmoid')
])
model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
hist = model.fit(ds_features_train, validation_data=ds_features_test)
# Expected: validation accuracy around 90%
بدقة تقارب 90%، هذا يبرهن على قوة الميزات المدربة مسبقا! ومع ذلك، فإن الحوسبة اليدوية للميزات مسبقة أمر مرهق.
التعلم التحويلي باستخدام شبكة VGG واحدة
يمكننا تجنب الحوسبة اليدوية للميزات مسبقا من خلال دمج مستخرج ميزات VGG-16 والمصنف في شبكة واحدة. المفتاح هو تجميد الطبقات المدربة مسبقا حتى لا يتم تحديث أوزانها أثناء التدريب.
ننقل الخطوة preprocess_input إلى خط أنابيب البيانات بدلا من تضمينها في النموذج كطبقة Lambda . هذا يحافظ على إمكانية التسلسل للنموذج حتى نتمكن من حفظه وتحميله لاحقا:
def preprocess(x, y):
return keras.applications.vgg16.preprocess_input(x), y
ds_train_preprocessed = ds_train.map(preprocess)
ds_test_preprocessed = ds_test.map(preprocess)
ملحوظة
نظرا لأن المعالجة المسبقة أصبحت الآن جزءا من خط أنابيب البيانات وليس النموذج، يجب عليك أيضا التطبيق preprocess_input على بيانات الإدخال في وقت الاستدلال.
الآن نبني النموذج باستخدام قاعدة VGG-16 المجمدة:
vgg_base = keras.applications.VGG16(include_top=False, input_shape=(224, 224, 3))
vgg_base.trainable = False
model = keras.Sequential([
keras.layers.Input(shape=(224, 224, 3)),
vgg_base,
keras.layers.Flatten(),
keras.layers.Dense(1, activation='sigmoid')
])
# Notice: ~15 million params are non-trainable (VGG-16), only ~25k are trainable
model.summary()
من خلال تجميد طبقات VGG-16، نحتاج فقط لتدريب الطبقة الكثيفة النهائية، والتي تحتوي على حوالي 25,000 معلمة بدلا من 15 مليون كاملة. وهذا يجعل التدريب أسرع:
التحذير
كما في القسم السابق، نستخدم .take(50) و .take(10) لتقييد مجموعة البيانات من أجل تدريب أسرع. هذا يعني أننا نتدرب على حوالي 1600 صورة ونتحقق من صحة 320. قد تختلف نتائج الدقة عند التدريب على مجموعة البيانات الكاملة.
model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
hist = model.fit(ds_train_preprocessed.take(50), validation_data=ds_test_preprocessed.take(10))
# Expected: validation accuracy around 90% or higher
حفظ وتحميل النموذج
بمجرد أن يكون لدينا نموذج مدرب، يمكننا حفظه على القرص وإعادة تحميله لاحقا دون إعادة تدريب:
model.save('data/cats_dogs.keras')
ملحوظة
يستخدم الامتداد .keras صيغة Keras 3 الأصلية. إذا كنت تستخدم نسخة أقدم من TensorFlow/Keras، استخدم .h5 (صيغة HDF5) أو تنسيق مجلد SavedModel بدلا من ذلك.
لتحميل النموذج المحفوظ:
model = keras.models.load_model('data/cats_dogs.keras')
نماذج رؤية حاسوبية أخرى
تعد VGG-16 واحدة من أبسط بنى CNN العميقة للفهم، بسبب هيكلها الموحد من الالتفافات المكدسة 3×3. يوفر كيراس العديد من الشبكات المدربة مسبقا. الأكثر استخداما من بينها هي معماريات ResNet ، التي طورتها مايكروسوفت، وInception من قبل جوجل.
تحسين النتائج من خلال تعزيز البيانات
عند العمل مع بيانات تدريب محدودة، يمكن أن تحسن تعزيز البيانات بشكل كبير التعميم. من خلال تطبيق تحويلات عشوائية (مثل القلبات الأفقية، والدورانات، والتكبير) على صور التدريب، نزيد من تنوع مجموعة البيانات بشكل مصطنع. Keras يوفر طبقات تعزيز مثل keras.layers.RandomFlip، keras.layers.RandomRotation، keras.layers.RandomZoom ويمكن إضافتها مباشرة إلى نموذجك أو خط أنابيب البيانات.
استنتاج
مع التعلم التحويلي تمكنا بسرعة من إعداد مصنف لمهمة تصنيف الكائنات المخصصة وتحقيق دقة عالية. لم يكن هذا المثال عادلا تماما لأن شبكة VGG-16 الأصلية كانت مدربة مسبقا على ImageNet، والتي تتضمن بالفعل فئات لسلالات القطط والكلاب المختلفة، وبالتالي كنا نعيد استخدام معظم الأنماط الموجودة بالفعل في الشبكة. يمكنك توقع دقة أقل لكائنات خاصة بمجالات أخرى، مثل تفاصيل خط الإنتاج في نبات أو أوراق أشجار مختلفة. يمكنك أن ترى أن المهام الأكثر تعقيدا تتطلب قوة حسابية أعلى وغالبا ما تستفيد من تسريع وحدة معالجة الرسومات للتدريب.
اختبر معلوماتك
الملاحظات
هل كانت هذه الصفحة مفيدة؟
لا
هل تحتاج إلى مساعدة مع هذا الموضوع؟
هل تريد محاولة استخدام Ask Learn لتوضيح هذا الموضوع أو إرشادك خلاله؟