生成对抗网络的一篇实践文章,使用PyTorch,用很简单的代码搭建了一个GANs,非常通俗易懂。
我们创建了一个生成对抗网络,可以生成显示世界中没有的鸟。
在我们实际创建GAN之前,我们先看看GANs背后的思想。GANs是Ian Goodfellow发明的,他在斯坦福获得了本科和硕士学位,在蒙特利尔大学获得了博士学位。这是深度学习领域的一个新的大事。Yann LeCun说过:
“生成对抗网络是近年来机器学习领域最有趣的想法”
什么是GANs?我们为什么要创造GANs?
神经网络很擅长分类和预测事情,但是AI的研究者想要让神经网络更加像人类,通过创造东西而不仅仅是看见东西。 Ian Goodfellow成功的发明了这样一类深度学习模型,可以用来创造东西。
GANs是怎么工作的?
GANs有两个独立的神经网络。一个叫做“G”,代表了生成器,另外一个叫做“D”,代表了判别器。生成器首先随机的产生图像,判别器通过观察这些图像告诉生成器这些图片有多真实。
让我们考虑一个生成器
在开始的时候,生成器用一个随机噪声信号作为输入,产生一个随机图像作为输出,通过判别器的帮助,开始产生越来越真实的图像。
判别器
判别器是生成器的一个对手,它的输入即有真实的图像,同时也有生成器生成的图像,判别器输出这个图像的真实程度。
到了某个点的时候,判别器无法判断出这个图像是否是真实图像了,这时我们可以发现某个由生成器输出的图像是之前从没有存在过的了。
GANs的应用
- 超分辨率
- 艺术辅助
- 元素抽取
开始写代码 !
注意:下面的代码并不适合深度学习的新手,我希望你有一些python深度学习的经验。
开始我们先导入一些GAN需要的包。首先需要确保PyTorch已安装。
#importing required libraries
from __future__ import print_function
import torch
import torch.nn as nn
import torch.nn.parallel
import torch.optim as optim
import torch.utils.data
import torchvision.datasets as dset
import torchvision.transforms as transforms
import torchvision.utils as vutils
from torch.autograd import Variable
设置一些超参数,batch-size和图像的尺寸:
# Setting hyperparameters
batchSize = 64
imageSize = 64
第一行我们设置了batchsize为64,第二行设置了输出图像的尺寸为64×64。
然后我们创建一个图像的转换器的对象,如下:
# Creating the transformations
transform = transforms.Compose([transforms.Scale(imageSize),transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),])
上面的转化器是将图像作为判别器的输入所必须的。
注意:如果需要获取数据集,点击这里:https://github.com/venkateshtata/GAN_Medium.git>,clone这个仓库,然后替换 “dcgan.py” 文件为你需要写入的python文件, “data” 文件夹存储的是数据集。
现在我们加载数据集。这里我们使用的是 CIFAR-10的数据集。我们批量加载,确保你的python文件和你导入的数据集在同一个文件夹。
# Loading the dataset
dataset = dset.CIFAR10(root = './data', download = True, transform = transform)
dataloader = torch.utils.data.DataLoader(dataset, batch_size = batchSize, shuffle =True, num_workers = 2)
我们将数据集下载后放在./data目录下,应用我们之前定义的转化器。然后使用dataLoader 来获取训练图像。其中‘num_workers’ 表示的是读取数据用的线程的数量,其他的参数可以从字面意思理解。
由于这里我们需要处理两个神经网络,我们会定义一个全局的函数来初始化给定的神经网络,只要将神经网络模型通过参数传给这个函数即可。
def weights_init(m):
classname = m.__class__.__name__
if classname.find('Conv') != -1:
m.weight.data.normal_(0.0, 0.02)
elif classname.find('BatchNorm') != -1:
m.weight.data.normal_(1.0, 0.02)
m.bias.data.fill_(0)
上面的函数获取神经网络的模型作为参数,初始化所有的参数。这个函数在训练开始时在每个迭代都会调用。
第一步就是定义我们的生成器神经网络。我们创建一个生成器的类,里面包含了一系列的层。
class G(nn.Module):
def __init__(self):
super(G, self).__init__()
self.main = nn.Sequential(
nn.ConvTranspose2d(100, 512, 4, 1, 0, bias = False),
nn.BatchNorm2d(512),
nn.ReLU(True),
nn.ConvTranspose2d(512, 256, 4, 2, 1, bias = False),
nn.BatchNorm2d(256),
nn.ReLU(True),
nn.ConvTranspose2d(256, 128, 4, 2, 1, bias = False),
nn.BatchNorm2d(128),
nn.ReLU(True),
nn.ConvTranspose2d(128, 64, 4, 2, 1, bias = False),
nn.BatchNorm2d(64),
nn.ReLU(True),
nn.ConvTranspose2d(64, 3, 4, 2, 1, bias = False),
nn.Tanh()
)
分解上面的代码:
- 我们创建了一个类‘G’,继承了 ‘nn.module’,这个类里有构建模型所需要的各种功能,只要将各种应用和连接放到神经网络里即可。
- 然后我们创建了一个模型,包含了一系列的模块,如卷积,全连接等。
- 这里从图中可以看大,生成器和判别器是相互倒着的。生成器的输入时一个向量,所以这里我们使用了转置卷积 ‘ConvTranspose2d’。
- 然后我们在batch的维度上对所有的特征进行了归一化,然后使用ReLU进行了非线性变换。
- 我们重复上面的操作,输入的节点从100变到了512,特征数从512变到了256,bias保持为False。
- 在最后的 ‘ConvTranspose2d’ 中,我们输出了3个通道,因为输出的是‘RGB’的图像,使用了‘Tanh’作为激活函数。
现在我们创建一个forward函数来进行生成器信号的前向传播。
def forward(self, input):
output = self.main(input)
return output
上面的函数的输入时长度为100的随机向量。返回的是一个生成的图像。随机向量产生随机图像。
创建生成器:
netG = G()
netG.apply(weights_init)
这里我们创建了一个生成器,然后进行了参数初始化。
现在我们再定义一个判别器类:
class D(nn.Module):
def __init__(self):
super(D, self).__init__()
self.main = nn.Sequential(
nn.Conv2d(3, 64, 4, 2, 1, bias = False),
nn.LeakyReLU(0.2, inplace = True),
nn.Conv2d(64, 128, 4, 2, 1, bias = False),
nn.BatchNorm2d(128),
nn.LeakyReLU(0.2, inplace = True),
nn.Conv2d(128, 256, 4, 2, 1, bias = False),
nn.BatchNorm2d(256),
nn.LeakyReLU(0.2, inplace = True),
nn.Conv2d(256, 512, 4, 2, 1, bias = False),
nn.BatchNorm2d(512),
nn.LeakyReLU(0.2, inplace = True),
nn.Conv2d(512, 1, 4, 1, 0, bias = False),
nn.Sigmoid()
)
判别器分解:
- 和G类似,判别器也是继承了‘nn.module’,输入是生成器生成的图像,返回一个0~1之间的数字。
- 由于用生成器的输出作为输入,第一个操作时卷积,我们的激活函数使用了LeakyReLU。
- 可以看到,不同于生成器,我们这里使用了LeakyReLU,这个是经验得来的。
- 我们使用了‘BatchNorm2d’ 来进行特征归一化。
- 最后,我们使用了sigmoid函数,输入0~1之间的概率。
为了进行前向传播,我们定义一个forward函数,使用生成器的输出作为输入:
def forward(self, input):
output = self.main(input)
return output.view(-1)
最后一行,我们的输出值在0~1之间,由于我们需要把向量铺平,确保向量有相同的维度。
创建判别器 :
netD = D()
netD.apply(weights_init)
上面我们创建了判别器,初始化所有的参数:
现在,我们开始训练生成对抗网络。开始之前,我们需要得到一个损失函数,用来评价判别器的损失。我们使用 BCE Loss,非常适合对抗网络。然后生成器和判别器我们都需要一个优化器。
criterion = nn.BCELoss()
optimizerD = optim.Adam(netD.parameters(), lr = 0.0002, betas = (0.5, 0.999))
optimizerG = optim.Adam(netG.parameters(), lr = 0.0002, betas = (0.5, 0.999))
我们创建了一个评价函数用来度量预测和目标之间的差别。我们为判别器和生成器各创建了一个优化器。
我们使用了 ‘Adam’ 优化器,这是个SGD的升级版。
我们训练神经网络25个epochs:
for epoch in range(25):
我们从数据集中循环读取图像 :
for i, data in enumerate(dataloader, 0):
第一步需要更新判别器中的参数,我们把判别器中所有的梯度清零。
netD.zero_grad()
我们知道,判别器需要用真实和虚假的图像同时训练。这里我们先用一个真实图像来训练
real, _ = data
input = Variable(real)
target = Variable(torch.ones(input.size()[0]))
output = netD(input)
errD_real = criterion(output, target)
我们从数据集中获取一个真实图像训练判别器,然后包装成一个变量。然后前向传播,得到预测值,然后计算loss。
现在,使用生成器输出的虚假图像训练判别器:
noise = Variable(torch.randn(input.size()[0], 100, 1, 1))
fake = netG(noise)
target = Variable(torch.zeros(input.size()[0]))
output = netD(fake.detach())
errD_fake = criterion(output, target)
这里,我们先让一个随机向量通过生成器,得到一个虚假的图像。然后将这个虚假图像通过判别器,得到预测,计算损失。
误差反向传播:
errD = errD_real + errD_fake
errD.backward()
optimizerD.step()
这里我们计算判别器总的loss作为判别器的loss,更新判别器的时候,不更新生成器的权值。最后我们通过优化器来判别器更新权值。
下面我们更新生成器的权值:
netG.zero_grad()
target = Variable(torch.ones(input.size()[0]))
output = netD(fake)
errG = criterion(output, target)
errG.backward()
optimizerG.step()
就像之前一样,我们先将所有的梯度清零。然后将loss是通过计算生成器的梯度来反向传播,然后通过生成器的优化器来更新生成器的权值。
现在,我们最后的步骤就是在每100个steps时打印loss,存储真实的图像和生成的图像,可以这么做:
print('[%d/%d][%d/%d] Loss_D: %.4f Loss_G: %.4f' % (epoch, 25, i, len(dataloader),errD.data[0], errG.data[0]))
if i % 100 == 0:
vutils.save_image(real, '%s/real_samples.png' % "./results", normalize = True)
fake = netG(noise)
vutils.save_image(fake.data, '%s/fake_samples_epoch_%03d.png' %("./results", epoch), normalize = True)
完整代码 :
from __future__ import print_function
import torch
import torch.nn as nn
import torch.nn.parallel
import torch.optim as optim
import torch.utils.data
import torchvision.datasets as dset
import torchvision.transforms as transforms
import torchvision.utils as vutils
from torch.autograd import Variable
batchSize = 64
imageSize = 64
transform = transforms.Compose([transforms.Scale(imageSize),transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),]) # We create a list of transformations (scaling, tensor conversion, normalization) to apply to the input images.
dataset = dset.CIFAR10(root = './data', download = True, transform = transform)
dataloader = torch.utils.data.DataLoader(dataset, batch_size = batchSize, shuffle =True, num_workers = 2)
def weights_init(m):
classname = m.__class__.__name__
if classname.find('Conv') != -1:
m.weight.data.normal_(0.0, 0.02)
elif classname.find('BatchNorm') != -1:
m.weight.data.normal_(1.0, 0.02)
m.bias.data.fill_(0)
class G(nn.Module):
def __init__(self):
super(G, self).__init__()
self.main = nn.Sequential(
nn.ConvTranspose2d(100, 512, 4, 1, 0, bias = False),
nn.BatchNorm2d(512),
nn.ReLU(True),
nn.ConvTranspose2d(512, 256, 4, 2, 1, bias = False),
nn.BatchNorm2d(256),
nn.ReLU(True),
nn.ConvTranspose2d(256, 128, 4, 2, 1, bias = False),
nn.BatchNorm2d(128),
nn.ReLU(True),
nn.ConvTranspose2d(128, 64, 4, 2, 1, bias = False),
nn.BatchNorm2d(64),
nn.ReLU(True),
nn.ConvTranspose2d(64, 3, 4, 2, 1, bias = False),
nn.Tanh()
)
def forward(self, input):
output = self.main(input)
return output
netG = G()
netG.apply(weights_init)
class D(nn.Module):
def __init__(self):
super(D, self).__init__()
self.main = nn.Sequential(
nn.Conv2d(3, 64, 4, 2, 1, bias = False),
nn.LeakyReLU(0.2, inplace = True),
nn.Conv2d(64, 128, 4, 2, 1, bias = False),
nn.BatchNorm2d(128),
nn.LeakyReLU(0.2, inplace = True),
nn.Conv2d(128, 256, 4, 2, 1, bias = False),
nn.BatchNorm2d(256),
nn.LeakyReLU(0.2, inplace = True),
nn.Conv2d(256, 512, 4, 2, 1, bias = False),
nn.BatchNorm2d(512),
nn.LeakyReLU(0.2, inplace = True),
nn.Conv2d(512, 1, 4, 1, 0, bias = False),
nn.Sigmoid()
)
def forward(self, input):
output = self.main(input)
return output.view(-1)
netD = D()
netD.apply(weights_init)
criterion = nn.BCELoss()
optimizerD = optim.Adam(netD.parameters(), lr = 0.0002, betas = (0.5, 0.999))
optimizerG = optim.Adam(netG.parameters(), lr = 0.0002, betas = (0.5, 0.999))
for epoch in range(25):
for i, data in enumerate(dataloader, 0):
netD.zero_grad()
real, _ = data
input = Variable(real)
target = Variable(torch.ones(input.size()[0]))
output = netD(input)
errD_real = criterion(output, target)
noise = Variable(torch.randn(input.size()[0], 100, 1, 1))
fake = netG(noise)
target = Variable(torch.zeros(input.size()[0]))
output = netD(fake.detach())
errD_fake = criterion(output, target)
errD = errD_real + errD_fake
errD.backward()
optimizerD.step()
netG.zero_grad()
target = Variable(torch.ones(input.size()[0]))
output = netD(fake)
errG = criterion(output, target)
errG.backward()
optimizerG.step()
print('[%d/%d][%d/%d] Loss_D: %.4f Loss_G: %.4f' % (epoch, 25, i, len(dataloader),errD.data[0], errG.data[0]))
if i % 100 == 0:
vutils.save_image(real, '%s/real_samples.png' % "./results", normalize = True)
fake = netG(noise)
vutils.save_image(fake.data, '%s/fake_samples_epoch_%03d.png' %("./results", epoch), normalize = True)
你可以从我的GitHub仓库看到代码:
https://github.com/venkateshtata/GAN_Medium
作者:Venkatesh Tata编译:ronghuaiyang
本文为专栏文章,来自:AI公园,内容观点不代表本站立场,如若转载请联系专栏作者,本文链接:https://www.afenxi.com/66243.html 。