1.VAE和GAN

  1. 变分自编码器(VAE,variatinal autoencoder)   
  2. 生成式对抗网络(GAN,generative adversarial network)

两者不仅适用于图像,还可以探索声音、音乐甚至文本的潜在空间;

  1. VAE非常适合用于学习具有良好结构的潜在空间,其中特定方向表示数据中有意义的变化轴; 
  2. GAN生成的图像可能非常逼真,但它的潜在空间可能没有良好结构,也没有足够的连续型。

 

自编码,简单来说就是把输入数据进行一个压缩和解压缩的过程。 原来有很多 Feature,压缩成几个来代表原来的数据,解压之后恢复成原来的维度,再和原数据进行比较。它是一种非监督算法,只需要输入数据,解压缩之后的结果与原数据本身进行比较。

  在实践中,这种经典的自编码器不会得到特别有用或具有良好结构的潜在空间。它们也没有对数据做多少压缩。因此,它们已经基本上过时了(Keras 0.x版本还有AutoEncoder这个层,后来直接都删了)。但是,VAE向自编码器添加了一点统计魔法,迫使其学习连续的、高度结构化的潜在空间。这使得VAE已成为图像生成的强大工具。变分编码器和自动编码器的区别就在于,传统自动编码器的隐变量z的分布是不知道的,因此我们无法采样得到新的z,也就无法通过解码器得到新的x。下面我们来变分,我们现在不要从x中直接得到z,而是得到z的均值和方差,然后再迫使它逼近正态分布的均值和方差,则网络变成下面的样子:

然而上面这个网络最大的问题是,它是断开的。前半截是从数据集估计z的分布,后半截是从一个z的样本重构输入。最关键的采样这一步,恰好不是一个我们传统意义上的操作。这个网络没法求导,因为梯度传到f(z)以后没办法往前走了。为了使得整个网络得以训练,使用一种叫reparemerization的trick,使得网络对均值和方差可导,把网络连起来。这个trick的idea见下图:

实际上,这是将原来的单输入模型改为二输入模型了。因为服从标准正态分布,所以它乘以估计的方差加上估计的均值,效果跟上上图直接从高斯分布里抽样本结果是一样的。这样,梯度就可以通上图红线的方向回传,整个网络就变的可训练了。

VAE的工作原理:

(1)一个编码器模块将输入样本input_img转换为表示潜在空间中的两个参数z_mean和z_log_variance;

(2)我们假定潜在正态分布能够生成输入图像,并从这个分布中随机采样一个点:z=z_mean + exp(z_log_variance)*epsilon,其中epsilon是取值很小的随机张量;

(3)一个解码器模块将潜在空间的这个点映射回原始输入图像。

因为epsilon是随机的,所以这个过程可以确保,与input_img编码的潜在位置(即z-mean)靠近的每个点都能被解码为与input_img类似的图像,从而迫使潜在空间能够连续地有意义。潜在空间中任意两个相邻的点都会被解码为高度相似的图像。连续性以及潜在空间的低维度,将迫使潜在空间中的每个方向都表示数据中一个有意义的变化轴,这使得潜在空间具有非常良好的结构,因此非常适合通过概率向量来进行操作。

VAE的参数通过两个损失函数来进行训练:一个是重构损失(reconstruction loss),它迫使解码后的样本匹配初始输入;另一个是正则化损失(regularization loss),它有助于学习具有良好结构的潜在空间,并可以降低训练数据上的过拟合。

 实现代码如下:

编码自编码器是更现代和有趣的一种自动编码器,它为码字施加约束,使得编码器学习到输入数据的隐变量模型。
隐变量模型是连接显变量集和隐变量集的统计模型,隐变量模型的假设是显变量是由隐变量的状态控制的,各个显变量之间条件独立。
也就是说,变分编码器不再学习一个任意的函数,而是学习你的数据概率分布的一组参数。
通过在这个概率分布中采样,你可以生成新的输入数据,即变分编码器是一个生成模型。

import keras
from keras import layers
from keras import backend as K
from keras.models import Model
from keras.layers import Input,Dense
import numpy as np img_shape = (28,28,1)
latent_dim = 2 #潜在空间的维度:一个二维平面 input_img = keras.Input(shape=img_shape) encoded = layers.Conv2D(32,3,padding='same',activation='relu')(input_img)
encoded = layers.Conv2D(64,3,padding='same',activation='relu',strides=(2,2))(encoded)
encoded = layers.Conv2D(64,3,padding='same',activation='relu')(encoded)
encoded = layers.Conv2D(64,3,padding='same',activation='relu')(encoded)
shape_before_flattening = K.int_shape(encoded)
shape_before_flattening

  

卷积层的输入必须是3维的(长,宽,1或者3)

keras不需要输入batch的大小,fit时候再设置

shape_before_flattening

(None, 14, 14, 64)

encoded = layers.Flatten()(encoded)
encoded = layers.Dense(32,activation='relu')(encoded) #输入图像最终被编码为这两个参数
z_mean = layers.Dense(latent_dim)(encoded)
z_log_var = layers.Dense(latent_dim)(encoded) #编码器 输入图片-->得到二维特征
encoder = Model(input_img,z_mean)

  

z_mean  --->

<tf.Tensor 'dense_5/BiasAdd:0' shape=(?, 2) dtype=float32>

 K.shape(z_mean) --->
<tf.Tensor 'Shape:0' shape=(2,) dtype=int32>
 

#潜在空间采样的函数
def sampling(args):
z_mean,z_log_var = args
epsilon = K.random_normal(shape=(K.shape(z_mean)[0],latent_dim),mean=0.,stddev=1.)
return z_mean + K.exp(z_log_var)*epsilon z = layers.Lambda(sampling,output_shape=(latent_dim,))([z_mean,z_log_var])

在keras中,任何对象都应该是一个层,如果代码不是内置层的一部分,

我们应该将其包装到一个Lambda层(或自定义层)中

Keras的Lambda层以一个张量函数为参数,对输入的数据按照张量函数的要求做映射。

本质上就是Keras layer中.call()的快捷方式。先定义运算逻辑

K.int_shape(z) ---> (None,2)      None应该是batch_size

#VAE解码器网络,将潜在空间点映射为图像
decoder_input = layers.Input(K.int_shape(z)[1:]) #将z调整为图像大小,需要将z输入到这里 #对输入进行上采样
decoded = layers.Dense(np.prod(shape_before_flattening[1:]),activation='relu')(decoder_input) #将z转换为特征图,使其形状与编码器模型最后一个Flatten层之前的特征图的形状相同
decoded = layers.Reshape(shape_before_flattening[1:])(decoded) #使用一个Conv2DTranspose层和一个Conv2D层,将z解码为与原始输入图像具有相同尺寸的特征图
decoded = layers.Conv2DTranspose(32,3,padding='same',activation='relu',strides=(2,2))(decoded)
decoder_output = layers.Conv2D(1,3,padding='same',activation='sigmoid')(decoded) #将解码器模型实例化,它将decoder_input转换为解码后的图像
decoder = Model(decoder_input,decoder_output)

#将这个实例应用于z,以得到解码后的z
z_decoded = decoder(z)
 

#用于计算VAE损失的自定义层
class CustomVariationalLayer(keras.layers.Layer):
def vae_loss(self,x,z_decoded):
x = K.flatten(x)
z_decoded = K.flatten(z_decoded)
xent_loss = keras.metrics.binary_crossentropy(x,z_decoded) #正则化损失
kl_loss = -5e-4 * K.mean(1 + z_log_var - K.square(z_mean) - K.exp(z_log_var),axis=-1 ) #重构损失
return K.mean(xent_loss + kl_loss) #编写一个call方法,来实现自定义层
def call(self,inputs):
x = inputs[0]
z_decoded = inputs[1]
loss = self.vae_loss(x,z_decoded)
self.add_loss(loss,inputs=inputs)
return x #我们不适用这个输出,但层必须要有返回值 #对输入和解码后的输出调用自定义层,以得到最终的模型输出
y = CustomVariationalLayer()([input_img,z_decoded])

正则化损失 + 重构损失

我们一般认为采样函数的形式为loss(input,target),VAE的双重损失不符合这种形式。

因此,损失的设置方法为:编写一个自定义层,并在其内部使用内置的add_loss层方法

来创建一个你想要的损失

 

#训练VAE
vae = Model(input_img,y)
vae.compile(optimizer='rmsprop',loss=None)
vae.summary()

  


 

from keras.datasets import mnist
(x_train,_),(x_test,y_test) = mnist.load_data()
x_train = x_train[:600]
x_test = x_test[:100]
x_train = x_train.astype('float32')/255.
print('x_train.shape',x_train.shape)
x_train =x_train.reshape(x_train.shape+(1,))
print('x_train.shape',x_train.shape) x_test = x_test.astype('float32')/255.
print('x_test.shape',x_test.shape)
x_test = x_test.reshape(x_test.shape+(1,))
print('x_test.shape',x_test.shape)

  

 
x_train.shape (600, 28, 28)
x_train.shape (600, 28, 28, 1)
x_test.shape (100, 28, 28)
x_test.shape (100, 28, 28, 1)

vae.fit(x_train,None,
shuffle=True,
epochs=1,
batch_size=100,
validation_data = (x_test,None)
)
 

一旦训练好了这样的模型,我们就可以使用decoder网络将任意潜在空间向量

转换为图像

#从二维潜在空间中采样一组点的网络,并将其解码为图像
import matplotlib.pyplot as plt
from scipy.stats import norm batch_size = 100
n = 15 #我们将显示15*15的数字网格(共225个数字)
digit_size=28
figure = np.zeros((digit_size*n,digit_size*n)) #使用scipy的ppf函数对线性分割的坐标进行变换,以生存潜在变量z的值(因为潜在空间的先验分布是高斯分布)
grid_x = norm.ppf(np.linspace(0.05,0.95,n))
grid_y = norm.ppf(np.linspace(0.05,0.95,n))
print(grid_x)
print(grid_y) for i,yi in enumerate(grid_x):
for j,xi in enumerate(grid_y):
z_sample = np.array([[xi,yi]])
z_sample = np.tile(z_sample,batch_size).reshape(batch_size,2)#将z多次重复,以构建一个完整的批量
x_decoded = decoder.predict(z_sample,batch_size=batch_size)#将批量解码为数字图像
digit = x_decoded[0].reshape(digit_size,digit_size)#将批量第一个数字形状从28*28*1转变为28*28
figure[i*digit_size:(i+1)*digit_size,j*digit_size:(j+1)*digit_size] = digit plt.figure(figsize=(10,10))
plt.imshow(figure,cmap='Greys_r')
plt.show()

  

 

因为训练时候就用了600个数据,所以效果很差....电脑实在带不动,┭┮﹏┭┮

以后有服务器再试试,7777777

小结:  用深度学习进行图像生成,就是通过对潜在空间进行学习来实现的,这个潜在空间能够捕捉到关于图像数据集的统计信息。 通过对潜在空间中的点进行采样和编码,我们可以生成前所未见的图像。

网上的代码大部分都是关于mnist数据集的,直接load_dataset就完事了,我找到了名人头像的数据集celebrity_data,用这个数据集做vae更有趣一点。

import keras
from keras import layers
from keras import backend as K
from keras.models import Model
import numpy as np
import skimage
import glob
from skimage import io
import os
import imageio

  

  • skimage即是Scikit-Image。基于python脚本语言开发的数字图片处理包,比如PIL,Pillow, opencv, scikit-image等。
  • PIL和Pillow只提供最基础的数字图像处理,功能有限;opencv实际上是一个c++库,只是提供了python接口,更新速度非常慢。
  • scikit-image是基于scipy的一款图像处理包,它将图片作为numpy数组进行处理,正好与matlab一样,
  • 因此,我们最终选择scikit-image进行数字图像处理。

train_imgs = glob.glob('./celebrity_data/train/*.jpg')
np.random.shuffle(train_imgs)
test_imgs = glob.glob('./celebrity_data/test/*.jpg')
np.random.shuffle(train_imgs) nxf_image = io.imread(test_imgs[0])

Image读出来的是PIL的类型,而skimage.io读出来的数据是numpy格式的

import Image as img
import os
from matplotlib import pyplot as plot
from skimage import io,transform
#Image和skimage读图片
img_file1 = img.open('./CXR_png/MCUCXR_0042_0.png')
img_file2 = io.imread('./CXR_png/MCUCXR_0042_0.png')

输出可以看出Img读图片的大小是图片的(width, height);而skimage的是(height,width, channel)

height,width = imageio.imread(train_imgs[0]).shape[:2]
center_height = int((height-width)/2)
img_xdim = 218
img_ydim = 178
z_dim = 512

  

 训练集里面的图片都是218*178*3的,训练的时候我也没有改大小,直接放进去训练的

def imread(f):
x = imageio.imread(f)
x = x[center_height:center_height+width,:]
x = skimage.transform.resize(x,(img_xdim,img_ydim),mode='constant')
return x.astype(np.float32)/255 * 2 - 1 def train_data_generator(batch_size=32):
X = []
while True:
np.random.shuffle(train_imgs)
for f in train_imgs:
X.append(imread(f))
if len(X) == batch_size:
X = np.array(X)
yield X,None
X = []

  

train_data_generator是训练集图片生成器,每次生成一个图片

img_shape = (img_xdim,img_ydim,3)
latent_dim = 2 #潜在空间的维度:一个二维平面 input_img = keras.Input(shape=img_shape) encoded = layers.Conv2D(32,3,padding='same',activation='relu')(input_img)
encoded = layers.Conv2D(64,3,padding='same',activation='relu',strides=(2,2))(encoded)
encoded = layers.Conv2D(64,3,padding='same',activation='relu')(encoded)
encoded = layers.Conv2D(64,3,padding='same',activation='relu')(encoded)
shape_before_flattening = K.int_shape(encoded) encoded = layers.Flatten()(encoded)
encoded = layers.Dense(32,activation='relu')(encoded) #输入图像最终被编码为这两个参数
z_mean = layers.Dense(latent_dim)(encoded)
z_log_var = layers.Dense(latent_dim)(encoded) encoder = Model(input_img,z_mean)

  

 这部分和上面基于minist数据集的encoder部分一样

#将图片转换为二维向量

nxf_image = nxf_image.reshape((1,)+nxf_image.shape)
nxf_image_encoder = encoder.predict(nxf_image)
print('nxf_image_encoder',nxf_image_encoder)

  

这里是我在测试encoder,随机输入一张图片,输出了二维的一个值,一个是均值,一个是方差,encoder没有编译,

也没有fit,就相当于将多维图片降维成二维的一组

# 潜在空间采样的函数
def sampling(args):
z_mean,z_log_var = args
epsilon = K.random_normal(shape=(K.shape(z_mean)[0],latent_dim),mean=0.,stddev=1.)
return z_mean + K.exp(z_log_var)*epsilon z = layers.Lambda(sampling,output_shape=(latent_dim,))([z_mean,z_log_var]) #VAE解码器网络,将潜在空间点映射为图像
decoder_input = layers.Input(K.int_shape(z)[1:]) #将z调整为图像大小,需要将z输入到这里 #对输入进行上采样
decoded = layers.Dense(np.prod(shape_before_flattening[1:]),activation='relu')(decoder_input) #将z转换为特征图,使其形状与编码器模型最后一个Flatten层之前的特征图的形状相同
decoded = layers.Reshape(shape_before_flattening[1:])(decoded) #使用一个Conv2DTranspose层和一个Conv2D层,将z解码为与原始输入图像具有相同尺寸的特征图
decoded = layers.Conv2DTranspose(32,3,padding='same',activation='relu',strides=(2,2))(decoded)
decoder_output = layers.Conv2D(3,3,padding='same',activation='sigmoid')(decoded) #将解码器模型实例化,它将decoder_input转换为解码后的图像
decoder = Model(decoder_input,decoder_output) #将这个实例应用于z,以得到解码后的z
z_decoded = decoder(z)
# decoder.summary()

  

 这部分也是一样的,解码操作,随机生成一个点(均值,方差)放入decoder中,看看生成的图片能不能和原来的图片一样

# 用于计算VAE损失的自定义层
class CustomVariationalLayer (keras.layers.Layer):
def vae_loss(self, x, z_decoded):
x = K.flatten (x)
z_decoded = K.flatten (z_decoded)
xent_loss = keras.metrics.binary_crossentropy (x, z_decoded) # 正则化损失
kl_loss = -5e-4 * K.mean (1 + z_log_var - K.square (z_mean) - K.exp (z_log_var), axis=-1) # 重构损失
return K.mean (xent_loss + kl_loss) # 编写一个call方法,来实现自定义层
def call(self, inputs):
x = inputs[0]
z_decoded = inputs[1]
loss = self.vae_loss (x, z_decoded)
self.add_loss(loss, inputs=inputs)
return x # 我们不适用这个输出,但层必须要有返回值 # 对输入和解码后的输出调用自定义层,以得到最终的模型输出
y = CustomVariationalLayer() ([input_img, z_decoded]) # 训练VAE
vae = Model(input_img, y)
vae.compile(optimizer='rmsprop', loss=None)
# vae.summary()

  

VAE的两个损失,由于keras自带的损失函数没有同时有正则损失和重构损失,所以需要自定义一个损失层,

使用call函数来定义该损失层的功能

def sample(path):
figure_nxf = np.array(nxf_image_encoder)
nxf_recon = decoder.predict(figure_nxf)[0] imageio.imwrite(path,nxf_recon) from keras.callbacks import Callback class Evaluate(Callback):
def __init__(self):
import os
self.lowest = 1e10
self.losses = []
if not os.path.exists('samples'):
os.mkdir('samples') def on_epoch_end(self, epoch, logs=None):
path = 'samples/test_%s.png' % epoch
sample(path)
self.losses.append((epoch, logs['loss']))
if logs['loss'] <= self.lowest:
self.lowest = logs['loss']
encoder.save_weights('./best_encoder.weights') evaluator = Evaluate()
vae.fit_generator(train_data_generator(),
epochs=1,
steps_per_epoch=1,
callbacks=[evaluator])

  

 sample函数,我就随机输入两个值(encoder的输出值),看看能不能生成一个相似的图片

参考文献:

【1】Keras示例程序解析(4):变分编码器VAE

【2】变分自编码器(Variational Autoencoder, VAE)通俗教程

【3】变分自编码器VAE:一步到位的聚类方案

【4】如何使用变分自编码器VAE生成动漫人物形象

【5】vae 名人数据集的使用

4.keras实现-->生成式深度学习之用变分自编码器VAE生成图像(mnist数据集和名人头像数据集)的更多相关文章

  1. 4.keras实现-->生成式深度学习之用GAN生成图像

    生成式对抗网络(GAN,generative adversarial network)由Goodfellow等人于2014年提出,它可以替代VAE来学习图像的潜在空间.它能够迫使生成图像与真实图像在统 ...

  2. 4.keras实现-->生成式深度学习之DeepDream

    DeepDream是一种艺术性的图像修改技术,它用到了卷积神经网络学到的表示,DeepDream由Google于2015年发布.这个算法与卷积神经网络过滤器可视化技术几乎相同,都是反向运行一个卷积神经 ...

  3. 从零开始学会GAN 0:第一部分 介绍生成式深度学习(连载中)

    本书的前四章旨在介绍开始构建生成式深度学习模型所需的核心技术.在第1章中,我们将首先对生成式建模领域进行广泛的研究,并从概率的角度考虑我们试图解决的问题类型.然后,我们将探讨我们的基本概率生成模型的第 ...

  4. 深度学习课程笔记(九)VAE 相关推导和应用

    深度学习课程笔记(九)VAE 相关推导和应用 2018-07-10 22:18:03 Reference: 1. TensorFlow code: https://jmetzen.github.io/ ...

  5. 用MXnet实战深度学习之一:安装GPU版mxnet并跑一个MNIST手写数字识别

    用MXnet实战深度学习之一:安装GPU版mxnet并跑一个MNIST手写数字识别 http://phunter.farbox.com/post/mxnet-tutorial1 用MXnet实战深度学 ...

  6. 转:TensorFlow和Caffe、MXNet、Keras等其他深度学习框架的对比

    http://geek.csdn.net/news/detail/138968 Google近日发布了TensorFlow 1.0候选版,这第一个稳定版将是深度学习框架发展中的里程碑的一步.自Tens ...

  7. PyTorch中使用深度学习(CNN和LSTM)的自动图像标题

    介绍 深度学习现在是一个非常猖獗的领域 - 有如此多的应用程序日复一日地出现.深入了解深度学习的最佳方法是亲自动手.尽可能多地参与项目,并尝试自己完成.这将帮助您更深入地掌握主题,并帮助您成为更好的深 ...

  8. 学习《深度学习与计算机视觉算法原理框架应用》《大数据架构详解从数据获取到深度学习》PDF代码

    <深度学习与计算机视觉 算法原理.框架应用>全书共13章,分为2篇,第1篇基础知识,第2篇实例精讲.用通俗易懂的文字表达公式背后的原理,实例部分提供了一些工具,很实用. <大数据架构 ...

  9. 【神经网络与深度学习】Caffe使用step by step:使用自己数据对已经训练好的模型进行finetuning

    在经过前面Caffe框架的搭建以及caffe基本框架的了解之后,接下来就要回到正题:使用caffe来进行模型的训练. 但如果对caffe并不是特别熟悉的话,从头开始训练一个模型会花费很多时间和精力,需 ...

随机推荐

  1. canvas练习 - 圆

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  2. canvas练习 - 七巧板绘制

    用到的方法: 注意点: stokeStyle等样式要在stroke前边 如果最后只有一个stroke或者fill,那么只填充最后一次路径的,之前的也会画出来但是没有填充看不到.所以每次begin+cl ...

  3. jQuery mobile 初始化页面的过程

  4. 问题记录,如何解决confluence的office预览的时候的乱码问题

    在新的服务器(ubuntu16.04)上安装confluence,预览office的附件的时候,发现中文无法正确显示 在网上搜了一下,搜到一篇官方的文档,是关于这个问题的 问题原因: 在服务器上没有安 ...

  5. 自定义容器启动脚本报错:exec user process caused "no such file or directory"

    创建容器起不来,一直是restarting状态,查看容器的报错日志如下: standard_init_linux.go:178: exec user process caused "no s ...

  6. Android服务开发——WebService

    我在学习Android开发过程中遇到的第一个疑问就是Android客户端是怎么跟服务器数据库进行交互的呢?这个问题是我当初初次接触Android时所困扰我的一个很大的问题,直到几年前的一天,我突然想到 ...

  7. H3C系列之三层交换机文件管理

    笔者本篇文章所用h3c交换机的型号为三层交换机S3600-28TP-SI 对于文件的操作一般都在用户视图下操作,常见的有如下一些操作: 1.查看操作,常用的查看操作可以使用如下命令: <H3C& ...

  8. Gradle gitignore Gradle 模式 上传SVN 要忽略的文件

    .gradle /local.properties /.idea/workspace.xml /.idea/libraries .DS_Store /build /captures /.idea *. ...

  9. SqlServer数据库查询表信息/列信息(列ID/列名/数据类型/长度/精度/是否可以为null/默认值/是否自增/是否是主键/列描述)

    查询表信息(表名/表描述) Value ) AS value FROM sysobjects a Where a.xtype = 'U' AND a.name <> 'sysdiagram ...

  10. POJ3268 Silver Cow Party【最短路】

    One cow from each of N farms (1 ≤ N ≤ 1000) conveniently numbered 1..N is going to attend the big co ...