Generative Adversarial Nets 研究

简述

填坑,打算认真做下关于GAN论文的研究,并做实现。

http://papers.nips.cc/paper/5423-generative-adversarial-nets.pdf

深度生成模型的缺点

  1. 如果概率模型很复杂的话,是很难用最大似然估计或者是其他类似的策略来去估计的。
  2. 很难利用到分段线性函数的好处(这个部分,主要是想要结合之前论文提到的,很多成功的深度学习的东西,如果用到判别模块,一般都用到了分段线性函数。)

模型思想

生成模型需要参与到一场“辩论”当中。简单来说,就是多出一个判别模型,判别模型的作用是判断一个输入的数据是来自于生成模型生成的fake data 还是来自于真实数据。

这篇论文对于两个模型的结构设定:

  • 生成器:多层感知机
  • 判别器:多层感知机

对抗生成网络

  • 生成器,一个多层感知机,利用数据zz(分布为pz(z)p_z(z))作为模型的输入,来生成结果分布为pgp_g。生成器可以表示为一个映射G(z;θg)G(z;\theta_g)
  • 判别器,一个多层感知机,利用数据xx,输出为一个标量。判别器可以表示一个一个映射D(x;θd)D(x;\theta_d)。其中D(x)D(x)表示为xx来自于真实数据而不是生成器的内容的概率。

因此,很自然的是,训练D去最大化判断准确的概率​;同时训练G去最大化骗过​D的概率。​

理论分析

这篇论文给出的目标函数为:
在这里插入图片描述

这个函数的中加了log的最大目的是为了在后面的计算中转换成KL散度,再转成JS散度,从而估计最优值结果。

准确性相关证明

命题1: 当G给定的时候,最优的D为

在这里插入图片描述

证明也很简单,如下:

在这里插入图片描述
需要考虑的是,第一行的后面的积分部分,是如何转成第二行的后面部分。

这个部分不能简单用换元的思路来,不然会搞出G`(z)这种东西来。而是应该从映射的角度来看。

对于每一个z,都有与之对应的x=g(z),概率密度函数值p(z)。但这里需要考虑到pg(x)的生成,pg(x)本质上就是所有通过G之后映射到相同的x的z对应的pz的求和。所以,把积分拆成求和的极限之后,就会发现,其实就是把部分的项部分的求和之后,再做极限(得到新的积分)。

又很显然对应函数,
f(x)=alogx+blog(1x)f(x) = a log x+ b log(1-x)在a,b不都为0的情况下,在定义域[0,1],求导之后很简单得到极值在aa+b\frac{a}{a+b}上取最大值。
值得注意的是,值得注意的是,考虑到原来的积分部分,可以看到,概率密度为0的部分 ,在积分中其实不是不需要考虑的。故, 符合上面的函数要求。即,命题一得证。

当最大化D之后,构造新的函数(主要是为了表示方便而已):
在这里插入图片描述
因此全局最优解,现在变成了C(G)函数的最小值。

定理 最优值,当且仅当pg=pdatap_g = p_{data}时,取到。并且这个时候的最小值为log4-log4

这个证明也很简单:
在C(G)第三行的表示的两个期望中,分母部分都除以2。此外再一个2。毫无意外,就多出了下面这两个项。
在这里插入图片描述
剩余的部分,可以表示为两个KL散度的求和。

在这里插入图片描述

而这个部分,其实就是一个JS散度。

因此C(G)可以做下面两个变换。
在这里插入图片描述

在这里插入图片描述
但是JS散度,当且仅当pdata=pgp_{data} = p_g 取到最小值0,即得证。

实现

工具:pytorch

数据

因为GAN的实力还不够强,因此这里直接用简单的图片数据。

关于MNIST数据集的之前已经有做过了 基于MNIST的GANs实现【Pytorch】
这里尝试用下kaggle上的猫狗数据集,下载方式:https://www.microsoft.com/en-us/download/details.aspx?id=54765

下载之后,再解压一下就好了。
里面有12500张猫的照片,也有12500张狗的照片。(多出来的那个数据不是图片来的)
在这里插入图片描述

文件目录结构

在这里插入图片描述

代码简述

  • 所有的代码中的main部分,main.py和judge.py的main部分,其余的都是用于测试
  • 由于图片可能损坏(数量很少),因此,main.py中实际训练用到的数据量是小于等于(但近似)给定的图片训练数量
  • 模型最终会使得 对于任何的数据D的输出都是0.5,导致最后的两个loss收敛于 -2log0.5(0.6左右) 以及log0.5(-0.3左右)
  • 每个epoch给出的loss的输出,其实是data_size / batch_size * loss。因为关于每个batch是平均的,但是在不同的batch之间是直接求和。
  • 因为相比于数字图片,这种图片更加复杂,所以采用了GPU的训练(多跑几个epoch)。如果是用cpu,就把所有的.gpu()去掉就好了(当然对应的.cpu()部分也是需要去掉的)。

model.py

import os

import torch
import torch.nn as nn
import torch.utils.data as Data
import torchvision
from torch.utils.data import DataLoader
from dataloader import MyDataset


class Generater(nn.Module):
    def __init__(self, input_size, mid_size):
        super(Generater, self).__init__()
        self.input_size = input_size
        self.mid_size = mid_size
        self.layer1 = nn.Sequential(
            nn.Linear(self.input_size, self.mid_size),
            nn.ReLU(),
            nn.Linear(self.mid_size, self.input_size),
            nn.Sigmoid(),
        )

    def forward(self, x):
        shape = x.shape
        x = x.view(shape[0], self.input_size)
        x = self.layer1(x)
        x = x.view(shape)
        return x


class Discriminator(nn.Module):
    def __init__(self, input_size, mid_size):
        super(Discriminator, self).__init__()
        self.input_size = input_size
        self.mid_size = mid_size
        self.layer1 = nn.Sequential(
            nn.Linear(self.input_size, self.mid_size),
            nn.ReLU(),
            nn.Linear(self.mid_size, 1),
            nn.Sigmoid(),
        )

    def forward(self, x):
        shape = x.shape
        x = x.view(shape[0], self.input_size)
        x = self.layer1(x)
        return x


if __name__ == '__main__':
    G = Generater(3 * 120 * 120, 1024)
    D = Discriminator(3 * 120 * 120, 1024)

    path = "D:\Code\Python\Project\GAN\GAN\kagglecatsanddogs_3367a\PetImages\Cat"
    mydataset = MyDataset(path=path, Len=2, resize=120, img_type='jpg')

    print(mydataset[0].shape)

    train_loader = DataLoader(mydataset, batch_size=10, shuffle=True)
    for step, x in enumerate(train_loader):
        print(x.shape)
        print(G(x).shape)
        print(D(x))

dataloader.py

import torch.utils.data as data
import glob
import os
import torchvision.transforms as transforms
from PIL import Image
import matplotlib.pyplot as plt
import numpy as np
import torch

import piexif
import imghdr


class MyDataset(data.Dataset):
    def __init__(self, path, Train=True, Len=-1, resize=-1, img_type='png'):

        if resize != -1:
            transform = transforms.Compose([
                transforms.Resize(resize),
                transforms.CenterCrop(resize),
                transforms.ToTensor(),
            ])
        else:
            transform = transforms.Compose([
                transforms.ToTensor(),
            ])
        img_format = '*.%s' % img_type

        for name in glob.glob(os.path.join(path, img_format)):
            try:
                piexif.remove(name)  # 去除exif
            except Exception:
                continue
        # imghdr.what(img_path) 判断是否为损坏图片
        if Len == -1:
            self.dataset = [np.array(transform(Image.open(name).convert("RGB"))) for name in
                            glob.glob(os.path.join(path, img_format)) if imghdr.what(name)]
        else:
            self.dataset = [np.array(transform(Image.open(name).convert("RGB"))) for name in
                            glob.glob(os.path.join(path, img_format))[:Len] if imghdr.what(name)]
        self.dataset = np.array(self.dataset)
        self.dataset = torch.Tensor(self.dataset)
        self.Train = Train

    def __len__(self):
        return len(self.dataset)

    def __getitem__(self, idx):
        return self.dataset[idx]


if __name__ == '__main__':
    path = "D:\Code\Python\Project\GAN\GAN\kagglecatsanddogs_3367a\PetImages\Cat"
    mydataset = MyDataset(path=path, Len=100, resize=120, img_type='jpg')
    print(len(mydataset))
    print(mydataset[0].shape)
    print(type(mydataset[0].numpy()))
    print(mydataset[0].numpy().shape)
    img = mydataset[0].numpy().transpose((1, 2, 0))
    print(img.shape, img.max(), img.min())

    plt.imshow(mydataset[0].numpy().transpose((1, 2, 0)))
    plt.show()

main.py

import os

import cv2
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.data import Dataset, DataLoader

from model import Generater, Discriminator
from dataloader import MyDataset

if __name__ == '__main__':
    LR = 0.00001
    EPOCH = 1500
    path = "D:\Code\Python\Project\GAN\GAN\kagglecatsanddogs_3367a\PetImages\Cat"
    img_type = 'jpg'
    img_size = 64
    dataset_len = 1000
    batch_size = 100
    img_shape = (3, img_size, img_size)  # 默认是三维图,即彩图

    model_mid_size = 1024

    mydataset = MyDataset(path=path, Len=dataset_len, resize=img_size, img_type=img_type)
    train_loader = DataLoader(mydataset, batch_size=batch_size, shuffle=True)

    G = Generater(3 * img_size * img_size, model_mid_size).cuda()
    D = Discriminator(3 * img_size * img_size, model_mid_size).cuda()

    optimizerG = torch.optim.Adam(G.parameters(), lr=LR)
    optimizerD = torch.optim.Adam(D.parameters(), lr=LR)

    for epoch in range(EPOCH):
        tmpD, tmpG = 0, 0
        for step, x in enumerate(train_loader):
            x = x.cuda()
            rand_noise = torch.randn((x.shape[0], *img_shape)).cuda()
            G_imgs = G(rand_noise)

            D_fake_probs = D(G_imgs)
            D_real_probs = D(x)

            D_loss = - torch.mean(torch.log(D_real_probs) + torch.log(1. - D_fake_probs))
            G_loss = torch.mean(torch.log(1. - D_fake_probs))

            optimizerD.zero_grad()
            D_loss.backward(retain_graph=True)
            optimizerD.step()

            optimizerG.zero_grad()
            G_loss.backward(retain_graph=True)
            optimizerG.step()

            tmpD += D_loss.cpu().detach().data
            tmpG += G_loss.cpu().detach().data

        print(
            'epoch %d sum of loss: D: %.6f, G: %.6f' % (epoch, tmpD, tmpG)
        )
    torch.save(G, 'G.pkl')
    torch.save(D, 'D.pkl')

judge.py

import numpy as np
import torch
import matplotlib.pyplot as plt
from model import Generater, Discriminator

from torch.utils.data import Dataset, DataLoader
from dataloader import MyDataset

if __name__ == '__main__':
    path = "D:\Code\Python\Project\GAN\GAN\kagglecatsanddogs_3367a\PetImages\Cat"
    mydataset = MyDataset(path=path, Len=100, resize=64, img_type='jpg')
    train_loader = DataLoader(mydataset, batch_size=10, shuffle=True)

    G = torch.load("G.pkl").cuda()
    img_size = 64
    img_shape = (3, img_size, img_size)

    rand_noise = torch.randn((1000, *img_shape)).cuda()
    G_imgs = G(rand_noise)
    D = torch.load('D.pkl').cuda()
    D_G = D(G_imgs).cpu().detach().numpy().reshape(-1)
    # print(D_G)

    tmp = 0
    for step, x in enumerate(train_loader):
        x = x.cuda()
        val = D(x)
        tmp += np.sum(val.cpu().detach().numpy())

    print(tmp / 100)

    G_imgs = G_imgs.cpu().detach()
    for i in range(len(G_imgs)):
        if abs(D_G[i] - 0.5) < 0.01:
            print(D_G[i])
            img = G_imgs[i].numpy().transpose((1, 2, 0))
            plt.imshow(img)
            plt.show()
            # print(img.max(), img.min())
        # else:
            # print(D_G[i])


一组实验结果

  • LR = 0.00001
  • EPOCH = 1500
  • path = “D:\Code\Python\Project\GAN\GAN\kagglecatsanddogs_3367a\PetImages\Cat”
  • img_type = ‘jpg’
  • img_size = 64
  • dataset_len = 1000
  • batch_size = 100
  • img_shape = (3, img_size, img_size) # 默认是三维图,即彩图
  • model_mid_size = 1024

在这里插入图片描述
远看的话,还是能看出来这像只猫的。因为,这里生成器模型还是判别式模型都只用了感知机模型来做,还是中间就只加了一层的感知机。并且中间层只有1024(相比于数据本身的3 * 64 * 64 = 12288,因此数据其实会在中间层被压缩10倍,有信息损失很正常)
并且,这里只用了1000张猫做训练集,感觉出到这样的图片还算是幸运的了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章