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張貓做訓練集,感覺出到這樣的圖片還算是幸運的了。

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