>- ** 本文为[365天深度学习训练营]中的学习记录博客**
>- ** 原作者:[K同学啊]**
本人往期文章可查阅: 深度学习总结
我的环境:
论文地址:Conditional Image Synthesis with Auxiliary Classifier GANs
ACGAN的全称是 Auxiliary Classifier Generative Adversarial Network ,翻译成汉语的意思就是带辅助分类器的GAN,ACGAN的原理与GAN(CGAN)相似。对于CGAN和ACGAN,生成器输入均为潜在矢量及其标签,输出是属于输入类标签的伪造图像。
对于CGAN,判别器的输入是图像(包含假的或真实的图像)及其标签,输出是图像属于真实图像的概率。对于ACGAN,判别器的输入是一副图像,而输出是该图像属于真实图像的概率以及其类别。
图1是ACGAN的网络结构,可以发现该结构其实和之前的CGAN和SGAN都非常接近,可以说是两者的结合体。但这样的修改可以有效生成高质量的生成结果,并且使得训练更加稳定。
本质上,在CGAN中,向网络提供了标签。在ACGAN中,使用辅助解码器网络重建辅助信息。ACGAN理论认为,强制网络执行其他任务可以提高原始任务的性能。在这种情况下,辅助任务是图像分类。原始任务是生成伪造图像。
项目代码:详见页面顶端,可直接下载
此段代码与.py文件中的稍有区别,Jupyter Notebook 并不提供一个真正的命令行环境,因此 argparse
会尝试解析 Jupyter Notebook 启动时传递给内核的参数,而不是你期望的自定义参数,故使用 sys.argv 模拟命令行参数
import argparse,os,math
import numpy as np
import torchvision.transforms as transforms
from torchvision.utils import save_image
from torch.utils.data import DataLoader
from torchvision import datasets
from torch.autograd import Variable
import torch
import torch.nn as nn
import torch.nn.functional as F
# 创建用于存储生成图像的目录
os.makedirs("images",exist_ok=True)
# 模拟命令行参数
import sys
sys.argv=[
'script.py', # 脚本名称
'--n_epochs', '200',
'--batch_size', '64',
'--lr', '0.0002',
'--b1', '0.5',
'--b2', '0.999',
'--n_cpu', '8',
'--latent_dim', '100',
'--n_classes', '10',
'--img_size', '32',
'--channels', '1',
'--sample_interval', '400'
]
# 解析命令行参数
parser=argparse.ArgumentParser()
parser.add_argument("--n_epochs",type=int,default=200,help="训练的总轮数")
parser.add_argument("--batch_size",type=int,default=64,help="每个批次的大小")
parser.add_argument("--lr",type=float,default=0.0002,help="Adam优化器的学习率")
parser.add_argument("--b1",type=float,default=0.5,help="Adam优化器的一阶动量衰减")
parser.add_argument("--b2",type=float,default=0.999,help="Adam优化器的二阶动量衰减")
parser.add_argument("--n_cpu",type=int,default=8,help="用于批次生成的CPU线程数")
parser.add_argument("--latent_dim",type=int,default=100,help="潜在空间的维度")
parser.add_argument("--n_classes",type=int,default=10,help="数据集的类别数")
parser.add_argument("--img_size",type=int,default=32,help="每个图像的尺寸")
parser.add_argument("--channels",type=int,default=1,help="图像通道数")
parser.add_argument("--sample_interval",type=int,default=400,help="图像采样间隔")
opt=parser.parse_args()
print(opt)
# 检查是否支持GPU加速
cuda=True if torch.cuda.is_available() else False
运行结果:
Namespace(n_epochs=200, batch_size=64, lr=0.0002, b1=0.5, b2=0.999, n_cpu=8, latent_dim=100, n_classes=10, img_size=32, channels=1, sample_interval=400)
# 初始化神经网络权重的函数
def weights_init_normal(m):
classname=m.__class__.__name__
if classname.find("Conv")!=-1:
torch.nn.init.normal_(m.weight.data,0.0,0.02)
elif classname.find("BatchNorm2d")!=-1:
torch.nn.init.normal_(m.weight.data,1.0,0.02)
torch.nn.init.constant_(m.bias.data,0.0)
# 生成器网络类
class Generator(nn.Module):
def __init__(self):
super(Generator,self).__init__()
# 为类别标签创建嵌入层
self.label_emb=nn.Embedding(opt.n_classes,opt.latent_dim)
# 计算上采样前的初始大小
self.init_size=opt.img_size//4 # Initial size before upsampling
# 第一层线性层
self.l1=nn.Sequential(nn.Linear(opt.latent_dim,128*self.init_size**2))
# 卷积层块
self.conv_blocks=nn.Sequential(
nn.BatchNorm2d(128),
nn.Upsample(scale_factor=2),
nn.Conv2d(128,128,3,stride=1,padding=1),
nn.BatchNorm2d(128,0.8),
nn.LeakyReLU(0.2,inplace=True),
nn.Upsample(scale_factor=2),
nn.Conv2d(128,64,3,stride=1,padding=1),
nn.BatchNorm2d(64,0.8),
nn.LeakyReLU(0.2,inplace=True),
nn.Conv2d(64,opt.channels,3,stride=1,padding=1),
nn.Tanh(),
)
def forward(self,noise,labels):
# 将标签嵌入到噪声中
gen_input=torch.mul(self.label_emb(labels),noise)
# 通过第一层线性层
out=self.l1(gen_input)
# 重新整形为合适的形状
out=out.view(out.shape[0],128,self.init_size,self.init_size)
# 通过卷积层块生成图像
img=self.conv_blocks(out)
return img
# 判别器网络类
class Discriminator(nn.Module):
def __init__(self):
super(Discriminator,self).__init__()
# 定义判别器块的函数
def discriminator_block(in_filters,out_filters,bn=True):
"""返回每个判别器块的层"""
block=[nn.Conv2d(in_filters,out_filters,3,2,1),nn.LeakyReLU(0.2,inplace=True),nn.Dropout2d(0.25)]
if bn:
block.append(nn.BatchNorm2d(out_filters,0.8))
return block
# 判别器的卷积层块
self.conv_blocks=nn.Sequential(
*discriminator_block(opt.channels,16,bn=False),
*discriminator_block(16,32),
*discriminator_block(32,64),
*discriminator_block(64,128),
)
# 下采样后图像的高度和宽度
ds_size=opt.img_size//2**4
# 输出层
self.adv_layer=nn.Sequential(nn.Linear(128*ds_size**2,1),nn.Sigmoid())
self.aux_layer=nn.Sequential(nn.Linear(128*ds_size**2,opt.n_classes),nn.Softmax())
def forward(self,img):
out=self.conv_blocks(img)
out=out.view(out.shape[0],-1)
validity=self.adv_layer(out)
label=self.aux_layer(out)
return validity,label
# 损失函数
adversarial_loss=torch.nn.BCELoss()
auxiliary_loss=torch.nn.CrossEntropyLoss()
# 初始化生成器和判别器
generator=Generator()
discriminator=Discriminator()
if cuda:
generator.cuda()
discriminator.cuda()
adversarial_loss.cuda()
auxiliary_loss.cuda()
# 初始化权重
generator.apply(weights_init_normal)
discriminator.apply(weights_init_normal)
运行结果:
Discriminator(
(conv_blocks): Sequential(
(0): Conv2d(1, 16, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1))
(1): LeakyReLU(negative_slope=0.2, inplace=True)
(2): Dropout2d(p=0.25, inplace=False)
(3): Conv2d(16, 32, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1))
(4): LeakyReLU(negative_slope=0.2, inplace=True)
(5): Dropout2d(p=0.25, inplace=False)
(6): BatchNorm2d(32, eps=0.8, momentum=0.1, affine=True, track_running_stats=True)
(7): Conv2d(32, 64, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1))
(8): LeakyReLU(negative_slope=0.2, inplace=True)
(9): Dropout2d(p=0.25, inplace=False)
(10): BatchNorm2d(64, eps=0.8, momentum=0.1, affine=True, track_running_stats=True)
(11): Conv2d(64, 128, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1))
(12): LeakyReLU(negative_slope=0.2, inplace=True)
(13): Dropout2d(p=0.25, inplace=False)
(14): BatchNorm2d(128, eps=0.8, momentum=0.1, affine=True, track_running_stats=True)
)
(adv_layer): Sequential(
(0): Linear(in_features=512, out_features=1, bias=True)
(1): Sigmoid()
)
(aux_layer): Sequential(
(0): Linear(in_features=512, out_features=10, bias=True)
(1): Softmax(dim=None)
)
)
# 配置数据加载器
os.makedirs(r"E:\DATABASE\G-series\acgan\data",exist_ok=True)
dataloader=torch.utils.data.DataLoader(
datasets.MNIST(
r"E:\DATABASE\G-series\acgan\data",
train=True,
download=False,
transform=transforms.Compose(
[transforms.Resize(opt.img_size),transforms.ToTensor(),transforms.Normalize([0.5],[0.5])]
),
),
batch_size=opt.batch_size,
shuffle=True,
)
# 优化器
optimizer_G=torch.optim.Adam(generator.parameters(),lr=opt.lr,betas=(opt.b1,opt.b2))
optimizer_D=torch.optim.Adam(discriminator.parameters(),lr=opt.lr,betas=(opt.b1,opt.b2))
FloatTensor=torch.cuda.FloatTensor if cuda else torch.FloatTensor
LongTensor=torch.cuda.LongTensor if cuda else torch.LongTensor
# 保存生成图像的函数
def sample_image(n_row,batches_done):
"""保存从0到n_classes的生成数字的图像网络"""
# 采样噪声
z=Variable(FloatTensor(np.random.normal(0,1,(n_row**2,opt.latent_dim))))
# 为n行生成标签从0到n_classes
labels=np.array([num for _ in range(n_row) for num in range(n_row)])
labels=Variable(LongTensor(labels))
gen_imgs=generator(z,labels)
save_image(gen_imgs.data,"images/%d.png"%batches_done,nrow=n_row,normalize=True)
# --------
# 训练
# --------
for epoch in range(opt.n_epochs):
for i,(imgs,labels) in enumerate(dataloader):
batch_size=imgs.shape[0]
# 真实数据的标签
valid=Variable(FloatTensor(batch_size,1).fill_(1.0),requires_grad=False)
# 生成数据的标签
fake=Variable(FloatTensor(batch_size,1).fill_(0.0),requires_grad=False)
# 配置输入
real_imgs=Variable(imgs.type(FloatTensor))
labels=Variable(labels.type(LongTensor))
# --------------
# 训练生成器
# --------------
optimizer_G.zero_grad()
# 采样噪声和标签作为生成器的输入
z=Variable(FloatTensor(np.random.normal(0,1,(batch_size,opt.latent_dim))))
gen_labels=Variable(LongTensor(np.random.randint(0,opt.n_classes,batch_size)))
# 生成一批图像
gen_imgs=generator(z,gen_labels)
# 损失度量生成器的欺骗判别器的能力
validity,pred_label=discriminator(gen_imgs)
g_loss=0.5*(adversarial_loss(validity,valid)+auxiliary_loss(pred_label,gen_labels))
g_loss.backward()
optimizer_G.step()
# --------------
# 训练判别器
# ---------------
optimizer_D.zero_grad()
# 真实图像的损失
real_pred,real_aux=discriminator(real_imgs)
d_real_loss=(adversarial_loss(real_pred,valid)+auxiliary_loss(real_aux,labels))/2
# 生成图像的损失
fake_pred,fake_aux=discriminator(gen_imgs.detach())
d_fake_loss=(adversarial_loss(fake_pred,fake)+auxiliary_loss(fake_aux,gen_labels))/2
# 判别器的总损失
d_loss=(d_real_loss+d_fake_loss)/2
# 计算判别器的准确率
pred=np.concatenate([real_aux.data.cpu().numpy(),fake_aux.data.cpu()],axis=0)
gt=np.concatenate([labels.data.cpu().numpy(),gen_labels.data.cpu().numpy()],axis=0)
d_acc=np.mean(np.argmax(pred,axis=1)==gt)
d_loss.backward()
optimizer_D.step()
print(
"[Epoch %d/%d] [Batch %d/%d] [D_loss:%f,acc:%d%%] [G_loss:%f]"
% (epoch,opt.n_epochs,i,len(dataloader),d_loss.item(),100*d_acc,g_loss.item())
)
batches_done=epoch*len(dataloader)+i
if batches_done % opt.sample_interval==0:
sample_image(n_row=10,batches_done=batches_done)
与前面的GAN和CGAN相比,类似地方较多。