利用DCGAN来实现人脸的创建

其实网上很多博主写了这块的内容,但是我感觉那些博主的代码有些过时了,怎么说呢,模型这块我没办法改,但是在训练阶段,我总感觉他们的代码有些怪异,最开始我按照自己的习惯去更新梯度这些,但是梯度的变化总感觉很奇怪,鉴别器都快到0了,然后就开始怀疑自己,不停的找原因,但是找来找去,原理就是这样,没有错呀,然后又改变我的训练阶段的函数,梯度变化就感觉比较正常了。为什么会这样呢,纠结了很久,后面想了很久,我也是在仔细观察那些博主给的梯度曲线之后,发现其实他们的梯度最初的时候其实也非常不稳定,更新器的损失函数也会很大,然后就不管了,直接让代码跑一段时间再说,还好,再跑了一段时间之后,生成器生成的照片开始有人脸的趋势了。这也说明,我的代码并没有错,完全符合要求。现在再次记录一下,毕竟我自己也弄了这么久,留个纪念。

首先,关于dcgan的原理,我就不再说了,大家自行的去看其它博主的文章,多年前的技术了,资料还是很完善。我直接给你们看论文中给的模型框架吧:
利用DCGAN来实现人脸的创建_第1张图片我最初是完全按照上面这张图来构件网络的,但是可能是由于自己的理解不到位,我将100z理解为一个【100】的向量,然后通过全连接生成[1024x4x4]的向量,然后进行维度变化为[1024, 4,4],然后模型按照上面搭建完后就开始训练了,但是训练的效果很奇怪,最后看了看pytorch官方给的dcgan模型,我才发现,整个dcgan的过程,没有一个fc全连接层,生成器的输入数据的维度是[batchsize, 100, 1,1 ],我看了看论文,百思不得其解为什么要这么做,难道真的是我对论文的理解不到位造成的?不管了,先借鉴pytorch官方给的模型方案进行搭建吧。搭建的过程,我还发现一个不一样的地方,对于参数,我们这里要自己进行初始化,而且对于bias我们还要设置为false,代表梯度更新的时候不更新这个参数,由于我没有深入研究gan以及自己的统计学方面的数学知识基本为空,所以也不是很理解为什么要这样做,先就这样吧,试一试官方的模型框架,代码如下:

'''this code was desinged by nike hu'''
import torch
import torch.nn as nn

device = torch.device('cuda:0')



# 鉴定器网络
class Discrimite(nn.Module):
    def __init__(self):
        super(Discrimite, self).__init__()
        self.conv = nn.Sequential(
            nn.Conv2d(3, 64, kernel_size=4, stride=2, padding=1, bias=False), # 这里false就是bias不进行梯度更新
            nn.LeakyReLU(0.2),
            nn.Conv2d(64, 128, kernel_size=4, stride=2, padding=1, bias=False),
            nn.BatchNorm2d(128),
            nn.LeakyReLU(0.2),
            nn.Conv2d(128, 256, kernel_size=4, stride=2, padding=1, bias=False),
            nn.BatchNorm2d(256),
            nn.LeakyReLU(0.2),
            nn.Conv2d(256, 512, kernel_size=4, stride=2, padding=1, bias=False),
            nn.BatchNorm2d(512),
            nn.LeakyReLU(0.2),
            nn.Conv2d(512, 1, kernel_size=4, stride=1, bias=False),
            nn.Sigmoid()
        )
        self.weight_init()

    def forward(self, x):
        x = self.conv(x)
        return x

    # 初始化参数
    def weight_init(self):
        for m in self.conv.modules():
            if isinstance(m, nn.ConvTranspose2d): # 判断一个变量是否是某个类型可以用isinstance()判断
                nn.init.normal_(m.weight.data, 0, 0.02)
            elif isinstance(m, nn.BatchNorm2d):
                nn.init.normal_(m.weight.data, 0, 0.02) # 初始化为正太分布,torch.nn.init.uniform_(tensor, a=0, b=1)是均匀分布
                nn.init.constant_(m.bias.data, 0) # 初始化为常数


class Generate(nn.Module):
    def __init__(self):
        super(Generate, self).__init__()
        self.conv = nn.Sequential(
            nn.ConvTranspose2d(100, 512, kernel_size=4, stride=1, bias=False),
            nn.BatchNorm2d(512),
            nn.ReLU(),
            nn.ConvTranspose2d(512, 256, kernel_size=4, stride=2, padding=1,bias=False),
            nn.BatchNorm2d(256),
            nn.ReLU(),
            nn.ConvTranspose2d(256, 128, kernel_size=4, stride=2, padding=1, bias=False),
            nn.BatchNorm2d(128),
            nn.ReLU(),
            nn.ConvTranspose2d(128, 64, kernel_size=4, stride=2, padding=1, bias=False),
            nn.BatchNorm2d(64),
            nn.ReLU(),
            nn.ConvTranspose2d(64, 3, kernel_size=4, stride=2, padding=1, bias=False),
            nn.Tanh()
        )
        self.weight_init()

    def forward(self, x):
        x = self.conv(x)
        return x

    # 初始化参数
    def weight_init(self):
        for m in self.conv.modules():
            if isinstance(m, nn.ConvTranspose2d): # 判断一个变量是否是某个类型可以用isinstance()判断
                nn.init.normal_(m.weight.data, 0, 0.02)
            elif isinstance(m, nn.BatchNorm2d):
                nn.init.normal_(m.weight.data, 0, 0.02)
                nn.init.constant_(m.bias.data, 0) # 将bias的值设置为0





if __name__ == '__main__':
    # x = torch.rand(64, 100, 1, 1)
    # x = x.cuda()
    # net = Generate()
    # net.to(device)
    # x = net(x)
    # print(x.shape)
    x = torch.randn(16, 3, 64, 64)
    x = x.to(device)
    net = Discrimite()
    net.to(device)
    x = net(x)
    print(x.shape)

网络框架搭建好了之后,我们就可以开始进行训练代码的设计了,代码如下:

'''this code was desinged by nike hu'''

import torch
from torch.utils.data import DataLoader
from torchvision import transforms
from face_model import Discrimite, Generate # 这里face_model是搭建模型的py文件的名字
import visdom
from torchvision.datasets import ImageFolder
from torch import nn, autograd
from torch.autograd import Variable

batchsize = 64

def getData():
    trainData = ImageFolder('F:/code/DataSet/data/focusight1_round1_train_part1/OK_Images', transform=transforms.Compose([
     transforms.Resize((64, 64)),
        transforms.CenterCrop(64),
        transforms.ToTensor(),
        transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),
    ])) # 使用transform.Compose(),里面要加上[],否则会报错,而且无法迭代数据,这里是加载训练图片路径的
    trainLoader = DataLoader(trainData, batch_size=batchsize, shuffle=True, drop_last=True)

    return trainLoader


# 这里的函数是根据wgan的原理设计的,目的是让鉴别器的梯度变化在1附近
def gradient_penalty(D, xr, xf):
    batch = xr.size(0)
    t = torch.rand(batch, 1, 1, 1).cuda()
    t = t.expand_as(xr)
    mid = t * xr + (1 - t) * xf # 线性差值
    mid.requires_grad_() # 设置上面的线性差值需要求导
    pred = D(mid)
    grads = autograd.grad(outputs=pred, inputs=mid,
                          grad_outputs=torch.ones_like(pred), create_graph=True,
                          retain_graph=True, only_inputs=True)[0]
    # 如果输入x,输出是y,则求y关于x的导数(梯度):
    gp = torch.pow(grads.norm(2, dim=1) - 1, 2).mean() # 二范数和1的差值的平方的均值
    return gp

def trainModel():
    torch.manual_seed(23) # 随机种子设置
    generate_net = Generate()
    print(generate_net)
    discrimi_net = Discrimite()
    print(discrimi_net)
    device = torch.device('cuda:0')
    generate_net = generate_net.to(device)
    discrimi_net = discrimi_net.to(device)
    generate_optimer = torch.optim.Adam(generate_net.parameters(), lr=0.0002, betas=(0.5, 0.9))
    discrimi_optimer = torch.optim.Adam(discrimi_net.parameters(), lr=0.0002, betas=(0.5, 0.9))
    trainLoader = getData()
    viz = visdom.Visdom()
    critimer = nn.BCELoss()
    viz.line([[0, 0]], [0], win='loss', opts=dict(title='loss', legend=['D', 'G']))
    epoch = 0
    for i in range(10000):
        print('------------------------------第', i, '次的函数统计----------------------------------------------------')
        for really_x, _ in trainLoader:
            for _ in range(1): # 这里可以设置为先训练鉴别器多次然后再训练生成器,只需要把1改一下,然后把下面的一行代码恢复
                # really_x = next(iter(trainLoader))[0]
                really_x = really_x.to(device)
                batchs = really_x.size(0)
                pred_r = discrimi_net(really_x).view(batchs, -1) # 生成器生成的数据,最后维度是[batch, 1]


                real_label = Variable(torch.ones((batchs, 1)), requires_grad=False)
                fake_label = Variable(torch.zeros((batchs, 1)), requires_grad=False)
                real_label = real_label.to(device)
                fake_label = fake_label.to(device)
                loss_r = critimer(pred_r, real_label)
                fake_x = torch.randn((batchs, 100, 1, 1))
                fake_x = fake_x.to(device) # 放到gpu上
                fake_x = generate_net(fake_x).detach()
                pred_f = discrimi_net(fake_x).view(batchs, -1) # 生成的照片还要进行判别

                loss_f = critimer(pred_f, fake_label)

                gp = gradient_penalty(discrimi_net, really_x, fake_x.detach())
                loff_D = loss_r + loss_f + 0.2*gp # 这里的0.2是一个可以变化的参数,数值不一样,可能最后效果不一样
                discrimi_optimer.zero_grad()
                loff_D.backward()
                discrimi_optimer.step()
            # 接下来是生成器的loss
            fake_x1 = torch.randn((batchs, 100, 1, 1))
            fake_x1 = fake_x1.to(device)
            fake_image2 = generate_net(fake_x1)
            fake_data2 = discrimi_net(fake_image2).view(batchs, -1)

            real_label = Variable(torch.ones((batchs, 1)), requires_grad=False)
            real_label = real_label.to(device)
            generate_losses = critimer(fake_data2, real_label)

            generate_optimer.zero_grad()
            generate_losses.backward()
            generate_optimer.step()
            print('第', i, '个', '生成器的loss->', generate_losses.item(), '判断器的loss->', loff_D.item())
            viz.images(fake_image2, nrow=8, win='x', opts=dict(title='x'))
            viz.line([[loff_D.item(), generate_losses.item()]], [epoch], win='loss', update='append')
            epoch += 1
        # torch.save(generate_net, '/content/drive/My Drive/model/generate.pkl')
        # torch.save(discrimi_net, '/content/drive/My Drive/model/discrimi.pkl')


if __name__ == '__main__':
    trainModel()

我相信能看这篇文章到最后的人,代码能力也不差,所以就不再解释代码了,我个人觉得,如果你想用我的代码去训练看看效果,也知道修改哪些地方,基本就是训练图像的路径改一下就行,但是要注意一下ImageFolder这个接口要求的目录结构,我这不好画图,你们大家自己百度一下就行,然后就是如果训练效果你觉得不满意,可以试着调一下超参数。然后就是pytorch给的代码中,关于鉴别器的损失函数,是没有加上wgan那部分的,按照pytorch官方代码,上面一行代码要如下改变:

loff_D = loss_r + loss_f + 0.2*gp 要变成 loff_D = loss_r + loss_f 

大家如果觉得效果还不好,也可以试着按照pytorch官方代码去掉gp看看效果。

然后,我们看看训练效果:
利用DCGAN来实现人脸的创建_第2张图片这是我用rtx2060训练了大概两个小时后的效果,接下来看看我在谷歌的colaboratory上训练了十个小时的效果吧:
利用DCGAN来实现人脸的创建_第3张图片感觉效果好了一些,但是没有我想象的那么好,难道是由于训练时长还不够或者上面的gp要去掉再试试?算了算了,不折腾了,大家有兴趣的,可以去试一试。

说到这了,还是忍不住要吐槽一下百度的aistudio,这平台显卡是v100,显卡比谷歌的colaboratory更牛逼(colaboratory有时候还会分配到k80这种已经过时的显卡,目前这种k80显卡也就1000多,而v100显卡要七万左右),操作方式百度也更人性化,但是,重点来了,我在aistudio上如果使用非飞浆的框架,比如我使用pytorch,那么我过不了多久就会被断掉这个项目。感觉百度在强制性的推广自己的框架。推广自己的框架这没错,但是这种强制性的推广,感觉就很烦了,只能去谷歌上面弄了。

github链接,我将代码放到上面了

2020 6.16

你可能感兴趣的:(gan)