مجموعه دانش‌بنیان شناسا
مجموعه دانش‌بنیان شناسا
خواندن ۹ دقیقه·۴ سال پیش

استفاده از MixUp برای Data Augmentation

ایده تکنیک MixUp در سال 2018 در این مقاله معرفی شد و به سرعت مورد استقبال افراد فعال در این حوزه قرار گرفت. پیاده سازی این ایده بسیار آسان است و قابلیت تعمیم مدل را به صورت معنا داری افزایش می‌دهد. در تصویر زیر تاثیر استفاده از MixUp بر روی دیتاست ImageNet را مشاهده می‌کنید.

افزایش دقت با استفاده از MixUp روی دیتاست ImageNet
افزایش دقت با استفاده از MixUp روی دیتاست ImageNet

این روش یک تکنیک Data Augmentation است که وابستگی به نوع داده ندارد و شما می‌توانید از آن در زمان آموزش تمام مدل‌های خود استفاده کنید.

ایده‌ی اصلی این روش بدین صورت است که شما دو کلاس مختلف را با یک ضریب مشخص با هم ترکیب می‌کنید. برای نمونه در تصویر زیر یک نمونه از کلاس Dog با یک نمونه از کلاس Cat ترکیب شده است.

ترکیب خطی دو کلاس
ترکیب خطی دو کلاس

این نمونه‌ها به صورت تصادفی از مجموعه داده انتخاب می‌شوند. ترکیب آن‌ها از نوع weighted linear interpolation است. در مرحله بعد نیاز است همین ترکیب با ضریب قبلی برای label های متناظر نیز انجام شود.

فرمول MixUp
فرمول MixUp

عبارت اول دو x را با ضریب λ با هم ترکیب می‌کند و عبارت دوم همین کار را برای دو y متناظر انجام می‌دهد. در این فرمول λ که همان ضریب ترکیب است به صورت تصادفی از توزیع احتمال Beta انتخاب می‌شود. توزیع Beta در بیشتر مواقع ۰ یا ۱ است. به این معنی که در اکثر مواقع داده‌ها تغییری نخواهند کرد. در مواقع دیگر نیز عددی بین ۰ و ۱ است که نسب ترکیب دو تصویر و برچسب‌های متناظر آن‌ها را مشخص می‌کند. توزیع Beta پارامتری به نام alpha که میزان انتخاب شدن عددی به جز صفر و یک را کنترل می‌کند.

توزیع Beta
توزیع Beta

همانطور که در تصویر مشخص است، هر چقدر Alpha کوچک‌تر باشد توزیع بیشتر ۰ و یا ۱ را تولید می‌کند. در مقابل، اگر Alpha عدد بزرگی باشد احتمال این که عدد انتخاب شده بین ۰ یا ۱ باشد بیشتر است. در مقاله پیشنهاد شده عددی بین ۰/۲ تا ۰/۴ برای Alpha انتخاب شود. استفاده از عددهای کوچک‌تر از ۰/۲ باعث کم شدن تاثیر استفاده از این تکنیک می‌شود؛ زیرا باعث می‌شود که مدل مثل شرایط عادی (با نمونه‌هایی که هر کدام فقط به یک class تعلق دارند) آموزش ببیند. انتخاب اعداد بزرگ‌تر از ۰/۴ نیز باعث دشواری مساله می‌شود که سبب Underfitting مدل خواهد شد.

برای تولید توزیع‌های بالا می‌توانید از کد زیر استفاده کنید.

import numpy as np import matplotlib.pyplot as plt plt.figure(figsize=(15, 8)) for step, alpha in enumerate([.1, .2, .4, .6, .8, .9]): x = np.random.beta(alpha, alpha, size=90000) plt.subplot(2, 3, step+1) plt.title('alpah:' + str(alpha)) plt.hist(x, bins=100) plt.show()

تکنیک MixUp چگونه به تعمیم مدل کمک می‌کند؟

نکته اول و مهم این است که این روش فقط برای تصاویر قابل استفاده نیست؛ بلکه می‌توان در زمینه‌های دیگر نیز از آن استفاده کرد. برای مثال در حوزه NLP می‌توان Embedding ها را با هم ترکیب کرد و ...

با استفاده از این روش Data Augmentation مدل شما بیش از اندازه در مورد رابطه ای بین Features ها و Label ها مطمئن نخواهد بود. برای درک بهتر این که چرا MixUp قابلیت تعمیم مدل را افزایش می دهد به تصویر زیر توجه کنید.

تفاوت مرز تصمیم گیری در MixUp
تفاوت مرز تصمیم گیری در MixUp

در سمت چپ شما دسته‌بندی عادی نمونه‌ها را مشاهده می‌کنید. زمانی که نمونه جدید درون هر کدام از این حباب‌ها قرار بگیرد مدل با اطمینان بالا پاسخ درست می‌دهد ولی اگر نمونه‌ی جدید کمی بیرون از این حباب‌ها باشد، مدل هیچ دانش و اطلاعات قبلی ‌ای درباره‌ی این نمونه جدید ندارد و نمی‌تواند آن را به درستی دسته‌بندی کند.

در مقابل، در شکل سمت راست از MixUp در زمان آموزش استفاده شده است. این مدل متوجه شده است که هرچه از مرکز یک دسته دورتر شود احتمال آن کلاس کمتر خواهد بود. در واقع این مدل یک مرز تصمیم‌گیری احتمالاتی ایجاد کرده، نه صرفا یک خط که برا‌ی نمونه‌های خارج از آن خط هیچ پیش‌بینی‌ای نداشته باشد.

در تصویر زیر نحوه آموزش مدل را مشاهده می‌کنید.

در زمان آموزش
در زمان آموزش

در این جا نمونه مشخص شده ۰/۶ برای کلاس قرمز و ۰/۴ برای کلاس سبز است. این عمل باعث می‌شود مدل فرا گیرد که کدام ویژگی‌ها کلاس‌ها را از هم متمایز می‌کند. این تکنیک مرز‌های تصمیم‌گیری بین دسته‌ها را نرم‌تر می‌کند و باعث می‌شود نمونه‌های جدید با دقت بیشتری دسته‌بندی شوند. این مرز تصمیم‌گیری Smooth به دلیل ترکیب کردن نمونه‌ها ایجاد شده است.

مزیت‌های استفاده از MixUp

  1. به نوع داده‌خاصی محدود نیست.
  2. مرز‌های تصمیم Smooth
  3. باعث کم اهمیت شدن corrupt labels می‌شود
  4. حساسیت مدل به Outlier ها را کاهش می‌دهد
  5. ایجاد ترکیبی از نمونه های داخل یک کلاس

نکته: این که فقط نمونه‌های یک دسته را با هم ترکیب کنیم هم روش خوبی است اما به اندازه‌ی MixUp قابلیت Generalization را افزایش نخواهد داد. (مرز بین کلاس‌ها را تغییر نمی‌دهد.)


پیاده سازی در Tensorflow

تمام کدها همراه با خروجی در این notebook نیز در دسترس است.

این مثال تا حدی زیادی تاثیر گرفته از مثال رسمی وبسایت Keras است.

import tensorflow as tf from tensorflow.keras import layers

برای این پیاده‌سازی از دیتاست MNIST استفاده می‌کنیم. فراموش نکنید که برای راحت تر شدن ترکیب Label ها نیاز داریم که آن‌ها را One Hot کنیم.

(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data() x_train = x_train.astype(&quotfloat32&quot) / 255.0 x_train = np.reshape(x_train, (-1, 28, 28, 1)) y_train = tf.one_hot(y_train, 10) x_test = x_test.astype(&quotfloat32&quot) / 255.0 x_test = np.reshape(x_test, (-1, 28, 28, 1)) y_test = tf.one_hot(y_test, 10)

در این قسمت دیتا را به tf.Data تبدیل می‌کنیم. این روش مزایای خیلی زیادی نسبت به Numpy بودن دیتاست دارد. اگر با نحوه کار tf.Data آشنایی ندارید می‌توانید از این آموزش استفاده کنید.

train_ds = tf.data.Dataset.from_tensor_slices((x_train, y_train)) train_ds = train_ds.shuffle(512).batch(64) # Because we will be mixing up the images and their corresponding labels, we will be # combining two shuffled datasets from the same training data. train_ds_mu = tf.data.Dataset.zip((train_ds, train_ds)) test_ds = tf.data.Dataset.from_tensor_slices((x_test, y_test)).batch(64)

در این قسمت تابعی نوشته شده است که دو Batch دریافت می‌کند و آن‌ها را با استفاده از فرمولی که در بالا گفته شد ترکیب می‌کند.

نکته: چون tf.random توزیع Beta را به طور مستقیم برای ساخت آن از ترکیب دو توزیع Gamma استفاده شده است که تفاوتی در نتیجه‌ی نهایی ندارد.

def sample_beta_distribution(size, alpha): gamma_left = tf.random.gamma(shape=[size], alpha=alpha) gamma_right = tf.random.gamma(shape=[size], alpha=alpha) beta = gamma_left / (gamma_left + gamma_right) return beta def linear_combination(x1, x2, alpha): return x1 * alpha + x2 * (1 - alpha) def mix_up(ds_one, ds_two, alpha=0.2): # Unpack two datasets images_one, labels_one = ds_one images_two, labels_two = ds_two batch_size = tf.shape(images_one)[0] # Sample lambda and reshape it to do the mixup λ = sample_beta_distribution(batch_size, alpha) images_λ = tf.reshape(λ, (batch_size, 1, 1, 1)) # 3channel images labels_λ = tf.reshape(λ, (batch_size, 1)) # Perform mixup on both images and labels by combining a pair of images/labels # (one from each dataset) into one image/label images = linear_combination(images_one, images_two, images_λ) labels = linear_combination(labels_one, labels_two, labels_λ) return (images, labels)

تابع بالا را به شئ tf.Data ی قبلی Map می‌کنیم. این قطعه کد مثالی از ورودی مدل را نمایش می‌دهد.

train_ds_mu = train_ds_mu.map(mix_up, num_parallel_calls=tf.data.AUTOTUNE) # Let's preview 9 samples from the dataset sample_images, sample_labels = next(iter(train_ds_mu)) plt.figure(figsize=(10, 10)) for i, (image, label) in enumerate(zip(sample_images[:9], sample_labels[:9])): ax = plt.subplot(3, 3, i + 1) classes = np.argsort(label)[-2:][::-1] scores = np.round(label, 2)[classes] plt.title(f'C:{classes}, S:{scores}') plt.imshow(image.numpy().squeeze(), cmap='gray') plt.axis(&quotoff&quot)

خروجی کد بالا: حرف C نشان دهنده Class و حرف S نشان دهنده میزان ترکیب دسته‌هاست.

نمونه ای از ورودی تصاویر و Label های مدل
نمونه ای از ورودی تصاویر و Label های مدل

نکته: برای ایجاد تصاویر بالا از دو کلاس استفاده شده است. از نظر تئوری می‌توانیم هر چند کلاس دلخواه را ترکیب کنیم. این کار باعث افزایش هزینه‌ی محاسباتی می‌شود. هم‌چنین در عمل نیز باعث افزایش چشمگیر عملکرد مدل نخواهد شد. برای اطلاعات بیشتر راجع به این موضع به مقاله اصلی مراجعه کنید.

در این قسمت یک مدل پایه ایجاد می‌کنیم.

def create_model(): model = tf.keras.Sequential( [ layers.Conv2D(32, (3, 3), strides=(2, 2), activation=&quotrelu&quot, input_shape=(28, 28, 1)), layers.Conv2D(64, (3, 3), strides=(2, 2), activation=&quotrelu&quot), layers.Dropout(0.1), layers.GlobalAvgPool2D(), layers.Dense(10, activation=&quotsoftmax&quot), ] ) return model initial_model = create_model() initial_model.save_weights(&quotinitial_weights.h5&quot)

حال با استفاده از دیتاست MixUp مدل را آموزش می‌دهیم.

model = create_model() model.load_weights(&quotinitial_weights.h5&quot) optimizer = tf.keras.optimizers.Adam(3e-3) model.compile(loss=&quotcategorical_crossentropy&quot, optimizer=optimizer, metrics=[&quotaccuracy&quot]) model.fit(train_ds_mu, validation_data=test_ds, epochs=10) _, test_acc = model.evaluate(test_ds) print(&quotTest accuracy: {:.2f}%&quot.format(test_acc * 100))

خروجی قطعه کد بالا:

Epoch 1/10 938/938 [==============================] - 5s 4ms/step - loss: 1.9706 - accuracy: 0.3163 - val_loss: 1.0601 - val_accuracy: 0.6620 Epoch 2/10 938/938 [==============================] - 4s 4ms/step - loss: 1.3937 - accuracy: 0.6013 - val_loss: 0.7975 - val_accuracy: 0.7675 Epoch 3/10 938/938 [==============================] - 4s 4ms/step - loss: 1.2209 - accuracy: 0.6878 - val_loss: 0.6442 - val_accuracy: 0.8230 Epoch 4/10 938/938 [==============================] - 4s 4ms/step - loss: 1.1278 - accuracy: 0.7283 - val_loss: 0.5390 - val_accuracy: 0.8629 Epoch 5/10 938/938 [==============================] - 4s 4ms/step - loss: 1.0743 - accuracy: 0.7568 - val_loss: 0.5087 - val_accuracy: 0.8686 Epoch 6/10 938/938 [==============================] - 4s 4ms/step - loss: 1.0389 - accuracy: 0.7708 - val_loss: 0.4936 - val_accuracy: 0.8649 Epoch 7/10 938/938 [==============================] - 4s 4ms/step - loss: 1.0115 - accuracy: 0.7806 - val_loss: 0.4389 - val_accuracy: 0.8877 Epoch 8/10 938/938 [==============================] - 4s 4ms/step - loss: 0.9849 - accuracy: 0.7961 - val_loss: 0.4022 - val_accuracy: 0.9004 Epoch 9/10 938/938 [==============================] - 4s 4ms/step - loss: 0.9680 - accuracy: 0.8006 - val_loss: 0.3768 - val_accuracy: 0.9079 Epoch 10/10 938/938 [==============================] - 4s 4ms/step - loss: 0.9546 - accuracy: 0.8100 - val_loss: 0.3737 - val_accuracy: 0.9050 Test accuracy: 91.50%

آموزش همین مدل بدون استفاده از MixUp:

model = create_model() model.load_weights(&quotinitial_weights.h5&quot) optimizer = tf.keras.optimizers.Adam(3e-3) model.compile(loss=&quotcategorical_crossentropy&quot, optimizer=optimizer, metrics=[&quotaccuracy&quot]) model.fit(train_ds, validation_data=test_ds, epochs=10) _, test_acc = model.evaluate(test_ds) print(&quotTest accuracy: {:.2f}%&quot.format(test_acc * 100))

خروجی قطعه کد بالا:

Epoch 1/10 938/938 [==============================] - 4s 4ms/step - loss: 1.8111 - accuracy: 0.3506 - val_loss: 0.9461 - val_accuracy: 0.7019 Epoch 2/10 938/938 [==============================] - 3s 4ms/step - loss: 0.9680 - accuracy: 0.6812 - val_loss: 0.7535 - val_accuracy: 0.7526 Epoch 3/10 938/938 [==============================] - 3s 4ms/step - loss: 0.7687 - accuracy: 0.7570 - val_loss: 0.5767 - val_accuracy: 0.8304 Epoch 4/10 938/938 [==============================] - 3s 4ms/step - loss: 0.6574 - accuracy: 0.7903 - val_loss: 0.4974 - val_accuracy: 0.8548 Epoch 5/10 938/938 [==============================] - 4s 4ms/step - loss: 0.5800 - accuracy: 0.8197 - val_loss: 0.4542 - val_accuracy: 0.8633 Epoch 6/10 938/938 [==============================] - 3s 4ms/step - loss: 0.5232 - accuracy: 0.8373 - val_loss: 0.4071 - val_accuracy: 0.8787 Epoch 7/10 938/938 [==============================] - 4s 4ms/step - loss: 0.4858 - accuracy: 0.8499 - val_loss: 0.3633 - val_accuracy: 0.8955 Epoch 8/10 938/938 [==============================] - 3s 4ms/step - loss: 0.4471 - accuracy: 0.8623 - val_loss: 0.3546 - val_accuracy: 0.8948 Epoch 9/10 938/938 [==============================] - 3s 4ms/step - loss: 0.4235 - accuracy: 0.8676 - val_loss: 0.3201 - val_accuracy: 0.9054 Epoch 10/10 938/938 [==============================] - 3s 4ms/step - loss: 0.4235 - accuracy: 0.8676 - val_loss: 0.3201 - val_accuracy: 0.9001 Test accuracy: 90.01%

با مقایسه هر دو روش متوجه می‌شوید که MixUp نیاز به تعداد Epoch بیشتری دارد؛ ترکیب نمونه‌ها مساله را کمی سخت‌تر کرده است. اما باعث شده که مدل خیلی زودتر به دقت بالای ۹۰ درصد روی دیتاست تست برسد. این نشان‌دهنده بالا بودن قدرت تعمیم در این روش است.

نکات مهم:

  1. این روش برای دیتاست‌های کوچک بسیار خوب عمل می‌کند.
  2. این روش با Label smoothing مغایرت دارد.
  3. به خوبی با Dropout سازگار است.
  4. ایده‌های دیگری نظیر ترکیب کردن لایه‌های میانی مدل و غیره در مقاله های CutMix و AugMix بررسی شده‌اند.


نویسندگان: سجاد ایوبی، بهار برادران افتخاری

منابع:

  1. Paper: mixup: Beyond Empirical Risk Minimization
  2. Keras Code Example: MixUp augmentation for image classification
deep learningیادگیری عمیقData AugmentationMixUp
شاید از این پست‌ها خوشتان بیاید