[深度学习]半监督学习、无监督学习之DCGAN深度卷积生成对抗网络(附代码)

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/sinat_33487968/article/details/84966391

论文全称:《Generative Adversarial Nets》

论文地址:https://arxiv.org/pdf/1406.2661.pdf

论文全称:《UNSUPERVISED REPRESENTATION LEARNING WITH DEEP CONVOLUTIONAL GENERATIVE ADVERSARIAL NETWORKS》

论文地址:https://arxiv.org/pdf/1511.06434.pdf

论文代码:

pytorch MNIST :https://colab.research.google.com/github/smartgeometry-ucl/dl4g/blob/master/gan.ipynb

pytorch Celeb-A Faces datasethttps://pytorch.org/tutorials/beginner/dcgan_faces_tutorial.html

TensorFlow Celeb-A Faces datasethttps://github.com/carpedm20/DCGAN-tensorflow

目录

GAN的原理

DCGANs原理

DCGANs in pytorch with MNIST


首先这里有两篇论文,第一篇是2014年Lan Goodfellow首次提出的GAN的框架,这个时候还只是用多层感知机,第二篇是2016年Facebook AI  Research 提出的DCGANs,在前一篇的基础上使用了深度卷积神经网络。

GAN的原理

GAN主要分为两个网络,一个是G(Generator)生成网络,一个是D(Discriminator)判别网络。

他们的作用分别是:

G:利用随机的vector z生成一张图片,即为G(z)。

D:判别图片是否为真实图片。如果为1,就代表100%是真实的图片,而输出为0,就代表不可能是真实的图片。

在训练过程中,生成网络G的目标就是尽量生成真实的图片去欺骗判别网络D。而D的目标就是尽量把G生成的图片和真实的图片分别开来。这样,G和D构成了一个动态的“博弈过程”。

最后博弈的结果是什么?在最理想的状态下,G可以生成足以“以假乱真”的图片G(z)。对于D来说,它难以判定G生成的图片究竟是不是真实的,因此D(G(z)) = 0.5。

这样我们的目的就达成了:我们得到了一个生成式的模型G,它可以用来生成图片。

接下来我们看看D和G究竟需要怎么博弈去逼近我们想要的结果呢?

公式分析:

  • 整个式子由两项构成。x表示真实图片,z表示输入G网络的vector,而G(z)表示G网络生成的图片。

  • D(x)表示D网络判断真实图片是否真实的概率(因为x就是真实的,所以对于D来说,这个值越接近1越好)。而D(G(z))是D网络判断G生成的图片的是否真实的概率。

  • G的目的:上面提到过,D(G(z))是D网络判断G生成的图片是否真实的概率,G应该希望自己生成的图片“越接近真实越好”。也就是说,G希望D(G(z))尽可能得大,这时V(D, G)会变小。因此我们看到式子的最前面的记号是min_G。

  • D的目的:D的能力越强,D(x)应该越大,D(G(x))应该越小。这时V(D,G)会变大。因此式子对于D来说是求最大(max_D)

在训练的过程中也体现了上面min和max的顺序关系,先训练D,再训练G。注意蓝色框框里面ascending和descending的区别,在训练D时,因为我们要使得V最大,所以要做梯度上升,同理,在训练G时,要使得V最小,所以要做梯度下降。

DCGANs原理

简单点说DCGANs就是CNN+GAN。DCGANs把原论文中的D和G用CNN代替了。

DCGAN对卷积神经网络的结构做了一些改变,以提高样本的质量和收敛的速度,这些改变有:

  • 取消所有pooling层。G网络中使用转置卷积(transposed convolutional layer)进行上采样,D网络中用加入stride的卷积代替pooling。

  • 在D和G中均使用batch normalization

  • 去掉FC层,使网络变为全卷积网络

  • G网络中使用ReLU作为激活函数,最后一层使用tanh

  • D网络中使用LeakyReLU作为激活函数

DCGAN中的G网络示意:

DCGANs in pytorch with MNIST

首先导入必要的包和设置超参数:

import os

import torch
import torch.nn as nn
import torch.nn.functional as F

latent_dims = 10
num_epochs = 20
batch_size = 128
learning_rate = 2e-4
use_gpu = True

加载MNIST:

import torchvision.transforms as transforms
from torch.utils.data import DataLoader
from torchvision.datasets import MNIST

img_transform = transforms.Compose([
    transforms.Resize(64),
    transforms.ToTensor(),
    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])

train_dataset = MNIST(root='./data/MNIST', download=True, train=True, transform=img_transform)
train_dataloader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)

test_dataset = MNIST(root='./data/MNIST', download=True, train=False, transform=img_transform)
test_dataloader = DataLoader(test_dataset, batch_size=batch_size, shuffle=True)

定义生成网络,注意:

  • 使用转置卷积(transposed convolutional layer)进行上采样
  • 使用batchnorm
  • G网络中使用ReLU作为激活函数,最后一层使用tanh
class Generator(nn.Module):
    def __init__(self, d=128):
        super(Generator, self).__init__()
        self.deconv1 = nn.ConvTranspose2d(100, d*8, 4, 1, 0)
        self.deconv1_bn = nn.BatchNorm2d(d*8)
        self.deconv2 = nn.ConvTranspose2d(d*8, d*4, 4, 2, 1)
        self.deconv2_bn = nn.BatchNorm2d(d*4)
        self.deconv3 = nn.ConvTranspose2d(d*4, d*2, 4, 2, 1)
        self.deconv3_bn = nn.BatchNorm2d(d*2)
        self.deconv4 = nn.ConvTranspose2d(d*2, d, 4, 2, 1)
        self.deconv4_bn = nn.BatchNorm2d(d)
        self.deconv5 = nn.ConvTranspose2d(d, 1, 4, 2, 1)

    def forward(self, input):
        # x = F.relu(self.deconv1(input))
        x = F.relu(self.deconv1_bn(self.deconv1(input)))
        x = F.relu(self.deconv2_bn(self.deconv2(x)))
        x = F.relu(self.deconv3_bn(self.deconv3(x)))
        x = F.relu(self.deconv4_bn(self.deconv4(x)))
        x = torch.tanh(self.deconv5(x))

        return x

定义判别网络,使用普通2d卷积和batchnorm,注意:

D网络中使用LeakyReLU作为激活函数,最后一层使用sigmoid

class Discriminator(nn.Module):
    def __init__(self, d=128):
        super(Discriminator, self).__init__()
        self.conv1 = nn.Conv2d(1, d, 4, 2, 1)
        self.conv2 = nn.Conv2d(d, d*2, 4, 2, 1)
        self.conv2_bn = nn.BatchNorm2d(d*2)
        self.conv3 = nn.Conv2d(d*2, d*4, 4, 2, 1)
        self.conv3_bn = nn.BatchNorm2d(d*4)
        self.conv4 = nn.Conv2d(d*4, d*8, 4, 2, 1)
        self.conv4_bn = nn.BatchNorm2d(d*8)
        self.conv5 = nn.Conv2d(d*8, 1, 4, 1, 0)

    def forward(self, input):
        x = F.leaky_relu(self.conv1(input), 0.2)
        x = F.leaky_relu(self.conv2_bn(self.conv2(x)), 0.2)
        x = F.leaky_relu(self.conv3_bn(self.conv3(x)), 0.2)
        x = F.leaky_relu(self.conv4_bn(self.conv4(x)), 0.2)
        x = torch.sigmoid(self.conv5(x))

        return x

初始化G和D,查看参数:

generator = Generator()
discriminator = Discriminator()

device = torch.device("cuda:0" if use_gpu and torch.cuda.is_available() else "cpu")
generator = generator.to(device)
discriminator = discriminator.to(device)

num_params_gen = sum(p.numel() for p in generator.parameters() if p.requires_grad)
num_params_disc = sum(p.numel() for p in discriminator.parameters() if p.requires_grad)
print('Number of parameters for generator: %d and discriminator: %d' % (num_params_gen, num_params_disc))

训练网络,基本上有代码注释。先训练D,使得D识别真伪能力更强,然后再训练G,使得G生成的图片更真实,更能骗过D。

# Train GAN
# 分别定义两个优化器,作为生成器和判别器的优化器
gen_optimizer = torch.optim.Adam(params=generator.parameters(), lr=learning_rate, betas=(0.5, 0.999))
disc_optimizer = torch.optim.Adam(params=discriminator.parameters(), lr=learning_rate, betas=(0.5, 0.999))

# 设置为训练模式
generator.train()
discriminator.train()

gen_loss_avg = []
disc_loss_avg = []

print("Training...")
for epoch in range(num_epochs):
    gen_loss_avg.append(0)
    disc_loss_avg.append(0)
    num_batches = 0

    for image_batch, _ in train_dataloader:
        image_batch = image_batch.to(device)
        label_real = torch.ones(image_batch.size(0), device=device)
        label_fake = torch.zeros(image_batch.size(0), device=device)

        # 随机生成,作为生成器的输入
        latent = torch.randn(image_batch.size(0), 100, 1, 1, device=device)
        fake_image_batch = generator(latent)

        # 生成判别器的label,1为真,0为假
        pred_real = discriminator(image_batch).squeeze()  # squeeze()是去除张量里维度为一
        pred_fake = discriminator(fake_image_batch.detach()).squeeze()  # detach是分离生成器和判别器

        # 定义判别器的loss,使他能区分真假
        disc_loss = 0.5 * (
                F.binary_cross_entropy(pred_fake, label_fake) + F.binary_cross_entropy(pred_real, label_real))

        # 判别器的反向传播和权重更新
        disc_optimizer.zero_grad()
        disc_loss.backward()
        disc_optimizer.step()

        # 用权重依旧得到更新的判别器再次判别生成器生成的图像,这时候predict的结果应该比上面更能区分真假
        pred_fake = discriminator(fake_image_batch).squeeze()

        # 定义生成器的loss 使他更接近真实值
        gen_loss = F.binary_cross_entropy(pred_fake, label_real)

        # 生成器的反向传播和权重更新
        gen_optimizer.zero_grad()
        gen_loss.backward()
        gen_optimizer.step()

        # 累加loss
        gen_loss_avg[-1] += gen_loss.item()
        disc_loss_avg[-1] += disc_loss.item()
        num_batches += 1

    # 输出loss
    gen_loss_avg[-1] /= num_batches
    disc_loss_avg[-1] /= num_batches
    print('Epoch [%d / %d] average loss generator vs. discrim.: %f vs. %f' %
          (epoch+1, num_epochs, gen_loss_avg[-1], disc_loss_avg[-1]))

绘制训练loss的曲线:

import matplotlib.pyplot as plt
plt.ion()

fig = plt.figure()
plt.plot(gen_loss_avg, label='generator')
plt.plot(disc_loss_avg, label='discriminator')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
plt.show()

在vector z之间插值,观察生成器生成的图像:

import numpy as np
import matplotlib.pyplot as plt
plt.ion()

import torchvision.utils

generator.eval()

def interpolation(lambda1, model, latent_1, latent_2):

    with torch.no_grad():

        # interpolation of the two latent vectors
        inter_latent = lambda1* latent_1 + (1- lambda1) * latent_2

        # reconstruct interpolated image
        inter_latent = inter_latent.to(device)
        inter_image = model(inter_latent)
        inter_image = inter_image.cpu()

        return inter_image
    
# sample two latent vectors from the standard normal distribution
latent_1 = torch.randn(1, 100, 1, 1, device=device)
latent_2 = torch.randn(1, 100, 1, 1, device=device)

# interpolation lambdas
lambda_range=np.linspace(0,1,10)

fig, axs = plt.subplots(2,5, figsize=(15, 6))
fig.subplots_adjust(hspace = .5, wspace=.001)
axs = axs.ravel()

for ind,l in enumerate(lambda_range):
    inter_image=interpolation(float(l), generator, latent_1, latent_2)
   
    inter_image = to_img(inter_image)
    
    image = inter_image.numpy()
   
    axs[ind].imshow(image[0,0,:,:], cmap='gray')
    axs[ind].set_title('lambda_val='+str(round(l,1)))
plt.show() 

 

随机生成一些latent vector ,看看生成器的生成效果。

import numpy as np
import matplotlib.pyplot as plt
plt.ion()

import torchvision.utils

generator.eval()

def to_img(x):
    x = 0.5 * (x + 1)
    x = x.clamp(0, 1)
    return x

def show_image(img):
    img = to_img(img)
    npimg = img.numpy()
    plt.imshow(np.transpose(npimg, (1, 2, 0)))

with torch.no_grad():

    # sample latent vectors from the standard normal distribution
    latent = torch.randn(image_batch.size(0), 100, 1, 1, device=device)
    fake_image_batch = generator(latent)
    fake_image_batch = fake_image_batch.cpu()

    fig, ax = plt.subplots(figsize=(8, 8))
    show_image(torchvision.utils.make_grid(fake_image_batch.data[:100],10,5))
    plt.show()

展开阅读全文

没有更多推荐了,返回首页