Autoencoder
参考:https://github.com/nathanhubens/Autoencoders
自动编码器(Autoencoders, AE)是一种神经网络,其目标是将输入复制到输出。它们通过将输入压缩到一个潜在空间表示中,然后从该表示中重建输出。这样的网络由两个部分组成:
编码器(Encoder):网络的这一部分负责将输入压缩到潜在空间表示中。可以用编码函数 h=f(x) 来表示。
解码器(Decoder):这一部分旨在从潜在空间表示中重建输入。可以用解码函数 r=g(h) 来表示。
自动编码器的架构:整个自动编码器可以描述为函数 g(f(x))=r,其中希望 r 尽可能接近原始输入 x。
AE与VAE概念
我现在要详细讲解一下变分自编码器(VAE)和传统的自编码器(AE)之间的区别。我对这两个模型有一些基本了解,但具体细节可能还不够深入。
首先,自编码器(AE)的基本概念是怎样的?它是一个无监督学习模型,用于进行降维和数据压缩。它的结构包括一个编码器(encoder)和解码器(decoder)。编码器将输入数据映射到潜在空间的一个低维向量,称为latent vector或潜在代码。然后,解码器再把这个潜在代码还原回原来的输入数据。这种结构在无监督学习中很常用,比如用于图像压缩或者生成类似的数据。
传统自动编码器(AE)
- 传统AE学习的是一个确定性的映射,将输入样本映射到潜在空间中的一个固定向量
- 这个向量是通过编码器网络直接计算出来的,没有考虑样本的概率分布
- 由于AE没有显式地对潜在空间进行正则化或概率建模,潜在空间的结构可能是非连续的或不规则的。
重建能力:
由于AE学习的是一个确定的映射,它能够很好地重建输入样本。
但是,由于没有学习到潜在空间的概率分布,AE缺乏生成新样本的能力。
变分自动编码器(VAE)
- VAE学习的是输入样本在潜在空间中的概率分布,VAE通过KL散度对潜在空间进行正则化,使其接近标准正态分布(人为设定的高斯分布,当然也是可以是其它“已知”分布)
- 这意味着潜在空间是连续的和结构化的,任何从中采样的向量都更有可能对应于合理的输入数据。
- 编码器输出的是这个分布的参数:均值 zmean 和方差 zlogvar。
生成能力:
通过学习潜在空间的分布,VAE不仅能够重建输入样本,还能够从潜在空间中采样生成新样本。
这种生成能力是通过重参数化技巧实现的,使得VAE可以在训练过程中进行反向传播。
AE与VAE区别
那么,传统的自编码器(AE)和变分自编码器(VAE)有什么不同呢?VAE引入了一些统计学的概念,特别是贝叶斯推断。这可能涉及到概率模型,而传统AE更像是一个确定性的模型。
首先,目标函数方面
- AE的目标是最小化重建误差,也就是说,输入经过编码和解码后的输出与原输入之间的差异尽可能小。通常使用的是均方误差(MSE)或者交叉熵作为损失函数。
- VAE的目标则不仅仅是重建数据,还引入了一个 latent space 的正则化项,目的是让潜在向量服从某种先验分布,通常是标准正态分布。这意味着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)
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) ...
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)好
变分自编码器
在变分自动编码器(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) self.z_mean = Dense(latent_dim)(self.h) self.z_log_var = Dense(latent_dim)(self.h)
|
这里的input_layer
和h
与传统AE一样,但VAE通过学习潜在空间的均值 (z_mean) 和对数方差 (z_log_var) 来建模数据的潜在分布。这与传统的AE不同,后者直接学习一个确定性的潜在表示。
重参数化技巧
采样过程:VAE使用重参数化技巧来从潜在空间中采样。通过引入噪声 epsilon,VAE能够在训练过程中进行反向传播。这一过程在 sampling 函数中实现:
1 2 3 4 5
| 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 是从一个由均值 zmean 和方差 zlogvar 参数化的正态分布中采样的。
直接从这个分布中采样的过程是不可导的,因为随机采样本身不具备确定性。
解决方法(重参数化技巧)
通过引入一个独立于模型参数的随机噪声 epsilon,我们可以将采样过程表示为一个确定性函数:
z=z_{mean}+𝜖×exp(z_{log var}/2)
其中,epsilon 是从标准正态分布 N(0,1) 中采样的。
可导性:
由于 epsilon 是独立的随机噪声,zmean 和 zlogvar 的梯度可以通过反向传播计算出来。
这使得我们可以对 zmean 和 zlogvar 进行优化,从而间接优化 z 的分布。
优化过程
目标:通过优化 zmean 和 zlogvar,使得潜在空间的分布能够更好地表示输入数据。
损失函数:VAE的损失函数包括重建损失和KL散度,后者用于正则化潜在空间的分布。
总结
重参数化技巧将不可导的采样过程转化为可导的过程,使得我们可以通过反向传播来优化潜在空间的参数。这是VAE能够有效训练的关键技术之一。通过这种方式,我们可以学习到一个更好的潜在表示,并生成新数据样本。
VAE的编码器和解码器结构
并将它们组合成完整的自动编码器模型
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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.x_decoded_mean = self.decoder_mean(self.decoder_h(self.z)) self.autoencoder = Model(inputs=self.input_layer, outputs=self.x_decoded_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): 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))
|
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) 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 = 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_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