GAN生成對抗網絡合集(三):InfoGAN和ACGAN-指定類別生成模擬樣本的GAN(附代碼)

1 InfoGAN-帶有隱含信息的GAN

       InfoGAN是一種把信息論與GAN相融合的神經網絡,能夠使網絡具有信息解讀功能。
       GAN的生成器在構建樣本時使用了任意的噪聲向量x’,並從低維的噪聲數據x’中還原出來高維的樣本數據。這說明數據x’中含有具有與樣本相同的特徵
       由於隨意使用的噪聲都能還原出高維樣本數據,表明噪聲中的特徵數據部分是與無用的數據部分高度地糾纏在一起的,即我們能夠知道噪聲中含有有用特徵,但無法知道哪些是有用特徵
       InfoGAN是GAN模型的一種改進,是一種能夠學習樣本中的關鍵維度信息的GAN,即對生成樣本的噪音進行了細化。先來看它的結構,相比對抗自編碼,InfoGAN的思路正好相反,InfoGAN是先固定標準高斯分佈作爲網絡輸入,再慢慢調整網絡輸出去匹配複雜樣本分佈

在這裏插入圖片描述
                                                                                                         圖3.1 InfoGAN模型

       如圖3.1所示,InfoGAN生成器是從標準高斯分佈中隨機採樣來作爲輸入,生成模擬樣本,解碼器是將生成器輸出的模擬樣本還原回生成器輸入的隨機數中的一部分,判別器是將樣本作爲輸入來區分真假樣本。
       InfoGAN的理論思想是將輸入的隨機標準高斯分佈當成噪音數據,並將噪音分爲兩類,第一類是不可壓縮的噪音Z,第二類是可解釋性的信息C。假設在一個樣本中,決定其本身的只有少量重要的維度,那麼大多數的維度是可以忽略的。而這裏的解碼器可以更形象地叫成重構器,即通過重構一部分輸入的特徵來確定與樣本互信息的那些維度。最終被找到的維度可以代替原始樣本的特徵(類似PCA算法中的主成份),實現降維、解耦的效果。

2 AC-GAN-帶有輔助分類信息的GAN

       AC-GAN(Auxiliary Classifier GAN),即在判別器discriminator中再輸出相應的分類概率,然後增加輸出的分類與真實分類的損失計算,使生成的模擬數據與其所屬的class一一對應。一般來講,AC-GAN可以屬於InfoGAN的一部分,class信息可以作爲InfoGAN中的潛在信息,只不過這部分信息可以使用半監督方式來學習。

在這裏插入圖片描述

在這裏插入圖片描述

在這裏插入圖片描述

3 代碼

       首先明確,GAN的代碼沒有目標檢測的複雜,以一個目標檢測程序demo的篇幅就涵蓋了GAN的數據輸入、訓練、定義網絡結構和參數、loss函數和優化器以及可視化部分。
       還可以學習到的是,GAN基本除開兩個大的網絡框架G和D以外,就是加各種約束(分類信息、隱含信息等)用以生成想要的數據
       下面是代碼實現學習MINST數據特徵,生成以假亂真的MNIST模擬樣本,並發現內部潛在的特徵信息。

在這裏插入圖片描述
代碼總綱

  1. 加載數據集;
  2. 定義G和D;
  3. 定義網絡模型的參數、輸入輸出、中間過程(經過G/D)的輸入輸出;
  4. 定義loss函數和優化器;
  5. 訓練和測試(套循環);
  6. 可視化

3.1 加載數據集、引入頭文件

       MNIST數據集下載到相應的地址,其加載方式是固定的。

# -*- coding: utf-8 -*-
##################################################################
#  1.引入頭文件並加載mnist數據
##################################################################
import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt
from scipy.stats import norm
import tensorflow.contrib.slim as slim

from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("/media/S318080208/py_pictures/minist/")  # ,one_hot=True)


tf.reset_default_graph()  # 用於清除默認圖形堆棧並重置全局默認圖形

3.2 定義G和D

  • 生成器G
    通過“兩個全連接+兩個反捲積(轉置卷積slim.conv2d_transpose)”模擬樣本的生成,每一層都有BN(批量歸一化)處理。
  • 判別器D
    判別器中有使用leaky_relu函數,其餘的在slim庫裏有,不用重新定義;
    判別器也是由“兩次卷積+兩次全連接”組成。生成的數據可以分別連接不同的輸出層產生不同的結果,其中1維的輸出層產生判別結果1或0,10維的輸出層產生分類結果,2維輸出層產生隱含維度信息。
##################################################################
#  2.定義生成器與判別器
##################################################################
def generator(x):  # 生成器函數 : 兩個全連接+兩個反捲積模擬樣本的生成,每一層都有BN(批量歸一化)處理
    reuse = len([t for t in tf.global_variables() if t.name.startswith('generator')]) > 0   # 確認該變量作用域沒有變量
    # print (x.get_shape())
    with tf.variable_scope('generator', reuse=reuse):
        x = slim.fully_connected(x, 1024)
        # print(x)
        x = slim.batch_norm(x, activation_fn=tf.nn.relu)
        x = slim.fully_connected(x, 7*7*128)
        x = slim.batch_norm(x, activation_fn=tf.nn.relu)
        x = tf.reshape(x, [-1, 7, 7, 128])
        # print ('22', tf.tensor.get_shape())
        x = slim.conv2d_transpose(x, 64, kernel_size=[4, 4], stride=2, activation_fn = None)
        # print ('gen',x.get_shape())
        x = slim.batch_norm(x, activation_fn=tf.nn.relu)
        z = slim.conv2d_transpose(x, 1, kernel_size=[4, 4], stride=2, activation_fn=tf.nn.sigmoid)
        # print ('genz',z.get_shape())
    return z


def leaky_relu(x):
     return tf.where(tf.greater(x, 0), x, 0.01 * x)


def discriminator(x, num_classes=10, num_cont=2):  # 判別器函數 : 兩次卷積,再接兩次全連接
    reuse = len([t for t in tf.global_variables() if t.name.startswith('discriminator')]) > 0
    # print (reuse)
    # print (x.get_shape())
    with tf.variable_scope('discriminator', reuse=reuse):
        x = tf.reshape(x, shape=[-1, 28, 28, 1])
        x = slim.conv2d(x, num_outputs=64, kernel_size=[4, 4], stride=2, activation_fn=leaky_relu)
        x = slim.conv2d(x, num_outputs=128, kernel_size=[4, 4], stride=2, activation_fn=leaky_relu)
        # print ("conv2d",x.get_shape())
        x = slim.flatten(x)  # 輸入扁平化
        shared_tensor = slim.fully_connected(x, num_outputs=1024, activation_fn=leaky_relu)
        recog_shared = slim.fully_connected(shared_tensor, num_outputs=128, activation_fn=leaky_relu)

        # 生成的數據可以分別連接不同的輸出層產生不同的結果
        # 1維的輸出層產生判別結果1或是0
        disc = slim.fully_connected(shared_tensor, num_outputs=1, activation_fn=None)
        disc = tf.squeeze(disc, -1)
        # print ("disc",disc.get_shape()) # 0 or 1

        # 10維的輸出層產生分類結果 (樣本標籤)
        recog_cat = slim.fully_connected(recog_shared, num_outputs=num_classes, activation_fn=None)

        # 2維輸出層產生重構造的隱含維度信息
        recog_cont = slim.fully_connected(recog_shared, num_outputs=num_cont, activation_fn=tf.nn.sigmoid)
    return disc, recog_cat, recog_cont

3.3 定義網絡模型 輸入/輸出/中間參數

       輸入進生成器的是兩個噪聲數據(一般噪聲隨機向量z_rand 38列 / 隱含信息約束z_con 2列)和分類標籤labels的one_hot編碼 10 列。生成模擬樣本,然後將模擬樣本gen和真實樣本x分別輸入到判別器中,生成判別結果dis_fake/樣本標籤class_fake/重構造的隱含信息con_fake 以及 dis_real/class_real/ _ 。

:隱含信息在這裏是指字體的粗細和傾斜信息。它不由我們控制,比如我想讓字體擁有這兩個信息的特徵生成,就給他們兩個隱含信息;如果沒有這種特徵生成,就多加幾個隱含信息,假如加10個隱含信息,看裏面有沒有能控制的,多餘的就當是隨機變量。如果再都沒有,就說明這個太複雜了,學習不了(個人理解)。

##################################################################
#  3.定義網絡模型 : 定義 參數/輸入/輸出/中間過程(經過G/D)的輸入輸出
##################################################################
batch_size = 10   # 獲取樣本的批次大小32
classes_dim = 10  # 10 classes
con_dim = 2       # 隱含信息變量的維度, 應節點爲z_con
rand_dim = 38     # 一般噪聲的維度, 應節點爲z_rand, 二者都是符合標準高斯分佈的隨機數。
n_input = 784     # 28 * 28


x = tf.placeholder(tf.float32, [None, n_input])     # x爲輸入真實圖片images
y = tf.placeholder(tf.int32, [None])                # y爲真實標籤labels

z_con = tf.random_normal((batch_size, con_dim))  # 2列
z_rand = tf.random_normal((batch_size, rand_dim))  # 38列
z = tf.concat(axis=1, values=[tf.one_hot(y, depth=classes_dim), z_con, z_rand])  # 50列 shape = (10, 50)
gen = generator(z)  # shape = (10, 28, 28, 1)
genout= tf.squeeze(gen, -1)  # shape = (10, 28, 28)


# labels for discriminator
y_real = tf.ones(batch_size)  # 真
y_fake = tf.zeros(batch_size)  # 假

# 判別器
disc_real, class_real, _ = discriminator(x)
disc_fake, class_fake, con_fake = discriminator(gen)
pred_class = tf.argmax(class_fake, dimension=1)

3.4 定義損失函數和優化器

       判別器D的損失函數有兩個:真實輸入的結果loss_d_r和模擬輸入的結果loss_d_f。二者結合爲loss_d;(輸入真實樣本,判別爲真/輸入模擬樣本,判別爲假)
       生成器G的損失函數是想要“以假亂真”,自己輸出的模擬數據,讓它在D中判別爲真,loss值爲loss_g;
       還要定義網絡中共有的loss值:真實的標籤與輸入模擬樣本判別出的標籤loss_cf、真實的標籤與輸入真實樣本判別的標籤loss_cr、隱含信息的重構誤差loss_con。
       之後用AdamOptimizer分別優化G和D。其中用了一個技巧,將D的學習率設小0.0001,將G的學習率設大0.001,可以讓G有更快的進化速度來模擬真實數據

##################################################################
#  4.定義損失函數和優化器
##################################################################
# 判別器 loss
loss_d_r = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=disc_real, labels=y_real))
loss_d_f = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=disc_fake, labels=y_fake))
loss_d = (loss_d_r + loss_d_f) / 2
# print ('loss_d', loss_d.get_shape())

# 生成器 loss
loss_g = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=disc_fake, labels=y_real))

# categorical factor loss 分類因素損失
loss_cf = tf.reduce_mean(tf.nn.sparse_softmax_cross_entropy_with_logits(logits=class_fake, labels=y))
loss_cr = tf.reduce_mean(tf.nn.sparse_softmax_cross_entropy_with_logits(logits=class_real, labels=y))
loss_c = (loss_cf + loss_cr) / 2


# continuous factor loss 隱含信息變量的損失
loss_con = tf.reduce_mean(tf.square(con_fake-z_con))

# 獲得各個網絡中各自的訓練參數列表
t_vars = tf.trainable_variables()
d_vars = [var for var in t_vars if 'discriminator' in var.name]
g_vars = [var for var in t_vars if 'generator' in var.name]

# 優化器
disc_global_step = tf.Variable(0, trainable=False)
gen_global_step = tf.Variable(0, trainable=False)

train_disc = tf.train.AdamOptimizer(0.0001).minimize(loss_d + loss_c + loss_con, var_list=d_vars, global_step=disc_global_step)
train_gen = tf.train.AdamOptimizer(0.001).minimize(loss_g + loss_c + loss_con, var_list=g_vars, global_step=gen_global_step)

       所謂的AC-GAN就是將 loss_cr 加入到 loss_c 中。如果沒有 loss_cr,令 loss_c = loss_c,對於網絡生成模擬數據是不影響的,但是會損失真實分類與模擬數據間的對應關係(未告知分類信息)(影響後果見可視化部分)。

3.5 訓練與測試

       建立 session,在循環裏使用 run 來運行前面構建的兩個優化器。測試部分分別使用 loss_d 和 loss_g 的 eval 完成。
       整個數據集運行3次後,判別誤差在0.5左右,基本可以認爲是對真假數據無法分辨。

##################################################################
#  5.訓練與測試
#  建立session,循環中使用run來運行兩個優化器
##################################################################
training_epochs = 3
display_step = 1

config = tf.ConfigProto()
config.gpu_options.per_process_gpu_memory_fraction = 0.4

with tf.Session(config=config) as sess:
    sess.run(tf.global_variables_initializer())
    
    for epoch in range(training_epochs):
        avg_cost = 0.
        total_batch = int(mnist.train.num_examples/batch_size)  # 5500

        # 遍歷全部數據集
        for i in range(total_batch):

            batch_xs, batch_ys = mnist.train.next_batch(batch_size)  # 取數據x:images, y:labels
            feeds = {x: batch_xs, y: batch_ys}

            # Fit training using batch data
            # 輸入數據,運行優化器
            l_disc, _, l_d_step = sess.run([loss_d, train_disc, disc_global_step], feeds)
            l_gen, _, l_g_step = sess.run([loss_g, train_gen, gen_global_step], feeds)

        # 顯示訓練中的詳細信息
        if epoch % display_step == 0:
            print("Epoch:", '%04d' % (epoch + 1), "cost=", "{:.9f} ".format(l_disc), l_gen)

    print("完成!")
    
    # 測試
    print("Result: loss_d = ", loss_d.eval({x: mnist.test.images[:batch_size], y: mnist.test.labels[:batch_size]}),
          "\n        loss_g = ", loss_g.eval({x: mnist.test.images[:batch_size], y: mnist.test.labels[:batch_size]}))

測試結果如下:

在這裏插入圖片描述

3.6 可視化

       可視化部分分爲兩部分,一部分是對原圖片和對應的模擬數據圖片進行plt。另一部分是利用隱含信息生成的模擬樣本圖片。

  • 第一部分
##################################################################
#  6.可視化
##################################################################
    # 根據圖片模擬生成圖片
    show_num = 10
    gensimple, d_class, inputx, inputy, con_out = sess.run(
        [genout, pred_class, x, y, con_fake], feed_dict={x: mnist.test.images[:batch_size], y: mnist.test.labels[:batch_size]})

    f, a = plt.subplots(2, 10, figsize=(10, 2))  # figure 1000*20 , 分爲10張子圖
    for i in range(show_num):
        a[0][i].imshow(np.reshape(inputx[i], (28, 28)))
        a[1][i].imshow(np.reshape(gensimple[i], (28, 28)))
        print("d_class", d_class[i], "inputy", inputy[i], "con_out", con_out[i])  # 輸出 判決預測種類/真實輸入種類/隱藏信息
        
    plt.draw()
    plt.show()

    # 將隱含信息分佈對應的圖片打印出來
    my_con = tf.placeholder(tf.float32, [batch_size, 2])
    myz = tf.concat(axis=1, values=[tf.one_hot(y, depth=classes_dim), my_con, z_rand])
    mygen = generator(myz)
    mygenout= tf.squeeze(mygen, -1) 
    
    my_con1 = np.ones([10, 2])
    a = np.linspace(0.0001, 0.99999, 10)
    y_input = np.ones([10])
    figure = np.zeros((28 * 10, 28 * 10))
    my_rand = tf.random_normal((10, rand_dim))
    for i in range(10):
        for j in range(10):
            my_con1[j][0] = a[i]
            my_con1[j][1] = a[j]
            y_input[j] = j
        mygenoutv = sess.run(mygenout, feed_dict={y: y_input, my_con: my_con1})
        for jj in range(10):
            digit = mygenoutv[jj].reshape(28, 28)
            figure[i * 28: (i + 1) * 28,
                   jj * 28: (jj + 1) * 28] = digit
    
    plt.figure(figsize=(10, 10))
    plt.imshow(figure, cmap='Greys_r')
    plt.show() 


得到的結果如下:

在這裏插入圖片描述
在這裏插入圖片描述

在這裏插入圖片描述
       可以看到前兩個結果是第一部分生成的,將原樣本與對應的模擬數據圖片的分類、預測分類、隱含信息打印出來;
       而最後一個結果是利用隱含信息生成的模擬樣本圖片,在整個【0,1】空間裏均勻抽樣,與樣本的標籤混合在一起,生成模擬數據。




       若去掉 loss_cf,只保留 loss_cr 約束:(直接不優化模擬數據的分類信息了,即我不努力了還不行麼)

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
若去掉loss_cr,只保留loss_cf約束(沒告訴什麼是對的。即分類分對了,但與本身生成的模擬數據沒啥關係)

在這裏插入圖片描述
在這裏插入圖片描述

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章