AIGC入门之生成对抗网络


✨✨ 欢迎大家来访Srlua的博文(づ ̄3 ̄)づ╭❤~✨✨

🌟🌟 欢迎各位亲爱的读者,感谢你们抽出宝贵的时间来阅读我的文章。

我是Srlua小谢,在这里我会分享我的知识和经验。🎥

希望在这里,我们能一起探索IT世界的奥妙,提升我们的技能。🔮

记得先点赞👍后阅读哦~ 👏👏

📘📚 所属专栏:传知代码论文复现

欢迎访问我的主页:Srlua小谢 获取更多信息和资源。✨✨🌙🌙

​​

​​

目录

1. GAN

2 GAN的优化函数

2.1 判别器D

2.2 生成器G

2.3 互相博弈

3 训练过程

4 在看优化

4.1 D的最优解

4.2 G的最优解

5. 主要代码结构

5.1 模型结构(以DCGAN为例)

5.2 训练代码(以DCGAN为例)

5.3 生成结果


  本文所有资源均可在该地址处获取。

1. GAN

原始论文:https://arxiv.org/abs/1406.2661
放一张GAN的结构,如下:我们有两个网络,生成网络G和判别网络D。生成网络接收一个(符合简单分布如高斯分布或者均匀分布的)随机噪声输入,通过这个噪声输出图片,记做G(z)。判别网络的输入是x,x代表一张图片,输出D(x)代表x为真实图片的概率。最终的目的式能够生成一个以假乱真的图片,使D无法判别真假,D存在的意义是不断去督促G生成的质量

先拿出论文中的优化公式,后面在详解由来。

minGmaxDV(G,D)=Ex∼pdata(x)[logD(x)]+Ez∼pz(z)[log(1−D(G(z)))]Gmin​Dmax​V(G,D)=Ex∼pdata​(x)​[logD(x)]+Ez∼pz​(z)​[log(1−D(G(z)))]

这里pdata(x)pdata​(x) 表示真实数据的分布,z是生成器G输入的噪声, pz(z)pz​(z)是噪声的分布,乍一看这个公式是不是很难理解。没关系,接下来,我们慢慢分析由来。

2 GAN的优化函数

2.1 判别器D

我们先看判别器D,作用是能够对真实数据x∼ pdata(x)x∼ pdata​(x)其能够准确分辨是真,对生成的假数据G(z)能够分辨是假,那么实际上这就是一个二分类的逻辑回归问题,还记得交叉熵吗?没错这也等价于交叉熵,只不过交叉熵是负对数,优化最小交叉熵必然等价于优化以下最大值:

maxDV(G,D)=Ex∼pdata(x)[logD(x)]+Ez∼pz(z)[log(1−D(G(z)))]Dmax​V(G,D)=Ex∼pdata​(x)​[logD(x)]+Ez∼pz​(z)​[log(1−D(G(z)))]

看过我前面写的熵的问题,公式由来很容易懂。我们现在单独从公式来看,这个函数要想取得最大值,必然当真实数据来的时候D(x)=1,当假数据G(z)来的时候D(x)=0。这也满足我们的初衷:能够分辨真假。实际上是一个二分类。
这一步目标是优化D,G是固定的不做优化,G为上一次迭代优化后的结果,因此可简写成:

DG∗=maxDV(G,D)DG∗​=Dmax​V(G,D)

2.2 生成器G

在来看看生成器,对于生成器来说,我不想判别器D能够识别我是真假,我希望判别器识别不出来最好,理想极端情况下:D(x)=0,D(G(z))=1,也就是真的识别成假,假的识别成真。反应在优化函数上就是,是不是很好理解了

minG=Ex∼pdata(x)[logD(x)]+Ez∼pz(z)[log(1−D(G(z)))]Gmin​=Ex∼pdata​(x)​[logD(x)]+Ez∼pz​(z)​[log(1−D(G(z)))]

当理想情况下D(x)=0,D(G(z))=1,必然是最小值优化。
同样这一步优化是优化G,D不做优化,D为上一次迭代优化后的结果,因此可简写成:

GD∗=minGV(G,D)GD∗​=Gmin​V(G,D)

2.3 互相博弈

作者习惯上把分开的两个优化写道一起,就变成了我们最初看到的论文中的公式:

minGmaxDV(G,D)=Ex∼pdata(x)[logD(x)]+Ez∼pz(z)[log(1−D(G(z)))]Gmin​Dmax​V(G,D)=Ex∼pdata​(x)​[logD(x)]+Ez∼pz​(z)​[log(1−D(G(z)))]

但是实际上,D和G在迭代过程中是分开优化的。
上面说了,我生成器又要能够准确判断真假,又要不能够判断,作为判别器他说他好难啊,怎么办呢,干脆判别器最终输出0.5,这也是理想优化结果,谁也不偏向。这也是整个GAN优化的终极目的。

3 训练过程


对于判别器D优化,因为这是个二分类,ylogq + (1-y)log(1-q):对于x,标签只会为1,因此只有log(D(x))这一项;对于g(z),其标签只会为0,因此只有log(1-D(G(z)))这一项,在损失函数上,$$loss=crossEntryLoss(D(x),1) + crossEntryLoss(D(G(z)),0)$$
对于生成器G优化:因为D(x)这一项,并不包含生成器的优化参数,因此在求梯度的时候D(x)这一项为0,因此只有log(1-D(G(z)))这一项,损失函数:$$ loss=crossEntryLoss(D(G(z)),1)$$

4 在看优化

4.1 D的最优解

还记得完美的优化结果是D=0.5吗?这到底是怎么来的呢。我们先看一下对于D的优化,去求D的最优解

maxDV(G,D)=Ex∼pdata(x)[logD(x)]+Ez∼pz(z)[log(1−D(G(z)))]Dmax​V(G,D)=Ex∼pdata​(x)​[logD(x)]+Ez∼pz​(z)​[log(1−D(G(z)))]

写成积分形式:不知道怎么来的可以补一下概率论均值的计算。

maxDV(G,D)=∫xpdata(x)logD(x)dx+∫xpz(z)log(1−D(g(z)))dzDmax​V(G,D)=∫x​pdata​(x)logD(x)dx+∫x​pz​(z)log(1−D(g(z)))dz

我们考虑在优化D的时候G是不变的,并且假设,通过G生成的g(z)满足的分布为pgpg​,因此上式子可写为:

maxDV(G,D)=∫xpdata(x)logD(x)+pg(x)log(1−D(x)dxDmax​V(G,D)=∫x​pdata​(x)logD(x)+pg​(x)log(1−D(x)dx

上式什么时候取得最大结果呢,alog(y)+blog(1−y)alog(y)+blog(1−y)在[0,1]上最大值是y=a/(a+b),因此上式最大值是

DG∗(x)=pdata(x)pdata(x)+pg(x)DG∗​(x)=pdata​(x)+pg​(x)pdata​(x)​

以上我们得到D的最优解,但是别忘了,我们目标是G能够生成的分布pg能和pdata一致,让D真假难辨,那么此时pg = pdata,D=0.5,判别器已经模棱两可了。然而这一结果只是我们的猜测。

4.2 G的最优解

作者也是先说了pg=pdata是G的最优解,后面才证明的。让我们跟着作者思路证明一下。
D的最优解已经得到了,带入求解G最优的公式,这里作者起了个C(G)的名称,按照他的思路来,已然求C(G)的最小值

C(G)=Ex∼pdata(x)[logDG∗(x)]+Ez∼pz(z)[log(1−DG∗(G(z)))]=Ex∼pdata(x)[logDG∗(x)]+Ex∼pg[log(1−DG∗(x))]=Ex∼pdata(x)[logpdata(x)pdata(x)+pg(x)]+Ex∼pg[log(pg(x)pdata(x)+pg(x))]=∫xpdata(x)logpdata(x)pdata(x)+pg(x)+pg(x)logpg(x)pdata(x)+pg(x)dx=∫x(log2−log2)pdata(x)+(log2−log2)pg(x)+pdata(x)logpdata(x)pdata(x)+pg(x)+pg(x)logpg(x)pdata(x)+pg(x)dx=−log2∫x[pdata(x)+pg(x)]dx+∫xpdata(x)(log2+logpdata(x)pdata(x)+pg(x))+pg(x)(log2+logpg(x)pdata(x)+pg(x))dxC(G)=Ex∼pdata​(x)​[logDG∗​(x)]+Ez∼pz​(z)​[log(1−DG∗​(G(z)))]=Ex∼pdata​(x)​[logDG∗​(x)]+Ex∼pg​​[log(1−DG∗​(x))]=Ex∼pdata​(x)​[logpdata​(x)+pg​(x)pdata​(x)​]+Ex∼pg​​[log(pdata​(x)+pg​(x)pg​(x)​)]=∫x​pdata​(x)logpdata​(x)+pg​(x)pdata​(x)​+pg​(x)logpdata​(x)+pg​(x)pg​(x)​dx=∫x​(log2−log2)pdata​(x)+(log2−log2)pg​(x)+pdata​(x)logpdata​(x)+pg​(x)pdata​(x)​+pg​(x)logpdata​(x)+pg​(x)pg​(x)​dx=−log2∫x​[pdata​(x)+pg​(x)]dx+∫x​pdata​(x)(log2+logpdata​(x)+pg​(x)pdata​(x)​)+pg​(x)(log2+logpdata​(x)+pg​(x)pg​(x)​)dx

由于对概率积分结果为1,上式继续化简为:

C(G)=−2log2+∫xpdata(x)logpdata(x)[pdata(x)+pg(x)]/2+∫xpg(x)logpg(x)[pdata(x)+pg(x)]/2C(G)=−2log2+∫x​pdata​(x)log[pdata​(x)+pg​(x)]/2pdata​(x)​+∫x​pg​(x)log[pdata​(x)+pg​(x)]/2pg​(x)​

看过熵的应该知道后两项其实式散度的形式,写为散度的形式,

C(G)=−log4+KL(pdata(x)∣∣pdata(x)+pg(x)2)+KL(pg(x)∣∣pdata(x)+pg(x)2)C(G)=−log4+KL(pdata​(x)∣∣2pdata​(x)+pg​(x)​)+KL(pg​(x)∣∣2pdata​(x)+pg​(x)​)

在我写熵的那篇文章里已经详细介绍和推导过,KL(P||Q)散度取最小值0的时候P=Q,因此上式最小值的情况是:
pdata(x)=pdata(x)+pg(x)2pdata​(x)=2pdata​(x)+pg​(x)​ 和pg(x)=pdata(x)+pg(x)2pg​(x)=2pdata​(x)+pg​(x)​。这两个当且仅当pg(x)=pdata(x)pg​(x)=pdata​(x)时满足。
又因为JSD散度和KL散度有如下关系:

JSD(P∣∣Q)=12KL(P∣∣M)+12KL(Q∣∣M),M=12(P+Q)JSD(P∣∣Q)=21​KL(P∣∣M)+21​KL(Q∣∣M),M=21​(P+Q)

因此继续简化:

C(G)=−log4+2JSD(pdata∣∣pg)C(G)=−log4+2JSD(pdata​∣∣pg​)

由于JSD的散度取值为(0,log2),当为0的时候pg=pdatapg​=pdata​,同样也证明了G最优解的情况是pg=pdatapg​=pdata​。至此也完成论文中的证明,不得不说GAN中的理论真的很强,这些理论对后面各种生成模型用处非常大。虽然GAN是历史的产物,但是他带来的价值却很高,如果想做AIGC,GAN必学习。

5. 主要代码结构

5.1 模型结构(以DCGAN为例)

"""  
Discriminator and Generator implementation from DCGAN paper  
"""  
  
import torch  
import torch.nn as nn  
from torchinfo import summary  
  
  
class Discriminator(nn.Module):  
    def __init__(self, channels_img, features_d):  
        super().__init__()  
        self.disc = nn.Sequential(  
            self._block(channels_img, features_d, kernel_size=4, stride=2, padding=1),  
            self._block(features_d, features_d * 2, 4, 2, 1),  
            self._block(features_d * 2, features_d * 4, 4, 2, 1),  
            self._block(features_d * 4, features_d * 8, 4, 2, 1),  
            nn.Conv2d(features_d * 8, 1, kernel_size=4, stride=2, padding=0),  
            nn.Sigmoid(),  
        )  
  
    def _block(self, in_channels, out_channels, kernel_size, stride, padding):  
        return nn.Sequential(  
            nn.Conv2d(  
                in_channels,  
                out_channels,  
                kernel_size,  
                stride,  
                padding,  
                bias=False  
            ),  
            nn.LeakyReLU(0.2),  
        )  
  
    def forward(self, x):  
        return self.disc(x)  
  
  
class Generator(nn.Module):  
    def __init__(self, channels_noise, channels_img, features_g):  
        super().__init__()  
        self.gen = nn.Sequential(  
            self._block(channels_noise, features_g * 16, 4, 1, 0),  
            self._block(features_g * 16, features_g * 8, 4, 2, 1),  
            self._block(features_g * 8, features_g * 4, 4, 2, 1),  
            self._block(features_g * 4, features_g * 2, 4, 2, 1),  
            nn.ConvTranspose2d(features_g * 2, channels_img, 4, 2, 1),  
            nn.Tanh(),  
        )  
  
    def _block(self, in_channels, out_channels, kernel_size, stride, padding):  
        return nn.Sequential(  
            nn.ConvTranspose2d(  
                in_channels,  
                out_channels,  
                kernel_size,  
                stride,  
                padding,  
                bias=False,  
            ),  
            nn.ReLU(),  
        )  
  
    def forward(self, x):  
        return self.gen(x)  
  
  
def initialize_weights(model):  
    ## initilialize weight according to paper  
    for m in model.modules():  
        if isinstance(m, (nn.Conv2d, nn.ConvTranspose2d,)):  
            nn.init.normal_(m.weight.data, 0.0, 0.02)  
  
  
def test():  
    N, in_channels, H, W = 8, 1, 64, 64  
    noise_dim = 100  
    x = torch.randn(N, in_channels, H, W)  
    disc = Discriminator(in_channels, 8)  
    initialize_weights(disc)  
    assert disc(x).shape == (N, 1, 1, 1), "Discriminator test failed"  
    gen = Generator(noise_dim, in_channels, 8)  
    initialize_weights(gen)  
    z = torch.randn(N, noise_dim, 1, 1)  
    assert gen(z).shape == (N, in_channels, H, W), "Generator test failed"  
  
  
if __name__ == "__main__":  
    gnet = Generator(100, 1, 64)  
    dnet = Discriminator(1, 64)  
  
    summary(gnet, input_data=[torch.randn(10, 100, 1, 1)])  
    summary(dnet, input_data=[torch.randn(10, 1, 64, 64)])

5.2 训练代码(以DCGAN为例)

训练数据集拿MNIST 为例。

import torch  
import torch.nn as nn  
import torch.optim as optim  
from torchvision.datasets import MNIST  
from torch.utils.data import DataLoader  
from dcgan import Generator, Discriminator, initialize_weights  
import torchvision.transforms as transforms  
import torchvision  
import cv2  
import numpy as np  
  
device = "cuda" if torch.cuda.is_available() else "cpu"  
LEARNING_RATE = 1e-4  
BATCH_SIZE = 128  
IMAGE_SIZE = 64  
NUM_EPOCHS = 5  
CHANNELS_IMG = 1  
NOISE_DIM = 100  
FEATURES_DISC = 64  
FEATURES_GEN = 64  
  
transforms = transforms.Compose(  
    [  
        transforms.Resize(IMAGE_SIZE),  
  
        transforms.ToTensor(),  
        transforms.Normalize(  
            [0.5 for _ in range(CHANNELS_IMG)], [0.5 for _ in range(CHANNELS_IMG)]  
        ),  
    ]  
)  
  
  
def train(NUM_EPOCHS):  
  
    # 数据load  
    dataset = MNIST(root='./data', train=True, download=True, transform=transforms)  
    dataloader = DataLoader(dataset, batch_size=BATCH_SIZE, shuffle=True)  
  
    gen = Generator(NOISE_DIM, CHANNELS_IMG, FEATURES_GEN).to(device)  
    disc = Discriminator(CHANNELS_IMG, FEATURES_DISC).to(device)  
    initialize_weights(gen)  
    initialize_weights(disc)  
  
    opt_gen = optim.Adam(gen.parameters(), lr=LEARNING_RATE, betas=(0.5, 0.999))  
    opt_disc = optim.Adam(disc.parameters(), lr=LEARNING_RATE, betas=(0.5, 0.999))  
    criterion = nn.BCELoss()  
  
    fixed_noise = torch.randn(32, NOISE_DIM, 1, 1).to(device)  
  
    gen.train()  
    disc.train()  
  
    for epoch in range(NUM_EPOCHS):  
        # 不需要目标的标签,无监督  
        for batch_id, (real, _) in enumerate(dataloader):  
            real = real.to(device)  
            noise = torch.randn(BATCH_SIZE, NOISE_DIM, 1, 1).to(device)  
            fake = gen(noise)  
  
            # Train Discriminator: max log(D(x)) + log(1 - D(G(z)))  
            disc_real = disc(real).reshape(-1)  
            loss_real = criterion(disc_real, torch.ones_like(disc_real))  
  
            # 训练判别器,生成器输出的值从计算图剥离出来  
            disc_fake = disc(fake.detach()).reshape(-1)  
            loss_fake = criterion(disc_fake, torch.zeros_like(disc_fake))  
  
            loss_disc = (loss_real + loss_fake) / 2  
  
            disc.zero_grad()  
            loss_disc.backward()  
            opt_disc.step()  
  
            # Train Generator: min log(1 - D(G(z))) <-> max log(D(G(z)),  
            output = disc(fake).reshape(-1)  
            loss_gen = criterion(output, torch.ones_like(output))  
  
            gen.zero_grad()  
            loss_gen.backward()  
            opt_gen.step()  
  
            if batch_id % 20 == 0:  
                print(  
                    f'Epoch [{epoch}/{NUM_EPOCHS}] Batch {batch_id}/{len(dataloader)} Loss D: {loss_disc.item()}, loss G: {loss_gen.item()}')  
  
                # 推理生成结果  
                with torch.no_grad():  
                    fake = gen(fixed_noise)  
                    img_grid_real = torchvision.utils.make_grid(real[:32], normalize=True)  
                    img_grid_fake = torchvision.utils.make_grid(fake[:32], normalize=True)  
  
                    img_grid_combined = torch.cat((img_grid_real, img_grid_fake), dim=2)  
                    img_grid_combined = img_grid_combined.permute(1, 2, 0).cpu().detach().numpy()  
                    img_grid_combined = (img_grid_combined * 255).astype(np.uint8)  
                    # 使用 cv2 显示图片  
                    cv2.imshow('Combined Image', img_grid_combined)  
                    cv2.waitKey(1)  
                    cv2.imwrite(f"ckpt/tmp_dc.jpg", img_grid_combined)  
  
                # 防止生成器崩溃,重新初始话模型  
                if loss_gen.item() > 5:  
                    gen = Generator(NOISE_DIM, CHANNELS_IMG, FEATURES_GEN).to(device)  
                    disc = Discriminator(CHANNELS_IMG, FEATURES_DISC).to(device)  
                    initialize_weights(gen)  
                    initialize_weights(disc)  
                    opt_gen = optim.Adam(gen.parameters(), lr=LEARNING_RATE, betas=(0.5, 0.999))  
                    opt_disc = optim.Adam(disc.parameters(), lr=LEARNING_RATE, betas=(0.5, 0.999))  
  
        if (epoch + 1) % 10 == 0:  
            torch.save(gen.state_dict(), f"ckpt/dc_gen_weights.pth")  
            torch.save(disc.state_dict(), f"ckpt/dc_disc_weights.pth")  
  
  
if __name__ == "__main__":  
    train(100)

5.3 生成结果

左边为训练的样本,右边为随机生成的手写数字。

​​

### AIGC 图片生成项目的实现方案 #### 项目概述 AIGC(AI Generated Content)是一种利用人工智能技术生成内容的方法,其核心在于通过机器学习算法生成高质量的内容。在图片生成方面,AIGC主要依赖于深度学习模型和生成对抗网络(GANs),以及其他先进的生成技术[^1]。 #### 技术选型与工具推荐 为了实现一个基于AIGC的图片生成项目,可以选择以下技术和框架: 1. **生成对抗网络 (GAN)** GAN 是一种常用的生成模型,由生成器和判别器组成。生成器负责生成逼真的图像,而判别器则用于区分真实图像和生成图像。近年来,许多改进版的 GAN 被开发出来,例如 StyleGAN 和 BigGAN,这些都可以作为图片生成的基础模型[^3]。 2. **扩散模型 (Diffusion Models)** 扩散模型是一类新兴的生成模型,在文本到图像的任务中表现尤为出色。Meta 的 EMU VIDEO 就是一个典型的例子,它通过显式的中间图像生成步骤增强了基于扩散的文本到视频生成的能力[^2]。 3. **Transformer 架构** Transformer 模型不仅适用于自然语言处理任务,也可以被改造为视觉生成任务的核心组件。例如,DALL·E 系列模型就是基于 Transformer 结构设计而成,能够在输入文本描述的情况下生成对应的高分辨率图像。 4. **开源工具与框架** 下面列举了一些流行的开源工具和框架供开发者选用: - PyTorch:支持快速构建神经网络并提供了丰富的预训练模型库。 - TensorFlow/Keras:适合初学者入门,同时也具备强大的功能满足复杂需求。 - Stable Diffusion API:专注于高效稳定的图像生成服务接口调用。 以下是使用 Python 编写的简单代码示例展示如何加载预训练好的 DALL-E 模型进行图片生成功能演示: ```python import torch from transformers import pipeline # 初始化管道对象 generator = pipeline('text-to-image', model='openai/dall-e') # 输入提示词 prompt = "a beautiful sunset over the ocean" # 开始生成图片 image = generator(prompt) # 展示结果 image.show() ``` #### 数据准备与处理 任何成功的 AI 项目都离不开良好的数据基础。对于图片生成而言,需要收集大量标注清晰的数据集,并对其进行清洗、裁剪等前处理操作以便更好地服务于后续建模阶段的需求。 #### 训练与评估 完成上述准备工作之后即可进入正式的模型训练环节。在此期间需要注意调整超参数设置以获得最佳性能;同时也要定期保存检查点文件方便后期恢复工作进度或者对比不同版本之间的差异情况。最后还要采用合适指标体系衡量最终成果质量水平是否达到预期标准。 #### 部署与优化 当模型经过充分验证后就可以考虑将其部署至生产环境当中去了。此时可能涉及到容器化封装(Docker)以及API网关搭建等工作事项。另外持续监控线上运行状态并且不断迭代升级也是保障长期稳定性的关键所在[^4]。 ---
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值