PyTorch深度学习实战(29)——神经风格迁移

0. 前言

神经风格迁移 (Neural Style Transfer) 是一种基于深度学习的技术,用于将两个不同图像的风格进行合成,生成新的图像。它通过将一个参考图像的风格应用于另一个内容图像,以创造出独特而富有艺术感的合成图像。神经风格迁移的基本思想是利用预训练的卷积神经网络,将内容图像和风格图像分别输入网络中,并在网络的中间层获取它们的特征表示。通过调整合成图像的像素值,使得合成图像的特征表示既与内容图像的特征表示相似,又与风格图像的特征表示相似。

1. 神经风格迁移原理

1.1 模型介绍

神经风格迁移 (Neural Style Transfer) 是一种基于深度学习的艺术风格转换技术。它的基本思想是利用神经网络将图像 A 的风格与图像 B 的内容进行混合,生成一幅具有 A 风格特征和 B 内容特征的新图像。
神经风格迁移一般通过预训练的卷积神经网络实现。通过将输入图像和风格图像分别输入到这个网络中,提取到它们在神经网络中的特征表示,然后在不同网络层上使用不同权重进行组合,通过优化损失函数,生成一张新图像,使得它同时包含了内容图像的内容特征和风格图像的风格特征。因此,在神经风格迁移中,需要一张内容图像和一张风格图像,将这两个图像组合在一起,使得组合图像同时保留内容图像的内容和风格图像的风格:

风格迁移

在上图中,将右侧左下角子图(风格图像)中的风格迁移到左侧图像(内容图像)中的内容之上,得到融合结果如右侧图像所示。
为了执行神经风格迁移,我们将损失拆分为内容损失 (content loss) 和风格损失 (style loss),内容损失衡量生成的融合图像与内容图像间的差异,风格损失衡量风格图像与生成的融合图像的相似度。
在具体计算时,我们通常并不直接使用原始图像计算内容损失,而是使用图像的特征层激活进行计算,例如,第 2 个网络层的内容损失是内容图像和生成图像通过第 2 个网络层后激活之间的平方差。利用特征层而非原始图像计算内容损失,是由于特征层可以捕获原始图像的某些属性(例如,高层捕获前景轮廓,低层捕获物体细节)。
风格损失是衡量生成图片与风格图像在风格方面相似度的一种评估指标。风格损失通常通过计算生成图片与风格图像在神经网络层的 Gram 矩阵之间的距离来计算。Gram 矩阵是特征矩阵的内积,可以捕捉到图像特征之间的相关性,反映了图像的风格信息:

L G M ( S , G , l ) = 1 4 N l 2 M l 2 ∑ i j ( G M [ l ] ( S ) i j − G M [ l ] ( G ) i j ) 2 L_{GM}(S,G,l)=\frac1 {4N_l^2M_l^2}\sum_{ij}(GM[l](S)_{ij}-GM[l](G)_{ij})^2 LGM(S,G,l)=4Nl2Ml21ij(GM[l](S)ijGM[l](G)ij)2

其中 G M [ l ] GM[l] GM[l] 是风格图像 S S S 和生成图像 G G G 在第 l l l 层的 gram 矩阵值。Gram 矩阵是通过将矩阵与其自身的转置矩阵相乘得到的。
假设网络层的特征图尺寸为 32 x 32 x 256,将特征矩阵重塑为形状为 1024 x 256 的矩阵,其中 256 为矩阵的通道数,32 x 32= 1024 为特征图中的像素点数。然后,比较风格图像和生成图像的 Gram 矩阵的风格损失。
计算得到风格损失和内容损失后,最终生成的修改图像是使总损失最小的图像,即令风格和内容损失的加权平均值最小。

1.2 GramMatrix 的重要性

在本节中,我们将毕加索的画作风格转移到真实图像上,假设毕加索风格为 S t S_t St (风格图像),真实图像为 S o S_o So (内容图像),生成图像为 T a T_a Ta (目标图像)。在神经风格迁移中,图像 T a T_a Ta 中的局部特征与 S t S_t St 中的局部特征相同。尽管内容可能不同,但是将风格图像中类似的的颜色、形状和纹理应用于目标图像中是风格迁移中的重点。
进一步,如果使用 VGG19 的中间层提取 S o S_o So 图像特征,与提取的 T a T_a Ta 图像特征并不相同。但在每层的特征集中,对应的向量将以相似的方式相对变化。例如,在两个特征集中,第一个通道的平均值与第二个通道的平均值之比是相似的,这就是使用 Gram 进行损失计算的原因。
内容损失是通过比较内容图像与生成图像之间的特征激活差异来计算的;风格损失的计算方法是首先计算预定义层中的 Gram 矩阵,然后比较生成图像和风格图像的 Gram 矩阵;风格迁移模型的总损失是风格损失和内容损失的加权平均值。

2. 神经风格迁移模型构建策略

在了解了神经风格迁移的基本原理之后,我们继续对模型的运行流程进行分析,可以使用以下策略实现神经风格迁移:

  1. 将内容图像传递给预训练模型
  2. 提取内容图像通过预定义层的激活值
  3. 将生成图像输入模型,并在相同的预定义层提取激活值
  4. 计算内容图像和生成图像在每一网络层对应的内容损失
  5. 将风格图像通过模型的多个指定网络层,计算风格图像的 Gram 矩阵值
  6. 将生成图像穿过风格图像通过的相同网络层,并计算对应的 Gram 矩阵值
  7. 计算两幅图像的 Gram 矩阵值的平方差作为风格损失
  8. 总损失是风格损失和内容损失的加权平均值
  9. 最终的生成图像是能够最小化总损失的输入图像

3. 使用 Keras 实现神经风格迁移

了解了模型原理和运算流程后,接下来,使用 PyTorch 实现上述策略。

(1) 导入相关库:

from torchvision import transforms as T
from torch.nn import functional as F
import torch
from PIL import Image
from torch import nn
import numpy as np
from torch import optim
from matplotlib import pyplot as plt
device = 'cuda' if torch.cuda.is_available() else 'cpu'

from torchvision.models import vgg19

(2) 定义用于图像预处理和后处理的函数:

preprocess = T.Compose([
    T.ToTensor(),
    T.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
    T.Lambda(lambda x: x.mul_(255))
])
postprocess = T.Compose([
    T.Lambda(lambda x: x.mul_(1./255)),
    T.Normalize(mean=[-0.485/0.229, -0.456/0.224, -0.406/0.225], std=[1/0.229, 1/0.224, 1/0.225]),
])

(3) 定义 GramMatrix 类:

class GramMatrix(nn.Module):
    def forward(self, input):
        b,c,h,w = input.size()
        feat = input.view(b, c, h*w)
        G = feat@feat.transpose(1,2)
        G.div_(h*w)
        return G

在以上代码中,计算所有特征与其转置矩阵的内积,用于获取所有向量之间的关系。

(4) 计算 Gram 矩阵对应的 MSE 损失 GramMSELoss

class GramMSELoss(nn.Module):
    def forward(self, input, target):
        out = F.mse_loss(GramMatrix()(input), target)
        return(out)

一旦计算出两个特征集的 Gram 向量,我们需要使用均方误差损失 mse_loss 使它们尽可能匹配。

(5) 定义模型类 vgg19_modified

初始化类:

class vgg19_modified(nn.Module):
    def __init__(self):
        super().__init__()

提取特征:

        features = list(vgg19(pretrained = True).features)
        self.features = nn.ModuleList(features).eval() 

定义 forward 方法,接受预定义网络层列表并返回每个网络层对应的特征:

    def forward(self, x, layers=[]):
        order = np.argsort(layers)
        _results, results = [], []
        for ix,model in enumerate(self.features):
            x = model(x)
            if ix in layers: _results.append(x)
        for o in order: results.append(_results[o])
        return results if layers is not [] else x

定义模型对象:

vgg = vgg19_modified().to(device)

(6) 导入内容和风格图片:

# 根据计算性能调整图像尺寸
imgs = [Image.open(path).resize((1200,675)).convert('RGB') for path in ['Vincent_van_Gogh_779.jpg', '3.jpeg']]
style_image, content_image = [preprocess(img).to(device)[None] for img in imgs]

(7) 使用 requires_grad = True 指定要修改的内容图像:

opt_img = content_image.data.clone()
opt_img.requires_grad = True

(8) 定义内容损失和风格损失的层,即我们使用哪些中间 VGG 层,来比较风格的 Gram 矩阵和内容的原始特征向量:

style_layers = [0, 5, 10, 19, 28] 
content_layers = [21]
loss_layers = style_layers + content_layers

(10) 定义内容和风格损失的损失函数:

loss_fns = [GramMSELoss()] * len(style_layers) + [nn.MSELoss()] * len(content_layers)
loss_fns = [loss_fn.to(device) for loss_fn in loss_fns]

(11) 定义用于平衡内容和风格损失的权重:

style_weights = [1000/n**2 for n in [64,128,256,512,512]] 
content_weights = [1]
weights = style_weights + content_weights

(12) 对图像进行操作,使其风格尽可能与 style_image 相似。因此,我们通过计算从几个选定的 VGG 层获得的特征的 GramMatrix 来计算 style_imagestyle_targets 值。由于应该保留整体内容,我们使用 content_layer 变量来计算 VGG 的原始特征:

style_targets = [GramMatrix()(A).detach() for A in vgg(style_image, style_layers)]
content_targets = [A.detach() for A in vgg(content_image, content_layers)]
targets = style_targets + content_targets

(13) 定义优化器和迭代次数 max_iters。尽管我们可以使用 Adam 或其他优化器,但 LBFGS 是在神经风格迁移模型中表现最佳的优化器。实验表明 LBFGS 收敛速度更快,并且损失值更小:

max_iters = 500
optimizer = optim.LBFGS([opt_img])

(14) 执行优化过程。优化过程中,我们需要迭代相同张量,因此可以将优化过程封装为函数并重复调用:

iters = 0
trn_loss = []
while iters < max_iters:
    def closure():
        global iters
        iters += 1
        optimizer.zero_grad()
        out = vgg(opt_img, loss_layers)
        layer_losses = [weights[a] * loss_fns[a](A, targets[a]) for a,A in enumerate(out)]
        loss = sum(layer_losses)
        loss.backward()
        trn_loss.append(loss.item())
        # log.record(pos=iters, loss=loss, end='\r')
        return loss
    optimizer.step(closure)

(15) 绘制损失的变化情况:

epochs = np.arange(len(trn_loss)) + 1
plt.plot(epochs, trn_loss, 'bo', label='loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
plt.grid('off')
plt.show()

损失变化情况
(16) 绘制神经风格迁移结果图像:

with torch.no_grad():
    out_img = postprocess(opt_img[0]).permute(1,2,0)
plt.imshow(out_img.detach().cpu())
plt.show()

神经风格迁移结果图像

从上图中可以看出,生成图像是内容图像和风格图像的组合。

小结

神经风格迁移是一种利用深度学习技术合成两个图像风格的方法,通过卷积神经网络提取图像的特征表示,并通过优化损失函数的方式合成新的图像,从而创造出独特而富有艺术感的合成图像。在本节中,首先介绍了神经风格迁移的核心思想与风格迁移图像的生成流程,然后利用 PyTorch 从零开始实现了神经风格迁移算法,可以通过修改模型中的超参数来生成不同观感的图像。

系列链接

PyTorch深度学习实战(1)——神经网络与模型训练过程详解
PyTorch深度学习实战(2)——PyTorch基础
PyTorch深度学习实战(3)——使用PyTorch构建神经网络
PyTorch深度学习实战(4)——常用激活函数和损失函数详解
PyTorch深度学习实战(5)——计算机视觉基础
PyTorch深度学习实战(6)——神经网络性能优化技术
PyTorch深度学习实战(7)——批大小对神经网络训练的影响
PyTorch深度学习实战(8)——批归一化
PyTorch深度学习实战(9)——学习率优化
PyTorch深度学习实战(10)——过拟合及其解决方法
PyTorch深度学习实战(11)——卷积神经网络
PyTorch深度学习实战(12)——数据增强
PyTorch深度学习实战(13)——可视化神经网络中间层输出
PyTorch深度学习实战(14)——类激活图
PyTorch深度学习实战(15)——迁移学习
PyTorch深度学习实战(16)——面部关键点检测
PyTorch深度学习实战(17)——多任务学习
PyTorch深度学习实战(18)——目标检测基础
PyTorch深度学习实战(19)——从零开始实现R-CNN目标检测
PyTorch深度学习实战(20)——从零开始实现Fast R-CNN目标检测
PyTorch深度学习实战(21)——从零开始实现Faster R-CNN目标检测
PyTorch深度学习实战(22)——从零开始实现YOLO目标检测
PyTorch深度学习实战(23)——使用U-Net架构进行图像分割
PyTorch深度学习实战(24)——从零开始实现Mask R-CNN实例分割
PyTorch深度学习实战(25)——自编码器(Autoencoder)
PyTorch深度学习实战(26)——卷积自编码器(Convolutional Autoencoder)
PyTorch深度学习实战(27)——变分自编码器(Variational Autoencoder, VAE)
PyTorch深度学习实战(28)——对抗攻击(Adversarial Attack)

猜你喜欢

转载自blog.csdn.net/LOVEmy134611/article/details/135281171
今日推荐