شبکه های عصبی کانوولوشن (Convolution Neural Networks- CNN) در Tensorflow


شبکه عصبی کانوولوشن (Convolution Neural Networks- CNN)
شبکه عصبی کانوولوشن (Convolution Neural Networks- CNN)

در این پست می خواهیم ابتدا مقدمه ای بر شبکه های عصبی کانوولوشن بیان کنیم. در این مقدمه فرض شده است شما با واژه هایی مانند يادگيري ماشين، يادگيري عميق، پردازش تصوير آشنا هستید. سپس برای یک پیاده سازی واقعی، مسئله Classification مجموعه داده آزمایشی MNIST را به زبان پایتون شرح دهیم. برای حل این مسئله از ابزار Tensorflow و امکاناتی که برای پیاده سازی شبکه های CNN بر روی GPU در اختیارمون گذاشته استفاده خواهیم کرد. CNNها از رايجترين روش­هاي يادگيري عميق در حل مسائل يادگيري ماشين مي­باشند و بویژه بطور گسترده در پردازش تصوير و ویدئو بکارگرفته شده­اند. CNN با ارتباطات محلي بين نورون­هاي لايه­هاي مجاور، همبستگي مکاني محلي ورودي را ا ستخراج مي­کند. معماري سه سطحي آن در شکل 1 نشان داده شده است

ساختار کلي شبکه عصبی کانولوشن   [1]
ساختار کلي شبکه عصبی کانولوشن [1]

در سطح اول لايه کانولوشن قرار دارد که با استفاده از kernel های متنوع مي­تواند ويژگي­ها جديدي را از تصوير استخراج کند. به دنبال آن عمليات Max pooling انجام مي­شود که وظيفه کاهش ابعاد و تعداد پارامترهاي شبکه را انجام مي­دهد. خروجي اين لايه بعد از تبديل به بردار يک بعدي به لايه شبکه اتصال کامل ارسال مي­شود. در اين لايه از الگوريتم­ها رايج شبکه­هاي عصبي استفاده مي­شود. بلاک convolution + max pooling که به عنوان لایهconvolution آن را میشناسیم می تواند به دفعات تکرار شود و شبکه ای عمیق تر ساخته شود. تعداد لایه های Fully connected نیز توسط کاربر تعیین می شود.

در سطح اول لايه کانولوشن قرار دارد که با استفاده از kernel های متنوع مي­تواند ويژگي­ها جديدي را از تصوير استخراج کند. به دنبال آن عمليات Max pooling انجام مي­شود که وظيفه کاهش ابعاد و تعداد پارامترهاي شبکه را انجام مي­دهد. خروجي اين لايه بعد از تبديل به بردار يک بعدي به لايه شبکه اتصال کامل ارسال مي­شود. در اين لايه از الگوريتم­ها رايج شبکه­هاي عصبي استفاده مي­شود. بلاک convolution + max pooling که به عنوان لایهconvolution آن را میشناسیم می تواند به دفعات تکرار شود و شبکه ای عمیق تر ساخته شود. تعداد لایه های Fully connected نیز توسط کاربر تعیین می شود.

شبکه های عصبی کانوولوشن در کاربردهاي مختلف پردازش تصوير مانند طبقه­بندي تصاوير، تشخيص اشياء و بازيابي محتوايي تصاوير کارايي خود را نشان داده است. در حل مسئله طبقه­بندي تصاوير براي مجموعه داده­هاي آزمايشي مانند Imagenet [2]، CIFAR [3] و MINST [4] به نتايج خوبي دست يافته است. در مسئله تشخيص اشياء نيز به عنوان مثال براي مجموعهPASCAL VOC به دقت­هاي بالایي رسيده است [5]. در بازيابي محتوايي تصاوير نيز به کارايي خوبي نسبت به روش­هاي کلاسيکي مانند ويژگي­هاي رنگ، شکل و بافت دست يافته است [6], [7].

تعداد بسيار زياد پارامترهاي قابل­يادگيري در CNN يکي از چالش­هاي آن مي­باشد. امروزه پياده­سازي محاسباتي آن بر روي GPU توسط چارچوب­هايي متن بازي مانند Tensorflow [8] و CNTK [9] که توسعه­دهنگان قوي از آن حمايت مي­کنند استفاده گسترده از CNN را امکانپذير نموده است و آن را به ابزار کاربردي و عملي در مطالعات پردازش تصوير و ويدئو تبديل کرده است.

بررسی یک کد واقعی بهترین روش برای تسلط بر برنامه نویسی و مفاهیم CNN می باشد. به این منظور من کد Classificationمجموعه داده آزمایشی MNIST به زبان پایتون و توسط Tensorflow را انتخاب کردم که در سایت ها و کتاب ها مختلف آموزشی شرح داده شده است. من چندین نوع از این کدها را بررسی کردم اما ساختار کد موجود در https://easy-tensorflow.com/tf-tutorials/convolutional-neural-nets-cnns/cnn1 را از همه بیشتر پسندیدم و می خواهم به بررسی آن بپردازم و آن را تکمیل کنم. البته لازم است شما کمی هم پایتون بلد باشید. ضمناً توابع Tensorflow استفاده شده را هم توضیح خواهیم داد.

# https://easy-tensorflow.com/tf-tutorials/convolutional-neural-nets-cnns/cnn1
 
 import tensorflow as tf
 import numpy as np
 import matplotlib.pyplot as plt
 import input_data

خب برای شروع کار این چندتا import را اول برنامه قرار می دهیم. برای راحتی خواندن و باز کردن و کار کردن با دیتاست MINST کتابخانه input_data در اختیار شما قرار گرفته است. input_data را می توانید از اینجا دانلود و به پروژه تون اضافه کنید.

توابع load_data و reformat برای خواندن داده ها از دیتاست MNIST و تغییر شکل آن برای اعمال به ورودی شبکه CNN می باشند. اگر دیتاست MNIST را تاکنون دانلود نکرده اید تابع read_data_sets اینکار را برای شما انجام میدهد. این تابع در اولین بار پوشه "MNIST_data" را ایجاد می کند و داده ها را با اسکریپت پایتون دانلود می کند (حجم آن حدود 11 مگابایت است) البته به شرطی که اتصال اینترنت برقرار باشد و سایت 'http://yann.lecun.com/exdb/mnist/'بسته نباشد (تا این لحظه بسته نیست). در دفعه های بعدی داده ها در پوشه ذکر شده وجود دارند و دیگر عملیات دانلود انجام نمی شود. ورودی one_hot در این تابع کدینگ one_hot را بر روی برچسب کلاس ها اعمال میکند.

def load_data(mode=&quottrain&quot):
  &quot&quot&quot
  Function to (download and) load the MNIST data
  :param mode: train or test
  :return: images and the corresponding labels
  &quot&quot&quot
  mnist = input_data.read_data_sets(&quotMNIST_data/&quot, one_hot=True)
  if mode == &quottrain&quot:
  x_train, lbl_train, x_valid, lbl_valid = mnist.train.images, mnist.train.labels,mnist.validation.images, mnist.validation.labels
  x_train, _ = reformat(x_train, lbl_train)
  x_valid, _ = reformat(x_valid, lbl_valid)
  return x_train, lbl_train, x_valid, lbl_valid
  elif mode == &quottest&quot:
  x_test, lbl_test = mnist.test.images, mnist.test.labels
  x_test, _ = reformat(x_test, lbl_test)
   return x_test, lbl_test
def reformat(x, lbl):
  &quot&quot&quot
  Reformats the data to the format acceptable for convolutional layers
  :param x: input array
  :param lbl: corresponding labels
  :return: reshaped input and labels
  &quot&quot&quot
  img_size, num_ch, num_class = int(np.sqrt(x.shape[-1])), 1, len(np.unique(np.argmax(lbl, 1)))
  dataset = x.reshape((-1, img_size, img_size, num_ch)).astype(np.float32)
  labels = (np.arange(num_class) == lbl[:, None]).astype(np.float32)
  return dataset, labels

این دیتا ست مجموعه تصاویر دستنویس 0 تا 9 به ابعاد 28 در 28 می باشد و از 55000 تصویر train، 5000 تصویر validation و 10000 تصویر تست تشکیل شده است. در داده های موجود، هر تصویر 28*28 به صورت یک آرایه یک بعدی به طول 784 ذخیره شده است. بنابراین لازم است توسط تابع reformat به صورت تنسور (-1, 28, 28, 1) تغییر شکل داده شود. خروجی labelsتابع reformatزمانی استفاده می شود که کدینگ one_hotمقدار Falseداشته باشد و در ادامه از این خروجی استفاده نمی کنیم.

فرمت ورودی یا همان data format در تابع covolution دو بعدی (conv2d) در تنسورفلو می تواند بر اساس فرمت های NHWC و NCHWباشد. N به معنی تعداد تنسورها ، H به معنی ارتفاع ، W به معنی پهنا و Cنشان دهنده تعداد کانال تنسورهای وروردی است. در صورت انتخاب NHWCتنسور ورودی باید به صورت [batch, height, width, channels]باشد که حالت پیش فرض تابع covolution دوبعدی می باشد. بعد از فرخوانی تابع reformat در تابع load_dataداده های x_train به شکل (55000, 28, 28, 1) در می آیند که اماده اعمال به تابع conv2d است. بدلیل اینکه تصاویر این دیتاست graylevel می باشند تعداد کانال ورودی، یک در نظر گرفته می شود.

برای تعریف شبکه CNN خود از توابع زیر استفاده می کنیم. بنابراین ابتدا آن ها را شرح می دهیم.

def weight_variable(shape):


    &quot&quot&quot
    Create a weight variable with appropriate initialization
    :param name: weight name
    :param shape: weight shape
    :return: initialized weight variable
    &quot&quot&quot
    initer = tf.truncated_normal_initializer(stddev=0.01)
    return tf.get_variable('W', shape=shape, dtype=tf.float32, initializer=initer)

def bias_variable(shape):
    &quot&quot&quot
    Create a bias variable with appropriate initialization
    :param name: bias variable name
    :param shape: bias variable shape
    :return: initialized bias variable
    &quot&quot&quot
    initial = tf.constant(0., shape=shape, dtype=tf.float32)
    return tf.get_variable('b', dtype=tf.float32, initializer=initial)
def conv_layer(x, filter_size, num_filters, stride, phase_train, name):
    &quot&quot&quot
    Create a 2D convolution layer
    :param x: input from previous layerphase_train
    :param filter_size: size of each filter
    :param num_filters: number of filters (or output feature maps)
    :param stride: filter stride
    :param name: layer name
    :return: The output array
    &quot&quot&quot
    with tf.variable_scope(name):
        num_in_channel = x.get_shape().as_list()[-1]
        shape = [filter_size, filter_size, num_in_channel, num_filters]
        W = weight_variable(shape)
        tf.summary.histogram('weight', W)
        b = bias_variable(shape=[num_filters])
        tf.summary.histogram('bias', b)
        layer = tf.nn.conv2d(x, W, strides=[1, stride, stride, 1], padding=&quotSAME&quot)
        layer += b
        bn_layer = tf.layers.batch_normalization(inputs=layer, training=phase_train)
        return tf.nn.relu(bn_layer)

تابع weight_variable(shape) : ماتریس وزن W را با مقداردهی تصادفی تعریف می کند. tf.get_variable در صورت عدم وجود متغیر W در scope خود، آن را تعریف می کند و در صورت وجود آن را بر میگرداند.

تابع bias_variable(shape): : ماتریس بایاس b را با مقداردهی تصادفی تعریف می کند.

تابع conv_layer(x, filter_size, num_filters, stride, phase_train, name) : عملیات conv2d با فراخوانی این تابع انجام می شود. پارامترهای این تابع به ترتیب: تنسور تصاویر ورودی، اندازه فیلتر کانولوشن یا همان اندازه ماتریس وزن W (یا همان کرنل کانولوشن)، گام کانولوشن، تعیین مرحله train یا تست و نام نود گراف تنسورفلو می باشد.

تنسور shape در این تابع ابعاد ماتریس وزن W (یا همان کرنل کانولوشن) را تعریف میکند. استاندار تنسورفلو برای تعریف آن به صورت [filter_height, filter_width, in_channels, out_channels] می باشد. یعنی بعد از عملیات کانولوشن تعداد کانال های خروجی out_channels می باشد. بعد از تعریف W و ماتریس b یا همان بایاس تابع conv2d را برای اجرای عملیات کانولوشن فراخوانی می کنیم. پارامتر stride، گام جابه جایی کرنل را در راستای افقی و عمودی را تعیین می کند. اگر data formatبرابر با NHWCباشد stride = [1, vertical stride, horizontal stride, 1] تعیین می شود. پارامتر padding می تواند مقادیر SAME یا VALID را بگیرد. مقدار SAME با اعمال zero paddingابعاد تصویر ورودی را بعد از عملیات کانولوشن تغییر نمی دهد. اما مقدار VALIDباعث down sample شدن تصویر ورودی می شود.

عملکرد تابع batch_normalization در اینجا توضیح داده شده است.

def max_pool(x, ksize, stride, name):
    &quot&quot&quot
    Create a max pooling layer
    :param x: input to max-pooling layer
    :param ksize: size of the max-pooling filter
    :param stride: stride of the max-pooling filter
    :param name: layer name
    :return: The output array
    &quot&quot&quot
    return tf.nn.max_pool(x,
                          ksize=[1, ksize, ksize, 1],
                          strides=[1, stride, stride, 1],
                          padding=&quotSAME&quot,
                          name=name)


def flatten_layer(layer):
    &quot&quot&quot
    Flattens the output of the convolutional layer to be fed into fully-connected layer
    :param layer: input array
    :return: flattened array
    &quot&quot&quot
    with tf.variable_scope('Flatten_layer'):
        layer_shape = layer.get_shape()
        num_features = layer_shape[1:4].num_elements()
        layer_flat = tf.reshape(layer, [-1, num_features])
    return layer_flat


def fc_layer(x, num_units, name, use_relu=True):
    &quot&quot&quot
    Create a fully-connected layer
    :param x: input from previous layer
    :param num_units: number of hidden units in the fully-connected layer
    :param name: layer name
    :param use_relu: boolean to add ReLU non-linearity (or not)
    :return: The output array
    &quot&quot&quot
    with tf.variable_scope(name):
        in_dim = x.get_shape()[1]
        W = weight_variable(shape=[in_dim, num_units])
        tf.summary.histogram('weight', W)
        b = bias_variable(shape=[num_units])
        tf.summary.histogram('bias', b)
        drop_x = tf.cond(phase_train, lambda: tf.nn.dropout(x, keep_prob=keep_prob), lambda: x)   # tf.cond or by keep_prob=1
        layer = tf.matmul(drop_x, W)
        layer += b
        if use_relu:
            layer = tf.nn.relu(layer)
        return layer


تابع max_pool(x, ksize, stride, name) : پارامترهای تابع max_poolشبیه پارامترهای تابع conv2d می باشد. با تغییر strideضریب downsampling را تنظیم میکنیم.

تابع flatten_layer(layer) : برای اتصال ویژگی های Spatialاستخراج شده توسط لایه های کانولوشن به لایه fully connectedلازم است ویژگی های Spatial بصورت خطی تغییر شکل یابند و به لایه fully connectedمتصل شوند. این عملیات توسط flatten_layerانجام می شود.

تابع fc_layer(x, num_units, name, use_relu=True) : برای تعریف لایه fully connected از آن استفاده می شود. عملیات dropout قبلا در اینجا توضیح داده شده است.

[1] Y. Guo, Y. Liu, A. Oerlemans, S. Lao, S. Wu, and M. S. Lew, “Deep learning for visual understanding: A review,” Neurocomputing, vol. 187, pp. 27–48, 2016.

[2] A. Krizhevsky, I. Sutskever, and G. E. Hinton, “ImageNet Classification with Deep Convolutional Neural Networks,” in Advances In Neural Information Processing Systems, 2012, pp. 1–9.

[3] K. He, X. Zhang, S. Ren, and J. Sun, “Deep Residual Learning for Image Recognition,” in 2016 IEEE Conference on Computer Vision and Pattern Recognition (CVPR), 2016, pp. 770–778.

[4] L. Wan, M. Zeiler, S. Zhang, Y. LeCun, and R. Fergus, “Regularization of neural networks using dropconnect,” Icml, no. 1, pp. 109–111, 2013.

[5] X. Wang, L. Zhang, L. Lin, Z. Liang, and W. Zuo, “Deep joint task learning for generic object extraction,” in Advances in Neural Information Processing Systems, 2014, pp. 523–531.

[6] Y. Liu, Y. Guo, S. Wu, and M. S. Lew, “Deepindex for accurate and efficient image retrieval,” in Proceedings of the 5th ACM on International Conference on Multimedia Retrieval, 2015, pp. 43–50.

[7] J. Wan et al., “Deep learning for content-based image retrieval: A comprehensive study,” in Proceedings of the 22nd ACM international conference on Multimedia, 2014, pp. 157–166.

[8] Martín Abadi, Ashish Agarwal, Paul Barham, Eugene Brevdo et al., “TensorFlow: Large-Scale Machine Learning on Heterogeneous Systems.” 2015.

[9] D. Yu et al., “An Introduction to Computational Networks and the Computational Network Toolkit,” Microsoft Research, Oct. 2014.