GAN生成动漫头像

GAN生成动漫头像

这篇文章主要是听的李宏毅老师的GAN课程,结合了一些《深度学习框架pytorch入门与实践》中的代码实现的。

GAN原理简介

GAN(Generative adversarial Networks)生成对抗网络,GAN解决了一个著名问题:给定一批样本,训练一个系统可以生成类似的新样本。生成对抗网络,顾名思义,有两个部分一个是生成器(Generator),一个判别器(Discriminator),两者相互对抗,左右互博。

  • 生成器(Generator):输入一个随机噪声,生成一张图片
  • 判别器(Discriminator):判别图像是真图片还是假图片

请添加图片描述

如图展示了基本训练过程,第一代的生成器最开始由于是随机初始化,传入一个随机噪点,产生的图片是模糊一片,第一代判别器做的事情就是,能够辨别图片是第一代的生成器产生的图片,还是真实图片。可能第一代判别器认为有颜色的是真图,而生成器要骗过判别器,所以要进化,进化成了第二代,产生有颜色的图片。判别器也随之进化,找出有嘴巴的是真图,辨别出了生成图和真图。那么生成器也进化到第三代,产生了嘴巴的图片,骗过第二代的判别器,判别器又进化成第三代。一步一步对抗,相互进化,最终生成二次元头像。

算法过程

训练Discriminator

首先初始化生成器和判别器,在每次训练迭代中,先固定住生成器,传入随机噪点给生成器,生成对应的图片。前面说了判别器的任务是判别真假图片,判别器主要是给图片打分,分数越高认为真图片的概率越高,分数越低认为是假图片概率越高。所以训练判别器时,收到Database中的图和生成器给的图,调整参数,如果是Database的图就给高分,如果是生成器给的图就给低分。换句话说,我们训练处的判别器就是要数据集给的图越接近1越好,生成器给的图越接近0越好。

请添加图片描述

训练Generator

前面训练好了判别器,现在训练生成器,生成器需要做的事情是,收到随机噪点,产生一张图片,要“骗”过判别器。如何“骗”过生成器呢。实际上的做法是,将生成的图片,送到判别器中进行评分,目标是让评分越高越好(越接近1越好)。整个部分可以看做整体,就是一个巨大的hidden layer,其中有Generator和Discriminator,输入噪点,生成评分。调整参数让评分不断靠近1,但是中间部分Discriminator不能调整。

请添加图片描述

整个过程

请添加图片描述

  1. 从数据集中选出m个样本{x1,x2…xm};接着创建m个噪声,这个z的维度由自己决定,这个就是后面生成器输入的噪声;获取生成数据,就是G(z)生成器生成的;更新判别器参数,使得下面的公式最大化,下边公式的意思是:D(x)判别器判别真图片的分数去log平均值 加上 1 - 判别器判别假图片的值 的log平均值。简单说就是要判别真图片的分数越大越好,判别假图片时,假图片的评分距离1的值越远越好。(相当于训练二分类器,用bceloss
  2. m个噪点z,更新生成器,生成器根据z产生的图片,喂给判别器,产生的分数越高越好。

BCE Loss

bce loss分类,用于二分类问题。数学公式如下
l o s s ( X i , y i ) = − w i [ y i l o g x i + ( 1 − y i ) l o g ( 1 − x i ) ] loss(X_i,y_i) = -w_i[y_ilogx_i + (1 - y_i)log(1 - xi)] loss(Xi,yi)=wi[yilogxi+(1yi)log(1xi)]
pytorch中bceloss

class torch.nn.BCELoss(weight: Optional[torch.Tensor] = None, size_average=None, reduce=None, reduction: str = 'mean')

weight: 初始化权重矩阵

size_average: 默认是True,对loss求平均数

reduction: 默认求和, 对于batch_size的loss平均数

代码实现

前面的理论了解之后,可以着手实现了我这里用的数据集是Extra Data,也可以用Anime Dataset尝试。请自行找梯子。

初始化

import matplotlib.pyplot as plt
import torch
import torchvision
import torchvision.datasets as DataSet
import torchvision.transforms as transform
import torch.utils.data as Data
import numpy as np
import torch.nn as nn
import torch.optim as optim
import os


# 用于图片保存
def saveImg(inp, name):
    inp = inp.numpy().transpose((1, 2, 0))
    mean = np.array([0.5, 0.5, 0.5])
    std = np.array([0.5, 0.5, 0.5])
    inp = std * inp + mean
    inp = np.clip(inp, 0, 1)
    plt.imshow(inp)
    plt.savefig(name)

    
# 用于图片显示,可以调试数据集是否加载成功
def imgshow(inp):
    inp = inp.numpy().transpose((1, 2, 0))
    mean = np.array([0.5, 0.5, 0.5])
    std = np.array([0.5, 0.5, 0.5])
    inp = std * inp + mean
    inp = np.clip(inp, 0, 1)
    plt.imshow(inp)
    plt.show()


    
batch_size = 20
# 图像处理,尺寸转为64 * 64,转tensor范围(0,1), Normalize之后转为 (-1, 1)
simple_transform = transform.Compose([
    transform.Resize((64, 64)),
    transform.ToTensor(),
    transform.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])
# 使用GPU or CPU
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# 噪点数量
noise_z = 100
generator_feature_map = 64
# 加载数据集
path = "AnimeDataset"
train_set = DataSet.ImageFolder(path, simple_transform)
train_loader = Data.DataLoader(train_set, batch_size=batch_size, shuffle=True, num_workers=0)
# 正确分数标签
true_label = torch.ones(batch_size).to(device)
true_label = true_label.view(-1, 1)
# 错误分数标签
false_label = torch.zeros(batch_size).to(device)
false_label = false_label.view(-1, 1)
# 固定的noises,这样在每个Epoch完成之后可以看到generator产生同个照片的过程
fix_noises = torch.randn(batch_size, noise_z, 1, 1).to(device)
# 随机noises
noises = torch.randn(batch_size, noise_z, 1, 1).to(device)
g_train_cycle = 1  # 训练生成器周期
save_img_cycle = 1  # 每几次epoch输出一次结果
print_step = 200  # 打印loss 信息周期

bceloss = nn.BCELoss()

生成器

class Generator(nn.Module):
    def __init__(self):
        super(Generator, self).__init__()
        self.layer1 = nn.Sequential(
            # 100*1*1 --> (64 * 8) * 4 *4
            nn.ConvTranspose2d(noise_z, generator_feature_map * 8, kernel_size=4, bias=False),
            nn.BatchNorm2d(generator_feature_map * 8),
            nn.ReLU(True))
        self.layer2 = nn.Sequential(
            # (64 * 8) * 4 * 4 --> (64 * 4)*8*8
            nn.ConvTranspose2d(generator_feature_map * 8, generator_feature_map * 4, kernel_size=4, stride=2,
                               padding=1),
            nn.BatchNorm2d(generator_feature_map * 4),
            nn.ReLU(True))
        self.layer3 = nn.Sequential(

            # (64*4)*8*8 --> (64*2)*16*16
            nn.ConvTranspose2d(generator_feature_map * 4, generator_feature_map * 2, kernel_size=4, stride=2, padding=1,
                               bias=False),
            nn.BatchNorm2d(generator_feature_map * 2),
            nn.ReLU(True))
        self.layer4 = nn.Sequential(

            # (64*2)*16*16 --> 64*32*32
            nn.ConvTranspose2d(generator_feature_map * 2, generator_feature_map, kernel_size=4, stride=2, padding=1,
                               bias=False),
            nn.BatchNorm2d(generator_feature_map),
            nn.ReLU(True))
        self.layer5 = nn.Sequential(
            # 64*32*32 --> 3*64*64
            nn.ConvTranspose2d(generator_feature_map, 3, kernel_size=4, stride=2, padding=1, bias=False),
            nn.Tanh()
        )

    def forward(self, x):
        out = self.layer1(x)
        out = self.layer2(out)
        out = self.layer3(out)
        out = self.layer4(out)
        out = self.layer5(out)
        return out

判别器

class Discriminator(nn.Module):
    def __init__(self, ndf=64):
        super(Discriminator, self).__init__()
        # layer1 输入 3 x 96 x 96, 输出 (ndf) x 32 x 32
        self.layer1 = nn.Sequential(
            nn.Conv2d(3, ndf, kernel_size=4, stride=2, padding=1, bias=False),
            nn.BatchNorm2d(ndf),
            nn.LeakyReLU(0.2, inplace=True)
        )
        # layer2 输出 (ndf*2) x 16 x 16
        self.layer2 = nn.Sequential(
            nn.Conv2d(ndf, ndf * 2, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ndf * 2),
            nn.LeakyReLU(0.2, inplace=True)
        )
        # layer3 输出 (ndf*4) x 8 x 8
        self.layer3 = nn.Sequential(
            nn.Conv2d(ndf * 2, ndf * 4, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ndf * 4),
            nn.LeakyReLU(0.2, inplace=True)
        )
        # layer4 输出 (ndf*8) x 4 x 4
        self.layer4 = nn.Sequential(
            nn.Conv2d(ndf * 4, ndf * 8, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ndf * 8),
            nn.LeakyReLU(0.2, inplace=True)
        )
        # layer5 输出一个数(概率)
        self.layer5 = nn.Sequential(
            nn.Conv2d(ndf * 8, 1, 4, 1, 0, bias=False),
            nn.Sigmoid()
        )

    # 定义NetD的前向传播
    def forward(self, x):
        out = self.layer1(x)
        out = self.layer2(out)
        out = self.layer3(out)
        out = self.layer4(out)
        out = self.layer5(out)
        out = out.view(-1,1)
        return out

优化器

generator = Generator().to(device)
discriminator = Discriminator().to(device)

learning_rate = 0.0002
beta = 0.5
# 优化器初始化
g_optim = optim.Adam(generator.parameters(), lr=learning_rate, betas=(beta, 0.999))
d_optim = optim.Adam(discriminator.parameters(), lr=learning_rate, betas=(beta, 0.999))

损失函数

def loss_g_func(testLabel, trueLabel):
    return bceloss(testLabel, trueLabel)


def loss_d_func(real_predicts, real_labels, fake_predicts, fake_labels):
    real = bceloss(real_predicts, real_labels)	# 真图片分数,不断靠近1
    fake = bceloss(fake_predicts, fake_labels)	# 假图片分数,不断靠近0
    real.backward()
    fake.backward()
    return real + fake

开始训练

# 训练Discriminator
train_num = 25
for trainIdx in range(train_num):
    for step, data in enumerate(train_loader):
        image_x, _ = data
        image_x = image_x.to(device)
        # 训练判别器
        noises.data.copy_(torch.randn(batch_size, noise_z, 1, 1))
        out = discriminator(image_x)	# 原图产生的分数
        fake_pic = generator(noises)	# 生成器生成图像
        fake_predict = discriminator(fake_pic.detach())	# 使用detach()切断求导关联
        d_optim.zero_grad()
        dloss = loss_d_func(out, true_label, fake_predict, false_label)
        d_optim.step()

        if step % g_train_cycle == 0:
            # 训练生成器
            g_optim.zero_grad()
            noises.data.copy_(torch.randn(batch_size, noise_z, 1, 1))
            fake_img = generator(noises)
            fake_out = discriminator(fake_img)
            # 尽可能让判别器把假图判别为1
            loss_fake = loss_g_func(fake_out, true_label)
            loss_fake.backward()
            g_optim.step()

        if step % print_step == print_step - 1:
            print("train: ", trainIdx, "step: ", step + 1, " d_loss: ", dloss.item(), "mean score: ",
                  torch.mean(out).item())
            print("train: ", trainIdx, "step: ", step + 1, " g_loss: ", loss_fake.item(), "mean score: ",
                  torch.mean(fake_out).item())

    if trainIdx % save_img_cycle == 0:
        fix_fake_image = generator(fix_noises)
        fix_fake_image = fix_fake_image.data.cpu()
        comb_img = torchvision.utils.make_grid(fix_fake_image, nrow=4)
        savepath = os.path.join("gan", "pics", "g_%s.jpg" % trainIdx)
        saveImg(comb_img, savepath)
        torch.save(discriminator.state_dict(), './gan/netd_%s.pth' % trainIdx)
        torch.save(generator.state_dict(), './gan/netg_%s.pth' % trainIdx)

结果

这个是1个Epoch的效果

请添加图片描述

5个Epoch

请添加图片描述

10个Epoch

请添加图片描述

25个Epoch

请添加图片描述

### 使用CycleGAN实现动漫风格的人脸图片生成 为了使用CycleGAN将真人照片转换为具有特定艺术风格的二次元图像,可以遵循以下方法: #### 准备工作环境 确保安装必要的依赖库。通常情况下,这涉及到PyTorch框架及其扩展包torchvision用于加载数据集以及处理图像。 ```bash pip install torch torchvision matplotlib opencv-python ``` #### 获取预训练模型 可以从指定资源获取预先训练好的CycleGAN模型权重文件。对于动漫风格转化的任务,可访问提供的Gitee仓库链接下载相应的checkpoint文件[^3]。 #### 数据准备 创建并整理好待处理的真实人脸图片路径,并设置输出目录来存储转换后的结果。如果采用的是官方教程中的配置,则应参照其结构建立`testA`子文件夹放置测试样本。 #### 调整参数与执行推理过程 修改脚本内的超参设定以适应个人需求,比如批次大小(batch size),线程数(workers)等;之后运行预测命令完成从真实域到卡通域之间的映射操作。 ```python import os from options.test_options import TestOptions from data import CreateDataLoader from models import create_model from util.visualizer import save_images from util import html if __name__ == '__main__': opt = TestOptions().parse() # hard-code some parameters for test opt.num_threads = 1 # 测试期间使用的CPU核心数量 opt.batch_size = 1 # 单次前向传播所用样本量 opt.serial_batches = True # 禁用数据洗牌 opt.no_flip = True # 不应用水平翻转的数据增强策略 opt.display_id = -1 # 关闭可视化窗口 data_loader = CreateDataLoader(opt) dataset = data_loader.load_data() model = create_model(opt) model.setup(opt) web_dir = os.path.join(opt.results_dir, opt.name, '{}_{}'.format(opt.phase, opt.epoch)) webpage = html.HTML(web_dir, 'Experiment = %s, Phase = %s, Epoch = %s' % (opt.name, opt.phase, opt.epoch)) for i, data in enumerate(dataset): if i >= opt.num_test: break model.set_input(data) model.test() visuals = model.get_current_visuals() img_path = model.get_image_paths() save_images(webpage, visuals, img_path, aspect_ratio=opt.aspect_ratio) webpage.save() ``` 此段代码展示了如何利用已有的CycleGAN架构来进行跨领域图像翻译的具体流程,包括但不限于初始化选项解析、构建合适的数据读取器、实例化网络对象、定义保存位置等一系列准备工作,最终通过调用`.test()`函数触发实际的推断逻辑并将得到的结果按照一定格式存盘以便后续查看效果[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值