手把手教你设计并训练一个VAE生成模型

  • 1 VAE简介
  • 2 生成手写数字实践
  • 3 调用生成模型生成指定数字

1 VAE简介

VAE(Variational Autoencoder)变分自编码器是一种使用变分推理的自编码器,其主要用于生成模型。 VAE 的编码器是模型的一部分,用于将输入数据压缩成潜在表示,即编码。

VAE 编码器包括两个子网络:一个是推断网络,另一个是生成网络。推断网络输入原始输入数据,并输出两个参数:均值和方差。这些参数用于描述编码的潜在分布。生成网络输入潜在编码并输出重构的输入数据。

为了从输入数据中学习潜在表示,VAE 采用变分推理的方法。变分推理是一种通过最大化对数似然来学习潜在分布的方法。首先,我们假设潜在分布为高斯分布,然后通过最大化对数似然估计参数。这些参数(均值和方差)由推断网络学习。

对于给定的输入数据,推断网络学习参数,然后使用这些参数计算潜在分布。我们从潜在分布中采样一个编码,然后将它输入生成网络。生成网络使用这个编码重构原始输入数据。最后,我们使用重构数据和原始数据之间的差异来计算损失。这个损失用来衡量 VAE 对原始输入数据的重构精度。

最后,VAE 编码器的目的是学习一种潜在表示,使得重构输入数据的损失最小。这个潜在表示可以用于生成新的数据,或者用于其他目的,如数据压缩或降维。
总的来说,VAE 编码器是一种使用变分推理的自编码器,用于学习潜在表示,并使用这个表示重构输入数据。

2 生成手写数字实践

VAE 生成模型的最简单例子可能是用于生成手写数字的模型。手写数字数据集通常被编码为 28x28 像素的灰度图像。我们可以使用 VAE 来学习生成新的手写数字图像。

# 加载 MNIST 数据集
transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.5,), (0.5,))
])
mnist = datasets.MNIST(root='.', download=True, transform=transform)

首先,我们需要定义 VAE 的网络结构。这个 VAE 的编码器可能包括一个卷积层,用于提取图像特征,以及一个全连接层,用于将卷积层的输出压缩成潜在表示。编码器的输出是两个参数:均值和方差。

# 定义 VAE 编码器
class VAEEncoder(nn.Module):
    def __init__(self, input_size, hidden_size, latent_size):
        super().__init__()
        self.fc1 = nn.Linear(input_size, hidden_size)
        self.fc2 = nn.Linear(hidden_size, latent_size * 2)
    def forward(self, x):
        x = torch.relu(self.fc1(x))
        x = self.fc2(x)
        mu, log_var = x.split(latent_size, dim=1)
        return mu, log_var

然后,我们可以使用这些参数计算潜在分布,并从中采样潜在编码。潜在编码是我们用于生成新图像的输入。我们的 VAE 还包括一个解码器,用于将潜在编码解码为图像。解码器可能包括一个全连接层和一个卷积层,用于将潜在编码转换为图像。

# 定义 VAE 解码器
class VAEDecoder(nn.Module):
    def __init__(self, latent_size, hidden_size, output_size):
        super().__init__()
        self.fc1 = nn.Linear(latent_size, hidden_size)
        self.fc2 = nn.Linear(hidden_size, output_size)
    def forward(self, x):
        x = torch.relu(self.fc1(x))
        x = torch.sigmoid(self.fc2(x))
        return x

最后,我们使用重构图像和原始图像之间的差异来计算 VAE 的损失。我们可以使用这个损失来训练 VAE,以使得重构图像尽可能接近原始图像。当我们的 VAE 训练完成后,我们就可以使用它来生成新的手写数字图像。

# 定义 VAE 损失函数
def vae_loss(recon, x, mu, log_var):
    recon_loss = nn.BCELoss(reduction='sum')(recon, x)
    kl_loss = -0.5 * torch.sum(1 + log_var - mu.pow(2) - log_var.exp())
    return recon_loss + kl_loss

为了生成新的图像,我们可以从 VAE 的潜在分布中采样一个潜在编码,然后将它输入 VAE 的解码器。解码器会使用这个编码生成一个新的图像。我们可以使用不同的潜在编码生成不同的图像,从而生成一系列新的手写数字图像。

 # 使用 VAE 生成图像
    with torch.no_grad():
        z = torch.randn(1, latent_size)
        image = model.decoder(z).view(28, 28)
        image = image.detach().numpy()
        plt.imshow(image, cmap='gray')
        plt.show() 

这是一个 VAE 生成模型的最简单例子。 VAE 可以用于生成各种各样的数据,包括图像、文本、音频和视频。 VAE 的更复杂的例子可能包括更复杂的网络结构、更多的层和更多的参数。

下面是使用 PyTorch 实现 VAE 生成手写数字的完整代码:

# VAE.py
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
import matplotlib.pyplot as plt
# 定义 VAE 编码器
class VAEEncoder(nn.Module):
    def __init__(self, input_size, hidden_size, latent_size):
        super().__init__()
        self.fc1 = nn.Linear(input_size, hidden_size)
        self.fc2 = nn.Linear(hidden_size, latent_size * 2)
    def forward(self, x):
        x = torch.relu(self.fc1(x))
        x = self.fc2(x)
        mu, log_var = x.split(latent_size, dim=1)
        return mu, log_var
# 定义 VAE 解码器
class VAEDecoder(nn.Module):
    def __init__(self, latent_size, hidden_size, output_size):
        super().__init__()
        self.fc1 = nn.Linear(latent_size, hidden_size)
        self.fc2 = nn.Linear(hidden_size, output_size)
    def forward(self, x):
        x = torch.relu(self.fc1(x))
        x = torch.sigmoid(self.fc2(x))
        return x
# 定义 VAE 模型
class VAE(nn.Module):
    def __init__(self, input_size, hidden_size, latent_size):
        super().__init__()
        self.encoder = VAEEncoder(input_size, hidden_size, latent_size)
        self.decoder = VAEDecoder(latent_size, hidden_size, input_size)
    def forward(self, x):
        mu, log_var = self.encoder(x)
        std = torch.exp(0.5 * log_var)
        eps = torch.randn_like(std)
        z = mu + std * eps
        recon = self.decoder(z)
        return recon, mu, log_var
# 定义 VAE 损失函数
def vae_loss(recon, x, mu, log_var):
    recon_loss = nn.BCELoss(reduction='sum')(recon, x)
    kl_loss = -0.5 * torch.sum(1 + log_var - mu.pow(2) - log_var.exp())
    return recon_loss + kl_loss
# 加载 MNIST 数据集
transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.5,), (0.5,))
])
mnist = datasets.MNIST(root='.', download=True, transform=transform)
# 定义训练参数
batch_size = 64
lr = 1e-3
num_epochs = 20
# 定义数据加载器
data_loader = DataLoader(mnist, batch_size=batch_size, shuffle=True) # shuffle=True 打乱数据
# 定义模型、优化器和损失函数
# 定义 VAE 模型
input_size = 28 * 28
hidden_size = 256
latent_size = 64
model = VAE(input_size, hidden_size, latent_size)
# 定义优化器
optimizer = optim.Adam(model.parameters(), lr=lr)
if __name__ == '__main__': # 仅在当前文件中运行时才执行以下代码
    # 训练 VAE 模型
    for epoch in range(num_epochs):
        epoch_loss = 0.0
        for x, _ in data_loader:
            x = x.view(-1, input_size)
            recon, mu, log_var = model(x)
            loss = vae_loss(recon, x, mu, log_var)
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()
            epoch_loss += loss.item()
        print(f'Epoch {epoch+1} loss: {epoch_loss / len(mnist):.4f}')              
    # 使用 VAE 生成图像
    with torch.no_grad():
        z = torch.randn(1, latent_size)
        image = model.decoder(z).view(28, 28)
        image = image.detach().numpy()
        plt.imshow(image, cmap='gray')
        plt.show() 
    # 保存模型
    torch.save(model.state_dict(), 'vae.pth')

3 调用生成模型生成指定数字

上面我们已经训练好了 VAE 模型,如果想使用该模型生成指定的数字,则不需要再次训练模型。我们可以直接使用训练好的模型,通过指定的 latent variables 生成想要的数字。

要做到这一点,需要按照以下步骤操作:

  1. 选择一个你想要生成的数字的图像作为样本,如:mnist [9][0]=4, [7][0]=3, [0][0]=5
  2. 使用 VAE 的编码器将该图像编码为 latent variables
  3. 将生成的 latent variables 作为输入传递给 VAE 的解码器,生成你想要的数字图像

下面是实现上述操作的示例代码:

在另一个文件 generate.py 中调用上面已经训练好的模型:

# generate.py 
import torch
import matplotlib.pyplot as plt
from VAE import model, input_size, mnist # 从 VAE.py 中导入模型、输入大小和 MNIST 数据集
# 加载已训练好的模型
model.load_state_dict(torch.load('vae.pth'))
# 选择mnist的样本图像 
sample_image = mnist[0][0] # mnist[0][0]是数字5的数据集
# 使用 VAE 的编码器将样本图像编码为 latent variables
mu, log_var = model.encoder(sample_image.view(-1, input_size))
# 将生成的 latent variables 作为输入传递给 VAE 的解码器,生成数字图像
generated_image = model.decoder(mu).view(28, 28)
# 显示原始图像和生成的图像
plt.subplot(1, 2, 1)
plt.title('Original Image')
plt.imshow(sample_image.view(28, 28), cmap='gray')
plt.subplot(1, 2, 2)
plt.title('Generated Image')
plt.imshow(generated_image.detach().numpy(), cmap='gray')
plt.show()

在上面的代码中,使用了 MNIST 数据集的第0个样本图像作为输入,所以模型生成的数字应该是数据集中第一个样本的数字,5。如果我们想生成不同的数字,可以使用不同的样本图像,例如 mnist[1][0],mnist[2][0] 等。

上面首先使用 VAE 的编码器将样本图像编码为 latent variables,然后使用 VAE 的解码器生成数字图像,再使用model.load_state_dict() 加载已保存的模型。最后,使用已加载的模型生成数字图像并显示。效果如下图:
手把手教你训练一个VAE生成模型一生成手写数字
上面模型的生成性能可能不是最好的,如果我们想改变 VAE 模型的表现,例如生成更加细腻、清晰的图像,则可能需要再次训练模型。我们可以通过调整训练参数,例如批次大小、学习率等来实现。

此外,我们还可以尝试改变 VAE 模型的结构,例如增加或减少网络层的数量,或者改变每一层的单元数量来提高模型的表现。这需要对深度学习和神经网络有较深的理解,并且可能需要多次尝试和调整才能找到最优的网络结构。

为了提升生成模型的性能,我们可以尝试以下操作:

欢迎关注,感谢支持!

发表回复