Autoencoder

参考:https://github.com/nathanhubens/Autoencoders

自动编码器(Autoencoders, AE)是一种神经网络,其目标是将输入复制到输出。它们通过将输入压缩到一个潜在空间表示中,然后从该表示中重建输出。这样的网络由两个部分组成:

编码器(Encoder):网络的这一部分负责将输入压缩到潜在空间表示中。可以用编码函数 h=𝑓(𝑥)ℎ=𝑓(𝑥) 来表示。

解码器(Decoder):这一部分旨在从潜在空间表示中重建输入。可以用解码函数 r=g(h)r=g(h) 来表示。

自动编码器的架构:整个自动编码器可以描述为函数 𝑔(𝑓(𝑥))=𝑟𝑔(𝑓(𝑥))=𝑟,其中希望 𝑟𝑟 尽可能接近原始输入 𝑥𝑥

AE与VAE概念

我现在要详细讲解一下变分自编码器(VAE)和传统的自编码器(AE)之间的区别。我对这两个模型有一些基本了解,但具体细节可能还不够深入。

首先,自编码器(AE)的基本概念是怎样的?它是一个无监督学习模型,用于进行降维和数据压缩。它的结构包括一个编码器(encoder)和解码器(decoder)。编码器将输入数据映射到潜在空间的一个低维向量,称为latent vector或潜在代码。然后,解码器再把这个潜在代码还原回原来的输入数据。这种结构在无监督学习中很常用,比如用于图像压缩或者生成类似的数据。

传统自动编码器(AE)

  • 传统AE学习的是一个确定性的映射,将输入样本映射到潜在空间中的一个固定向量
  • 这个向量是通过编码器网络直接计算出来的,没有考虑样本的概率分布
  • 由于AE没有显式地对潜在空间进行正则化或概率建模,潜在空间的结构可能是非连续的或不规则的

重建能力:

由于AE学习的是一个确定的映射,它能够很好地重建输入样本

但是,由于没有学习到潜在空间的概率分布,AE缺乏生成新样本的能力

变分自动编码器(VAE)

  • VAE学习的是输入样本在潜在空间中的概率分布,VAE通过KL散度对潜在空间进行正则化,使其接近标准正态分布(人为设定的高斯分布,当然也是可以是其它“已知”分布)
  • 这意味着潜在空间是连续的和结构化的,任何从中采样的向量都更有可能对应于合理的输入数据。
  • 编码器输出的是这个分布的参数:均值 zmeanz_{mean} 和方差 zlogvarz_{log var}

生成能力:

通过学习潜在空间的分布,VAE不仅能够重建输入样本,还能够从潜在空间中采样生成新样本

这种生成能力是通过重参数化技巧实现的,使得VAE可以在训练过程中进行反向传播。

AE与VAE区别

那么,传统的自编码器(AE)和变分自编码器(VAE)有什么不同呢?VAE引入了一些统计学的概念,特别是贝叶斯推断。这可能涉及到概率模型,而传统AE更像是一个确定性的模型。

首先,目标函数方面

  • AE的目标是最小化重建误差,也就是说,输入经过编码和解码后的输出与原输入之间的差异尽可能小。通常使用的是均方误差(MSE)或者交叉熵作为损失函数。
  • VAE的目标则不仅仅是重建数据,还引入了一个 latent space 的正则化项,目的是让潜在向量服从某种先验分布,通常是标准正态分布。这意味着VAE的损失函数分为两部分:重建损失和KL散度项
  • VAE损失函数的重建损失和KL散度项

接下来是模型结构的不同点。

编码器方面:AE的编码器输出的是一个确定性的latent vector,而VAE的编码器输出的是这个向量的均值和方差,这两个参数用于描述潜在变量的正态分布。

解码器方面:AE直接从latent vector映射回数据,而VAE可能同样如此,但潜在空间是通过重参数化技巧(比如将标准正态分布的噪声加到均值上)来实现的。

潜在空间的性质:在AE中,latent space是一个确定性的低维向量,它直接捕捉输入数据的特征。而在VAE中,latent space中的每个点都是一个概率分布,这使得模型具有生成能力,因为它可以采样新的潜在向量并将其解码为新数据。

生成能力方面:AE通常难以直接用于生成数据,因为它们没有明确的概率建模。而VAE由于其潜在空间是概率性的,并且可以通过对潜在变量进行采样来生成新样本,因此在图像生成等任务中表现更好。

鲁棒性方面:AE可能对噪声较为敏感,特别是在处理有噪声的数据时,重建可能会受到影响。而VAE通过引入正则化和概率建模,通常在处理带噪数据时表现更稳定,并且对模型的参数有一定的鲁棒性。

应用方面:AE常用于降维、特征提取和图像修复等任务,而VAE更多地应用于生成模型,比如图像生成、语音合成等领域。

总结一下,VAE通过引入概率建模和潜在空间的正则化,使得它在生成任务上表现更好,并且具有更好的鲁棒性和灵活性。而传统的AE更侧重于数据压缩和降维,功能相对单一。

从两者Encoder察觉出两者区别

在传统自动编码器(AE)和变分自动编码器(VAE)中,编码器(Encoder)的输出层设计有所不同,主要是因为它们的目标和结构不同。

传统自动编码器(AE)

在传统AE中,编码器的输出通常是一个确定性的潜在表示。

1
2
3
4
5
6
input_layer = Input(shape=(input_dim,))
hidden_layer = Dense(hidden_dim, activation='relu')(input_layer)
output_layer = Dense(input_dim, activation='sigmoid')(hidden_layer)
autoencoder = Model(inputs=input_layer, outputs=output_layer)
# 看下面outputs=hidden_layer
encoder = Model(inputs=input_layer, outputs=hidden_layer)

例如,hidden_layer=Dense(intermediate_dim, activation='relu')(self.input_layer) 表示将输入数据通过一个全连接层映射到一个中间维度的潜在空间。

这个潜在表示是一个固定的向量,直接用于解码器进行重建。

变分自动编码器(VAE)

1
2
3
4
5
6
7
input_layer = Input(shape=(input_dim,))
h = Dense(intermediate_dim, activation='relu')(input_layer)
z_mean = Dense(latent_dim)(h)
z_log_var = Dense(latent_dim)(h)
...
# 看下面outputs=z_mean
encoder = Model(inputs=input_layer, outputs=z_mean)

在VAE中,编码器的输出是潜在空间的概率分布参数,而不是一个确定性的向量。

z_mean = Dense(latent_dim)(self.h)z_log_var = Dense(latent_dim)(self.h) 分别表示潜在空间的均值和对数方差。

通过学习这两个参数,VAE可以在潜在空间中定义一个正态分布

采样过程:

VAE使用重参数化技巧从这个分布中采样潜在变量 z,以便在训练过程中进行反向传播。

这使得VAE能够生成新样本,并对潜在空间进行正则化。

总结

传统AE:专注于重建输入样本,缺乏生成新样本的能力。

VAE:通过学习潜在空间的概率分布,既能重建输入样本,又能生成新样本。

问题

给传统自动编码器(AE)一个随机的潜在空间向量,解码器会尝试将其映射回输入空间,它确实可以通过解码器生成一些输出数据

由于潜在空间没有经过正则化,随机向量可能不对应于训练数据的合理表示,因此生成的输出可能是模糊的或不真实的

效果通常不如变分自动编码器(VAE)好

变分自编码器

image-20250117220552557

在变分自动编码器(Variational Autoencoder, VAE)中,“变分”(variational)主要体现在以下几个方面:

潜在空间的概率建模

1
2
3
4
5
6
7
8
def __init__(self, input_dim, intermediate_dim, latent_dim, epsilon_std=1.0):
super().__init__(input_dim)
self.input_layer = Input(shape=(input_dim,))
self.h = Dense(intermediate_dim, activation='relu')(self.input_layer)
# Variational 体现在通过学习潜空间均值与对数方差来建模潜在分布,而不是直接学习潜在变量
# 可以对 z_mean 和 z_log_var 进行优化,从而间接优化 z 的分布
self.z_mean = Dense(latent_dim)(self.h)
self.z_log_var = Dense(latent_dim)(self.h)

这里的input_layerh与传统AE一样,但VAE通过学习潜在空间的均值 (z_mean) 和对数方差 (z_log_var) 来建模数据的潜在分布。这与传统的AE不同,后者直接学习一个确定性的潜在表示。

重参数化技巧

采样过程:VAE使用重参数化技巧来从潜在空间中采样。通过引入噪声 epsilon,VAE能够在训练过程中进行反向传播。这一过程在 sampling 函数中实现:

1
2
3
4
5
# sampling是一个函数,使用 z_mean 和 z_log_var 生成潜在变量 z
def sampling(self, args):
z_mean, z_log_var = args
epsilon = K.random_normal(shape=(K.shape(z_mean)[0], latent_dim), mean=0.0, stddev=epsilon_std)
return z_mean + K.exp(z_log_var / 2) * epsilon

重参数化技巧:它通过将不可导的随机采样过程转化为一个可导的过程,使得我们可以对潜在变量 z 的参数进行优化。以下是更详细的解释:

原理

不可导的采样过程:

在VAE中,潜在变量 z 是从一个由均值 zmeanz_{mean} 和方差 zlogvarz_{log var} 参数化的正态分布中采样的。

直接从这个分布中采样的过程是不可导的,因为随机采样本身不具备确定性。

解决方法(重参数化技巧)

通过引入一个独立于模型参数的随机噪声 epsilonepsilon,我们可以将采样过程表示为一个确定性函数:

z=z_{mean}+𝜖×exp⁡(z_{log var}/2)

其中,epsilonepsilon 是从标准正态分布 N(0,1)N(0, 1) 中采样的。

可导性:

由于 epsilonepsilon 是独立的随机噪声,zmeanz_meanzlogvarz_log_var 的梯度可以通过反向传播计算出来。

这使得我们可以对 zmeanz_meanzlogvarz_log_var 进行优化,从而间接优化 z 的分布。

优化过程

目标:通过优化 zmeanz_meanzlogvarz_log_var,使得潜在空间的分布能够更好地表示输入数据。

损失函数:VAE的损失函数包括重建损失和KL散度,后者用于正则化潜在空间的分布。

总结

重参数化技巧将不可导的采样过程转化为可导的过程,使得我们可以通过反向传播来优化潜在空间的参数。这是VAE能够有效训练的关键技术之一。通过这种方式,我们可以学习到一个更好的潜在表示,并生成新数据样本。

VAE的编码器和解码器结构

并将它们组合成完整的自动编码器模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def BuildModel(self):
# z是潜在变量,从潜在空间中采样
self.z = Lambda(self.sampling, output_shape=(self.latent_dim,))([self.z_mean, self.z_log_var])
# 解码器的中间层,将潜在空间的表示 z 转换为一个中间表示
self.decoder_h = Dense(self.intermediate_dim, activation='relu')
# 解码器的输出层,将中间表示解码为最终的输出数据。
self.decoder_mean = Dense(self.input_dim, activation='sigmoid')
# self.h_decoded = self.decoder_h(self.z)
# 将潜在空间的表示 z 通过解码器的中间层和输出层,得到最终的解码结果
self.x_decoded_mean = self.decoder_mean(self.decoder_h(self.z))
# VAE的目标是通过编码器和解码器网络,将输入数据 x 经过潜在空间的表示后,重建为 x_decoded_mean
self.autoencoder = Model(inputs=self.input_layer, outputs=self.x_decoded_mean)
# 构建编码器模型,输入为原始数据,输出为潜在空间的表示,这里使用z_mean,因为z_mean是潜在空间的均值
self.encoder = Model(inputs=self.input_layer, outputs=self.z_mean)

KL散度正则化

损失函数:VAE的损失函数包括重建损失(通常是交叉熵)和KL散度。KL散度用于衡量潜在空间的分布与标准正态分布之间的差异,从而对潜在空间进行正则化。这在 VAELossLayer 中实现:

1
2
3
4
5
6
7
8
9
class VAELossLayer(Layer):
def __init__(self, **kwargs):
super(VAELossLayer, self).__init__(**kwargs)

def call(self, inputs):
x, x_decoded_mean, z_mean, z_log_var = inputs
xent_loss = input_dim * binary_crossentropy(x, x_decoded_mean)
kl_loss = -0.5 * K.sum(1 + z_log_var - K.square(z_mean) - K.exp(z_log_var), axis=-1)
return K.mean(xent_loss + kl_loss)

训练

1
2
3
4
5
6
7
8
def Train(self,x_train,x_test):
#VAELossLayer:这是一个自定义的Keras层,用于计算VAE的损失,包括重建损失和KL散度
loss_layer = VAELossLayer()([self.input_layer, self.x_decoded_mean, self.z_mean, self.z_log_var])
self.autoencoder.add_loss(loss_layer)
#optimizer='adam':使用Adam优化器进行训练,因其在处理大规模数据和高维参数空间时表现良好。
self.autoencoder.compile(optimizer='adam', loss=None)
# VAE的训练目标是重建输入数据,因此输入和输出都是相同的数据集
self.autoencoder.fit(x_train, x_train, epochs=epochs, batch_size=batch_size, shuffle=True, validation_data=(x_test, x_test))

VAE的Model里设置:通过编码器和解码器网络,将输入数据x重建为输出数据x_decoded_mean。

训练目标:最小化输入数据x_train和重建数据x_decoded_mean之间的差异,同时正则化潜在空间的分布。

让vae输出的x_decoded_mean尽量像输入数据x_train

VAE完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class VariationalAutoencoder(BaseAutoencoder):
def __init__(self, input_dim, intermediate_dim, latent_dim, epsilon_std=1.0):
super().__init__(input_dim)
self.input_layer = Input(shape=(input_dim,))
self.h = Dense(intermediate_dim, activation='relu')(self.input_layer)
# Variational 体现在通过学习潜空间均值与对数方差来建模潜在分布,而不是直接学习潜在变量
self.z_mean = Dense(latent_dim)(self.h)
self.z_log_var = Dense(latent_dim)(self.h)

def sampling(self,args):
z_mean, z_log_var = args
# 引入噪声epsilon,使用重参数化技巧从潜空间采样
epsilon = K.random_normal(shape=(K.shape(z_mean)[0], latent_dim), mean=0.0, stddev=epsilon_std)
return z_mean + K.exp(z_log_var / 2) * epsilon

def BuildModel(self):
self.z = Lambda(self.sampling, output_shape=(self.latent_dim,))([self.z_mean, self.z_log_var])
self.decoder_h = Dense(self.intermediate_dim, activation='relu')
self.decoder_mean = Dense(self.input_dim, activation='sigmoid')
self.h_decoded = self.decoder_h(self.z)
self.x_decoded_mean = self.decoder_mean(self.h_decoded)
self.encoder = Model(inputs=self.input_layer, outputs=self.z_mean)
self.autoencoder = Model(inputs=self.input_layer, outputs=self.x_decoded_mean)

class VAELossLayer(Layer):
def __init__(self, **kwargs):
super(VAELossLayer, self).__init__(**kwargs)

def call(self, inputs):
x, x_decoded_mean, z_mean, z_log_var = inputs
xent_loss = input_dim * binary_crossentropy(x, x_decoded_mean)
# 计算KL散度,衡量潜在分布与标准正态分布的差异
kl_loss = -0.5 * K.sum(1 + z_log_var - K.square(z_mean) - K.exp(z_log_var), axis=-1)
return K.mean(xent_loss + kl_loss)

def Train(self,x_train,x_test):
loss_layer = VAELossLayer()([self.input_layer, self.x_decoded_mean, self.z_mean, self.z_log_var])
self.autoencoder.add_loss(loss_layer)
self.autoencoder.compile(optimizer='adam', loss=None)
self.autoencoder.fit(x_train, x_train, epochs=epochs, batch_size=batch_size, shuffle=True, validation_data=(x_test, x_test))
self.history=self.autoencoder.fit(x_train,x_train,epochs=EPOCHS,batch_size=BATCH_SIZE,shuffle=True,validation_data=(x_test,x_test))

参考

https://towardsdatascience.com/deep-inside-autoencoders-7e41f319999f

https://blog.csdn.net/quiet_girl/article/details/84401029