آشنایی با انواع روش‌های داده‌افزایی در تصاویر

همان‌طور که می‌دانید، اکثر سیستم‌های بینایی کامپیوتر با افزایش حجم داده‌ها، عملکرد بهتری خواهند داشت؛ اما در خیلی از مواقع جمع‌آوری داده‌ی بیشتر کاری سخت و پرهزینه است. بنابراین، data augmentation یا داده‌افزایی یکی از تکنیک‌هایی است که اغلب برای بهبود بازدهی سیستم‌های بینایی کامپیوتر به کار گرفته می‌شود. مهم نیست که از transfer learning استفاده می‌کنید و یا در حال train کردن مدل خود هستید، معمولا data augmentation کمک زیادی می‌کند. توجه داشته باشید که این امر در تمامی کاربردهای یادگیری ماشین صدق نمی‌کند اما در اکثر مساله‌های بینایی کامپیوتر با کمبود داده مواجه می‌شویم و از این رو نیاز به Data Augmentation در این حوزه احساس می‌شود.تکنیک Data augmentation با تولید تصاویر جدید به طور مصنوعی از تصاویر اصلی به ما کمک می‌کند تا تصاویر جدیدی برای آموزش داشته باشیم. این کار در هر کاربرد و دامنه‌ای از شبکه‌های یادگیری عمیق روش‌های مخصوص به خود را دارد؛ مثلاً در یادگیری تشخیص گفتار انسانی، کمی نویز به سیگنال صوت اضافه می‌کنیم تا شبکه تشخیص کلمات یا احساسات را حتی با صداهای غیرشفاف نیز یاد بگیرد. اما در مساله‌های تشخیص وجود شئ در تصویر یا پیدا کردن دقیق جای چند شئ در یک تصویر، تکنیک‌های دیگری مثل چرخش و تغییر روشنایی و بریدن تصویر استفاده می‌شود.آموزش شبکه‌های عصبی مصنوعی عمیق با داده‌های بیشتر می‌تواند به داشتن شبکه‌های قوی تر منتهی شود؛ و تکنیک‌های Image data augmentation می‌توانند گونه‌هایی از تصاویر را بسازند که شبکه بتواند با یادگیری خصوصیات جدیدی از آن‌ تصاویر - علاوه بر تصاویر اصلی - قدرت درک گستره بیشتری از هر شئ داشته باشد و یا به اصطلاح Generalized باشد.شاید بتوان گفت که Image data augmentation شناخته‌شده‌ترین نوع data augmentation است که شامل ساخت نسخه‌های دگرگون‌شده از تصاویر است که همچنان به کلاس و توزیع آماری تصاویر اصلی تعلق دارند. هدف data augmentation افزایش اندازه مجموعه با استفاده از تصاویر جدیدی است که محتمل و باور کردنی باشند. پس مشخص است که انتخاب تکنیک data augmentation به کار رفته باید با دقت و مطابق با محتوای دیتاست و نوع مسئله انجام شود.می توانید در این زمینه به آزمایش و خطا هم بپردازید. به این شکل که بخش کوچکی از تصاویر آموزش را با شبکه‌ای کوچک آموزش دهید و در این آموزش از تنظیم‌های مختلفی برای تولید تصاویر جدید استفاده کنید. در این روند لازم است بهبود یا عدم بهبود شبکه را به طور مداوم در نظر داشته باشید.حال می‌خواهیم که نگاهی به تکنیک‌های متفاوت image data augmentation بیندازیم. برای راحتی کار، از کلاس ImageDataGenerator در کتابخانه keras استفاده می‌کنیم. تکنیک‌های بسیار زیادی برای این کار وجود دارد که ما در این‌جا تعدادی از معروف‌ترین آن‌ها را مثال می‌زنیم. برای بررسی نتیجه این روش‌ها از عکس زیر استفاده می‌کنیم.تصویر نمونهبرای شروع، از کلاس ImageDataGenerator یک شیٔ ایجاد می‌‌کنیم. تنظیمات چگونگی و شدت انواع مختلف augmetation ها را می‌توان از طریق آرگومان‌ها به کلاس سازنده اطلاع داد.datagen = ImageDataGenerator()بعد از ساخته شدن این شئ، می‌توانیم با آن یک iterator از یک دیتاست تصویر بسازیم تا در حین فرآیند آموزش به صورت دسته ای یا batch based تصاویر تولید شده را در هر iteration به شبکه دهد.تکنیک شیفت عمودی و افقی تصاویراین کار به معنای هل دادن تمامی پیکسل‌های تصویر در یک جهت مشخص است، مثل شیفت عمودی و افقی و قطری. در حین این عمل اندازه تصویر ثابت می‌ماند. این به این معنی است که تعدادی از پیکسل‌ها از تصویر خارج و حذف می‌شوند و در بخشی از تصویر باید برای پیکسل‌ها مقداری جدید مشخص شود. با کمک آرگومان های width_shift_range و height_shift_range در تابع سازنده ImageDataGenerator می‌توان ضمن فعال‌سازی این شیفت‌ها، بازه‌ی ممکن برای شیفت احتمالی افقی و عمودی هر تصویر را مشخص کرد.این آرگومان‌ها می‌توانند مقدارهای اعشاری در قالب یک عدد یا آرایه‌ای از اعداد را بگیرند. اگر ورودی یک عدد کوچک‌تر از ۱ باشد، کسری از تصویر را نشان می‌دهد که می‌تواند شیفت بخورد و اگر بیشتر از یک باشد، تعداد پیکسل‌ها را مشخص می‌کنند. هم‌چنین اگر به جای یک عدد آرایه ای از اعداد ارسال شود، بازه‌ی ممکن برای انتخاب تصادفی میزان شیفت پیکسل‌ها را مشخص می کند. توضیحات زیر در سایت رسمی Keras نوع و مقادیر آرگومان‌ها را توضیح می‌دهد.width_shift_range: Float, 1-D array-like or int - float: fraction of total width, if < 1, or pixels if >= 1. - 1-D array-like: random elements from the array. - int: integer number of pixels from interval (-width_shift_range, +width_shift_range) - With width_shift_range=2 possible values are integers [-1, 0, +1], same as with width_shift_range=[-1, 0, +1], while with width_shift_range=1.0 possible values are floats in the interval [-1.0, +1.0).height_shift_range: Float, 1-D array-like or int - float: fraction of total height, if < 1, or pixels if >= 1. - 1-D array-like: random elements from the array. - int: integer number of pixels from interval (-height_shift_range, +height_shift_range) - With height_shift_range=2 possible values are integers [-1, 0, +1], same as with height_shift_range=[-1, 0, +1], while with height_shift_range=1.0 possible values are floats in the interval [-1.0, +1.0).مثال زیر نشان می دهد که یک شیفت افقی با مقداردهی width_shift_range بین [۴۰۰، ۴۰۰-] چطور نوشته می‌شود و خروجی آن به چه شکل است.from numpy import expand_dims from keras.preprocessing.image import load_img from keras.preprocessing.image import img_to_array from keras.preprocessing.image import ImageDataGenerator from matplotlib import pyplot # load the image img = load_img('cat.jpg') # convert to numpy array data = img_to_array(img) # expand dimension to one sample samples = expand_dims(data, 0) # create image data augmentation generator datagen = ImageDataGenerator(width_shift_range=[-400, 400]) # prepare iterator it = datagen.flow(samples, batch_size=1) # generate samples and plot for i in range(9): # define subplot pyplot.subplot(330 + 1 + i) # generate batch of images batch = it.next() # convert to unsigned integers for viewing image = batch[0].astype('uint8') # plot raw pixel data pyplot.imshow(image) # show the figure pyplot.show()اجرای مثال بالا موجب می‌شود که یک شئ از ImageDataGenerator با پیکربندی مشخص‌شده ساخته شود و پس از تعیین اندازه دسته تصاویر و ساخت یک iterator، تصاویر مختلف تولید کنید. گرفتن تصویر و نمایش آن در حلقه‌ای با ۹ تکرار انجام شده که به این ترتیب ۹ تصویر متفاوت داریم. نتیجه شیفت افقی بر روی تصویردر ادامه همین مثال برای ایجاد شیفت‌های عمودی آرگومان height_shift_range را با مقدار ۰.۵ مقداردهی می‌کنیم که یعنی تصویر می‌تواند تا ۵۰ درصد به بالا یا پایین شیفت بخورد.# load the image img = load_img('cat.jpg') # convert to numpy array data = img_to_array(img) # expand dimension to one sample samples = expand_dims(data, 0) # create image data augmentation generator datagen = ImageDataGenerator(height_shift_range=0.3) # prepare iterator it = datagen.flow(samples, batch_size=1) # generate samples and plot for i in range(9): # define subplot pyplot.subplot(330 + 1 + i) # generate batch of images batch = it.next() # convert to unsigned integers for viewing image = batch[0].astype('uint8') # plot raw pixel data pyplot.imshow(image) # show the figure pyplot.show()اجرای این کد ۹ تصویر ایجاد می‌کند که به اندازه حداکثر ۵۰ درصد به صورت مثبت یا منفی شیفت خورده‌اند.نتیجه شیفت عمودی بر روی تصویرهمان‌طور که می‌بینید در تصویری که ما به عنوان نمونه داریم، تکرار پیکسل‌ها برای پر کردن بخشی که تازه به تصویر اضافه شده، چندان غیرمنطقی به نظر نمی‌رسد؛ اما اگر در کاربرد مسئله شما این موضوع باید تغییر کند، با مشخص کردن مقدار آرگومان fill_mode (که باعث می‌شود از مقدار پیش‌فرض این آرگومان استفاده نشود) می‌توانید از این کار جلوگیری کنید.برگرداندن تصاویر از چپ به راست و بالا به پایینبرگرداندن تصویر به معنای معکوس کردن جهت ردیف‌ها یا ستون‌های پیکسل‌های یک تصویر است که در نهایت موجب می شود تصویر در جهت محورهای عمودی یا افقی تصویر معکوس شود. برای فعال‌سازی این قابلیت باید آرگومان‌های باینری horizontal_flip و vertical_flip در سازنده کلاس ImageDataGenerator مقداردهی شوند.در نمونه تصویر مورد نظر ما، معکوس کردن تصویر در راستای افقی منطقی به نظر می‌رسد اما برگرداندن عمودی آن خیر، چرا که گربه‌ها برعکس و روی هوا زندگی نمی‌کنند! برای انواع دیگر تصاویر، مانند عکس‌های هوایی، کیهان‌شناسی و میکروسکوپی شاید برگردان عمودی (بالا به پایین) هم معنی پیدا کند.در مثال زیر با تعیین و مقداردهی horizontal_flip=True در تابع سازنده به طور تصادفی جهت برخی تصاویر را از چپ به راست معکوس کند.# load the image img = load_img('cat.jpg') # convert to numpy array data = img_to_array(img) # expand dimension to one sample samples = expand_dims(data, 0) # create image data augmentation generator datagen = ImageDataGenerator(horizontal_flip=True) # prepare iterator it = datagen.flow(samples, batch_size=1) # generate samples and plot for i in range(9): # define subplot pyplot.subplot(330 + 1 + i) # generate batch of images batch = it.next() # convert to unsigned integers for viewing image = batch[0].astype('uint8') # plot raw pixel data pyplot.imshow(image) # show the figure pyplot.show()کد بالا ۹ تصویر زیر را تولید می‌کند که در برخی از آن‌ها تصویر به شکل افقی برگردانده شده است.نتیجه معکوس کردن تصاویر در راستای افقیتکنیک چرخش تصویر به مقدار تصادفیاین روش جهت تقویت دیتاست از چرخش و دوران تصاویر استفاده می‌کند و برای این کار هر تصویر را به مقداری تصادفی در جهت عقربه‌های ساعت می‌چرخاند. حداکثر زاویه چرخش باید در سازنده شئ ImageDataGenerator تعیین شود که عددی بین ۰ تا ۳۶۰ است.چرخش موجب می‌شود برخی از پیکسل‌های اصلی از قاب تصویر خارج شوند و نیاز شود برخی دیگر قسمت‌ها با مقادیر جدیدی پر شوند که چگونگی تعیین مقدار آن‌ها با همان آرگومان fill_mode تعیین می‌شود که مقادیر محتمل برای آن و دیگر آرگومان‌های سازنده‌ی کلاس در این لینک آمده است.در کد زیر با مقداردهی آرگومان rotation_range در سازنده کلاس، مشخص کرده‌ایم که می‌خواهیم تصاویر را به طور تصادفی بین ۰ تا ۹۰ درجه در جهت عقربه‌های ساعت بچرخاند.# load the image img = load_img('cat.jpg') # convert to numpy array data = img_to_array(img) # expand dimension to one sample samples = expand_dims(data, 0) # create image data augmentation generator datagen = ImageDataGenerator(rotation_range=90) # prepare iterator it = datagen.flow(samples, batch_size=1) # generate samples and plot for i in range(9): # define subplot pyplot.subplot(330 + 1 + i) # generate batch of images batch = it.next() # convert to unsigned integers for viewing image = batch[0].astype('uint8') # plot raw pixel data pyplot.imshow(image) # show the figure pyplot.show()کد بالا نمونه تصاویر زیر را تولید می‌کند. در این نمونه نیز مانند مثال‌های قبلی برای پر کردن پیکسل‌های از دست رفته (جابه‌جا شده) از nearest-neighbor یا مقدار پیش‌فرض fill_mode استفاده شده است.نتیجه چرخش تصادفی تصویر تکنیک بزرگنمایی تصویر به اندازه تصادفیبا فعال‌سازی این مورد تصاویر مختلفی با بزرگنمایی‌های تصادفی در بازه‌ی مشخص شده از تصویر اصلی ایجاد می‌شود. مقدار آرگومان zoom_range که در سازنده کلاس ImageDataGenerator می‌بایست تعیین شود، می‌تواند یک عدد اعشاری یا یک آرایه دو عددی برای تعیین بازه بزرگنمایی باشد. اگر تنها یک عدد اعشاری مشخص شود، به طور خودکار به بازه‌ای به شکل زیر تبدیل می‌شود:(lower, upper) = (1 - zoom_range, 1+zoom_range)مقادیر کمتر از ۱.۰ به معنای کوچک کردن تصویر است؛ مثلاً [۰.۵،۰.۵] به طور قطع تمام تصاویر را ۵۰ درصد کوچک‌تر می‌کند و بازه [۱.۵، ۱.۵] همه‌ی تصاویر را ۵۰ درصد بزرگ‌تر می‌کند. هم‌چنین تعیین بازه به شکل [۱.۰،۱.۰] هم هیچ تاثیری در بزرگنمایی ندارد.مثال زیر تصویر نمونه را به طور تصادفی تا حداکثر ۵۰ درصد کوچک می‌کند.# load the image img = load_img('cat.jpg') # convert to numpy array data = img_to_array(img) # expand dimension to one sample samples = expand_dims(data, 0) # create image data augmentation generator datagen = ImageDataGenerator(zoom_range=[0.5, 1.0]) # prepare iterator it = datagen.flow(samples, batch_size=1) # generate samples and plot for i in range(9): # define subplot pyplot.subplot(330 + 1 + i) # generate batch of images batch = it.next() # convert to unsigned integers for viewing image = batch[0].astype('uint8') # plot raw pixel data pyplot.imshow(image) # show the figure pyplot.show()پس از اجرای کد بالا تصاویر زیر ایجاد می‌شوند. توجه داشته باشید که طول و عرض تصویر برابر نیستند و نسبت طول و عرض ۱:۱ نیست.نتیجه بزرگنمایی تصادفی تصویر تکنیک‌های ذکر شده فقط نمونه‌هایی از معروف‌ترین تکنیک‌های image data augmentation بودند. همان‌طور که متوجه شدید، این که از کدام تکنیک‌ها و با چه شدتی استفاده کنیم، به نوع داده‌ها و کاربرد سیستم بینایی ماشین مورد نظر بستگی دارد. پس از بررسی برخی روش‌های ابتدایی داده‌افزایی بهتر است نگاهی به آرگومان‌های کلاس ImageDataGenerator بیندازیم.tf.keras.preprocessing.image.ImageDataGenerator( featurewise_center=False, samplewise_center=False, featurewise_std_normalization=False, samplewise_std_normalization=False, zca_whitening=False, zca_epsilon=1e-06, 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=&quotnearest&quot, cval=0.0, horizontal_flip=False, vertical_flip=False, rescale=None, preprocessing_function=None, data_format=None, validation_split=0.0, dtype=None)نیاز به افزایش داده‌ها، باعث شده است که تنوع این تکنیک‌ها در حال افزایش باشد. در ادامه چند نمونه دیگر از این تکنیک‌ها را به طور مختصر بررسی می‌کنیم.تکنیک تغییر فضای رنگیروش‌های زیادی برای اجرای این تکنیک وجود دارد و فضایی برای خلاقیت در تبدیل تصویر فراهم کرده است، اما تغییر روشنایی و کنتراست متداول‌ترین روش‌ها هستند.تغییر فضای رنگی جهت داده افزاییتکنیک پاک کردن به صورت تصادفیجالبی این تکنیک در این است که مدل را مجبور می‌کند به جای توجه به یک زیر مجموعه، به کل تصویر توجه کند. به عنوان مثال، مدل نمی‌تواند فیل را فقط با نگاه كردن به صورتش تشخیص دهد؛ زیرا این قسمت ممكن است در برخی از تصاویر حذف شده باشد. این کار در حقیقت مدل را مجبور می‌كند تا به تمامی قسمت‌های تصویر نگاه كند. این تکنیک از روش dropout الهام گرفته شده است که یک روش regularization است. این تکنیک بخصوص در حل مسائلی که در آن‌ها انسداد رخ داده یا برخی از تصاویر نامشخص‌اند (به عنوان مثال تشخیص فیلی که بخشی از آن پشت درخت پنهان شده است) کمک می‌کند.پاک کردن بخشی از تصویر به صورت تصادفی جهت داده‌افزاییتکنیک استفاده از فیلترهای kernelاین تکنیک برای داده‌افزایی از محو و یا شفاف کردن تصاویر (توسط فیلترها) استفاده می‌کند. در واقع در این روش از کرنل‌های NxN استفاده می‌شودکه شباهت زیادی با convolutional neural net ها دارد. هدف این تکنیک قدرتمند کردن مدل در برابر وضوح تصاویر است، به خصوص زمانی که داده‌های آموزش از لحاظ وضوح کاملا مشابه هستند.تکنیک مخلوط کردن تصاویر(mixing images)میانگین‌گیری از پیکسل‌ها (mixup) و همپوشانی بخش‌هایی از تصاویر (cutmix) دو راه مخلوط کردن تصاویر هستند. برای مطالعه بیشتر درباره این تکنیک می‌توانید به این پست مراجعه کنید.در پایان در نظر داشته باشید که با استفاده از این تکنیک‌ها می‌توان از یک تصویر، چندین تصویر تولید کرد؛ اما این که کدام تکنیک را استفاده کنیم به شدت به داشتن درکی درست از داده‌هایمان بستگی دارد.نویسندگان: محمود ابدالی - عرفان ملکیمنابع:keras.iocoursera.orgmachinelearningmastery.comtowardsdatascience.comopen-mind.ir