赞
踩
目录
Unsupervised learning Deep Generative Model(无监督学习深度生成模型)
Variational Auto Encoder(变分自编码器)
上周学习了GAN模型,它其实属于生成模型的一种,这周对无监督学习的深度生成模型进行深入学习,生成模型比较有名的有PixelRNN、Variational Autoencoder(VAE)、Generative Adversarial Network(GAN)这三种模型。首先学习了PixelRNN如何根据已有的输入得到机器自主生成的结果。其次了解到VAE的产生是为了弥补Auto-encoder存在的不足,通过直观和科学的解释,分析VAE为何可以对niose范围采样的数据生成更好的图片。分析VAE的不足之处,也就是GAN产生原因,最后学习GAN中生成器和判别器的演化,并通过生成手写数字的例子熟悉GAN模型的搭建以及训练过程。
Last week ,we learned about the GAN mode,which is actually belongs to a type of generative model. This week, we delve deeper into Unsupervised learning Deep Generative Model. The well-known generative models include PixelRNN, Variational Autoencoder (VAE), and Generative Adversarial Network (GAN). Firstly, we learned how PixelRNN can obtain machine generated results based on existing inputs. Secondly, we understand that the generation of VAE is to compensate for the shortcomings of Auto encoder. Through intuitive and scientific explanations, we analyze why VAE can generate better images from data sampled within the niose range. Analyze the shortcomings of VAE, which is the cause of GAN generation, and finally learn the evolution of generators and discriminators in GAN. Familiarize yourself with the construction and training process of GAN models by generating examples of handwritten numbers.
生成模型就是让机器自主进行学习,例如在图像上,我们现有的模型可以机器对图像进行分类,例如分类猫和狗的照片,那如果让机器自己画出一张猫的图片,那就是属于创造吗,也就是Generative Models要做的事情。
比较有名的Generative Models有下面几种:
PixelRNN
Variational Autoencoder(VAE)
Generative Adversarial Network(GAN)
Pixel RNN不需要label就可以 train,根据前面的pixel,输出下一个pixel。假如今天的目标是让machine自己画一张解析度为3×3,有9个pixel的图形。假设先随机给这个image一个橙色的pixel,将这个pixel作为NN的输入,得到的pixel,(颜色RGB是三种颜色,因此一个pixel就是一个三维的向量)将它转“换为颜色涂上去。接下来就把这两个颜色一起作为NN的输入,然后得到第三个pixel,直到得到9个pixel。machine看到前面的颜色就可以得到后面应该涂的颜色。
在不同general image方法里面,Pixel RNN是产生的图是最清晰的,这是一个非监督模型,例如给出一张狗狗照片的上半部分,让machine生成下半部分。
这个方法不仅可以用在image上,还可以用在语音上
首先回顾一下Auto-encoder,将一个图片作为输入,先经过NN Encoder进行编码,将得到的code输入NN Decoder进行解码得到一个图片的输出,我们希望输入和输出越接近越好。
如果用Auto Encode做生成任务,则不需要Encoder进行编码,随机产生一个向量作为code输入到NN Decoder产生一张图片。但这种方式生成的图片都不太好,解决方法就是采用VAE。
VAE模型与Auto-encoder的区别在于,现在NN Encoder会输出两组向量、,然后再从normal distribution中sample生成一个向量,将取exponential,然后再乘上向量,最后加上,得到的向量就是code,将其放入NN Decoder得到输出。除了输入输出的误差要最小化之外还要满足右下角的目标。
直观解释
有个直观的例子说明,当输入一个满月的图片到auto-encoder模型,我们希望得到一个同样的满月图片。同理,当输入一个半月的图片,我们希望得到一个半月的图片。如果在满月和半月之间sample一个code,我们会希望得到一个介于满月和半月两者之间的图像,但实际输出往往不是这样。
而在使用VAE的时候,当输入一个满月图片,在code上会有一个noise,即加上一个范围,意味着在noise的范围内得到的图片都是满月,同理可得,输入一个半月的图片,可以在一个范围内得到半月的图片。那这个时候满月和半月的noise范围会有重叠,在重叠的code那个点,使用VAE的时候就会minimize它的mean square error,得到一张介于满月和半月之间的的图片。
总结:从code的空间上sample一个code,在产生iamge的时候,VAE比Auto-encoder可以产生更好的理想结果。
代表原来的code
代表加上noise之后的code
代表noise的方差,由NN Encoder自主学习得到。
是常量,但是它决定noise的大小
右下角这个公式是对方差加一些限制,为了确保这个方差是正的,所以加上了exp。但是仅仅是这样是不够的,因为这个方差是自己学的,这样模型会让学习到的方差都是0来得到最小的reconstruction error,那就回到了原来的Auto-encoder。因此需要对这个方差作出限制,防止它学习到很小的方差。
这个公式里面用蓝色表示,用红色表示,用绿色表示,将蓝色的线减去红色的线得到的是绿色的那条线,这条线的最低点在的地方,而,,意味着它的方差就是1,的时候loss最低,也就是方差等于1的时候,loss最低,因此machine就不会让方差为0,或者是太小。最后的是L2正则化,让结果比较sparse。
科学解释
上面的是直观的解释,那么更正式的解释如下:
假设要machine做的事情是generate宝可梦的图,宝可梦的图可以当作高维空间的一个点,(图中用一维空间描述),其实要做的事情就是估计高维空间上面的几率分布p(x)(x是一个向量)。可以根据几率的值sample一张象宝可梦的图。因此在是宝可梦图片的地方,p(x)的值应该是大的,也就是几率是大的。
那怎么估计一个probability distribution(P(x))呢?就需要用到Gaussian Mixture Model
Gaussian Mixture Model(高斯混合模型)
下图中黑色的线是由多个高斯分布(蓝色的线)通过不同的weight叠合起来得到的分布,只要数量多就可以产生一个很复杂的高斯分布。每个高斯分布都会有对应的权重,需要根据weight决定从哪个高斯分布sample data,来组成Gaussian Mixture Model。于是这个决定的过程可以看做是一个关于m个样本的采样,从第m个高斯采样的概率是,找到指定的高斯以后,再根据这个高斯的参数进行采样。
怎么得到这个P(x)呢,通过下面的公式来计算。
m=0,1……m代表第几个高斯分布。
是选择第m个高斯分布的几率,也就是组成混合高斯的每一个小高斯的权重。
是从第m个高斯分布选取数据x的概率。
Gaussian Mixture Model有种种问题,例如你要决定mixture的数目。在确定mixture的数目之后,根据拥有的data、x,就可以很容易的估计这些高斯分布和这些分布对应的weight、mean和variance,用EM algorithm就可以解决了。現在每一个x都是从某一个 mixture 被 sample 出來的,这件事情就类似于在做 classification,每一个 x,它都来自于一个某个分类,而用某一个分类来表示是不够准确的,正确的做法是用一个分布来表示x,也就是说x可以表示为:有多少的几率从第a分布采样出来,有多少几率从b分布采样出来。因此回到VAE,VAE就可以看做是GMM中x的分布表示。
做法就是,首先从一个normal distribution随机sample一个z,z代表的是normal distribution的一个向量,向量z的每一维代表某种attribute,代表现在要sample的那个东西的某种特质。有了z之后,我们可以决定z对应的参数,假设mean和方差都来自一个函数,这个函数就可以是neural network,将z作为NN的输入就可以得到对应的mean和方差。
p(x)的公式如下所示,p(z)就是抽取z的函数, 就是从z的某一个高斯里面来抽取x的概率。
注意这里是积分,因为z是连续的。它和上面离散的GMM不一样了,刚才的GMM如果由10个高斯分布组成,那么对应的参数也就有10组,是可以定下来的。这里的z表示连续的分布,也就意味着有无穷多个高斯分布,无穷多组参数,因此,这里我们用一个函数表示z所对应的高斯分布参数,写成:
另外z不一定是从normal distribution中sample的,即使z是从简单的normal distribution得到的,P(x)也可以很复杂。
最大似然求解
根据上面公式,可知P(x)是一个简单的正态分布,而p(x|z)是的分布,只要知道z,就可以决定x是从什么样的mean和方差的高斯分布中产生的,mean和方差的函数就是要去估测的,而是由一个neural network产生,
现在我们手上有一组观测变量x,希望找到一组函数来表达,使产生的,可以使得使得x从P(x)分布中取出来的概率最大。用最大似然的思想写出来的损失函数为:
这里需要另一个分布,它与上面的NN相反,它是输入x,得到对应的z的mean和方差,上面的NN就是VAE中的Decoder,下面的NN就是VAE里面的Encoder。
原本我们只是找这一项来maxmize ,现在还需要找到,同时找到这两项去maxmize 。因为要找的likelihood是的upper bound,,如果只通过这一项来maxmize 的话,与KL没关系,因此KL的值不会发生变化,而当增大的时候有可能会增加likelihood 。而这一项与没有任何关系,但与和KL 有关,因此通过去maxmize ,的值是不会发生变化的。
如果固定住,只通过这一项来maxmize ,会让与越来越接近。当与完全一样的时候,KL会为0,但因为,如果一直maxmize ,那likelihood 也一定会上升。
由变形后的公式可知,要maximize,就是Maxmize ,Minimize ,Minimize是通过调节而q对应的一个neural network的输出,让它产生的distribution可以和一个normal distribution越接近越好。
Maxmize这一项可以当作是有一个,它用来做weight sum,因此可以写成是根据的期望值,意思就是给我们一个x,计算这个几率分布,然后从去sample data让的几率越大越好。这个就是Auto-encoder一直在做的事情。怎么从去sample data呢?根据放入NN的x产生一个mean和方差,然后根据和
就可以sample出一个z,maxmize根据z产生x的几率,而这个几率是将z丢入另一个NN产生的和,也就是让和最大化,如果只考虑这一项的话,因为是一个高斯分布,那么在等于mean的地方几率是最大的,即让输入和输出越接近越好。
这两项合起来就是VAE的loss function
它可以做的事情有产生手写的数字,给它一个digit,它可以抽出特性例如手写数字的粗细等,生成具有同样特性的图片。
VAE主要的问题是它从来没有去真的学怎么产生一张看起来是真正的图片,这也是为什么后面产生GAN。它想要产生一张image跟data base里面某张image越接近越好,但它不知道在评估相似度的时候,我们是用mean square error(均方误差)等。因此当Decoder的output的与data base的图片只相差一个piexl,但是这个piexl在不同的位置,给人直观上的差别是不一样的,例如图中的两个位置,左边的看起来很真实,但右边的很像生成的图片,但是他们都只与目标图片相差一个piexl,对于人来说,这两个图片相差很大,但是对于VAE来说,他们是一样的好坏。VAE从来没有想过产生以假乱真的图片,唯一做的就是模仿,也就是说它产生的图片其实就是data base里面image的线性组合得到的,从来没有想过产生新的图片。
而在GAN里面,Generator从来没有见过真正的image长什么样子,Discriminator见过真正的image长什么样子,它会比较与Generator生成的图片的不同,因此Generator产生的iamge是data base里面从来没有见过的,这才像machine要做的事情。
Generator和Discriminator其实都是一个network,下图中的Generator v1就是VAE的Decoder,从一个分布随机产生一个向量输入到里面,产生图片。Discriminator就是就是根据输入的图片,来输出0/1,通过sigmoid这样激活函数这样。输入的图片,真正的图片为1,Generator 产生的图片标为0,来训练这个Discriminator来更新参数。这个是Discriminator的演化
而在Generator 的演化中,Generator 和Discriminator可以合成一个network,将随机的一个vector作为network的输入,输入到Generator ,将生成的图片再输入到Discriminator。我们想让这个vector产生的图片通过Discriminator得到的数值是1。因为整个是一个network,因此想要得到的输出为1其实是一件很简单的事情,通过gradient descent进行更新参数就行,但需要注意的是,在调整参数的时候,只能调整,也就是只能计算Generator 的参数network的output的gradient,然后更新Generator 的参数。如果不固定Generator的参数一起训练,那只要discriminator的w为0,b为1就好。
这个是GAN的原始paper的Toy Example。在里面z是一维的东西(从normal distribution里面sample出来的),作为Decoder的input,丢在Generator的network里面会产生另外一个一维的东西x。不同的z可以得到不同的x,x的分布就是图中绿色的分布,黑色的点代表真实data的分布,我们希望那个绿色的点的分布和黑色的点的分布越接近越好。
按照gan的概念,就是把绿色的点和黑色的真实的点放进Discriminator里面,得到一个数值,这个数值代表输入的x是来自真实data的几率和是生成图片的几率,真的为1假的为0。然后会调整参数,那个蓝色的线就是x通过Discriminator得到的对应output,Generator会根据Generator的判断进一步训练,当左边的Discriminator得分比较高的时候,Generator产生的结果就会向左边靠拢,达到骗过Discriminator的目的,而Discriminator又会根据新的输入图片来更新那个蓝色的线。这个过程会反复进行下去,直到生成的分布与真实的分布重合,Discriminator无法再分辨。
注意:如果移动太多,会导致更差的结果,因此移动的距离很难把握,所以GAN是比较难train的,需要小心调参数。
Gan的训练最大的问题就是你不知道Discriminator是不是对的。假设Discriminator得到一个很好的结果,那可能Generator太废了。假设Discriminator得到一个很差的结果,可能Discriminator太差了。
①获取torch自带的MNIST数据集
Resize(28):因为图片为28×28的,固定大小为28。
ToTensor():将image转化为tensor,可以转化为0-1之间的浮点数。
Normalize():normalize里面的参数为均值和方差都设置为0.5
因此得到的每个样本size为1×28×28,1为通道数。
- #MNIST数据集获取,样本数约为六万个
- dataset = torchvision.datasets.MNIST("mnist_data", train=True, download=True,
- transform=torchvision.transforms.Compose(
- [
- torchvision.transforms.Resize(28),
- torchvision.transforms.ToTensor(),
- torchvision.transforms.Normalize([0.5], [0.5]),
- ]
- )
- )
之后通过DataLoader
方法加载数据集,代码如下:
dataloader = torch.utils.data.DataLoader(dataset, batch_size=batch_size, shuffle=True, drop_last=True)
准备好数据就可以构建模型,GAN模型包含生成器(Generator)和判别器(Discriminator),模型搭建均采用的全连接层,
②生成器模型搭建:
生成器是一个只有一个隐藏层的神经网络,将维度不断扩大,一直到1023,最后nn.Linear(1024, np.prod(image_size, dtype=np.int32))要把1024降维到image size,将特征空间映射到图片的大小,prod()为连乘,得到1×28×28。映射完之后加一个Sigmoid激活函数,
在隐藏层中使用 ReLU激活函数,与将任何负输入映射
到0的常ReLU函数不同, 在输出层使用sigmoid激活函数, 输出更为典型的0到1范围内的值,它有助于生成更清断的图像。
输入为z,z的形式为[batchsize,1,28,28]
- class Generator(nn.Module):
-
- def __init__(self):
- super(Generator, self).__init__()
-
- self.model = nn.Sequential(
- nn.Linear(latent_dim, 128), #全连接层
- torch.nn.BatchNorm1d(128),
- torch.nn.RELU(),
-
- nn.Linear(128, 256),
- torch.nn.BatchNorm1d(256),
- torch.nn.RELU(),
- nn.Linear(256, 512),
- torch.nn.BatchNorm1d(512),
- torch.nn.RELU(),
- nn.Linear(512, 1024),
- torch.nn.BatchNorm1d(1024),
- torch.nn.RELU(),
- nn.Linear(1024, np.prod(image_size, dtype=np.int32)),
- nn.Sigmoid(),
- )
-
- def forward(self, z):
- # shape of z: [batchsize, latent_dim]
-
- output = self.model(z)
- image = output.reshape(z.shape[0], *image_size)
-
- return image
③ 判别器模型搭建:
判别器接收到的是一张28×28×1的图像,并输出表示输入是否被视为真而不是假的概率。为简单起见,我们构造的判别器网络看起来与生成器几乎相同,相反的是,判别器网络是一步一步降低我们的维度,刚开始设置为大维度,然后依次降低它的维度,最后做一个逻辑回归回到一维,得到一个Sigmoid的概率值。为了将图片送入模型,需要reshape一下,image.reshape(image.shape[0], -1)将imag的第零个维度作为第一维,其他的维度一起作为第二维,将image重塑为一个二维的数据。
- class Discriminator(nn.Module):#判断图片是真正的图片还是生成的
-
- def __init__(self):
- super(Discriminator, self).__init__()
-
- self.model = nn.Sequential(
- nn.Linear(np.prod(image_size, dtype=np.int32), 512),
- torch.nn.RELU(),
- nn.Linear(512, 256),
- torch.nn.RELU(),
- nn.Linear(256, 128),
- torch.nn.RELU(),
- nn.Linear(128, 64),
- torch.nn.RELU(),
- nn.Linear(64, 32),
- torch.nn.RELU(),
- nn.Linear(32, 1),
- nn.Sigmoid(),
- )
-
- def forward(self, image):
- # shape of image: [batchsize, 1, 28, 28]
-
- prob = self.model(image.reshape(image.shape[0], -1))
-
- return prob
④模型搭建好后,我们会对损失函数、优化器等参数进行设置,需要两个Adam优化器,分别对生成器的参数和判别器的参数进行优化,需要用到它们的参数,所以对生成器模型和判别器模型都要实例化。这里采用的是BCELOSS损失函数。
- g_optimizer = torch.optim.Adam(generator.parameters(), lr=0.0003, betas=(0.4, 0.8), weight_decay=0.0001)
- d_optimizer = torch.optim.Adam(discriminator.parameters(), lr=0.0003, betas=(0.4, 0.8), weight_decay=0.0001)
- loss_fn = nn.BCELoss()
⑤训练GAN网络
num_epoch 迭代次数设置为 200,batch_size批次数设置为32。
训练为两个循环,将for循环放在每个需要遍历训练数据集的epoch上,在这个for循环中是另一个嵌套的for循环,它遍历每批样本enumrate,其中一个批次具有指定的batch大小样本数。
第一步:随机生成一个服从正态分布的
第二步:将z放入生成器模型得到一个预测的照片
第三步:定义一个生成器loss,再将得到的预测图片放入辨别器模型得到一个概率,然后同目标target一起放入,(因为这里是在训练优化生成器,希望判别器全部预测为真实的图片所以target应该全部设置为1的状态)就会得到一个g_loss,再通过g_loss.backward()计算它的后降梯度,最后通过g_optimizer.step()更新generator的参数。
第四步:对判别器进行优化,优化有两个部分,第一部分将真实的图片送入判别器进行优化,它的标签应改为torch.ones,希望把真实的照片分类成1,第二部分将生成的预测图片送入判别器,它的标签应改为torch.zero,希望判别器把生成的图片分类为0。由于d_loss不需要对生成器部分计算梯度,因此在pred_image这一项加上detach()隔离出去。
- num_epoch = 200
- for epoch in range(num_epoch):
- for i, mini_batch in enumerate(dataloader):
- gt_images, _ = mini_batch
-
-
- z = torch.randn(batch_size, latent_dim)
-
-
- pred_images = generator(z)
- g_optimizer.zero_grad()
-
-
- g_loss = loss_fn(discriminator(pred_images), labels_one)
-
- g_loss.backward()
- g_optimizer.step()
-
- d_optimizer.zero_grad()
-
- real_loss = loss_fn(discriminator(gt_images), labels_one)
- fake_loss = loss_fn(discriminator(pred_images.detach()), labels_zero)
- d_loss = (real_loss + fake_loss)
-
- # 观察real_loss与fake_loss,同时下降同时达到最小值,并且差不多大,说明D已经稳定了
-
- d_loss.backward()
- d_optimizer.step()
在Auto-encoder中,可以将输入的图像进行编码再解码恢复图像,让输入与输出越接近越好,但VAE可以将这些图片的编码向量存起来,通过这些编码向量来重构产生新的图像。可以对编码器添加约束,强迫它产生服从单位高斯分布的潜在变量。因此产生新的图片也变得容易,只要从单位高斯分布中进行采样,然后把它传给解码器就可以了。而VAE的一个劣势就是没有使用对抗网络,它永远只是在模仿,并没有生成真正的图像,所以会更趋向于产生模糊的图片,因此才有了GAN的产生,它可以想办法生成“骗过”辨别器的图像。比较VAE与GAN的效果,我们可以得到以下两点:1、GAN生成的效果要优于VAE,2、GAN比VAE要难于训练
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。