论文传送门: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)
我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co
我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何
我有一个包含模块的模型。我想在模块中覆盖模型的访问器方法。例如:classBlah这显然行不通。有什么想法可以实现吗? 最佳答案 您的代码看起来是正确的。我们正在毫无困难地使用这个确切的模式。如果我没记错的话,Rails使用#method_missing作为属性setter,因此您的模块将优先,阻止ActiveRecord的setter。如果您正在使用ActiveSupport::Concern(参见thisblogpost),那么您的实例方法需要进入一个特殊的模块:classBlah
我有一个表单,其中有很多字段取自数组(而不是模型或对象)。我如何验证这些字段的存在?solve_problem_pathdo|f|%>... 最佳答案 创建一个简单的类来包装请求参数并使用ActiveModel::Validations。#definedsomewhere,atthesimplest:require'ostruct'classSolvetrue#youcouldevencheckthesolutionwithavalidatorvalidatedoerrors.add(:base,"WRONG!!!")unlesss
我想向我的Controller传递一个参数,它是一个简单的复选框,但我不知道如何在模型的form_for中引入它,这是我的观点:{:id=>'go_finance'}do|f|%>Transferirde:para:Entrada:"input",:placeholder=>"Quantofoiganho?"%>Saída:"output",:placeholder=>"Quantofoigasto?"%>Nota:我想做一个额外的复选框,但我该怎么做,模型中没有一个对象,而是一个要检查的对象,以便在Controller中创建一个ifelse,如果没有检查,请帮助我,非常感谢,谢谢
我有一些非常大的模型,我必须将它们迁移到最新版本的Rails。这些模型有相当多的验证(User有大约50个验证)。是否可以将所有这些验证移动到另一个文件中?说app/models/validations/user_validations.rb。如果可以,有人可以提供示例吗? 最佳答案 您可以为此使用关注点:#app/models/validations/user_validations.rbrequire'active_support/concern'moduleUserValidationsextendActiveSupport:
我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden
对于Rails模型,是否可以/建议让一个类的成员不持久保存到数据库中?我想将用户最后选择的类型存储在session变量中。由于我无法从我的模型中设置session变量,我想将值存储在一个“虚拟”类成员中,该成员只是将值传递回Controller。你能有这样的类(class)成员吗? 最佳答案 将非持久属性添加到Rails模型就像任何其他Ruby类一样:classUser扩展解释:在Ruby中,所有实例变量都是私有(private)的,不需要在赋值前定义。attr_accessor创建一个setter和getter方法:classUs
我有一个正在构建的应用程序,我需要一个模型来创建另一个模型的实例。我希望每辆车都有4个轮胎。汽车模型classCar轮胎模型classTire但是,在make_tires内部有一个错误,如果我为Tire尝试它,则没有用于创建或新建的activerecord方法。当我检查轮胎时,它没有这些方法。我该如何补救?错误是这样的:未定义的方法'create'forActiveRecord::AttributeMethods::Serialization::Tire::Module我测试了两个环境:测试和开发,它们都因相同的错误而失败。 最佳答案
ruby如何管理内存。例如:如果我们在执行过程中采用C程序,则以下是内存模型。类似于这个ruby如何处理内存。C:__________________|||stack|||------------------||||------------------|||||Heap|||||__________________|||data|__________________|text|__________________Ruby:? 最佳答案 Ruby中没有“内存”这样的东西。Class#allocate分配一个对象并返回该对象。这就是程序