2025.1.12机器学习笔记:GAN文献阅读

第二十九周周报

文献阅读

题目信息

  • 题目: Generative Adversarial Nets
  • 会议: NeurIPS
  • 作者: Ian J. Goodfellow∗, Jean Pouget - Abadie†, Mehdi Mirza, Bing Xu, David Warde - Farley, Sherjil Ozair‡, Aaron Courville, Yoshua Bengio§
  • 发表时间: 2014/06/10
  • 文章链接: https://arxiv.org/pdf/1406.2661

摘要

本周阅读了GAN的奠基论文,目的是对GAN进行更加深入的理解,为后续要研究TimeGAN时序数据的增强做理论知识的准备。文章的背景是深度学习在判别模型方面取得了显著成功,但深度生成模型的表现在当时的表现还是很一般,这是因为在直接构造分布函数的时候,计算最大似然值的过程往往是非常困难的。为了避免复杂的计算过程,作者提出了一种新的生成对抗模型框架,即通过生成器(Generator)和辨别器(Discriminator)不断的博弈从而使得生成器能够生成高质量的数据。在博弈过程中,生成模型试图生成难以被判别模型区分的数据,判别模型则试图区分数据是来自真实数据分布还是生成模型。作者研究了生成模型和判别模型均为多层感知器这一特殊情况,并展示了该框架在多个数据集上的实验结果,证明了GAN的优越性以及后续发展的潜力。

Abstract

This week, I engaged with the seminal paper on Generative Adversarial Networks (GANs) to deepen my understanding of this framework. This study is instrumental for my forthcoming research on temporal data augmentation using TimeGAN. The paper's context highlights the significant achievements of deep learning in discriminative modeling, contrasting with the relatively modest performance of deep generative models at the time. The latter's underperformance was largely attributed to the computational complexity associated with directly modeling distribution functions and calculating maximum likelihood estimates. To mitigate these challenges, the authors proposed a novel generative adversarial framework that leverages a continuous game between a generator and a discriminator to produce high-quality data. During this adversarial process, the generator aims to produce data that is indistinguishable from the true data distribution, while the discriminator seeks to differentiate between real and generated data. The authors examined the specific case where both the generator and discriminator are multilayer perceptrons (MLPs) and demonstrated the framework's effectiveness across multiple datasets, thereby validating the superiority and potential of GANs for future development.

创新点

  1. 在当时提出新的生成对的模型,以对抗过程训练生成模型和判别模型。
  2. GAN与其他生成方法相比,不需要使用马尔可夫链等算法对复杂分布进行采样,体现了其计算简单的优势。

网络架构

GAN的结构如下图所示:

它由两个部分组成:生成器(Generator)和判别器(Discriminator)。通过生成网络 G G G和判别网络 D D D的不断博弈,使 G G G学习到数据的分布,从而能够生成逼真的数据样本。生成器 G G G通过接收一个随机的噪声z(通常是高斯分布),通过这个噪声生成新的数据 G ( z ) G(z) G(z)。判别器 D D D是一个判别网络,它的输入参数是 x x x, x x x代表一个数据样本,输出 D ( x ) D(x) D(x)代表 x x x为真实数据的概率。其中,判别器的目标是区分真实数据和生成器生成的假数据,生成器的目标是生成足够逼真的数据,以至于判别器无法区分它们和真实数据。

算法研究

  1. 作者在论文中得出了如下结论:对 f ( x ) f(x) f(x)的期望求导等同于对 f ( x ) f(x) f(x)自身求导

    公式表达如下所示:
    lim ⁡ σ → 0 ∇ x E ϵ ∼ N ( 0 , σ 2 I ) f ( x + ϵ ) = ∇ x f ( x ) \lim {\sigma \rightarrow 0} \nabla{\boldsymbol{x}} \mathbb{E}{\epsilon \sim \mathcal{N}\left(0, \sigma^{2} \boldsymbol{I}\right)} f(\boldsymbol{x}+\epsilon)=\nabla{\boldsymbol{x}} f(\boldsymbol{x}) limσ→0∇xEϵ∼N(0,σ2I)f(x+ϵ)=∇xf(x)

    这就解释了为什么能够直接使用GAN来作为生成模型,因为其求期望求导等同于对求梯度,GAN是使用梯度下降算法去优化的。

  2. 作者对模型的优化问题是这样描述的:

    所以基于上述描述,G与D的相互博弈进化的过程可以总结为如下公式:
    min ⁡ G max ⁡ D V ( D , G ) = E x ∼ p data ( x ) [ log ⁡ D ( x ) ] + E z ∼ p z ( z ) [ log ⁡ ( 1 − D ( G ( z ) ) ) ] ( 1 ) \min {G} \max {D} V(D, G)=\mathbb{E}{\boldsymbol{x} \sim p{\text {data }}(\boldsymbol{x})}[\log D(\boldsymbol{x})]+\mathbb{E}{\boldsymbol{z} \sim p{\boldsymbol{z}}(\boldsymbol{z})}[\log (1-D(G(\boldsymbol{z})))]\space(1) GminDmaxV(D,G)=Ex∼pdata (x)[logD(x)]+Ez∼pz(z)[log(1−D(G(z)))] (1)
    其博弈优化的过程,如下图所示:

    (a)中生成器生成的数据分布(绿色实线)与真实数据分布(黑色虚线)相似,判别器(蓝色虚线)能够较好地区分两者。

    (b)中判别器被训练来更好地区分真实数据和生成器生成的数据。

    ©中,生成器更新后,生成的数据分布开始向真实数据分布靠拢,判别器的梯度引导生成器生成更像真实数据的样本。

    (d)中,经过多次训练后,生成器和判别器会达到一个平衡点,此时生成器生成的数据分布与真实数据分布完全相同,判别器无法区分两者,即判别器的输出为0.5,表示它对任何输入数据都无法确定其是真实数据还是生成数据。

    此外,作者还提出了一个训练可以优化的点:

  3. GAN的训练步骤如下图所示:

    先从随机噪声中生成m个假数据,再从真实数据集中抽取个真实数据(总共有2m的数据),然后让判别器去判断这些数据的真假,并根据它的判断来调整它的参数,让辨别能力增强。然后训练我们的生成器,调整生成器的参数,让它生成的数据更能骗过判别器。这个步骤多次重复,直到生成器生成的数据使得判别器难以区分真假。

  4. 作者提出了 D^*^的最优解为 p d a t a ( x ) p d a t a ( x ) + p g ( x ) \frac{p_{data}(x)}{p_{data}(x)+p_{g}(x)} pdata(x)+pg(x)pdata(x),并通过数学理论证明了过程

    证明过程如下:

    且当即p~g~ = p~data~的时候,即D^*^=1/2,G就时最好的,因为此时对于G生成出来的图片与真实的数据,D已经失效了。

实验

论文通过将高斯Parzen窗拟合到生成器G生成的样本上,估计测试集数据在 p g p_{g} pg下的概率,并展示该分布下的对数似然值。其中,高斯的 σ \sigma σ参数通过在验证集上交叉验证获得。作者认为这种估计似然的方法方差较高,在高维空间中表现不佳,但是当前目前是已知的最佳方法。

在MNIST、多伦多人脸数据库(TFD)和CIFAR - 10等数据集上训练GAN。

  1. Parzen窗口法是一种非参数估计方法,用于估计概率密度函数。它通过在样本点周围放置窗口函数来构建概率密度函数的估计。高斯Parzen窗口就是使用高斯函数作为窗口函数。如,假设我们有一组样本数据{x~1~,x~2~,...,x~n~},这些样本是通过某种生成过程得到的。在高斯Parzen窗口法中,对于一个新的数据点x,我们会在每个样本点x~i~周围放置一个高斯函数 ϕ ( h x − x i ) ϕ( \frac{h}{x−x^i}) ϕ(x−xih),其中h是窗口宽度参数。
  2. 交叉验证的基本思想是将数据集分成若干个"折叠"(或"折")然后通过系统地将每个折叠作为测试集,其余折叠作为训练集,来评估模型的性能。比如1000张图片,你想用这些图片来训练和测试一个图像识别模型。你可以将这1000张图片分成10个"折叠",每个"折叠"包含100张图片。

将GAN与其他实值版本数据集的模型进行比较,以Parzen窗为基础的对数似然估计结果如表1所示:

在MNIST数据集上,,对抗网络的样本在测试集上的平均对数似然为 225 ± 2 225\pm2 225±2。

在TFD数据集上,计算了数据集各折的标准误差,通过对每折的验证集选择不同的 σ \sigma σ,计算每折的平均对数似然,对抗网络的结果为 2057 ± 26 2057\pm26 2057±26。

最后,论文展示了训练后从GAN抽取的样本,如下图所示:

在MNIST、TFD、CIFAR - 10等不同数据类型上都有可视化展示

虽然结果一般般,但是作者认为在当时的环境下,GAN是非常有潜力的。

此外,作者在下图中展示了通过在z空间坐标之间线性插值得到的数字:

代码如下:

python 复制代码
import argparse
import os
import numpy as np
import torchvision.transforms as transforms
from torchvision.utils import save_image
from torch.utils.data import DataLoader
from torchvision import datasets
from torch.autograd import Variable
import torch.nn as nn
import torch

## 创建文件夹
os.makedirs("./images/gan/", exist_ok=True)  ## 记录训练过程的图片效果
os.makedirs("./save/gan/", exist_ok=True)  ## 训练完成时模型保存的位置
os.makedirs("./datasets/mnist", exist_ok=True)  ## 下载数据集存放的位置

## 超参数配置
parser = argparse.ArgumentParser()
parser.add_argument("--n_epochs", type=int, default=50, help="number of epochs of training")
parser.add_argument("--batch_size", type=int, default=64, help="size of the batches")
parser.add_argument("--lr", type=float, default=0.0002, 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("--n_cpu", type=int, default=2, help="number of cpu threads to use during batch generation")
parser.add_argument("--latent_dim", type=int, default=100, help="dimensionality of the latent space")
parser.add_argument("--img_size", type=int, default=28, help="size of each image dimension")
parser.add_argument("--channels", type=int, default=1, help="number of image channels")
parser.add_argument("--sample_interval", type=int, default=500, help="interval betwen image samples")
opt = parser.parse_args()
## opt = parser.parse_args(args=[])                 ## 在colab中运行时,换为此行
print(opt)

## 图像的尺寸:(1, 28, 28),  和图像的像素面积:(784)
img_shape = (opt.channels, opt.img_size, opt.img_size)
img_area = np.prod(img_shape)

## 设置cuda:(cuda:0)
cuda = True if torch.cuda.is_available() else False

## mnist数据集下载
mnist = datasets.MNIST(
    root='./datasets/', train=True, download=True, transform=transforms.Compose(
        [transforms.Resize(opt.img_size), transforms.ToTensor(), transforms.Normalize([0.5], [0.5])]
    ),
)

## 配置数据到加载器
dataloader = DataLoader(
    mnist,
    batch_size=opt.batch_size,
    shuffle=True,
)


## ##### 定义判别器 Discriminator ######
## 将图片28x28展开成784,然后通过多层感知器,中间经过斜率设置为0.2的LeakyReLU激活函数,
## 最后接sigmoid激活函数得到一个0到1之间的概率进行二分类
class Discriminator(nn.Module):
    def __init__(self):
        super(Discriminator, self).__init__()
        self.model = nn.Sequential(
            nn.Linear(img_area, 512),  ## 输入特征数为784,输出为512
            nn.LeakyReLU(0.2, inplace=True),  ## 进行非线性映射
            nn.Linear(512, 256),  ## 输入特征数为512,输出为256
            nn.LeakyReLU(0.2, inplace=True),  ## 进行非线性映射
            nn.Linear(256, 1),  ## 输入特征数为256,输出为1
            nn.Sigmoid(),  ## sigmoid是一个激活函数,二分类问题中可将实数映射到[0, 1],作为概率值, 多分类用softmax函数
        )

    def forward(self, img):
        img_flat = img.view(img.size(0), -1)  ## 鉴别器输入是一个被view展开的(784)的一维图像:(64, 784)
        validity = self.model(img_flat)  ## 通过鉴别器网络
        return validity  ## 鉴别器返回的是一个[0, 1]间的概率


## ###### 定义生成器 Generator #####
## 输入一个100维的0~1之间的高斯分布,然后通过第一层线性变换将其映射到256维,
## 然后通过LeakyReLU激活函数,接着进行一个线性变换,再经过一个LeakyReLU激活函数,
## 然后经过线性变换将其变成784维,最后经过Tanh激活函数是希望生成的假的图片数据分布, 能够在-1~1之间。
class Generator(nn.Module):
    def __init__(self):
        super(Generator, self).__init__()

        ## 模型中间块儿
        def block(in_feat, out_feat, normalize=True):  ## block(in, out )
            layers = [nn.Linear(in_feat, out_feat)]  ## 线性变换将输入映射到out维
            if normalize:
                layers.append(nn.BatchNorm1d(out_feat, 0.8))  ## 正则化
            layers.append(nn.LeakyReLU(0.2, inplace=True))  ## 非线性激活函数
            return layers

        ## prod():返回给定轴上的数组元素的乘积:1*28*28=784
        self.model = nn.Sequential(
            *block(opt.latent_dim, 128, normalize=False),  ## 线性变化将输入映射 100 to 128, 正则化, LeakyReLU
            *block(128, 256),  ## 线性变化将输入映射 128 to 256, 正则化, LeakyReLU
            *block(256, 512),  ## 线性变化将输入映射 256 to 512, 正则化, LeakyReLU
            *block(512, 1024),  ## 线性变化将输入映射 512 to 1024, 正则化, LeakyReLU
            nn.Linear(1024, img_area),  ## 线性变化将输入映射 1024 to 784
            nn.Tanh()  ## 将(784)的数据每一个都映射到[-1, 1]之间
        )

    ## view():相当于numpy中的reshape,重新定义矩阵的形状:这里是reshape(64, 1, 28, 28)
    def forward(self, z):  ## 输入的是(64, 100)的噪声数据
        imgs = self.model(z)  ## 噪声数据通过生成器模型
        imgs = imgs.view(imgs.size(0), *img_shape)  ## reshape成(64, 1, 28, 28)
        return imgs  ## 输出为64张大小为(1, 28, 28)的图像


## 创建生成器,判别器对象
generator = Generator()
discriminator = Discriminator()

## 首先需要定义loss的度量方式  (二分类的交叉熵)
criterion = torch.nn.BCELoss()

## 其次定义 优化函数,优化函数的学习率为0.0003
## betas:用于计算梯度以及梯度平方的运行平均值的系数
optimizer_G = torch.optim.Adam(generator.parameters(), lr=opt.lr, betas=(opt.b1, opt.b2))
optimizer_D = torch.optim.Adam(discriminator.parameters(), lr=opt.lr, betas=(opt.b1, opt.b2))

## 如果有显卡,都在cuda模式中运行
if torch.cuda.is_available():
    generator = generator.cuda()
    discriminator = discriminator.cuda()
    criterion = criterion.cuda()

## ----------
##  Training
## ----------
## 进行多个epoch的训练
for epoch in range(opt.n_epochs):  ## epoch:50
    for i, (imgs, _) in enumerate(dataloader):  ## imgs:(64, 1, 28, 28)     _:label(64)

        ## =============================训练判别器==================
        ## view(): 相当于numpy中的reshape,重新定义矩阵的形状, 相当于reshape(128,784)  原来是(128, 1, 28, 28)
        imgs = imgs.view(imgs.size(0), -1)  ## 将图片展开为28*28=784  imgs:(64, 784)
        real_img = Variable(imgs).cuda()  ## 将tensor变成Variable放入计算图中,tensor变成variable之后才能进行反向传播求梯度
        real_label = Variable(torch.ones(imgs.size(0), 1)).cuda()  ## 定义真实的图片label为1
        fake_label = Variable(torch.zeros(imgs.size(0), 1)).cuda()  ## 定义假的图片的label为0

        ## ---------------------
        ##  Train Discriminator
        ## 分为两部分:1、真的图像判别为真;2、假的图像判别为假
        ## ---------------------
        ## 计算真实图片的损失
        real_out = discriminator(real_img)  ## 将真实图片放入判别器中
        loss_real_D = criterion(real_out, real_label)  ## 得到真实图片的loss
        real_scores = real_out  ## 得到真实图片的判别值,输出的值越接近1越好
        ## 计算假的图片的损失
        ## detach(): 从当前计算图中分离下来避免梯度传到G,因为G不用更新
        z = Variable(torch.randn(imgs.size(0), opt.latent_dim)).cuda()  ## 随机生成一些噪声, 大小为(128, 100)
        fake_img = generator(z).detach()  ## 随机噪声放入生成网络中,生成一张假的图片。
        fake_out = discriminator(fake_img)  ## 判别器判断假的图片
        loss_fake_D = criterion(fake_out, fake_label)  ## 得到假的图片的loss
        fake_scores = fake_out  ## 得到假图片的判别值,对于判别器来说,假图片的损失越接近0越好
        ## 损失函数和优化
        loss_D = loss_real_D + loss_fake_D  ## 损失包括判真损失和判假损失
        optimizer_D.zero_grad()  ## 在反向传播之前,先将梯度归0
        loss_D.backward()  ## 将误差反向传播
        optimizer_D.step()  ## 更新参数

        ## -----------------
        ##  Train Generator
        ## 原理:目的是希望生成的假的图片被判别器判断为真的图片,
        ## 在此过程中,将判别器固定,将假的图片传入判别器的结果与真实的label对应,
        ## 反向传播更新的参数是生成网络里面的参数,
        ## 这样可以通过更新生成网络里面的参数,来训练网络,使得生成的图片让判别器以为是真的, 这样就达到了对抗的目的
        ## -----------------
        z = Variable(torch.randn(imgs.size(0), opt.latent_dim)).cuda()  ## 得到随机噪声
        fake_img = generator(z)  ## 随机噪声输入到生成器中,得到一副假的图片
        output = discriminator(fake_img)  ## 经过判别器得到的结果
        ## 损失函数和优化
        loss_G = criterion(output, real_label)  ## 得到的假的图片与真实的图片的label的loss
        optimizer_G.zero_grad()  ## 梯度归0
        loss_G.backward()  ## 进行反向传播
        optimizer_G.step()  ## step()一般用在反向传播后面,用于更新生成网络的参数

        ## 打印训练过程中的日志
        ## item():取出单元素张量的元素值并返回该值,保持原元素类型不变
        if (i + 1) % 100 == 0:
            print(
                "[Epoch %d/%d] [Batch %d/%d] [D loss: %f] [G loss: %f] [D real: %f] [D fake: %f]"
                % (epoch, opt.n_epochs, i, len(dataloader), loss_D.item(), loss_G.item(), real_scores.data.mean(),
                   fake_scores.data.mean())
            )
        ## 保存训练过程中的图像
        batches_done = epoch * len(dataloader) + i
        if batches_done % opt.sample_interval == 0:
            save_image(fake_img.data[:25], "./images/gan/%d.png" % batches_done, nrow=5, normalize=True)

## 保存模型
torch.save(generator.state_dict(), './save/gan/generator.pth')
torch.save(discriminator.state_dict(), './save/gan/discriminator.pth')

以下是0到46500次中,GAN训练的结果图。

结论

作者提出一种生成对抗网络(GANs)框架,通过博弈优化生成模型。理论分析表明,在足够的容量和训练时间下,该框架能使生成模型的分布收敛于数据分布。且通过实验证明了GAN在生成模式上的潜力。

缺点以及后续展望

GAN的缺点:

使用GAN确实可以极大的减少计算的复杂度,但是其却没有分布的显式表达。此外,GAN在训练时判别器(D)必须与生成器(G)很好地同步,如果生成器优化的太好,就会导致判别器失效,如果判别器优化的太好就会导致 l o g ( 1 − D ( G ( z ) ) ) log(1-D(G(z))) log(1−D(G(z)))为0难以进行梯度下降,不利于模型的优化。最后,作者提出基础的GAN用于估计概率的方法方差较高,在高维空间表现较差。

作者的展望:

作者在表示可进行条件生成模型研究,对所有条件进行近似建模,创造一个条件GAN(Conditional GAN),对特定的研究方向有更好的效果。此外,作者还提出用于半监督学习去提高GAN的训练效率。

总结

这一周对GAN的奠基性论文进行了阅读,再复习GAN的同时,对GAN也有了更加深入的理解。为后面阅读TimeGAN论文打下扎实的基础。

GAN这篇论文的核心是通过生成器和判别器的对抗博弈,实现了高质量数据的生成。这种方法不仅避免了传统生成模型在计算复杂性,还在多个数据集上表现出了巨大潜力。通过对论文中算法研究和实验验证,对的GAN的运行机制进行了理解。同时,GAN也有需要改进的地方,如,GAN缺乏分布的显式表达,训练过程中生成器和判别器的同步优化较为困难,且在高维空间中估计概率的方法方差较高等等问题。这些问题也在未来的研究给出的解答和改进,如后面提出的conditional GAN、WGAN、TimeGAN等等。

下一周计划阅读《Enhanced physics-informed neural networks for efficient modelling of hydrodynamics in river networks》的论文,学习PINN在水文下的应用。然后后续再阅读TimeGAN和GPT-PINN等等高质量论文

相关推荐
siy23331 小时前
[c语言日寄]精英怪:三子棋(tic-tac-toe)3命慢通[附免费源码]
c语言·开发语言·笔记·学习·算法
java冯坚持1 小时前
AI大模型开发—1、百度的千帆大模型调用(文心一言的底层模型,ENRIE等系列)、API文档目的地
人工智能·百度·文心一言
网络安全queen1 小时前
Web 学习笔记 - 网络安全
前端·笔记·学习
dal118网工任子仪1 小时前
40,【6】CTFHUB WEB SQL MYSQL数据库
数据库·笔记·sql·学习·mysql
在线OJ的阿川2 小时前
大数据、人工智能、云计算、物联网、区块链序言【大数据导论】
大数据·人工智能·物联网·云计算·区块链
凡人的AI工具箱2 小时前
每日学习30分轻松掌握CursorAI:多文件编辑与Composer功能
人工智能·python·学习·ai·ai编程·composer·cursor
坐吃山猪2 小时前
卷积神经05-GAN对抗神经网络
人工智能·神经网络·生成对抗网络
Sheakan2 小时前
LightGCN:为推荐系统简化图卷积网络的创新之作
人工智能·机器学习
HUODUNYUN2 小时前
电子邮件安全及核心概念
网络·人工智能·安全·电子邮件