Section 2
1、论文中的i.i.d,表示的是独立同分布,是一种严谨的说法,通俗来看其实指的就是我们所经常见到的数据集中的特征,也可以理解为输入,如果是图结构的数据的话就是节点特征,这个独立同分布是进行建模的一个大前提。
2、思考:
a、注意针对于论文中概率建模的理解,论文中使用概率机型建模,不代表建模的物体是有概率的,比如说 小明爱打篮球,这是一个客观存在,如果使用概率建模,表现的形式是小明爱打篮球的概率为p。为什么要用概率去建模呢?
原因在于概率可以模拟更多复杂的场景,因为要通过一个模型来得到许多各种各样的变量之间的关系是不容易的,如果要建模成确定的模型,那么必然需要使用规则,而规则本身是死的,不灵活。因此,使用概率建模的时候,我们只需要知道小明和篮球的概率相对于小明和其他运动的概率是最大的,那么就可以确定小明爱打篮球这一事实。当然,更复杂的情况是,小明既爱打篮球又爱打乒乓球,那么这个时候概率将会是一个均衡的状态,具体的不深入,有个初步的感受就可以了
b、其次就是论文中的φ和θ,理论上来看二者对应的是都是x和z的联合分布,按理说应该是相同的,但是由于自编码器中使用的是神经网络的建模方式,只能向前传播,因此在训练的时候只能够分开计算,即使是使用变分方法也没有办法进行拆分。
3、
a、φ表示的是编码器部分的参数,θ表示的是解码器中的参数(后面有进一步的理解)
b、隐变量(latent variable),z如果在自编码器AE中其实指的就是由x经过encoder得到的变量,其实本质上就是embedding(x的表征,x就是样本,样本就是具体的客观存在,比如 human这个单词的embedding)
Section 2.1
论文中的pθ(z)和pθ(x|z)的参数是一样的,注意这两个式子都来源于x与z的联合分布pθ(x,z),前者属于联合分布的边际分布,后者属于条件分布,从理解上看后者也属于后验,前者属于先验。因此实际上模型建模的是联合分布,使用边际似然的原因我猜测是因为和对联合分布的最大似然估计是等效的,比如x1和z1如果是匹配的,那么其pθ(x1,z1)应当是概率最大的,要确定这个当然就是pθ(x1|z1)也要最大了,先验我猜测也是有这么一种转化,肯定是等效的。
section 2.2
①根据边际似然,论文中得到了
然而此时KL散度无法计算 因为从论文的思路中可以看到,θ建模的是对隐变量z的解码过程,在神经网络向前传播的特性下,它无法根据x给出z,因此,作者考虑对其进行变分
变分之后的(2)式子,对其优化,是不等号右边的项最大,就可以让边际似然最大,因为左边≥右边,因此下界最大的时候,边际似然也相应的最大。
在计算过程中,也就是(3)式子,此时KL散度相当于是整个模型的loss,后面的期望相当于是解码器的loss,两者联合构成了整个模型的loss function。
然而,期望项在计算梯度的时候比较困难,因此说在使用变分技巧的情况下,使用常规的AE自编码器方法,根据编码器直接得到隐变量z再对z进行
解码的思路走不通了,由此引入了贝叶斯的思想
Section 2.3
那么关键就是贝叶斯思想是如何体现的,最关键的地方体现自论文中提到的重参数化过程,重参数化的核心指的就是编码器的输出不必再是隐变量z,
而是作为一个分布的参数,每一个输入x经过编码器会对应一个分布,式子如下:
φ就是编码器Encoder的参数,它会确定一个分布,在VAE中的编码器和解码器中间的重参数化过程前会对编码器的参数进行解析,得到分布的
”location-scale” family of distributions,location其实可以看做是分布的峰值点,scale表示的是分布的范围。在得到了location和scale之后,(4)式
就可以进行计算了,因为该式子中的可微转移函数的定义为g(.) = location + scale · ϵ 。其中ϵ为噪音,通常根据使用一个标准的(0,I)或者(0,1)高斯分布
抽样得到。
Section 2.4
在有了前面三个小节的铺垫之后,会有一个疑问那就是Encoder的参数应该确定哪一种分布,这属于一种先验,论文中说可以建模为Laplace, Elliptical,
Student’s t, Logistic, Uniform, Triangular and Gaussian distributions.等分布,但是一般情况下,最好用的就是高斯分布,此时2.3中提到的locaiton和
scale分别指的就是高斯分布的μ和σ,如果是单变量的就是值,多变量的就是向量。
分享:
一个非常easy的代码实践,来源于kevin frans的一份代码及其英文解释http://kvfrans.com/variational-autoencoders-explained/。但是我已经对其根据
进行了适当的修改,能够适应现有的pytorch版本,此外添加了详细的中文注释,希望能帮助到大家:
import torch
from torch.autograd import Variable #pyton中自动求梯度
import numpy as np
import torch.nn.functional as F #相当于导入了functional一个包,里面包含了池化、激活、正则化、损失函数等函数的定义
import torchvision
import torch.optim as optim
from torchvision import transforms #目的是对图像进行变换
from torch import nn
import matplotlib.pyplot as plt
class Normal(object):#正态分布,mu指的是均值=μ,sigma指的是σ方差
def __init__(self,mu,sigma,log_sigma,v=None,r=None):
self.mu=mu
self.sigma=sigma
self.logsigma=log_sigma
dim=mu.get_shape()
if v is None:
v=torch.FloatTensor(*dim)
if r is None:
r=torch.FloatTensor(*dim)
self.v=v
self.r=r
class Encoder(torch.nn.Module):
def __init__(self,D_in,H,D_out):#定义好输入维度,隐藏层维度和输出维度
super(Encoder,self).__init__()
self.linear1=torch.nn.Linear(D_in,H) #初始化一个全连接层
self.linear2=torch.nn.Linear(H,D_out)
def forward(self,x):
x=F.relu(self.linear1(x)) #使用relu线性单元对第一层进行计算
return F.relu(self.linear2(x))
#编码器部分定义完成,使用了两个全连接层,激活函数是relu函数
class Decoder(torch.nn.Module):
def __init__(self,D_in,H,D_out):
super(Decoder,self).__init__()
self.linear1=nn.Linear(D_in,H)
self.linear2=nn.Linear(H,D_out)
def forward(self,x):
x=F.relu(self.linear1(x))
return F.relu(self.linear2(x))
class VAE(torch.nn.Module):
latent_dim=8 #隐藏层的维度是8
def __init__(self,encoder,decoder):#torch.nn.Module可以帮助不同模块的组合
super(VAE,self).__init__()
self.encoder=encoder
self.decoder=decoder
self._enc_mu=torch.nn.Linear(100,8) #Encoder的正态分布均值mu=μ
self._enc_log_sigma=torch.nn.Linear(100,8) #encoder的方差的对数
def _sample_latent(self,h_enc):#关键来了,变分自编码器的核心在于中间的抽样过程,编码器的参数蕴含的是分布
"""
返回隐藏层所定义的正态分布样本 z~N(μ=mu,σ^2=sigma^2)
"""
mu=self._enc_mu(h_enc) #将编码器的隐藏层中送入均值μ层中
log_sigma=self._enc_log_sigma(h_enc) #python中log默认以e为底
sigma=torch.exp(log_sigma)
std_z=torch.from_numpy(np.random.normal(0,1,size=sigma.size())).float()
#std_z定一个是一个以标准高斯分布为基础的随机数生成器,每次生成的大小跟隐藏层输出的维度相同
self.z_mean=mu
self.z_variance=sigma #sigma指的是高斯分布的方差
return mu+sigma*Variable(std_z,requires_grad=False) #重参数话,也就是变分过程
def forward(self,state):
h_enc=self.encoder(state)
z=self._sample_latent(h_enc)
return self.decoder(z)
def latent_loss(z_mean,z_variance):#表示的是隐变量的loss function,也就是更新神经网络层的各个参数时所使用的损失函数
mean_sq=z_mean*z_mean #sq表示平方
stddev_sq=z_variance #方差
return 0.5 * torch.mean(mean_sq+stddev_sq-torch.log(stddev_sq)-1)
if __name__=='__main__':
input_dim=28*28 #输入的维度大小,手写数据集,一张图片的像素点大小,mnist中的图像大小就是28*28
batch_size=32 #批处理大小
transform=transforms.Compose([transforms.ToTensor()])
#得到一个transform方法,目的是对图像进行一定的处理
mnist=torchvision.datasets.MNIST('./',download=True,transform=transform)
#使用transform对MNIST数据集中的数据进行变换
dataloader=torch.utils.data.DataLoader(mnist,batch_size=batch_size,
shuffle=True,num_workers=2)
print('Number of samples: ',len(mnist))
encoder=Encoder(input_dim,100,100)
decoder=Decoder(8,100,input_dim)
vae=VAE(encoder,decoder)
criterion=nn.MSELoss() #均方误差损失函数
optimizer=optim.Adam(vae.parameters(),lr=0.0001)
l=None#用于记录计算过程中模型loss的变化
for epoch in range(5):
for i,data in enumerate(dataloader,0):
inputs,classes=data #输入的特征,标签类别
inputs,classes=Variable(inputs.resize_(batch_size,input_dim)),Variable(classes)
#resize_()函数的目的是改变张量的维度,注意是任意改变,如果元素数量不够则会自动补全,但是补全的值是什么不固定,暂时只研究到这,如果元素数量多了那么会自动丢弃
#这里输入的shape是(32,1,28,28)处理之后得到了(32,28*28)的tensor作为输入
optimizer.zero_grad()#梯度置0,不管什么模型都需要这么做,除非梯度。。。
dec=vae(inputs) #将输入传入模型中
ll=latent_loss(vae.z_mean,vae.z_variance) #隐藏层的loss
loss=criterion(dec,inputs)+ll #输入和输出的loss + 隐藏层loss
loss.backward()
optimizer.step()
l=loss.data.item()
### pytorch 0.4后,python number的获取统一通过 .item()方式实现,也就是tensor的取值要加.item()不论是单个值还是list
print(epoch,l) #执行到第几步,loss是多少
plt.imshow(vae(inputs).data[0].numpy().reshape(28,28),cmap='gray')
plt.show(block=True)
我使用的版本是python3.8+pytorch1.7