系列文章目录
李宏毅作业九 Anomaly Detection异常检测
李宏毅作业八unsupervised无监督聚类学习
李宏毅作业七其三 Network Compression (Network Pruning)
李宏毅作业七其二 Network Compression (Knowledge Distillation)
李宏毅作业七其一 Network Compression (Architecuture Design)
李宏毅作业六 Adversarial Attack对抗攻击
Generative Adversarial Network 生成对抗网络
前言
本篇以代码为主,不过多涉及理论。
平台colab,语言python
一、生成对抗网络
1.生成对抗网络是什么
生成对抗网络中包含了两个模型,一个是生成模型G,另一个是判别模型D,下面通过一个生成图片的例子来解释两个模型的作用:
生成模型G:不断学习训练集中真实数据的概率分布,目标是将输入的随机噪声转化为可以以假乱真的图片(生成的图片与训练集中的图片越相似越好)
判别模型D:判断一个图片是否是真实的图片,目标是将生成模型G产生的“假”图片与训练集中的“真”图片分辨开。
GANs的实现方法是让D和G进行博弈,训练过程中通过相互竞争让这两个模型同时得到增强。由于判别模型D的存在,使得 G 在没有大量先验知识以及先验分布的前提下也能很好的去学习逼近真实数据,并最终让模型生成的数据达到以假乱真的效果
2.数学公式

判别模型D和生成模型G均采用多层感知机。GANs定义了一个噪声pz(x) 作为先验,用于学习生成模型G 在训练数据x上的概率分布pg,G(z)表示将输入的噪声z映射成数据(例如生成图片)。D(x)代表x 来自于真实数据分布pdata而不是pg的概率。
理论解释来源
二、代码
1.下载数据
除了下载数据集外,根据自身需要,来下载python套件。
""" Uncomment these lines to mount your own gdrive. """
# from google.colab import drive
# drive.mount('/content/drive')
""" You can replace the workspace directory with your gdrive if you want. """
workspace_dir = '.'
# workspace_dir = './drive/My Drive/Machine Learning/hw11 - GAN/colab_tmp'
""" Download the dataset. """
!gdown --id 1IGrTr308mGAaCKotpkkm8wTKlWs9Jq-p --output "{workspace_dir}/crypko_data.zip"
- 解压文件
!unzip -q "{workspace_dir}/crypko_data.zip" -d "{workspace_dir}/"
2.数据预处理
- 使用torchvision套件存图,使用cv2读取,并将cv2读取的图片(BGR)转换成torchvision格式(RGB)。
- 将图片输入大小调整为(64,64),方便实验,并将value(0-1)线性转换成(-1~1)。
- 代码里我注释的很清楚
from torch.utils.data import Dataset, DataLoader
import cv2
import os
class FaceDataset(Dataset):
#定义类似私有变量的
def __init__(self, fnames, transform):
self.transform = transform
self.fnames = fnames
self.num_samples = len(self.fnames)
def __getitem__(self,idx):
fname = self.fnames[idx]#图片索引赋给fname
img = cv2.imread(fname)#用cv2读取图片
img = self.BGR2RGB(img) #because "torchvision.utils.save_image" use RGB
img = self.transform(img)#转换格式,调用下方的transform
return img
def __len__(self):
return self.num_samples
def BGR2RGB(self,img):
return cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
import glob
import torchvision.transforms as transforms
def get_dataset(root):
fnames = glob.glob(os.path.join(root, '*'))
# resize the image to (64, 64)
# linearly map [0, 1] to [-1, 1]线性转换
#transform这个我前面的作业解释的很清楚,
#这就不再重复解释了,看过一遍就会觉得很简单
transform = transforms.Compose(
[transforms.ToPILImage(),
transforms.Resize((64, 64)),
transforms.ToTensor(),
transforms.Normalize(mean=[0.5] * 3, std=[0.5] * 3) ] )
dataset = FaceDataset(fnames, transform)
return dataset
3.随机种子
- 使用random seed函数,方便我们固定随机值,方便多次reproduce
import random
import torch
import numpy as np
#随机种子
def same_seeds(seed):
torch.manual_seed(seed)
if torch.cuda.is_available():
torch.cuda.manual_seed(seed)
torch.cuda.manual_seed_all(seed) # if you are using multi-GPU.
np.random.seed(seed) # Numpy module.
random.seed(seed) # Python random module.
torch.backends.cudnn.benchmark = False
torch.backends.cudnn.deterministic = True
4.模型

这里 DCGAN 作为 baseline mode。 DCGAN 架构示意图,图中数字仅供参考。
- 这里大致解释了如何在特征Z的基础上生成图片
- 实质上就是逆卷积和上采样的过程,在上采样的过程中,会用到一些函数,弥补图像在下采样后的细节损失。
import torch.nn as nn
import torch.nn.functional as F
def weights_init(m):
classname = m.__class__.__name__
if classname.find('Conv') != -1:#find() 返回字符串第一次出现的索引,如果没有匹配项则返回-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)#偏置填充0
#生成器模型
class Generator(nn.Module):
"""
input (N, in_dim)
output (N, 3, 64, 64)
"""
def __init__(self, in_dim, dim=64):
super(Generator, self).__init__()
#定义解码层的架构,后面会用到
#逆卷积神经网络ConvTranspose2d是对图片进行上采样,图片越来越大,卷积与逆卷积是互相对应的。
#其实就是还原图像
def dconv_bn_relu(in_dim, out_dim):
return nn.Sequential(
nn.ConvTranspose2d(in_dim, out_dim, 5, 2,
padding=2, output_padding=1, bias=False),
nn.BatchNorm2d(out_dim),
nn.ReLU())
self.l1 = nn.Sequential(
nn.Linear(in_dim, dim * 8 * 4 * 4, bias=False),
nn.BatchNorm1d(dim * 8 * 4 * 4),
nn.ReLU())
self.l2_5 = nn.Sequential(
dconv_bn_relu(dim * 8, dim * 4),
dconv_bn_relu(dim * 4, dim * 2),
dconv_bn_relu(dim * 2, dim),
nn.ConvTranspose2d(dim, 3, 5, 2, padding=2, output_padding=1),
nn.Tanh())
#dataframe.apply(func,axis=0)
#表示: 默认(默认axis=0)的情况下将dataframe表中的每一列的每一个元素分别作为实参传入函数func, 然后得出结果返回;
self.apply(weights_init)
def forward(self, x):
y = self.l1(x)
y = y.view(y.size(0), -1, 4, 4)
y = self.l2_5(y)
return y
#判别器模型
class Discriminator(nn.Module):
"""
input (N, 3, 64, 64)
output (N, )
"""
def __init__(self, in_dim, dim=64):
super(Discriminator, self).__init__()
#这里就是正常的编码器
def conv_bn_lrelu(in_dim, out_dim):
return nn.Sequential(
nn.Conv2d(in_dim, out_dim, 5, 2, 2),
nn.BatchNorm2d(out_dim),
nn.LeakyReLU(0.2))
self.ls = nn.Sequential(
nn.Conv2d(in_dim, dim, 5, 2, 2), nn.LeakyReLU(0.2),
conv_bn_lrelu(dim, dim * 2),
conv_bn_lrelu(dim * 2, dim * 4),
conv_bn_lrelu(dim * 4, dim * 8),
nn.Conv2d(dim * 8, 1, 4),
nn.Sigmoid())
self.apply(weights_init)
def forward(self, x):
y = self.ls(x)
y = y.view(-1)
return y
5.准备训练
- 设定超参数。准备数据加载,模型,损失标准,优化设定
hyperparameters, dataloader, model, loss criterion, optimizer。
import torch
from torch import optim
from torch.autograd import Variable
import torchvision
# hyperparameters超参数
batch_size = 64
z_dim = 120#特征维度
lr = 5e-5
n_epoch = 12
save_dir = os.path.join(workspace_dir, 'logs')
#os.path.join语法: os.path.join(path1[,path2[,……]])
#返回值:将多个路径组合后返回
os.makedirs(save_dir, exist_ok=True)#os.makedirs(path),
#他可以一次创建多级目录,哪怕中间目录不存在也能正常的(替你)创建
# model
G = Generator(in_dim=z_dim).cuda()
D = Discriminator(3).cuda()
G.train()
D.train()
# loss criterion
#BCELoss 是CrossEntropyLoss的一个特例,只用于二分类问题,
criterion = nn.BCELoss()
# optimizer
opt_D = torch.optim.Adam(D.parameters(), lr=lr, betas=(0.5, 0.999))
opt_G = torch.optim.Adam(G.parameters(), lr=lr, betas=(0.5, 0.999))
same_seeds(0)
# dataloader (You might need to edit the dataset path if you use extra dataset.)
#这里可以加载自己想加载的数据
dataset = get_dataset(os.path.join(workspace_dir, 'faces'))
dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=True, num_workers=4)
- 查看一张
# 随便选一张查看
import matplotlib.pyplot as plt
plt.imshow(dataset[10].numpy().transpose(1,2,0))
6.训练开始
# for logging
z_sample = Variable(torch.randn(100, z_dim)).cuda()
for e, epoch in enumerate(range(n_epoch)):#遍历
for i, data in enumerate(dataloader):
imgs = data
imgs = imgs.cuda()#图片格式改为适合GPU处理的张量
bs = imgs.size(0)#batch.size大小是图片0维的数值
""" Train D 训练判别器"""
z = Variable(torch.randn(bs, z_dim)).cuda()
r_imgs = Variable(imgs).cuda()
f_imgs = G(z)#生成器通过特征Z生成的图片
#判别器对原始图像和生成的图片进行判别
# label
r_label = torch.ones((bs)).cuda()
f_label = torch.zeros((bs)).cuda()
# dis
r_logit = D(r_imgs.detach())
f_logit = D(f_imgs.detach())
# compute loss
r_loss = criterion(r_logit, r_label)
f_loss = criterion(f_logit, f_label)
loss_D = (r_loss + f_loss) / 2
# update model
D.zero_grad()
loss_D.backward()
opt_D.step()
""" train G """
# leaf
z = Variable(torch.randn(bs, z_dim)).cuda()
f_imgs = G(z)
# dis
f_logit = D(f_imgs)
# compute loss
loss_G = criterion(f_logit, r_label)
# update model
G.zero_grad()
loss_G.backward()
opt_G.step()
# log
print(f'\rEpoch [{epoch+1}/{n_epoch}] {i+1}/{len(dataloader)} Loss_D: {loss_D.item():.4f} Loss_G: {loss_G.item():.4f}', end='')
G.eval()
f_imgs_sample = (G(z_sample).data + 1) / 2.0
filename = os.path.join(save_dir, f'Epoch_{epoch+1:03d}.jpg')
torchvision.utils.save_image(f_imgs_sample, filename, nrow=10)
print(f' | Save some samples to {filename}.')
# show generated image图片的可视化
grid_img = torchvision.utils.make_grid(f_imgs_sample.cpu(), nrow=10)
plt.figure(figsize=(10,10))
plt.imshow(grid_img.permute(1, 2, 0))
plt.show()
G.train()
if (e+1) % 5 == 0:
torch.save(G.state_dict(), os.path.join(workspace_dir, f'dcgan_g.pth'))
torch.save(D.state_dict(), os.path.join(workspace_dir, f'dcgan_d.pth'))
7.使用生成器生成图片
import torch
# load pretrained model加载预训练模型
G = Generator(z_dim)
G.load_state_dict(torch.load(os.path.join(workspace_dir, 'dcgan_g.pth')))
G.eval()
G.cuda()
# generate images and save the result
n_output = 20
z_sample = Variable(torch.randn(n_output, z_dim)).cuda()
imgs_sample = (G(z_sample).data + 1) / 2.0
save_dir = os.path.join(workspace_dir, 'logs')
filename = os.path.join(save_dir, f'result.jpg')
torchvision.utils.save_image(imgs_sample, filename, nrow=10)
# show image
grid_img = torchvision.utils.make_grid(imgs_sample.cpu(), nrow=10)
plt.figure(figsize=(10,10))
plt.imshow(grid_img.permute(1, 2, 0))
plt.show()
总结
这里的对抗生成网络还是简单一些的,代码还是很容易就看懂的,不过要想实现它,还是需要自己在多敲几下。看懂不代表会用。

本文以代码实战为主,介绍生成对抗网络(GANs)的基本原理与实现步骤。通过使用Python及PyTorch框架,从数据准备到模型训练,逐步解析GANs的工作流程。
528

被折叠的 条评论
为什么被折叠?



