DCGAN其主要贡献在于把原始GAN中的全连接层替换为了卷积层。具体如下:
DCGAN的生成器结构可以用如下的图来表示:
DCGAN的判别器和生成器的结构基本相反,其主要是通过进行卷积降维从而把输入的图像生成为一个标量,从而使用Sigmoid激活层确认其概率。
# 使用pytorch在ununtu20上使用的代码
# gpu:Nvidia RTX2070s 8g显存
import os,math,torch,torchvision
import numpy as np
import torchvision.transforms as transforms
from torchvision.utils import save_image
from torch.utils.data import DataLoader
import torch.nn as nn
import random
from torch.utils.data import Dataset
random.seed(666)
torch.manual_seed(666)
from torch.autograd import Variable
import torch.nn.functional as F
os.makedirs('myImages', exist_ok=True)
#下面是一些初始化数据的定义
n_epochs=2
batch_size=512
lr=0.0002
b1=0.5
b2=0.999
n_cpu=8
latent_dim=100
img_size=64
channels=3
sample_interval=400
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
# dataset = torchvision.datasets.MNIST(root='../../data/mnist',download=True,
# transform=transforms.Compose([transforms.Resize(size=img_size),
# transforms.ToTensor(),
# transforms.Normalize([0.5]*3,[0.5]*3)]
# )
# )
# dataloader = DataLoader(dataset=dataset,batch_size=batch_size,shuffle=False,num_workers=n_cpu)
import PIL.Image as Image
class CeleBaDataset(torch.utils.data.Dataset):
def __init__(self,img_root:str,transform=None):
super(CeleBaDataset,self).__init__()
temp_list=list()
for s in os.listdir(path=img_root):
if s.find('.png'):
temp_list.append(os.path.join(img_root,s))
self.datalist = temp_list
self.transform = transform
def __len__(self):
return len(self.datalist)
def __getitem__(self,idx):
image = Image.open(self.datalist[idx])
if self.transform:
image = self.transform(image)
return image
dataloader = DataLoader(dataset=CeleBaDataset(img_root='/home/hx/Desktop/WorkDisk/DadaSets/CelebA/Img/img_align_celeba_png.7z/img_align_celeba_png/'
,transform=transforms.Compose([transforms.Resize(size=img_size),
transforms.Resize(64),
transforms.CenterCrop(64),
transforms.ToTensor(),
transforms.Normalize([0.5]*3,[0.5]*3),
])
),
batch_size=batch_size,
num_workers=n_cpu,
shuffle=False,
pin_memory=True)
#%%
def weight_init(modules:torch.nn.Module):
for m in modules.modules():
if isinstance(m,nn.ConvTranspose2d):
nn.init.normal_(m.weight.data,0,0.02)
elif isinstance(m,nn.BatchNorm2d):
nn.init.normal_(m.weight.data,0,0.02)
def weight_init_apply(m:object):
if m.__class__.__name__.find('Conv'):
nn.init.normal_(m.weight.data,0,0.02)
elif m.__class__.__name__.find('BatchNorm'):
nn.init.normal_(m.weight.data,0,0.02)
class Generator(nn.Module):
def __init__(self):
super(Generator,self).__init__()
in_channels=[latent_dim,512,256,128,64]
out_channels=[512,256,128,64,3]
paddings=[0,1,1,1,1]
strides=[1,2,2,2,2]
layers=[]
for i in range(5):
layers.append(nn.BatchNorm2d(num_features=in_channels[i]))
layers.append(nn.ConvTranspose2d(in_channels=in_channels[i],
out_channels=out_channels[i],
kernel_size=4,
stride=strides[i],
padding=paddings[i]))
if i != 4:
layers.append(nn.LeakyReLU(negative_slope=0.2,inplace=True))
else:
layers.append(nn.Tanh())
self.G=nn.Sequential(*layers)
def forward(self,x):
return self.G(x)
class Discriminator(nn.Module):
def __init__(self):
super(Discriminator,self).__init__()
layers=[]
def block(in_channels,out_channels,stride=2,padding=1,if_bn=True,if_relu=True):
if if_bn:
layers.append(nn.BatchNorm2d(in_channels))
layers.append(nn.Conv2d(in_channels=in_channels,out_channels=out_channels,kernel_size=4,stride=stride,padding=padding))
if if_relu:
layers.append(nn.LeakyReLU(negative_slope=0.2,inplace=True))
else:
layers.append(nn.Sigmoid())
block(3,64,stride=2,padding=1,if_bn=False) # 此时64*32*32
block(64,128,2,1) # 此时128*16*16
block(128,256,2,1) # 此时256*8*8
block(256,512,2,1) # 此时512*4*4
block(512,1,1,0,if_relu=False) # 此时1*1*1
self.D=nn.Sequential(*layers)
def forward(self,x):
return self.D(x)
#%%
generator = Generator()
weight_init(generator)
discriminator=Discriminator()
weight_init(discriminator)
loss_fn = torch.nn.BCELoss()
generator.to(device)
discriminator.to(device)
loss_fn.to(device)
opm_G = torch.optim.Adam(generator.parameters(),lr=lr,betas=(b1,b2))
opm_D = torch.optim.Adam(discriminator.parameters(),lr=lr,betas=(b1,b2))
#%%
data = CeleBaDataset(img_root='/home/hx/Desktop/WorkDisk/DadaSets/CelebA/Img/img_align_celeba_png.7z/img_align_celeba_png/')
data.__getitem__(10000)
#%%
for epoch in range(20):
for i,img in enumerate(dataloader):
img = img.to(device)
real = torch.ones((img.shape[0],1),device=device)
fake = torch.zeros((img.shape[0],1),device=device)
z = torch.randn(size=(img.shape[0],latent_dim,1,1),device=device)
opm_D.zero_grad()
real_loss = loss_fn(discriminator(img).view(img.shape[0],-1),real)
fake_loss = loss_fn(discriminator(generator(z).detach())view(img.shape[0],-1),fake)
d_loss = (real_loss+fake_loss)/2
d_loss.backward()
opm_D.step()
print('Dloss:',d_loss)
opm_G.zero_grad()
z = torch.randn(size=(img.shape[0],latent_dim,1,1),device=device)
g_loss = loss_fn(discriminator(generator(z)).view(img.shape[0],-1),fake)
g_loss.backward()
opm_G.step()
print('Gloss:',g_loss)
print('epoch:{}Dloss:{}Gloss:{}',epoch,d_loss,g_loss)
有时候简单的想法却有着一些很有用的效果,比方说就是CGAN,其思想很简单的来说就是,在原本的 n o i s e noise noise输入中增加一些维度(一般来说这个维度都是使用了one-hot向量来表示,比方说MINIST有10个不同的向量,那么这个需要增加的维度就是10个维度,其中使用one-hot表达,即:每一个维度对应一个变量,如果一个为1那么其他就为0),这个维度用来表示你想要输出的属性,并在最后的损失函数中增加关于生成的是否按照要求生成的一个损失,这就是CGAN的基本原理。
关于损失函数,原始GAN的损失函数可以用如下的公式进行表示:
E x ∼ P r e a l ( x ) l o g ( D ( x ) ) + E z ∼ P z ( z ) l o g ( 1 − D ( G ( z ) ) ) E_{x\sim P_{real}(x)}log(D(x))+E_{z\sim P_{z(z)}}log(1-D(G(z)))\\ Ex∼Preal(x)log(D(x))+Ez∼Pz(z)log(1−D(G(z)))
CGAN的损失函数被修改了为如下:
E x ∼ P r e a l ( x ) l o g ( D ( x ∣ y ) ) + E z ∼ P z ( z ) l o g ( 1 − D ( G ( z ∣ y ) ) ) E_{x\sim P_{real}(x)}log(D(x|y))+E_{z\sim P_{z(z)}}log(1-D(G(z|y)))\\ Ex∼Preal(x)log(D(x∣y))+Ez∼Pz(z)log(1−D(G(z∣y)))
其基本思路为:我是一个带标签的东西,生成器带着这个标签生成了数据,并且判别器也带着这个标签来判别数据,并且这个数据的标签就是dataloader中取出来的标签,如果生成的照片和这个照片的风格不一致,那么判别器就可以顺着这个标签带来的梯度就会比较大,那么这个判别器就能够很轻易的判断这个图片是一张假的图片,那么就会导致生成器的误差,其梯度也会顺着这个方向来进行判断,那样的话生成器就可以通过调整这个误差来更好的生成图片,为什么用one-hot来判断的方式是因为每一个种类有着自己单独的一个维度,这样的话梯度可以更好的顺利沿着过来,从而能够更方便的找到这个维度的梯度方向而不用拟合一个一个变量带着好几个含义的输入(这样的拟合对于神经网络的层数有着更好的要求),one-hot向量的优势就是在一起。`