GAN(论文+Jittor框架实现代码)

论文地址:https://arxiv.org/abs/1406.2661

翻译后的论文地址:深度学习:生成式对抗网络,让机器在博弈中实现“自我成长” - 前沿洞察 - 恒生研究院 (hundsun.com)

论文项目地址:GitHub - goodfeli/adversarial: Code and hyperparameters for the paper "Generative Adversarial Networks"

一、论文核心要点: 

  1. 提出了一个基于对抗的新生成式模型,它由一个生成器和一个判别器组成
  2. 生成器的 目标是学习到样本的数据分布,从而能生成样本欺骗判别器;判别器的目标是判断输入样本是生成/真实的概率
  3. GAN模型等同于博弈论中的二人零和博弈
  4. 对于任意的生成器和判别器,都存在一个独特的全局最优解
  5. 生成器和判别器都由多层感知机实现,整个网络可以用反向传播算法来训练
  6. 通过实验的定性与定量分析显示,GAN的潜力很大

二、研究背景:

1、判别式模型 

  • 模型学习的是条件概率分布P(Y|X):(X成立时Y成立的样本数)/(X成立的样本数)
  • 任务是从属性X(特征)预测标记Y(类别)
  • 线性回归、逻辑回归、K近邻(KNN)、支持向量机(SVM)、决策树、条件随机场(CRF)、boosting方法

2、生成式模型

  • 模型学习的是联合概率分布P(X,Y)(X与Y同时成立的样本数)/(总样本数)
  • 任务是得到属性为X且类别为Y时的联合概率 
  • 朴素贝叶斯、混合高斯模型、隐马尔科夫模型(HMM)、贝叶斯网络、马尔可夫随机场、深度信念网络(DBN)、变分自编码器(VAE)

3、零和博弈

  •  一方的收益必须意味着另一方的损失,博弈各方的收益和损失相加永远为“零”,双方不存在合作的可能

4、Minimax

  • 在零和博弈中,为了使己方达到最优解,所以把目标设为让对方的最大收益最小化 

 5、GAN历史意义

  • 使AI具备了图像视频的创作编辑能力
  • 启发了无/弱监督学习的研究

6、GAN应用领域

  • 图像生成 Image Generation
  • 图像转换 Image Translation
  • 图像编辑 Image Editing 

 三、论文结构

  1. 提出GAN模型
  2. 解决问题,模型组成,训练方式
  3. 无向图莫下,DBNs,GSN,VAE
  4. 价值函数
  5. 训练过程,全局最优性,收敛性
  6. 结果比对,生成图像展示
  7. 各方法综合比对
  8. 条件生成式模型

1、价值函数

min\underset{G}{}\, max\underset{D}{}V(D,G)=E\underset{x\sim p\underset{data}{}(x)}{}[logD(x)]+E{\underset{z\sim p{\underset{z}{}(z)}}{}}[log(1-D(G(z)))]

x:从真实数据中的采样

data:真实数据

D:判别器,输出值为0(假)或1(真),代表输入来自真实数据的概率,D(x)属于0~1,二分类神经网络

z:随机噪声

G:生成器,输出为合成数据

x~Pdata(x):真实数据服从的概率分布

Pz(z):随机噪声服从的概率分布

        D的目标是最大化加载函数V,对数函数log在底数大于1时,为单调函数,最大化V就是最大化D(x)和1-D(G(z)),队任意的x,都有D(x)=1和D(G(z))=0,即判别器对真实数据的采样判定为真实数据,对生成器生成的数据判断为合成数据。

        G的目标是针对特定的D,去最小化价值函数V,最小化V就是最小化D(x)、1-D(G(z)),对任意的z都有D(G(z))=1,即使判别器对来自生成器的数据都认为是合成的数据。

        在训练开始时,G性能较差,D(G(z))接近0,此时,log(1-D(G(z)))的梯度值较小,log(D(G(z)))的梯度值较大,把G的目标改为最大化log D(G(z)),在早期学习在能提供更强的梯度。

gan.py

import jittor as jt
from jittor import init
from jittor import nn
from jittor.dataset.mnist import MNIST
from jittor.dataset.dataset import ImageFolder
import jittor.transform as transform
import argparse
import os
import numpy as np
from loguru import logger
import time
import cv2

jt.flags.use_cuda = 1
logger.add('BCELoss_celebA_log.log')# 生成训练日志文件
img_save_path = './images'# 生成器生成的图片保存地址
os.makedirs(img_save_path, exist_ok=True)
model_save_path = './save_models'# 训练完的模型保存地址 
os.makedirs(model_save_path, exist_ok=True)

parser = argparse.ArgumentParser()
parser.add_argument('--n_epochs', type=int, default=2000, help='训练的时期数')
parser.add_argument('--batch_size', type=int, default=64, help='批次大小')
parser.add_argument('--lr', type=float, default=0.0002, help='学习率')
parser.add_argument('--b1', type=float, default=0.5, help='梯度的一阶动量衰减')
parser.add_argument('--b2', type=float, default=0.999, help='梯度的一阶动量衰减')
parser.add_argument('--n_cpu', type=int, default=8, help='批处理生成期间要使用的 cpu 线程数')
parser.add_argument('--latent_dim', type=int, default=100, help='潜在空间的维度')
parser.add_argument('--img_size', type=int, default=28, help='每个图像尺寸的大小')
parser.add_argument('--sample_interval', type=int, default=10000, help='图像样本之间的间隔')
parser.add_argument('--channels', type=int, default=3, help='图像通道数')
parser.add_argument('--dataclass', type=str, default='celebA', help='数据集类型')
parser.add_argument('--train_dir', type=str, default='./Dataset/CelebA_train', help='数据集地址')
# parser.add_argument('--channels', type=int, default=1, help='图像通道数')
# parser.add_argument('--dataclass', type=str, default='MNIST', help='数据集类型')
# parser.add_argument('--train_dir', type=str, default='./Dataset/MNIST', help='数据集地址')
opt = parser.parse_args()
print(opt)
img_shape = (opt.channels, opt.img_size, opt.img_size)

# 保存生成器生成的图片样本数据
def save_image(img, path, nrow=None):
    N,C,W,H = img.shape# (25, 1, 28, 28)
    '''
    [-1,700,28] , img2的形状(1,700,28)
    img[0][0][0] = img2[0][0]
    img2:[
            [1*28]
            ......(一共700个)    
                    ](1,700,28)
    '''
    img2=img.reshape([-1,W*nrow*nrow,H])
    # [:,:28*5,:],img:(1,140,28)
    img=img2[:,:W*nrow,:]
    for i in range(1,nrow):#[1,5)
        '''
        img(1,140,28),img2(1,700,28)
        img从(1,140,28)->(1,140,28+28)->...->(1,140,28+28+28+28)=(1,140,140)
        np.concatenate把两个三维数组合并
        '''        
        img=np.concatenate([img,img2[:,W*nrow*i:W*nrow*(i+1),:]],axis=2)
    # img中的数据大小从(-1,1)--(+1)-->(0,2)--(/2)-->(0,1)--(*255)-->(0,255)转换成了像素值
    img=(img+1.0)/2.0*255
    # (1,140,140)--->(140,140,1)
    # (channels通道数,imagesize,imagesize)转化为(imagesize,imagesize,channels通道数)
    img=img.transpose((1,2,0))
    # 根据地址保存图片样本数据
    cv2.imwrite(path,img)

# 生成器
class Generator(nn.Module):

    def __init__(self):
        super(Generator, self).__init__()

        def block(in_feat, out_feat, normalize=True):
            layers = [nn.Linear(in_feat, out_feat)]
            if normalize:
                layers.append(nn.BatchNorm1d(out_feat, 0.8))
            layers.append(nn.LeakyReLU(scale=0.2))
            return layers
        self.model = nn.Sequential(*block(opt.latent_dim, 128, normalize=False), *block(128, 256), *block(256, 512), *block(512, 1024), nn.Linear(1024, int(np.prod(img_shape))), nn.Tanh())

    def execute(self, z):
        img = self.model(z)
        img = img.view((img.shape[0], *img_shape))
        return img

# 判别器
class Discriminator(nn.Module):

    def __init__(self):
        super(Discriminator, self).__init__()
        self.model = nn.Sequential(nn.Linear(int(np.prod(img_shape)), 512), nn.LeakyReLU(scale=0.2), nn.Linear(512, 256), nn.LeakyReLU(scale=0.2), nn.Linear(256, 1), nn.Sigmoid())

    def execute(self, img):
        img_flat = img.view((img.shape[0], (- 1)))
        validity = self.model(img_flat)
        return validity

# bce loss分类器 (b这里指的是binary,所以用于二分类问题)
'''
源码:
class BCELoss(Module):
    def __init__(self, weight=None, size_average=True):
        self.weight = weight
        self.size_average = size_average
    def execute(self, output, target):
        return bce_loss(output, target, self.weight, self.size_average)

# weight:表示对loss中每个元素的加权权值,默认为None
# size_average:指定输出的格式,包括'mean','sum'
# output:判别器对生成的数据的判别结果(64*1)
# target:判别器对真实的数据的判别结果(64*1)
def bce_loss(output, target, weight=None, size_average=True):
    # jt.maximum(x,y):返回x和y的元素最大值
    # 公式:损失值 = -权重*[ 理想结果*log(判别结果) + (1-理想结果)*log(1-判别结果) ]
    loss = - ( 
                target * jt.log(jt.maximum(output, 1e-20)) 
                + 
                (1 - target) * jt.log(jt.maximum(1 - output, 1e-20))
            )
    if weight is not None:
        loss *= weight    
    if size_average:
        return loss.mean()# 求均值
    else:
        return loss.sum()# 求和
'''
# 对抗性损失函数
adversarial_loss = nn.BCELoss()

# 初始化生成器和判别器
generator = Generator()
discriminator = Discriminator()

# 配置数据加载器
def DataLoader(dataclass, img_size, batch_size, train_dir, eval_dir):
    if dataclass == 'MNIST':
        Transform = transform.Compose([
            transform.Resize(size=img_size),
            transform.Gray(),
            transform.ImageNormalize(mean=[0.5], std=[0.5])])
        train_loader = MNIST (data_root=train_dir, train=True, transform=Transform).set_attrs(batch_size=batch_size, shuffle=True)
        eval_loader = MNIST (data_root=eval_dir, train=False, transform = Transform).set_attrs(batch_size=1, shuffle=True)
    else:
        # celebA数据集中有20万张图片,每张图片的尺寸178*218
        Transform = transform.Compose([
            transform.Resize(size=img_size),
            transform.ImageNormalize(mean=[0.5, 0.5, 0.5],std=[0.5, 0.5, 0.5])])
        train_loader = ImageFolder(train_dir)\
            .set_attrs(transform=Transform, batch_size=batch_size, shuffle=True)
        eval_loader = ImageFolder(eval_dir)\
            .set_attrs(transform=Transform, batch_size=batch_size, shuffle=True)
    
    return train_loader, eval_loader
# 加载训练集数据
train_loader, eval_loader = DataLoader(dataclass=opt.dataclass,
                                        img_size=opt.img_size,
                                        batch_size=opt.batch_size,
                                        train_dir=opt.train_dir,
                                        eval_dir=opt.train_dir)

# 优化器
optimizer_G = jt.optim.Adam(generator.parameters(), lr=opt.lr, betas=(opt.b1, opt.b2))
optimizer_D = jt.optim.Adam(discriminator.parameters(), lr=opt.lr, betas=(opt.b1, opt.b2))

warmup_times = -1
run_times = 3000
total_time = 0.
cnt = 0

# ----------
#  训练
# ----------

for epoch in range(opt.n_epochs):#[1,2000),2000次迭代
    for (i, (real_imgs, _)) in enumerate(train_loader):# 938

        '''
        valid表示真,全为1,fake表示假,全为0
        img.shape[0]:图像的垂直尺寸(高度)h
        [ [1.0]...(一共h个)...[1.0] ]  64*1的数组
        '''
        valid = jt.ones([real_imgs.shape[0], 1]).stop_grad()
        fake = jt.zeros([real_imgs.shape[0], 1]).stop_grad()

        # --------------------- 训练生成器 ---------------------  

        # TODO 第一步:生成服从正态分布的噪音数据
        '''
        随机生成一个符合正态分布的噪声,numpy.random.normal(loc=0.0, scale=1.0, size=None)
        loc:正态分布的均值,对应着这个分布的中心,0说明这一个以Y轴为对称轴的正态分布
        scale:正态分布的标准差,对应分布的宽度,scale越大,正态分布的曲线越矮胖,scale越小,曲线越高瘦
        shape:(图片的高度h,潜在空间的维度100) == (64,100) == z.shape
        '''
        z = jt.array(np.random.normal(0, 1, (real_imgs.shape[0], opt.latent_dim)).astype(np.float32))
        # TODO 第二步:生成器加载噪音数据生成图片数据[64,1,28,28]
        '''
        gen_imgs的形状:(64,1,28,28), 64*1中每个元素都是28*28
        [
            [28*28]
            ......   (一共64个28*28)
                    ]
        '''
        gen_imgs = generator(z)
        # TODO 第三步:根据生成数据的判别结果和真的数据(都是64*1)计算损失值
        '''
        把生成的图片数据放进判别器中,让判别器对其进行分类,计算出数据可能是真实数据的概率值(0-1之间的数)
        valid当作是判别器分类的结果,全为1说明判别器认为这个数据来源于真实图片
        adversarial_loss会调用bce_loss求损失值
        因为我们需要使生成器生成的数据越来越像真实的数据,所以我们需要这两个数据越来越相似[discriminator(gen_imgs)和valid]
        loss(x,y)=-w*[ylogx+(1-y)log(1-x)]
        生成器理想条件下,discriminator(gen_imgs)=1,loss(1,1)=0
        '''
        g_loss = adversarial_loss(discriminator(gen_imgs), valid)
        # TODO 第四步:反向传播,训练生成器的参数
        optimizer_G.step(g_loss)
        
        # --------------------- 训练判别器 ---------------------
        
        # TODO 第一步:根据训练集中的数据和真的数据计算损失值
        '''
        real_imgs:加载的训练集数据
        把训练集数据放进判别器,得到判别器对训练集数据的判别结果,计算出数据可能是真实数据的概率值
        valid当作是判别器分类的结果,全为1说明判别器认为这个数据来源于真实图片
        因为我们需要使判别器把训练集数据判别为真实数据,所以我们需要使这两个数据越来越相似[discriminator(real_imgs), valid]
        loss(x,y)=-w*[ylogx+(1-y)log(1-x)]
        判别器理想条件下,discriminator(real_imgs)=1,loss(1,1)=0
        '''
        real_loss = adversarial_loss(discriminator(real_imgs), valid)#
        # TODO 第二步:根据生成数据的判别结果和假的数据(都是64*1)计算损失值
        '''
        gen_imgs:生成器生成的图片数据
        把生成的图片数据放进判别器中,让判别器对其进行分类,计算出数据可能是真实数据的概率值(0-1之间的数)
        fake当作是判别器分类的结果,全为0说明判别器认为这个数据来源于生成的数据,而不是真实现实中的数据
        调用bce_loss求损失值
        因为我们需要使判别器能识别出机器生成的图片数据,所以我们需要使这两个数越来越相似[discriminator(gen_imgs), fake]
        loss(x,y)=-w*[ylogx+(1-y)log(1-x)]
        判别器理想条件下,discriminator(gen_imgs)=0,loss(0,0)=0
        '''
        fake_loss = adversarial_loss(discriminator(gen_imgs), fake)# 
        # TODO 第三步:对这两个损失值求平均
        d_loss = ((real_loss + fake_loss) / 2)
        # TODO 第四步:反向传播,训练判别器的参数
        optimizer_D.step(d_loss)

        # ---------------------
        #  打印训练进度,打印生成器和判别器的损失值
        #  保存生成器生成的图片样本数据
        # ---------------------

        if warmup_times==-1:
            '''
            D loss:判别器的损失值,越小越好(0-1的数)
            G loss:生成器的损失值,越小越好(0-1的数)
            numpy():把Var数据类型的数据转换成array数据类型
            '''
            # [0,200) * 938 + [0,938) = [0,199*938+937] = [0,187599]
            batches_done = ((epoch * len(train_loader)) + i)
            # opt.sample_interval = 10000 , 187599 / 400 = 468
            if ((batches_done % opt.sample_interval) == 0):
                loss_log = '[Epoch %d/%d] [Batch %d/%d] [D loss: %f] [G loss: %f]' % (epoch, opt.n_epochs, i, len(train_loader), d_loss.numpy()[0], g_loss.numpy()[0])
                print(loss_log)# 打印每次训练的损失值
                logger.debug(loss_log)# 把每次训练的损失值保存到日志文件中去
                # gen_imgs.data[:25] -> (25, 1, 28, 28)
                path = os.path.join(img_save_path, '%d.png'%batches_done)# 图片保存路径
                save_image(gen_imgs.data[:25], path, nrow=5)
        else:
            jt.sync_all()
            cnt += 1
            print(cnt)
            if cnt == warmup_times:
                jt.sync_all(True)
                sta = time.time()
            if cnt > warmup_times + run_times:
                jt.sync_all(True)
                total_time = time.time() - sta
                print(f"run {run_times} iters cost {total_time} seconds, and avg {total_time / run_times} one iter.")
                exit(0)

    # 没训练10个批次,保存一次模型
    if (epoch+1) % 10 == 0:
        generator.save(os.path.join(model_save_path,'BCE_generator_celebA.pkl'))
        discriminator.save(os.path.join(model_save_path,'BCE_discriminator_celebA.pkl'))

gan_model_load_test.py

import jittor as jt
from jittor import init
from jittor import nn

import argparse
import numpy as np
import cv2

jt.flags.use_cuda = 1

parser = argparse.ArgumentParser()
parser.add_argument('--latent_dim', type=int, default=100, help='潜在空间的维度')
parser.add_argument('--img_size', type=int, default=28, help='每个图像尺寸的大小')
parser.add_argument('--channels', type=int, default=1, help='图像通道数')

opt = parser.parse_args()
print(opt)
img_shape = (opt.channels, opt.img_size, opt.img_size)

# 生成器
class Generator(nn.Module):

    def __init__(self):
        super(Generator, self).__init__()

        def block(in_feat, out_feat, normalize=True):
            layers = [nn.Linear(in_feat, out_feat)]
            if normalize:
                layers.append(nn.BatchNorm1d(out_feat, 0.8))
            layers.append(nn.LeakyReLU(scale=0.2))
            return layers
        self.model = nn.Sequential(*block(opt.latent_dim, 128, normalize=False), *block(128, 256), *block(256, 512), *block(512, 1024), nn.Linear(1024, int(np.prod(img_shape))), nn.Tanh())

    def execute(self, z):
        img = self.model(z)
        img = img.view((img.shape[0], *img_shape))
        return img

# 判别器
class Discriminator(nn.Module):

    def __init__(self):
        super(Discriminator, self).__init__()
        self.model = nn.Sequential(nn.Linear(int(np.prod(img_shape)), 512), nn.LeakyReLU(scale=0.2), nn.Linear(512, 256), nn.LeakyReLU(scale=0.2), nn.Linear(256, 1), nn.Sigmoid())

    def execute(self, img):
        img_flat = img.view((img.shape[0], (- 1)))
        validity = self.model(img_flat)
        return validity

def deal_image(img, path=None, nrow=None):
    N,C,W,H = img.shape# (25, 1, 28, 28)
    '''
    [-1,700,28] , img2的形状(1,700,28)
    img[0][0][0] = img2[0][0]
    img2:[
            [1*28]
            ......(一共700个)    
                    ](1,700,28)
    '''
    img2=img.reshape([-1,W*nrow*nrow,H])
    # [:,:28*5,:],img:(1,140,28)
    img=img2[:,:W*nrow,:]
    for i in range(1,nrow):#[1,5)
        '''
        img(1,140,28),img2(1,700,28)
        img从(1,140,28)->(1,140,28+28)->...->(1,140,28+28+28+28)=(1,140,140)
        np.concatenate把两个三维数组合并
        '''        
        img=np.concatenate([img,img2[:,W*nrow*i:W*nrow*(i+1),:]],axis=2)
    # img中的数据大小从(-1,1)--(+1)-->(0,2)--(/2)-->(0,1)--(*255)-->(0,255)转换成了像素值
    img=(img+1.0)/2.0*255
    # (1,140,140)--->(140,140,1)
    # (channels通道数,imagesize,imagesize)转化为(imagesize,imagesize,channels通道数)
    img=img.transpose((1,2,0))
    if path:
        # 根据地址保存图片样本数据
        cv2.imwrite(path,img)
    cv2.imshow('1',img)
    cv2.waitKey(0)

# 初始化生成器和判别器,并加载模型,训练了2000批次
generator = Generator()
g_model_path = "./save_models/BCE_generator.pkl"
generator.load_parameters(jt.load(g_model_path))
generator.load(g_model_path)
discriminator = Discriminator()
d_model_path = "./save_models/BCE_discriminator.pkl"
discriminator.load_parameters(jt.load(d_model_path))
discriminator.load(d_model_path)

z = jt.array(np.random.normal(0, 1, (64, opt.latent_dim)).astype(np.float32))
gen_imgs = generator(z)
deal_image(gen_imgs.data[:25], "./images_test/BEC_1.png",nrow=5)

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
基于GAN的人脸老化算法是一种利用生成对抗网络(GAN)来实现人脸老化效果的技术。该算法通过训练生成器和判别器两个网络,使得生成器能够生成逼真的老化人脸图片,同时判别器能够准确地区分老化和非老化的人脸图片。 首先,我们需要收集一组包括不同年龄段的人脸图片数据集,以用于训练生成器和判别器网络。然后,我们可以使用Python和深度学习框架如TensorFlow或PyTorch来实现基于GAN的人脸老化算法。 在代码实现中,我们首先构建生成器网络和判别器网络的架构。生成器网络可以使用卷积神经网络(CNN)来生成逼真的老化人脸图片,而判别器网络也可以使用CNN来判断生成的图片是老化还是非老化的。然后,我们定义生成器和判别器的损失函数,通常是使用对抗损失、内容损失和周期一致性损失来训练生成器和判别器网络。 接下来,我们可以通过迭代训练生成器和判别器网络,不断优化它们的参数,直到生成器可以生成逼真的老化人脸图片并且判别器能够准确地识别老化的人脸图片。 最后,我们可以使用训练好的模型来对新的人脸图片进行老化效果的生成。这些实现代码可以在计算机上进行训练和测试,并可以通过调整参数和网络架构来优化老化效果。 总之,基于GAN的人脸老化算法实现代码是一种通过深度学习实现人脸老化效果的技术,通过生成对抗网络的训练和优化,我们可以实现模型来生成逼真的老化人脸图片。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值