Чтобы просмотреть содержимое статьи с изображениями, перейдите наStudylove.com/py факел-1.4…
В этом руководстве показано, как реализовать алгоритм Neural-Style, разработанный Леоном А. Гатисом. Neural-Style, или Neural-Transfer, позволяет вам использовать новый художественный стиль изображения и представления изображения. Алгоритм принимает три изображения: входное изображение, изображение-содержимое и изображение-стиль и модифицирует входные данные таким образом, чтобы содержимое изображения-содержимого и художественный стиль изображения-стиля были похожи. содержание1
основной принцип
Принцип прост: мы определяем два расстояния, одно для содержания (DC ), один для стилей (DS). DC измеряет, насколько отличается содержимое между двумя изображениями, в то время как DS
Измерьте, насколько разные стили между двумя изображениями. Затем мы берем третье изображение в качестве входных данных и преобразовываем его, чтобы минимизировать расстояние его содержимого от изображения содержимого и расстояние стиля от изображения стиля. Теперь мы можем импортировать необходимые пакеты и запустить нейронную передачу. Импорт пакетов и выбор устройств
Перечисленные ниже пакеты — это все пакеты, используемые для реализации нейронной передачи.
torch, torch.nn, numpy (用PyTorch神经网络不可缺少的软件包)
torch.optim (高效的梯度下降算法优化包)
PIL, PIL.Image, matplotlib.pyplot (加载和展示图像的包)
torchvision.transforms (把 PIL 图像转换为tensors)
torchvision.models (训练 和 加载 预训练的模型)
copy (深度拷贝模型; system package)
from __future__ import print_function
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from PIL import Image
import matplotlib.pyplot as plt
import torchvision.transforms as transforms
import torchvision.models as models
import copy
Далее нам нужно выбрать, на каком устройстве запустить сеть, и импортировать контент и изображения стилей. Запуск алгоритма нейронной передачи на больших изображениях занимает больше времени и работает намного быстрее на графических процессорах. Мы можем использовать torch.cuda.is_available(), чтобы определить, доступен ли графический процессор. Затем мы настраиваем torch.device для использования на протяжении всего руководства. Кроме того, метод .to(device) используется для перемещения тензора или модуля на нужное устройство.
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
загрузить изображение
Теперь мы будем импортировать изображения стилей и изображения содержимого. Исходные изображения PIL имеют значения от 0 до 255, но при преобразовании в тензоры факелов их значения преобразуются в значения от 0 до 1. Изображения также должны быть изменены, чтобы иметь одинаковые размеры. Важно отметить, что значения тензоров нейронных сетей в библиотеке torch варьируются от 0 до 1. Если вы попытаетесь скормить сети тензорное изображение со значениями от 0 до 255, активированная карта признаков не будет воспринимать ожидаемое содержимое и стиль. Однако предварительно обученные сети из библиотеки Caffe обучены тензорным изображениям от 0 до 255.
Note
Вот ссылки для загрузки двух изображений, используемых в этом уроке: picasso.jpg и dance.jpg Загрузите эти два изображения и поместите их в папку с именем images в вашем текущем рабочем каталоге.
# 输出图像的期望尺寸
imsize = 512 if torch.cuda.is_available() else 128 # 如果没有GPU的话,就把尺寸搞小点儿
loader = transforms.Compose([
transforms.Resize(imsize), # 缩放导入的图像
transforms.ToTensor()]) # 把它转换成 torch tensor
def image_loader(image_name):
image = Image.open(image_name)
# 虚拟的 batch 维 ,为了满足网络输入对纬度的要求
image = loader(image).unsqueeze(0)
return image.to(device, torch.float)
style_img = image_loader("./data/images/neural-style/picasso.jpg")
content_img = image_loader("./data/images/neural-style/dancing.jpg")
assert style_img.size() == content_img.size(), \
"we need to import style and content images of the same size"
Теперь давайте создадим функцию, которая отображает изображение, преобразуя копию изображения в формат PIL и используя plt.imshow для отображения копии. Мы попытаемся отобразить изображение содержимого и изображение стиля, чтобы убедиться, что они импортированы правильно.
unloader = transforms.ToPILImage() # 再次转换为 PIL image
plt.ion()
def imshow(tensor, title=None):
image = tensor.cpu().clone() # we clone the tensor to not do changes on it
image = image.squeeze(0) # remove the fake batch dimension
image = unloader(image)
plt.imshow(image)
if title is not None:
plt.title(title)
plt.pause(0.001) # pause a bit so that plots are updated
plt.figure()
imshow(style_img, title='Style Image')
plt.figure()
imshow(content_img, title='Content Image')
функция потерь
Потеря контента
Потеря содержимого — это функция, представляющая взвешенное расстояние до содержимого отдельного слоя. Функция получает и обрабатывает вход X Карта функций FXL уровня L сети, возвращающая входное изображение X и изображение контента C. Карта признаков (FCL) изображения контента должна быть известна, чтобы можно было вычислить расстояние контента. Мы реализуем эту функцию как модуль факела с конструктором, который принимает FCL в качестве входных данных. Расстояние ∥FXL−FCL∥2
— среднеквадратическая ошибка между двумя наборами карт объектов, которую можно рассчитать с помощью nn.MSELoss.
Мы добавим этот модуль потери контента сразу после сверточного слоя, который вычисляет расстояние до контента. Таким образом, каждый раз, когда сеть получает входное изображение, потери контента будут рассчитываться на требуемом слое, а из-за автоградации будут рассчитываться все градиенты. Теперь, чтобы сделать слой потери контента прозрачным, нам нужно определить прямой метод, который вычисляет потерю контента и возвращает входные данные слоя. Рассчитанный убыток сохраняется как параметр модуля.
class ContentLoss(nn.Module):
def __init__(self, target,):
super(ContentLoss, self).__init__()
# we 'detach' the target content from the tree used
# to dynamically compute the gradient: this is a stated value,
# not a variable. Otherwise the forward method of the criterion
# will throw an error.
self.target = target.detach()
def forward(self, input):
self.loss = F.mse_loss(input, self.target)
return input
Note
Важная деталь: хотя этот модуль называется ContentLoss, он не является настоящей функцией потери PyTorch. Если вы хотите определить потерю контента как функцию потери PyTorch, вам необходимо создать функцию автоматического градиента PyTorch, чтобы градиенты рассчитывались/реализовывались вручную в обратном методе.
Потеря стиля
Реализация модуля потери стиля аналогична реализации модуля потери контента. Он действует как прозрачный слой в сети, чтобы вычислить потерю стиля этого слоя, нам нужно вычислить граммовую матрицу GXL. . Матрица граммов — это результат умножения данной матрицы и транспонирования этой матрицы. В этом приложении данная матрица представляет собой измененную версию карты объектов FXL слоя L. FXL преобразуется в F^XL, матрицу KxN, где K — количество карт объектов слоя L, а N — длина произвольно векторизованной карты объектов FkXL. Например, первая строка F^XL соответствует первой векторизованной карте объектов F1XL.
.
В конечном итоге матрица граммов должна быть нормализована путем деления каждого элемента на общее количество элементов в матрице. Эта нормализация должна компенсировать большие N Размер F^XL
Дело в том, что матрица выдает большие значения в матрице граммов. Такое особенно большое значение приведет к тому, что предыдущие слои (слои перед объединяющим слоем) будут оказывать значительное влияние на процесс градиентного спуска. Особенности стиля, как правило, находятся на более глубоких уровнях сети, поэтому этот шаг нормализации чрезвычайно важен.
def gram_matrix(input):
a, b, c, d = input.size() # a=batch size(=1)
# b=number of feature maps
# (c,d)=dimensions of a f. map (N=c*d)
features = input.view(a * b, c * d) # resise F_XL into \hat F_XL
G = torch.mm(features, features.t()) # compute the gram product
# we 'normalize' the values of the gram matrix
# by dividing by the number of element in each feature maps.
return G.div(a * b * c * d)
Теперь модуль потери стиля выглядит почти так же, как и модуль потери контента. Использование GXL и GSL
Вычислите расстояние стиля между среднеквадратичными ошибками.
class StyleLoss(nn.Module):
def __init__(self, target_feature):
super(StyleLoss, self).__init__()
self.target = gram_matrix(target_feature).detach()
def forward(self, input):
G = gram_matrix(input)
self.loss = F.mse_loss(G, self.target)
return input
импортная модель
Теперь нам нужно ввести предварительно обученную нейронную сеть. Мы будем использовать 19-слойную сеть VGG, подобную той, что использовалась в статье.
VGG, реализованный PyTorch, представляет собой модуль, который разделен на два подчиненных модуля Sequential: функции (содержит сверточные слои и слои пула) и классификатор (содержит полностью связанные слои). мы будем использоватьfeatures
модуль, так как нам нужны выходные данные отдельных сверточных слоев для измерения потери контента и потери стиля. Некоторые слои ведут себя иначе во время обучения, чем при оценке, поэтому мы должны использовать .eval(), чтобы перевести сеть в режим оценки.
cnn = models.vgg19(pretrained=True).features.to(device).eval()
Кроме того, сеть VGG обучается на изображениях, каждый канал которых нормализован средним значением = [0,485, 0,456, 0,406] и стандартным значением = [0,229, 0,224, 0,225]. Мы будем использовать их для нормализации изображения, а затем отправим нормализованное изображение в сеть для обработки.
cnn_normalization_mean = torch.tensor([0.485, 0.456, 0.406]).to(device)
cnn_normalization_std = torch.tensor([0.229, 0.224, 0.225]).to(device)
# 创建一个module去归一化输入图像,以便我们可以简单滴将它们送给 nn.Sequential 。
class Normalization(nn.Module):
def __init__(self, mean, std):
super(Normalization, self).__init__()
# .view the mean and std to make them [C x 1 x 1] so that they can
# directly work with image Tensor of shape [B x C x H x W].
# B is batch size. C is number of channels. H is height and W is width.
self.mean = torch.tensor(mean).view(-1, 1, 1)
self.std = torch.tensor(std).view(-1, 1, 1)
def forward(self, img):
# normalize img
return (img - self.mean) / self.std
Последовательный модуль содержит упорядоченный список дочерних модулей. Например, vgg19.features содержит последовательность (Conv2d, ReLU, MaxPool2d, Conv2d, ReLU…), выровненную в правильном порядке глубины Нам нужно добавить слои потери контента и потери стиля сразу после обнаруженных ими сверточных слоев. Для этого нам нужно создать новый модуль Sequential с правильно вставленными модулями потери контента и модуля потери стиля.
# 计算 style/content losses 所需要的深度的层:
content_layers_default = ['conv_4']
style_layers_default = ['conv_1', 'conv_2', 'conv_3', 'conv_4', 'conv_5']
def get_style_model_and_losses(cnn, normalization_mean, normalization_std,
style_img, content_img,
content_layers=content_layers_default,
style_layers=style_layers_default):
cnn = copy.deepcopy(cnn)
# normalization module
normalization = Normalization(normalization_mean, normalization_std).to(device)
# just in order to have an iterable access to or list of content/syle
# losses
content_losses = []
style_losses = []
# assuming that cnn is a nn.Sequential, so we make a new nn.Sequential
# to put in modules that are supposed to be activated sequentially
model = nn.Sequential(normalization)
i = 0 # increment every time we see a conv
for layer in cnn.children():
if isinstance(layer, nn.Conv2d):
i += 1
name = 'conv_{}'.format(i)
elif isinstance(layer, nn.ReLU):
name = 'relu_{}'.format(i)
# The in-place version doesn't play very nicely with the ContentLoss
# and StyleLoss we insert below. So we replace with out-of-place
# ones here.
layer = nn.ReLU(inplace=False)
elif isinstance(layer, nn.MaxPool2d):
name = 'pool_{}'.format(i)
elif isinstance(layer, nn.BatchNorm2d):
name = 'bn_{}'.format(i)
else:
raise RuntimeError('Unrecognized layer: {}'.format(layer.__class__.__name__))
model.add_module(name, layer)
if name in content_layers:
# add content loss:
target = model(content_img).detach()
content_loss = ContentLoss(target)
model.add_module("content_loss_{}".format(i), content_loss)
content_losses.append(content_loss)
if name in style_layers:
# add style loss:
target_feature = model(style_img).detach()
style_loss = StyleLoss(target_feature)
model.add_module("style_loss_{}".format(i), style_loss)
style_losses.append(style_loss)
# now we trim off the layers after the last content and style losses
for i in range(len(model) - 1, -1, -1):
if isinstance(model[i], ContentLoss) or isinstance(model[i], StyleLoss):
break
model = model[:(i + 1)]
return model, style_losses, content_losses
Далее мы выбираем входное изображение. В качестве входного изображения можно использовать копию изображения содержимого или изображение белого шума.
input_img = content_img.clone()
# 如果你想使用白噪声,就去掉下面这行代码的注释:
# input_img = torch.randn(content_img.data.size(), device=device)
# 把原始输入图像加入到 figure 中:
plt.figure()
imshow(input_img, title='Input Image')
градиентный спуск
Авторы этого алгоритма предлагают использовать алгоритм L-BFGS для запуска градиентного спуска. Вместо обучения сети мы хотим обучить входные изображения, чтобы минимизировать потери контента/стиля. Мы создадим оптимизатор PyTorch L-BFGS, optim.LBFGS, и передадим ему наше изображение в качестве оптимизируемого тензора.
def get_input_optimizer(input_img):
# this line to show that input is a parameter that requires a gradient
optimizer = optim.LBFGS([input_img.requires_grad_()])
return optimizer
Наконец, мы должны определить функцию, которая выполняет нейронную передачу. Для каждой итерации сети она получает обновленные входные данные и вычисляет новые потери. Мы запустим обратный метод каждого модуля потерь, чтобы динамически вычислить их градиенты. Оптимизатору требуется функция «закрытия», которая переоценивает модуль и возвращает потерю.
У нас есть одно последнее ограничение, которое нужно решить. Сеть может попытаться оптимизировать входные значения, превышающие тензорный диапазон изображения от 0 до 1. Мы можем это исправить, корректируя входные значения от 0 до 1 каждый раз при запуске сети.
def run_style_transfer(cnn, normalization_mean, normalization_std,
content_img, style_img, input_img, num_steps=300,
style_weight=1000000, content_weight=1):
"""Run the style transfer."""
print('Building the style transfer model..')
model, style_losses, content_losses = get_style_model_and_losses(cnn,
normalization_mean, normalization_std, style_img, content_img)
optimizer = get_input_optimizer(input_img)
print('Optimizing..')
run = [0]
while run[0] <= num_steps:
def closure():
# correct the values of updated input image
input_img.data.clamp_(0, 1)
optimizer.zero_grad()
model(input_img)
style_score = 0
content_score = 0
for sl in style_losses:
style_score += sl.loss
for cl in content_losses:
content_score += cl.loss
style_score *= style_weight
content_score *= content_weight
loss = style_score + content_score
loss.backward()
run[0] += 1
if run[0] % 50 == 0:
print("run {}:".format(run))
print('Style Loss : {:4f} Content Loss: {:4f}'.format(
style_score.item(), content_score.item()))
print()
return style_score + content_score
optimizer.step(closure)
# a last correction...
input_img.data.clamp_(0, 1)
return input_img
Наконец, мы можем запустить алгоритм
output = run_style_transfer(cnn, cnn_normalization_mean, cnn_normalization_std,
content_img, style_img, input_img)
plt.figure()
imshow(output, title='Output Image')
# sphinx_gallery_thumbnail_number = 4
plt.ioff()
plt.show()