生成模型
生成模型的概念属于概率统计和机器学习,是指一系列用于随机生成可观测数据的模型。简而言之,我们就是要生成的样本和实际的样本尽可能的相似,其主要功能有两个:
自动编码器
自动编码器最开始是一种数据压缩的方法,其特点有:
- 跟数据的相关程度很高:这意味着自动编码器只能压缩与训练数据相似的数据
- 有损压缩
现在自动编码器主要应用于:
![8CzFi.png](https://s1.328888.xyz/2022/04/26/8CzFi.png)
如图所示自动编码器结构,其主要由两部分组成:编码器(Encoder)和解码器(Decoder),编码器和解码器都可以是任意的模型,主要使用神经网络模型。输入数据经过神经网络降维到一个编码,接着又通过另外一个神经网络去解码得到一个与输入数据一模一样的生成数据。然后通过比较这两个数据,通过最小化他们之间的差异来训练网络中的参数。
训练完成后,拿出解码器,随机传入一个编码,就能生成一个和原始数据差不多的生成数据。
import os
import torch from torch.autograd import Variable from torch import nn from torch.utils.data import DataLoader
from torchvision.datasets import MNIST from torchvision import transforms as tfs from torchvision.utils import save_image
im_tfs = tfs.Compose([ tfs.ToTensor(), tfs.Normalize(0.5,0.5) ])
train_set = MNIST('./data', transform=im_tfs,download=True) train_data = DataLoader(train_set, batch_size=128, shuffle=True)
class autoencoder(nn.Module): def __init__(self): super(autoencoder, self).__init__() self.encoder = nn.Sequential( nn.Linear(28*28, 128), nn.ReLU(True), nn.Linear(128, 64), nn.ReLU(True), nn.Linear(64, 12), nn.ReLU(True), nn.Linear(12, 3) ) self.decoder = nn.Sequential( nn.Linear(3, 12), nn.ReLU(True), nn.Linear(12, 64), nn.ReLU(True), nn.Linear(64, 128), nn.ReLU(True), nn.Linear(128, 28*28), nn.Tanh() )
def forward(self, x): encode = self.encoder(x) decode = self.decoder(encode) return encode, decode
net = autoencoder().cuda() x = Variable(torch.randn(1, 28*28)).cuda() code, _ = net(x) print(code.shape)
criterion = nn.MSELoss(size_average=False) optimizer = torch.optim.Adam(net.parameters(), lr=1e-3)
def to_img(x): ''' 定义一个函数将最后的结果转换回图片 ''' x = 0.5 * (x + 1.) x = x.clamp(0, 1) x = x.view(x.shape[0], 1, 28, 28) return x
for e in range(100): for batch in train_data: im,_=batch im = im.view(im.shape[0], -1) im = Variable(im).cuda() _, output = net(im) loss = criterion(output, im) / im.shape[0] optimizer.zero_grad() loss.backward() optimizer.step() print('epoch: {}, Loss: {:.4f}'.format(e + 1, loss.item())) if (e+1) % 20 == 0: pic = to_img(output.cpu().data) print(pic.shape) if not os.path.exists('./simple_autoencoder'): os.mkdir('./simple_autoencoder') save_image(pic, './simple_autoencoder/image_{}.png'.format(e + 1))
import matplotlib.pyplot as plt from matplotlib import cm from mpl_toolkits.mplot3d import Axes3D
view_data = Variable((train_set.train_data[:200].type(torch.FloatTensor).view(-1, 28*28) / 255. - 0.5) / 0.5).cuda() encode, _ = net(view_data) encode=encode.cpu() fig = plt.figure(2) ax = Axes3D(fig)
X = encode.data[:, 0].numpy() Y = encode.data[:, 1].numpy() Z = encode.data[:, 2].numpy() values = train_set.train_labels[:200].numpy() for x, y, z, s in zip(X, Y, Z, values): c = cm.rainbow(int(255*s/9)) ax.text(x, y, z, s, backgroundcolor=c) ax.set_xlim(X.min(), X.max()) ax.set_ylim(Y.min(), Y.max()) ax.set_zlim(Z.min(), Z.max())
code = Variable(torch.FloatTensor([[1.19, -3.36, 2.06]])).cuda() decode = net.decoder(code).cpu() decode_img = to_img(decode).squeeze() decode_img = decode_img.data.numpy() * 255 plt.figure(3) plt.imshow(decode_img.astype('uint8'), cmap='gray')
plt.show()
|
![8pM2y.png](https://s1.328888.xyz/2022/04/26/8pM2y.png)
![8pSjd.png](https://s1.328888.xyz/2022/04/26/8pSjd.png)
![8pBJQ.png](https://s1.328888.xyz/2022/04/26/8pBJQ.png)
如图二所示,不同数字通过自动编码器所得编码大概聚集在一起,且通过解编码所得图三大概也能看出是5.
变分自动编码
变分编码器是自动编码器的升级版本,其结构跟自动编码器是类似的,也由编码器和解码器构成。
自动编码器有个问题,就是并不能任意生成图片,因为我们没有办法自己去编码向量,需要通过一张图片输入编码我们才知道得到的编码向量是什么,这时我们就可以通过变分自动编码器来解决这个问题。
其实原理特别简单,只需要在编码过程给它增加一些限制,迫使其生成的编码向量能够粗略的遵循一个标准正态分布,这就是其与一般的自动编码器最大的不同。
这样我们生成一张新图片就很简单了,我们只需要给它一个标准正态分布的随机隐含向量,这样通过解码器就能够生成我们想要的图片,而不需要给它一张原始图片先编码。
综上所述,就是让编码都服从一个分布,这样我们就可以任意取服从该分布的编码。
一般来讲,我们通过 encoder 得到的隐含向量并不是一个标准的正态分布,为了衡量两种分布的相似程度,我们使用 KL divergence,利用其来表示隐含向量与标准正态分布之间差异的 loss,另外一个 loss 仍然使用生成图片与原图片的均方误差来表示。
KL divergence 的公式如下
$$
D{KL} (P || Q) = \sum_{-\infty}^{\infty} p(i) \log \frac{p(i)}{q(i)} dx
$$
$$
D{KL} (P || Q) = \int_{-\infty}^{\infty} p(x) \log \frac{p(x)}{q(x)} dx
$$
重参数
很明显,对于变分自动编码器的输出向量做积分或求和是十分麻烦的,我们不如直接生成两个向量:一个表示均值,一个表示标准差,那么我们所需要的编码向量直接用一个标准正态分布先乘上标准差再加上均值就行了。这时候尽量让均值接近0,标准差接近1,也就不需要再去计算KL divergence。
$$
\begin{aligned}
DKL(P\Vert Q)&=\int_{-\infty}^{\infty}p(x)\log\frac{p(x)}{q(x)}dx\\
&=\int_{-\infty}^{\infty}p(x)\log p(x)-p(x)\log q(x)dx
\end{aligned}
$$
$$
\begin{aligned}
\int_{-\infty}^{\infty}p(x)\log p(x)dx&=\int_{-\infty}^{\infty}p(x)\log (\frac{1}{\sqrt{2\pi}\sigma_1}\exp(-\frac{(x-\mu_1)^2}{2\sigma_1^2}))dx\\
&=-\frac{1}{2}\log(2\pi\sigma_1^2)\int p(x)dx
+\int p(x)(-\frac{(x-\mu_1)^2}{2\sigma_1^2})dx\\
&=-\frac{1}{2}\log(2\pi\sigma_1^2)-\frac{\int p(x)x^2dx-\int p(x)2x\mu_1dx+\int p(x)\mu_1^2dx}{2\sigma_1^2}\\
&=-\frac{1}{2}\log(2\pi\sigma_1^2)-\frac{(\mu_1^2+\sigma_q^2)-(2\mu_1\times \mu_1)+u_1^2}{2\sigma_1^2}\\
&=-\frac{1}{2}[1+\log(2\pi \sigma_1^2)]
\end{aligned}
$$
$$
\begin{aligned}
\int_{-\infty}^{\infty}p(x)\log q(x)dx&=\int_{-\infty}^{\infty}p(x)\log (\frac{1}{\sqrt{2\pi}\sigma_2}\exp(-\frac{(x-\mu_2)^2}{2\sigma_2^2}))dx\\
&=-\frac{1}{2}\log(2\pi\sigma_2^2)\int p(x)dx
+\int p(x)(-\frac{(x-\mu_2)^2}{2\sigma_1^2})dx\\
&=-\frac{1}{2}\log(2\pi\sigma_2^2)-\frac{\int p(x)x^2dx-\int p(x)2x\mu_2dx+\int p(x)\mu_2^2dx}{2\sigma_2^2}\\
&=-\frac{1}{2}\log(2\pi\sigma_2^2)-\frac{(\mu_1^2+\sigma12^2)-(2\mu_2\times \mu_1)+u_2^2}{2\sigma_2^2}\\
&=-\frac{1}{2}\log(2\pi \sigma_2^2)-\frac{\sigma_1^2+(\mu_1-\mu_2)^2}{2\sigma_2^2}
\end{aligned}
$$
$$
\begin{aligned}
D{KL} (P || Q)&=-\frac{1}{2}[1+\log(2\pi \sigma_1^2)]-[-\frac{1}{2}\log(2\pi \sigma_2^2)-\frac{\sigma_1^2+(\mu_1-\mu_2)^2}{2\sigma_2^2}]\\
&=\log\frac{\sigma_2}{\sigma_1}+\frac{\sigma_1^2+(\mu_1-\mu_2)^2}{2\sigma_2^2}-\frac{1}{2}
\end{aligned}
$$
![80uoM.png](https://s1.328888.xyz/2022/04/26/80uoM.png)
那么我们的总体loss就等于$MSE+KLD$
reconstruction_function=nn.MSELoss() def loss_function(recon_x, x, mu, logvar): """ recon_x: generating images x: origin images mu: latent mean logvar: latent log variance """ MSE = reconstruction_function(recon_x, x) KLD_element = mu.pow(2).add_(logvar.exp()).mul_(-1).add_(1).add_(logvar) KLD = torch.sum(KLD_element).mul_(-0.5) return MSE + KLD
|
import os
import torch from torch.autograd import Variable import torch.nn.functional as F from torch import nn from torch.utils.data import DataLoader
from torchvision.datasets import MNIST from torchvision import transforms as tfs from torchvision.utils import save_image im_tfs = tfs.Compose([ tfs.ToTensor(), tfs.Normalize(0.5,0.5) ])
train_set = MNIST('./data', transform=im_tfs) train_data = DataLoader(train_set, batch_size=128, shuffle=True) class VAE(nn.Module): def __init__(self): super(VAE, self).__init__()
self.fc1 = nn.Linear(784, 400) self.fc21 = nn.Linear(400, 20) self.fc22 = nn.Linear(400, 20) self.fc3 = nn.Linear(20, 400) self.fc4 = nn.Linear(400, 784)
def encode(self, x): h1 = F.relu(self.fc1(x)) return self.fc21(h1), self.fc22(h1)
def reparametrize(self, mu, logvar): std = logvar.mul(0.5).exp_() eps = torch.FloatTensor(std.size()).normal_() if torch.cuda.is_available(): eps = Variable(eps.cuda()) else: eps = Variable(eps) return eps.mul(std).add_(mu)
def decode(self, z): h3 = F.relu(self.fc3(z)) return F.tanh(self.fc4(h3))
def forward(self, x): mu, logvar = self.encode(x) z = self.reparametrize(mu, logvar) return self.decode(z), mu, logvar net = VAE() if torch.cuda.is_available(): net = net.cuda() x, _ = train_set[0] x = x.view(x.shape[0], -1) if torch.cuda.is_available(): x = x.cuda() x = Variable(x) _, mu, var = net(x) print(mu)
reconstruction_function = nn.MSELoss(size_average=False)
def loss_function(recon_x, x, mu, logvar): """ recon_x: generating images x: origin images mu: latent mean logvar: latent log variance """ MSE = reconstruction_function(recon_x, x) KLD_element = mu.pow(2).add_(logvar.exp()).mul_(-1).add_(1).add_(logvar) KLD = torch.sum(KLD_element).mul_(-0.5) return MSE + KLD
optimizer = torch.optim.Adam(net.parameters(), lr=1e-3)
def to_img(x): ''' 定义一个函数将最后的结果转换回图片 ''' x = 0.5 * (x + 1.) x = x.clamp(0, 1) x = x.view(x.shape[0], 1, 28, 28) return x for e in range(100): for im, _ in train_data: im = im.view(im.shape[0], -1) im = Variable(im) if torch.cuda.is_available(): im = im.cuda() recon_im, mu, logvar = net(im) loss = loss_function(recon_im, im, mu, logvar) / im.shape[0] optimizer.zero_grad() loss.backward() optimizer.step() print('epoch: {}, Loss: {:.4f}'.format(e + 1, loss.item())) if (e + 1) % 20 == 0: save = to_img(recon_im.cpu().data) if not os.path.exists('./vae_img'): os.mkdir('./vae_img') save_image(save, './vae_img/image_{}.png'.format(e + 1))
x, _ = train_set[0] x = x.view(x.shape[0], -1) if torch.cuda.is_available(): x = x.cuda() x = Variable(x) _, mu, _ = net(x) print(mu)
|
![8YQSJ.png](https://s1.328888.xyz/2022/04/26/8YQSJ.png)
可以明显看出变分自动编码的输出图片比自动编码器输出的图片更清晰,但其仍然使用MESLoss为loss函数,所以生成的图片会有模糊现象。