前言
大家好,我是机长
本专栏将持续收集整理市场上深度学习的相关项目,旨在为准备从事深度学习工作或相关科研活动的伙伴,储备、提升更多的实际开发经验,每个项目实例都可作为实际开发项目写入简历,且都附带完整的代码与数据集。可通过百度云盘进行获取,实现开箱即用
正在跟新中~
项目背景
(基于GAN(生成对抗网络)生成动漫人物图像)
在数字艺术与创意产业蓬勃发展的今天,动漫文化以其独特的视觉风格、丰富的故事情节和广泛的受众基础,成为了全球流行文化的重要组成部分。动漫人物作为这一领域的核心元素,其设计既需要艺术创造力,也依赖于高度精细化的图像处理技术。然而,传统的手绘或基于软件的动漫人物创作过程往往耗时耗力,难以快速响应市场变化和个性化需求。
在此背景下,生成对抗网络(Generative Adversarial Networks, GANs)作为一种前沿的深度学习技术,展现出了在生成高质量、多样化动漫人物图像方面的巨大潜力。GANs通过构建两个相互竞争的网络——生成器(Generator)和判别器(Discriminator),在不断地对抗学习中优化生成模型,从而能够生成以假乱真的图像数据。这一技术不仅极大地提高了图像生成的效率,还赋予了创作者前所未有的创作自由度,使得动漫人物的设计与生成过程更加智能化、自动化。
本项目旨在利用GANs技术,开发一套能够自动生成动漫人物图像的系统。该系统将通过分析大量动漫人物图像数据,学习其风格特征、色彩搭配、人物比例等关键要素,进而生成具有鲜明动漫风格、高度个性化的新角色图像。项目的成功实施,不仅有望为动漫产业带来全新的创作模式和高效的生产流程,还能够激发更多创意灵感,推动动漫文化的多元化发展。同时,该项目也将为GANs技术在其他视觉艺术领域的应用提供宝贵经验和参考,进一步拓展深度学习技术在创意产业中的应用边界。
项目运行环境
- 平台:windows 10
- 语言环境:python 3.8
- 编辑器:PyCharm
- PyThorch版本:1.8
1.创建并跳转到虚拟环境
python -m venv myenvmyenv\Scripts\activate.bat
2. 虚拟环境pip命令安装其他工具包
pip install torch torchvision torchaudio
注:此处只示范安装pytorch,其他工具包安装类似,可通过运行代码查看所确实包提示进行安装
3.pycharm 运行环境配置
进入pytcharm =》点击file =》点击settings=》点击Project:...=》点击 Python Interpreter,进入如下界面
点击add =》点击Existing environment =》 点击 ... =》选择第一步1创建虚拟环境目录myenv\Scripts\下的python.exe文件点击ok完成环境配置
数据集介绍
数据集大小由63632个高质量动画人脸数据组成
训练数据获取:
私信博主获取
GAN网络介绍
U-Net网络的以其独特的U型结构著称,这种结构由编码器(Encoder)和解码器(Decoder)两大部分组成,非常适合于医学图像分割等任务。下面我将进一步解释您提到的关键点,并补充一些细节。
GAN网络,全称为生成对抗网络(Generative Adversarial Networks),是近年来人工智能领域的一项重要突破,由Ian Goodfellow等人在2014年提出。GAN网络通过两个相互对抗的神经网络——生成器(Generator)和判别器(Discriminator)来实现对复杂数据的生成和模拟。
- 生成器:负责接收随机噪声作为输入,生成看似真实的样本,目标是欺骗判别器,使其无法区分生成样本和真实样本。
- 判别器:接收一个样本(可能是真实样本或生成样本)作为输入,输出该样本为真实样本的概率,目标是准确区分真实样本和生成样本。
GAN网络的训练过程是一个“博弈”过程,生成器和判别器交替训练,不断对抗以提升各自性能。随着训练的进行,生成器生成的样本质量逐渐提高,最终能够生成高质量的样本,而判别器的判断能力也达到最佳。
GAN网络在多个领域具有广泛应用,如图像生成、数据增强、图像修复、风格迁移等。此外,基于GAN网络还衍生出了许多变体,如DCGAN、CycleGAN和StyleGAN等,这些变体进一步拓展了GAN网络的应用范围和性能。
综上所述,GAN网络作为一种强大的生成模型,通过生成器和判别器的对抗训练,为数据生成和模拟提供了全新的方法和思路。
定义配置类(参数)
为了清晰的定义各个参数,我们定义一个配置类,里面存储相关参数,下文可以直接用config类调用相关的变量。
class Config(object):data_path = 'data/'image_size = 96batch_size = 32epochs = 200lr1 = 2e-3lr2 = 2e-4beta1 = 0.5gpu = Falsedevice = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu') # 设备nz = 100ngf = 64ndf = 64save_path = './images'generator_path = './generator.pkl' # 模型保存路径discriminator_path = './discriminator.pkl' # 模型保存路径gen_img = 'result.png'gen_num = 64gen_search_num = 5000gen_mean = 0gen_std = 1config = Config()
定义生成器Generator
生成器在GAN(生成对抗网络)中的输入通常是一个低维的随机噪声向量,这里假设为100维的高斯噪声。这个噪声向量可以被视为一个包含100个随机值的“种子”,这些值共同定义了生成图像的基本属性和特征,虽然这些值本身并不直接对应于图像中的像素或特征图。
然而,为了形象地说明生成过程,我们可以将这个100维的噪声向量视为一个初始的、高度压缩的“特征图”,尽管在传统意义上,特征图通常指的是在卷积神经网络中经过卷积层处理后的图像表示,具有空间维度(如高度、宽度)和深度(如通道数)。但在这里,我们可以将噪声向量看作是一个极简的、未展开的特征表示。
接下来,生成器利用这个“特征图”(即噪声向量)作为起点,通过一系列的网络层,特别是转置卷积层(也称为反卷积层或分数步长卷积层),来逐步放大这个初始表示,最终生成一张指定大小的图片。
转置卷积并不是标准卷积的直接逆操作,因为卷积操作(尤其是带有步长的卷积)在降低空间维度的同时会丢失信息,这些信息在转置卷积过程中无法完全恢复。但转置卷积通过特定的操作(如插入零值、使用转置的卷积核矩阵进行乘法等)来实现空间维度的增加,从而允许生成器从低维的噪声向量构建出高维的图像数据。
因此,通过转置卷积层,生成器能够逐步将初始的噪声向量“展开”成一张逐渐增大并最终达到指定大小的图像,这个过程中,网络学习到了如何将噪声映射到图像空间,从而生成逼真的图像。
class Generator(nn.Module):def __init__(self, config):super().__init__()ngf = config.ngfself.model = nn.Sequential(nn.ConvTranspose2d(config.nz, ngf * 8, 4, 1, 0),nn.BatchNorm2d(ngf * 8),nn.ReLU(True),nn.ConvTranspose2d(ngf * 8, ngf * 4, 4, 2, 1),nn.BatchNorm2d(ngf * 4),nn.ReLU(True),nn.ConvTranspose2d(ngf * 4, ngf * 2, 4, 2, 1),nn.BatchNorm2d(ngf * 2),nn.ReLU(True),nn.ConvTranspose2d(ngf * 2, ngf, 4, 2, 1),nn.BatchNorm2d(ngf),nn.ReLU(True),nn.ConvTranspose2d(ngf, 3, 5, 3, 1),nn.Tanh())def forward(self, x):output = self.model(x)return output
定义判别器Discriminator
判别器在GAN中是一个二分类网络,其输入是图片。通过一系列卷积操作提取特征,形成一维特征向量,最后进行分类。与生成器不同,判别器的卷积操作是正向的,用于特征提取。两者操作虽在结构上相似但目的相反,且关键差异在于激活函数选择和最终层的分类目标:生成器追求生成逼真图像,而判别器则力求准确区分真实与生成图像。
class Discriminator(nn.Module):def __init__(self, config):super().__init__()ndf = config.ndfself.model = nn.Sequential(nn.Conv2d(3, ndf, 5, 3, 1),nn.LeakyReLU(0.2, inplace=True),nn.Conv2d(ndf, ndf * 2, 4, 2, 1),nn.BatchNorm2d(ndf * 2),nn.LeakyReLU(0.2, inplace=True),nn.Conv2d(ndf * 2, ndf * 4, 4, 2, 1),nn.BatchNorm2d(ndf * 4),nn.LeakyReLU(0.2, inplace=True),nn.Conv2d(ndf * 4, ndf * 8, 4, 2, 1),nn.BatchNorm2d(ndf * 8),nn.LeakyReLU(0.2, inplace=True),nn.Conv2d(ndf * 8, 1, 4, 1, 0))def forward(self, x):output = self.model(x)return output.view(-1)
定义模型、优化器及噪声
generator = Generator(config)
discriminator = Discriminator(config)optimizer_generator = torch.optim.Adam(generator.parameters(), config.lr1, betas=(config.beta1, 0.999))
optimizer_discriminator = torch.optim.Adam(discriminator.parameters(), config.lr2, betas=(config.beta1, 0.999))true_labels = torch.ones(config.batch_size)
fake_labels = torch.zeros(config.batch_size)
fix_noises = torch.randn(config.batch_size, config.nz, 1, 1)
noises = torch.randn(config.batch_size, config.nz, 1, 1)
处理数据集
为了能够训练数据,这里我们利用pytorch自带的lmageFolder形成训练集,我们只需要把所有需要训练的图像放在指定目录下即可,然后利用生成的训练集形成迭代器方便后面进行训练。
# 1.数据转换
data_transform = transforms.Compose([transforms.Resize(config.image_size),transforms.CenterCrop(config.image_size),transforms.ToTensor(),transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])
])# 2.形成训练集
train_dataset = datasets.ImageFolder(root=os.path.join(config.data_path),transform=data_transform)# 3.形成迭代器
train_loader = torch.utils.data.DataLoader(train_dataset,config.batch_size,True,drop_last=True)print('using {} images for training.'.format(len(train_dataset)))
模型训练
GAN训练步骤简述:
(1) 训练判别器:固定生成器,调整判别器参数,使其能更准确地分类真实图像为真、生成图像为假。
(2) 训练生成器:固定判别器,调整生成器参数,生成更逼真的图像以欺骗判别器,提高生成图像被判别为真的概率。
(3) 交替循环:重复上述两个步骤,不断优化生成器和判别器,直至达到均衡状态,此时生成器能够生成难以区分的图像。
for epoch in range(config.epochs):for ii, (img, _) in tqdm(enumerate(train_loader)):real_img = img.to(config.device)if ii % 2 == 0:optimizer_discriminator.zero_grad()r_preds = discriminator(real_img)noises.data.copy_(torch.randn(config.batch_size, config.nz, 1, 1))fake_img = generator(noises).detach()f_preds = discriminator(fake_img)r_f_diff = (r_preds - f_preds.mean()).clamp(max=1)f_r_diff = (f_preds - r_preds.mean()).clamp(min=-1)loss_d_real = (1 - r_f_diff).mean()loss_d_fake = (1 + f_r_diff).mean()loss_d = loss_d_real + loss_d_fakeloss_d.backward()optimizer_discriminator.step()else:optimizer_generator.zero_grad()noises.data.copy_(torch.randn(config.batch_size, config.nz, 1, 1))fake_img = generator(noises)f_preds = discriminator(fake_img)r_preds = discriminator(real_img)r_f_diff = r_preds - torch.mean(f_preds)f_r_diff = f_preds - torch.mean(r_preds)loss_g = torch.mean(F.relu(1 + r_f_diff)) + torch.mean(F.relu(1 - f_r_diff))loss_g.backward()optimizer_generator.step()if epoch == config.epochs - 1:# 保存模型torch.save(discriminator.state_dict(), config.discriminator_path)torch.save(generator.state_dict(), config.generator_path)print('Finished Training')
生成图像
下面代码是用加载训练好的模型生成图像的
generator = Generator(config)
discriminator = Discriminator(config)noises = torch.randn(config.gen_search_num, config.nz, 1, 1).normal_(config.gen_mean, config.gen_std)
noises = noises.to(config.device)generator.load_state_dict(torch.load(config.generator_path, map_location='cpu'))
discriminator.load_state_dict(torch.load(config.discriminator_path, map_location='cpu'))
generator.to(config.device)
discriminator.to(config.device)fake_img = generator(noises)
scores = discriminator(fake_img).detach()indexs = scores.topk(config.gen_num)[1]
result = []
for ii in indexs:result.append(fake_img.data[ii])torchvision.utils.save_image(torch.stack(result), config.gen_img, normalize=True, value_range=(-1, 1))
完整可运行代码
import math
import pickle
import osimport numpy as np
import pandas as pd
import torch
import torch.nn as nn
import torch.nn.functional as F
from keras.preprocessing.sequence import pad_sequences
from sklearn.model_selection import train_test_split
from torch.utils.data import TensorDataset
import torchvision
from torchvision import transforms, datasets
from torch import optim
from torchnet import meter
from tqdm import tqdm
from PIL import Imageimport matplotlib.pyplot as pltclass Config(object):data_path = 'data/'image_size = 96batch_size = 32epochs = 200lr1 = 2e-3lr2 = 2e-4beta1 = 0.5gpu = Falsedevice = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu') # 设备nz = 100ngf = 64ndf = 64save_path = './images'generator_path = './generator.pkl' # 模型保存路径discriminator_path = './discriminator.pkl' # 模型保存路径gen_img = 'result.png'gen_num = 64gen_search_num = 5000gen_mean = 0gen_std = 1config = Config()# 1.数据转换
data_transform = transforms.Compose([transforms.Resize(config.image_size),transforms.CenterCrop(config.image_size),transforms.ToTensor(),transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])
])# 2.形成训练集
train_dataset = datasets.ImageFolder(root=os.path.join(config.data_path),transform=data_transform)# 3.形成迭代器
train_loader = torch.utils.data.DataLoader(train_dataset,config.batch_size,True,drop_last=True)print('using {} images for training.'.format(len(train_dataset)))class Generator(nn.Module):def __init__(self, config):super().__init__()ngf = config.ngfself.model = nn.Sequential(nn.ConvTranspose2d(config.nz, ngf * 8, 4, 1, 0),nn.BatchNorm2d(ngf * 8),nn.ReLU(True),nn.ConvTranspose2d(ngf * 8, ngf * 4, 4, 2, 1),nn.BatchNorm2d(ngf * 4),nn.ReLU(True),nn.ConvTranspose2d(ngf * 4, ngf * 2, 4, 2, 1),nn.BatchNorm2d(ngf * 2),nn.ReLU(True),nn.ConvTranspose2d(ngf * 2, ngf, 4, 2, 1),nn.BatchNorm2d(ngf),nn.ReLU(True),nn.ConvTranspose2d(ngf, 3, 5, 3, 1),nn.Tanh())def forward(self, x):output = self.model(x)return outputclass Discriminator(nn.Module):def __init__(self, config):super().__init__()ndf = config.ndfself.model = nn.Sequential(nn.Conv2d(3, ndf, 5, 3, 1),nn.LeakyReLU(0.2, inplace=True),nn.Conv2d(ndf, ndf * 2, 4, 2, 1),nn.BatchNorm2d(ndf * 2),nn.LeakyReLU(0.2, inplace=True),nn.Conv2d(ndf * 2, ndf * 4, 4, 2, 1),nn.BatchNorm2d(ndf * 4),nn.LeakyReLU(0.2, inplace=True),nn.Conv2d(ndf * 4, ndf * 8, 4, 2, 1),nn.BatchNorm2d(ndf * 8),nn.LeakyReLU(0.2, inplace=True),nn.Conv2d(ndf * 8, 1, 4, 1, 0))def forward(self, x):output = self.model(x)return output.view(-1)generator = Generator(config)
discriminator = Discriminator(config)optimizer_generator = torch.optim.Adam(generator.parameters(), config.lr1, betas=(config.beta1, 0.999))
optimizer_discriminator = torch.optim.Adam(discriminator.parameters(), config.lr2, betas=(config.beta1, 0.999))true_labels = torch.ones(config.batch_size)
fake_labels = torch.zeros(config.batch_size)
fix_noises = torch.randn(config.batch_size, config.nz, 1, 1)
noises = torch.randn(config.batch_size, config.nz, 1, 1)for epoch in range(config.epochs):for ii, (img, _) in tqdm(enumerate(train_loader)):real_img = img.to(config.device)if ii % 2 == 0:optimizer_discriminator.zero_grad()r_preds = discriminator(real_img)noises.data.copy_(torch.randn(config.batch_size, config.nz, 1, 1))fake_img = generator(noises).detach()f_preds = discriminator(fake_img)r_f_diff = (r_preds - f_preds.mean()).clamp(max=1)f_r_diff = (f_preds - r_preds.mean()).clamp(min=-1)loss_d_real = (1 - r_f_diff).mean()loss_d_fake = (1 + f_r_diff).mean()loss_d = loss_d_real + loss_d_fakeloss_d.backward()optimizer_discriminator.step()else:optimizer_generator.zero_grad()noises.data.copy_(torch.randn(config.batch_size, config.nz, 1, 1))fake_img = generator(noises)f_preds = discriminator(fake_img)r_preds = discriminator(real_img)r_f_diff = r_preds - torch.mean(f_preds)f_r_diff = f_preds - torch.mean(r_preds)loss_g = torch.mean(F.relu(1 + r_f_diff)) + torch.mean(F.relu(1 - f_r_diff))loss_g.backward()optimizer_generator.step()if epoch == config.epochs - 1:# 保存模型torch.save(discriminator.state_dict(), config.discriminator_path)torch.save(generator.state_dict(), config.generator_path)print('Finished Training')generator = Generator(config)
discriminator = Discriminator(config)noises = torch.randn(config.gen_search_num, config.nz, 1, 1).normal_(config.gen_mean, config.gen_std)
noises = noises.to(config.device)generator.load_state_dict(torch.load(config.generator_path, map_location='cpu'))
discriminator.load_state_dict(torch.load(config.discriminator_path, map_location='cpu'))
generator.to(config.device)
discriminator.to(config.device)fake_img = generator(noises)
scores = discriminator(fake_img).detach()indexs = scores.topk(config.gen_num)[1]
result = []
for ii in indexs:result.append(fake_img.data[ii])torchvision.utils.save_image(torch.stack(result), config.gen_img, normalize=True, value_range=(-1, 1))