python中argparse模块(VAE模型——pytorch实现)
论文传送门:https://arxiv.org/pdf/1312.6114.pdf
参考代码:GitHub - AntixK/PyTorch-VAE: A Collection of Variational Autoencoders (VAE) in PyTorch.
VAE的目的:构建一个解码器Decoder ,通过输入从标准正态分布中采样得到的采样变量X ,得到生成样本Y,使Y的分布与输入样本X的分布尽可能接近 ,从而完成图像生成任务 。
VAE的模型结构:编码器Encoder+解码器Decoder ,输入样本X经过编码器Encoder输出分布的均值和方差(对数) ,从该分布中采样得到采样变量X ,采样变量X经过解码器Decoder输出生成样本Y 。
VAE的方法:通过构建损失函数:
①使得生成样本Y接近输入样本X;
②使得编码器Encoder的输出分布接近标准正态分布 ,使得分布方差不为0 ,即采样变量X具有随机性 ,保证模型的生成能力 。
VAE的损失函数:Loss = recons_loss + w * kld_loss
recons_loss描述生成样本Y与输入样本X之间的距离 ,使用MSE计算;
kld_loss描述解码器Encoder输出分布与标准正态分布之间的距离 ,使用KL散度计算,化简过程如下图;
w为kld_loss项系数 。实现过程见代码82-85行 。
重参数技巧:直接从编码器Encoder输出的分布中采样难以实现 ,但我们知道其均值mu和标准差std ,于是我们从标准正态分布中采样得到Z,Z = mu + Z * std计算得到Z ,等价于从输出分布中进行采样得到Z 。实现过程见代码63-66行 。
import os import torch import torch.nn as nn import torch.nn.functional as F from torch.utils.data import DataLoader from torchvision import transforms, datasets from torchvision.utils import save_image from tqdm import tqdm class VAE(nn.Module): # 定义VAE模型 def __init__(self, img_size, latent_dim): # 初始化方法 super(VAE, self).__init__() # 继承初始化方法 self.in_channel, self.img_h, self.img_w = img_size # 由输入图片形状得到图片通道数C 、图片高度H 、图片宽度W self.h = self.img_h // 32 # 经过5次卷积后 ,最终特征层高度变为原图片高度的1/32 self.w = self.img_w // 32 # 经过5次卷积后,最终特征层宽度变为原图片高度的1/32 hw = self.h * self.w # 最终特征层的尺寸hxw self.latent_dim = latent_dim # 采样变量Z的长度 self.hidden_dims = [32, 64, 128, 256, 512] # 特征层通道数列表 # 开始构建编码器Encoder layers = [] # 用于存放模型结构 for hidden_dim in self.hidden_dims: # 循环特征层通道数列表 layers += [nn.Conv2d(self.in_channel, hidden_dim, 3, 2, 1), # 添加conv nn.BatchNorm2d(hidden_dim), # 添加bn nn.LeakyReLU()] # 添加leakyrelu self.in_channel = hidden_dim # 将下次循环的输入通道数设为本次循环的输出通道数 self.encoder = nn.Sequential(*layers) # 解码器Encoder模型结构 self.fc_mu = nn.Linear(self.hidden_dims[-1] * hw, self.latent_dim) # linaer ,将特征向量转化为分布均值mu self.fc_var = nn.Linear(self.hidden_dims[-1] * hw, self.latent_dim) # linear ,将特征向量转化为分布方差的对数log(var) # 开始构建解码器Decoder layers = [] # 用于存放模型结构 self.decoder_input = nn.Linear(self.latent_dim, self.hidden_dims[-1] * hw) # linaer ,将采样变量Z转化为特征向量 self.hidden_dims.reverse() # 倒序特征层通道数列表 for i in range(len(self.hidden_dims) - 1): # 循环特征层通道数列表 layers += [nn.ConvTranspose2d(self.hidden_dims[i], self.hidden_dims[i + 1], 3, 2, 1, 1), # 添加transconv nn.BatchNorm2d(self.hidden_dims[i + 1]), # 添加bn nn.LeakyReLU()] # 添加leakyrelu layers += [nn.ConvTranspose2d(self.hidden_dims[-1], self.hidden_dims[-1], 3, 2, 1, 1), # 添加transconv nn.BatchNorm2d(self.hidden_dims[-1]), # 添加bn nn.LeakyReLU(), # 添加leakyrelu nn.Conv2d(self.hidden_dims[-1], img_size[0], 3, 1, 1), # 添加conv nn.Tanh()] # 添加tanh self.decoder = nn.Sequential(*layers) # 编码器Decoder模型结构 def encode(self, x): # 定义编码过程 result = self.encoder(x) # Encoder结构,(n,1,32,32)-->(n,512,1,1) result = torch.flatten(result, 1) # 将特征层转化为特征向量,(n,512,1,1)-->(n,512) mu = self.fc_mu(result) # 计算分布均值mu,(n,512)-->(n,128) log_var = self.fc_var(result) # 计算分布方差的对数log(var),(n,512)-->(n,128) return [mu, log_var] # 返回分布的均值和方差对数 def decode(self, z): # 定义解码过程 y = self.decoder_input(z).view(-1, self.hidden_dims[0], self.h, self.w) # 将采样变量Z转化为特征向量 ,再转化为特征层,(n,128)-->(n,512)-->(n,512,1,1) y = self.decoder(y) # decoder结构,(n,512,1,1)-->(n,1,32,32) return y # 返回生成样本Y def reparameterize(self, mu, log_var): # 重参数技巧 std = torch.exp(0.5 * log_var) # 分布标准差std eps = torch.randn_like(std) # 从标准正态分布中采样,(n,128) return mu + eps * std # 返回对应正态分布中的采样值 def forward(self, x): # 前传函数 mu, log_var = self.encode(x) # 经过编码过程 ,得到分布的均值mu和方差对数log_var z = self.reparameterize(mu, log_var) # 经过重参数技巧 ,得到分布采样变量Z y = self.decode(z) # 经过解码过程 ,得到生成样本Y return [y, x, mu, log_var] # 返回生成样本Y ,输入样本X ,分布均值mu,分布方差对数log_var def sample(self, n, cuda): # 定义生成过程 z = torch.randn(n, self.latent_dim) # 从标准正态分布中采样得到n个采样变量Z ,长度为latent_dim if cuda: # 如果使用cuda z = z.cuda() # 将采样变量Z加载到GPU images = self.decode(z) # 经过解码过程 ,得到生成样本Y return images # 返回生成样本Y def loss_fn(y, x, mu, log_var): # 定义损失函数 recons_loss = F.mse_loss(y, x) # 重建损失,MSE kld_loss = torch.mean(0.5 * torch.sum(mu ** 2 + torch.exp(log_var) - log_var - 1, 1), 0) # 分布损失 ,正态分布与标准正态分布的KL散度 return recons_loss + w * kld_loss # 最终损失由两部分组成 ,其中分布损失需要乘上一个系数w if __name__ == "__main__": total_epochs = 100 # epochs batch_size = 64 # batch size lr = 5e-4 # lr w = 0.00025 # kld_loss的系数w num_workers = 8 # 数据加载线程数 image_size = 32 # 图片尺寸 image_channel = 1 # 图片通道 latent_dim = 128 # 采样变量Z长度 sample_images_dir = "sample_images" # 生成样本示例存放路径 train_dataset_dir = "../dataset/mnist" # 训练样本存放路径 os.makedirs(sample_images_dir, exist_ok=True) # 创建生成样本示例存放路径 os.makedirs(train_dataset_dir, exist_ok=True) # 创建训练样本存放路径 cuda = True if torch.cuda.is_available() else False # 如果cuda可用,则使用cuda img_size = (image_channel, image_size, image_size) # 输入样本形状(1,32,32) vae = VAE(img_size, latent_dim) # 实例化VAE模型 ,传入输入样本形状与采样变量长度 if cuda: # 如果使用cuda vae = vae.cuda() # 将模型加载到GPU # dataset and dataloader transform = transforms.Compose( # 图片预处理方法 [transforms.Resize(image_size), # 图片resize ,(28x28)-->(32,32) transforms.ToTensor(), # 转化为tensor transforms.Normalize([0.5], [0.5])] # 标准化 ) dataloader = DataLoader( # 定义dataloader dataset=datasets.MNIST(root=train_dataset_dir, # 使用mnist数据集 ,选择数据路径 train=True, # 使用训练集 transform=transform, # 图片预处理 download=True), # 自动下载 batch_size=batch_size, # batch size num_workers=num_workers, # 数据加载线程数 shuffle=True # 打乱数据 ) # optimizer optimizer = torch.optim.Adam(vae.parameters(), lr=lr) # 使用Adam优化器 # train loop for epoch in range(total_epochs): # 循环epoch total_loss = 0 # 记录总损失 pbar = tqdm(total=len(dataloader), desc=f"Epoch {epoch + 1}/{total_epochs}", postfix=dict, miniters=0.3) # 设置当前epoch显示进度 for i, (img, _) in enumerate(dataloader): # 循环iter if cuda: # 如果使用cuda img = img.cuda() # 将训练数据加载到GPU vae.train() # 模型开始训练 optimizer.zero_grad() # 模型清零梯度 y, x, mu, log_var = vae(img) # 输入训练样本X ,得到生成样本Y ,输入样本X ,分布均值mu ,分布方差对数log_var loss = loss_fn(y, x, mu, log_var) # 计算loss loss.backward() # 反向传播 ,计算当前梯度 optimizer.step() # 根据梯度 ,更新网络参数 total_loss += loss.item() # 累计loss pbar.set_postfix(**{"Loss": loss.item()}) # 显示当前iter的loss pbar.update(1) # 步进长度 pbar.close() # 关闭当前epoch显示进度 print("total_loss:%.4f" % (total_loss / len(dataloader))) # 显示当前epoch训练完成后,模型的总损失 vae.eval() # 模型开始验证 sample_images = vae.sample(25, cuda) # 获得25个生成样本 save_image(sample_images.data, "%s/ep%d.png" % (sample_images_dir, (epoch + 1)), nrow=5, normalize=True) # 保存生成样本示例(5x5)创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!