• 0

زيادة حجم البيانات Data Augmentation  في كيراس Keras وتنسرفلو Tensorflow باستخدام ImageDataGenerator

لدي مجموعة بيانات صغيرة لصور (حوالي 2000 عينة فقط)، وأعرف أن هناك تكنيك يسمى Data Augmentation يسمح لنا بزيادة حجم البيانات عن طريق التلاعب بها، لذا هل هناك طريقة للقيام بذلك في تنسرفلو Tensorflow؟

1 شخص أعجب بهذا

انشر على الشّبكات الاجتماعية


رابط هذه المساهمة
شارك على الشبكات الإجتماعية
  • 1

نعم يمكنك القيام بذلك من خلال الكلاس الرائع ImageDataGenerator في تنسرفلو وكيراس. حيث يقوم هذا التابع بتوليد صور جديدة من البيانات الأصلية (يقوم بأخذ صورة ثم يطبق عليها مجموعة من التحويلات مثل زيادة السطوع أو تقليله أو قلب الصورة رأسياً أو أفقياً أو تدويرها أو إزاحة البكسلات . إلخ). وروعته في أنه يقوم بتوليد الصور الجديدة في ال  real-time أي بقوم بإنتاجها أثناء تدريب النموذج. وتضمن لك أن النموذج سيتلقى صوراً مختلفة في كل epoch وأيضاً يجب أن تعلم أنه لايقوم بإضافة الصور الجديدة إلى مجموعة البيانات الأصلية (ومن المنطقي جداً حدوث هذا لأنه قد يؤدي إلى ال OF على الصورة). وميزة أخرى لـ ImageDataGenerator هي أنه يتطلب استخدام ذاكرة أقل. حيث يقوم بإنشاء batches بدلاً من توليد كامل مجموعة البيانات ووضعها كلها في الذاكرة. ويمكن  إنشائه بالشكل التالي:

dataGen=tf.keras.preprocessing.image.ImageDataGenerator(
    rotation_range=0,
    width_shift_range=0.0,
    height_shift_range=0.0,
    brightness_range=None,
    shear_range=0.0,
    zoom_range=0.0,
    channel_shift_range=0.0,
    fill_mode="nearest",
    cval=0.0,
    horizontal_flip=False,
    vertical_flip=False,
    rescale=None,
    data_format=None,
    validation_split=0.0
)

هذا الكلاس نحدد له العمليات التي نريد تنفيذها على الصور، فبالنسبة لأول وسيط وهو rotation_range فهو يقوم بتدوير الصورة الأصلية بدرجة معينة نقوم بتحديدها (من 0 إلى 360). لكن عندما يتم تدوير الصورة، ستتحرك بعض وحدات البكسل خارج الصورة وتترك مساحة فارغة يجب ملؤها. يمكنك ملء ذلك بطرق مختلفة مثل القيمة الثابتة "constant" أو أقرب قيم البكسل "nearest" .. إلخ. يتم تحديد هذا في وسيطة fill_mode والقيمة الافتراضية هي "nearest" والتي تقوم ببساطة باستبدال المساحة الفارغة بأقرب قيم للبكسل.
أيضاً قد لا يكون الكائن دائماً في وسط الصورة. للتغلب على هذه المشكلة ، يمكننا إزاحة وحدات البكسل في الصورة إما أفقياً أو رأسياً، ويتم ذلك عن طريق إضافة قيمة ثابتة معينة لجميع وحدات البكسل، ويمكننا القيام بذلك من خلال الوسيطة height_shift_range للإزاحة الرأسية و width_shift_range للإزاحة الأفقية للصورة. إذا كانت القيمة رقم float، فسيشير ذلك إلى النسبة المئوية لعرض أو ارتفاع الصورة المراد إزاحتها. بخلاف ذلك ، إذا كانت القيمة عدد صحيح، فسيتم إزاحة العرض أو الارتفاع ببساطة بواسطة قيم البكسل العديدة هذه.

يعد قلب الصور أيضاً تقنية رائعة ويمكننا القيام بذلك من خلال الوسيط horizo1ntal_flip و vertical_flip للقلب على طول المحور الرأسي أو الأفقي. ومع ذلك، يجب أن تكون هذه التقنية وفقاً للكائن الموجود في الصورة. على سبيل المثال ، لن يكون القلب الرأسي للسيارة أمراً منطقياً لأنه في البيانات الحقيقية لن يتم اختبار هكذا حالات. أيضاً brightness_range يغير سطوع الصورة بشكل عشوائي. إنها أيضاً تقنية لزيادة البيانات مفيدة للغاية لأن الكائن الخاص بنا لن يكون في حالة إضاءة مثالية في معظم الأوقات. لذلك، يصبح من الضروري تدريب نموذجنا على الصور تحت ظروف الإضاءة المختلفة. ويقبل قائمة من قيمتين عائمتين float ويختار قيمة إزاحة السطوع من هذا النطاق.  أيضاً تمثل القيمة 0.0 حالة عدم إضافة أي سطوع بينما القيمة 1.0 تمثل أعظم سطوع. أيضاً يمكنك عمل zoom in و zoom out للصورة من خلال الوسيط zoom_range حيث تعطيه قائمة بقيمتين تحددان الحد الأدنى والحد الأعلى لعملية ال zoom. أو إذا قمت بتحديد قيمة عائمة، فسيتم إجراء التكبير / التصغير في النطاق:[1-zoom_range ، 1 + zoom_range].

أي قيمة أصغر من 1 سيتم تكبير الصورة. في حين أن أي قيمة أكبر من 1 سيتم تصغيرها. أيضاً تسمح لك الوسيطة shear_range بتنفيذ عملية غريبة نوعاً ما فهي تشبه إلى حدما عملية التدوير لكنها تمدد الصورة (تمديد للبكسلات) وأيضاً يجب أن تحدد له درجة لكن هذه الدرجة لاتمثل درجة الدوران وإنما درجة تسمى درجة "القص" أو الميل (ليس لدي فكرة جيدة عنها). أيضاً هناك الوسيطة channel_shift_range التي تسمح لك بتطبيق إزاحة أيضاً لكن على قنوات الصورة. الوسيطة fill_mode كما قلنا من أجل تحديد الآلية التي سيتم فيه تعويض البكسلات التي  انتقلت (تغير مكانها). أما cval فتستخدم مع fill mode في حالة اخترنا التكنيك constant وذلك من أجل تحديد القيمة التي نريد التعويض بها (قيمة float طبعاً). أما الوسيطة rescale فهي تمنحنا القدرة على عملي standaraization للصورة (توحيد قيمها لتكون ضمن المجال 0 ل 1) وهذه العملية ضرورية دوماً. أما الوسيط data_format فهو لتحديد ترتيب أبعاد الصورة أي لتحديد فيما إذا كانت القناة هي أخر  بعد "channels_last" أو أول بعد"channels_first" . أما الوسيط الأخير فيسمح لنا بأخذ جزء من البيانات ك validation data. الآن بعد أن تقوم بتحديد هذه الوسطاء لباني الصف هذا، كل ماعليك فعله هو استخدام التايع  flow_from_directory لقراءة الصور مباشرةً من المجلد وتنفيذ ال augmantaion أثناء تدريب الشبكة العصبية على بيانات التدريب. ويتوقع التابع أن الصور التي تنتمي إلى فئات مختلفة موجودة في مجلدات مختلفة ولكنها داخل نفس المجلد الأصلي. وهذا التابع له الشكل التالي:

ImageDataGenerator.flow_from_directory(
    directory, # المسار الخاص بالمجلد
    target_size=(256, 256), # حجم الصور
    color_mode="rgb", # النمط  "grayscale", "rgb", "rgba". Default: "rgb"
    classes=None, # الفئات ويمكنك تحديدها أولا أي أنه اختياري  ['dogs', 'cats'] مثلاً
    class_mode="categorical",
    batch_size=32, # حجم الباتش للبيانات
    shuffle=True, # خلط البيانات أو لا
    save_to_dir=None, # إذا اردت الاحتفاظ بالصور التي تم توليدها ضع له مسار مجلد ليحفظها فيهم
    save_format="png", # الامتداد الذي تريد حفظ الصور فيه في حال أردت حفظها
    follow_links=False,
    subset=None,
    interpolation="nearest", # تحديد الطريقة التي سيتم فيها تصغير الصورة في حالة كانت أكبر من الحجم المحدد أو أصغر منه
)

بالنسبة للوسيط subset يجب أن تحدد في هذه الوسيطة إما ("training" أو "validation") وذلك في حالة كنت قد حدد جزءاً من البيانات ك validation في باني الكلاس. أما class_mode فهو يحدد نوع البيانات التي لديك أي binary أي أن لديك فئتين وبالتالي سيكون لدينا  1D binary labels، أو categorical سيكون لدينا 2D one-hot encoded labels أو sparse سيكون  1D integer labels. أو None وبالتالي هنا لن يقوم بتوليد ال labels للصور لن ينتج عن المُنشئ سوى مجموعات من بيانات الصورة ، وهو أمر مفيد للاستخدام مع model.predict ()). الآن دعنا نرى تطبيقاً عملياً:

from keras.models import Sequential
from keras.layers import Dense,Conv2D,Dropout,MaxPooling2D,Flatten
from keras.preprocessing.image import ImageDataGenerator
import cv2
import numpy as np
from keras.preprocessing.image import ImageDataGenerator
from keras.models import Sequential
from keras.layers import Conv2D, MaxPooling2D
from keras.layers import Activation, Dropout, Flatten, Dense
from keras import backend as K
#########################################
obj = ImageDataGenerator(
    rescale=1. / 255,
    shear_range=0.2,
    zoom_range=0.2,
    horizontal_flip=True)
train_generator = obj.flow_from_directory(
    '/content/safe/train',
    target_size=(img_width, img_height),
    batch_size=batch_size,
    class_mode='binary')
# validation data
val_datagen = ImageDataGenerator(rescale=1. / 255)
validation_generator = val_datagen.flow_from_directory(
    '/content/safe/val',
    target_size=(img_width, img_height),
    batch_size=batch_size,
    class_mode='binary')
"""
Found 2039 images belonging to 2 classes.
Found 384 images belonging to 2 classes.
"""
print(train_generator.class_indices)
# {'ClosedFace': 0, 'OpenFace': 1}
# Number of Training and Validation Samples
nb_train_samples = 2039
nb_validation_samples = 384
################################## Bulding Model ###############################3
#Instantiation
model = Sequential()
#1st Convolutional Layer
model.add(Conv2D(32, (3, 3), input_shape=(100,100,3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
#2nd Convolutional Layer
model.add(Conv2D(32, (3, 3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
#3rd Convolutional Layer
model.add(Conv2D(64, (3, 3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
#Passing it to a Fully Connected layer
model.add(Flatten())
# 1st Fully Connected Layer
model.add(Dense(64))
model.add(Activation('relu'))
# Add Dropout to prevent overfitting
model.add(Dropout(0.5))
#Output Layer
model.add(Dense(1))
model.add(Activation('sigmoid'))
# compile
model.compile(loss='binary_crossentropy',
              optimizer='rmsprop',
              metrics=['accuracy'])
# Fitting
epochs = 15
batch_size = 16
model.fit_generator(
    train_generator,
    steps_per_epoch=nb_train_samples // batch_size,  
    epochs=15,
    validation_data=validation_generator,
    validation_steps=nb_validation_samples // batch_size)
#fit أيضاً يمكنك استخدام الدالة

أيضاً في حالة كانت بياناتك موجودة  ضمن Dataframe يمكنك استخدام  flow_from_dataframe بنفس الطريقة مع اختلاف بسيط وهو تمرير الداتافريم الخاص بك وتحديد العمود الذي يحوي أسماء الملفات والعمود الذي يحوي ال labels:

ImageDataGenerator.flow_from_dataframe(
    dataframe,
    directory=None,
    x_col="filename",
    y_col="class",
    weight_col=None,
    target_size=(256, 256),
    color_mode="rgb",
    classes=None,
    class_mode="categorical",
    batch_size=32,
    shuffle=True,
    seed=None,
    save_to_dir=None,
    save_prefix="",
    save_format="png",
    subset=None,
    interpolation="nearest",
    validate_filenames=True,
)

 

تمّ تعديل بواسطة Ali Haidar Ahmad

انشر على الشّبكات الاجتماعية


رابط هذه المساهمة
شارك على الشبكات الإجتماعية

يجب أن تكون عضوًا لدينا لتتمكّن من التعليق

انشاء حساب جديد

يستغرق التسجيل بضع ثوان فقط


سجّل حسابًا جديدًا

تسجيل الدخول

تملك حسابا مسجّلا بالفعل؟


سجّل دخولك الآن