关联文章
AIGC涉及到的算法-之-自然语言处理算法(NLP)
AIGC涉及到的算法-之-生成对抗网络(GAN)
生成对抗网络 (GAN) 的功劳通常归功于 Ian Goodfellow 博士等人。事实是,它是由 Pawel Adamicz 博士(左)和他的博士生 Kavita Sundarajan 博士(右)发明的,他们在 2000 年就有了 GAN 的基本概念——比 Goodfellow 博士发表 GAN 论文早了 14 年。
这个故事是假的,Pawel Adamicz 博士和 Kavita Sundarajan 博士的照片也是假的。它们不存在,是由 GAN 创建的!
GAN 不仅用于有趣的应用程序;他们正在推动深度学习的重大进步。
Yann Lecun 博士是发明卷积神经网络 (CNN) 的真人,他说得再好不过了:
生成对抗网络是机器学习领域过去十年中最有趣的想法。
GAN 非常擅长生成与您的训练数据分布非常相似的逼真新数据实例,事实证明,GAN 是人工智能领域的游戏规则改变者。它们使机器能够在人类的写作、绘画和音乐等活动中表现出色。
什么是生成对抗网络 (GAN)?
生成对抗网络 (GAN) 是一种神经网络,它以随机噪声作为输入并生成输出(例如人脸图片),这些输出似乎是来自训练集(例如其他人脸集)分布的样本。
GAN 通过同时训练两个模型来实现这一壮举
- 捕获训练集分布的生成模型。
- 判别模型估计样本来自训练数据而不是上述生成模型的概率。
为什么选择生成对抗网络 (GAN)?
- 如果您的训练数据不足,没问题。GAN 可以了解您的数据并生成合成图像来增强您的数据集。
- 可以创建看起来像人脸照片的图像,即使这些人脸不属于给定分布中的任何真实人物。那不是很不可思议吗?
- 从描述生成图像(文本到图像合成)。
- 提高视频的分辨率,以捕捉更精细的细节(从低分辨率到高分辨率)。
- 即使在音频领域,GAN 也可用于生成合成的高保真音频或执行语音翻译。
这几个例子展示了 GAN 的能力。这些包括文本到图像合成、图像到图像转换、年龄变化和超分辨率等。
GAN 相对于其他生成模型的优势
今天,GAN 在所有其他生成模型上占据主导地位。让我们看看为什么:
- 数据标记是一项昂贵的任务。GAN 是无监督的,因此不需要标记数据来训练它们。
- GAN 目前生成的图像最清晰。对抗式训练使这成为可能。均方误差产生的模糊图像在 GAN 面前没有机会。
- GAN 中的两个网络都可以仅使用反向传播进行训练。
生成对抗网络 (GAN) 背后的直觉
有两种方法可以查看 GAN。
- 称其为从头开始素描逼真图像的艺术家。像许多成功的艺术家一样,它也觉得需要一个导师来达到更高的熟练程度。因此,GAN 包括:
- 艺术家,即 Generator
- 和一个导师,即 Discriminator
鉴别器帮助生成器从纯粹的噪声中生成逼真的图像。
当 GAN 训练开始时,生成器会产生乱码,不知道真实的观察结果可能是什么样子。在整个训练过程中,噪声是生成器的唯一输入。它一次也看不到原始观察结果。最初,即使是判别器也无法区分真假,尽管它在训练过程中确实会遇到真实和虚假的观察结果。
GAN 的组成部分
生成对抗网络 (GAN) 的理念彻底改变了生成建模领域。蒙特利尔大学的 Ian Goodfellow 等人于 2014 年在 NIPS 会议上首次发表了一篇关于生成对抗网络的论文,他将 GAN 作为一种通过对抗过程估计生成模型的新框架,其中生成模型 G 捕获数据分布,而判别模型 D 估计样本是否来自训练数据而不是 G。
生成器
GAN 中的生成器是一个神经网络,它给定一组随机值,进行一系列非线性计算以生成逼真的图像。生成器生成假图像X假当馈送一个随机向量 Z 时,从多元高斯分布中采样。
虽然我们用 GAN 解决的问题是一个无监督的问题,但我们的目标是从某个类中生成示例。例如,如果我们用猫和狗的图像训练我们的 GAN,我们希望经过训练的生成器能够从这两个类中生成图像。
import torch
z = torch.randn(100)
print(z.mean(), z.var())
(tensor(0.0526), tensor(1.0569))
鉴别器
判别器基于判别建模的概念,您了解到它是一个分类器,它尝试使用特定于类的标签对数据集中的不同类进行分类。因此,从本质上讲,它类似于监督分类问题。此外,判别器对观测值进行分类的能力不仅限于图像,还包括视频、文本和许多其他域(多模态)。
判别器尝试将生成器生成的图像分类为真图像或假图像。
鉴别器在 GAN 中的作用是解决一个二元分类问题,该问题学习区分真实图像和虚假图像。它通过以下方式实现此目的:
- 预测观测值是由生成器 (假) 还是由原始数据分布 (实) 生成。
- 在此过程中,它会学习一组参数或权重 (theta)。随着训练的进行,权重会不断更新。
二进制交叉熵 (BCE) 损失函数用于训练判别器。我们将在此处详细讨论此函数。
从一开始,生成对抗网络 (GAN) 就一直在判别器中使用密集层,您在这里的编码部分也将如此。然而,在 2015 年出现了深度卷积 GAN (DCGAN),它证明了卷积层比 GAN 中的全连接层效果更好。
GAN 的目标函数
您看到的生成器和判别器是根据判别器最后一层给出的分类分数进行训练的,从而判断其输入的假装或真实程度。当然,这使得交叉熵函数成为训练此类网络时的明显选择。我们在这里处理的是二进制类分类问题,因此使用了二进制交叉熵 (BCE) 函数。
binary_cross_entropy = tf.keras.losses.BinaryCrossentropy()
在方程中,您可以看到完整的 BCE 损失函数。
用几行代码编写一个 vanilla GAN
在这里,我们将使用 Pytorch 框架编写 GAN。您将学习从杂色矢量生成与时尚类别相关的图像。
数据集包括:
- 包含 60,000 张时尚图像的数据库。
- 每张大小为 28×28(灰度)的图像都与 10 个类别的标签相关联,例如 T 恤、裤子、运动鞋等。
下列代码,我们将仅使用此数据集的训练拆分,其中包含 60,000 张图像。
来自该数据集的图像将是我们在这篇文章中一直在讨论的真实图像。经过训练后,我们的生成器将能够生成逼真的时尚图像,就像上面显示的那样。
Pytorch 实现
导入模块
import torch
import argparse
import numpy as np
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms
from torch.autograd import Variable
from torchvision.utils import save_image
from torchvision.utils import make_grid
from torch.utils.tensorboard import SummaryWriter
parser = argparse.ArgumentParser()
parser.add_argument("--n_epochs", type=int, default=200, help="number of epochs of training")
parser.add_argument("--batch_size", type=int, default=128, help="size of the batches")
parser.add_argument("--lr", type=float, default=2e-4, help="adam: learning rate")
parser.add_argument("--b1", type=float, default=0.5, help="adam: decay of first order momentum of gradient")
parser.add_argument("--b2", type=float, default=0.999, help="adam: decay of first order momentum of gradient")
parser.add_argument("--latent_dim", type=int, default=100, help="dimension of the latent space (generator's input)")
parser.add_argument("--img_size", type=int, default=28, help="image size")
parser.add_argument("--channels", type=int, default=1, help="image channels")
args = parser.parse_args()
我们首先在第 2-11 行导入必要的包,如 Torch、Torchvision 和 numpy。在今天的教程中,您需要 Torch 1.6 和 Torchvision 0.7 以及 Cuda 10.1。无需在 Google Collaborator 上安装任何代码即可复制该代码
从第 12-21 行开始,我们解析命令行参数:
--n_epochs
:您训练模型的 epoch 数。--batch_size
:每次前向传递中传递给模型的图像数。--lr
:网络的学习率。--b1
和 :亚当优化器梯度的一阶动量衰减。--b2
--latent_dim
:作为输入馈送到生成器的噪声矢量的维数。--img_size
:每个维度的图像大小。--channels
:图像中的通道数( 灰度:1,rgb:3 )。
加载和预处理数据集
train_transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize(mean=(0.5), std=(0.5))])
train_dataset = datasets.FashionMNIST(root='./data/', train=True, transform=train_transform, download=True)
train_loader = torch.utils.data.DataLoader(dataset=train_dataset, batch_size=args.batch_size, shuffle=True)
- 您传递要组合的转换列表。使用平均值和标准差 0.5 对图像进行归一化。请注意,两者都有一个值,因为我们在这里处理的是灰度图像。归一化将像素值从 [0, 255] 映射到 [-1, 1]。范围 [-1, 1] 是首选,已被证明可用于训练 GAN(生成对抗网络)
- 您还可以将图像转换为张量。
生成器网络
class Generator(nn.Module):
def __init__(self):
super(Generator, self).__init__()
self.model = nn.Sequential(nn.Linear(noise_vector, 128),
nn.LeakyReLU(0.2, inplace=True),
nn.Linear(128, 256),
nn.BatchNorm1d(256, 0.8),
nn.LeakyReLU(0.2, inplace=True),
nn.Linear(256, 512),
nn.BatchNorm1d(512, 0.8),
nn.LeakyReLU(0.2, inplace=True),
nn.Linear(512, 1024),
nn.BatchNorm1d(1024, 0.8),
nn.LeakyReLU(0.2, inplace=True),
nn.Linear(1024, image_dim),
nn.Tanh())
def forward(self, noise_vector):
image = self.model(noise_vector)
image = image.view(image.size(0), *image_shape)
return image
定义了生成器的 Sequences 模型,上面的模型结构非常直观。Generator 是一个完全连接的网络,它以噪声矢量 ( latent_dim ) 作为输入并输出 784 维矢量。将生成器视为由低维向量 ( 100-d ) 馈送的解码器,并输出上采样的高维向量 ( 784-d )。
网络主要由层、激活函数等组成。
- 第一层有 128 个神经元,在每个新的线性层上翻倍,最多 1024 个神经元。
- 在此网络中,Leaky ReLU 已用作具有负斜率的中间层的激活函数,如 ,这意味着值低于 的特征将被压缩为 。
- BatchNorm1d 还用于对中间特征向量进行归一化,其值为 0.8,以实现数值稳定性。默认值:1e-5。
- 输出层的 tanh 激活确保像素值与其自己的输出一致映射,即 ( 请记住,我们将图像标准化为 range )。
鉴别器网络
class Discriminator(nn.Module):
def __init__(self):
super(Discriminator, self).__init__()
self.model = nn.Sequential(nn.Linear(image_dim, 512),
nn.LeakyReLU(0.2, inplace=True),
nn.Linear(512, 256),
nn.LeakyReLU(0.2, inplace=True),
nn.Linear(256, 1),
nn.Sigmoid())
def forward(self, image):
image_flattened = image.view(image.size(0), -1)
result = self.model(image_flattened)
return result
鉴别器是一个二进制分类器,仅由全连接层组成。这是一个更简单的模型,其层数比生成器少。
- 输入维度 784 的拼合图像,并输出介于 0 和 1 之间的分数。
- 中间层有 Leaky Relu
- 在输出层具有 Sigmoid 激活函数
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
generator = Generator().to(device)
discriminator = Discriminator().to(device)
定义了 Torch 在训练网络时应使用的设备。生成器和判别器模型都被移动到一个设备中,该设备可以是 CPU 或 GPU,具体取决于硬件。
损失函数
adversarial_loss = nn.BCELoss()
二进制交叉熵损失有助于对两个网络的目标进行建模。
优化
G_optimizer = optim.Adam(generator.parameters(), lr=args.lr, betas=(args.b1, args.b2)
D_optimizer = optim.Adam(discriminator.parameters(), lr=args.lr, betas=(args.b1, args.b2)
训练网络
鉴别器
鉴别器在真实和虚假图像上进行了训练。
for epoch in range(1, args.n_epochs+1):
D_loss_list, G_loss_list = [], []
for index, (real_images, _) in enumerate(train_loader):
D_optimizer.zero_grad() # zero-out the old gradients
real_images = real_images.to(device)
real_target = Variable(torch.ones(real_images.size(0),
1).to(device))
fake_target= Variable(torch.zeros(real_images.size(0),
1).to(device))
# Training Discriminator on Real Data
D_real_loss =
adversarial_loss(discriminator(real_images),
real_target)
# noise vector sampled from a normal distribution
noise_vector =
Variable(torch.randn(real_images.size(0),
args.latent_dim).to(device))
noise_vector = noise_vector.to(device)
generated_image = generator(noise_vector)
# Training Discriminator on Fake Data
D_fake_loss =
adversarial_loss(discriminator(generated_image),
fake_target)
D_total_loss = D_real_loss + D_fake_loss
D_loss_list.append(D_total_loss)
D_total_loss.backward()
D_optimizer.step()
判别器的训练分两部分完成:真实图像和虚假图像(由生成器产生)。当我们处理数据集中的批次时,判别器将图像分类为真实或虚假。它有两个损失:真亏和假亏。加起来,它们给出了组合损失(您甚至可以取两个损失的平均值),用于优化判别器的权重。
生成器
生成器使用判别器的反馈进行训练。
# Train G on D's output
G_optimizer.zero_grad() # zero out the old gradients
generated_image = generator(noise_vector)
G_loss =
adversarial_loss(discriminator(generated_image),
real_target) # G tries to dupe the discriminator
G_loss_list.append(G_loss)
G_loss.backward()
G_optimizer.step()
生成器还从潜在变量向量生成图像,并使用生成器损失更新其参数。