• 基于PyTorch搭建你的生成对抗性网络


    cd0651e77ef4cd23df4cfc0475b1bb07.jpeg

    前言

    你听说过GANs吗?还是你才刚刚开始学?GANs是2014年由蒙特利尔大学的学生 Ian Goodfellow 博士首次提出的。GANs最常见的例子是生成图像。有一个网站包含了不存在的人的面孔,便是一个常见的GANs应用示例。也是我们将要在本文中进行分享的。

    生成对抗网络由两个神经网络组成,生成器和判别器相互竞争。我将在后面详细解释每个步骤。希望在本文结束时,你将能够从零开始训练和建立自己的生财之道对抗性网络。所以闲话少说,让我们开始吧。

    目录

    步骤0: 导入数据集

    步骤1: 加载及预处理图像

    步骤2: 定义判别器算法

    步骤3: 定义生成器算法

    步骤4: 编写训练算法

    步骤5: 训练模型

    步骤6: 测试模型

    步骤0: 导入数据集

    第一步是下载并将数据加载到内存中。我们将使用 CelebFaces Attributes Dataset (CelebA)来训练你的对抗性网络。主要分以下三个步骤:

    1. 下载数据集:

    https://s3.amazonaws.com/video.udacity-data.com/topher/2018/November/5be7eb6f_processed-celeba-small/processed-celeba-small.zip;

    2. 解压缩数据集;

    3. Clone 如下 GitHub地址:

    https://github.com/Ahmad-shaikh575/Face-Generation-using-GANS

    这样做之后,你可以在 colab 环境中打开它,或者你可以使用你自己的 pc 来训练模型。

    导入必要的库

    1. #import the neccessary libraries
    2. import pickle as pkl
    3. import matplotlib.pyplot as plt
    4. import numpy as np
    5. import torch.nn as nn
    6. import torch.nn.functional as F
    7. import torch
    8. from torchvision import datasets
    9. from torchvision import transforms
    10. import torch
    11. import torch.optim as optim

    步骤1: 加载及预处理图像

    在这一步中,我们将预处理在前一节中下载的图像数据。

    将采取以下步骤:

    1. 调整图片大小

    2. 转换成张量

    3. 加载到 PyTorch 数据集中

    4. 加载到 PyTorch DataLoader 中

    1. # Define hyperparameters
    2. batch_size = 32
    3. img_size = 32
    4. data_dir='processed_celeba_small/'
    5. # Apply the transformations
    6. transform = transforms.Compose([transforms.Resize(image_size)
    7. ,transforms.ToTensor()])
    8. # Load the dataset
    9. imagenet_data = datasets.ImageFolder(data_dir,transform= transform)
    10. # Load the image data into dataloader
    11. celeba_train_loader = torch.utils.data.DataLoader(imagenet_data,
    12. batch_size,
    13. shuffle=True)

    图像的大小应该足够小,这将有助于更快地训练模型。Tensors 基本上是 NumPy 数组,我们只是将图像转换为在 PyTorch 中所必需的 NumPy 数组。

    然后我们加载这个转换成的 PyTorch 数据集。在那之后,我们将把我们的数据分成小批量。这个数据加载器将在每次迭代时向我们的模型训练过程提供图像数据。

    随着数据的加载完成。现在,我们可以预处理图像。

    图像的预处理

    我们将在训练过程中使用 tanh 激活函数。该生成器的输出范围在 -1到1之间。我们还需要对这个范围内的图像进行缩放。代码如下所示:

    1. def scale(img, feature_range=(-1, 1)):
    2. '''
    3. Scales the input image into given feature_range
    4. '''
    5. min,max = feature_range
    6. img = img * (max-min) + min
    7. return img

    这个函数将对所有输入图像缩放,我们将在后面的训练中使用这个函数。

    现在我们已经完成了无聊的预处理步骤。

    接下来是令人兴奋的部分,现在我们需要为我们的生成器和判别器神经网络编写代码。

    步骤2: 定义判别器算法

    97127e11857e4e2d08f27fe2e2848c52.png

    判别器是一个可以区分真假图像的神经网络。真实的图像和由生成器生成的图像都将提供给它。

    我们将首先定义一个辅助函数,这个辅助函数在创建卷积网络层时非常方便。

    1. # helper conv function
    2. def conv(in_channels, out_channels, kernel_size, stride=2, padding=1, batch_norm=True):
    3. layers = []
    4. conv_layer = nn.Conv2d(in_channels, out_channels,
    5. kernel_size, stride, padding, bias=False)
    6. #Appending the layer
    7. layers.append(conv_layer)
    8. #Applying the batch normalization if it's given true
    9. if batch_norm:
    10. layers.append(nn.BatchNorm2d(out_channels))
    11. # returning the sequential container
    12. return nn.Sequential(*layers)

    这个辅助函数接收创建任何卷积层所需的参数,并返回一个序列化的容器。现在我们将使用这个辅助函数来创建我们自己的判别器网络。

    1. class Discriminator(nn.Module):
    2. def __init__(self, conv_dim):
    3. super(Discriminator, self).__init__()
    4. self.conv_dim = conv_dim
    5. #32 x 32
    6. self.cv1 = conv(3, self.conv_dim, 4, batch_norm=False)
    7. #16 x 16
    8. self.cv2 = conv(self.conv_dim, self.conv_dim*2, 4, batch_norm=True)
    9. #4 x 4
    10. self.cv3 = conv(self.conv_dim*2, self.conv_dim*4, 4, batch_norm=True)
    11. #2 x 2
    12. self.cv4 = conv(self.conv_dim*4, self.conv_dim*8, 4, batch_norm=True)
    13. #Fully connected Layer
    14. self.fc1 = nn.Linear(self.conv_dim*8*2*2,1)
    15. def forward(self, x):
    16. # After passing through each layer
    17. # Applying leaky relu activation function
    18. x = F.leaky_relu(self.cv1(x),0.2)
    19. x = F.leaky_relu(self.cv2(x),0.2)
    20. x = F.leaky_relu(self.cv3(x),0.2)
    21. x = F.leaky_relu(self.cv4(x),0.2)
    22. # To pass throught he fully connected layer
    23. # We need to flatten the image first
    24. x = x.view(-1,self.conv_dim*8*2*2)
    25. # Now passing through fully-connected layer
    26. x = self.fc1(x)
    27. return x

    步骤3: 定义生成器算法

    d30da4cfe3a34a4e28baacfc833e75f8.png

    正如你们从图中看到的,我们给网络一个高斯矢量或者噪声矢量,它输出 s 中的值。图上的“ z”表示噪声,右边的 G (z)表示生成的样本。

    与判别器一样,我们首先创建一个辅助函数来构建生成器网络,如下所示:

    1. def deconv(in_channels, out_channels, kernel_size, stride=2, padding=1, batch_norm=True):
    2. layers = []
    3. convt_layer = nn.ConvTranspose2d(in_channels, out_channels,
    4. kernel_size, stride, padding, bias=False)
    5. # Appending the above conv layer
    6. layers.append(convt_layer)
    7. if batch_norm:
    8. # Applying the batch normalization if True
    9. layers.append(nn.BatchNorm2d(out_channels))
    10. # Returning the sequential container
    11. return nn.Sequential(*layers)

    现在,是时候构建生成器网络了! !

    1. class Generator(nn.Module):
    2. def __init__(self, z_size, conv_dim):
    3. super(Generator, self).__init__()
    4. self.z_size = z_size
    5. self.conv_dim = conv_dim
    6. #fully-connected-layer
    7. self.fc = nn.Linear(z_size, self.conv_dim*8*2*2)
    8. #2x2
    9. self.dcv1 = deconv(self.conv_dim*8, self.conv_dim*4, 4, batch_norm=True)
    10. #4x4
    11. self.dcv2 = deconv(self.conv_dim*4, self.conv_dim*2, 4, batch_norm=True)
    12. #8x8
    13. self.dcv3 = deconv(self.conv_dim*2, self.conv_dim, 4, batch_norm=True)
    14. #16x16
    15. self.dcv4 = deconv(self.conv_dim, 3, 4, batch_norm=False)
    16. #32 x 32
    17. def forward(self, x):
    18. # Passing through fully connected layer
    19. x = self.fc(x)
    20. # Changing the dimension
    21. x = x.view(-1,self.conv_dim*8,2,2)
    22. # Passing through deconv layers
    23. # Applying the ReLu activation function
    24. x = F.relu(self.dcv1(x))
    25. x= F.relu(self.dcv2(x))
    26. x= F.relu(self.dcv3(x))
    27. x= F.tanh(self.dcv4(x))
    28. #returning the modified image
    29. return x

    为了使模型更快地收敛,我们将初始化线性和卷积层的权重。根据相关研究论文中的描述:所有的权重都是从0中心的正态分布初始化的,标准差为0.02

    我们将为此目的定义一个功能如下:

    1. def weights_init_normal(m):
    2. classname = m.__class__.__name__
    3. # For the linear layers
    4. if 'Linear' in classname:
    5. torch.nn.init.normal_(m.weight,0.0,0.02)
    6. m.bias.data.fill_(0.01)
    7. # For the convolutional layers
    8. if 'Conv' in classname or 'BatchNorm2d' in classname:
    9. torch.nn.init.normal_(m.weight,0.0,0.02)

    现在我们将超参数和两个网络初始化如下:

    1. # Defining the model hyperparamameters
    2. d_conv_dim = 32
    3. g_conv_dim = 32
    4. z_size = 100 #Size of noise vector
    5. D = Discriminator(d_conv_dim)
    6. G = Generator(z_size=z_size, conv_dim=g_conv_dim)
    7. # Applying the weight initialization
    8. D.apply(weights_init_normal)
    9. G.apply(weights_init_normal)
    10. print(D)
    11. print()
    12. print(G)

    输出结果大致如下:

    b0dbbadcdafb3a9f8bf28da76d19bd8f.png

    判别器损失:

    根据 DCGAN Research Paper 论文中描述:

            判别器总损失 = 真图像损失 + 假图像损失,即:d_loss = d_real_loss + d_fake_loss。

           不过,我们希望鉴别器输出1表示真正的图像和0表示假图像,所以我们需要设置的损失来反映这一点。

    我们将定义双损失函数。一个是真正的损失,另一个是假的损失,如下:

    1. def real_loss(D_out,smooth=False):
    2. batch_size = D_out.size(0)
    3. if smooth:
    4. labels = torch.ones(batch_size)*0.9
    5. else:
    6. labels = torch.ones(batch_size)
    7. labels = labels.to(device)
    8. criterion = nn.BCEWithLogitsLoss()
    9. loss = criterion(D_out.squeeze(), labels)
    10. return loss
    11. def fake_loss(D_out):
    12. batch_size = D_out.size(0)
    13. labels = torch.zeros(batch_size)
    14. labels = labels.to(device)
    15. criterion = nn.BCEWithLogitsLoss()
    16. loss = criterion(D_out.squeeze(), labels)
    17. return loss

    生成器损失:

    根据 DCGAN Research Paper 论文中描述:

            生成器的目标是让判别器认为它生成的图像是真实的。

    现在,是时候为我们的网络设置优化器了:

    1. lr = 0.0005
    2. beta1 = 0.3
    3. beta2 = 0.999 # default value
    4. # Optimizers
    5. d_optimizer = optim.Adam(D.parameters(), lr, betas=(beta1, beta2))
    6. g_optimizer = optim.Adam(G.parameters(), lr, betas=(beta1, beta2))

    我将为我们的训练使用 Adam 优化器。因为它目前被认为是对GANs最有效的。根据上述介绍论文中的研究成果,确定了超参数的取值范围。他们已经尝试了它,这些被证明是最好的!超参数设置如下:

    步骤4: 编写训练算法

    我们必须为我们的两个神经网络编写训练算法。首先,我们需要初始化噪声向量,并在整个训练过程中保持一致。

    1. # Initializing arrays to store losses and samples
    2. samples = []
    3. losses = []
    4. # We need to initilialize fixed data for sampling
    5. # This would help us to evaluate model's performance
    6. sample_size=16
    7. fixed_z = np.random.uniform(-1, 1, size=(sample_size, z_size))
    8. fixed_z = torch.from_numpy(fixed_z).float()

    对于判别器:

    我们首先将真实的图像输入判别器网络,然后计算它的实际损失。然后生成伪造图像并输入判别器网络以计算虚假损失。

    在计算了真实和虚假损失之后,我们对其进行求和,并采取优化步骤进行训练。

    1. # setting optimizer parameters to zero
    2. # to remove previous training data residue
    3. d_optimizer.zero_grad()
    4. # move real images to gpu memory
    5. real_images = real_images.to(device)
    6. # Pass through discriminator network
    7. dreal = D(real_images)
    8. # Calculate the real loss
    9. dreal_loss = real_loss(dreal)
    10. # For fake images
    11. # Generating the fake images
    12. z = np.random.uniform(-1, 1, size=(batch_size, z_size))
    13. z = torch.from_numpy(z).float()
    14. # move z to the GPU memory
    15. z = z.to(device)
    16. # Generating fake images by passing it to generator
    17. fake_images = G(z)
    18. # Passing fake images from the disc network
    19. dfake = D(fake_images)
    20. # Calculating the fake loss
    21. dfake_loss = fake_loss(dfake)
    22. #Adding both lossess
    23. d_loss = dreal_loss + dfake_loss
    24. # Taking the backpropogation step
    25. d_loss.backward()
    26. d_optimizer.step()

    对于生成器:

    对于生成器网络的训练,我们也会这样做。刚才在通过判别器网络输入假图像之后,我们将计算它的真实损失。然后优化我们的生成器网络。

    1. ## Training the generator for adversarial loss
    2. #setting gradients to zero
    3. g_optimizer.zero_grad()
    4. # Generate fake images
    5. z = np.random.uniform(-1, 1, size=(batch_size, z_size))
    6. z = torch.from_numpy(z).float()
    7. # moving to GPU's memory
    8. z = z.to(device)
    9. # Generating Fake images
    10. fake_images = G(z)
    11. # Calculating the generator loss on fake images
    12. # Just flipping the labels for our real loss function
    13. D_fake = D(fake_images)
    14. g_loss = real_loss(D_fake, True)
    15. # Taking the backpropogation step
    16. g_loss.backward()
    17. g_optimizer.step()

    步骤5: 训练模型

    现在我们将开始100个epoch的训练: D

    经过训练,损失的图表看起来大概是这样的:

    297583a673349a29e8b1e16942b38a73.png

    我们可以看到,判别器 Loss 是相当平滑的,甚至在100个epoch之后收敛到某个特定值。而生成器的Loss则飙升。

    我们可以从下面步骤6中的结果看出,60个时代之后生成的图像是扭曲的。由此可以得出结论,60个epoch是一个最佳的训练节点。

    步骤6: 测试模型

    10个epoch之后:

    96d12f9703a12ac0e77a0c2f87c3f146.png

    20个epoch之后:

    dc196f60f97d770c6d0c79c8f4d85ae7.png

    30个epoch之后:

    33365e822eda268d5e048d296cbea7be.png

    40个epoch之后:

    0198316b876a53571d5550f9f97556d5.png

    50个epoch之后:

    bdcf6ad6c3a1977c55cdd7f6fd9a4816.png

    60个epoch之后:

    4152c319441182ae8907a6b354fb6a44.png

    70个epoch之后:

    b48254e7675b521a66d059da034db13b.png

    80个epoch之后:

    e567311618525ee9d2e67e0292fe067d.png

    90个epoch之后:

    c933353f759c001a2d1cd8d9ec81f3ba.png

    100个epoch之后:

    0f83fe27e164d062d3afad75e7960e75.png

    总结

    我们可以看到,训练一个生成对抗性网络并不意味着它一定会产生好的图像。

    从结果中我们可以看出,训练40-60个 epoch 的生成器生成的图像相对比其他更好。

    您可以尝试更改优化器、学习速率和其他超参数,以使其生成更好的图像!

  • 相关阅读:
    【Jetson】使用 Jetson 控制无人车常用指令
    leetCode 70.爬楼梯 动态规划
    密码学奇妙之旅、02 混合加密系统、AES、RSA标准、Golang代码
    牛客小白月赛#55
    Linux fork函数详解
    Sealos 安装报错问题解决
    【LeetCode75】第五十五题 寻找峰值
    C/C++公交路线自动化选择系统
    【Linux】权限
    OpenWRT配置SFTP远程文件传输,实现数据安全保护
  • 原文地址:https://blog.csdn.net/qq_39312146/article/details/134410046