Python深度学习之VAE

2023-11-13

Deep Learning with Python

这篇文章是我学习《Deep Learning with Python》(第二版,François Chollet 著) 时写的系列笔记之一。文章的内容是从 Jupyter notebooks 转成 Markdown 的,你可以去 GitHubGitee 找到原始的 .ipynb 笔记本。

你可以去这个网站在线阅读这本书的正版原文(英文)。这本书的作者也给出了配套的 Jupyter notebooks

本文为 第8章 生成式深度学习 (Chapter 8. Generative deep learning) 的笔记之一。

8.4 Generating images with variational autoencoders

用变分自编码器生成图像

前两篇介绍的 DeepDream 和 Neural Style Transfer 都只是有限地“修改”现有作品。而下面我们要介绍地 GAN 和 VAE 则是更加富有创造性的,这两种技术都是从图像的潜在空间中采样,并创建全新图像或编辑现有图像。

  • VAE:变分自编码器(Variational AutoEncoder)
  • GAN:生成式对抗网络(Generative Adversarial Network)

从潜在空间采样

潜在空间(latent space)是一个向量空间,其中任意点都可以被映射为一张逼真的图像。而实现这种映射(潜在点->图像)的模块就是 GAN 的 generator,或者 VAE 的 decoder。

GAN、VAE 生成图像的关键就在于找到一个低维的「表示潜在空间」(latent space of representations)。一旦找到这样的潜在空间,从中采样,映射到图像空间,就可以生成全新的图像。

学习图像的潜在向量空间,并利用这个空间来采样新图像

GAN 和 VAE 学习的潜在空间有很大的区别:

  • VAE 善于学习具有良好结构的潜在空间,其中的特定方向可以编码(表示)数据中一个有意义的变化的轴。
  • GAN 生成的图像可以非常逼真,但潜在空间缺乏良好的结构、没有足够的连续性。

概念向量

概念向量(concept vector):给定一个表示的潜在空间或一个嵌入空间,空间中的特定方向可能表示原始数据中有意义的变化轴。例如对于图像,人脸图像的潜在空间中可能存在一个代表「微笑」这个概念的向量(称为微笑向量,smile vector):对于代表某张人脸的潜在点 z,z+s 就是同一张人脸面带微笑的表示。

找到了这样的一些概念向量之后,我们就可以用这种方法来编辑图像了:将图像投射到潜在空间,和概念向量做运算来移动其表示,然后再解码到图像空间,就可以改变图像中的某一概念了——比如微笑程度:

微笑向量

变分自编码器

自编码器是一种网络类型,接收一张图像,通过 encoder 模块将其映射到「潜在空间」,然后再通过 decoder 模块将其解码成与原始图像尺寸相同的输出。这东西训练时的目标是使输出和输入相同,所以我们把输入、输出用同一张图片。所以自编码器学习的是对原始输入进行重新构建。

通过对编码(编码器的输出)施加限制,可以让自编码器学到有用的数据潜在表示。比如限制编码要低维并且是稀疏的,这样编码器就可以将输入数据压缩为更少二进制位的信息:

自编码器:将输入x映射为压缩表示,然后再将其解码为x’

变分自编码器 VAE,是一种现代化的自编码器。它是一种生成式模型,特别做利用概念向量进行图像编辑的任务。比起经典自编码器,VAE 可以学习更连续的、高度结构化的潜在空间。

VAE 不是将输入图像压缩成潜在空间中的固定编码,而是将图像转换为统计分布的参数——平均值和方差。VAE 解码的时候利用平均值和方差,从分布中随机采样一个元素,并将这个元素解码到原始输入。所以 VAE 的编码/解码过程是有一定的随机性的。

这个过程的随机性提高了 VAE 潜在空间的稳健性:VAE 需保证潜在空间采样的每个点都能解码为有效的输出,这迫使潜在空间的任何位置都对应有意义的表示。

VAE 将一张图像映射为两个向量 z_mean 和 z_log_var,二者定义了潜在 空间中的一个概率分布,用于采样一个潜在点并对其进行解码

上图展现了 VAE 的工作原理:

  1. Encoder 模块将输入样本 input_img 转换为表示潜在空间中的参数 z_meanz_log_variance;
  2. 从这个潜在正态分布中随机采样一个点 z: z = z_mean + exp(z_log_variance) * epsilon,其中 epsilon 是取值很小的随机张量;
  3. Decoder 模块将这个潜在点映射回原始输入图像。

epsilon 是随机的,所以需要与 input_img 编码的潜在位置(z-mean)靠近的每个点都能被解码为与 input_img 类似的图像,这个性质迫使潜在空间能够连续地有意义:潜在空间中任意两个相邻的点都会被解码为高度相似的图像。连续性以及潜在空间的低维度,又迫使潜在空间中的每个方向都表示数据中一个有意义的变化轴,这样就可以通过概念向量来进行操作。

用 Keras 实现 VAE 的伪代码如下:

z_mean, z_log_variance = encoder(input_img)
z = z_mean + exp(z_log_variance) * epsilon
reconstructed_img = decoder(z)
model = Model(input_img, reconstruced_img)

训练 VAE 需要两个损失函数:

  • 重构损失(reconstruction loss):使解码后的样本匹配初始输入;
  • 正则化损失(regularization loss):使潜在空间具有良好结构(连续性、概念向量可用性),同时也降低在训练数据上的过拟合;

我们具体实现编码器(encoder)网络:通过一个卷积神经网络,将输入图像 x 映射为两个向量 z_mean 和 z_log_var。

# 不使用及时执行模式

import tensorflow as tf

tf.compat.v1.disable_eager_execution()
# VAE 编码器网络

from tensorflow import keras
from tensorflow.keras import layers
from tensorflow.keras import backend as K
from tensorflow.keras.models import Model
import numpy as np

img_shape = (28, 28, 1)
batch_size = 16
latent_dim = 2    # 潜在空间的维度:2D平面

input_img = keras.Input(shape=img_shape)
x = layers.Conv2D(32, 3, padding='same', activation='relu')(input_img)
x = layers.Conv2D(64, 3, padding='same', activation='relu', strides=(2, 2))(x)
x = layers.Conv2D(64, 3, padding='same', activation='relu')(x)
x = layers.Conv2D(64, 3, padding='same', activation='relu')(x)

shape_before_flattening = K.int_shape(x)

x = layers.Flatten()(x)
x = layers.Dense(32, activation='relu')(x)

z_mean = layers.Dense(latent_dim)(x)
z_log_var = layers.Dense(latent_dim)(x)

接下来的代码将使用 z_mean 和 z_log_var 来生成(采样)一个潜在空间点 z。

# 潜在空间采样的函数

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)([z_mean, z_log_var])    # 封装为层

然后是解码器的实现:将向量 z 的尺寸调整为图像大小,然后使用几个卷积层来得到最终的图像输出。

# VAE 解码器网络

decoder_input = layers.Input(K.int_shape(z)[1:])
x = layers.Dense(np.prod(shape_before_flattening[1:]),
                 activation='relu')(decoder_input)
x = layers.Reshape(shape_before_flattening[1:])(x)
x = layers.Conv2DTranspose(32, 3,
                           padding='same',
                           activation='relu',
                           strides=(2, 2))(x)
x = layers.Conv2D(1, 3,
                  padding='same',
                  activation='sigmoid')(x)

decoder = Model(decoder_input, x)

z_decoded = decoder(z)

VAE 要用两个损失,所以不能直接写成 loss(input, target),我们需要编写一个自定义层,在其中使用内置的 add_loss 方法来创建需要的损失。

# 用于计算 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)
    
    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=None),所以也就不需要外部指定的目标数据(y=None)。

这里我们用 MNIST 去训练它,也就是生成手写数字的潜在空间。

from tensorflow.keras.datasets import mnist

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

(x_train, _), (x_test, y_test) = mnist.load_data()

x_train = x_train.astype('float32') / 255.
x_train = x_train.reshape(x_train.shape + (1,))

x_test = x_test.astype('float32') / 255.
x_test = x_test.reshape(x_test.shape + (1,))

vae.fit(x=x_train, y=None,
        shuffle=True,
        epochs=10,
        batch_size=batch_size,
        validation_data=(x_test, None))
WARNING:tensorflow:Output custom_variational_layer_1 missing from loss dictionary. We assume this was done on purpose. The fit and evaluate APIs will not be expecting any data to be passed to custom_variational_layer_1.
Model: "functional_7"
__________________________________________________________________________________________________
Layer (type)                    Output Shape         Param #     Connected to                     
==================================================================================================
input_1 (InputLayer)            [(None, 28, 28, 1)]  0                                            
__________________________________________________________________________________________________
conv2d_5 (Conv2D)               (None, 28, 28, 32)   320         input_1[0][0]                    
__________________________________________________________________________________________________
conv2d_6 (Conv2D)               (None, 14, 14, 64)   18496       conv2d_5[0][0]                   
__________________________________________________________________________________________________
conv2d_7 (Conv2D)               (None, 14, 14, 64)   36928       conv2d_6[0][0]                   
__________________________________________________________________________________________________
conv2d_8 (Conv2D)               (None, 14, 14, 64)   36928       conv2d_7[0][0]                   
__________________________________________________________________________________________________
flatten_1 (Flatten)             (None, 12544)        0           conv2d_8[0][0]                   
__________________________________________________________________________________________________
dense_4 (Dense)                 (None, 32)           401440      flatten_1[0][0]                  
__________________________________________________________________________________________________
dense_5 (Dense)                 (None, 2)            66          dense_4[0][0]                    
__________________________________________________________________________________________________
dense_6 (Dense)                 (None, 2)            66          dense_4[0][0]                    
__________________________________________________________________________________________________
lambda_1 (Lambda)               (None, 2)            0           dense_5[0][0]                    
                                                                 dense_6[0][0]                    
__________________________________________________________________________________________________
functional_5 (Functional)       (None, 28, 28, 1)    56385       lambda_1[0][0]                   
__________________________________________________________________________________________________
custom_variational_layer_1 (Cus (None, 28, 28, 1)    0           input_1[0][0]                    
                                                                 functional_5[0][0]               
==================================================================================================
Total params: 550,629
Trainable params: 550,629
Non-trainable params: 0
__________________________________________________________________________________________________
Train on 60000 samples, validate on 10000 samples
Epoch 1/10
60000/60000 [==============================] - 219s 4ms/sample - loss: 0.2173 - val_loss: 0.2016
...
Epoch 10/10
60000/60000 [==============================] - 234s 4ms/sample - loss: 0.1840 - val_loss: 0.1826

训练好模型,我们就可以使用 decoder 将任意潜在空间中的向量转换为图像。

# 从潜在空间中采样一组点,解码为图像

import matplotlib.pyplot as plt
from scipy.stats import norm

n = 15    # 显示 15x15个数
digit_size = 28
figure = np.zeros((digit_size * n, digit_size * n))

grid_x = norm.ppf(np.linspace(0.05, 0.95, n))  # ppf 函数对线性分隔的坐标进行变换,以生成潜在变量 z 的值
grid_y = norm.ppf(np.linspace(0.05, 0.95, n))

for i, yi in enumerate(grid_x):
    for j, xi in enumerate(grid_y):
        z_simple = np.array([[xi, yi]])
        z_simple = np.tile(z_simple, batch_size).reshape(batch_size, 2)
        x_decoded = decoder.predict(z_simple, batch_size=batch_size)
        digit = x_decoded[0].reshape(digit_size, digit_size)
        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()

png

书上到这里就结束了,并没有深入写之前提到的概念向量的应用

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Python深度学习之VAE 的相关文章

随机推荐

  • 史上最全!大数据开源框架技术扫盲

    一 目录 系统平台 Hadoop CDH HDP 监控管理 CM Hue Ambari Dr Elephant Ganglia Zabbix Eagle 文件系统 HDFS GPFS Ceph GlusterFS Swift BeeGFS
  • 图像构成与信号处理之二——信号滤波

    一 信号滤波与图像滤波 信号滤波和图像滤波都是信号处理的重要任务 它们在不同领域中有广泛的应用 一 信号滤波 信号滤波是对信号进行处理的过程 通过去除或抑制不需要的频率成分 以实现信号的平滑或去噪 信号滤波的目标是改变信号的频谱分布 以达到
  • WiFi-ESP8266入门开发(十三)-使用SPI

    注 对于ESP8266开源技术感兴趣的可以加群 我们一起探索交流学习 群号 579932824 群名 ESP8266开源技术交流群 介绍 串行外设接口 SPI 是摩托罗拉公司最初启动的总线接口连接协议 SPI接口使用四根线进行通信 因此也被
  • 【JS实用技巧篇】01-函数防抖

    JavaScript专栏 js实用技巧篇 该专栏博主会持续更新 目的是给大家分享一些非常实用的技巧 同时巩固自己的基础 共同进步 欢迎大家在评论区留言交流技术以及学习方法 心得方面的问题 你的一键三连是对我的最大支持 祝大家国庆快乐 文章目
  • 利用python进行数据分析之数据清洗与准备--小白笔记

    数据清洗和准备 处理缺失数据 import pandas as pd import numpy as np string data pd Series aardvark artichoke np nan avocado string dat
  • OpenStack部署之前需要安装哪些必备组件

    二 安全 下面的表格给出了需要密码的服务列表以及它们在指南中关联关系 密码 密码名称 描述 数据库密码 不能使用变量 数据库的root密码 ADMIN PASS admin 用户密码 CEILOMETER DBPASS Telemetry
  • three.js 创建文字的几种方法

    three js 创建文字的几种方法 1 DOM CSS 传统网页html实现 2 将文字绘制到画布中 并将其用作Texture 纹理 将文字保存为图片格式 再将其当作一张蒙皮材质 贴到某个物体上 3 在你所喜欢的3D软件里创建模型 并导出
  • 简单的数字水印加密技术

    最近我一个朋友问谍战情节里是怎样办到将数据隐藏到一般图片里的 正好有一段时间我也研究过这个问题 既然他问了干脆我就写出来和大家也一起分享一下吧 大都是自己琢磨的 如有更加专业的做法欢迎大家讨论啊 由于时间比较久远 当年研究的代码找了半天也没
  • [leetcode] 面试题 17.20. 连续中值

    有很多种形式可以实现中位数的求解 比如将所有的数放到一个数组中 然后sort一下获取中间的值 但这样在时间复杂度上不太优雅 为了能够更快的求解 可以使用对顶堆来求解 对顶堆通常用来实现动态k大 小 的问题 在这个题里 因为在往里面加数的过程
  • api接口的获取调用方式是什么?

    API接口的获取调用方式 通常分为以下几个步骤 1 注册账号并申请API Key 在API服务提供商的官方网站上注册账号 并申请API Key 包括通行证ID和密钥 以便后面的API调用验证 2 查看API接口文档 根据API服务提供商的官
  • OSI Network Layer 網絡層

    OSI Network Layer 網絡層 OSI 網絡層 網絡層數據報 IP 數據報結構 IP 地址 IP 地址分類 私有 IP 地址 子網 subnet 子網是什麼 子網掩碼 subnet mask 路由器 Router static
  • 手把手实现AI诗歌生成(AI写诗)

    本模型采用的是字符级别的诗歌生成 pytorch 环境 python3 X pytorch GPU或CPU版本都行 另外天有点冷 建议用GPU训练 电脑绝对比暖手宝好用 目录 项目文件结构 数据已经打包 1 数据集处理 2 构建模型与训练模
  • C# winform 调用webService 格式化程序尝试对消息反序列化时引发异常: 读取 XML 数据时,超出最大字符串内容长度配额 (8192)。

    错误信息 格式化程序尝试对消息反序列化时引发异常 尝试对参数 http ws xzsoft com 进行反序列化时出错 getWagonResponse InnerException 消息是 反序列化对象 属于类型 CallWeb Serv
  • Jina AI 受邀出席 WAIC 2023「科技无障碍」论坛,与行业专家共话 AI 普惠未来

    7 月 6 日 2023 世界人工智能大会 WAIC 在上海世博中心及世博展览馆开幕 并在浦东张江 徐汇西岸设分会场 同步在闵行等产业集聚区开展同期活动 本届大会由上海市人民政府和国家发改委 工信部 科技部 国家网信办 中国科学院 中国工程
  • 什么是css预处理器?

    CSS 预处理器定义了一种新的语言 其基本思想是 用一种专门的编程语言 为 CSS 增加了一些编程的特性 将 CSS 作为目标生成文件 然后开发者就只要使用这种语言进行web页面样式设计 通俗的说 CSS 预处理器用一种专门的编程语言 进行
  • qt 插件加载失败

    不小心把Release版本的QT NO DEBUG预定义宏删除了 导致插件加载提示 The plugin E Qt Trunk Software GT90 GT90Solution Win32 Release plugins Diagnos
  • MySQL数据库免安装版

    MySQL数据库免安装 1 安装配置启动 MySQL现在的版本主要分为 5 x 版本 现在互联网企业中的主流版本 包括 头条 美图 百度 腾讯等互联网公司主流的版本 8 x 版本 新增了一些了窗口函数 持久化配置 隐藏索引等其他功能 所以
  • linux将数字转16进制,使用linux命令将十六进制信息转换为二进制

    我的linux系统上有这个二进制文件 udit udit Dabba cat file enc Salted s bO lt 0 F Jw C LK l 使用hexdump命令 我看到它的信息如下 udit udit Dabba hexdu
  • SpringBoot:Druid 管理界面配置

    SpringBoot MyBatis MySQL Druid PageHeler 核心jar类
  • Python深度学习之VAE

    Deep Learning with Python 这篇文章是我学习 Deep Learning with Python 第二版 Fran ois Chollet 著 时写的系列笔记之一 文章的内容是从 Jupyter notebooks