虽然我没看完李嘉图,但是也没闲着呀,我还是在写pggan的呀。代号屁股gan计划。我是不会承认我想拿pggan去生成大长腿的。
这个GAN是NVIDIA在17年发表的论文,文章写的比较糙。一开始官方放出了Theano的版本,后来更新了基于TensorFlow的版本。都不是我喜欢的框架。然后就看到北大的一位很快做了一个PyTorch的版本。不过写的太复杂了,后面找到的其他版本基本上也写得跟官方的差不多复杂得一塌糊涂。最后找到一个我能看懂,并且很直观的实现方案:https://github.com/rosinality/progressive-gan-pytorch。然后我就在这个基础上进行修改,做成我比较舒服的脚本。
接下来把几个核心部分做个笔记。
两个trick
Equalized learning rate
作者这里用了第一个trick,就是让每个weight的更新速度是一样的。用的公式是\(\hat{w_i} = w_i/c\)。其中\(w_i\)就是权重,而\(c\)是每一层用何恺明标准化的一个常数。代码如下:
1 | class EqualLR: |
这个很明显是原来作者写的啦,我大蟒蛇还没这么工程化的水平。
Pixelwise normalization
这个是在生成器中进行normalization。公式也很简单,就是\(b_{x,y} = a_{x,y} / \sqrt{\frac{1}{N} \sum_{j=0}{N-1}(a_{x,y}^j)^2 + \epsilon}\)。其中\(\epsilon\)是一个常数\(10^{-8}\),\(N\)是有多少feature map,\(a_{x,y}和b_{x,y}\)是原始feature vector和normalize后的feature vector。代码如下:
1 | class PixelNorm(nn.Module): |
这是两个文章重点提出来的trick。其他其实还有很多trick,不过是偏向设计网络结构的。
#PG-GAN主体
接下来就是最核心的部分,生成器和分类器。生成器和分类器的学习方法就是一步步放大图像的尺寸,从\(4\times 4\)最后放大到\(1024 \times 1024\)。生成器和分类器也是放大一次增加一个block。而这个block的设计也是参考了resnet,因为突然放大会导致模型不稳定,用这种方法可以平滑过渡。
然后就是PG-GAN和dcgan不一样的地方,dcgan放大的方式是用conv_transpose而PG-GAN用的是上采样的方法。Deconvolution and Checkerboard Artifacts这篇文章讲了为什么用上采样更好,不过我没来得及细看。
所以我们先定义好一个block:
1 | class ConvBlock(nn.Module): |
generator
直接上代码:
1 | class Generator(nn.Module): |
这个generator只定义到了\(128\times 128\)这个分辨率的,要是想要增大分辨率可以参考文章最后的附录table 2的数据自己一个个加上去就好了,discriminator一样的操作就行。然后就是代码里面的这个skip_rgb,这个操作就是上面讲的平滑操作。
discriminator
跟generator差不多。
1 | class Distriminator(nn.Module): |
然后mean_std这个地方就是文章里面的另一个trick,叫minibatch stddev,主要是用来增加差异性的,文章的第三部分。
最后只要按照wgan的方法训练就好了。不过还要注意一点的就是,wgan是discriminator训练5次,训练一次generator,而pggan是训一次discriminator,一次generator这样交替来。
1 | experiment_path = 'checkpoint/pggan' |
然后这里面最要注意的是,wgan-gp里面用到了一个很重要的方法,就是gradient penalty,也就是训练里面的这一部分:
1 | eps = torch.rand(b_size, 1, 1, 1, device=device) |
别的也就没什么了,坐等结果就好了。具体在我的notebook里面。