Изменения
→Описание алгоритма
[[Файл:Image1.jpeg|500px|thumb|right|[https://towardsdatascience.com/neural-style-transfer-tutorial-part-1-f5cd3315fa7f Рис. 1. Принцип работы алгоритма]]]
Алгоритм '''нейронного переноса стиля'''<ref>[https://arxiv.org/pdf/1508.06576.pdf Gatys, L.A., Ecker, A.S., Bethge, M.: A neural algorithm of artistic style, 2015]</ref> (англ. ''Neural Style Transfer''), разработанный Леоном Гатисом, Александром Экером и Матиасом Бетге, позволяет получить преобразует полученное на вход изображение и воспроизводить его в новом художественном стилесоответствии с выбранным стилем. Алгоритм берет три изображения, входное изображение (англ. ''input image''), изображение контента (англ. ''content image'') и изображение стиля (англ. ''style image''), и изменяет входные данные так, чтобы они соответствовали содержанию изображения контента и художественному стилю изображения стиля. Авторами в качестве модели сверточной нейронной сети предлагается использовать сеть [http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%B2%D0%B5%D1%80%D1%82%D0%BE%D1%87%D0%BD%D1%8B%D0%B5_%D0%BD%D0%B5%D0%B9%D1%80%D0%BE%D0%BD%D0%BD%D1%8B%D0%B5_%D1%81%D0%B5%D1%82%D0%B8[Сверточные_нейронные_сети#VGG | VGG16]].
== Принцип работы алгоритма ==
[[Файл:Image2.png|500px|thumb|right|[https://towardsdatascience.com/neural-style-transfer-tutorial-part-1-f5cd3315fa7f Рис. 2. Архитектура сверточной сети VGG16]]]
Рассмотрим 1-й [[Сверточные_нейронные_сети#Сверточный слой | сверточный слой ]] (англ. ''[http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%B2%D0%B5%D1%80%D1%82%D0%BE%D1%87%D0%BD%D1%8B%D0%B5_%D0%BD%D0%B5%D0%B9%D1%80%D0%BE%D0%BD%D0%BD%D1%8B%D0%B5_%D1%81%D0%B5%D1%82%D0%B8#.D0.A1.D0.B2.D0.B5.D1.80.D1.82.D0.BE.D1.87.D0.BD.D1.8B.D0.B9_.D1.81.D0.BB.D0.BE.D0.B9 convolution layer]'') VGG16, который использует ядро 3x3 и обучает 64 карты признаков (англ. ''feature map'') для генерации представления изображения размерности 224x224x64, принимая 3-канальное изображение размером 224x224 в качестве входных данных (''Рисунок 2''). Во время обучения эти карты признаков научились обнаруживать простые шаблоны, например, такие как прямые линии, окружности или даже не имеющие никакого смысла для человеческого глаза шаблоны, которые тем не менее имеют огромное значение для этой модели. Такое "обнаружение" шаблонов называется обучением представления признаков. Теперь давайте рассмотрим 10-й сверточный слой VGG16, который использует ядро 3x3 с 512 картами признаков для обучения и в итоге генерирует вывод представления изображения размерности 28x28x512. Нейроны 10-го слоя уже могут обнаруживать более сложные шаблоны такие как, например, колесо автомобиля, окно или дерево и т.д.
Собственно вышеперечисленные свойства характерны для любой [http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%B2%D0%B5%D1%80%D1%82%D0%BE%D1%87%D0%BD%D1%8B%D0%B5_%D0%BD%D0%B5%D0%B9%D1%80%D0%BE%D0%BD%D0%BD%D1%8B%D0%B5_%D1%81%D0%B5%D1%82%D0%B8 [Сверточные_нейронные_сети | сверточной нейронной сети]], работа которой обычно интерпретируется как переход от конкретных особенностей изображения к более абстрактным деталям, и далее к ещё более абстрактным деталям вплоть до выделения понятий высокого уровня. При этом сеть самонастраивается и вырабатывает необходимую иерархию абстрактных признаков (последовательности карт признаков), фильтруя маловажные детали и выделяя существенное.
Такая природа представления кодирования сама по себе является ключом к передаче стиля, который используется для вычисления функции потерь между сгенерированным изображением относительно изображения контента и изображения стиля. При обучении модели более десяти тысяч изображений на класс модель может генерировать аналогичное представление признаков для множества различных изображений, если они принадлежат к одному классу или имеют схожий контент или стиль.
<math>L_{content}(C, G, L) = \frac{1}{2} \sum\limits_{ij}(a[Ll](C)_{ij} - a[Ll](G)_{ij})^2</math>
Это поможет сохранить исходный контент в сгенерированном изображении, а также минимизировать разницу в представлении признаков, которое логически фокусируется на разнице между содержимым обоих изображений.
=== Матрица Грама ===
Рассмотрим, как мы передаем наше изображение стиля через VGG16 и получаем значения функции активации из 7-го уровня, который генерирует матрицу представления объектов размером 56x56x256.
В этом трехмерном массиве имеется 256 каналов размером 56x56 каждый. Теперь предположим, что есть канал ''A'', чьи блоки активации могут активироваться, когда они сталкиваются с разделом изображения, содержащим коричнево-черные полосы, а затем есть канал ''B'', чьи блоки активации могут активироваться, когда они сталкиваются с чем-то похожим на глазное яблоко. Если оба этих канала ''A'' и ''B'' активируются вместе для одного и того же входа, существует высокая вероятность того, что изображение может содержать лицо тигра (поскольку у него было два канала с высокими значениями, которые активируются для глазного яблока и коричнево-черных полос). Теперь, если оба эти канала будут запущены с высокими значениями активации, это означает, что они будут иметь высокую корреляцию по сравнению с корреляцией между каналом ''A'' и ''С'', где канал ''С'' может активироваться, когда он видит ромбовидный шаблон.
Таким образом, чтобы получить корреляцию всех этих каналов друг с другом, нам нужно вычислить нечто называемое матрицей Грама, будем использовать ее для измерения степени корреляции между каналами, которая позже будет служить мерой самого стиля. Рисунок 4 помогает лучше понять как рассчитывается матрица Грама на примере.
=== Функция потерь на основе корреляции матриц Грама ===
<math>L_{style}(S, G) = \sum\limits_{l=0}^L w_l * L_{GM}(S, G, l)</math>
== Пример кода на Python == Данный пример реализован на основе [http://neerc.ifmo.ru/wiki/index.php?title=%D0%9E%D0%B1%D0%B7%D0%BE%D1%80_%D0%B1%D0%B8%D0%B1%D0%BB%D0%B8%D0%BE%D1%82%D0%B5%D0%BA_%D0%B4%D0%BB%D1%8F_%D0%BC%D0%B0%D1%88%D0%B8%D0%BD%D0%BD%D0%BE%D0%B3%D0%BE_%D0%BE%D0%B1%D1%83%D1%87%D0%B5%D0%BD%D0%B8%D1%8F_%D0%BD%D0%B0_Python#.D0.91.D0.B8.D0.B1.D0.BB.D0.B8.D0.BE.D1.82.D0.B5.D0.BA.D0.B8_.D0.B4.D0.BB.D1.8F_.D0.B3.D0.BB.D1.83.D0.B1.D0.BE.D0.BA.D0.BE.D0.B3.D0.BE_.D0.BE.D0.B1.D1.83.D1.87.D0.B5.D0.BD.D0.B8.D1.8F открытой платформы глубокого обучения PyTorch ] '''Функция потери контента''' '''class''' ContentLoss(nn.Module): '''def''' __init__(self, target,): super(ContentLoss, self).__init__() <font color="green"># we 'detach' the target content from the tree used</font> <font color="green"># to dynamically compute the gradient: this is a stated value,</font> <font color="green"># not a variable. Otherwise the forward method of the criterion</font> <font color="green"># will throw an error.</font> self.target = target.detach() '''def''' forward(self, input): self.loss = F.mse_loss(input, self.target) return input '''Функция потери стиля''' '''def''' gram_matrix(input): a, b, c, d = input.size() <font color="green"># a=batch size(=1)</font> <font color="green"># b=number of feature maps</font> <font color="green"># (c,d)=dimensions of a f. map (N=c*d)</font> features = input.view(a * b, c * d) <font color="green"># resise F_XL into \hat F_XL</font> G = torch.mm(features, features.t()) <font color="green"># compute the gram product</font> <font color="green"># we 'normalize' the values of the gram matrix</font> <font color="green"># by dividing by the number of element in each feature maps.</font> return G.div(a * b * c * d) '''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 '''Инициализация модели''' cnn = models.vgg19(pretrained=True).features.to(device).eval() '''Нормализация''' 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) <font color="green"># create a module to normalize input image so we can easily put it in a</font> <font color="green"># nn.Sequential</font> '''class''' Normalization(nn.Module): '''def''' __init__(self, mean, std): super(Normalization, self).__init__() <font color="green"># .view the mean and std to make them [C x 1 x 1] so that they can</font> <font color="green"># directly work with image Tensor of shape [B x C x H x W].</font> <font color="green"># B is batch size. C is number of channels. H is height and W is width.</font> self.mean = torch.tensor(mean).view(-1, 1, 1) self.std =torch.tensor(std).view(-1, 1, 1) '''def''' forward(self, img): <font color="green"># normalize img</font> return (img - self.mean) / self.std
'''Запуск алгоритма''' '''def''' run_style_transfer(cnn, normalization_mean, normalization_std, content_img, style_img, input_img, num_steps=300, style_weight=1000000, content_weight=1): 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) run = [0] while run[0] <= num_steps: def forwardclosure(): <font color="green"># correct the values of updated input image</font> input_img.data.clamp_(self0, input1) optimizer.zero_grad() model(input_img) style_score = 0 content_score = 0 for sl in style_losses: self style_score += sl.loss for cl in content_losses: content_score += Fcl.mse_lossloss 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(input), selfcontent_score.targetitem())) print() return style_score + content_score optimizer.step(closure) <font color="green"># a last correction...</font> input_img.data.clamp_(0, 1) return inputinput_img <font color="green"># run style transfer</font> output = run_style_transfer(cnn, cnn_normalization_mean, cnn_normalization_std, content_img, style_img, input_img)
==См. также==