自动编码器是一种学习有效编码输入数据的神经网络。给定一些输入,神经网络首先使用一系列变换将数据映射到低维空间,神经网络的这一部分称为编码器。然后,网络使用编码的低维数据来尝试重建输入。网络的这一部分称为解码器。我们可以使用编码器将数据压缩成神经网络可以理解的类型。然而,自动编码器很少用于此目的,因为通常有比它们更有效的手写算法(例如jpg压缩)。此外,自动编码器通常用于执行降噪任务,可以学习如何重建原始图像。什么是变分自编码器?有许多与自动编码器相关的有趣应用。其中之一称为变分自动编码器。使用变分自动编码器不仅可以压缩数据,还可以生成自动编码器之前遇到过的新对象。使用通用自动编码器时,我们根本不知道网络生成的编码到底是什么。虽然我们可以比较不同的编码对象,但几乎不可能理解它的内部编码方式。这也意味着我们不能使用编码器来生成新对象。我们甚至不知道输入应该是什么样子。而我们以相反的方式使用变分自动编码器。我们不试图去关注隐藏向量的分布,我们只是告诉网络我们希望这个分布变换什么。通常,我们限制网络生成具有单位正态分布属性的隐藏向量。然后,当尝试生成数据时,我们只是从这个分布中采样并将样本提供给解码器,解码器返回看起来与我们用来训练网络的对象完全一样的新对象。下面我们将展示如何使用Python和TensorFlow来做到这一点,我们将教我们的网络绘制MNIST字符。第一步是加载训练数据。首先,让我们执行一些基本的导入操作。TensorFlow有非常方便的功能,可以让我们轻松访问MNIST数据集。importtensorflowastfimportnumpyasnpiimportmatplotlib.pyplotasplt%matplotlibinlinefromtensorflow.examples.tutorials.mnistimportinput_datamnist=input_data.read_data_sets('MNIST_data')定义输入数据和输出数据MNIST图像尺寸均为28*28像素,只有单色通道。我们的输入数据X_in是一批MNIST字符,网络学习如何重建它们。然后将它们输出到与输入具有相同维度的占位符Y中。后面计算损失函数时会用到Y_flat,应用dropout时会用到keep_prob(作为正则化方法)。在训练的时候,它的值会被设置为0.8,在生成新数据的时候,我们没有使用dropout,所以它的值会变成1。lrelu函数需要自己定义,因为没有预定义的LeakyReLU函数在张量流中。tf.reset_default_graph()batch_size=64X_in=tf.placeholder(dtype=tf.float32,shape=[None,28,28],name='X')Y=tf.placeholder(dtype=tf.float32,shape=[None,28,28],name='Y')Y_flat=tf.reshape(Y,shape=[-1,28*28])keep_prob=tf.placeholder(dtype=tf.float32,shape=(),name='keep_prob')dec_in_channels=1n_latent=8reshaped_dim=[-1,7,7,dec_in_channels]inputs_decoder=49*dec_in_channels/2deflrelu(x,alpha=0.3):returntf.maximum(x,tf.multiply(x,alpha))来定义编码器由于我们的输入是图像,因此使用一些卷积变换会更有意义。最值得注意的是,我们在编码器中创建了两个向量,因为编码器应该创建遵循高斯分布的对象。一个是均值向量,另一个是标准差向量。稍后您将看到我们如何“强制”编码器以确保它确实生成服从正态分布的数据点。我们可以把输入到解码器中的编码值记为z。在计算损失函数时,我们需要选择分布的均值和标准差。defencoder(X_in,keep_prob):activation=lreluwithtf.variable_scope("编码器",reuse=None):X=tf.reshape(X_in,shape=[-1,28,28,1])x=tf.layers.conv2d(X,filters=64,kernel_size=4,strides=2,padding='same',activation=activation)x=tf.nn.dropout(x,keep_prob)x=tf.layers.conv2d(x,filters=64,kernel_size=4,strides=2,padding='same',activation=activation)x=tf.nn.dropout(x,keep_prob)x=tf.layers.conv2d(x,filters=64,kernel_size=4,strides=1,padding='same',activation=activation)x=tf.nn.dropout(x,keep_prob)x=tf.contrib.layers.flatten(x)mn=tf.layers.dense(x,units=n_latent)sd=0.5*tf.layers.dense(x,units=n_latent)epsilon=tf.random_normal(tf.stack([tf.shape(x)[0],n_latent]))z=mn+tf.multiply(epsilon,tf.exp(sd))returnz,mn,sd定义解码器解码器不关心输入值是否是从我们定义的某个分布中采样的。它只是试图重建输入图像。最后,我们使用一系列转置卷积。defdecoder(sampled_z,keep_prob):withtf.variable_scope("解码器",reuse=None):x=tf.layers.dense(sampled_z,units=inputs_decoder,activation=lrelu)x=tf.layers.dense(x,units=inputs_decoder*2+1,activation=lrelu)x=tf.reshape(x,reshaped_dim)x=tf.layers.conv2d_transpose(x,filters=64,kernel_size=4,strides=2,padding='same',activation=tf.nn.relu)x=tf.nn.dropout(x,keep_prob)x=tf.layers.conv2d_transpose(x,filters=64,kernel_size=4,strides=1,padding='same',activation=tf.nn.relu)x=tf.nn.dropout(x,keep_prob)x=tf.layers.conv2d_transpose(x,filters=64,kernel_size=4,strides=1,padding='same',activation=tf.nn.relu)x=tf.contrib.layers.flatten(x)x=tf.layers.dense(x,units=28*28,activation=tf.nn.sigmoid)img=tf.reshape(x,shape=[-1,28,28])returning现在,我们将两个部分连接在一起。sampled,mn,sd=encoder(X_in,keep_prob)dec=decoder(sampled,keep_prob)计算损失函数并实现高斯隐藏分布为了计算图像重建的损失函数,我们简单地使用平方差(有时会使图像有点模糊)。这个损失函数还结合了KL散度,它确保我们的隐藏值将从标准分布中采样。如果您想了解有关此主题的更多信息,可以查看这篇文章(https://jaan.io/what-is-variational-autoencoder-vae-tutorial/)。unreshaped=tf.reshape(dec,[-1,28*28])img_loss=tf.reduce_sum(tf.squared_difference(unreshaped,Y_flat),1)latent_loss=-0.5*tf.reduce_sum(1.0+2.0*sd-tf.square(mn)-tf.exp(2.0*sd),1)loss=tf.reduce_mean(img_loss+latent_loss)optimizer=tf.train.AdamOptimizer(0.0005).minimize(loss)sess=tf.Session()sess.run(tf.global_variables_initializer())训练网络现在我们终于可以训练我们的VAE了!每200步,我们将查看当前重建的样子。在处理了大约2000次迭代后,大多数重建看起来是合理的。foriinrange(30000):batch=[np.reshape(b,[28,28])forbinmnist.train.next_batch(batch_size=batch_size)[0]]sess.run(optimizer,feed_dict={X_in:batch,Y:batch,keep_prob:0.8})ifnoti%200:ls,d,i_ls,d_ls,mu,sigm=sess.run([loss,dec,img_loss,dst_loss,mn,sd],feed_dict={X_in:batch,Y:batch,keep_prob:1.0})plt.imshow(np.reshape(batch[0],[28,28]),cmap='gray')plt.show()plt.imshow(d[0],cmap='gray'')plt.show()print(i,ls,np.mean(i_ls),np.mean(d_ls))生成新数据最神奇的是我们现在可以生成新的字符了。最后,我们简单地从单位正态分布中采样一个值并将其提供给解码器。大多数生成的字符与人手书写的字符相同。randoms=[np.random.normal(0,1,n_latent)for_inrange(10)]imgs=sess.run(dec,feed_dict={sampled:randoms,keep_prob:1.0})imgs=[np.reshape(imgs[i],[28,28])foriinrange(len(imgs))]forimginimgs:plt.figure(figsize=(1,1))plt.axis('off')plt.imshow(img,cmap='gray')一些自动生成的字符。总结这是一个相当简单的VAE应用程序示例。但想象一下可能性!神经网络可以学习作曲,它们可以自动创建书籍、游戏的描述。借用创新思维,VAE可以为一些新颖的项目开辟空间。
