用pytorch编写的DCGAN生成动漫角色的头像 您所在的位置:网站首页 办公卡通头像 用pytorch编写的DCGAN生成动漫角色的头像

用pytorch编写的DCGAN生成动漫角色的头像

2023-02-28 22:03| 来源: 网络整理| 查看: 265

我以前听说过DCGAN,并且对此感兴趣。我最近开始学习pytorch,所以我想写DCGAN作为练习。

已经有很多使用DCGAN生成动漫角色头像的示例,但是我找不到用pytorch编写的示例,因此我决定自己编写。

首先,结果示例

基本上,我是用pytorch编写的,是指我之前编写的诸如mattya和rezoolab之类的代码。

用于参考的代码 https://qiita.com/rezoolab/items/5cc96b6d31153e0c86bc https://qiita.com/mattya/items/e5bfe5e04b9d2f0bbd47 https://qiita.com/triwave33/items/35b4adc9f5b41c5e8141 https://github.com/carpedm20/DCGAN-tensorflow https://github.com/chainer/chainer/tree/master/examples/dcgan https://github.com/pytorch/examples/tree/master/dcgan

在上面的链接中详细解释了该理论,因此在这里我将写有关如何用pytorch编写和结果可视化的文章。

实施理念

图像像素可以是64、80、96 可以在CPU和GPU上运行 每次训练后输出样本结果 始终保存参数,以便以后继续训练 保存优化器参数

首先,我想知道被16整除的像素数量是否适用于任何大图像,而当我尝试以112px进行尝试时,我无法生成任何图片,这是一个很大的错误。毕竟64px,80px和96px似乎是最好的。但是,96px很容易失败,结果似乎不及80px。

生成器和鉴别器模型

生成器和鉴别器由这些层组成。

卷积卷积层 卷积反卷积层 林全连接层 BatchNorm批次正则化层 ReLU 吕鲁 h 乙状结肠

这样从上到下排列。

生成器

鉴别器

Conv和ConvT之后的数字是:输入大小,输出大小,过滤器大小,步幅,填充 LIN之后的数字是:输入大小,输出大小 BatchNorm之后的数字是:输入和输出大小

这是使用80像素大小的图像时的模型。使用64像素和96像素时,结构相同,但是数字略有不同。

使用的模型图片

数据来自https://konachan.net和https://safebooru.org上的图像,并使用lbpcascade-animeface(https://github.com/nagadomi/lbpcascade_animeface)使用cv2进行人脸检测。

实作

tomodcgan.py

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191# -*- coding: UTF-8 -*- import numpy as np from skimage.io import imsave,imread import torch import time,sys,os,re from glob import glob from random import shuffle nn = torch.nn relu = nn.ReLU(True) lrelu = nn.LeakyReLU(0.2,True) sigmoid = nn.Sigmoid() tanh = nn.Tanh() entropy = nn.BCELoss() # 損失を計算する関数 # それぞれの層の定義 class Conv(nn.Conv2d):     def __init__(self,*arg,**kw):         if('bias' not in kw):             kw['bias'] = False # ここでバイアスは使用しない         super(Conv,self).__init__(*arg,**kw)         self.weight.data.normal_(0,0.02) # 初期値の設定 class ConvT(nn.ConvTranspose2d):     def __init__(self,*arg,**kw):         if('bias' not in kw):             kw['bias'] = False         super(ConvT,self).__init__(*arg,**kw)         self.weight.data.normal_(0,0.02) class Lin(nn.Linear):     def __init__(self,*arg,**kw):         if('bias' not in kw):             kw['bias'] = False         super(Lin,self).__init__(*arg,**kw)         self.weight.data.normal_(0,0.02) class BN(nn.BatchNorm2d):     def __init__(self,*arg,**kw):         super(BN,self).__init__(*arg,**kw)         self.weight.data.normal_(1,0.02)         self.bias.data.fill_(0) # テンソルを変形するためのモジュール class Henkei(nn.Module):     def __init__(self,*k):         super(Henkei,self).__init__()         self.k = k     def forward(self,x):         return x.view(x.size()[0],*self.k) # ジェネレータの定義 class Genera(nn.Sequential):     def __init__(self,nz=100,c=3,d=64,p=4,kn=4,st=2,pd=1):         d = [d*2**i for i in range(4)]         super(Genera,self).__init__(             Henkei(nz,1,1),             ConvT(nz,d[3],p,1,0),             BN(d[3]),             relu,             ConvT(d[3],d[2],kn,st,pd),             BN(d[2]),             relu,             ConvT(d[2],d[1],kn,st,pd),             BN(d[1]),             relu,             ConvT(d[1],d[0],kn,st,pd),             BN(d[0]),             relu,             ConvT(d[0],c,kn,st,pd),             tanh) # ディスクリミネータの定義 class Discrim(nn.Sequential):     def __init__(self,c=3,d=64,p=4,kn=4,st=2,pd=1):         d = [d*2**i for i in range(4)]         super(Discrim,self).__init__(             Conv(c,d[0],kn,st,pd),             lrelu,             Conv(d[0],d[1],kn,st,pd),             BN(d[1]),             lrelu,             Conv(d[1],d[2],kn,st,pd),             BN(d[2]),             lrelu,             Conv(d[2],d[3],kn,st,pd),             BN(d[3]),             lrelu,             Henkei(d[3]*p*p),             Lin(d[3]*p*p,1),             Henkei(),             nn.Sigmoid()) # DCGANの定義 class DCGAN:     def __init__(self,nz,kk,eta=0.002,gpu=0):         assert(kk%16==0)         super(DCGAN,self).__init__()         self.nz = nz # zの数         self.kk = kk # 画像のサイズ         self.g = Genera(nz,p=int(kk/16))         self.g.opt = torch.optim.Adam(self.g.parameters(),lr=eta,betas=(0.5,0.999))         self.d = Discrim(p=int(kk/16))         self.d.opt = torch.optim.Adam(self.d.parameters(),lr=eta,betas=(0.5,0.999))         if(gpu):             self.g.cuda()             self.d.cuda()             self.dev = torch.device('cuda')         else:             self.dev = torch.device('cpu')     # 学習     def gakushuu(self,X,kurikaeshi=200,n_batch=64):         if(not os.path.exists(outfolder)):             os.mkdir(outfolder)         param = sorted(glob(os.path.join(outfolder,'tomopara*.pkl')))         # 前に保存されたパラメータを探して、見つかったら読み出す         if(param):             param = param[-1]             state_dict = torch.load(param)             self.g.load_state_dict(state_dict[0])             self.d.load_state_dict(state_dict[1])             self.g.opt.load_state_dict(state_dict[2])             self.d.opt.load_state_dict(state_dict[3])             z0 = torch.load(os.path.join(outfolder,'z0.pkl'))             hajime = int(re.search(r'(\d+)\.pkl',param)[1])         else:             hajime = 0             z0 = (torch.rand(n_batch,self.nz)*2-1).to(self.dev)             torch.save(z0,os.path.join(outfolder,'z0.pkl'))         n = len(X)         t0 = time.time()         v0 = torch.zeros(n_batch).to(self.dev)         v1 = torch.ones(n_batch).to(self.dev)         for j in range(hajime,hajime+kurikaeshi):             sentaku = np.random.permutation(n) # ミニバッチのランダム選択             for i in range(0,n,n_batch):                 X_mohan = X[sentaku[i:i+n_batch]]                 X_mohan = [imread(img) for img in X_mohan] # 模範の画像を読み出す                 X_mohan = np.stack(X_mohan)                 X_mohan = X_mohan/127.5-1.                 X_mohan = X_mohan.transpose(0,3,1,2)                 X_mohan = torch.FloatTensor(X_mohan).to(self.dev) # テンソルに変換                 nx = len(X_mohan)                 z = (torch.rand(nx,self.nz)*2-1).to(self.dev)                 X_seisei = self.g(z) # ジェネレーターで画像を生成する                 hyouka_seisei = self.d(X_seisei) # ディスクリミネータで生成された画像を評価する                 hyouka_mohan = self.d(X_mohan) # 模範の画像も評価する                 # 損失を計算して逆伝播                 self.d.opt.zero_grad()                 loss_d_mohan = entropy(hyouka_mohan,v1[:nx])                 loss_d_mohan.backward()                 loss_d_seisei = entropy(hyouka_seisei,v0[:nx])                 loss_d_seisei.backward(retain_graph=True) # 後でまた逆伝播を再利用するためにretain_graphは必須                 self.d.opt.step()                 self.g.opt.zero_grad()                 loss_g = entropy(hyouka_seisei,v1[:nx])                 loss_g.backward() # retain_graphをしておいていなかったらここでエラーが出る                 self.g.opt.step()                 # サンプル画像を生成して書き込む                 if(i+n_batch>=n or i/n_batch%100==99):                     gazou = np.hstack(np.hstack(self.g(z0).data.cpu().numpy().transpose(0,2,3,1).reshape(8,8,self.kk,self.kk,3)))/2.+0.5                     if(i/n_batch%100==99):                         namae = os.path.join(outfolder,'pts%03d_%06d.jpg'%(j,i+nx))                     else:                         namae = os.path.join(outfolder,'pts%03d.jpg'%(j+1))                     imsave(namae,gazou)                 print('%d/%d 第%d回 %.3f分過ぎた'%(i+nx,n,j+1,(time.time()-t0)/60)) # 毎回進度発表             state_dict = [self.g.state_dict(),self.d.state_dict(),self.g.opt.state_dict(),self.d.opt.state_dict()]             torch.save(state_dict,os.path.join(outfolder,'tomopara%03d.pkl'%(j+1))) # パラメータを保存 if(__name__=='__main__'):     nz = 100 # zの数     lr = 0.0002 # 学習率     n_batch = 64 # ミニバッチの数     kurikaeshi = 151 # 何回繰り返す     pixel = 80 # 画像の大きさ     gpu = 1 # GPUを使うかどうか     datafolder = 'imgdata' # 模範の画像のフォルダ     outfolder = 'dcganimg' # 生成の画像とパラメータを保存するフォルダ     X = glob(os.path.join(datafolder,'*.jpg'))     shuffle(X)     X = np.array(X)     dcgan = DCGAN(nz,pixel,lr,gpu)     dcgan.gakushuu(X,kurikaeshi,n_batch) # 学習開始

哪里要小心

鉴别器对生成图像的评估将用于生成器和鉴别器的反向传播,因此,如果不对.backward()添加keep_graph = True,则会出现错误。 使用pytorch的torch.nn.Sequential很方便,因为您可以通过安排模块来生存,而不必像keras一样往前写,但是从卷积层到完全连接层时,需要转换张量。 pytorch不会自动转换,也没有模块可以转换它,因此您必须自己编写它。 似乎设置参数的初始值出乎意料是必要的。在pytorch中,默认初始值设置不太适合DCGAN,因此我将继承Conv2d,ConvTranspose2d和BatchNorm2d的类,并自行更改初始值设置。 由于生成器的输出使用tanh,它将是-1到1,但是如果要将其输出到图像,则需要将其转换为0到1。 输入到鉴别器时,模型图像以及生成器生成的图像必须转换为-1到1。

结果

我尝试了64px,80px和96px,但是在这里我将宣布80px的结果

根据训练数据,结果将有所不同,但首先,使用可从konachan进行检测的117870个面部图像的结果如下

这似乎不是一个很好的结果。通过自动检测脸部创建的图像似乎包含约4%的无关图像。我本人试图擦除不相关的图像,但是当我期望处理所有这些图像可能需要几天的时间,因为有太多图像时,我放弃了它,并按原样用于学习。结果差可能也归因于不良样品的存在。

以下是我第一次学习使用所有图像时的结果。我仍然不成熟,无法清晰地画画,但是我认为这也令人惊讶。

之后,我使用safebooru的图像进行了尝试。由于safebooru具有更多图像,因此这次我只取出了全部为19008的蓝色和红色头发。

这个数字很小,但是不良样本很少。这要归功于safebooru精心制作的标签。

这是每个阶段输出的图像。

1/3次

2/3次

1次

1 1/3倍

1 2/3次

2次

3次

5次

7次

10次

20次

30次

50次

151次

起初变化很快,但是经过一段时间的学习,变化已经逐渐。似乎学得越多越好。相反,如果您学得太多,变异似乎就会消失。这就是为什么在151张图像中,有多少张脸变得相同。

我试图制作一段学习阶段的视频,但由于它很大,所以将其放在pixiv上



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有