大家好,欢迎来到专栏《百战GAN》,在这个专栏里,我们会进行GAN相关项目的核心思想讲解,代码的详解,模型的训练和测试等内容。

背景要求:会使用Python和Pytorch

附带资料:参考论文和项目,视频讲解

1 项目背景

作为一门新兴的技术,GAN在很多人脸图像任务中有着广泛的应用,下面列举了一些典型的方向。

在阅读接下来的内容之前,请大家务必回顾之前StyleGAN人脸生成的内容,没有这一部分内容基础,无法学习接下来的内容。

【百战GAN】StyleGAN原理详解与人脸图像生成代码实战

【项目实战课】基于Pytorch的StyleGAN v1人脸图像生成实战

2 原理简介

(1) 如何获得真实人脸的潜在编码向量,它对应StyleGAN中的映射 络(mapping network)的输入Z或者输出W。

(2) 如何通过修改Z或者W向量,控制生成人脸图像的高层语义属性。

当前对真实人脸编码向量的求取基本上基于两种思路,一种是学习一个编码器来实现映射,一种是直接对向量进行优化求解。

2.1 基于编码器的求解

基于编码器的求解框架如下图所示,由两部分模块组成。

Encoder表示需要训练的编码器,Decoder表示已经训练完的生成模型,如StyleGAN的生成器部分。真实图像输入编码器得到Z或者W,再输入生成器得到生成的人脸,完成人脸图像的重建。

通过直接学习一个编码器,可以不需要对每一张图都进行优化,实现一次训练,对任意图像都能提取潜在编码向量,但是也容易在训练数据集上发生过拟合。

2.2 基于优化求解的方法

基于优化求解的方法包括以下几步:

(1) 给定图片I,以及预训练好的生成器G。

(2) 初始化潜在编码向量,如W,其初始值可以使用计算得到的统计平均值。

(3) 根据优化目标进行反复迭代,直到达到预设的终止条件。

优化目标的常见形式为:

其中Lpercept为特征空间中的感知损失距离,是很通用的问题,具体形式不再赘述。lamda用于平衡感知损失和MSE损失之间的权重比。可以使用梯度下降算法进行求解。

基于优化求解方法的优点是精度较高,但是优化速度慢,而且对每一个图片都必须进行优化迭代。

3.1 人脸重建

接下来我们来看人脸重建的求解,核心代码如下:

if __name__ == "__main__":    ## 预训练模型权重    parser.add_argument(        "--ckpt", type=str, required=True, help="path to the model checkpoint"    )    ## 输出图像尺寸    parser.add_argument(        "--size", type=int, default=256, help="output image sizes of the generator"    )    ## 学习率参数    parser.add_argument(        "--lr_rampup",        type=float,        default=0.05,        help="duration of the learning rate warmup",    )    parser.add_argument(        "--lr_rampdown",        type=float,        default=0.25,        help="duration of the learning rate decay",    )    parser.add_argument("--lr", type=float, default=0.1, help="learning rate")    ## 噪声相关参数,噪声水平,噪声衰减范围,噪声正则化    parser.add_argument(        "--noise", type=float, default=0.05, help="strength of the noise level"    )    parser.add_argument(        "--noise_ramp",        type=float,        default=1.0,        help="duration of the noise level decay",    )    parser.add_argument(        "--noise_regularize",        type=float,        default=10000,        help="weight of the noise regularization",    )    ## MSE损失    parser.add_argument("--mse", type=float, default=0.5, help="weight of the mse loss")    ## 迭代次数    parser.add_argument("--step", type=int, default=1000, help="optimize iterations")    ## 重建图像    parser.add_argument(        "--files", type=str, help="path to image files to be projected"    )    ## 重建结果    parser.add_argument(        "--results", type=str, help="path to results files to be stored"    )## 计算学习率def get_lr(t, initial_lr, rampdown=0.25, rampup=0.05):    lr_ramp = min(1, (1 - t) / rampdown)    lr_ramp = 0.5 - 0.5 * math.cos(lr_ramp * math.pi)lr_ramp = lr_ramp * min(1, t / rampup)return initial_lr * lr_ramp## 合并latent向量和噪声def latent_noise(latent, strength):noise = torch.randn_like(latent) * strength    return latent + noise## 生成图片def make_image(tensor):    return (        tensor.detach()        .clamp_(min=-1, max=1)        .add(1)        .div_(2)        .mul(255)        .type(torch.uint8)        .permute(0, 2, 3, 1)        .to("cpu")        .numpy()    )## 生成与图像大小相等的噪声def make_noise(device,size):    noises = []    step = int(math.log(size, 2)) - 2    for i in range(step + 1):            size = 4 * 2 ** i            noises.append(torch.randn(1, 1, size, size, device=device))return noises## 噪声归一化def noise_normalize_(noises):    for noise in noises:        mean = noise.mean()        std = noise.std()        noise.data.add_(-mean).div_(std)args = parser.parse_args()    device = "cpu"    ## 计算latent向量的平均次数    n_mean_latent = 10000    ## 获得用于计算损失的最小图像尺寸    resize = min(args.size, 256)    ## 预处理函数    transform = transforms.Compose(        [            transforms.Resize(resize),            transforms.CenterCrop(resize),            transforms.ToTensor(),            transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5]),        ]    )    ## 投影的人脸图片,将图片处理成一个batch    imgs = []    imgfiles = os.listdir(args.files)    for imgfile in imgfiles:        img = transform(Image.open(os.path.join(args.files,imgfile)).convert("RGB"))        imgs.append(img)    imgs = torch.stack(imgs, 0).to(device)    ## 载入模型    netG = StyledGenerator(512,8)    netG.load_state_dict(torch.load(args.ckpt,map_location=device)["g_running"], strict=False)    netG.eval()    netG = netG.to(device)    step = int(math.log(args.size, 2)) - 2    with torch.no_grad():        noise_sample = torch.randn(n_mean_latent, 512, device=device)        latent_out = netG.style(noise_sample) ##输入噪声向量Z,输出latent向量W=latent_out        latent_mean = latent_out.mean(0)        latent_std = ((latent_out - latent_mean).pow(2).sum() / n_mean_latent) ** 0.5    ## 感知损失计算    percept = lpips.PerceptualLoss(        model="net-lin", net="vgg", use_gpu=device.startswith("cuda")    )    ## 构建噪声输入    noises_single = make_noise(device,args.size)    noises = []    for noise in noises_single:        noises.append(noise.repeat(imgs.shape[0], 1, 1, 1).normal_())    ## 初始化Z向量    latent_in = latent_mean.detach().clone().unsqueeze(0).repeat(imgs.shape[0], 1)    latent_in.requires_grad = True    for noise in noises:        noise.requires_grad = True    optimizer = optim.Adam([latent_in] + noises, lr=args.lr)    pbar = tqdm(range(args.step))    ## 优化学习Z向量    for i in pbar:        t = i / args.step ## t的范围是(0,1)        lr = get_lr(t, args.lr)        optimizer.param_groups[0]["lr"] = lr## 噪声衰减        noise_strength = latent_std * args.noise * max(0, 1 - t / args.noise_ramp) ** 2        latent_n = latent_noise(latent_in, noise_strength.item())        latent_n.to(device)        img_gen = netG([latent_n], noise=noises, step=step) ## 生成的图片        batch, channel, height, width = img_gen.shape## 在不超过256的分辨率上计算损失        if height > 256:            factor = height // 256            img_gen = img_gen.reshape(                batch, channel, height // factor, factor, width // factor, factor            )            img_gen = img_gen.mean([3, 5])        p_loss = percept(img_gen, imgs).sum()  ## 感知损失        n_loss = noise_regularize(noises)      ## 噪声损失        mse_loss = F.mse_loss(img_gen, imgs)   ## MSE损失        loss = p_loss + args.noise_regularize * n_loss + args.mse * mse_loss        optimizer.zero_grad()        loss.backward()        optimizer.step()        noise_normalize_(noises)        pbar.set_description(            (                f"perceptual: {p_loss.item():.4f}; noise regularize: {n_loss.item():.4f};"                f" mse: {mse_loss.item():.4f}; loss: {loss.item():.4f}; lr: {lr:.4f}"            )        )    ## 重新生成高分辨率图片    img_gen = netG([latent_in], noise=noises,step=step)    img_ar = make_image(img_gen)    result_file = {}    for i, input_name in enumerate(imgfiles):        noise_single = []        for noise in noises:            noise_single.append(noise[i : i + 1])        print("i="+str(i)+"; len of imgs:"+str(len(img_gen)))        result_file[input_name] = {            "img": img_gen[i],            "latent": latent_in[i],            "noise": noise_single,        }        img_name = os.path.join(args.results,input_name)        pil_img = Image.fromarray(img_ar[i])        pil_img.save(img_name) ##存储图片        np.save(os.path.join(args.results,input_name.split('.')[0]+'.npy'),latent_in[i].detach().numpy())##存储latent向量

在上面的代码中,latent_in就是要优化学习的潜在变量,当使用netG([latent_n], noise=noises, step=step)的调用方式时,latent_n是映射 络(mapping network)的向量Z,它由latent_in和随着迭代不断衰减的噪声向量组成,此时没有输入平均风格向量。

根据之前我们对生成器模型结构的解读,此时只有latent_n会影响生成的风格,因此为基于向量Z的重建方法。

当然我们也可以通过将latent_n设为平均风格向量,将混合权重style_weight设置为0,这样也只有latent_n会影响生成的风格,并且它会作为生成 络(s

声明:本站部分文章内容及图片转载于互联 、内容不代表本站观点,如有内容涉及侵权,请您立即联系本站处理,非常感谢!

(0)
上一篇 2022年7月21日
下一篇 2022年7月21日

相关推荐