یادگیری نیمه نظارتی و شبکههای مولد تخاصمی (GAN)
وینسنت ونگوگ Vincent Van Gogh در سال 1889 نقاشی زیبایی به نام «شب پرستاره» را کشید. مدل شبکههای مولد تخاصمی من (که نام آن را گَن گوگ گذاشتهام) نیز توانست به کمک دادههای دیتاست MNIST که تنها 20% آنها برچسب داشتند، یک نقاشی بکشد. اما این مدل چگونه توانست به چنین موفقیتی دست یابد؟ در ادامه مقاله با من همراه باشید تا عامل این موفقیت را به شما بگویم.
یادگیری نیمه نظارتی Semi-supervised learning چیست؟
اغلب طبقهبندهای مبتنی بر یادگیری عمیق برای تعمیم نتایج خود به حجم زیادی از نمونههای برچسبدار نیاز دارند. اما فرآیند به دست آوردن چنین دادههای بسیار دشوار و هزینهبر است. یادگیری نیمه نظارتی با هدف فائق آمدن بر این محدودیتها به وجود آمد. این الگوریتم مجموعهای از تکنیکهاست که برای یادگیری نیاز به حجم زیادی داده بدون برچسب و مقدار کمی داده برچسبدار دارد. براساس مطالعات انجام گرفته در حوزه یادگیری ماشین، زمانی که در کنار مقدار کمی دادههای برچسبدار از دادههای بدون برچسب نیز استفاده کنیم، شاهد افزایش چشمگیر دقت فرآیند یادگیری خواهیم بود. شبکههای مولد تخاصمی (GAN) در حوزه یادگیری نیمه نظارتی پتانسیل و قابلیتهای زیادی دارند، زیرا این شبکهها، تنها با داشتن مقداری داده برچسبدار میتوانند عملکرد خوبی از خود ارائه دهند.
پیشینه شبکههای مولد تخاصمی (GAN)
شبکههای GAN یکی از انواع مدلهای عمیق مولد هستند. دلیل اصلی جذابیت این شبکه این است که توزیع احتمال در فضای دادهای را بهطور واضح نشان نمیدهد. بلکه، با تهیه چند نمونه از این توزیع احتمال، به صورت غیرمستقیم با آن تعامل میکند.
ایده اصلی شبکه مولد تخاصمی فراهم آوردن مقدمات بازی بین دو بازیکن است:
- مولد Generator G: مولد با دریافت یک نویز تصادفی به نام z به عنوان ورودی، یک تصویر x به عنوان خروجی تحویل میدهد. پارامترهای این مولد به نحوی با تنظیم میشوند که بتوانند برای تصویر جعلی که تولید کردهاند، امتیاز بالایی از الگوریتم تفکیککننده دریافت کنند.
- تفکیک کننده Discriminator D: تفکیککننده یا ابزار تفکیک تصویر x را به عنوان ورودی دریافت میکند و خروجی آن امتیازی است که میزان شباهت تصویر جعلی با تصویر واقعی را نشان میدهد. پارامترهای ابزار تفکیک به نحوی تنظیم شدهاند که وقتی مولد یک تصویر واقعی را به آنها میدهد، امتیاز بالایی دریافت کند و زمانی که تصویر داده شده جعلی باشد، امتیاز مولد پایین بیاید.
پیشنهاد میکنم برای جزئیات بیشتر در این خصوص به مقالات «An introduction to Generative Adversarial Networks (with code in TensorFlow)» و «Overview of GANs (Generative Adversarial Networks) – Part I» مراجعه نمایید. درادامه به بررسی یکی از مهمترین کاربردهای شبکه مولد تخاصمی یعنی یادگیری نیمه نظارتی خواهیم پرداخت.
[irp posts=”16210″]مبانی یادگیری نیمه نظارتی
تفکیک کننده با معماری وانیلا برای جداسازی و طبقهبندی احتمالات R/F تنها یک نرون خروج دارد. ما هر دو شبکه (مولد و تفکیک کننده) را بهطور همزمان آموزش میدهیم، اما تفکیککننده را پس از آموزش دادن کنار میگذاریم، زیرا تنها کاربرد آن بهبود عملکرد مولد است.
تفکیک کننده در یادگیری نیمه نظارتی، علاوه بر نورون R/F، 10 نورون دیگر نیز برای طبقهبندی ارقام MNIST دارد. به علاوه، در این مرحله نقش تفکیک کننده و مولد تغییر میکند و ما میتوانیم مولد را پس از آموزش کنار بگذاریم، زیرا تنها هدف آن تولید تفکیک کننده در یادگیری نیمه نظارتی، علاوه بر نورون R/F، 10 نورون دیگر نیز برای طبقهبندی ارقام MNIST دارد. به علاوه، در این مرحله نقش تفکیک کننده و مولد تغییر میکند و ما میتوانیم مولد را پس از آموزش کنار بگذاریم، زیرا تنها هدف آن تولید دادههای بدون برچسب برای بهبود عملکرد تفکیک کننده است.
تفکیک کننده در این مرحله به یک جداساز با 11 کلاس تبدیل میشود که یکی از این نورونها (نورون R/F) نشاندهنده خروجی دادههای جعلی است. 10 کلاس دیگر نیز بیانگر دادههای واقعی هستند. موارد زیر را باید بهخاطر بسپارید:
- زمانی که دیتاست داده واقعی و نظارت نشده به مدل بدهد، برچسب نورون خروجی R/F «صفر» است.
- زمانی که مولد داده جعلی و نظارتنشده به مدل بدهد، برچسب نورون خروجی R/F عدد «1» خواهد بود.
- زمانی که داده واقعی و نظارتشده به مدل داده شود، برچسب خروجی R/F «صفر» و خروجی برچسب متناظر با آن «1» خواهد بود.
اگر به جای دادن مقدار کمی داده برچسبدار، منابع مختلف دادهای را با یکدیگر ترکیب کنیم، تفکیککننده میتواند طبقهبندی و جداسازی را با دقت بیشتری انجام دهد.
معماری
حال زمان آن است به بحث کدنویسی بپردازیم.
تفکیک کننده
معماری که در اینجا شرح دادیم، شباهت زیادی به معماری تعریفشده در مقاله «UNSUPERVISED REPRESENTATION LEARNING WITH DEEP CONVOLUTIONAL GENERATIVE ADVERSARIAL NETWORKS» دارد. در اینجا برای کاهش ابعاد بردارهای ویژگی به جای استفاده از لایههای ادغام، از پیچشهای قدم برداشته strided convolutions استفاده میکنیم و توابع leaky_relu ،dropout و BN را برای همه لایهها اجرا میکنیم تا فرآیند یادگیری تثبیت شود. باید توجه کنید که تابع BN برای لایه ورودی و آخرین لایه اعمال نمیشود (هدف از این کار منطبق کردن ویژگیها با یکدیگر است). در آخر نیز از روش Global Average Pooling، میانگین تمامی ابعاد فضایی بردارهای ویژگی را به دست میآوریم. با اینکر ابعاد تنسور را در یک مقدار واحد خلاصه میکنیم. پس از مسطح کردن ویژگیها، از یک لایه متراکم متشکل از 11 کلاس و یک تابع فعالسازی بیشینه نرم softmax activation استفاده میکنیم تا یک خروجی چند کلاسی به دست بیاوریم.
def discriminator(x, dropout_rate = 0., is_training = True, reuse = False): # input x -> n+1 classes with tf.variable_scope('Discriminator', reuse = reuse): # x = ?*64*64*1 #Layer 1 conv1 = tf.layers.conv2d(x, 128, kernel_size = [4,4], strides = [2,2], padding = 'same', activation = tf.nn.leaky_relu, name = 'conv1') # ?*32*32*128 #No batch-norm for input layer dropout1 = tf.nn.dropout(conv1, dropout_rate) #Layer2 conv2 = tf.layers.conv2d(dropout1, 256, kernel_size = [4,4], strides = [2,2], padding = 'same', activation = tf.nn.leaky_relu, name = 'conv2') # ?*16*16*256 batch2 = tf.layers.batch_normalization(conv2, training = is_training) dropout2 = tf.nn.dropout(batch2, dropout_rate) #Layer3 conv3 = tf.layers.conv2d(dropout2, 512, kernel_size = [4,4], strides = [4,4], padding = 'same', activation = tf.nn.leaky_relu, name = 'conv3') # ?*4*4*512 batch3 = tf.layers.batch_normalization(conv3, training = is_training) dropout3 = tf.nn.dropout(batch3, dropout_rate) # Layer 4 conv4 = tf.layers.conv2d(dropout3, 1024, kernel_size=[3,3], strides=[1,1], padding='valid',activation = tf.nn.leaky_relu, name='conv4') # ?*2*2*1024 # No batch-norm as this layer's op will be used in feature matching loss # No dropout as feature matching needs to be definite on logits # Layer 5 # Note: Applying Global average pooling flatten = tf.reduce_mean(conv4, axis = [1,2]) logits_D = tf.layers.dense(flatten, (1 + num_classes)) out_D = tf.nn.softmax(logits_D) return flatten,logits_D,out_D[irp posts=”6387″]
مولد
معماری مولد به نحوی طراحی شده که خروجیهای فضایی تفکیک کننده را بازنمایی کند. ما از لایههای پیچشی قدم برداشته
Fractional strided convolutions استفاده میکنیم تا بعد فضایی ارائه را افزایش دهیم. ورودی که به مولد داده میشود یک تنسور 4-D از نویز z است که H پیچشهای ترانهاده transposed convolutions و توابع relu ،BN (غیر از لایه خروجی) و dropout روی آن اعمال میشوند. درنهایت، تصویر خروجی توسط تابع فعالسازی tanh در بازه (-1,1) نگاشته میشود.
def generator(z, dropout_rate = 0., is_training = True, reuse = False): # input latent z -> image x with tf.variable_scope('Generator', reuse = reuse): #Layer 1 deconv1 = tf.layers.conv2d_transpose(z, 512, kernel_size = [4,4], strides = [1,1], padding = 'valid', activation = tf.nn.relu, name = 'deconv1') # ?*4*4*512 batch1 = tf.layers.batch_normalization(deconv1, training = is_training) dropout1 = tf.nn.dropout(batch1, dropout_rate) #Layer 2 deconv2 = tf.layers.conv2d_transpose(dropout1, 256, kernel_size = [4,4], strides = [4,4], padding = 'same', activation = tf.nn.relu, name = 'deconv2')# ?*16*16*256 batch2 = tf.layers.batch_normalization(deconv2, training = is_training) dropout2 = tf.nn.dropout(batch2, dropout_rate) #Layer 3 deconv3 = tf.layers.conv2d_transpose(dropout2, 128, kernel_size = [4,4], strides = [2,2], padding = 'same', activation = tf.nn.relu, name = 'deconv3')# ?*32*32*256 batch3 = tf.layers.batch_normalization(deconv3, training = is_training) dropout3 = tf.nn.dropout(batch3, dropout_rate) #Output layer
زیان مدل
ما در ابتدا با دادن مقدار صفر به برچسبهای واقعی یک برچسب را به تمام دسته تعمیم دادیم. هدف این بود که پس از دادن دادهها به مدل، خروجی نورون R/F صفر شود. تابع زیان تفکیک کننده برای دادههای بدون برچسب را میتوان یک تابع زیان سیگموئید دودویی binary sigmoid loss درنظر گرفت که در آن خروجی نورون R/F برای تصاویر جعلی 1 و برای تصاویر واقعی صفر است.
### Discriminator loss ### # Supervised loss -> which class the real data belongs to temp = tf.nn.softmax_cross_entropy_with_logits_v2(logits = D_real_logit, labels = extended_label) # Labeled_mask and temp are of same size = batch_size where temp is softmax cross_entropy calculated over whole batch D_L_Supervised = tf.reduce_sum(tf.multiply(temp,labeled_mask)) / tf.reduce_sum(labeled_mask) # Multiplying temp with labeled_mask gives supervised loss on labeled_mask # data only, calculating mean by dividing by no of labeled samples # Unsupervised loss -> R/F D_L_RealUnsupervised = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits( logits = D_real_logit[:, 0], labels = tf.zeros_like(D_real_logit[:, 0], dtype=tf.float32))) D_L_FakeUnsupervised = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits( logits = D_fake_logit[:, 0], labels = tf.ones_like(D_fake_logit[:, 0], dtype=tf.float32))) D_L = D_L_Supervised + D_L_RealUnsupervised + D_L_FakeUnsupervised
تابع زیان مولد ترکیبی است از تابع زیان تصویر جعلی و تابع زیان تطبیق ویژگی. هدف تابع زیان تصویر جعلی fake_image loss
گرفتن یک خروجی جعلی با مقدار صفر از نورون R/F است. تابع زیان تطبیق ویژگی feature matching loss نیز میانگین قدرمطلق خطای بین مقدار متوسط مجموعهای از ویژگیهای دادههای آموزشی و مقدار متوسط همان ویژگیها در دادههای تولید شده را کاهش میدهد.
آموزش
### Generator loss ### # G_L_1 -> Fake data wanna be real G_L_1 = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits( logits = D_fake_logit[:, 0],labels = tf.zeros_like(D_fake_logit[:, 0], dtype=tf.float32)))
ما در اینجا سایز تصاویر آموزشی را از [batch_size, 28 ,28 , 1] به [batch_size, 64, 64, 1] تغییر دادیم تا مناسب معماری مولد و ابزار تفکیک شوند. سپس توابع زیان، دقت الگوریتمها و نمونههای تولید شده برآورد میشوند و به منظور بهبود و پیشرفت طی هر دوره تحت بررسی قرار میگیرند.
for epoch in range(epochs): train_accuracies, train_D_losses, train_G_losses = [], [], [] for it in range(no_of_batches): batch = mnist_data.train.next_batch(batch_size, shuffle = False) # batch[0] has shape: batch_size*28*28*1 batch_reshaped = tf.image.resize_images(batch[0], [64, 64]).eval() # Reshaping the whole batch into batch_size*64*64*1 for disc/gen architecture batch_z = np.random.normal(0, 1, (batch_size, 1, 1, latent)) mask = get_labeled_mask(labeled_rate, batch_size) train_feed_dict = {x : scale(batch_reshaped), z : batch_z, label : batch[1], labeled_mask : mask, dropout_rate : 0.7, is_training : True} #The label provided in dict are one hot encoded in 10 classes D_optimizer.run(feed_dict = train_feed_dict) G_optimizer.run(feed_dict = train_feed_dict) train_D_loss = D_L.eval(feed_dict = train_feed_dict) train_G_loss = G_L.eval(feed_dict = train_feed_dict) train_accuracy = accuracy.eval(feed_dict = train_feed_dict) train_D_losses.append(train_D_loss) train_G_losses.append(train_G_loss) train_accuracies.append(train_accuracy) tr_GL = np.mean(train_G_losses) tr_DL = np.mean(train_D_losses) tr_acc = np.mean(train_accuracies) print ('After epoch: '+ str(epoch+1) + ' Generator loss: ' + str(tr_GL) + ' Discriminator loss: ' + str(tr_DL) + ' Accuracy: ' + str(tr_acc)) gen_samples = fake_data.eval(feed_dict = {z : np.random.normal(0, 1, (25, 1, 1, latent)), dropout_rate : 0.7, is_training : False}) # Dont train batch-norm while plotting => is_training = False
نتیجهگیری
باتوجه به محدودیت GPU، آموزش 5 دورهای این مدل با استفاده از دادههایی انجام گرفت که تنها 20% آنها برچسب داشتند. برای کسب نتایج بهتر و دقیقتر پیشنهاد میکنم که دورههای آموزشی را افزایش داده و نرخ برچسبدار بودن دادهها را پایین بیاورید. برای تکمیل کدهای مربوطه میتوانید به این لینک مراجعه نمایید.
در حوزه هوش مصنوعی عمومی (AGI) یادگیری بدون نظارت یک نقطه مبهم است. شبکههای مولد تخاصمی برای رفع این ابهامات آمدهاند. این شبکههای مولد تخاصمی راهحل مناسبی برای مسائل پیچیده یادگیری با دادههای بدون برچسب هستند. با توجه به ارائه روشهای جدید در حوزه یادگیری نیمه نظارتی و بدون نظارت، انتظار میرود که این ابهامات روزبهروز برای ما روشنتر شوند. باید خاطرنشان کنم که ایده این مدل را از مقاله زیبای « Semi-supervised learning with Generative Adversarial Networks (GANs) » و دستیار یکی از همکارانم الهام گرفتهام.