填坑,打算认真做下关于GAN论文的研究,并做实现。
http://papers.nips.cc/paper/5423-generative-adversarial-nets.pdf
生成模型需要参与到一场“辩论”当中。简单来说,就是多出一个判别模型,判别模型的作用是判断一个输入的数据是来自于生成模型生成的fake data 还是来自于真实数据。
这篇论文对于两个模型的结构设定:
因此,很自然的是,训练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 ) = a l o g x + b l o g ( 1 − x ) f(x) = a log x+ b log(1-x) f(x)=alogx+blog(1−x)在a,b不都为0的情况下,在定义域[0,1],求导之后很简单得到极值在 a a + b \frac{a}{a+b} a+ba上取最大值。
值得注意的是,值得注意的是,考虑到原来的积分部分,可以看到,概率密度为0的部分 ,在积分中其实不是不需要考虑的。故, 符合上面的函数要求。即,命题一得证。
当最大化D之后,构造新的函数(主要是为了表示方便而已):
因此全局最优解,现在变成了C(G)函数的最小值。
定理 最优值,当且仅当 p g = p d a t a p_g = p_{data} pg=pdata时,取到。并且这个时候的最小值为 − l o g 4 -log4 −log4。
这个证明也很简单:
在C(G)第三行的表示的两个期望中,分母部分都除以2。此外再一个2。毫无意外,就多出了下面这两个项。
剩余的部分,可以表示为两个KL散度的求和。
而这个部分,其实就是一个JS散度。
但是JS散度,当且仅当 p d a t a = p g p_{data} = p_g pdata=pg 取到最小值0,即得证。
工具:pytorch
因为GAN的实力还不够强,因此这里直接用简单的图片数据。
关于MNIST数据集的之前已经有做过了 基于MNIST的GANs实现【Pytorch】
这里尝试用下kaggle上的猫狗数据集,下载方式:https://www.microsoft.com/en-us/download/details.aspx?id=54765
下载之后,再解压一下就好了。
里面有12500张猫的照片,也有12500张狗的照片。(多出来的那个数据不是图片来的)
.gpu()
去掉就好了(当然对应的.cpu()
部分也是需要去掉的)。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))
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()
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')
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])
远看的话,还是能看出来这像只猫的。因为,这里生成器模型还是判别式模型都只用了感知机模型来做,还是中间就只加了一层的感知机。并且中间层只有1024(相比于数据本身的3 * 64 * 64 = 12288,因此数据其实会在中间层被压缩10倍,有信息损失很正常)
并且,这里只用了1000张猫做训练集,感觉出到这样的图片还算是幸运的了。