数据集

数据集我们用AnimeFaces数据集,共5万多张动漫头像。

链接:https://pan.baidu.com/s/1cp-A8ZV74YBelkSuKxuM6A
提取码:face

要把所有的图片保存于data/face/目录下,后边用ImageFolder就能直接读取到。

模型

模型我们选择DCGAN。

#coding:utf-8from torch import nnclass NetG(nn.Module):"""生成器定义"""def __init__(self,opt):super(NetG,self).__init__()ngf = opt.ngf # 生成器feature map数 self.main = nn.Sequential(# 输入是一个nz维度的噪声,我们可以认为它是一个1*1×nz的feature mapnn.ConvTranspose2d(opt.nz,ngf*8,4,1,0,bias=False),nn.BatchNorm2d(ngf*8),nn.ReLU(True),# 这一步的输出形状:(ngf*8)*4*4nn.ConvTranspose2d(ngf*8,ngf*4,4,2,1,bias=False),nn.BatchNorm2d(ngf*4),nn.ReLU(True),# 这一步的输出形状:(ngf*4)*8*8nn.ConvTranspose2d(ngf*4,ngf*2,4,2,1,bias=False),nn.BatchNorm2d(ngf*2),nn.ReLU(True),# 这一步的输出形状:(ngf*2)*16*16nn.ConvTranspose2d(ngf*2,ngf,4,2,1,bias=False),nn.BatchNorm2d(ngf),nn.ReLU(True),# 这一步的输出形状:(ngf*1)*32*32nn.ConvTranspose2d(ngf,3,5,3,1,bias=False),nn.Tanh(), # 输出范围-1~1,故而采用Tanh# 最后的输出形状:3*96*96)def forward(self,x):return self.main(x)'''
这里需要注意上卷积ConvTransposed2d的使用。当kernel_size为4,stride为2,padding为1时,根据公式输出尺寸刚好变成输入的两倍。
最后一层采用kernel_size为5,stride为3,padding为1,是为了将32*32上采样到96*96,这正好是我们输入图片的尺寸。
最后一层用Tanh将输出图片的像素归一化到-1~1,如果希望归一化到0~1则需要使用Sigmoid。'''class NetD(nn.Module):"""判别器定义"""def __init__(self,opt):super(NetD,self).__init__()ndf = opt.ndf # 判别器feature map数self.main = nn.Sequential(# 输入 3*96*96nn.Conv2d(3,ndf,5,3,1,bias=False),nn.LeakyReLU(0.2,inplace=True),# 输出 (ndf*1)*32*32nn.Conv2d(ndf,ndf*2,4,2,1,bias=False),nn.BatchNorm2d(ndf*2),nn.LeakyReLU(0.2,inplace=True),# 输出 (ndf*2)*16*16nn.Conv2d(ndf*2,ndf*4,4,2,1,bias=False),nn.BatchNorm2d(ndf*4),nn.LeakyReLU(0.2,inplace=True),# 输出 (ndf*4)*8*8nn.Conv2d(ndf*4,ndf*8,4,2,1,bias=False),nn.BatchNorm2d(ndf*8),nn.LeakyReLU(0.2,inplace=True),# 输出 (ndf*8)*4*4nn.Conv2d(ndf*8,1,4,1,0,bias=False),nn.Sigmoid() # 输出一个数(概率))def forward(self,x):return self.main(x).view(-1)'''
判别器和生成器的网络结构差不多是对称的。
这里需要注意的是生成器的激活函数用的是ReLU,而判别器使用的是LeakyReLU,二者并无本质区别,这里的选择更多是经验总结。
每一个样本经过判别器后,输出一个0~1的数,表示这个样本是真图片的概率。'''

训练过程

train.py

#coding:utf-8import os
import torch as t
import torchvision as tv
import tqdm
from model import NetG,NetD
import time
import numpy as np
import scipy.io as io# 在训练函数前,先写配置参数
class Config(object):data_path = 'data/' # 数据集存放路径num_workers = 4 # 多进程加载数据所用的进程数image_size = 96 # 图片尺寸batch_size = 256 # 批处理数max_epoch = 200 # 训练的总轮数last_epoch = 0 # 上次训练到的位置,默认为0lr1 = 2e-4 # 生成器的学习率lr2 = 2e-4 # 判别器的学习率beta1 = 0.5 # Adam优化器的beta1参数beta2 = 0.999 # Adam优化器的beta2参数gpu = True # 是否使用GPUnz = 100 # 噪声维度,用于生成器生成图片ngf = 64 # 生成器feature map数ndf = 64 # 判别器feature map数save_path = 'imgs' # 生成图片保存路径d_every = 1 # 每1个batch训练一次判别器g_every = 5 # 每5个batch训练一次生成器save_every = 10 # 每10个epoch保存一次模型netd_path = None # 'netd_num.pth' 模型参数文件netg_path = None # 'netg_num.pth'opt = Config()
'''
这些只是模型的默认参数,可以利用Fire等工具通过命令行传入,覆盖默认值。'''# 训练过程
def train(**kwargs):for k_,v_ in kwargs.items(): # 加载参数setattr(opt,k_,v_)# 数据预处理transforms = tv.transforms.Compose([tv.transforms.Resize(opt.image_size), # 调整图片大小tv.transforms.CenterCrop(opt.image_size), # 中心裁剪tv.transforms.ToTensor(),tv.transforms.Normalize((0.5,0.5,0.5),(0.5,0.5,0.5)) # 中心化])# 加载数据集dataset = tv.datasets.ImageFolder(opt.data_path,transform=transforms)dataloader = t.utils.data.DataLoader(dataset,batch_size=opt.batch_size,shuffle=True,num_workers=opt.num_workers,drop_last=True) # drop_last表示不用数据集最后不足一个batch的数据print("dataset:"+str(len(dataset))+",dataloader:"+str(len(dataloader)))# 网络,使用gpuif opt.gpu:if t.cuda.is_available():netg,netd = NetG(opt).cuda(),NetD(opt).cuda()print("Train CUDA OK!")else:netg,netd = NetG(opt),NetD(opt)# 在加载预训练模型时,最好指定map_location# 因为如果程序之前在GPU上运行,那么模型就会被存成torch.cuda.Tensor,这样加载时会默认将数据加载至显存。# 如果运行改程序的计算机中没有GPU,加载就会报错,故通过指定map_location将Tensor默认加载入内存中,待有需要时再移至显存中。map_location = lambda storage,loc: storageif opt.netd_path:netd.load_state_dict(t.load('checkpoints/%s'%opt.netd_path,map_location=map_location))print("%s"%opt.netd_path,"loading...OK!")if opt.netg_path:netg.load_state_dict(t.load('checkpoints/%s'%opt.netg_path,map_location=map_location))print("%s"%opt.netg_path,"loading...OK!")# 定义优化器和损失函数optimizer_g = t.optim.Adam(netg.parameters(),opt.lr1,betas=(opt.beta1,opt.beta2))optimizer_d = t.optim.Adam(netd.parameters(),opt.lr2,betas=(opt.beta1,opt.beta2))criterion = t.nn.BCELoss()# 真图片label为1,假图片label为0# noises为生成网络的输入true_labels = t.ones(opt.batch_size).cuda()fake_labels = t.zeros(opt.batch_size).cuda()noises = t.randn(opt.batch_size,opt.nz,1,1).cuda()fix_noises = t.randn(opt.batch_size,opt.nz,1,1).cuda()# 使用已经保存的噪声,保存生成的fix_noises的方法,会在下面显示出来# mat_noises = io.loadmat('noises_double.mat') # 读取文件加载noises# fix_noises = t.from_numpy(mat_noises['np_noises']).cuda() # 重新转换成tensornow = time.clock()epochs = range(opt.last_epoch,opt.max_epoch)for epoch in iter(epochs):g_loss = 0 # 这里为了平均训练一个epoch的损失值d_loss = 0for ii,(img,_) in tqdm.tqdm(enumerate(dataloader)):real_img = img.cuda()# 每1个batch训练一次判别器if ii%opt.d_every == 0:# 训练判别器optimizer_d.zero_grad() # 梯度清零# 尽可能的把真图片判别为1output = netd(real_img)error_d_real = criterion(output,true_labels)error_d_real.backward() # 真图片,反向传播# 尽可能把假图片(生成器生成的)判别为0noises.data.copy_(t.randn(opt.batch_size,opt.nz,1,1)) # noises的值改变了,copy_直接覆盖原有的noises值fake_img = netg(noises).detach() # 根据噪声生成假图 .detach()安全的获得out的值,比.data安全,避免梯度传递到G上,因为训练D时不更新Goutput = netd(fake_img)error_d_fake = criterion(output,fake_labels)error_d_fake.backward() # 假图片,反向传播optimizer_d.step() # 更新参数error_d = error_d_real + error_d_faked_loss += error_d.item()# 每5个batch训练一次生成器if ii % opt.g_every == 0:# 训练生成器optimizer_g.zero_grad()noises.data.copy_(t.randn(opt.batch_size,opt.nz,1,1))fake_img = netg(noises)output = netd(fake_img)error_g = criterion(output,true_labels)error_g.backward()optimizer_g.step()g_loss += error_g.item()# 输出友好信息print("Epoch:{},D_Loss:{:.6f},G_Loss:{:.6f},Time:{:.4f}s".format(epoch,2*d_loss/len(dataset),5*g_loss/len(dataset),time.clock()-now))# 保存模型、图片,这里每次保存一次图片# 噪声可以用我们之前保存的noises.mat文件中的noisesfix_fake_imgs = netg(fix_noises)tv.utils.save_image(fix_fake_imgs.data[:64],'%s/%s.png'%(opt.save_path,epoch),normalize=True,range=(-1,1)) # 这里只保存前64张96*96图片,它们是拼在一起的if epoch%opt.save_every == 0: # 这样做就可以每次10个epoch保存一个checkpointt.save(netd.state_dict(),'checkpoints/netd_%s.pth'%epoch)t.save(netg.state_dict(),'checkpoints/netg_%s.pth'%epoch)# t.cuda.empty_cache() # 周期性的清理显存if __name__ == '__main__':import firefire.Fire()

​ 这里可以每1个batch训练一次判别器并训练一次生成器,也可以每1个batch训练一次生成器并3个batch才训练一次生成器,这些模型都会收敛,只是速度的快慢。

​ 但是,我实验了每1个batch训练一次生成器的同时每3个batch训练一次生成器,这样的模型训练不起来。虽然g_loss会比上面那样的低,但是这并不代表结果就好。

​ 我们可以通过下面代码保存我们的随机生成的noise。这样我们在训练过程中就可以通过这保存文件中的noise来生成图片,进而可以方便观察模型收敛的过程。

import numpy as np
import scipy.io as ionoises = t.randn(64,100,1,1) # B×C×w×H
np_noises = np.array(noises) # 先将tensor转换为array
io.savemat('noises_double.mat',{'np_noises':np_noises}) # 以键值对的形式,保存在.mat文件中
mat_noises = io.loadmat('noises.mat') # 读取这个文件
noises = t.from_numpy(mat_noises['np_noises']) # 重新转换成tensor
noises = noises.cuda()

​ 运行时可以通过终端敲这样的形式运行训练程序,参数以–开头,字符串的双引号可以省略。

python train.py train --netg_path=net_800.pth ……

训练结果

由于我是断断续续训练的,打印的损失值的信息没有保存下来。由于刚开始没有意识到把noise存下来的好处,而且刚开始我在存图片的时候也是每10个epoch才存一张,所以下面图片不连贯。,但是可以清楚的发现,模型是在不停的收敛的。(我本来做了一个gif,但是CSDN传不了那么大的,只能找中间几张图贴出来了)

第9个batch
第109个batch
第209个batch
第309个batch
第409个batch
第509个batch
第609个batch
第709个batch
第800个batch

​ 我总共训练800个epoch(时间花了很久,1060每batch也花了1分多钟),在300个epoch左右生成的图片很少有包含嘴巴的,到后边嘴巴慢慢生成了,这说明模型还在收敛。在训练到600-800epoch时,模型几乎已经不能再变好了,有的图片已经很逼真了,但是相比较训练数据集中的真实图片还是有区别的。而且图片的分辨率才96*96,太小了,所以看起来不是很高清。

​ 我在想是不是模型太小了,生成网络NetG和判别网络NetD内主要都只是由5层卷积层组成的,图片的有些特征是不是还没有被学习到?还是损失函数的选择,会不会有更好的选择?怎样才能生成更高清的图片呢?

​ 这需要去实验,我已经换一个模型在训练了,用的是DRGAN中的网络,之后会整理再发到博客上。

测试过程

用tkinter写一个简单的GUI来显示测试生成的图片。

test.py

#coding:utf-8from tkinter import *
from PIL import Image,ImageTk
from torch import nn
import torch as t
import torchvision as tvclass tk_main:def __init__(self):# 创建窗口,标题,大小self.window = Tk()self.window.title("Image")self.window.geometry('900x900')# 初始化模型def model_init(self):# 模型参数文件netd_path = 'netd_800.pth' # 这里放训练到最后生成的模型参数文件netg_path = 'netg_800.pth'if t.cuda.is_available():netg,netd = NetG(64).cuda().eval(),NetD(64).cuda().eval() # 默认的ngf和ndf都是64,所以这里我直接传给模型64print("Test CUDA OK!")else:netg,netd = NetG(),NetD()# 将模型参数加载到内存中map_location = lambda storage,loc: storageif netd_path:netd.load_state_dict(t.load(netd_path,map_location=map_location))print("%s"%netd_path,"loading...OK!")if netg_path:netg.load_state_dict(t.load(netg_path,map_location=map_location))print("%s"%netg_path,"loading...OK!")return netg,netddef Generate(self,netg,netd):"""随机生成动漫图片,并根据netd的分数选择较好的"""# 生成图片存放地址img_path = 'result.png'with t.no_grad():# 噪声的生成,2048*100*1*1noises = t.randn(2048,100,1,1)noises = noises.cuda()# 生成图片,并计算图片在判别器的分数fake_img = netg(noises)scores = netd(fake_img).detach()# 挑选最好的某几张,这里是从2048张图片中挑出64张indexs = scores.topk(64)[1] # 这里是因为topk()返回两个列表,一个是具体值,一个是具体值在原输入张量中的索引result = []for ii in indexs:result.append(fake_img.data[ii])# 保存图片,这里用到这个stack()函数,是因为我们是挑选出来的图片,需要将它们拼接在一起tv.utils.save_image(t.stack(result),img_path, normalize=True, range=(-1, 1))print('图片存储成功!')# 加载图片load = Image.open(img_path)render = ImageTk.PhotoImage(load)img = Label(image=render)img.image = renderimg.place(x=57, y=57) # 图片居中显示def run(self):netg,netd = self.model_init()# 生成图片Button(self.window,text='单击生成64张动漫图片',command=lambda :self.Generate(netg,netd)).pack()# 主窗口循环显示self.window.mainloop()if __name__ == '__main__':root = tk_main()root.run()

​ 这里有个问题,在判别器判别生成器时输出的是一个数(得分),我们借这个数来排序找到得分最高的64张图片显示出来。但是就是这样一个得分的判断有问题。生成的有的图片,我们人为看起来很明显它不符合要求,但是它的得分却很高。我认为有可能它符合判别器的判断标准。不过综合来看,生成图片中有些图片还是符合要求的。

​ 可以很明显看到,生成的图片是有很多缺陷的,有的人物的双眼是不同颜色的,有的没有嘴巴,有的少一只眼睛等。图片基本上能生成,下面就该思考如何让模型更加强悍。

参考

https://github.com/chenyuntc/pytorch-book/tree/master/chapter7-GAN%E7%94%9F%E6%88%90%E5%8A%A8%E6%BC%AB%E5%A4%B4%E5%83%8F

通过PyTorch用DCGAN生成动漫头像相关推荐

  1. pytorch:DCGAN生成动漫头像

    动漫头像数据集下载地址:动漫头像数据集_百度云连接,DCGAN论文下载地址: https://arxiv.org/abs/1511.06434 数据集里面的图片是这个样子的: 这是DCGAN的主要改进 ...

  2. vs2019 利用Pytorch和TensorFlow分别实现DCGAN生成动漫头像

    这是针对于博客vs2019安装和使用教程(详细)的DCGAN生成动漫头像项目新建示例 目录 一.DCGAN架构及原理 二.项目结构 1.TensorFlow 2.Pytorch 三.数据集下载(两种方 ...

  3. pytorch实现DCGAN生成动漫人物头像

    pytorch实现DCGAN生成动漫人物头像 DCGAN原理 参考这一系列文章 数据集 21551张64*64动漫人物头像 生成效果 训练1个epoch(emm-) 训练10个epoch(起码有颜色了 ...

  4. DCGAN生成动漫头像【学习】

    DCGAN生成动漫头像 在假期看了李宏毅老师的GAN的介绍,看到了课后题DCGAN生成动漫头像的作业,实现一下.记录学习过程. 参考的文章: [Keras] 基于GAN自动生成动漫头像 因为使用的是t ...

  5. Pytorch 使用DCGAN生成动漫人物头像 入门级实战教程

    有关DCGAN实战的小例子之前已经更新过一篇,感兴趣的朋友可以点击查看 Pytorch 使用DCGAN生成MNIST手写数字 入门级教程 有关DCGAN的相关原理:DCGAN论文解读-----DCGA ...

  6. Pytorch实现GAN 生成动漫头像

    什么是GAN? ​ 生成式对抗网络(GAN, Generative Adversarial Networks )是一种深度学习模型,是近年来复杂分布上无监督学习]最具前景的方法之一.GAN的核心思想来 ...

  7. 使用TensorFlow2.0搭建DCGAN生成动漫头像(内含生成过程GIF图)

    文章目录 生成对抗网络介绍 一.造假 二.训练判别器 三.训练生成器 DCGAN介绍 搭建DCGAN 数据来源 必要工作 读取数据 构建生成器 构建判别器 连接模型 连接图片 生成函数 训练 生成对抗 ...

  8. 基于Tensorflow和DCGAN生成动漫头像实践(二)

    本篇内容为动漫头像生成的主要代码部分,第一次写这种代码,从读取数据到生成走了一个完整的流程.创建TFrecord过程可以看上一篇内容. 代码内容: #!/usr/bin/env python2 # - ...

  9. pytorch学习——DCGAN——生成动漫人物头像

    本文参考官方博客以及李宏毅老师讲解. 另参考https://blog.csdn.net/sunqiande88/article/details/80219842 关于其中转置卷积和卷积的问题,会另外开 ...

最新文章

  1. 初始Java DVD项目
  2. 难兄难弟!华为发话:绝不挖中兴的人,不管多优秀都不考虑!
  3. 详解SpringBoot应用跨域访问解决方案
  4. DEAP:使用生理信号进行情绪分析的数据库(二、实验设计与主观分析)
  5. 深度学习armv8/armv9 cache的原理
  6. MySQL事务的可串行化
  7. 一个操作系统的实现(1):分析linux下如何运行一个执行文件
  8. 丢失MySQL root 密码?
  9. 微软封闭服务器切换,执行服务器切换:Exchange 2013 帮助 | Microsoft Docs
  10. c语言可以编制出功能复杂的程序,2018级《C语言程序设计》复习题及答案(5页)-原创力文档...
  11. 游戏是如何限制多开的,如何解决
  12. 没有用户的可用性测试
  13. 能力不是仅靠原始积累(一)
  14. php的gd怎么打开,CentOS 中PHP开启 GD功能
  15. web压力测试的几个指标
  16. 数据风云、十年变迁(DTCC会议总结)
  17. 自建pop邮件服务器,POP是什么?怎样开通?
  18. 戴戒指的含义(以后要结婚的必看)
  19. java毕业设计——基于java+JSP+MySQL的健身俱乐部会员管理系统设计与实现(毕业论文+程序源码)——健身俱乐部会员管理系统
  20. 入坑Java_入坑Java的自学之路

热门文章

  1. 记 计算机 科学学院 教师,学姐般的老师--记校优秀青年教师、计算机科学与工程学院刘文娟学生...
  2. MPP数据库之Doris(1):Doris编译
  3. SSM整合Jackson
  4. 讨论,国产示波器采用的ADC
  5. 连接中心服务器异常,FF14排队出现2002错误详细解决办法 连接大厅服务器发生错误怎么办?...
  6. SpringCloud升级之路2020.0.x版-12.UnderTow 简介与内部原理
  7. 压缩文件格式:gz与xz的对比
  8. How to convert string to wstring?
  9. Java开发环境之idea
  10. DynaSLAM论文笔记