这篇文章之所以来的比较早, 是因为我们机器人比赛字符识别数据集不够, 想自己造点数据集其实
课程内容总结
所谓GAN, 原理很简单, 我们有一个生成器网络和鉴别器网络, 生成器生成假的数据, 鉴别器分辨真假, 二者知己知彼互相优化自己, 从而达到博弈的效果.
实际操作中, 我们一般是训练k步鉴别器, 随后训练一步生成器(或者一步&多步, 这东西其实不绝对, 现在很多GAN变种解决了k超参数问题). 生成器从噪声生成目标的图像等, 例如下面就是一个简单的生成器结构(卷积/反卷积)
我们优化的函数是下面的表现形式:
这个公式有两层含义: 首先我们要优化鉴别器参数, 尽可能提高真实数据判断为真的概率, 以及生成数据判断为假的概率, 随后要优化生成器, 尽可能骗过生成器.
但是实际上, 考虑到函数在初期梯度不够大, 不符合一般的梯度优化规律, 所以我们会适当对优化的具体形式做一些变化:
实际上, 两个模型同步是最大的问题, 因此GAN往往是比较难训练出来的, 其对于超参数很敏感. 课程上的插图也能说明这一点
那么下面我们就开始激动人心的实战吧.
MNIST数据集
想必各位入坑深度学习第一次接触的就是MNIST数据集吧. 其是一个28*28的手写图片的系列集合, 我们可以看到部分的内容:
我们初始生成的数据是一个-1,1之间的完全随机的数, 根据dim要求完成代码很简单.
两个网络
其实两个网络的组成和一般的网络并无二致, 仅是训练方式有所区别. 在初期为了训练方便我们仅采用了普通的全连接网络模型. 考虑到题目提示已经很明白了, 所以我们可以写出代码:
(这里的非线性函数选择中, tanh是为了数据规范化, 和前面对数据的预处理格式一致, 而其他函数可能是经过实践得到的最好结果)
# discriminator
model = nn.Sequential(
nn.Flatten(),
nn.Linear(784,256),
nn.LeakyReLU(),
nn.Linear(256,256),
nn.LeakyReLU(),
nn.Linear(256,1)
)
# generator
model = nn.Sequential(
nn.Linear(noise_dim,1024),
nn.ReLU(),
nn.Linear(1024,1024),
nn.ReLU(),
nn.Linear(1024,784),
nn.Tanh()
)
其中discriminator的输出为一维数据, 数据是一个连续的值(∈R), 也就表明我们最终输出的并不是一个绝对的真假判断, 而是一个打分值[注意: 实际上sigmoid包含在了bce的loss内, 但是这里没写出来].
GAN误差
我们来看原本作业的解说:
可以看到, 我们考虑到了s和y的实际取值范围, 我们要求s越大越好, 与此同时1-s越小越好, 意即真实的数据打分要比较高, 虚假的数据打分要比较低. 注意到s取值范围, 1-s>0和s>0在log下不能严格成立, 所以需要进行一些修改. 可能这个不好看出来, 这里将输出打分的sigmoid合并在一起了, 大概就是这么个思路:
结果的表达形式的要求就是ln内的数值>0, 因此我们分为两个部分, 其中s>0多处一项并且符号相反, 整合了sigmoid之后能够有这么简单的表达形式, 可以说大道至简,非常巧妙.
# input.clamp(min=0) => max(input,0)
loss = input.clamp(min=0) - input * target + (1 + neg_abs.exp()).log()
这个函数包含了所有真实和虚假的情况, 现在因为真实和虚假是分开投入的, 所以target只需要分别设置为全0和全1即可.
def discriminator_loss(logits_real, logits_fake):
size=logits_real.size()
true_labels = torch.ones(size).type(dtype)
fake_labels = torch.zeros(size).type(dtype)
loss = bce_loss(logits_real,true_labels)+bce_loss(logits_fake,fake_labels-1)
return loss
def generator_loss(logits_fake):
size=logits_fake.size()
true_labels = torch.ones(size).type(dtype)
loss = bce_loss(logits_fake,true_labels)
return loss
训练
随后我们按照题目设置adam优化器就可以开始优化流程了. 优化流程可以说很简单, 就是应用loss. 需要注意, 在这里我们直接D和G相继训练一次, 也就是超参数k=1.
我们监控一下流程. 不难发现, 最初得到的就是纯纯的噪声, 随后慢慢成型. 然而目前我们发现生成图像杂点还是很多, 且D和G的误差是不收敛的.
LS-GAN
LS-GAN的区别仅仅在于误差函数不同, 原本的误差函数是Log, 现在我们变成了平方.
所以我们现在也就不必担心数值稳定性了. 这里因为已知真假, 所以函数只和打分相关.
loss=0.5*(torch.pow(scores_real-1,2).mean()+torch.pow(scores_fake,2).mean()) # discriminator
loss = 0.5*torch.pow(scores_fake-1,2).mean() # generator
因为误差表现形式的不同, 所以误差绝对值会小一些,但是仍然不收敛, 且训练过程表现也不是很一样, 最终效果依然称不上好, 这应该就是网络瓶颈.
DC-GAN
DC-GAN在误差上相对于前两者来说并无区别, 但是采用的D和G网络换成了卷积网络. D网络和常规的LeNet-5基本架构是一致的, 根据题目提示容易得到答案:
Unflatten(batch_size,1,28,28),
nn.Conv2d(in_channels=1,out_channels=32,kernel_size=5,stride=1),
nn.LeakyReLU(),
nn.MaxPool2d(2),
nn.Conv2d(in_channels=32,out_channels=64,kernel_size=5,stride=1),
nn.LeakyReLU(),
nn.MaxPool2d(2),
Flatten(),
nn.Linear(4*4*64,4*4*64),
nn.LeakyReLU(),
nn.Linear(4*4*64,1)
而G网络需要进行上采样. 这个步骤是从pytorch函数convTranspose2d来实现的, 原理就是大家看过的名场面:
这一过程可以表示为: (假设输入和输出的通道数都是1)
upsample = nn.ConvTranspose2d(1, 1, kernel_size=3, stride=2, padding=1)
得到的图像尺寸利用下面的公式得到: (公式源自pytorch文档)
Hout=(Hin−1)×stride[0]−2×padding[0]+dilation[0]×(kernel_size[0]−1)+output_padding[0]+1
Wout=(Win−1)×stride[1]−2×padding[1]+dilation[1]×(kernel_size[1]−1)+output_padding[1]+1
最终的架构如下:
return nn.Sequential(
nn.Linear(noise_dim, 1024),
nn.ReLU(),
nn.BatchNorm1d(1024), # 对于全连接向量使用batchnorm1d而非2d
nn.Linear(1024, 7*7*128),
nn.ReLU(),
nn.BatchNorm1d(7*7*128),
Unflatten(batch_size, 128, 7, 7),
nn.ConvTranspose2d(128, 64, kernel_size=4, stride=2, padding=1),
nn.ReLU(),
nn.BatchNorm2d(64),
nn.ConvTranspose2d(64, 1, kernel_size=4, stride=2, padding=1),
nn.Tanh(), # 规范化输出范围
Flatten(),
)
可以看出, 最终DC-GAN的生成图像质量明显更高,即使在较少的训练次数下也有相对不错的表现.
inline question
问题1
求解:
最终回到了这个点. 由此我们就看出, 在这种情况下问题构成了一个循环, 互相对抗无法达到双方的最优值, 这可能也是某些时候GAN难以训练的原因之一吧.
答案很显然不是, 因为此时我们G可以很好地骗过D, 但是你说D本身段位不够, 我就能说G很高明吗? 肯定不是这样. 只有当双方都收敛,才是我们希望看到的.
标签:loss,CS231N,Linear,nn,assignment,GAN,fake,size From: https://www.cnblogs.com/360MEMZ/p/17364219.html