DCGAN

有趣的图像生成——使用DCGAN与pytorch生成动漫头像

文章目录

  • 有趣的图像生成——使用DCGAN与pytorch生成动漫头像
    • 一、源码下载
    • 二、什么是DCGAN
    • 三、DCGAN的实现
      • 1、**数据集的选择**
      • 2、**生成器的构建**
      • 3、**判别器的构建**
      • 4、**DCGAN的训练**
    • 四、生成图片
    • 五、结论
    • 六、参考

一、源码下载

下载地址:https://gitee.com/yang_guo123/dcgan-pytorch

数据集下载地址:链接: https://pan.baidu.com/s/1PcZ4TLyqcN52096eIfbmCA 密码: auc5

二、什么是DCGAN

生成对抗网络(GAN)是由Ian Goodfellow在2014年提出的一种生成网络,它由生成器(Generator)与判别器(Discriminator)构成。

生成器:负责伪造假图片,试图以假乱真。

判别器:负责区分图片,希望能从图片中区分真实图片与假图片

在GAN的训练过程中,生成器通过凭空捏造出一幅图片,令判别器进行打分,并通过判别器的分数进行梯度更新,而判别器可以看到真实图片与生成器的伪造图片,通过区分伪造图片与真实图片进行梯度更新。

使用通俗的话来讲,生成器好比制作假冒伪劣手机的奸商,而判别器好比鉴定专家。在一开始,奸商与鉴定专家都是新手,奸商制作的山寨手机都是随机的形状,而鉴定专家也是随机判断手机的好坏;突然有一天奸商了解到手机是长方形的,由此制作的手机能够轻松骗过鉴定专家,这样一来,鉴定专家损失惨重;由于时间的积累,鉴定专家有了一定的经验,能够判定山寨手机的一些特征,则奸商损失惨重,由此这样多轮的较量,鉴定专家能够精确的判定手机是否为赝品,而奸商也可以制作可以以假乱真的赝品手机。

在传统的GAN中,通常使用全连接神经网络,并且其难以训练,于是,在2016年Alec Radford等人提出了DCGAN(论文详见:https://arxiv.org/pdf/1511.06434.pdf),通过改进CNN的模型,将深度卷积神经网络引入GAN,并且获得了很好的效果。

由于本文主要为实践方面,理论层面就不再赘述,有兴趣的网友可以自行下载论文阅读。

三、DCGAN的实现

1、数据集的选择

本文所采用的数据集为动漫头像数据集,图像大小为96x96,由下图所示:

2、生成器的构建

DCGAN的生成器器由全卷积神经网络构成,首先在网络中输入一个长度为100的随机向量,通过多次的反卷积运算,并使用tanh激活函数输出,将随机向量变为长宽为64x64三通道图像,具体结构由下图所示:

DCGAN_第1张图片

有图可见,在每一层反卷积时,图像的通道数都会缩小一半,并在最后一层将通道数目调整为3通道,输出一张RGB图像,构建的代码由下文所示:

class Generator(nn.Module):
    def __init__(self, nz, nc, ngf):
        super(Generator, self).__init__()
        self.net = Sequential(
            nn.ConvTranspose2d(nz, ngf * 8, 4, 1, 0, bias=False),
            nn.BatchNorm2d(ngf * 8),
            nn.ReLU(inplace=True),
            nn.ConvTranspose2d(ngf * 8, ngf * 4, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ngf * 4),
            nn.ReLU(inplace=True),
            nn.ConvTranspose2d(ngf * 4, ngf * 2, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ngf * 2),
            nn.ReLU(inplace=True),
            nn.ConvTranspose2d(ngf * 2, ngf, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ngf),
            nn.ReLU(inplace=True),
            nn.ConvTranspose2d(ngf, nc, 4, 2, 1, bias=False),
            nn.Tanh()
        )

    def forward(self, x):
        return self.net(x)

3、判别器的构建

DCGAN有趣的地方在于判别器的网络结构与生成器正好是对称的,唯一有所不同的是其激活函数换成了LeakyRelu,并且输出端使用的激活函数为sigmoid,下面是判别器的代码实现:

class Discriminator(nn.Module):
    def __init__(self, nc, ndf):
        super(Discriminator, self).__init__()
        self.net = Sequential(
            nn.Conv2d(nc, ndf, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ndf),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Conv2d(ndf, ndf*2, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ndf*2),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Conv2d(ndf*2, ndf*4, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ndf*4),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Conv2d(ndf*4, ndf*8, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ndf*8),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Conv2d(ndf*8, 1, 4, 1, 0, bias=False),
            nn.Sigmoid()
        )

    def forward(self, x):
        return self.net(x)

4、DCGAN的训练

在训练的开始,使用pytorch自带的Detaset进行数据集的预处理与读取:

class DCGAN_dataloader(Dataset):
    def __init__(self, csv_path, transforms=None):
        super(DCGAN_dataloader, self).__init__()
        self.transforms = transforms
        self.data = pd.read_csv(csv_path)

    def __getitem__(self, item):
        img_path = self.data["img"][item]
        img = Image.open(img_path).convert("RGB")
        img = self.transforms(img)
        return img

    def __len__(self):
        return len(self.data["img"])

在这里,本文使用了一些数据增广的方法,例如随机翻转:

def transform():
    return transforms.Compose([
        transforms.Resize((64, 64)),
        transforms.RandomHorizontalFlip(0.3),
        transforms.ToTensor(),
        transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])
    ])

最后通过DataLoader传入网络:

# 读取数据集
dataset = DCGAN_dataloader("./dataset/data.csv", transforms=transform())
data = DataLoader(
    dataset,
    batch_size=bach_size,
    shuffle=True,
    drop_last=True
)

在DCGAN的论文中,使用了Adam优化器,并且将β1设置为0.5(Adam的具体实现请看吴恩达深度学习教程):

# 设置生成器和判别器的优化器为Adam
optim_D = Adam(
    self.Discriminator.parameters(),
    lr=self.lr_d,
    betas=[0.5, 0.999]
)
optim_G = Adam(
    self.Generator.parameters(),
    lr=self.lr_g,
    betas=[0.5, 0.999]
)

在这里将生成器与判别器的损失函数设为二值交叉熵损失:

# 设置损失函数为二值交叉熵损失
loss_func = BCELoss()

接下来就到了正式的训练过程,由于比较复杂,就分开来讲了。

首先读取数据集,并且将真实数据喂入判别器,并将标签设置为1,令判别器通过观测真实数据进行梯度更新:

img = Variable(img)
if self.device == "cuda":
    img = img.cuda()
    # 将数据喂入Discriminator
self.Discriminator.zero_grad()
label = torch.ones((bach_size,), device=self.device)
out = self.Discriminator(img).view(-1)
errD_real = loss_func(out, label)
errD_real.backward()

然后通过生成器生成一个批次的假数据并喂入判别器,并将标签设置为0,令判别器知道这次喂入的数据为假数据:

# 使用Generator生成假数据喂入Discriminator进行判别
noise = torch.randn(bach_size, self.nz, 1, 1, device=self.device)
fake = self.Generator(noise)
label.fill_(0)
out = self.Discriminator(fake.detach()).view(-1)
errD_fake = loss_func(out, label)
errD_fake.backward()
D_G_z1 = out.mean().item()
errD = errD_fake + errD_real
loss_d += errD.item()
optim_D.step()

最后通过生成器生成一个批次的假数据并让判别器判断,通过设置标签为1,使用蒙骗过判别器的数据进行梯度更新:

# 通过Discriminator返回的数值更新Generator
self.Generator.zero_grad()
label.fill_(1)
out = self.Discriminator(fake).view(-1)
errG = loss_func(out, label)
errG.backward()
loss_g += errG.item()
optim_G.step()

以下为全部代码:

    def train(self, epoch=20, bach_size=64):
        """
        进行训练
        :param epoch: epoch数值
        :param bach_size: batch的大小
        :return: None
        """

        # 读取数据集
        dataset = DCGAN_dataloader("./dataset/data.csv", transforms=transform())
        data = DataLoader(
            dataset,
            batch_size=bach_size,
            shuffle=True,
            drop_last=True
        )
        # 设置生成器和判别器的优化器为Adam
        optim_D = Adam(
            self.Discriminator.parameters(),
            lr=self.lr_d,
            betas=[0.5, 0.999]
        )
        optim_G = Adam(
            self.Generator.parameters(),
            lr=self.lr_g,
            betas=[0.5, 0.999]
        )
        # 设置损失函数为二值交叉熵损失
        loss_func = BCELoss()

        loss_g_log = []
        loss_d_log = []
        loss_d = 0
        loss_g = 0
        print("#" * 10 + " 开始训练 " + "#" * 10)
        # 开始训练
        with tqdm(total=epoch, desc="训练进度", postfix=dict, mininterval=0.3) as pbar:
            for i_epoch in range(epoch):
                loss_d = 0
                loss_g = 0
                for i, img in enumerate(data):
                    img = Variable(img)
                    if self.device == "cuda":
                        img = img.cuda()
                    # 将数据喂入Discriminator
                    self.Discriminator.zero_grad()
                    label = torch.ones((bach_size,), device=self.device)
                    out = self.Discriminator(img).view(-1)
                    errD_real = loss_func(out, label)
                    errD_real.backward()
                    D_x = out.mean().item()
                    # 使用Generator生成假数据喂入Discriminator进行判别
                    noise = torch.randn(bach_size, self.nz, 1, 1, device=self.device)
                    fake = self.Generator(noise)
                    label.fill_(0)
                    out = self.Discriminator(fake.detach()).view(-1)
                    errD_fake = loss_func(out, label)
                    errD_fake.backward()
                    D_G_z1 = out.mean().item()
                    errD = errD_fake + errD_real
                    loss_d += errD.item()
                    optim_D.step()
                    # 通过Discriminator返回的数值更新Generator
                    self.Generator.zero_grad()
                    label.fill_(1)
                    out = self.Discriminator(fake).view(-1)
                    errG = loss_func(out, label)
                    errG.backward()
                    loss_g += errG.item()
                    optim_G.step()

                    if i % 5 == 0:
                        pbar.set_postfix(**{'loss_D': "{:.4f}".format(loss_d / (i + 1)),
                                            'loss_G': "{:.4f}".format(loss_g / (i + 1)),
                                            'D(x)': "{:.4f}".format(D_x),
                                            'D(G(z))': "{:.4f}".format(D_G_z1)
                                            })
                        loss_g_log.append(loss_g / (i + 1))
                        loss_d_log.append(loss_d / (i + 1))
                with torch.no_grad():
                    img = self.Generator(torch.randn(20, self.nz, 1, 1, device=self.device)).cpu()
                img = (img + 1) * 127
                os.mkdir("./log/img_log/epoch{}".format(i_epoch))
                for j in range(20):
                    img_ = np.array(img[j]).transpose((1, 2, 0))

                    cv2.imwrite("./log/img_log/epoch{}/img{}.jpg".format(i_epoch, j), img_)
                pbar.update(1)
                torch.save(self.Generator.state_dict(),
                           "./log/model_g/epoch{}_loss{}.pth".format(i_epoch, loss_g / len(loss_g_log)))
                torch.save(self.Discriminator.state_dict(),
                           "./log/model_d/epoch{}_loss{}.pth".format(i_epoch, loss_d / len(loss_d_log)))
        # 清空缓存
        torch.cuda.empty_cache()
        # 保存loss日志文件
        loss_log = pd.DataFrame()
        loss_log["g_loss"] = loss_g_log
        loss_log["d_loss"] = loss_d_log
        loss_log.to_csv("./log/loss/loss.csv")

四、生成图片

在训练好GAN模型以后,需要生成图片时,只需调用生成器网络,并通过喂入一组随机数进行生成图片。值得注意的一点在于,由于生成器的最后一层通过tanh输出,输出的数据分布为[-1,1],而正常的图片像素值在0~255之间,所以要转换一下输出图片的分布:

    def generate(self, path="./img/generate.jpg"):
        """
        生成图片
        :param path: 生成图片的路径
        :return: None
        """
        param = torch.load(self.model_path)
        self.Generator.load_state_dict(param)
        with torch.no_grad():
            img = self.Generator(torch.randn(1, self.nz, 1, 1, device=self.device))
        img = np.array(img[0].cpu()).transpose((1, 2, 0))
        img = (img + 1) * 127
        cv2.imshow("generate", img.astype(np.uint8))
        cv2.waitKey(0)
        cv2.imwrite(path, img)

五、结论

以下为训练过程中生成的结果

epoch0:

在这里插入图片描述

epoch4:

在这里插入图片描述

epoch30:
在这里插入图片描述

可见,随着迭代次数的增加,生成的图像的质量越来越好了。

六、参考

  1. PyTorch教程之DCGAN_我的学习笔记-CSDN博客
  2. DCGAN Tutorial — PyTorch Tutorials 1.9.0+cu102 documentation

你可能感兴趣的:(pytorch,深度学习,神经网络)