换脸原理,使用GAN网络再造ZAO应用:可变自动编解码器基本原理

从本节开始,我们介绍一种人工智能实现无缝变脸的网络名为可变自动编解码器,英文名称:variational autoencoder。 在前面章节中我们曾介绍过,很多事物看起来似乎很复杂,但只要抓住其关键变量,那就等同于抓住事物的本质,例如一个圆柱体,它的关键变量就是底部圆的半径,和高度,掌握了这两个变量的信息,我们可以轻易的将圆柱构造出来。
其实像人脸这种复杂图案,它也包含了对应关键信息,如果能抽取出这些信息,我们就能对人脸图像进行各种平滑变化,而抽取这些信息的责任就得由神经网络来承当。 抓取人脸关键信息其实不难,我们只要使用多个卷积网络识别人脸图片,把识别结果转换成一个一维向量,该向量里面的分量其实就是人脸图像的关键信息,在深入人脸变换之前,我们先看一个简单的自动编解码器,首先我们构造编码器,也就是识别输入数据对应的关键信息:
 
  
class AutoEncoder():
def __init__(self, input_dim, encoder_conv_filters, encoder_conv_kernel_size, encoder_conv_strides,z_dim,
use_batch_norm = False, use_dropout = False):
'''
input_dim是输入数据的格式,encoder_conv_filters指定每层卷积层包含几个kernel,
encoder_conv_kernel_size指定kernel的大小,encoder_conv_strides指定kernel的跨度,
z_dim表示关键向量的长度
'''
self.input_dim = input_dim
self.encoder_conv_filters = encoder_conv_filters
self.encoder_conv_kernel_size = encoder_conv_kernel_size
self.encoder_conv_strides = encoder_conv_strides
self.z_dim = z_dim
self.use_batch_norm = use_batch_norm
self.use_dropout = use_dropout
self.n_layers_encoder = len(encoder_conv_filters)
self._build() #根据参数构建网络层
def _build(self):
encoder_input = Input(shape = self.input_dim, name = "encoder_input")
x = encoder_input
for i in range(self.n_layers_encoder):
conv_layer = Conv2D(filters = self.encoder_conv_filters[i],
kernel_size = self.encoder_conv_kernel_size[i],
strides = self.encoder_conv_strides[i],
padding = 'same',
name = 'encoder_conv' + str(i))
x = conv_layer(x)
x = LeakyReLU()(x)
if self.use_batch_norm:
x = BatchNormalization()(x)
if self.use_dropout:
x = Dropout(rate = 0.25)(x)
shape_before_flattening = K.int_shape(x)[1:]
x = Flatten()(x)
encoder_output = Dense(self.z_dim, name='encoder_output')(x) #输出关键向量
self.encoder = Model(encoder_input, encoder_output)
self.encoder.summary()
AE = AutoEncoder(input_dim = [28, 28, 1], encoder_conv_filters = [32, 64, 64, 64], encoder_conv_kernel_size = [3,3,3,3],
encoder_conv_strides = [1, 2, 2, 1], z_dim = 2)
上面代码与我们前面构造卷积网络差不多,关键在于最后一层,它包含z_dim个分量,这一层输出对应所谓的关键向量,这个向量将会帮我们把人脸的所有关键变量记录下来,上面代码运行后结果如下:

Layer (type) Output Shape Param #

encoder_input (InputLayer) (None, 28, 28, 1) 0

encoder_conv0 (Conv2D) (None, 28, 28, 32) 320

leaky_re_lu_19 (LeakyReLU) (None, 28, 28, 32) 0

encoder_conv1 (Conv2D) (None, 14, 14, 64) 18496

leaky_re_lu_20 (LeakyReLU) (None, 14, 14, 64) 0

encoder_conv2 (Conv2D) (None, 7, 7, 64) 36928

leaky_re_lu_21 (LeakyReLU) (None, 7, 7, 64) 0

encoder_conv3 (Conv2D) (None, 7, 7, 64) 36928

leaky_re_lu_22 (LeakyReLU) (None, 7, 7, 64) 0

flatten_5 (Flatten) (None, 3136) 0

encoder_output (Dense) (None, 2) 6274

Total params: 98,946
接下来我们看看解码器的实现,解码器的目标是根据编码器生成的关键向量,将人脸尽可能还原出来,解码器在运行时会按照编码器的运行原理进行反向操作。 由于编码器在解读图像时进行了卷积操作,因此解码器需要进行反卷积操作。 反卷积操作原理其实与卷积操作类似。 回忆一下,当我们使用一个3*3的内核作用在5*5的图像上时,如果我们在卷积时不填充图像,卷积操作后得到的结果是3*3的矩阵。
反卷积操作的原理是从卷积操作后得到的3*3矩阵还原回5*5矩阵,具体做法是在3*3矩阵的每个像素点上下左右方向用0填充,由此我们能将其填充成一个8*8矩阵,然后再使用一个3*3内核与填充后的矩阵做卷积操作得到一个5*5的矩阵,这相当于把3*3的卷积结果还原为原来的5*5矩阵,其基本原理如下图所示:
640?wx_fmt=png
如上图所示,底部蓝色的点对应卷积操作后所得3*3矩阵的分量,蓝色点上下左右被白色方块包围,它对应我们用0填充3*3矩阵,使之成为8*8矩阵,阴影部分对应3*3卷积内核,上面绿色的5*5矩阵就是阴影对应的卷积内核与底部矩阵做卷积后的结果,keras框架给我们提供了接口直接实现反卷积操作: Conv2DTranspose,接下来我们看看解码器的实现:
 
  
def __init__(self, input_dim, encoder_conv_filters, encoder_conv_kernel_size, encoder_conv_strides,z_dim,
decoder_conv_t_filters, decoder_conv_t_kernel_size, decoder_conv_t_strides,
use_batch_norm = False, use_dropout = False):
'''
input_dim是输入数据的格式,encoder_conv_filters指定每层卷积层包含几个kernel,
encoder_conv_kernel_size指定kernel的大小,encoder_conv_strides指定kernel的跨度,
z_dim表示关键向量的长度
'''
self.input_dim = input_dim
self.encoder_conv_filters = encoder_conv_filters
self.encoder_conv_kernel_size = encoder_conv_kernel_size
self.encoder_conv_strides = encoder_conv_strides
self.z_dim = z_dim
self.use_batch_norm = use_batch_norm
self.use_dropout = use_dropout
self.n_layers_encoder = len(encoder_conv_filters)
#解码器相关参数设置
self.decoder_conv_t_filters = decoder_conv_t_filters
self.decoder_conv_t_kernel_size = decoder_conv_t_kernel_size
self.decoder_conv_t_strides = decoder_conv_t_strides
self.n_layers_decoder = len(decoder_conv_t_filters)
self._build() #根据参数构建网络层
def _build(self):
....
#设置解码器,它接收编码器的最终输出
decoder_input = Input(shape = (self.z_dim,), name = 'decoder_iunput')
x = Dense(np.prod(shape_before_flattening))(decoder_input)
x = Reshape(shape_before_flattening)(x) #注意解码器每一步都跟编码器反着来
for i in range(self.n_layers_decoder):
conv_t_layer = Conv2DTranspose(filters = self.decoder_conv_t_filters[i],
kernel_size = self.decoder_conv_t_kernel_size[i],
strides = self.decoder_conv_t_strides[i],
padding = 'same',
name = 'decoder_conv_t' + str(i))
x = conv_t_layer(x)
if i < self.n_layers_decoder - 1:
x = LeakyReLU()(x)
if self.use_batch_norm:
x = BatchNormalization()(x)
if self.use_dropout:
x = Dropout(rate = 0.25)(x)
else:
x = Activation('sigmoid')(x)
decoder_output = x
self.decoder = Model(decoder_input, decoder_output)
#将编码器与解码器连接起来
model_input = encoder_input
model_output = self.decoder(encoder_output)
self.model = Model(model_input, model_output)
self.model.summary()
我们注意看,解码器的每个步骤几乎与编码器正好相反。 完成解码器后,我们把编码器与解码器衔接起来,编码器的输出正好是解码器的输入,最后编解码器的基本结构如下:
640?wx_fmt=png
我们把一张手写数字图片输入编码器,编码器把它抽象成只含有2个元素的一维向量,该向量输入到解码器,解码器将向量还原为输入编码器的图像。 下一节我们将数据输入到网络中进行训练,看看编解码器对输入图像的还原效果。

更多详细内容请点击’阅读原文‘
发布了341 篇原创文章 · 获赞 247 · 访问量 39万+

猜你喜欢

转载自blog.csdn.net/tyler_download/article/details/102559554