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

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

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

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

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

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

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

فرمول MixUp

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

توزیع Beta  

همانطور که در تصویر مشخص است، هر چقدر Alpha کوچک‌تر باشد توزیع بیشتر 0 و یا 1 را تولید می‌کند. در مقابل، اگر Alpha عدد بزرگی باشد احتمال این که عدد انتخاب شده بین 0 یا 1 باشد بیشتر است. در مقاله پیشنهاد شده عددی بین 0/2 تا 0/4 برای Alpha انتخاب شود. استفاده از عددهای کوچک‌تر از 0/2 باعث کم شدن تاثیر استفاده از این تکنیک می‌شود؛ زیرا باعث می‌شود که مدل مثل شرایط عادی (با نمونه‌هایی که هر کدام فقط به یک class تعلق دارند) آموزش ببیند. انتخاب اعداد بزرگ‌تر از 0/4 نیز باعث دشواری مساله می‌شود که سبب 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 در زمان آموزش استفاده شده است. این مدل متوجه شده است که هرچه از مرکز یک دسته دورتر شود احتمال آن کلاس کمتر خواهد بود. در واقع این مدل یک مرز تصمیم‌گیری احتمالاتی ایجاد کرده، نه صرفا یک خط که برا‌ی نمونه‌های خارج از آن خط هیچ پیش‌بینی‌ای نداشته باشد.

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

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

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

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

نکته: این که فقط نمونه‌های یک دسته را با هم ترکیب کنیم هم روش خوبی است اما به اندازه‌ی 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("float32") / 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("float32") / 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("off")

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

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

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

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

def create_model():
    model = tf.keras.Sequential(
        [
            layers.Conv2D(32, (3, 3), strides=(2, 2), activation="relu", input_shape=(28, 28, 1)),
            layers.Conv2D(64, (3, 3), strides=(2, 2), activation="relu"),
            layers.Dropout(0.1),
            layers.GlobalAvgPool2D(),
            layers.Dense(10, activation="softmax"),
        ]
    )
    return model
initial_model = create_model()
initial_model.save_weights("initial_weights.h5")

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

model = create_model()
model.load_weights("initial_weights.h5")
optimizer = tf.keras.optimizers.Adam(3e-3)
model.compile(loss="categorical_crossentropy", optimizer=optimizer, metrics=["accuracy"])
model.fit(train_ds_mu, validation_data=test_ds, epochs=10)
_, test_acc = model.evaluate(test_ds)
print("Test accuracy: {:.2f}%".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("initial_weights.h5")
optimizer = tf.keras.optimizers.Adam(3e-3)
model.compile(loss="categorical_crossentropy", optimizer=optimizer, metrics=["accuracy"])
model.fit(train_ds, validation_data=test_ds, epochs=10)
_, test_acc = model.evaluate(test_ds)
print("Test accuracy: {:.2f}%".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 بیشتری دارد؛ ترکیب نمونه‌ها مساله را کمی سخت‌تر کرده است. اما باعث شده که مدل خیلی زودتر به دقت بالای 90 درصد روی دیتاست تست برسد. این نشان‌دهنده بالا بودن قدرت تعمیم در این روش است.

نکات مهم:

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

منابع:

Paper: mixup: Beyond Empirical Risk Minimization
Keras Code Example: MixUp augmentation for image classification

ارسال دیدگاه

کد امنیتی