生成对抗网络(GANs)

顾名思义,生成对抗网络由两部分组成,一是生成模型,就像之前介绍的自动编码器的解码部分。二是对抗模型:严格来说是一个判断真假图片的判别器。

简单来说,生成对抗网络就是希望两个网络相互竞争,通过生成网络生成假的数据,对抗网络判别真伪,最后希望生成网络生成的数据能够以假乱真。

生成模型

此处生成模型不再是将图片输入编码器再通过解编码生成假图片,而是随机初始化一个隐含向量,生成图片,具体步骤如下:首先给出一个简单的高维的正态分布的噪声向量,然后这个时候我们可以通过仿射变换,也就是 xw+b 将其映射到一个更高的维度,然后将他重新排列成一个矩形,这样看着更像一张图片,接着进行一些卷积、转置卷积、池化、激活函数等进行处理,最后得到了一个与我们输入图片大小一模一样的噪音矩阵,这就是我们所说的假的图片。

这个时候我们如何去训练这个生成器呢?这就需要通过对抗学习,增大判别器判别这个结果为真的概率,通过这个步骤不断调整生成器的参数,希望生成的图片越来越像真的,而在这一步中我们不会更新判别器的参数,因为如果判别器不断被优化,可能生成器无论生成什么样的图片都无法骗过判别器。

对抗模型

对抗模型简单来说是一个真假图片的判别器,所以其实就是一个二分类问题。对于假的图片我们期望它输出为0,真的图片我希望它输出为1,这个输出与原图片label没关系,不管原图片有多少总分类,对抗模型都只是一个判断图片为真或为假 的二分类问题。

在训练时,我们先对对抗模型进行训练,希望其能正确区分真的或假的图片。然后再训练生成模型,希望其生成的图片能够骗过对抗模型。

对于此二分类问题,可以用我们前面讲过的很多方法去处理,比如 logistic 回归,深层网络,卷积神经网络,循环神经网络都可以。

生成对抗网络的数学推导

引入KL divergence:
$$
DKL(P\Vert Q)=\int_{-\infty}^{\infty}p(x)\log\frac{p(x)}{q(x)}dx
$$

$$
DKL(P\Vert Q)=\sum_{-\infty}^{\infty}p(i)\log\frac{p(i)}{q(i)}
$$

其可用于衡量两个分布的差异程度,KL divergence越小,两种概率分布越接近。

生成对抗网络想要将一个随机高斯噪声z通过生成网络G得到一个和真实数据分布$p_{data}(x)$差不多的分布$p_G(x;\theta)$,其中$\theta$为网络参数。

从真实数据分布$p_{data}(x)$中采样m个点${x^1,x^2,…,x^m}$,那么生成数据中与这m个点相同的似然为:
$$
L=\prod_{i=1}^mp_G(x^i;\theta)
$$

那么我们所需网络参数$ \theta $可由最大化以上似然函数求得:

$$
\begin{aligned}
\theta^&=\arg_\theta max\prod p_G(x^i;\theta)\\
\log \theta^
&=\arg_\theta max\log\prod p_G(x^i;\theta)\\
&=\arg_\theta max \sum \log(p_G(x^i;\theta))\\
&\approx\arg_\theta maxE_{x\sim p_{data}}\log(p_G(x;\theta))\\
&\Leftrightarrow \arg_\theta max \int_xp_{data}(x)\log\frac{p_{G}(x;\theta)}{p_{data}(x)}\\
&=\arg_\theta\min DKL(p_{data}(x)\Vert p_G(x;\theta))
\end{aligned}
$$

但是我们如何求得$p_G(x;\theta)$呢?我们只知道生成器的先验分布,这里很难通过极大化似然估计求得$\theta$

有没有办法来取代极大似然估计呢?答案是有的,首先定义如下函数:

$$
V(G,D)=E_{x\sim P_{data}}[\log D(X)]+E_{x\sim P_{G}}[\log (1-D(X))]
$$

其中D为对抗模型参数,先给出我们求G的公式:

$$
G^*=\arg \min_G\max_DV(G,D)
$$

怎么得到这个式子的呢?请往下看:

$$
\begin{aligned}
V(G,D)&=E_{x\sim P_{data}}[\log D(X)]+E_{x\sim P_{G}}[\log (1-D(X))]\\
&=\int_x P_{data}(x)\log D(x)dx+\int_x p_G(x)\log(1-D(x))dx\\
&=\int_x[P_{data}(x)\log D(x)]+[p_G(x)\log(1-D(x))]dx
\end{aligned}
$$

首先我们固定G,求一个最优的D使得上式最大,看看其代表什么含义吧:

$$
\begin{aligned}
f(D)&=[P_{data}(x)\log D(x)]+[p_G(x)\log(1-D(x))]\\
&=a\log(D)+b\log(1-D)\\
\frac{df(D)}{dD}&=a\times\frac{1}{D}-b\times \frac{1}{1-D}=0\\
D^*&=\frac{P_{data}(x)}{P_{data}(x)+P_G(x)}
\end{aligned}
$$

那么将$D^*$带入原式:

$$
\begin{aligned}
\max V(G,D)&=V(G,D^*)\\
&=\int_x P_{data}(x)\log\frac{P_{data}(x)}{P_{data}(x)+P_G(x)} dx+\int_x p_G(x)\log\frac{P_{G}(x)}{P_{data}(x)+P_G(x)}dx\\
&=\int_x P_{data}(x)\log\frac{\frac{1}{2}P_{data}(x)}{\frac{P_{data}(x)+P_G(x)}{2}} dx+\int_x p_G(x)\log\frac{\frac{1}{2}P_{G}(x)}{\frac{P_{data}(x)+P_G(x)}{2}}dx\\
&=-2\log2+2JSD(P_{data}(x)\Vert P_G(x))
\end{aligned}
$$

其中:

$$
JSD(P\Vert Q)=\frac{1}{2}DKL(P\Vert M)+\frac{1}{2}DKL(Q\Vert M),M=\frac{1}{2}(P+Q)
$$

由$$D^* $$所获的$$maxV(G,D^)$$包含两个KL divergence,那我们所求$$G^$$就得使其最小:

$$
G^*=\arg \min_G\max_DV(G,D)
$$

生成对抗网络的代码实现

下面我们来进行一下代码实现:

简单的全连接神经网络作为生成和对抗模型:

import torch
from torch import imag, nn
from torch.autograd import Variable
import torchvision.transforms as tfs
from torch.utils.data import DataLoader, sampler
from torchvision.datasets import MNIST
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import argparse
from torchvision.utils import save_image
import os
plt.rcParams['figure.figsize'] = (10.0, 8.0) # 设置画图的尺寸
plt.rcParams['image.interpolation'] = 'nearest'
plt.rcParams['image.cmap'] = 'gray'


def preprocess_img(x):
x = tfs.ToTensor()(x)
return (x - 0.5) / 0.5

def deprocess_img(x):
return (x + 1.0) / 2.0
class ChunkSampler(sampler.Sampler): # 定义一个取样的函数
"""Samples elements sequentially from some offset.
Arguments:
num_samples: # of desired datapoints
start: offset where we should start selecting from
"""
def __init__(self, num_samples, start=0):
self.num_samples = num_samples
self.start = start

def __iter__(self):#迭代器
return iter(range(self.start, self.start + self.num_samples))

def __len__(self):
return self.num_samples

parse1=argparse.ArgumentParser()
parse1.add_argument('-num_train',default=50000,help='用于训练的图片数目')
parse1.add_argument('-num_val',default=5000,help='val_set图片数目')
parse1.add_argument('-noise_dim',default=96)
parse1.add_argument('-batch_size',default=128)
args=parse1.parse_args()



train_set = MNIST('./data', train=True, download=True, transform=preprocess_img)

train_data = DataLoader(train_set, batch_size=args.batch_size, sampler=ChunkSampler(args.num_train, 0))

val_set = MNIST('./data', train=True, download=True, transform=preprocess_img)

val_data = DataLoader(val_set, batch_size=args.batch_size, sampler=ChunkSampler(args.num_val, args.num_train))



def discriminator():#对抗模型
net = nn.Sequential(
nn.Linear(784, 256),
nn.LeakyReLU(0.2),#f(x) = max($\alpha$ x, x)
nn.Linear(256, 256),
nn.LeakyReLU(0.2),
nn.Linear(256, 1)
)
return net
def generator(noise_dim=args.noise_dim):#生成模型
net = nn.Sequential(
nn.Linear(noise_dim, 1024),
nn.ReLU(True),
nn.Linear(1024, 1024),
nn.ReLU(True),
nn.Linear(1024, 784),
nn.Tanh()
)
return net
bce_loss = nn.BCEWithLogitsLoss()

def discriminator_loss(logits_real, logits_fake): # 判别器的 loss由两部分组成:真实图片的loss,生成器生成的假图片的loss
size = logits_real.shape[0]
true_labels = Variable(torch.ones(size, 1)).float().cuda()
false_labels = Variable(torch.zeros(size, 1)).float().cuda()
loss = bce_loss(logits_real, true_labels) + bce_loss(logits_fake, false_labels)
return loss
def generator_loss(logits_fake): # 生成器的 loss
size = logits_fake.shape[0]
true_labels = Variable(torch.ones(size, 1)).float().cuda()
loss = bce_loss(logits_fake, true_labels)
return loss
# 使用 adam 来进行训练,学习率是 3e-4, beta1 是 0.5, beta2 是 0.999
def get_optimizer(net):
optimizer = torch.optim.Adam(net.parameters(), lr=3e-4, betas=(0.5, 0.999))
return optimizer
def train_a_gan(D_net, G_net, D_optimizer, G_optimizer, discriminator_loss, generator_loss, show_every=500,
noise_size=96, num_epochs=10):
iter_count = 0
for epoch in range(num_epochs):
for x, _ in train_data:
bs = x.shape[0]
# 判别网络
real_data = Variable(x).view(bs, -1).cuda() # 真实数据
logits_real = D_net(real_data) # 判别网络得分

sample_noise = (torch.rand(bs, noise_size) - 0.5) / 0.5 # -1 ~ 1 的均匀分布
g_fake_seed = Variable(sample_noise).cuda()
fake_images = G_net(g_fake_seed) # 生成的假的数据
logits_fake = D_net(fake_images) # 判别网络得分

d_total_error = discriminator_loss(logits_real, logits_fake) # 判别器的 loss
D_optimizer.zero_grad()
d_total_error.backward()
D_optimizer.step() # 优化判别网络

# 生成网络
g_fake_seed = Variable(sample_noise).cuda()
fake_images = G_net(g_fake_seed) # 生成的假的数据

gen_logits_fake = D_net(fake_images)
g_error = generator_loss(gen_logits_fake) # 生成网络的 loss
G_optimizer.zero_grad()
g_error.backward()
G_optimizer.step() # 优化生成网络

if (iter_count % show_every == 0):
print('Iter: {}, D: {:.4}, G:{:.4}'.format(iter_count, d_total_error.item(), g_error.item()))
imgs_numpy = deprocess_img(fake_images.data.cpu())
print(imgs_numpy.shape)
if not os.path.exists('./gan_img'):
os.mkdir('./gan_img')
save_image(imgs_numpy.reshape(128,1,28,28)[0:16,:,:],'./gan_img/image_{}.png'.format(iter_count + 1))
iter_count += 1
D = discriminator().cuda()
G = generator().cuda()

D_optim = get_optimizer(D)
G_optim = get_optimizer(G)

train_a_gan(D, G, D_optim, G_optim, discriminator_loss, generator_loss)

8jYfX.png

8jcFC.png

8jwGg.png

8j3l1.png

8jKOt.png

8jOxe.png

上述为该网络经过训练生成的图片,可以看到已经接近手写数字,但效果还不是很好。都说CNN是处理图片最好的网络,那么我们能不能使用CNN作为生成和对抗模型呢?答案当然是可以的,这样的网络被称为 DC GANs,那效果又如何呢?

import torch
from torch import imag, nn
from torch.autograd import Variable
import torchvision.transforms as tfs
from torch.utils.data import DataLoader, sampler
from torchvision.datasets import MNIST
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import argparse
from torchvision.utils import save_image
import os
plt.rcParams['figure.figsize'] = (10.0, 8.0) # 设置画图的尺寸
plt.rcParams['image.interpolation'] = 'nearest'
plt.rcParams['image.cmap'] = 'gray'


def preprocess_img(x):
x = tfs.ToTensor()(x)
return (x - 0.5) / 0.5

def deprocess_img(x):
return (x + 1.0) / 2.0
class ChunkSampler(sampler.Sampler): # 定义一个取样的函数
"""Samples elements sequentially from some offset.
Arguments:
num_samples: # of desired datapoints
start: offset where we should start selecting from
"""
def __init__(self, num_samples, start=0):
self.num_samples = num_samples
self.start = start

def __iter__(self):#迭代器
return iter(range(self.start, self.start + self.num_samples))

def __len__(self):
return self.num_samples

parse1=argparse.ArgumentParser()
parse1.add_argument('-num_train',default=50000,help='用于训练的图片数目')
parse1.add_argument('-num_val',default=5000,help='val_set图片数目')
parse1.add_argument('-noise_dim',default=96)
parse1.add_argument('-batch_size',default=128)
args=parse1.parse_args()



train_set = MNIST('./data', train=True, download=True, transform=preprocess_img)

train_data = DataLoader(train_set, batch_size=args.batch_size, sampler=ChunkSampler(args.num_train, 0))

val_set = MNIST('./data', train=True, download=True, transform=preprocess_img)

val_data = DataLoader(val_set, batch_size=args.batch_size, sampler=ChunkSampler(args.num_val, args.num_train))



class build_dc_classifier(nn.Module):#对抗模型
def __init__(self):
super(build_dc_classifier, self).__init__()
self.conv = nn.Sequential(
nn.Conv2d(1, 32, 5, 1),
nn.LeakyReLU(0.01),
nn.MaxPool2d(2, 2),
nn.Conv2d(32, 64, 5, 1),
nn.LeakyReLU(0.01),
nn.MaxPool2d(2, 2)
)
self.fc = nn.Sequential(
nn.Linear(1024, 1024),
nn.LeakyReLU(0.01),
nn.Linear(1024, 1)
)

def forward(self, x):
x = self.conv(x)
x = x.view(x.shape[0], -1)
x = self.fc(x)
return x

class build_dc_generator(nn.Module):
def __init__(self, noise_dim=args.noise_dim):
super(build_dc_generator, self).__init__()
self.fc = nn.Sequential(
nn.Linear(noise_dim, 1024),
nn.ReLU(True),
nn.BatchNorm1d(1024),
nn.Linear(1024, 7 * 7 * 128),
nn.ReLU(True),
nn.BatchNorm1d(7 * 7 * 128)
)

self.conv = nn.Sequential(
nn.ConvTranspose2d(128, 64, 4, 2, padding=1),
nn.ReLU(True),
nn.BatchNorm2d(64),
nn.ConvTranspose2d(64, 1, 4, 2, padding=1),
nn.Tanh()
)

def forward(self, x):
x = self.fc(x)
x = x.view(x.shape[0], 128, 7, 7) # reshape 通道是 128,大小是 7x7
x = self.conv(x)
return x
bce_loss = nn.BCEWithLogitsLoss()

def discriminator_loss(logits_real, logits_fake): # 判别器的 loss由两部分组成:真实图片的loss,生成器生成的假图片的loss
size = logits_real.shape[0]
true_labels = Variable(torch.ones(size, 1)).float().cuda()
false_labels = Variable(torch.zeros(size, 1)).float().cuda()
loss = bce_loss(logits_real, true_labels) + bce_loss(logits_fake, false_labels)
return loss
def generator_loss(logits_fake): # 生成器的 loss
size = logits_fake.shape[0]
true_labels = Variable(torch.ones(size, 1)).float().cuda()
loss = bce_loss(logits_fake, true_labels)
return loss
# 使用 adam 来进行训练,学习率是 3e-4, beta1 是 0.5, beta2 是 0.999
def get_optimizer(net):
optimizer = torch.optim.Adam(net.parameters(), lr=3e-4, betas=(0.5, 0.999))
return optimizer
def train_a_gan(D_net, G_net, D_optimizer, G_optimizer, discriminator_loss, generator_loss, show_every=500,
noise_size=96, num_epochs=10):
iter_count = 0
for epoch in range(num_epochs):
for x, _ in train_data:
bs = x.shape[0]
# 判别网络
real_data = Variable(x).cuda() # 真实数据
logits_real = D_net(real_data) # 判别网络得分

sample_noise = (torch.rand(bs, noise_size) - 0.5) / 0.5 # -1 ~ 1 的均匀分布
g_fake_seed = Variable(sample_noise).cuda()
fake_images = G_net(g_fake_seed) # 生成的假的数据
logits_fake = D_net(fake_images) # 判别网络得分

d_total_error = discriminator_loss(logits_real, logits_fake) # 判别器的 loss
D_optimizer.zero_grad()
d_total_error.backward()
D_optimizer.step() # 优化判别网络

# 生成网络
g_fake_seed = Variable(sample_noise).cuda()
fake_images = G_net(g_fake_seed) # 生成的假的数据

gen_logits_fake = D_net(fake_images)
g_error = generator_loss(gen_logits_fake) # 生成网络的 loss
G_optimizer.zero_grad()
g_error.backward()
G_optimizer.step() # 优化生成网络

if (iter_count % show_every == 0):
print('Iter: {}, D: {:.4}, G:{:.4}'.format(iter_count, d_total_error.item(), g_error.item()))
imgs_numpy = deprocess_img(fake_images.data.cpu())
if not os.path.exists('./gan_img'):
os.mkdir('./gan_img')
save_image(imgs_numpy.reshape(128,1,28,28)[0:16,:,:],'./gan_img/image_{}.png'.format(iter_count + 1))
iter_count += 1
D = build_dc_classifier().cuda()
G = build_dc_generator().cuda()

D_optim = get_optimizer(D)
G_optim = get_optimizer(G)

train_a_gan(D, G, D_optim, G_optim, discriminator_loss, generator_loss)

8jZFS.png

8jvGR.png

8j6ti.png

8jMOv.png

8jNx0.png

8jo4J.png

可以看到,生成图像十分清晰,比全连接神经网络效果好很多

Least Squares GAN

Least Squares GAN 比最原始的 GANs 的 loss 更加稳定,通过名字我们也能够看出这种 GAN 是通过最小平方误差来进行估计,而不是通过二分类的损失函数,下面我们看看 loss 的计算公式

$$\ell_G = \frac{1}{2}\mathbb{E}_{z \sim p(z)}\left[\left(D(G(z))-1\right)^2\right]$$

$$
\ell_D = \frac{1}{2}\mathbb{E}{x \sim p\text{data}}\left[\left(D(x)-1\right)^2\right] + \frac{1}{2}\mathbb{E}_{z \sim p(z)}\left[ \left(D(G(z))\right)^2\right]
$$

def ls_discriminator_loss(scores_real, scores_fake):
loss = 0.5 * ((scores_real - 1) ** 2).mean() + 0.5 * (scores_fake ** 2).mean()
return loss

def ls_generator_loss(scores_fake):
loss = 0.5 * ((scores_fake - 1) ** 2).mean()
return loss

8jENW.png

清晰度更高,效果有所提升。