ایده تکنیک MixUp در سال 2018 در این مقاله معرفی شد و به سرعت مورد استقبال افراد فعال در این حوزه قرار گرفت. پیاده سازی این ایده بسیار آسان است و قابلیت تعمیم مدل را به صورت معنا داری افزایش میدهد. در تصویر زیر تاثیر استفاده از MixUp بر روی دیتاست ImageNet را مشاهده میکنید.
این روش یک تکنیک Data Augmentation است که وابستگی به نوع داده ندارد و شما میتوانید از آن در زمان آموزش تمام مدلهای خود استفاده کنید.
ایدهی اصلی این روش بدین صورت است که شما دو کلاس مختلف را با یک ضریب مشخص با هم ترکیب میکنید. برای نمونه در تصویر زیر یک نمونه از کلاس Dog با یک نمونه از کلاس Cat ترکیب شده است.
این نمونهها به صورت تصادفی از مجموعه داده انتخاب میشوند. ترکیب آنها از نوع weighted linear interpolation است. در مرحله بعد نیاز است همین ترکیب با ضریب قبلی برای label های متناظر نیز انجام شود.
عبارت اول دو x را با ضریب λ با هم ترکیب میکند و عبارت دوم همین کار را برای دو y متناظر انجام میدهد. در این فرمول λ که همان ضریب ترکیب است به صورت تصادفی از توزیع احتمال Beta انتخاب میشود. توزیع Beta در بیشتر مواقع ۰ یا ۱ است. به این معنی که در اکثر مواقع دادهها تغییری نخواهند کرد. در مواقع دیگر نیز عددی بین ۰ و ۱ است که نسب ترکیب دو تصویر و برچسبهای متناظر آنها را مشخص میکند. توزیع Beta پارامتری به نام alpha که میزان انتخاب شدن عددی به جز صفر و یک را کنترل میکند.
همانطور که در تصویر مشخص است، هر چقدر 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()
نکته اول و مهم این است که این روش فقط برای تصاویر قابل استفاده نیست؛ بلکه میتوان در زمینههای دیگر نیز از آن استفاده کرد. برای مثال در حوزه NLP میتوان Embedding ها را با هم ترکیب کرد و ...
با استفاده از این روش Data Augmentation مدل شما بیش از اندازه در مورد رابطه ای بین Features ها و Label ها مطمئن نخواهد بود. برای درک بهتر این که چرا MixUp قابلیت تعمیم مدل را افزایش می دهد به تصویر زیر توجه کنید.
در سمت چپ شما دستهبندی عادی نمونهها را مشاهده میکنید. زمانی که نمونه جدید درون هر کدام از این حبابها قرار بگیرد مدل با اطمینان بالا پاسخ درست میدهد ولی اگر نمونهی جدید کمی بیرون از این حبابها باشد، مدل هیچ دانش و اطلاعات قبلی ای دربارهی این نمونه جدید ندارد و نمیتواند آن را به درستی دستهبندی کند.
در مقابل، در شکل سمت راست از MixUp در زمان آموزش استفاده شده است. این مدل متوجه شده است که هرچه از مرکز یک دسته دورتر شود احتمال آن کلاس کمتر خواهد بود. در واقع این مدل یک مرز تصمیمگیری احتمالاتی ایجاد کرده، نه صرفا یک خط که برای نمونههای خارج از آن خط هیچ پیشبینیای نداشته باشد.
در تصویر زیر نحوه آموزش مدل را مشاهده میکنید.
در این جا نمونه مشخص شده ۰/۶ برای کلاس قرمز و ۰/۴ برای کلاس سبز است. این عمل باعث میشود مدل فرا گیرد که کدام ویژگیها کلاسها را از هم متمایز میکند. این تکنیک مرزهای تصمیمگیری بین دستهها را نرمتر میکند و باعث میشود نمونههای جدید با دقت بیشتری دستهبندی شوند. این مرز تصمیمگیری Smooth به دلیل ترکیب کردن نمونهها ایجاد شده است.
نکته: این که فقط نمونههای یک دسته را با هم ترکیب کنیم هم روش خوبی است اما به اندازهی MixUp قابلیت Generalization را افزایش نخواهد داد. (مرز بین کلاسها را تغییر نمیدهد.)
تمام کدها همراه با خروجی در این 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 نشان دهنده میزان ترکیب دستههاست.
نکته: برای ایجاد تصاویر بالا از دو کلاس استفاده شده است. از نظر تئوری میتوانیم هر چند کلاس دلخواه را ترکیب کنیم. این کار باعث افزایش هزینهی محاسباتی میشود. همچنین در عمل نیز باعث افزایش چشمگیر عملکرد مدل نخواهد شد. برای اطلاعات بیشتر راجع به این موضع به مقاله اصلی مراجعه کنید.
در این قسمت یک مدل پایه ایجاد میکنیم.
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 بیشتری دارد؛ ترکیب نمونهها مساله را کمی سختتر کرده است. اما باعث شده که مدل خیلی زودتر به دقت بالای ۹۰ درصد روی دیتاست تست برسد. این نشاندهنده بالا بودن قدرت تعمیم در این روش است.
نویسندگان: سجاد ایوبی، بهار برادران افتخاری
منابع: