前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >换脸原理,使用GAN网络再造ZAO应用:可变自动编解码器基本原理

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

作者头像
望月从良
发布2019-10-15 15:31:38
7490
发布2019-10-15 15:31:38
举报
文章被收录于专栏:Coding迪斯尼

从本节开始,我们介绍一种人工智能实现无缝变脸的网络名为可变自动编解码器,英文名称:variational autoencoder。在前面章节中我们曾介绍过,很多事物看起来似乎很复杂,但只要抓住其关键变量,那就等同于抓住事物的本质,例如一个圆柱体,它的关键变量就是底部圆的半径,和高度,掌握了这两个变量的信息,我们可以轻易的将圆柱构造出来。

其实像人脸这种复杂图案,它也包含了对应关键信息,如果能抽取出这些信息,我们就能对人脸图像进行各种平滑变化,而抽取这些信息的责任就得由神经网络来承当。抓取人脸关键信息其实不难,我们只要使用多个卷积网络识别人脸图片,把识别结果转换成一个一维向量,该向量里面的分量其实就是人脸图像的关键信息,在深入人脸变换之前,我们先看一个简单的自动编解码器,首先我们构造编码器,也就是识别输入数据对应的关键信息:

代码语言:javascript
复制
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矩阵,其基本原理如下图所示:

如上图所示,底部蓝色的点对应卷积操作后所得3*3矩阵的分量,蓝色点上下左右被白色方块包围,它对应我们用0填充3*3矩阵,使之成为8*8矩阵,阴影部分对应3*3卷积内核,上面绿色的5*5矩阵就是阴影对应的卷积内核与底部矩阵做卷积后的结果,keras框架给我们提供了接口直接实现反卷积操作:Conv2DTranspose,接下来我们看看解码器的实现:

代码语言:javascript
复制
  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()

我们注意看,解码器的每个步骤几乎与编码器正好相反。完成解码器后,我们把编码器与解码器衔接起来,编码器的输出正好是解码器的输入,最后编解码器的基本结构如下:

我们把一张手写数字图片输入编码器,编码器把它抽象成只含有2个元素的一维向量,该向量输入到解码器,解码器将向量还原为输入编码器的图像。下一节我们将数据输入到网络中进行训练,看看编解码器对输入图像的还原效果。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-10-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Coding迪斯尼 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Layer (type) Output Shape Param #
  • encoder_output (Dense) (None, 2) 6274
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档