Pytorch之GAN实战

AE,VAE原理

  • 原理(encoder-neck-reconstruct,降维)

    • 自动编码机Auto-Encoder (AE)由两部分encoder和decoder组成,encoder输入x数据,输出潜在变量z,decoder输入z然后输出一个x’,目的是让x’与x的分布尽量一致,当两者完全一样时,中间的潜在变量z可以看作是x的一种压缩状态,包含了x的全部feature特征,此时监督信号就是原数据x本身。

Pytorch之GAN实战_第1张图片

  • 损失函数

Pytorch之GAN实战_第2张图片

  • 两个变种:

  • 加噪声:防止只学到表层特征,对输入图片添加噪声

  • dropout:不依赖于所有上层神经元,增加模型鲁棒性

  • 对抗 AE:

    想让经过encoder后的数据符合某一分布,防止对于任意的分布都会重建为相同的值,我们可以增加一个discriminator鉴别。

Pytorch之GAN实战_第3张图片

  • VAE:变分自编码器

    • 变分自动编码机VAE是自动编码机的一种扩展,它假设输出的潜在变量z服从一种先验分布,如高斯分布。这样,在训练完模型后,我们可以通过采样这种先验分布得到z’,这个z’可能是训练过程中没有出现过的,但是我们依然能在解码器中通过这个z’获得x’,从而得到一些符合原数据x分布的新样本,具有“生成“新样本的能力。
    • 我们通过两个独立的损失项来优化网络,这两个损失项分别是生成损失和KL散度。其中,生成损失指的是生成图片和目标图片之间的像素值均方差,它描述的是网络重建图片的精度。KL散度描述的则是隐变量和标准正态分布之间的匹配程度。这里有一个trick,让编码网络生成均值向量和标准差向量,而不是直接生成隐变量。

AutoEncoder实战

class AE(nn.Module):
    def __init__(self):
        super(AE, self).__init__()
        # [b, 784] => [b, 20]
        self.encoder = nn.Sequential(
            nn.Linear(784, 256),
            nn.ReLU(),
            nn.Linear(256, 64),
            nn.ReLU(),
            nn.Linear(64, 20),
            nn.ReLU()
        )
        # [b, 20] => [b, 784]
        self.decoder = nn.Sequential(
            nn.Linear(20, 64),
            nn.ReLU(),
            nn.Linear(64, 256),
            nn.ReLU(),
            nn.Linear(256, 784),
            nn.Sigmoid()
        )
    def forward(self, x): 		#param x: [b, 1, 28, 28]
        batchsz = x.size(0)
        # flatten
        x = x.view(batchsz, 784)
        # encoder
        x = self.encoder(x)
        # decoder
        x = self.decoder(x)
        # reshape
        x = x.view(batchsz, 1, 28, 28)
        return x

VAE实战

class VAE(nn.Module):
    def __init__(self):
        super(VAE, self).__init__()
        # [b, 784] => [b, 20]
        # u: [b, 10] 均值
        # sigma: [b, 10] 方差 (采样正态分布) 
        self.encoder = nn.Sequential(
            nn.Linear(784, 256),
            nn.ReLU(),
            nn.Linear(256, 64),
            nn.ReLU(),
            nn.Linear(64, 20),
            nn.ReLU()
        )
        # [b, 20] => [b, 784]
        self.decoder = nn.Sequential(
            nn.Linear(10, 64),
            nn.ReLU(),
            nn.Linear(64, 256),
            nn.ReLU(),
            nn.Linear(256, 784),
            nn.Sigmoid()
        )
        self.criteon = nn.MSELoss()
    def forward(self, x):	#param x: [b, 1, 28, 28]
        batchsz = x.size(0)
        # flatten
        x = x.view(batchsz, 784)
        # encoder
        # [b, 20], including mean and sigma
        h_ = self.encoder(x)
        # [b, 20] => [b, 10] and [b, 10] 在1维拆分
        mu, sigma = h_.chunk(2, dim=1)
        # reparametrize trick, epison~N(0, 1)
        h = mu + sigma * torch.randn_like(sigma)
        # decoder
        x_hat = self.decoder(h)
        # reshape
        x_hat = x_hat.view(batchsz, 1, 28, 28)
        # kl散度
        kl = 0.5 * torch.sum(
            torch.pow(mu, 2) +
            torch.pow(sigma, 2) -
            torch.log(1e-8 + torch.pow(sigma, 2)) - 1
        ) / (batchsz*28*28)
        return x_hat, kl

GAN原理

GAN受博弈论中的零和博弈启发,将数据(以图片为例)的生成问题视作判别器和生成器这两个网络的对抗和博弈。生成器从给定噪声中(一般是指均匀分布或者正态分布)产生生成的图片,而判别器分辨生成器输出的生成样本与真实样本。前者G网络试图产生更接近真实样本的样本,相应地,后者D网络试图更完美地分辨真实数据与生成数据。这样,两个网络在对抗中进步,在进步后继续对抗,由G网络生成的样本也就越来越完美,越来越逼近真实的样本,达到生成模型的功能。

为了量化以及数学推导,我们设真实数据为x,随机噪声为z,生成网络为G,判别网络为D,那么GAN网络的loss(交叉熵)可以表示为:

img

其中Pr(x)代表真实样本分布,Pz(z)代表生成样本的分布。D(x)的输出是0-1范围内的一个实数,用来判断这个图片是一个真实图片的概率是多大。通过这样一个极大极小(Max-min)博弈,循环交替地分别优化G和D来训练所需要的生成式网络与判别式网络,直到到达纳什均衡点。所谓循环交替优化,也就是固定G,优化D,一段时间后,固定D再优化G,不断重复上述过程,直到损失函数收敛。

Pytorch之GAN实战_第4张图片

如下图所示,假设在训练开始时,真实样本分布、生成样本分布以及判别模型分别是图中的黑线、绿线和蓝线。可以看出,在训练开始时,判别模型是无法很好地区分真实样本和生成样本的。接下来当我们固定生成模型,而优化判别模型时,优化结果如第二幅图所示,可以看出,这个时候判别模型已经可以较好的区分生成数据和真实数据了。第三步是固定判别模型,改进生成模型,试图让判别模型无法区分生成图片与真实图片,在这个过程中,可以看出由模型生成的图片分布与真实图片分布更加接近,这样的迭代不断进行,直到最终收敛,生成分布和真实分布重合。

Pytorch之GAN实战_第5张图片

DCGAN

2014年提出的最原始的GAN模型在D网络和G网络的网络结构上是通过以多层全连接网络为主体的多层感知机(MLP) 实现的,然而其调参难度较大,训练失败相当常见,生成图片质量也相当不佳(尤其对于复杂的数据集)。

由于卷积神经网络比MLP有更强的拟合与表达能力,并在判别式模型中取得了很大的成果。因此,研究者将CNN引入生成器和判别器,称作深度卷积对抗神经网络(Deep Convolutional GAN),如下图所示。本质上,DCGAN是在GAN的基础上提出了一种训练架构,并对其做了训练指导,比如用卷积层取代了大部分全连接层,去掉池化层,采用Batch Normalization技术,将判别模型的发展成果引入到了生成模型中。模型结构如下。

Pytorch之GAN实战_第6张图片

DCGAN使用的上采样的操作叫做转置卷积,我们可以通过上采样生成指定大小的图片。

WGAN

DCGAN和朴素GAN在训练时存在一个共同的问题就是可能会产生梯度消失。原因是朴素GAN的目标函数本质上可以等价于优化真实分布与生成分布的JS散度,而JS散度有一个问题就是如果两个分布P,Q离得很远,完全没有重叠的时候,那么JS散度值是一个常数,也就意味此时这一点的梯度为0,产生了梯度消失的情况。而且,当真实分布与生成分布是高维空间上的低维流形时,两者重叠部分的测度为0的概率为1,这也就是朴素GAN调参困难且训练容易失败的原因之一。

针对这种现象,研究者使用Wasserstein-1距离(也叫EM距离)来代替JS散度

img

其中, p r p_r pr, p g p_g pg分别为真实分布和生成分布, γ \gamma γ p r , p g p_r,p_g pr,pg的联合分布,相较于JS散度,Wasserstein-1距离的优点是即使 p r , p g p_r,p_g pr,pg两个分布不重叠,Wasserstein-1距离依旧可以清楚地反应出两个分布的距离。为了与GAN相结合,将其转换成对偶形式

img

上式中, f w ( x ) f_w(x) fw(x)表示判别器,WGAN不再需要将判别器当作0-1分类将其值限定在[0,1]之间。而是fw越大,表示其越接近真实分布,反之,就越接近生成分布。此外, f w ( x ) f_w(x) fw(x) 满足1-Lipschitz约束。由于Lipschitz连续在判别器上是难以约束的,因此,采用权重剪枝代替1-Lipschitz约束,即要求参数w ∈ [−c, c],这样判别器的目标函数为:

img

生成器的损失函数为:

img

WGAN的贡献在于,从理论上阐述了因生成器梯度消失而导致训练不稳定的原因,并用1-Wasserstein距离替代了Jensen-Shannon散度,在理论上解决了梯度消失问题。

WGAN-GP

WGAN虽然在理论上解决了梯度消失问题,但是也有很多缺点。其使用权重剪枝(clip)来近似替代1-Lipschitz约束,但是其实这两个条件并不等价,而且满足1-Lipschitz约束的情况多数不满足权重剪枝约束,因此还是产生了很多效果不好的情况。这时,有研究者提出了带梯度惩罚的WGAN(WGAN with gradient penalty),也就是WGAN-GP模型。将1-Lipschitz约束正则化,通过把约束写成目标函数的惩罚项,来近似1-Lipschitz约束条件。效果还是很好的。

Pytorch之GAN实战_第7张图片

因而,WGAN-GP的目标函数为

img

WGAN-GP的贡献在于,它用正则化的形式表达了对判别器的约束,基本从理论和实验上解决了梯度消失的问题,并且具有强大的稳定性,几乎不需要调参,在大多数网络框架下训练成功率极高。

GAN实战

class Generator(nn.Module):
    def __init__(self):
        super(Generator, self).__init__()
        self.net = nn.Sequential(
            nn.Linear(2, h_dim),
            nn.ReLU(True),
            nn.Linear(h_dim, h_dim),
            nn.ReLU(True),
            nn.Linear(h_dim, h_dim),
            nn.ReLU(True),
            nn.Linear(h_dim, 2),
        )
    def forward(self, z):
        output = self.net(z)
        return output
class Discriminator(nn.Module):
    def __init__(self):
        super(Discriminator, self).__init__()
        self.net = nn.Sequential(
            nn.Linear(2, h_dim),
            nn.ReLU(True),
            nn.Linear(h_dim, h_dim),
            nn.ReLU(True),
            nn.Linear(h_dim, h_dim),
            nn.ReLU(True),
            nn.Linear(h_dim, 1),
            nn.Sigmoid()
        )
    def forward(self, x):
        output = self.net(x)
        return output.view(-1)
def data_generator():
	# 8中心高斯混合模型生成数据
    scale = 2.
    centers = [
        (1, 0),
        (-1, 0),
        (0, 1),
        (0, -1),
        (1. / np.sqrt(2), 1. / np.sqrt(2)),
        (1. / np.sqrt(2), -1. / np.sqrt(2)),
        (-1. / np.sqrt(2), 1. / np.sqrt(2)),
        (-1. / np.sqrt(2), -1. / np.sqrt(2))
    ]
    centers = [(scale * x, scale * y) for x, y in centers]
    while True:
        dataset = []
        for i in range(batchsz):
            point = np.random.randn(2) * .02
            center = random.choice(centers)
            point[0] += center[0]
            point[1] += center[1]
            dataset.append(point)
        dataset = np.array(dataset, dtype='float32')
        dataset /= 1.414
        yield dataset
def main():
    G = Generator()
    D = Discriminator()
    optim_G = optim.Adam(G.parameters(), lr=1e-3, betas=(0.5, 0.9))
    optim_D = optim.Adam(D.parameters(), lr=1e-3, betas=(0.5, 0.9))
    data_iter = data_generator()
    for epoch in range(50000):
        # 1. train discriminator (5次)
        for _ in range(5):
            x = next(data_iter)
            xr = torch.from_numpy(x)
            predr = D(xr)
            # max predr, 因为要让真实数据输出尽可能大(real)
            lossr = - (predr.mean())

            z = torch.randn(batchsz, 2)
            xf = G(z).detach()	# 训练D网络,G网络上不传梯度
            predf = D(xf)
            # min predf, 因为要让生成数据输出尽可能小(fake)
            lossf = predf.mean()
            
            loss_D = lossr + lossf
            optim_D.zero_grad()
            loss_D.backward()
            optim_D.step()
        # 2. train Generator
        z = torch.randn(batchsz, 2)
        xf = G(z)
        predf = D(xf)
        # max predf, 作为生成器,想让自己生成的fake数据输出尽可能大
        loss_G = - (predf.mean())
        optim_G.zero_grad()
        loss_G.backward()
        optim_G.step()

WGAN-GP

Pytorch之GAN实战_第8张图片

# 模型及数据生成部分相似
def gradient_penalty(D, xr, xf):
    LAMBDA = 0.3
    # only constrait for Discriminator
    xf = xf.detach()
    xr = xr.detach()
    # [b, 1], 随机sample一个均值分布
    t = torch.rand(batchsz, 1)
    t = alpha.expand_as(xr) # 将alpha扩展为xr的维度 -> [b, 2]
    # real 和 fake线性插值,需要求导
    interpolates = t * xr + (1 - t) * xf
    interpolates.requires_grad_()
    
    disc_ip = D(interpolates)
    gradients = autograd.grad(outputs=disc_ip, inputs=interpolates,
                              grad_outputs=torch.ones_like(disc_ip),
                              create_graph=True, retain_graph=True, only_inputs=True)[0]
    gp = ((gradients.norm(2, dim=1) - 1) ** 2).mean() * LAMBDA
    return gp
def main():
    G = Generator()
    D = Discriminator()
    optim_G = optim.Adam(G.parameters(), lr=1e-3, betas=(0.5, 0.9))
    optim_D = optim.Adam(D.parameters(), lr=1e-3, betas=(0.5, 0.9))
    data_iter = data_generator()
    for epoch in range(50000):
        # 1. train discriminator (5次)
        for _ in range(5):
            x = next(data_iter)
            xr = torch.from_numpy(x)
            predr = D(xr)
            # max predr, 因为要让真实数据输出尽可能大(real)
            lossr = - (predr.mean())

            z = torch.randn(batchsz, 2)
            xf = G(z).detach()	# 训练D网络,G网络上不传梯度
            predf = D(xf)
            # min predf, 因为要让生成数据输出尽可能小(fake)
            lossf = predf.mean()
            
            # gradient penalty
            gp = gradient_penalty(D, xr, xf)
            
            loss_D = lossr + lossf + gp
            optim_D.zero_grad()
            loss_D.backward()
            optim_D.step()
        # 2. train Generator
        z = torch.randn(batchsz, 2)
        xf = G(z)
        predf = D(xf)
        # max predf, 作为生成器,想让自己生成的fake数据输出尽可能大
        loss_G = - (predf.mean())
        optim_G.zero_grad()
        loss_G.backward()
        optim_G.step()
)
            loss_D.backward()
            optim_D.step()
        # 2. train Generator
        z = torch.randn(batchsz, 2)
        xf = G(z)
        predf = D(xf)
        # max predf, 作为生成器,想让自己生成的fake数据输出尽可能大
        loss_G = - (predf.mean())
        optim_G.zero_grad()
        loss_G.backward()
        optim_G.step()

你可能感兴趣的:(Pytorch)