前言
- 博客主页:睡晚不猿序程
- ⌚首发时间:2022.8.19
- ⏰最近更新时间:2022.8.19
- 本文由 睡晚不猿序程 原创,首发于 CSDN
- 作者是蒻蒟本蒟,如果文章里有任何错误或者表述不清,请 tt 我,万分感谢!orz
相关文章目录 :
上一次作业我们完成了 Transformer,在这次作业,我们将会完成 GAN
也就是生成对抗网络。
在之前,我们实现的都是判别模型,接受一个输入,然后训练模型来生成一个输出的标签
在这里,我们将会构建一个生成模型,具体而言,我们将会学习如果构建可以生成奇特图片的模型
这是一种训练生成模型的方法,首先我们要构建两个不同的神经网络
第一个网络是传统的分类网络,称为判别器
这个网络被训练来分类图片是假的(不在训练集的)或者是真实的(来自训练集)
第二个网络是生成器,将会输入随机噪声,并使用这个噪声来生成图片,其目标是迷惑判别器
minimize G maximize D E x ∼ p data [ log D ( x ) ] + E z ∼ p ( z ) [ log ( 1 − D ( G ( z ) ) ) ] \underset{G}{\operatorname{minimize}} \underset{D}{\operatorname{maximize}} \mathbb{E}_{x \sim p_{\text {data }}}[\log D(x)]+\mathbb{E}_{z \sim p(z)}[\log (1-D(G(z)))] GminimizeDmaximizeEx∼pdata [logD(x)]+Ez∼p(z)[log(1−D(G(z)))]
解释一下上面的公式,D代表着判别器对图像的判断,1代表是绝对真实,0代表着绝对虚假
所以:
生成器的目标:让判别器认为生成图像是真实的
判别器的目标:判别出所有生成器生成的虚假图像
我们再来看一下上面的公式,如果判别器非常优秀,D(x)=1,1-D(G(z))=1,所以整个损失函数为0
反过来,生成器非常优秀,D(G(z))=1,那么整个损失会倾向于无负无穷大
所以生成器要让损失接近负无穷,而判别器要让损失接近0,所以这就是这个 maxmin game
优化
我们要对这场博弈进行优化,我们使用梯度下降来交替更新G和D
上面所说的方法虽然理论可行,但是实践上表现并不好。所以我们用另一种方法——更新G的时候,最大化让判别器做出错误判断的几率
上面这种方法可以避免在判别器过于强大的时候,生成器的梯度消失
所以在作业中,我们采取以下的更新方式:
maximize G E z ∼ p ( z ) [ log D ( G ( z ) ) ] \underset{G}{\operatorname{maximize}} \mathbb{E}_{z \sim p(z)}[\log D(G(z))] GmaximizeEz∼p(z)[logD(G(z))]
maximize D E x ∼ p data [ log D ( x ) ] + E z ∼ p ( z ) [ log ( 1 − D ( G ( z ) ) ) ] \underset{D}{\operatorname{maximize}} \mathbb{E}_{x \sim p_{\text {data }}}[\log D(x)]+\mathbb{E}_{z \sim p(z)}[\log (1-D(G(z)))] DmaximizeEx∼pdata [logD(x)]+Ez∼p(z)[log(1−D(G(z)))]
接下来给我们展示以下,我们将要完成的模型的三个输出
可以看出,我们要完成的是手写数字的模拟
GANs 对超参数非常的挑剔,而且要求更多的训练 epoch,所以我们选择了 MNIST 数据集,简单实用。
我们直接使用 pytorch 来导入数据集,记得查看文档
我们要生成一个均匀分布的噪声,数值范围[-1,1],大小为(batch_size,dim)
现在打开文件cs231n/gan_pytorch.py
完成sample_noise
部分
def sample_noise(batch_size, dim, seed=None):
"""
Generate a PyTorch Tensor of uniform random noise.
Input:
- batch_size: Integer giving the batch size of noise to generate.
- dim: Integer giving the dimension of noise to generate.
Output:
- A PyTorch Tensor of shape (batch_size, dim) containing uniform
random noise in the range (-1, 1).
"""
if seed is not None:
torch.manual_seed(seed)
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
return 2*torch.rand((batch_size, dim))-1
# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
代码详解
torch.rand
只能生成[0,1]范围的均匀分布,我们把他乘2减1就可以得到均匀分布啦(约等于是平移了一下(调用我们之前实现过的函数,我们也提供了Unflatten
,以供我们在完成基于卷积的生成器的时候使用
同时也提供了初始化权重的方法initialize_weights
,使用的是Xavier的初始化方法
我们的第一步将会是构建判别器,使用nn.Sequential
来进行构建,结构如作业中所示,全连接层包含偏置
结构:
def discriminator(seed=None):
"""
Build and return a PyTorch model implementing the architecture above.
"""
if seed is not None:
torch.manual_seed(seed)
model = None
##############################################################################
# TODO: Implement architecture #
# #
# HINT: nn.Sequential might be helpful. You'll start by calling Flatten(). #
##############################################################################
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
model = nn.Sequential(
Flatten(),
nn.Linear(784, 256),
nn.LeakyReLU(),
nn.Linear(256, 256),
nn.LeakyReLU(),
nn.Linear(256, 1)
)
# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
##############################################################################
# END OF YOUR CODE #
##############################################################################
return model
代码详解
在这里我们要完成生成器的实现了
生成器结构:
完成 generator
部分
def generator(noise_dim=NOISE_DIM, seed=None):
"""
Build and return a PyTorch model implementing the architecture above.
"""
if seed is not None:
torch.manual_seed(seed)
model = None
##############################################################################
# TODO: Implement architecture #
# #
# HINT: nn.Sequential might be helpful. #
##############################################################################
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
model = nn.Sequential(
nn.Linear(noise_dim, 1024),
nn.ReLU(),
nn.Linear(1024, 1024),
nn.ReLU(),
nn.Linear(1024, 784),
nn.Tanh()
)
# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
##############################################################################
# END OF YOUR CODE #
##############################################################################
return model
计算生成器和判别器的损失
首先是生成器损失:
ℓ G = − E z ∼ p ( z ) [ log D ( G ( z ) ) ] \ell_{G}=-\mathbb{E}_{z \sim p(z)}[\log D(G(z))] ℓG=−Ez∼p(z)[logD(G(z))]
然后是判别器损失:
ℓ D = − E x ∼ p data [ log D ( x ) ] − E z ∼ p ( z ) [ log ( 1 − D ( G ( z ) ) ) ] \ell_{D}=-\mathbb{E}_{x \sim p_{\text {data }}}[\log D(x)]-\mathbb{E}_{z \sim p(z)}[\log (1-D(G(z)))] ℓD=−Ex∼pdata [logD(x)]−Ez∼p(z)[log(1−D(G(z)))]
因为我们要最小化损失,所以我们在损失函数前面增加了负号
提示
使用bce_loss
损失函数来计算交叉熵损失,这是计算判别器给出的真实标签 log 概率所需要的
给出分数 s 和标签 y,交叉熵损失被定义为:
b c e ( s , y ) = − y ∗ l o g ( s ) − ( 1 − y ) ∗ l o g ( 1 − s ) bce(s,y)=-y*log(s)-(1-y)*log(1-s) bce(s,y)=−y∗log(s)−(1−y)∗log(1−s)
这里的s如果越接近1,表示真图片,越接近0则表示假图片
这种简单的实现会导致数值不稳定,我们基于nn.BCEWithlogitsLoss
实现了一个稳定的版本
您还需要计算与真假相对应的标签,并使用 logit 参数来确定它们的大小。 确保使用全局 dtype
变量将这些标签转换为正确的数据类型,例如:true_labels = torch.ones(size).type(dtype)
讲了这么多,就是损失函数用上面这个函数来替代啦!
请记得对比最开始的损失函数公式,只是D(X)和D(G(x))被s替代了而已
discriminator loss
def discriminator_loss(logits_real, logits_fake):
"""
Computes the discriminator loss described above.
Inputs:
- logits_real: PyTorch Tensor of shape (N,) giving scores for the real data.
- logits_fake: PyTorch Tensor of shape (N,) giving scores for the fake data.
Returns:
- loss: PyTorch Tensor containing (scalar) the loss for the discriminator.
"""
loss = None
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
N = logits_real.shape[0]
real_labels = torch.ones(N).type(dtype)
fake_labels = 1 - real_labels
loss = bce_loss(logits_real, real_labels) + \
bce_loss(logits_fake, fake_labels)
# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
return loss
generator loss
def generator_loss(logits_fake):
"""
Computes the generator loss described above.
Inputs:
- logits_fake: PyTorch Tensor of shape (N,) giving scores for the fake data.
Returns:
- loss: PyTorch Tensor containing the (scalar) loss for the generator.
"""
loss = None
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
N = logits_fake.shape[0]
fake_label = torch.ones(N).type(dtype)
loss = bce_loss(logits_fake, fake_label)
# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
return loss
使用 Adam 优化器来进行优化
def get_optimizer(model):
"""
Construct and return an Adam optimizer for the model with learning rate 1e-3,
beta1=0.5, and beta2=0.999.
Input:
- model: A PyTorch model that we want to optimize.
Returns:
- An Adam optimizer for the model with the desired hyperparameters.
"""
optimizer = None
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
optimizer = optim.Adam(model.parameters(), lr=1e-3, betas=(0.5, 0.999))
# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
return optimizer
简单的加上一行代码就可以了
接下来我们要及逆行训练,我们不需要修改,但是鼓励我们去看一下下训练代码
看代码就大家自己去看一下啦~我们直接运行底下的 cell,看一下最终的结果
可以看出效果还是可以的,有点数字的样子了
这是一个对原本的损失函数进行的改动,使其更稳定
公式如下:
ℓ G = 1 2 E z ∼ p ( z ) [ ( D ( G ( z ) ) − 1 ) 2 ] ℓ D = 1 2 E x ∼ p data [ ( D ( x ) − 1 ) 2 ] + 1 2 E z ∼ p ( z ) [ ( D ( G ( z ) ) ) 2 ] \ell_{G}=\frac{1}{2} \mathbb{E}_{z \sim p(z)}\left[(D(G(z))-1)^{2}\right]\\ \ell_{D}=\frac{1}{2} \mathbb{E}_{x \sim p_{\text {data }}}\left[(D(x)-1)^{2}\right]+\frac{1}{2} \mathbb{E}_{z \sim p(z)}\left[(D(G(z)))^{2}\right] ℓG=21Ez∼p(z)[(D(G(z))−1)2]ℓD=21Ex∼pdata [(D(x)−1)2]+21Ez∼p(z)[(D(G(z)))2]
其中, D ( x ) 和 D ( G ( z ) ) D(x)和D(G(z)) D(x)和D(G(z)) 用 scores_real
和scores_fake
来代替
不会很困难,一定要记得加上绝对值,我们直接来看
def ls_discriminator_loss(scores_real, scores_fake):
"""
Compute the Least-Squares GAN loss for the discriminator.
Inputs:
- scores_real: PyTorch Tensor of shape (N,) giving scores for the real data.
- scores_fake: PyTorch Tensor of shape (N,) giving scores for the fake data.
Outputs:
- loss: A PyTorch Tensor containing the loss.
"""
loss = None
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
loss = (0.5*(scores_real-1)**2).mean()+(0.5*scores_fake**2).mean()
# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
return loss
def ls_generator_loss(scores_fake):
"""
Computes the Least-Squares GAN loss for the generator.
Inputs:
- scores_fake: PyTorch Tensor of shape (N,) giving scores for the fake data.
Outputs:
- loss: A PyTorch Tensor containing the loss.
"""
loss = None
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
loss = 0.5*((scores_fake-1)**2).mean()
# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
return loss
接下来我们运行训练,来看看我们的此次修改效果如何
可以明显感觉到,收敛的速度似乎变快了,最后生成的效果如下
上面我们完全拷贝的最原始的 GAN,我们可以看出边缘并不锐利,在这里我们引入卷积,我们将实现 DCGAN 中的一些想法,也就是使用 CNN
使用如下的结构,修改build_dc_classifier
函数
按部就班实现即可:
def build_dc_classifier(batch_size):
"""
Build and return a PyTorch model for the DCGAN discriminator implementing
the architecture above.
"""
##############################################################################
# TODO: Implement architecture #
# #
# HINT: nn.Sequential might be helpful. #
##############################################################################
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
model = nn.Sequential(
Unflatten(batch_size, 1, 28, 28),
nn.Conv2d(1, 32, 5, 1),
nn.LeakyReLU(),
nn.MaxPool2d(2, 2),
nn.Conv2d(32, 64, 5, 1),
nn.LeakyReLU(),
nn.MaxPool2d(2, 2),
Flatten(),
nn.Linear(64*4*4, 4*4*64),
nn.LeakyReLU(),
nn.Linear(4*4*64, 1)
)
return model
# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
##############################################################################
# END OF YOUR CODE #
##############################################################################
代码详解
我们直接使用 InfoGAN 中的生成器,建议看一下nn.ConvTranspose2d
的文档
生成器结构如下:
接下来比较需要注意的是转置卷积的公式,首先要先进行填充,假设核大小k=3,步长stride=1,填充padding=0的情况:
有了上面的假设,我们就可以把转置卷积转化成卷积操作,也就可以使用卷积操作的公式进行计算了
所以一起来看代码:
def build_dc_generator(noise_dim=NOISE_DIM):
"""
Build and return a PyTorch model implementing the DCGAN generator using
the architecture described above.
"""
##############################################################################
# TODO: Implement architecture #
# #
# HINT: nn.Sequential might be helpful. #
##############################################################################
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
model = nn.Sequential(
nn.Linear(noise_dim, 1024),
nn.ReLU(),
nn.BatchNorm1d(1024),
nn.Linear(1024, 7*7*128),
nn.ReLU(),
nn.BatchNorm1d(7*7*128),
Unflatten(-1, 128, 7, 7),
nn.ConvTranspose2d(128, 64, 4, 2, 1),
nn.ReLU(),
nn.BatchNorm2d(64),
nn.ConvTranspose2d(64, 1, 4, 2, 1),
nn.Tanh(),
Flatten(),
)
return model
# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
##############################################################################
# END OF YOUR CODE #
##############################################################################
代码详解
nn.BatchNorm
需要我们手动算出特征的维度好了接下来看看结果,明显生成的图像边缘更为锐利了,背景也更纯粹
在这次作业中,我们成功实现了几个简单的 GAN 模型,相信大家对 GAN 也有了简单的了解,接下来就剩下最后一项作业啦,cs231n作业也即将更新完毕(除了漏了的几个作业二的),我们下期见!