آشنایی با Autoencoder ها در Tensorflow برای حذف نویز

در پست قبلی به درک شبکه عصبی پیشخور پرداختیم. در این مقاله ، با خودرمزگذار ( Autoencoder ) ها در یادگیری عمیق آشنا خواهیم شد. ما به عنوان مثال یک پیاده سازی عملی از استفاده از یک خودرمزگذار حذف نویز در مجموعه داده های ارقام دست نویس MNIST را نشان خواهیم داد. علاوه بر این ، ما پیاده سازی خودرمزگذار در Tensorflow را انجام خواهیم داد.

۱- خودرمزگذار چیست؟

خودرمزگذار یا Autoencoder یک الگوریتم یادگیری ماشین بدون نظارت است که یک تصویر به عنوان ورودی دریافت می کند و با استفاده از تعداد بیت کمتر آن را بازسازی می کند. این کار ممکن است شبیه فشرده سازی تصویر به نظر برسد ، اما بزرگترین تفاوت بین الگوریتم های فشرده سازی تصاویر و خودرمزگذار ها این است که در مورد خودرمزگذار ها ، فشرده سازی با یادگیری مجموعه داده های آموزشی به دست می آید. در حالی که فشرده سازی معقول و منطقی زمانی حاصل می شود که تصویری مشابه مجموعه آموزشی استفاده شده باشد ، خودرمزگذار ها هر تصویری را فشرده سازی می کنند. فشرده سازی JPEG بسیار بهتر عمل خواهد کرد.

خودرمزگذار ها به تکنیک های کاهش ابعاد مانند تحلیل مولفه های اصلی شباهت دارند. آن ها فضایی را ایجاد می کنند که در آن قسمت های اساسی داده ها حفظ می شوند ، در حالی که قسمت های غیرضروری ( یا نویزی ) حذف می شوند.

یک خودرمزگذار از دو قسمت تشکیل شده است :

  1. رمزگذار : این بخشی از شبکه است که ورودی را به تعداد کمتری از بیت ها فشرده می کند. فضای نشان داده شده توسط این تعداد بیت کمتر “فضای پنهان” و نقطه حداکثر فشرده سازی “گلوگاه” نامیده می شود. این بیت های فشرده شده که نشانگر ورودی اصلی هستند ، “رمزگذاری” ورودی خوانده می شوند.
  2. رمزگشا : این بخشی از شبکه است که تصویر ورودی را با استفاده از رمزگذاری تصویر ، بازسازی می کند.

برای درک بهتر مفهوم نگاهی به مثال زیر خواهیم داشت.

خودرمزگذار دو لایه
خودرمزگذار دو لایه

در تصویر بالا ، ما یک Autoencoder ساده را نشان می دهیم ، یک خودرمزگذار ۲ لایه با یک لایه مخفی. لایه های ورودی و خروجی تعداد نورون های یکسان دارند. ما پنج مقدار واقعی را وارد خودرمزگذار می کنیم که توسط رمزگذار به سه مقدار واقعی در گلوگاه (لایه میانی) فشرده می شود. با استفاده از این سه مقدار واقعی ، رمزگشا می کوشد پنج مقدار واقعی را که به عنوان ورودی شبکه وارد کرده بودیم ، بازسازی کند.

در عمل ، تعداد بسیار بیشتری از لایه های مخفی بین ورودی و خروجی وجود دارد.

انواع مختلفی از خودرمزگذار ها وجود دارد مانند Sparse Autoencoder ، Variational Autoencoder و Denoising Autoencoder که در این مقاله ، ما با یک خودرمزگذار حذف نویز ( Denoising ) آشنا می شویم.

۲- خودرمزگذار حذف نویز

مثال حذف نویز با خودرمزگذار ها
مثال حذف نویز با خودرمزگذار ها

ایده خودرمزگذار حذف نویز ، یادگیری بازنمایی (فضای پنهان) است که در برابر نویز ایمن است. نویز را به یک تصویر اضافه می کنیم و سپس این تصویر نویزی را به عنوان ورودی به شبکه خود اعمال می کنیم. قسمت رمزگذار خودرمزگذار ، تصویر را به فضای دیگری تبدیل می کند که ارقام دست نویس را حفظ می کند اما نویز را از بین می برد. همانطور که در ادامه خواهیم دید ، ابعاد تصویر اصلی ۲۸x28x1 است و تصویر تبدیل شده ۷x7x32 است. شما می توانید تصویر ۷x7x32 را یک تصویر ۷×۷ با ۳۲ کانال رنگی در نظر بگیرید.

سپس قسمت رمزگشایی شبکه ، تصویر اصلی را از این تصویر ۷x7x32 بازسازی می کند و نویز از بین می رود!

این جادو چگونه اتفاق می افتد؟

در طول آموزش ، ما یک ضرر (تابع هزینه) را تعریف می کنیم تا تفاوت بین تصویر بازسازی شده و تصویر بدون نویز اصلی را به حداقل برسانیم. به عبارت دیگر ، ما یک فضای ۷x7x32 بُعدی را یاد می گیریم که بدون نویز است.

۳- پیاده سازی خودرمزگذار حذف نویز

۱-۳- شبکه

این تصاویر درواقع ماتریس هایی با اندازه ۲۸×۲۸ هستند. ما تصویر را به اندازه ۲۸x28x1 تغییر شکل می دهیم ، تصویر ماتریسی را که تغییر اندازه دادیم به یک آرایه تبدیل می کنیم ، روشنایی پیکسل ها را بین ۰ تا ۱ نرمال می کنیم و آن را به عنوان ورودی به شبکه وارد می کنیم. رمزگذار تصویر ۲۸x28x1 را به یک تصویر ۷x7x32 تبدیل می کند. شما می توانید این تصویر ۷x7x32 را به عنوان یک نقطه در فضای ۱۵۶۸ بُعدی (۷x7x32 =1568) در نظر بگیرید. به این فضای ۱۵۶۸ بُعدی ، “گلوگاه” یا فضای پنهان گفته می شود. معماری گرافیکی در زیر نشان داده شده است.

معماری خودرمزگذار حذف نویز
معماری رمزگذار حذف نویز

رمزگشا دقیقاً برعکس رمزگذار عمل می کند. یعنی بردار ۱۵۶۸ بُعدی را به یک تصویر ۲۸x28x1 تبدیل می کند. ما به این تصویر خروجی، “بازسازی” تصویر اصلی می گوییم. ساختار رمزگشایی در زیر نشان داده شده است.

معماری رمز گشا حذف نویز
معماری رمز گشا حذف نویز

بیایید با استفاده از tensorflow به پیاده سازی یک خودرمزگذار بپردازیم.

۲-۳- رمزگذار

رمزگذار دارای دو لایه کانولوشن و دو لایه حداکثر تجمع است. هر دو لایه Convolution-1 و Convolution layer-2 دارای ۳۲ فیلتر ۳×۳ هستند. دو لایه حداکثر تجمع با اندازه ۲×۲ وجود دارد.

encoder = Sequential([
    # convolution
    Conv2D(
        filters=32,
        kernel_size=(3,3),
        strides=(1,1),
        padding='SAME',
        use_bias=True,
        activation=lrelu,
        name='conv1'
    ),
    # the input size is 28x28x32
    MaxPooling2D(
        pool_size=(2,2),
        strides=(2,2),
        name='pool1'
    ),
    # the input size is 14x14x32
    Conv2D(
        filters=32,
        kernel_size=(3,3),
        strides=(1,1),
        padding='SAME',
        use_bias=True,
        activation=lrelu,
        name='conv2'
    ),
    # the input size is 14x14x32
    MaxPooling2D(
        pool_size=(2,2),
        strides=(2,2),
        name='encoding'
    )
    # the output size is 7x7x32
])

بلوک دیاگرام رمزگذار
بلوک دیاگرام رمزگذار

۳-۳- رمزگشا

رمزگشا دارای دو لایه Conv2d_transpose ، دو لایه Convolution و یک تابع فعالسازی Sigmoid است. Conv2d_transpose برای نمونه افزایی است که برخلاف یک لایه کانولوشن عمل می کند. لایه Conv2d_transpose هر بار استفاده از تصویر فشرده شده را دو مرتبه نمونه افزایی می کند.

decoder = Sequential([
    Conv2D(
        filters=32,
        kernel_size=(3,3),
        strides=(1,1),
        name='conv3',
        padding='SAME',
        use_bias=True,
        activation=lrelu
    ),
    # updampling, the input size is 7x7x32
    Conv2DTranspose(
        filters=32,
        kernel_size=3,
        padding='same',
        strides=2,
        name='upsample1'
    ),
    # upsampling, the input size is 14x14x32
    Conv2DTranspose(
        filters=32,
        kernel_size=3,
        padding='same',
        strides=2,
        name='upsample2'
    ),
    # the input size is 28x28x32
    Conv2D(
        filters=1,
        kernel_size=(3,3),
        strides=(1,1),
        name='logits',
        padding='SAME',
        use_bias=True
    )    
])

بلوک دیاگرام رمزگشا
بلوک دیاگرام رمزگشا

کلاس مدل رمزگذار-رمزگشای حاصل به شرح زیر است :

# model class definition
class EncoderDecoderModel(Model):
    def __init__(self, is_sigmoid=False):
        super(EncoderDecoderModel, self).__init__()
        # assign encoder sequence
        self._encoder = encoder
        # assign decoder sequence 
        self._decoder = decoder
        self._is_sigmoid = is_sigmoid
        
    # forward pass
    def call(self, x):
        x = self._encoder(x)
        decoded = self._decoder(x)
        if self._is_sigmoid:
            decoded = tf.keras.activations.sigmoid(decoded)
        return decoded

در نهایت ، ما با استفاده از تابع ضرر cross-entropy ، تلفات خروجی را محاسبه می کنیم و از بهینه ساز Adam برای بهینه سازی تابع ضرر خود استفاده می کنیم.

۴-۳- چرا ما به جای ReLU ازleaky ReLU  به عنوان تابع فعالسازی استفاده می کنیم؟

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

# define leaky ReLU function
def lrelu(x, alpha=0.1):
    return tf.math.maximum(alpha*x, x)

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

۵-۳- بارگذاری داده ها

پس از تعریف معماری ، داده های آموزش و اعتبارسنجی را بارگذاری می کنیم.

همانطور که در زیر نشان داده شده است ، Tensorflow به ما امکان می دهد تا داده های MNIST را به راحتی بارگذاری کنیم. داده های آموزشی و آزمایشی بارگذاری شده به ترتیب در متغیرهای train_imgs و test_imgs ذخیره می شوند. از آنجا که این کار بدون نظارت است ، نیازی برچسب گذاری نیست.

# load mnist dataset
(train_imgs, train_labels), (test_imgs, test_labels) = tf.keras.datasets.mnist.load_data()

# fit image pixel values from 0 to 1
train_imgs, test_imgs = train_imgs / 255.0, test_imgs / 255.0 

۶-۳- تجزیه و تحلیل داده ها

قبل از آموزش شبکه عصبی ، بهتر است همیشه داده ها را بررسی کنید. بیایید ببینیم چه داده هایی را در اختیار داریم. داده ها شامل اعداد دست نویس از ۰ تا ۹ به همراه برچسب های متناسب هستند. این مجموعه دارای ۵۵۰۰۰ نمونه آموزشی و ۱۰۰۰۰ نمونه آزمایشی است. هر نمونه ، تصویری با مقیاس خاکستری به ابعاد ۲۸ × ۲۸ است. اجازه دهید نگاهی به جزئیات داده ها داشته باشیم :

# check data array shapes:
print("Size of train images: {}, Number of train images: {}".format(train_imgs.shape[-2:], train_imgs.shape[0]))
print("Size of test images: {}, Number of test images: {}".format(test_imgs.shape[-2:], test_imgs.shape[0]))

خروجی:

Size of train images: (28, 28), Number of train images: 60000
Size of test images: (28, 28), Number of test images: 10000

نمایش تصاویر آموزشی و آزمایشی :

# plot image example from training images
plt.imshow(train_imgs[1], cmap='Greys')
plt.show()

# plot image example from test images
plt.imshow(test_imgs[0], cmap='Greys')
plt.show()
plt.close()

خروجی:

آموزش و تست تصاویر MNIST
آموزش و تست تصاویر MNIST

۷-۳- پیش پردازش داده ها

تصاویر در مقیاس خاکستری هستند و مقادیر پیکسل از ۰ تا ۲۵۵ است. ما پیش پردازش داده ها را قبل از ورود آن ها به شبکه انجام می دهیم.

۱٫ بُعد جدیدی را به تصاویر آموزشی و آزمایشی اضافه می کنیم ، تا به شبکه وارد شوند.

# prepare training reference images: add new dimension
train_imgs_data = train_imgs[..., tf.newaxis]

# prepare test reference images: add new dimension
test_imgs_data = test_imgs[..., tf.newaxis]

۲٫ نویز را هم به تصاویر آموزشی و هم به تصاویر آزمایشی اضافه می کنیم که سپس به شبکه وارد می کنیم. فاکتور نویز ، یک ابرپارامتر است و می توان آن را بطور متناسب تنظیم کرد.

# add noise to the images for train and test cases
def distort_image(input_imgs, noise_factor=0.5):
    noisy_imgs = input_imgs + noise_factor * np.random.normal(loc=0.0, scale=1.0, size=input_imgs.shape) 
    noisy_imgs = np.clip(noisy_imgs, 0., 1.)
    return noisy_imgs

# prepare distorted input data for training
train_noisy_imgs = distort_image(train_imgs_data)

# prepare distorted input data for evaluation
test_noisy_imgs = distort_image(test_imgs_data)

اجازه دهید تصاویر نویزی را نشان دهیم :

# plot distorted image example from training images
image_id_to_plot = 0
plt.imshow(tf.squeeze(train_noisy_imgs[image_id_to_plot]), cmap='Greys')
plt.title("The number is: {}".format(train_labels[image_id_to_plot]))
plt.show()

# plot distorted image example from test images
plt.imshow(tf.squeeze(test_noisy_imgs[image_id_to_plot]), cmap='Greys')
plt.title("The number is: {}".format(test_labels[image_id_to_plot]))
plt.show()
plt.close()

خروجی:

آموزش و تست تصاویر نویز دار MNIST
آموزش و تست تصاویر نویز دار MNIST

۸-۳- آموزش و ارزیابی مدل

شبکه آماده آموزش است. تعداد دوره ها را ۲۵ دوره با اندازه دسته ای ۶۴ تعیین می کنیم. یعنی در حالت کلی ، مجموعه داده ، ۲۵ بار وارد شبکه می شود. ما برای اعتبار سنجی از داده های آزمایشی استفاده خواهیم کرد.

# define custom target function for further minimization
def cost_function(labels=None, logits=None, name=None):
    loss = tf.nn.sigmoid_cross_entropy_with_logits(labels=labels, logits=logits, name=name)
    return tf.reduce_mean(loss)

# init the model
encoder_decoder_model = EncoderDecoderModel()

# training loop params
num_epochs = 25
batch_size_to_set = 64

# training process params
learning_rate = 1e-5
# default number of workers for training process
num_workers = 2

# initialize the training configurations such as optimizer, loss function and accuracy metrics
encoder_decoder_model.compile(optimizer=tf.compat.v1.train.AdamOptimizer(learning_rate=learning_rate),loss=cost_function,metrics=None)

results = encoder_decoder_model.fit(
    train_noisy_imgs,
    train_imgs_data,
    epochs=num_epochs,
    batch_size=batch_size_to_set,
    validation_data=(test_noisy_imgs, test_imgs_data),
    workers=num_workers,
    shuffle=True
)

بعد از ۲۵ دوره می توانیم تلفات آموزشی خود را ببینیم و همینطور تلفات اعتبارسنجی که بسیار کم است ، این بدان معنی است که شبکه ما عملکرد بسیار خوبی انجام داده است. حال بیایید نمودار تلفات روی داده های آموزشی و اعتبارسنجی را با استفاده از تابع مطلوبیت (plot_losses) معرفی کنیم :

۹-۳- مقایسه ضرر آموزش و اعتبارسنجی

ما تابع مطلوبیت را برای رسم تلفات تعریف کرده ایم :

# funstion for train and val losses visualizations
def plot_losses(results):
    plt.plot(results.history['loss'], 'bo', label='Training loss')
    plt.plot(results.history['val_loss'], 'r', label='Validation loss')
    plt.title('Training and validation loss',fontsize=14)
    plt.xlabel('Epochs ',fontsize=14)
    plt.ylabel('Loss',fontsize=14)
    plt.legend()
    plt.show()
    plt.close()

# visualize train and val losses
plot_losses(results)

نتیجه :

نمودار ضرر آموزش و ارزیابی
نمودار ضرر آموزش و ارزیابی

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

ما می توانیم اعتبار نتایج خود را با مشاهده تصاویر اصلی ، نویزی و بازسازی تصاویر آزمایشی ، افزایش دهیم.

۱۰-۳- نتیجه

تصاویر MNIST در مراحل مختلف
تصاویر MNIST در مراحل مختلف

از شکل های بالا ، می توانیم مشاهده کنیم که مدل ما، در حذف نویز تصاویر نویزی که در مدل خود قرار داده بودیم ، عملکرد خوبی داشته است.

بیشتر بخوانید :

منبع Learn OpenCV

درباره‌ی امیر اقتدائی

همچنین ببینید

آیا می خواهید در زمینه یادگیری ماشین استخدام شوید؟

آیا می خواهید در زمینه یادگیری ماشین استخدام شوید؟

مسیر های شغلی زیادی در حوزه یادگیری ماشین وجود دارد، اما از کجا بفهمیم که …

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *