传统GAN
传统的GAN可以看我的这篇文章:Paddle 基于ANN(全连接神经网络)的GAN(生成对抗网络)实现-CSDN博客
DCGAN
DCGAN是适用于图像生成的GAN,它的特点是:
- 只采用卷积层和转置卷积层,而不采用全连接层
- 在每个卷积层或转置卷积层之间,插入一个批归一化层和ReLU激活函数
转置卷积层
转置卷积层执行的是转置卷积或反卷积的操作,即它是常规卷积层的反向操作。它接收一个低分辨率的输入,然后将其通过转置滤波器升采样到更高的分辨率。
对于一个卷积层,它的输出大小公式是:
其中,o表示输出大小,i表示输入大小,p表示填充(padding),k表示卷积核大小(kernel_size),s表示步长(stride)。也就是说:输出大小 = (输入大小 - 卷积核大小 + 2 × 填充数) ÷ 步长 + 1
而对于一个转置卷积层,它的输出大小公式是:
其中,o表示输出大小,i表示输入大小,p表示填充(padding),k表示反卷积核大小(kernel_size),s表示步长(stride),u表示输出填充(output padding)。也就是说:输出大小 = (输入大小 - 1) * 步长 - 2*填充 + 反卷积大小 + 输出填充
在paddle中,转置卷积层可以这么定义:
paddle.nn.Conv2DTranspose(in_channels, out_channels, kernel_size, stride, padding)
像卷积层一样,反卷积层的in_channels表示输入通道数(如形如(3, 32, 32)的图片张量的通道数就是3),out_channels表示输出通道数(如把(64, 32, 32)变成3通道的彩色图像(3, 32, 32))。
代码实现
这里我们采用NWPU-RESISC45数据集,从中选择“freeway”(高速公路)作为训练数据,让机器生成高速公路的图片。这个训练数据内有700张256x256的图片,但由于我的电脑显存不足,因此将图片大小设置为64x64.
先写dataset.py:
import paddle
import numpy as np
from PIL import Image
import os
def getAllPath(path):
return [os.path.join(path, f) for f in os.listdir(path)]
class FreewayDataset(paddle.io.Dataset):
def __init__(self, transform=None):
super().__init__()
self.data = []
for path in getAllPath('./freeway'):
img = Image.open(path)
img = img.resize((64, 64))
img = np.array(img, dtype=np.float32).transpose((2, 1, 0))
if transform is not None:
img = transform(img)
self.data.append(img)
self.data = np.array(self.data, dtype=np.float32)
def __getitem__(self, idx):
return self.data[idx]
def __len__(self):
return len(self.data)
然后写训练脚本:
from dataset import FreewayDataset
import paddle
from models import Generator, Discriminator
import numpy as np
dataset = FreewayDataset()
dataloader = paddle.io.DataLoader(dataset, batch_size=32, shuffle=True)
netG = Generator()
netD = Discriminator()
if 1:
try:
mydict = paddle.load('generator.params')
netG.set_dict(mydict)
mydict = paddle.load('discriminator.params')
netD.set_dict(mydict)
except:
print('fail to load model')
loss = paddle.nn.BCELoss()
optimizerD = paddle.optimizer.Adam(parameters=netD.parameters(), learning_rate=0.0002, beta1=0.5, beta2=0.999)
optimizerG = paddle.optimizer.Adam(parameters=netG.parameters(), learning_rate=0.0002, beta1=0.5, beta2=0.999)
# 最大迭代epoch
max_epoch = 1000
for epoch in range(max_epoch):
now_step = 0
for step, data in enumerate(dataloader):
############################
# (1) 更新鉴别器
###########################
# 清除D的梯度
optimizerD.clear_grad()
# 传入正样本,并更新梯度
pos_img = data
label = paddle.full([pos_img.shape[0], 1, 1, 1], 1, dtype='float32')
pre = netD(pos_img)
loss_D_1 = loss(pre, label)
loss_D_1.backward()
# 通过randn构造随机数,制造负样本,并传入D,更新梯度
noise = paddle.randn([pos_img.shape[0], 100, 1, 1], 'float32')
neg_img = netG(noise)
label = paddle.full([pos_img.shape[0], 1, 1, 1], 0, dtype='float32')
pre = netD(neg_img.detach()) # 通过detach阻断网络梯度传播,不影响G的梯度计算
loss_D_2 = loss(pre, label)
loss_D_2.backward()
# 更新D网络参数
optimizerD.step()
optimizerD.clear_grad()
loss_D = loss_D_1 + loss_D_2
############################
# (2) 更新生成器
###########################
# 清除D的梯度
optimizerG.clear_grad()
noise = paddle.randn([pos_img.shape[0], 100, 1, 1], 'float32')
fake = netG(noise)
label = paddle.full((pos_img.shape[0], 1, 1, 1), 1, dtype=np.float32, )
output = netD(fake)
# 这个写法没有问题,因为这个loss既会影响到netG(output=netD(netG(noise)))的梯度,也会影响到netD的梯度,但是之后的代码并没有更新netD的参数,而循环开头就清除了netD的梯度
loss_G = loss(output, label)
loss_G.backward()
# 更新G网络参数
optimizerG.step()
optimizerG.clear_grad()
now_step += 1
###########################
# 输出日志
###########################
if now_step % 10 == 0:
print(f'Epoch ID={epoch} Batch ID={now_step} \n\n D-Loss={float(loss_D)} G-Loss={float(loss_G)}')
paddle.save(netG.state_dict(), "generator.params")
paddle.save(netD.state_dict(), "discriminator.params")
最后编写图片生成脚本:
import paddle
from models import Generator
import matplotlib.pyplot as plt
# 加载模型
netG = Generator()
mydict = paddle.load('generator.params')
netG.set_dict(mydict)
# 设置matplotlib的显示环境
fig, axs = plt.subplots(nrows=2, ncols=5, figsize=(15, 6)) # 创建一个2x5的子图网格
# 生成10个噪声向量
for i, ax in enumerate(axs.flatten()):
noise = paddle.randn([1, 100, 1, 1], 'float32')
img = netG(noise)
img = img.numpy()[0].transpose((2, 1, 0)) # img.numpy():张量转np数组
img[img < 0] = 0 # 将img中所有小于0的元素赋值为0
# 显示图片
ax.imshow(img)
ax.axis('off') # 不显示坐标轴
# 显示图像
plt.show()
经过数次训练,最终的效果如下:
这样看来,至少有点高速公路的感觉了。
参考
通过DCGAN实现人脸图像生成-使用文档-PaddlePaddle深度学习平台
卷积层和反卷积层输出特征图大小计算_输出特征图大小的计算方法-CSDN博客