昇思 25 天学习打卡营第 20 天 | DCGAN生成漫画头像

今天是参加昇思学习打卡营的第20天,学习内容是DCGAN生成漫画头像

DCGAN原理

DCGAN(深度卷积对抗生成网络,Deep Convolutional Generative Adversarial Networks)是GAN的直接扩展。不同之处在于,DCGAN会分别在判别器和生成器中使用卷积和转置卷积层。

它最早由Radford等人在论文Unsupervised Representation Learning With Deep Convolutional Generative Adversarial Networks中进行描述。判别器由分层的卷积层、BatchNorm层和LeakyReLU激活层组成。输入是3x64x64的图像,输出是该图像为真图像的概率。生成器则是由转置卷积层、BatchNorm层和ReLU激活层组成。输入是标准正态分布中提取出的隐向量𝑧𝑧,输出是3x64x64的RGB图像。

本教程将使用动漫头像数据集来训练一个生成式对抗网络,接着使用该网络生成动漫头像图片。

数据准备与处理

首先我们将数据集下载到指定目录下并解压。示例代码如下:

from download import download

url = "https://download.mindspore.cn/dataset/Faces/faces.zip"

path = download(url, "./faces", kind="zip", replace=True)

数据处理

首先为执行过程定义一些输入:

batch_size = 128          # 批量大小
image_size = 64           # 训练图像空间大小
nc = 3                    # 图像彩色通道数
nz = 100                  # 隐向量的长度
ngf = 64                  # 特征图在生成器中的大小
ndf = 64                  # 特征图在判别器中的大小
num_epochs = 3           # 训练周期数
lr = 0.0002               # 学习率
beta1 = 0.5               # Adam优化器的beta1超参数

定义create_dataset_imagenet函数对数据进行处理和增强操作。

import numpy as np  # 导入NumPy库,用于数学运算
import mindspore.dataset as ds  # 导入MindSpore的数据集模块
import mindspore.dataset.vision as vision  # 导入MindSpore的视觉处理模块

def create_dataset_imagenet(dataset_path):
    """
    数据加载
    dataset_path: 数据集的路径
    """
    # 使用ImageFolderDataset加载数据集
    # dataset_path: 数据集路径
    # num_parallel_workers: 并行工作线程数
    # shuffle: 是否打乱数据
    # decode: 是否解码图像
    dataset = ds.ImageFolderDataset(dataset_path,
                                    num_parallel_workers=4,
                                    shuffle=True,
                                    decode=True)

    # 数据增强操作
    transforms = [
        vision.Resize(image_size),  # 调整图像大小
        vision.CenterCrop(image_size),  # 中心裁剪图像
        vision.HWC2CHW(),  # 将图像的数据格式从高度x宽度x通道(HWC)转换为通道x高度x宽度(CHW)
        lambda x: ((x / 255).astype("float32"))  # 归一化图像数据到[0, 1]区间,并转换为float32类型
    ]

    # 数据映射操作
    # 只保留'image'列
    dataset = dataset.project('image')
    # 应用数据增强操作
    dataset = dataset.map(transforms, 'image')

    # 批量操作
    # batch_size: 每批的样本数
    dataset = dataset.batch(batch_size)
    return dataset

# 调用函数创建数据集,传入数据集路径
dataset = create_dataset_imagenet('./faces')

通过create_dict_iterator函数将数据转换成字典迭代器,然后使用matplotlib模块可视化部分训练数据。

import matplotlib.pyplot as plt  # 导入matplotlib的pyplot模块,用于数据可视化

def plot_data(data):
    """
    可视化部分训练数据
    data: 包含图像数据的元组
    """
    # 创建一个图形窗口,设置大小和分辨率
    plt.figure(figsize=(10, 3), dpi=140)
    
    # 遍历数据集中的前30张图像
    for i, image in enumerate(data[0][:30], 1):
        # 创建子图,3行10列的布局,索引i
        plt.subplot(3, 10, i)
        # 关闭子图的坐标轴显示
        plt.axis("off")
        # 显示图像,需要将图像从CHW格式转换为HWC格式
        plt.imshow(image.transpose(1, 2, 0))

    # 显示所有子图
    plt.show()

# 使用create_tuple_iterator创建一个迭代器,并获取第一个样本数据
sample_data = next(dataset.create_tuple_iterator(output_numpy=True))
# 调用plot_data函数显示样本数据中的图像
plot_data(sample_data)

构造网络

当处理完数据后,就可以来进行网络的搭建了。按照DCGAN论文中的描述,所有模型权重均应从mean为0,sigma为0.02的正态分布中随机初始化。

生成器

生成器G的功能是将隐向量z映射到数据空间。由于数据是图像,这一过程也会创建与真实图像大小相同的 RGB 图像。在实践场景中,该功能是通过一系列Conv2dTranspose转置卷积层来完成的,每个层都与BatchNorm2d层和ReLu激活层配对,输出数据会经过tanh函数,使其返回[-1,1]的数据范围内。

DCGAN论文生成图像如下所示:

dcgangenerator

图片来源:Unsupervised Representation Learning With Deep Convolutional Generative Adversarial Networks.

我们通过输入部分中设置的nzngfnc来影响代码中的生成器结构。nz是隐向量z的长度,ngf与通过生成器传播的特征图的大小有关,nc是输出图像中的通道数。

以下是生成器的代码实现:

import mindspore as ms
from mindspore import nn, ops
from mindspore.common.initializer import Normal

# 初始化权重的函数,使用正态分布
weight_init = Normal(mean=0, sigma=0.02)
# 初始化BatchNorm2d中gamma参数的函数,也使用正态分布
gamma_init = Normal(mean=1, sigma=0.02)

class Generator(nn.Cell):
    """DCGAN网络生成器"""

    def __init__(self):
        super(Generator, self).__init__()
        # 初始化生成器网络
        self.generator = nn.SequentialCell(
            # 第一个转置卷积层,将噪声向量从nz维扩展到ngf*8维
            nn.Conv2dTranspose(nz, ngf * 8, 4, 1, 'valid', weight_init=weight_init),
            # 第一个批量归一化层
            nn.BatchNorm2d(ngf * 8, gamma_init=gamma_init),
            nn.ReLU(),
            # 第二个转置卷积层,将特征图从ngf*8维扩展到ngf*4维
            nn.Conv2dTranspose(ngf * 8, ngf * 4, 4, 2, 'pad', 1, weight_init=weight_init),
            # 第二个批量归一化层
            nn.BatchNorm2d(ngf * 4, gamma_init=gamma_init),
            nn.ReLU(),
            # 第三个转置卷积层,将特征图从ngf*4维扩展到ngf*2维
            nn.Conv2dTranspose(ngf * 4, ngf * 2, 4, 2, 'pad', 1, weight_init=weight_init),
            # 第三个批量归一化层
            nn.BatchNorm2d(ngf * 2, gamma_init=gamma_init),
            nn.ReLU(),
            # 第四个转置卷积层,将特征图从ngf维扩展到nc维
            nn.Conv2dTranspose(ngf * 2, ngf, 4, 2, 'pad', 1, weight_init=weight_init),
            # 第四个批量归一化层
            nn.BatchNorm2d(ngf, gamma_init=gamma_init),
            nn.ReLU(),
            # 最后一个转置卷积层,将特征图从ngf维扩展到nc维,生成最终的图像
            nn.Conv2dTranspose(ngf, nc, 4, 2, 'pad', 1, weight_init=weight_init),
            # 使用Tanh激活函数将输出值映射到[-1, 1]区间
            nn.Tanh()
        )

    def construct(self, x):
        # 定义生成器的前向传播过程
        return self.generator(x)

# 创建生成器实例
generator = Generator()

判别器

如前所述,判别器D是一个二分类网络模型,输出判定该图像为真实图的概率。通过一系列的Conv2dBatchNorm2dLeakyReLU层对其进行处理,最后通过Sigmoid激活函数得到最终概率。

DCGAN论文提到,使用卷积而不是通过池化来进行下采样是一个好方法,因为它可以让网络学习自己的池化特征。

判别器的代码实现如下:

import mindspore as ms
from mindspore import nn
from mindspore.common.initializer import Normal

# 初始化权重的函数,使用正态分布
weight_init = Normal(mean=0, sigma=0.02)
# 初始化BatchNorm2d中gamma参数的函数,也使用正态分布
gamma_init = Normal(mean=1, sigma=0.02)

class Discriminator(nn.Cell):
    """DCGAN网络判别器"""

    def __init__(self):
        super(Discriminator, self).__init__()
        # 初始化判别器网络
        self.discriminator = nn.SequentialCell(
            # 第一个卷积层,将输入图像的通道数从nc降采样到ndf
            nn.Conv2d(nc, ndf, 4, 2, 'pad', 1, weight_init=weight_init),
            nn.LeakyReLU(0.2),  # 使用LeakyReLU激活函数
            # 第二个卷积层,将特征图的通道数从ndf*2降采样到ndf*4
            nn.Conv2d(ndf, ndf * 2, 4, 2, 'pad', 1, weight_init=weight_init),
            nn.BatchNorm2d(ndf * 2, gamma_init=gamma_init),  # 第二个批量归一化层
            nn.LeakyReLU(0.2),
            # 第三个卷积层,将特征图的通道数从ndf*4降采样到ndf*8
            nn.Conv2d(ndf * 2, ndf * 4, 4, 2, 'pad', 1, weight_init=weight_init),
            nn.BatchNorm2d(ndf * 4, gamma_init=gamma_init),  # 第三个批量归一化层
            nn.LeakyReLU(0.2),
            # 第四个卷积层,将特征图的通道数从ndf*8降采样到1
            nn.Conv2d(ndf * 4, ndf * 8, 4, 2, 'pad', 1, weight_init=weight_init),
            nn.BatchNorm2d(ndf * 8, gamma_init=gamma_init),  # 第四个批量归一化层
            nn.LeakyReLU(0.2),
            # 最后一个卷积层,将特征图的通道数从ndf*8降采样到1,输出判别结果
            nn.Conv2d(ndf * 8, 1, 4, 1, 'valid', weight_init=weight_init),
        )
        # 使用Sigmoid激活函数将输出值映射到[0, 1]区间,表示图像是真实的还是伪造的概率
        self.adv_layer = nn.Sigmoid()

    def construct(self, x):
        # 定义判别器的前向传播过程
        out = self.discriminator(x)
        # 将输出特征图重塑为一维向量
        out = out.reshape(out.shape[0], -1)
        return self.adv_layer(out)

# 创建判别器实例
discriminator = Discriminator()

模型训练

损失函数

当定义了DG后,接下来将使用MindSpore中定义的二进制交叉熵损失函数BCELoss

[8]:

# 定义损失函数
adversarial_loss = nn.BCELoss(reduction='mean')

优化器

这里设置了两个单独的优化器,一个用于D,另一个用于G。这两个都是lr = 0.0002beta1 = 0.5的Adam优化器。

[9]:

# 为生成器和判别器设置优化器
optimizer_D = nn.Adam(discriminator.trainable_params(), learning_rate=lr, beta1=beta1)
optimizer_G = nn.Adam(generator.trainable_params(), learning_rate=lr, beta1=beta1)
optimizer_G.update_parameters_name('optim_g.')
optimizer_D.update_parameters_name('optim_d.')

训练模型

训练分为两个主要部分:训练判别器和训练生成器。

  • 训练判别器

    训练判别器的目的是最大程度地提高判别图像真伪的概率。按照Goodfellow的方法,是希望通过提高其随机梯度来更新判别器,所以我们要最大化𝑙𝑜𝑔𝐷(𝑥)+𝑙𝑜𝑔(1−𝐷(𝐺(𝑧))𝑙𝑜𝑔𝐷(𝑥)+𝑙𝑜𝑔(1−𝐷(𝐺(𝑧))的值。

  • 训练生成器

    如DCGAN论文所述,我们希望通过最小化𝑙𝑜𝑔(1−𝐷(𝐺(𝑧)))𝑙𝑜𝑔(1−𝐷(𝐺(𝑧)))来训练生成器,以产生更好的虚假图像。

在这两个部分中,分别获取训练过程中的损失,并在每个周期结束时进行统计,将fixed_noise批量推送到生成器中,以直观地跟踪G的训练进度。

下面实现模型训练正向逻辑:

import mindspore as ms
from mindspore import nn, ops

# 定义噪声向量的维度
nz = 100

# 初始化权重的函数,使用正态分布
weight_init = Normal(mean=0, sigma=0.02)
# 初始化BatchNorm2d中gamma参数的函数,也使用正态分布
gamma_init = Normal(mean=1, sigma=0.02)

# 假设generator和discriminator已经被定义和初始化
generator = Generator()
discriminator = Discriminator()

# 假设optimizer_G和optimizer_D是生成器和判别器的优化器
optimizer_G = nn.Adam(generator.trainable_params(), learning_rate=1e-4)
optimizer_D = nn.Adam(discriminator.trainable_params(), learning_rate=1e-4)

def generator_forward(real_imgs, valid):
    """
    生成器的前向传播过程
    real_imgs: 真实图像
    valid: 真实标签
    """
    # 将噪声采样为生成器的输入
    z = ops.standard_normal((real_imgs.shape[0], nz, 1, 1))

    # 生成一批图像
    gen_imgs = generator(z)

    # 损失衡量生成器绕过判别器的能力
    g_loss = adversarial_loss(discriminator(gen_imgs), valid)

    return g_loss, gen_imgs

def discriminator_forward(real_imgs, gen_imgs, valid, fake):
    """
    判别器的前向传播过程
    real_imgs: 真实图像
    gen_imgs: 生成图像
    valid: 真实标签
    fake: 伪造标签
    """
    # 衡量判别器从生成的样本中对真实样本进行分类的能力
    real_loss = adversarial_loss(discriminator(real_imgs), valid)
    fake_loss = adversarial_loss(discriminator(gen_imgs), fake)
    d_loss = (real_loss + fake_loss) / 2
    return d_loss

# 定义生成器和判别器的梯度计算函数
grad_generator_fn = ms.value_and_grad(generator_forward, None,
                                      optimizer_G.parameters,
                                      has_aux=True)
grad_discriminator_fn = ms.value_and_grad(discriminator_forward, None,
                                          optimizer_D.parameters)

@ms.jit
def train_step(imgs):
    """
    训练过程中的单步更新
    imgs: 真实图像数据
    """
    valid = ops.ones((imgs.shape[0], 1), mindspore.float32)  # 真实标签
    fake = ops.zeros((imgs.shape[0], 1), mindspore.float32)  # 伪造标签

    (g_loss, gen_imgs), g_grads = grad_generator_fn(imgs, valid)  # 计算生成器的梯度
    optimizer_G(g_grads)  # 更新生成器的参数

    d_loss = discriminator_forward(imgs, gen_imgs, valid, fake)  # 计算判别器的损失
    d_grads = grad_discriminator_fn(imgs, gen_imgs, valid, fake)  # 计算判别器的梯度
    optimizer_D(d_grads)  # 更新判别器的参数

    return g_loss, d_loss, gen_imgs

循环训练网络,每经过50次迭代,就收集生成器和判别器的损失,以便于后面绘制训练过程中损失函数的图像。

import mindspore as ms
from mindspore import nn, ops

# 假设generator和discriminator已经被定义和初始化
generator = Generator()
discriminator = Discriminator()

# 假设optimizer_G和optimizer_D是生成器和判别器的优化器
optimizer_G = nn.Adam(generator.trainable_params(), learning_rate=1e-4)
optimizer_D = nn.Adam(discriminator.trainable_params(), learning_rate=1e-4)

# 定义训练过程中的损失记录列表
G_losses = []
D_losses = []
image_list = []

# 获取数据集的总样本数
total = dataset.get_dataset_size()

# 训练的总周期数
num_epochs = 50  # 假设值,需要在代码中定义

# 定义批量大小
batch_size = 64  # 假设值,需要在代码中定义

# 定义噪声向量的维度
nz = 100  # 假设值,需要在代码中定义

for epoch in range(num_epochs):
    # 设置生成器和判别器为训练模式
    generator.set_train()
    discriminator.set_train()
    
    # 为每轮训练读入数据
    for i, (imgs, ) in enumerate(dataset.create_tuple_iterator()):
        # 训练步骤,计算生成器和判别器的损失,并生成图像
        g_loss, d_loss, gen_imgs = train_step(imgs)
        
        # 每隔100步或最后一步输出训练记录
        if i % 100 == 0 or i == total - 1:
            print('[%2d/%d][%3d/%d]   Loss_D:%7.4f  Loss_G:%7.4f' % (
                epoch + 1, num_epochs, i + 1, total, d_loss.asnumpy(), g_loss.asnumpy()))
        
        # 记录损失
        D_losses.append(d_loss.asnumpy())
        G_losses.append(g_loss.asnumpy())

    # 每个epoch结束后,使用生成器生成一组图片
    generator.set_train(False)  # 设置生成器为评估模式
    fixed_noise = ops.standard_normal((batch_size, nz, 1, 1))  # 生成固定噪声
    img = generator(fixed_noise)  # 生成图像
    image_list.append(img.transpose(0, 2, 3, 1).asnumpy())  # 将图像添加到列表中

    # 保存生成器和判别器的网络模型参数为ckpt文件
    ms.save_checkpoint(generator, "./generator.ckpt")
    ms.save_checkpoint(discriminator, "./discriminator.ckpt")

结果展示

运行下面代码,描绘DG损失与训练迭代的关系图:

plt.figure(figsize=(10, 5))
plt.title("Generator and Discriminator Loss During Training")
plt.plot(G_losses, label="G", color='blue')
plt.plot(D_losses, label="D", color='orange')
plt.xlabel("iterations")
plt.ylabel("Loss")
plt.legend()
plt.show()

可视化训练过程中通过隐向量fixed_noise生成的图像。

import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np

def showGif(image_list):
    """
    将图像列表制作成GIF动画
    image_list: 生成的图像列表
    """
    show_list = []
    fig = plt.figure(figsize=(8, 3), dpi=120)  # 创建图形窗口

    # 遍历每个epoch生成的图像
    for epoch in range(len(image_list)):
        images = []
        # 将每个epoch生成的8张图像拼接成一行
        for i in range(3):
            row = np.concatenate((image_list[epoch][i * 8:(i + 1) * 8]), axis=1)
            images.append(row)
        # 将三行图像拼接成一幅图像
        img = np.clip(np.concatenate((images[:]), axis=0), 0, 1)
        plt.axis("off")  # 关闭坐标轴
        show_list.append([plt.imshow(img)])  # 将图像添加到显示列表中

    # 创建动画
    ani = animation.ArtistAnimation(fig, show_list, interval=1000, repeat_delay=1000, blit=True)
    # 保存动画为GIF文件
    ani.save('./dcgan.gif', writer='pillow', fps=1)

# 调用函数生成GIF动画
showGif(image_list)
import mindspore as ms
from mindspore import nn, ops
import numpy as np
import matplotlib.pyplot as plt

# 假设generator已经被定义和初始化
generator = Generator()

# 定义批量大小和噪声向量的维度
batch_size = 64  # 假设值,需要在代码中定义
nz = 100  # 假设值,需要在代码中定义

# 从文件中获取模型参数并加载到生成器网络中
ms.load_checkpoint("./generator.ckpt", generator)

# 生成固定噪声
fixed_noise = ops.standard_normal((batch_size, nz, 1, 1))
# 使用生成器生成图像
img64 = generator(fixed_noise).transpose(0, 2, 3, 1).asnumpy()

# 创建图形窗口
fig = plt.figure(figsize=(8, 3), dpi=120)
images = []

# 将生成的图像拼接成三行,每行显示8张图像
for i in range(3):
    images.append(np.concatenate((img64[i * 8:(i + 1) * 8]), axis=1))

# 将三行图像拼接成一幅图像
img = np.clip(np.concatenate((images[:]), axis=0), 0, 1)

# 关闭坐标轴
plt.axis("off")
# 显示图像
plt.imshow(img)
plt.show()

学习心得:

  1. 通过实践,我深入理解了DCGAN的工作原理,包括生成器和判别器的对抗过程,以及它们如何共同提升生成图像的质量。

  2. 学习了如何在MindSpore框架下构建和训练深度学习模型,体验了其简洁的API和高效的执行性能。

  3. 认识到了数据预处理在模型训练中的关键作用,如图像的归一化、裁剪和尺寸调整等,这些步骤直接影响模型的收敛速度和最终性能。

  4. 学习了如何选择和配置损失函数以及优化器,这对于训练稳定和模型性能至关重要。

  5. 理解了生成器和判别器的网络架构设计,包括卷积层、批量归一化层和激活函数的选择,这些都是影响模型性能的关键因素。

  6. 随着深度学习领域的快速发展,我意识到了持续学习新模型、新方法和新技术的重要性,这对于保持专业竞争力至关重要。

加油!!! 

  • 28
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个简单的微信小程序制作学习计划打卡记录页面的代码示例,其中包含了前几学习时间: ``` <view class="container"> <view class="header"> <text>学习计划打卡记录</text> </view> <view class="content"> <view class="card"> <view class="card-header"> <text>今</text> <text>2021-07-01</text> </view> <view class="card-body"> <view class="card-row"> <text>学习时间:</text> <input type="number" placeholder="请输入学习时间(分钟)"/> </view> </view> </view> <view class="card"> <view class="card-header"> <text>昨</text> <text>2021-06-30</text> </view> <view class="card-body"> <view class="card-row"> <text>学习时间:</text> <input type="number" placeholder="请输入学习时间(分钟)"/> </view> <view class="card-row"> <text>已学习:</text> <text>120分钟</text> </view> </view> </view> <view class="card"> <view class="card-header"> <text>前</text> <text>2021-06-29</text> </view> <view class="card-body"> <view class="card-row"> <text>学习时间:</text> <input type="number" placeholder="请输入学习时间(分钟)"/> </view> <view class="card-row"> <text>已学习:</text> <text>90分钟</text> </view> </view> </view> </view> </view> ``` 在上面的代码中,我们使用了三个 `card` 元素来展示前几打卡记录。每个 `card` 元素都包含一个 `card-header` 元素和一个 `card-body` 元素。`card-header` 元素中包含了日期信息,而 `card-body` 元素中包含了输入框和已学习时间的显示。 在实际开发中,你需要将上面的代码替换成你自己的样式和数据。同时,你还需要编写处理用户输入和计算已学习时间的逻辑代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值