首页 > 其他分享 >《Pytorch - 神经风格转换》

《Pytorch - 神经风格转换》

时间:2022-12-14 16:36:50浏览次数:39  
标签:opt style 转换 image step content Pytorch 神经 size


上一篇我们学习了神经风格转换的详细内容,现在我们找了个网上的例子,一起运行分析下,具体实现过程是如何操作的。

一:代码细节步骤解析
第一步:获取当前可用的设备信息,CPU还是GPU

# 获得当前的设备
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

第二步:顶一个加载图像的函数

def load_image(image_path, transform=None, max_size=None, shape=None):
"""加载图像,并进行Resize、transform操作"""
image = Image.open(image_path)

if max_size:
scale = max_size / max(image.size)
size = np.array(image.size) * scale
image = image.resize(size.astype(int), Image.ANTIALIAS)

if shape:
image = image.resize(shape, Image.LANCZOS)

if transform:
image = transform(image).unsqueeze(0)
pass

return image.to(device)

第三步:使用已经训练好的一个ConvNet模型来作为内容和风格的提取器们这里我们选用VGG19模型来做。这里我们不光是提取1个隐藏层,我们还要提取5个隐藏激活层的输出。将这5个隐藏激活的输出都采集下来,对每一层都是做相同的损失计算,然后累加作为总的损失值进行反向传播,后面会见到的。我们定义这个模型,也就是专门用来做内容/风格的提取器的。之前学习的是用一层隐藏激活输出,这里是用的是多层隐藏激活输出,不过也没有关系,顶多就是一个遍历激活层多次计算损失叠加的过程罢了。

class VGGNet(nn.Module):
def __init__(self):
"""Select conv1_1 ~ conv5_1 activation maps."""
# 选择conv_1到conv_5的激活图
super(VGGNet, self).__init__()
self.select = ['0', '5', '10', '19', '28']
self.vgg = models.vgg19(pretrained=True).features

def forward(self, x):
"""Extract multiple convolutional feature maps."""
# 提取多卷积特征图,也就是这里提取了不止一个隐藏层,而是提取了五个隐藏层。
features = []
for name, layer in self.vgg._modules.items():
x = layer(x)
if name in self.select:
features.append(x)
return features

四:定义训练过程
4.1:加载图像数据,我们定义一个transform去归一化预处理图像,跟VGGNet的悬链相同,
其次加载内容图像C,风格图像S,初始化一个随机图像G,这里采用直接拷贝图像C的方式初始化图像G,也就是target,这里要保持三个图像维度一致。

transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize(mean=(0.485, 0.456, 0.406),
std=(0.229, 0.224, 0.225))])
content = load_image(opt.content, transform, max_size=opt.max_size)
style = load_image(opt.style, transform, shape=[content.size(2), content.size(3)])
target = content.clone().requires_grad_(True)

4.2:定义优化器,实例化我们的内容/风格提取器。
这里需要注意的是,在风格转移处理中,我们是使用已经训练好的某个卷积模型,这个模型你可以用别人的,也可以自己定义的都是ok。但是我们的目的不是再次训练这个模型,而是将目标生成图像G作为参数进行优化的,因为我们想得到一个图像G,使得Loss最低,因为,G图像的每个像素就是目标优化的的参数。

实例化我们的内容/风格提取器,种类需要注意的是,我们不需要再训练这个模型了,因此使用eval()函数指定这个模型不需要计算梯度,提升效率。

optimizer = torch.optim.Adam([target], lr=opt.lr, betas=[0.5, 0.999])

vgg = VGGNet().to(device).eval() # 切换到eval()模式,省去梯度计算量,这样帝都计算的时候不会计算网络参数。

4.3:迭代每一个步进行训练

for step in range(opt.total_step):

4.3.1:每一个迭代次数中,第一步都是先对三个图像做相同的特征提取操作

提取每个图像的,多层特征向量

target_features = vgg(target)
content_features = vgg(content)
style_features = vgg(style)

4.3.2:由于我们设计是要提取多个隐藏层的激活输出,所以我们需要遍历激活层,对每一个隐藏输出都要用相同的方式计算损失值,再累加

for f1, f2, f3 in zip(target_features, content_features, style_features):
# Compute content loss with target and content images
# 计算content损失:target - content
content_loss += torch.mean((f1 - f2) ** 2)

# Reshape convolutional feature maps
# Reshape 卷积特征图
_, c, h, w = f1.size()
f1 = f1.view(c, h * w)
f3 = f3.view(c, h * w)

# Compute gram matrix
# 计算Gram矩阵(格拉姆矩阵)
f1 = torch.mm(f1, f1.t())
f3 = torch.mm(f3, f3.t())

# Compute style loss with target and style images
# 计算style损失:tartget - style
style_loss += torch.mean((f1 - f3) ** 2) / (c * h * w)

这里面我们能看到对content_loss 的损失值计过程,对于每个隐藏激活层就是求均方差。
至于style_loss,也跟之前学习的一样,先把每个隐藏激活层转换成二维矩阵,计算Gram矩阵,最后也是求均方差,再累加起来。

4.3.3:根据损失值,反向传播,更新参数,也就是就会更新G图像的每个像素值。

loss = opt.content_weight * content_loss + opt.style_weight * style_loss
optimizer.zero_grad()
loss.backward()
optimizer.step()

这里能看到对最终的损失函数的计算式一个加权累加操作,也是和学习的内容是一致的。

4.4:每到多少训练轮次,就记录一次log和保存此时的G图像。这个训练过程比较慢,因此,特别需要这样的中间输出,方便我们阶段性查看效果。

# 打印log
if (step + 1) % opt.log_step == 0:
print('Step [{}/{}], Content Loss: {:.4f}, Style Loss: {:.4f}'
.format(step + 1, opt.total_step, content_loss.item(), style_loss.item()))

# 输出保存图像
if (step + 1) % opt.sample_step == 0:
# Save the generated image
# 采样保存生成的风格图像
denorm = transforms.Normalize((-2.12, -2.04, -1.80), (4.37, 4.46, 4.44))
img = target.clone().squeeze()
img = denorm(img).clamp_(0, 1)
torchvision.utils.save_image(img, 'D:/software/Anaconda3/doc/3D_Img/nnStyleTrans/output-{}.png'.format(step + 1))

第五步:所有该定义的都定义好了,现在我们设置要训练的数据,和定义程序运行参数,启动运行即可。

content = './images/wuenda.jpg'
style = './images/naruto.jpg'
os.makedirs("D:/software/Anaconda3/doc/3D_Img/nnStyleTrans/", exist_ok=True)

parser = argparse.ArgumentParser()
parser.add_argument('--content', type=str, default=content)
parser.add_argument('--style', type=str, default=style)
parser.add_argument('--max_size', type=int, default=600)
parser.add_argument('--total_step', type=int, default=5000)
parser.add_argument('--log_step', type=int, default=50)
parser.add_argument('--sample_step', type=int, default=50)
parser.add_argument('--content_weight', type=float, default=1) # alpha value
parser.add_argument('--style_weight', type=float, default=100) # beta value
parser.add_argument('--lr', type=float, default=0.003)

opt = parser.parse_args(args=[]) # 合成命令 # 注意jupyter中需要注意无参数则添加args=[]这句话
print(opt)

transfer(opt) # 运行风格迁移函数

看到这里,是跟之前学习的内容是对的上的。现在就式运行一把了,做好心理准备,确实慢。

二:完整代码如下:

from __future__ import division
from torchvision import models
from torchvision import transforms
from PIL import Image
import argparse
import torch
import torchvision
import torch.nn as nn
import numpy as np
import os

# 导入图片显示相应的包
# import matplotlib.pyplot as plt # plt 用于显示图片
# import matplotlib.image as mpimg # mpimg 用于读取图片

# 获得当前的设备
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

# 图像加载函数
def load_image(image_path, transform=None, max_size=None, shape=None):
"""加载图像,并进行Resize、transform操作"""
image = Image.open(image_path)

if max_size:
scale = max_size / max(image.size)
size = np.array(image.size) * scale
image = image.resize(size.astype(int), Image.ANTIALIAS)

if shape:
image = image.resize(shape, Image.LANCZOS)

if transform:
image = transform(image).unsqueeze(0)
pass

return image.to(device)


# 加载已经训练好了的一个网络模型,不需要对他进行训练,在风格迁移的过程中不需要对他进行训练优化
# 这里特别注意的是,这里我们提取了五个隐藏层的激活。
class VGGNet(nn.Module):
def __init__(self):
"""Select conv1_1 ~ conv5_1 activation maps."""
# 选择conv_1到conv_5的激活图
super(VGGNet, self).__init__()
self.select = ['0', '5', '10', '19', '28']
self.vgg = models.vgg19(pretrained=True).features

def forward(self, x):
"""Extract multiple convolutional feature maps."""
# 提取多卷积特征图,也就是这里提取了不止一个隐藏层,而是提取了五个隐藏层。
features = []
for name, layer in self.vgg._modules.items():
x = layer(x)
if name in self.select:
features.append(x)
return features


# 定义处理流程和训练转换函数
def transfer(opt):
# 图像处理
# VGGNet在ImageNet数据集上训练的,ImageNet的图像已被归一化为mean=[0.485, 0.456, 0.406] and std=[0.229, 0.224, 0.225].
# 这里也进行使用同样的数据进行归一化
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize(mean=(0.485, 0.456, 0.406),
std=(0.229, 0.224, 0.225))])

# 加载待转换的内容图像content和目标风格图像style
# 两者的大小需要相同
content = load_image(opt.content, transform, max_size=opt.max_size)
style = load_image(opt.style, transform, shape=[content.size(2), content.size(3)])

# Initialize a target image with the content image
# 用content图像初始化一个target图像,target也就是那个G图像,把所有的像素都当做是参数。
# 这里的 G图像 赋予的初值是 图像content的所有像素值,概念上这个都是随机的,初始值不重要,因此也可以理解成随机就是一个跟content一模一样的图像也是ok
target = content.clone().requires_grad_(True)
optimizer = torch.optim.Adam([target], lr=opt.lr, betas=[0.5, 0.999])

vgg = VGGNet().to(device).eval() # 切换到eval()模式,省去梯度计算量,这样帝都计算的时候不会计算网络参数。

for step in range(opt.total_step):

# Extract multiple(5) conv feature vectors
# 提取每个图像的,多层特征向量
target_features = vgg(target)
content_features = vgg(content)
style_features = vgg(style)

style_loss = 0
content_loss = 0

# 对一个隐藏层的激活值进行损失计算,再累加
for f1, f2, f3 in zip(target_features, content_features, style_features):
# Compute content loss with target and content images
# 计算content损失:target - content
content_loss += torch.mean((f1 - f2) ** 2)

# Reshape convolutional feature maps
# Reshape 卷积特征图
_, c, h, w = f1.size()
f1 = f1.view(c, h * w)
f3 = f3.view(c, h * w)

# Compute gram matrix
# 计算Gram矩阵(格拉姆矩阵)
f1 = torch.mm(f1, f1.t())
f3 = torch.mm(f3, f3.t())

# Compute style loss with target and style images
# 计算style损失:tartget - style
style_loss += torch.mean((f1 - f3) ** 2) / (c * h * w)

# Compute total loss, backprop and optimize
# 计算全部隐藏激活层的所有损失,并进行反向传播和优化
loss = opt.content_weight * content_loss + opt.style_weight * style_loss
optimizer.zero_grad()
loss.backward()
optimizer.step()

# 打印log
if (step + 1) % opt.log_step == 0:
print('Step [{}/{}], Content Loss: {:.4f}, Style Loss: {:.4f}'
.format(step + 1, opt.total_step, content_loss.item(), style_loss.item()))

# 输出保存图像
if (step + 1) % opt.sample_step == 0:
# Save the generated image
# 采样保存生成的风格图像
denorm = transforms.Normalize((-2.12, -2.04, -1.80), (4.37, 4.46, 4.44))
img = target.clone().squeeze()
img = denorm(img).clamp_(0, 1)
torchvision.utils.save_image(img, 'D:/software/Anaconda3/doc/3D_Img/nnStyleTrans/output-{}.png'.format(step + 1))

# =============================================================== 定义程序运行参数
# argparse是一个命令行解析包,可以用来进行命令参数的设置
# 以往的超参数设置可以通过传递命令参数的方式实现

content = './images/wuenda.jpg'
style = './images/naruto.jpg'
os.makedirs("D:/software/Anaconda3/doc/3D_Img/nnStyleTrans/", exist_ok=True)

parser = argparse.ArgumentParser()
parser.add_argument('--content', type=str, default=content)
parser.add_argument('--style', type=str, default=style)
parser.add_argument('--max_size', type=int, default=600)
parser.add_argument('--total_step', type=int, default=5000)
parser.add_argument('--log_step', type=int, default=50)
parser.add_argument('--sample_step', type=int, default=50)
parser.add_argument('--content_weight', type=float, default=1) # alpha value
parser.add_argument('--style_weight', type=float, default=100) # beta value
parser.add_argument('--lr', type=float, default=0.003)

opt = parser.parse_args(args=[]) # 合成命令 # 注意jupyter中需要注意无参数则添加args=[]这句话
print(opt)

transfer(opt) # 运行风格迁移函数

效果如下:
Content图像和Style图像分别是Andrew Ng和Naruto:

《Pytorch - 神经风格转换》_加载

Generated图像是:

第350轮训练的输出

《Pytorch - 神经风格转换》_卷积_02


标签:opt,style,转换,image,step,content,Pytorch,神经,size
From: https://blog.51cto.com/u_12419595/5937520

相关文章

  • 《Pytorch - BP全连接神经网络模型》
    2020年10月4号,国内已经5号凌晨了,依然在家学习。今天是我写的第三个Pytorch程序,从今天起也算是入门了。这一次我想把之前自己手写的matlab实现的简易的传统的BP神经网络在......
  • Pytorch《LSTM模型》
    前面的博文我们讲了LSTM的原理与分析,这一篇我们用pytorch类LSTM做测试完整测试代码如下,用于进行MNIST数据集测试,主要学习LSTM类的输入输出维度。这里定义的LSTM模型是用了三......
  • 《Pytorch - CNN模型》
    2020年10月5号,依然在家学习。今天是我写的第四个Pytorch程序,这一次我想把之前基于PyTorch实现的简易的传统的BP全连接神经网络改写成CNN网络,想看看对比和效果差异。这一......
  • 《Pytorch - RNN模型》
    前言:之前的博文部分地讲解了RNN的标准结构,也用pytorch的RNNCell类和RNN类实现了前向传播的计算,这里我们再举一个例子,做一个特别简单特别简单特别简单特别简单的翻译器,目标......
  • 宏/VBA批量将文件夹下的csv文件转换成xlsx
    https://blog.csdn.net/weixin_43046974/article/details/120876697宏/VBA批量将文件夹下的csv文件转换成xlsx Subchange_CSV_to_XLSX() ChDir"C:\Users\Administrat......
  • Pytorch中Mnist数据集的加载
    训练集train_loader=torch.utils.data.DataLoader(datasets.MNIST('data',train=True,download=True,transform=transforms.Compose([......
  • 卷积神经网络之ResNet(2015)
    文章目录​​ResNet(2015)​​​​前言​​​​概要​​​​构思​​​​深度​​​​收敛​​​​退化​​​​解决​​​​相关工作​​​​残差表示​​​​快捷链接​​......
  • 卷积神经网络之早期架构
    文章目录​​早期架构​​​​lenet5架构​​​​小结​​​​代码​​​​DanCiresanNet​​​​后续几种网络的概要​​早期架构文档存放更新地址:​​https://github.co......
  • Linux系统Word转换PDF,文档字体乱码不显示问题解决
    Linux系统Word转换PDF,文档字体乱码不显示问题解决1、在windows目录C:\Windows\Fonts下找到字体文件。2、在linux上寻找Linux/usr/share/fonts/my_fonts目录,如果没有......
  • 通过ffmpeg将16k采样率的wav文件转换成采样率为8k的wav
    一安装win10的ffmpeg 二进入所在文件夹文件名字:temp.wavtrans_temp.pcmwav_name.wav先将wav转换成pcm文件ffmpeg-itemp.wav-acodecpcm_s16le-fs16l......