Press "Enter" to skip to content

GANs如何打造人工名人身份?

介绍

在人工智能时代,一个引人注目的现象正在发生——生成对抗网络(GANs)巧妙地创建了人工名人身份。这种科技与创意的有趣融合,催生出了一种全新的数字名人。加入我们,一起探索GANs的世界,揭开创造迷人虚拟身份的魔力。GANs是如何实现这一切的?让我们来探索这个数字艺术背后的秘密。

来源:Hello Future

学习目标

在本文中,我们将学习:

  • 生成对抗网络(GANs)的概念
  • 如何训练生成器和判别器?
  • 实施GAN模型的逐步过程
  • 通过对抗训练获取GANs随时间改进的见解

本文是数据科学博文马拉松的一部分。

生成对抗网络(GAN)

生成对抗网络(GAN)是由Goodfellow提出的深度学习模型。从名称就可以理解GAN的目的。是的!我们用它来生成东西。它是一个生成数据的网络。这些数据包括图像、文本、音频等等,与真实世界数据相似。GAN包含两个神经网络,它们被称为生成器和判别器。在训练过程中,这两个网络相互竞争,不断提高自己。

生成器是什么?

生成器是负责生成的神经网络。为了输出结果,它需要输入。生成器所接受的输入是一些随机噪声。生成器将这些随机噪声转化为与真实数据相似的输出。每次从判别器那里得到反馈后,它都会不断改善自己,并在下一次生成更好的数据。例如,以图像生成为例,生成器会生成图像。随着训练的进行,它从随机噪声开始,最终改进输出,使其越来越逼真。第一次可能不会产生与原始数据最相似的输出。有时它甚至生成根本不是图像的东西。随着训练的进行,会生成更准确的数据。

判别器是什么?

判别器是负责评估的神经网络。为了更易于理解,我们可以把它称为侦探。判别器同时接收由生成器生成的真实数据和伪造数据。它必须区分伪造数据和真实数据。简单来说,它包括将实际数据与伪造数据进行分类。和生成器一样,随着训练的进行,判别器能够越来越好地区分它们。在第一次尝试时可能无法表现出最佳结果。但在训练过程中,它会不断提高,最终能够正确区分大部分伪造数据。正如我所说的,它必须像一个侦探一样工作。

对抗训练

生成器和判别器都要经历训练,这称为对抗训练。正如我之前提到的,它们会进行竞争性的训练。我们知道生成器生成的伪造数据看起来像真实数据,而判别器则试图区分伪造数据。在训练过程的下一步中,生成器旨在改善并生成能够欺骗判别器的伪造数据。然后判别器会检测到这些伪造数据。这样一来,它们在各自的任务中不断提高。该过程将持续进行,直到生成器生成的数据非常逼真且判别器无法与真实数据区分。此时,GAN达到了一种平衡状态,生成的数据非常类似于真实数据。

实施

让我们首先导入所有必要的库。这主要包括一些torch模块。我们将使用matplotlib进行可视化。

from __future__ import print_function%matplotlib inlineimport argparseimport osimport randomimport torchimport torch.nn as nnimport torch.nn.parallelimport torch.backends.cudnn as cudnnimport torch.optim as optimimport torch.utils.dataimport torchvision.datasets as dsetimport torchvision.transforms as transformsimport torchvision.utils as vutilsimport numpy as npimport matplotlib.pyplot as pltimport matplotlib.animation as animationfrom IPython.display import HTML

数据集

在这个项目实现中,我们将使用CelebFaces Attributes(CelebA)数据集。这个数据集可以在Kaggle中找到。你可以从这里下载。

数据集链接:https://www.kaggle.com/datasets/jessicali9530/celeba-dataset

这个数据集包含202,599张不同名人的脸部图片。用它来训练和测试人脸检测模型,特别是那些能够识别特定面部特征的模型。

初始配置和设置

现在让我们在实际实现之前设置一些参数和配置。首先,我们必须提供数据集的路径。定义工作人员的数量。它表示用于数据加载的工作人员数量。我们使用数据加载工作人员并行加载数据批次,加快训练期间的数据加载过程。定义批量大小和图像大小,训练图像中的通道数,潜在向量的大小,生成器中特征图的大小,判别器中特征图的大小,周期数,学习率,Adam优化器的beta1超参数。可用于训练的GPU数量。如果没有GPU可用,将使用CPU。代码设置这些配置,并打印所选设备(CPU或GPU)和用于训练的GPU数量。在训练GAN模型时,你必须严格控制这些设置。

# 数据集dataroot = r"C:\Users\Admin\Downloads\faces\img_align_celeba"workers = 1batch_size = 16image_size = 64nc = 3# z潜在向量的大小nz = 64# 生成器中特征图的大小ngf = 64# 判别器中特征图的大小ndf = 64num_epochs = 5lr = 0.0001beta1 = 0.2ngpu = 1device = torch.device("cuda:0" if (torch.cuda.is_available() and ngpu > 0) else "cpu")torch.backends.cudnn.benchmark = Trueprint(device)print(ngpu)

数据加载器

让我们为我们的训练数据创建一个PyTorch数据集和数据加载器。使用dset.ImageFolder类创建数据集变量。我们创建了这个PyTorch数据集类来加载按文件夹组织的数据,需要两个必要的参数。我们对数据集中的每个图像应用了一系列的图像变换,称为transform。

使用torch.utils.data.DataLoader创建数据加载器变量。这个类负责批量加载数据。它需要之前定义的数据集和批量大小。除此之外,我们还必须提到用于数据加载的工作进程数以及是否对数据进行洗牌。我们之前定义了这个工作进程数量。为了确保模型不会在每个周期中看到相同顺序的图像,你必须在训练之前对数据进行洗牌。

然后我们绘制一些训练图像,以了解我们的训练数据是什么样子的。

# 创建数据集dataset = dset.ImageFolder(root=dataroot,                           transform=transforms.Compose([                               transforms.Resize(image_size),                               transforms.CenterCrop(image_size),                               transforms.ToTensor(),                               transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),                           ]))# 创建数据加载器dataloader = torch.utils.data.DataLoader(dataset, batch_size=batch_size,                                         shuffle=True, num_workers=workers)# 绘制一些训练图像real_batch = next(iter(dataloader))plt.figure(figsize=(8,8))plt.axis("off")plt.title("Training Images")plt.imshow(np.transpose(vutils.make_grid(real_batch[0].to(device)[:64],            padding=2, normalize=True).cpu(),(1,2,0)))

创建一些噪音。这些噪音是GAN中生成器的输入。noise()函数生成随机噪音。它不带参数,并创建一个形状为(nz, ngf)的随机张量,其中的值来自于0到1之间的均匀分布。还将随机张量缩放,使其值介于-1和1之间。这通过将随机张量乘以2,然后减去1实现。可以将这个噪音张量与其他数据(例如标签或潜在向量)连接起来,并输入生成器以生成图像。

def noise():  return 2*torch.rand(nz, ngf, device = device) - 1

生成器

现在是定义生成器的时候了。为此,我们必须定义生成器类。接下来,指定构造函数方法。它接受ngpu输入,指示有多少个GPU可用于训练。ngpu参数用于处理多GPU训练,但在这段代码中,它并未被使用。在构造函数内部定义了生成器的体系结构。

Sequential块将一系列卷积操作、批归一化和激活函数应用于输入x。然后我们定义了产生输出图像的最终卷积层。即self.toImage。它使用过去层的特征图来创建所需的3通道图像。

接下来,我们必须定义前向方法。生成器的生成过程通过该方法来定义。它接受输入x。这个x是噪音向量或潜在向量,通过构造函数中定义的层将其传递。结果就是生成的图像。初始时,输入x在经过潜在层传递后被转换。然后它经过所有定义的卷积层进行处理。在每个卷积层之后,将特征图与上采样之前对应层的特征图拼接在一起。这有助于生成器捕捉高级特征和空间细节。生成的图像是从最终卷积层的输出中获取并作为前向传递的结果返回。

# GeneratorCodeclass Generator(nn.Module):    def __init__(self, ngpu):        super(Generator, self).__init__()        self.ngpu = ngpu        self.fromLatent = nn.Linear(ngf, (image_size*image_size//16)*ndf)        self.dlayer1 = nn.Sequential(            nn.Conv2d(ndf, ndf, 3, padding=1),            nn.BatchNorm2d(ndf),            nn.ELU(inplace=True),            nn.Conv2d(ndf, ndf, 3, padding=1),            nn.BatchNorm2d(ndf),            nn.ELU(inplace=True)        )        self.dlayer2 = nn.Sequential(            nn.Upsample(scale_factor=2),            nn.Conv2d(2 * ndf, ndf, 3, padding=1),            nn.BatchNorm2d(ndf),            nn.ELU(inplace=True),            nn.Conv2d(ndf, ndf, 3, padding=1),            nn.BatchNorm2d(ndf),            nn.ELU(inplace=True)        )        self.dlayer3 = nn.Sequential(            nn.Upsample(scale_factor=2),            nn.Conv2d(2 * ndf, ndf, 3, padding=1),            nn.BatchNorm2d(ndf),            nn.ELU(inplace=True),            nn.Conv2d(ndf, ndf, 3, padding=1),            nn.BatchNorm2d(ndf),            nn.ELU(inplace=True)        )        self.toImage = nn.Conv2d(ndf, 3, 3, padding = 1)     def forward(self, x):        x = self.fromLatent(x)        x = x.view(x.size(0), ndf, image_size//4, image_size//4)        h0 = x        x = self.dlayer1(x)        x = torch.cat([x, h0], dim=1)        x = self.dlayer2(x)        h0 = nn.functional.interpolate(h0, scale_factor=2, mode='nearest')         x = torch.cat([x, h0], dim=1)        x = self.dlayer3(x)        x = self.toImage(x)        return x

鉴别器

为了识别真假图片,我们需要构建一个类似于之前定义的生成器的鉴别器网络。在这个实例中,会有一个鉴别器类。定义鉴别器类的构造方法,它接受ngpu输入,该输入指定了用于训练的GPU数量,就像生成器一样。

elayer1包含三个卷积层,每个层之后都跟着批量标准化和ELU激活函数。self.toLatent是一个全连接层,它将卷积层的输出映射到一个张量。self.fromLatent是另一个全连接层,它将生成器的输出映射到一个张量。

这些块与生成器中的块相似,但用于解码,也就是图像生成。self.toImage是最后一个卷积层,它产生一个输出图像。它使用之前层的特征图创建一个3通道的图像。forward方法指定了鉴别器的前向传递。它获取真实或生成的图像输入,并通过构造方法中定义的层进行处理。结果是类似于图像的输出。

代码实现

class Discriminator(nn.Module):    def __init__(self, ngpu):        super(Discriminator, self).__init__()        self.ngpu = ngpu        self.elayer1 = nn.Sequential(            nn.Conv2d(通道数, 卷积核数, 3, padding = 1),            nn.BatchNorm2d(卷积核数),            nn.ELU(inplace=True),            nn.Conv2d(卷积核数, 卷积核数, 3, padding = 1),            nn.BatchNorm2d(卷积核数),            nn.ELU(inplace=True),            nn.Conv2d(卷积核数, 2 * 卷积核数, 3, padding = 1),            nn.BatchNorm2d(2 * 卷积核数),            nn.ELU(inplace=True),        )        self.elayer2 = nn.Sequential(            nn.MaxPool2d(2),            nn.Conv2d(2 * 卷积核数, 2 * 卷积核数, 3, padding = 1),            nn.BatchNorm2d(2 * 卷积核数),            nn.ELU(inplace=True),            nn.Conv2d(2 * 卷积核数, 3 * 卷积核数, 3, padding = 1),            nn.BatchNorm2d(3 * 卷积核数),            nn.ELU(inplace=True)        )        self.elayer3 = nn.Sequential(            nn.MaxPool2d(2),            nn.Conv2d(3 * 卷积核数, 3 * 卷积核数, 3, padding = 1),            nn.BatchNorm2d(3 * 卷积核数),            nn.ELU(inplace=True),            nn.Conv2d(3 * 卷积核数, 3 * 卷积核数, 3, padding = 1),            nn.BatchNorm2d(3 * 卷积核数),            nn.ELU(inplace=True)        )        self.toLatent = nn.Linear((image_size*image_size//16)*3*卷积核数, ngf)        self.fromLatent = nn.Linear(ngf, (image_size*image_size//16)*卷积核数)        self.dlayer1 = nn.Sequential(            nn.Conv2d(卷积核数, 卷积核数, 3, padding=1),            nn.BatchNorm2d(卷积核数),            nn.ELU(inplace=True),            nn.Conv2d(卷积核数, 卷积核数, 3, padding=1),            nn.BatchNorm2d(卷积核数),            nn.ELU(inplace=True)        )        self.dlayer2 = nn.Sequential(            nn.Upsample(scale_factor=2),            nn.Conv2d(2*卷积核数, 卷积核数, 3, padding=1),            nn.BatchNorm2d(卷积核数),            nn.ELU(inplace=True),            nn.Conv2d(卷积核数, 卷积核数, 3, padding=1),            nn.BatchNorm2d(卷积核数),            nn.ELU(inplace=True)        )        self.dlayer3 = nn.Sequential(            nn.Upsample(scale_factor=2),            nn.Conv2d(2*卷积核数, 卷积核数, 3, padding=1),            nn.BatchNorm2d(卷积核数),            nn.ELU(inplace=True),            nn.Conv2d(nsdf, 卷积核数, 3, padding=1),            nn.BatchNorm2d(卷积核数),            nn.ELU(inplace=True)        )        self.toImage = nn.Conv2d(卷积核数, 3, 3, padding = 1)            def forward(self, x):        x = self.elayer1(x)        x = self.elayer2(x)        x = self.elayer3(x)        x = x.view(x.size(0), -1)        x = self.toLatent(x)        x = self.fromLatent(x)        x = x.view(x.size(0), 卷积核数, image_size//4, image_size//4)        h0 = x        x = self.dlayer1(x)        x = torch.cat([x, h0], dim=1)        x = self.dlayer2(x)        h0 = torch.nn.functional.interpolate(h0, scale_factor=2, mode='nearest')        x = torch.cat([x, h0], dim=1)        x = self.dlayer3(x)        x = self.toImage(x)        return x

现在,创建一个名为Generator的类,并将ngpu传递给它以构建生成器神经网络。如果有多个GPU,可以使用更多的GPU来提高训练效率。鉴别器神经网络也是类似的方式创建的,通过实例化一个名为Discriminator的类来实现。在下一步中初始化损失函数。

接下来,我们需要创建一个被称为fixed_noise的潜在向量批次。这些潜在向量通常是随机噪声向量,通常从正态分布中抽取。它们用于在训练过程中从生成器生成假图像。然后,我们需要为生成器和鉴别器设置优化器。我们将使用Adam优化器进行优化,Adam是一种流行的优化算法。

# 创建生成器
netG = Generator(ngpu).to(device)

# 处理多GPU
if (device.type == 'cuda') and (ngpu > 1):
    netG = nn.DataParallel(netG, list(range(ngpu)))

# 创建鉴别器
netD = Discriminator(ngpu).to(device)

# 处理多GPU
if (device.type == 'cuda') and (ngpu > 1):
    netD = nn.DataParallel(netD, list(range(ngpu)))

# 初始化损失函数
criterion = nn.L1Loss()

# 创建固定噪声
fixed_noise = noise()

# 为生成器和鉴别器设置Adam优化器
optimizerD = optim.Adam(netD.parameters(), lr=lr, betas=(beta1, 0.999))
optimizerG = optim.Adam(netG.parameters(), lr=lr, betas=(beta1, 0.999))

训练

现在是训练模型的时候了。在此之前,我们需要创建一个名为Timer的类,它将帮助您计算每步的训练时间。创建一些必要的空列表,并定义一些必要的参数来存储训练期间的数据。在训练循环中,迭代预先确定的训练周期和数据加载器中的数据批次,鉴别器通过生成器生成的伪数据进行训练。在训练过程中,生成器和鉴别器都会得到更新。同时,在训练过程中的每一步,所有统计信息都会被打印出来。它收集损失和生成的图像,以便在训练期间和训练结束后进行分析和可视化。

import time

class Timer():
    def __init__(self):
        self.startTime = time.time()
        self.lastTime = time.time()

    def timeElapsed(self):
        auxTime = self.lastTime
        self.lastTime = time.time()
        return self.lastTime - auxTime

    def timeSinceStart(self):
        self.lastTime = time.time()
        return self.lastTime - self.startTime

# 训练循环
k = 0
gamma = 0.4
lambda_k = 0.005
img_list = []
G_losses = []
D_losses = []
iters = 0

print("训练循环开始...")
timer = Timer()

for epoch in range(num_epochs):
    # 对于数据加载器中的每个批次
    for i, data in enumerate(dataloader, 0):
        netD.zero_grad()
        
        # 格式化批次
        real_cpu = data[0].to(device)
        b_size = real_cpu.size(0)
        
        # 鉴别器前向传播真实批次
        output = netD(real_cpu).view(-1)
        
        # 计算真实批次上的损失
        errD_real = criterion(output, real_cpu.view(-1))
        
        # 计算鉴别器反向传播的梯度
        D_x = output.mean().item()
        
        fake = netG(noise())
        
        # 鉴别器对伪批次进行分类
        output = netD(fake.detach()).view(-1)
        
        # 计算鉴别器对伪批次的损失
        errD_fake = criterion(output, fake.view(-1))
        
        # 计算此批次的梯度
        D_loss = errD_real - k * errD_fake
        D_loss.backward()
        D_G_z1 = output.mean().item()
        
        # 添加真实和伪批次的梯度
        optimizerD.step()
        
        netG.zero_grad()
        fake = netG(noise())
        output = netD(fake).view(-1)
        
        # 根据此输出计算G的损失
        errG = criterion(output, fake.view(-1))
        
        # 计算G的梯度
        errG.backward()
        D_G_z2 = output.mean().item()
        
        # 更新G
        optimizerG.step()
        
        delta = (gamma*errD_real - errG).data
        k = max(min(k + lambda_k*delta, 1.0), 0.0)
        
        # 输出训练统计信息
        if i % 50 == 0:
            print('[%.4f] [%d/%d][%d/%d]\tLoss_D: %.4f\tLoss_G: %.4f\tD(x): %.4f\tD(G(z)): %.4f / %.4f' % (timer.timeElapsed(), epoch, num_epochs, i, len(dataloader), D_loss.item(), errG.item(), D_x, D_G_z1, D_G_z2))
        
        # 保存损失以供后续绘图
        G_losses.append(errG.item())
        D_losses.append(D_loss.item())
        
        if (iters % 1000 == 0) or ((epoch == num_epochs-1) and (i == len(dataloader)-1)):
            with torch.no_grad():
                fake = netG(fixed_noise).detach().cpu()
            img_list.append(vutils.make_grid(fake, padding=2, normalize=True))
        
        iters += 1

可视化

现在让我们生成一个绘图来可视化GAN训练过程中生成器和判别器的损失。

plt.figure(figsize=(10,5))plt.title("训练过程中的生成器和判别器损失")plt.plot(G_losses,label="G")plt.plot(D_losses,label="D")plt.xlabel("迭代次数")plt.ylabel("损失")plt.legend()plt.show() 

GANs如何打造人工名人身份? 四海 第2张

同样地,我们生成一个图来比较GAN生成的真实图片和伪造图片。为此,我们需要从数据加载器中获取一批真实图片。这些真实图片用于与GAN生成的图片进行比较。然后我们将绘制真实图片和GAN生成的伪造图片。这样可以直观地比较生成的图片质量与真实数据。

# 从数据加载器中获取一批真实图片real_batch = next(iter(dataloader))# 绘制真实图片plt.figure(figsize=(15,15))plt.subplot(1,2,1)plt.axis("off")plt.title("真实图片")plt.imshow(np.transpose(vutils.make_grid(real_batch[0].to(device)[:64],            padding=5, normalize=True).cpu(),(1,2,0)))# 绘制上一个时期的伪造图片plt.subplot(1,2,2)plt.axis("off")plt.title("伪造图片")plt.imshow(np.transpose(img_list[-1],(1,2,0)))plt.savefig('fake_image.png')plt.show()

GANs如何打造人工名人身份? 四海 第3张

结论

本文中,我们使用生成对抗网络(GAN)对CelebFaces Attributes(CelebA)数据集进行操作,并生成了一些假的名人脸。生成对抗网络(GAN)是技术上的一项重大突破。它们可以创建看起来非常真实的假数据。这有许多用途,对于需要大量数据的项目来说非常有帮助。它的发展非常快速,在近年来使用得越来越多。这项技术是人工智能领域的一项有趣发展,因为它在许多不同的应用中有着广阔的未来。

要点

  • 生成对抗网络(GAN)是一项革命性的人工智能技术,能够生成与真实数据非常接近的数据。
  • 它由两个神经网络组成,一个是生成器,另一个是判别器。这两个网络进行对抗性训练。
  • GAN在图像生成、超分辨率、风格转换和数据增强等多个领域有应用。
  • 有许多种类型的GAN,它们各自有优点和缺点,并且适用于不同的应用。
  • GAN可能引发与深度伪造、虚假内容生成以及隐私侵犯有关的道德问题。

常见问题

本文中显示的媒体不归Analytics Vidhya所有,仅在作者的自由裁量下使用。

Leave a Reply

Your email address will not be published. Required fields are marked *