Tensorflow:简单GAN生成MNIST

本文深入探讨了生成对抗网络(GANs)的工作原理,通过生成器和辨别器的相互竞争,实现从随机噪声生成逼真的MNIST手写数字图像。介绍了GAN的基本结构、训练过程及优化策略,展示了训练过程中的生成器样本演化,以及如何使用保存的模型生成全新图像。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

GANs有两个网络,生成器G和辨别器D,两个网络相互竞争。生成器生成假数据传递给鉴别器。辨别器能接收真实数据,并预测接收到的数据是真实的还是虚假的。生成器被训练用来欺骗辨别器,它希望输出尽可能接近真实数据的数据。辨别器被训练来分辨哪些数据是真实的,哪些是假的。最终结果是,生成器生成的数据,无法让辨别器分辨出是假数据,达到以假乱真。

 GAN的一般结构如图所示,使用MNIST图像作为数据。潜在样本是一个随机向量,生成器用来构造假图像。当生成器通过训练学习时,它会找出如何将这些随机向量映射到可识别的图像上,从而欺骗辨别器。

数据加载

%matplotlib inline

import pickle as pkl
import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt

from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets('MNIST_data')

模型输入

首先,我们需要为图形创建输入。我们需要两个输入,一个用于辨别器,一个用于生成器。这里我们将声明辨别器输入 inputs_real和生成器输入inputs_z。我们将为每个网络分配适当的大小。

def model_inputs(real_dim, z_dim):
    inputs_real = tf.placeholder(tf.float32,(None,real_dim),name="input_real")
    inputs_z = tf.placeholder(tf.float32,(None,z_dim),name="input_z")
    
    return inputs_real, inputs_z

生成器和辨别器网络

Generator network

我们将构建生成网络。为了使这个网络成为一个通用的函数逼近器,我们至少需要一个隐藏层。我们应该使用一个Leaky ReLU来允许梯度不受阻碍地向后向后传递。Leaky ReLU与正常的ReLU类似,只是对于负的输入值有一个很小的非零输出。

Variable Scope

这里我们需要用到tf.variable_scope有两个原因。一,我们要确保所有的变量名都以generator开头,同样需要准备discriminator的变量。这将有助于我们以后训练单独的网络。

我们可以只使用tf.name_scope来设置名称,但是我们还希望使用不同的输入重用这些网络。对于生成器,我们不仅要训练它,还要在训练中和训练后对它进行采样。辨别器需要在真数据和假数据共享变量。可以使用reuse关键字的tf.variable_scope来告诉TensorFlow,如果我们再次构建图形,则重用变量而不是创建新变量。

Leaky ReLU

f(x)=max(a*x,x)

Tanh Output

生成器已经发现tanh可以作为最好的输出。这意味着我们必须将MNIST图像重新缩放到-1和1之间,而不是0和1之间。

def generator(z, out_dim, n_units=128, reuse=False,  alpha=0.01):
    ''' Build the generator network.
    
        Arguments
        ---------
        z : Input tensor for the generator
        out_dim : Shape of the generator output
        n_units : Number of units in hidden layer
        reuse : Reuse the variables with tf.variable_scope
        alpha : leak parameter for leaky ReLU
        
        Returns
        -------
        out, logits: 
    '''
    with tf.variable_scope('generator',reuse=reuse): # finish this
        # Hidden layer
        h1 = tf.layers.dense(z,n_units,activation=None)
        # Leaky ReLU
        h1 = tf.maximum(alpha*h1,h1)
        
        # Logits and tanh output
        logits = tf.layers.dense(h1,out_dim,activation=None)
        out = tf.tanh(logits)
        
        return out

Discriminator

辨别器网络与生成器网络几乎完全相同,只是我们使用的是sigmoid输出层。

def discriminator(x, n_units=128, reuse=False, alpha=0.01):
    ''' Build the discriminator network.
    
        Arguments
        ---------
        x : Input tensor for the discriminator
        n_units: Number of units in hidden layer
        reuse : Reuse the variables with tf.variable_scope
        alpha : leak parameter for leaky ReLU
        
        Returns
        -------
        out, logits: 
    '''
    with tf.variable_scope('discriminator',reuse=reuse): # finish this
        # Hidden layer
        h1 = tf.layers.dense(x,n_units,activation=None)
        # Leaky ReLU
        h1 = tf.maximum(alpha*h1,h1)
        
        logits = tf.layers.dense(h1,1,activation=None)
        out = tf.sigmoid(logits)
        
        return out, logits

参数

# Size of input image to discriminator
input_size = 784 # 28x28 MNIST images flattened
# Size of latent vector to generator
z_size = 100
# Sizes of hidden layers in generator and discriminator
g_hidden_size = 128
d_hidden_size = 128
# Leak factor for leaky ReLU
alpha = 0.01
# Label smoothing 
smooth = 0.1

构建网络

首先,获得我们的输入。model_inputs使用input和z的尺寸,得到input_real和input_z。

然后,我们将实现生成器,generator(input_z, input_size)。这将构建具有适当输入和输出大小的生成器。

然后构建辨别器。我们将构建两个,一个用于真实数据,另一个用于假数据。由于我们希望真实数据和假数据的权重相同,所以需要重用变量。对于假数据,我们将它作为g_model从生成器中获得。所以真数据辨别器是discriminator(input_real),而假辨别器是discriminator(g_model, reuse=True)。

tf.reset_default_graph()  #擦除之前定义的任何图,直接重置它
# Create our input placeholders
input_real, input_z = model_inputs(input_size,z_size)

# Generator network here
g_model = generator(input_z,input_size,n_units=g_hidden_size, alpha=alpha)
# g_model is the generator output

# Disriminator network here
d_model_real, d_logits_real = discriminator(input_real,n_units=d_hidden_size,alpha=alpha)
d_model_fake, d_logits_fake = discriminator(g_model,n_units=d_hidden_size,reuse=True,alpha=alpha)

生成器和辨别器loss

现在我们需要计算损失,这个有点麻烦。对于辨别器,总损失为真假图像的损失之和,d_loss = d_loss_real + d_loss_fake。损失将使用sigmoid cross-entropies。我们能得到 tf.nn.sigmoid_cross_entropy_with_logits.。我们也会把它用tf.reduce_mean包起来,获取批处理中所有图像的平均值。所以最后看起来像

tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=logits, labels=labels))

对于真实图像的logits,我们使用从上面的辨别器得到的d_logits_real。对于标签来说,我们想要它们全部都是1,因为它们都是真图像。为了使辨别器更好地泛化能力,标签从1.0减到0.9,例如使用参数smooth。这被称为平滑标签,通常与分类器一起使用,以提高性能。在tensorflow中,看起来像labels = tf.ones_like(tensor) * (1 - smooth)。

假数据的辨别器loss是一样的。logits是d_logits_fake,可以从生成器的输出传到辨别器的输入得到。这些假数据的logits与所有0的标签一起使用。记住,我们希望辨别器对真实图像输出1,对假图像输出0,所以我们需要设置损失来反映这一点。

最后,生成器的损失使用d_logits_fake,假图像的logits。但是,现在标签全部都为1。生成器试图欺骗辨别器,所以它想要辨别器输出假图像。

# Calculate losses
d_loss_real = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=d_logits_real,
                                                                   labels=tf.ones_like(d_logits_real) * (1-smooth)))

d_loss_fake = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=d_logits_fake,
                                                                   labels=tf.zeros_like(d_logits_real)))

d_loss = d_loss_real + d_loss_fake

g_loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=d_logits_fake,
                                                              labels=tf.ones_like(d_logits_fake)))

Optimizer

我们想分别更新生成器和辨别器的变量。因此,我们需要为每个部分获取变量,并为这两个部分构建optimizer。为了得到所有可训练变量,我们使用 tf.trainable_variables()。这将创建一个列表,其中包含我们在图中定义的所有变量。

对于生成器optimizer,我们只想要生成器变量。我们只需要遍历列表tf.trainable_variables(),保留开始时为generator的变量。每个变量目标有一个name的属性,name将变量的名称保存为字符串(例如var.name == 'weights_0')。

我们可以对辨别器做类似的事情。辨别器中的所有变量都以discriminator开始。然后,在optimizer中,我们将变量列表传递给minimize方法的var_list关键字参数。这告诉optimizier只更新列出的变量。像tf.train.AdamOptimizer().minimize(loss, var_list=var_list),只训练var_list中的变量。

# Optimizers
learning_rate = 0.002

# Get the trainable_variables, split into G and D parts
t_vars = tf.trainable_variables()
g_vars = [var for var in t_vars if var.name.startswith("generator")]
d_vars = [var for var in t_vars if var.name.startswith("discriminator")] 

d_train_opt = tf.train.AdamOptimizer(learning_rate).minimize(d_loss,var_list=d_vars)
g_train_opt = tf.train.AdamOptimizer(learning_rate).minimize(g_loss,var_list=g_vars)

训练

batch_size = 100
epochs = 100
samples = []
losses = []
saver = tf.train.Saver(var_list = g_vars)
with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    for e in range(epochs):
        for ii in range(mnist.train.num_examples//batch_size):
            batch = mnist.train.next_batch(batch_size)
            
            # Get images, reshape and rescale to pass to D
            batch_images = batch[0].reshape((batch_size, 784))
            batch_images = batch_images*2 - 1        #图像缩放-1到1
            
            # Sample random noise for G
            batch_z = np.random.uniform(-1, 1, size=(batch_size, z_size))   #生成器本征向量
            
            # Run optimizers
            _ = sess.run(d_train_opt, feed_dict={input_real: batch_images, input_z: batch_z})
            _ = sess.run(g_train_opt, feed_dict={input_z: batch_z})
        
        # At the end of each epoch, get the losses and print them out
        train_loss_d = sess.run(d_loss, {input_z: batch_z, input_real: batch_images})
        train_loss_g = g_loss.eval({input_z: batch_z})
            
        print("Epoch {}/{}...".format(e+1, epochs),
              "Discriminator Loss: {:.4f}...".format(train_loss_d),
              "Generator Loss: {:.4f}".format(train_loss_g))    
        # Save losses to view after training
        losses.append((train_loss_d, train_loss_g))
        
        # Sample from generator as we're training for viewing afterwards
        sample_z = np.random.uniform(-1, 1, size=(16, z_size))
        gen_samples = sess.run(
                       generator(input_z, input_size, reuse=True),
                       feed_dict={input_z: sample_z})
        samples.append(gen_samples)
        saver.save(sess, './checkpoints/generator.ckpt')

# Save training generator samples
with open('train_samples.pkl', 'wb') as f:
    pkl.dump(samples, f)

训练loss

%matplotlib inline

import matplotlib.pyplot as plt
fig, ax = plt.subplots()
losses = np.array(losses)
plt.plot(losses.T[0], label='Discriminator')
plt.plot(losses.T[1], label='Generator')
plt.title("Training Losses")
plt.legend()

训练生成器样本

def view_samples(epoch, samples):
    fig, axes = plt.subplots(figsize=(7,7), nrows=4, ncols=4, sharey=True, sharex=True)
    for ax, img in zip(axes.flatten(), samples[epoch]):
        ax.xaxis.set_visible(False)
        ax.yaxis.set_visible(False)
        im = ax.imshow(img.reshape((28,28)), cmap='Greys_r')
    
    return fig, axes
# Load samples from generator taken while training
with open('train_samples.pkl', 'rb') as f:
    samples = pkl.load(f)
_ = view_samples(-1, samples)

这些是最后一个训练阶段的样本。您可以看到生成器能够重新生成像5、7、3、0、9这样的数字。因为这只是一个示例,所以它不能代表这个生成器所能生成的全部图像。

rows, cols = 10, 6
fig, axes = plt.subplots(figsize=(7,12), nrows=rows, ncols=cols, sharex=True, sharey=True)

for sample, ax_row in zip(samples[::int(len(samples)/rows)], axes):
    for img, ax in zip(sample[::int(len(sample)/cols)], ax_row):
        ax.imshow(img.reshape((28,28)), cmap='Greys_r')
        ax.xaxis.set_visible(False)
        ax.yaxis.set_visible(False)

一开始都是噪音。然后它学会只把中间变成白色,其余的变成黑色。你可以开始看到一些像数字一样的结构出现在噪音中。看起来像1 9 8先出现。然后,它学习5和3。

生成器采样

我们还可以使用训练后保存的检查点从生成器中获得全新的图像。

saver = tf.train.Saver(var_list=g_vars)
with tf.Session() as sess:
    saver.restore(sess, tf.train.latest_checkpoint('checkpoints'))
    sample_z = np.random.uniform(-1, 1, size=(16, z_size))
    gen_samples = sess.run(
                   generator(input_z, input_size, reuse=True),
                   feed_dict={input_z: sample_z})
view_samples(0, [gen_samples])

 GAN 的结果非常不错,但仍然有很多噪声图像,以及有些图像看起来不太像数字。但是,要让生成器生成的图像与 MNIST 数据集几乎一样,是完全可能的。与大多数神经网络一样,使用的层越多,网络的性能就越好。所以可以尝试两三层,而不只是一层。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值