Човешкото същество се справя изключително добре в създаването на уникален стил за произведения на изкуството, но е много трудно да накараш компютър да научи нечий стил. Тъй като „стил“ е толкова абстрактно понятие, няма ясна дефиниция за „стил“. Позовавайки се на статията на Gatys et al, той предлага алгоритъм за прилагане на стил на изображение върху дадено изображение на съдържание. Ще го приложим в тази статия.

Трансфер на невронен стил. Gatys и др. (2015) (https://arxiv.org/abs/1508.06576)

На първо място, ще трябва да изберем устройство за изпълнение на модела, ще бъде много по-бързо да стартираме мрежата на GPU, отколкото на CPU. Тук проверяваме дали GPU е наличен за обучение.

Помощни функции

Сега изграждаме помощна функция за зареждане/показване на изображение, първо преоразмеряваме изображението до желаната от нас резолюция и го конвертираме в тензор на PyTorch. Оригиналното изображение на PIL има стойност от 0 до 255 за всички RGB канали, а тензорът на PyTorch ще го трансформира в 0 в 1. Важно е да разберете, че всички модели на PyTorch са обучени с диапазон на стойност от 0 до 1.

Изображение на съдържанието и изображение на стил

В тази статия ще зададем снимката на пристанище Виктория със звезден ферибот на Хонконг като изображение на съдържание и ще приемем маджонг като наш стил на желание. Тук имаме два известни артикула, които представляват моя роден град Хонг Конг, нека видим как можем да приложим маджонг стила в пристанището Виктория. И двете снимки са показани по-долу.

Нормализиране на изображението

Друго важно скрито правило е, че моделът VGG19, предоставен от PyTorch, се обучава с нормализирано изображение. Така че ние също ще трябва да нормализираме нашето съдържание и стилово изображение, преди да го пуснем в мрежата, в противен случай ще получите неочакван резултат и ще бъде много трудно да забележите проблема.

Как работи

За да започнем, трябва да дефинираме какво е „стил“. Авторът на статията предложи да разделим проблема на два: загуба на съдържание и загуба на стил. Съдържанието измерва разстоянието между генерираното изображение и изображението на съдържанието, докато загубата на стил измерва разстоянието между генерираното изображение и стиловото изображение. Като направим баланс между двете загуби, да се надяваме, че ще успеем да генерираме изображение някъде по средата. За разлика от нормалния модел на CNN, ние не тренираме с етикети, а вместо това ще коригираме теглото на модела и ще оптимизираме изходното изображение.

Загуба на съдържание

Загубата на съдържание е ясна, това е просто загубата на квадратна грешка между представянето на две функции. Загубата се умножава по 1/2 само за по-лесно изчисляване на производната. Но не е необходимо, затова го пропускаме в нашия пример.

Загуба на стил

Ето каква е интересната част, как да дефинираме стила? Стилът може да се дефинира като някакъв модел или текстура, може да са извивки, ръбове, цвят… и т.н. Звучи ми познато? Да, невронната мрежа има многослойна структура, всеки от слоевете ще може да улови някакъв вид представяне на характеристики. В плиткия слой невронната мрежа обикновено улавя прост модел. По-дълбоката мрежа ще може да научи сложна структура, която е съставена от прост модел от предишния слой. Авторът предложи да използваме функцията на средния слой и тук ви показваме загубата на стил.

Грам матрица

Дадена е матрица F, която е представянето на характеристиките на слоя „l“ на модела. Като направите вътрешно произведение между F и F транспониране, ще получите грам матрица. Той представлява връзката между характеристиките.

Загубата на стил се получава чрез средноквадратичното разстояние между елементите на матрицата на Грам от оригиналното изображение и матрицата на Грам на изображението, което ще се генерира. Нека „a“ и „g“ представляват представянето на характеристиките на слой „l“. След това изчисляваме съответната грам матрица „A“ и „G“. Тогава квадратната грешка ще бъде нормализирана. Отново 1/4 е само за по-лесно получаване на производната.

Второ, няма да вземем просто един слой функции. Но вместо това минимизиране на загубата на множество слоеве едновременно с претегляне на слой.

Извличане на характеристики

Позовавайки се на статията, авторът предлага да се извлече характеристика от първия слой с пет навивки. Наричаме го conv_1, conv_2, conv_3, conv_4 и conv_5, което съответства на слой 0, 5, 10, 19, 28 в модела PyTorch VGG19, който ще видим по-късно.

Обучете модела (LBFGS)

Ето основния поток на обучението:

  1. Получаване на функцията на средния слой на стиловото изображение
  2. Получаване на функцията на средния слой на изображението на съдържанието

LOOP (обучителна стъпка)

5. Получаване на функцията на средния слой на изходното изображение

6. Намерете загубата на съдържание

7. Намерете загубата на стил

8. Изчислете общата загуба: алфа загуба на съдържание + загуба на бета стил

9. Извършете едностъпково градиентно спускане върху „Изходно изображение“

Според статията авторът използва LBFGS за оптимизиране на изхода. Той обаче не посочи причината за избора си.

Обучете модела (ADAM)

Можем също да оптимизираме изходното изображение с ADAM, работният поток е повече или по-малко същият като в предишния пример.

Изтеглете предварително обучения модел VGG19

Изтеглете предварително обучения модел VGG19. Заменяме тези ReLU слоеве с такива, които не са на място, тъй като версията на място не работи много добре, когато се опитваме да получим характеристиките на средния слой.

Sequential(
  (0): Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  (1): ReLU()
  (2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  (3): ReLU()
  (4): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  (5): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  (6): ReLU()
  (7): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  (8): ReLU()
  (9): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  (10): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  (11): ReLU()
  (12): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  (13): ReLU()
  (14): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  (15): ReLU()
  (16): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  (17): ReLU()
  (18): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  (19): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  (20): ReLU()
  (21): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  (22): ReLU()
  (23): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  (24): ReLU()
  (25): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  (26): ReLU()
  (27): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  (28): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
)

Резултат от трансфер на стил с помощта на LBFGS

И накрая, ние сме готови да генерираме изображение, започвайки от изображение на съдържание, използвайки оптимизатора LBFGS. Всеки стилов слой се претегля с 1/5, както е предложено от автора. Теглото на слоя всъщност е променлив хиперпараметър.

Starting transfer...
Iteration[100] Total loss: 1929.011719 - (content: 450.000916, style 1479.010864)
Iteration[200] Total loss: 1141.951538 - (content: 463.555481, style 678.396057)
Iteration[300] Total loss: 791.199158 - (content: 459.424805, style 331.774353)
Iteration[400] Total loss: 749.459595 - (content: 461.496948, style 287.962646)
Iteration[500] Total loss: 1082.324951 - (content: 444.428894, style 637.896118)
Finished transfer...

Резултат от трансфер на стил с помощта на ADAM

И можем също да генерираме изхода с помощта на ADAM и този път се опитваме да започнем с произволен бял шум. Да видим какво ще получим.

Starting transfer...
Iteration[100] Total loss: 489.444641 - (content: 232.080826, style 257.363831)
Iteration[200] Total loss: 388.175659 - (content: 217.801422, style 170.374222)
Iteration[300] Total loss: 349.264343 - (content: 209.968674, style 139.295654)
Iteration[400] Total loss: 327.177948 - (content: 204.975128, style 122.202827)
Iteration[500] Total loss: 312.928192 - (content: 201.571472, style 111.356712)
Iteration[600] Total loss: 302.883362 - (content: 198.948883, style 103.934494)
Iteration[700] Total loss: 295.332245 - (content: 196.794342, style 98.537895)
Iteration[800] Total loss: 289.500610 - (content: 195.026703, style 94.473900)
Iteration[900] Total loss: 284.735077 - (content: 193.422012, style 91.313065)
Iteration[1000] Total loss: 280.884064 - (content: 192.166962, style 88.717110)
Iteration[1100] Total loss: 277.672119 - (content: 191.090607, style 86.581512)
Iteration[1200] Total loss: 274.882874 - (content: 190.064362, style 84.818512)
Iteration[1300] Total loss: 272.421234 - (content: 189.194229, style 83.227005)
Iteration[1400] Total loss: 270.255920 - (content: 188.385971, style 81.869942)
Iteration[1500] Total loss: 268.334473 - (content: 187.646240, style 80.688232)
Iteration[1600] Total loss: 266.598938 - (content: 186.970596, style 79.628349)
Iteration[1700] Total loss: 265.058807 - (content: 186.353195, style 78.705612)
Iteration[1800] Total loss: 263.676880 - (content: 185.819397, style 77.857498)
Iteration[1900] Total loss: 262.423462 - (content: 185.330231, style 77.093231)
Iteration[2000] Total loss: 261.278259 - (content: 184.863541, style 76.414734)
Iteration[2100] Total loss: 260.220673 - (content: 184.496017, style 75.724663)
Iteration[2200] Total loss: 259.233124 - (content: 184.007675, style 75.225456)
Iteration[2300] Total loss: 258.319275 - (content: 183.757706, style 74.561577)
Iteration[2400] Total loss: 257.470093 - (content: 183.619034, style 73.851074)
Iteration[2500] Total loss: 256.676697 - (content: 183.516342, style 73.160355)
Iteration[2600] Total loss: 255.935364 - (content: 183.571335, style 72.364037)
Iteration[2700] Total loss: 255.206375 - (content: 183.285263, style 71.921112)
Iteration[2800] Total loss: 254.505219 - (content: 182.278564, style 72.226662)
Iteration[2900] Total loss: 253.956146 - (content: 183.996185, style 69.959953)
Iteration[3000] Total loss: 253.355484 - (content: 184.029602, style 69.325882)
Iteration[3100] Total loss: 252.644409 - (content: 179.877167, style 72.767235)
Iteration[3200] Total loss: 252.278503 - (content: 183.824402, style 68.454094)
Iteration[3300] Total loss: 251.668427 - (content: 178.669907, style 72.998528)
Iteration[3400] Total loss: 251.086090 - (content: 181.005371, style 70.080727)
Iteration[3500] Total loss: 250.745728 - (content: 182.713974, style 68.031746)
Iteration[3600] Total loss: 250.370544 - (content: 176.382141, style 73.988411)
Iteration[3700] Total loss: 249.884674 - (content: 183.262207, style 66.622475)
Iteration[3800] Total loss: 249.469055 - (content: 176.172562, style 73.296486)
Iteration[3900] Total loss: 248.990631 - (content: 182.117065, style 66.873566)
Iteration[4000] Total loss: 248.512497 - (content: 178.094940, style 70.417557)
Iteration[4100] Total loss: 248.176697 - (content: 180.735245, style 67.441452)
Iteration[4200] Total loss: 247.894470 - (content: 176.769608, style 71.124855)
Iteration[4300] Total loss: 247.552078 - (content: 180.386200, style 67.165878)
Iteration[4400] Total loss: 247.346893 - (content: 175.786301, style 71.560585)
Iteration[4500] Total loss: 247.139633 - (content: 181.971237, style 65.168396)
Iteration[4600] Total loss: 246.928680 - (content: 175.374069, style 71.554611)
Iteration[4700] Total loss: 246.467590 - (content: 178.710480, style 67.757118)
Iteration[4800] Total loss: 246.204773 - (content: 179.035278, style 67.169487)
Iteration[4900] Total loss: 246.128571 - (content: 174.557266, style 71.571304)
Iteration[5000] Total loss: 245.969849 - (content: 181.300140, style 64.669701)
Finished transfer...

Ключове за вкъщи

  • Ние внедряваме различни функции за загуба и използваме обратно разпространение, за да прехвърлим стил върху изображението на нашето съдържание.
  • За да направим това, ние използваме предварително обучен модел VGG19 и извличаме представянето на характеристиките от средния слой
  • Пробваме различен оптимизатор (LBFGS & ADAM)
  • Демонстрирайте възможността за обучение от случаен бял шум и изображението на съдържанието

От моя гледна точка това е много примитивен метод за машинно научаване на стил, но е определена добра отправна точка за развитие. Има твърде много хиперпараметри, като теглото на стиловия слой, съотношението алфа и бета, избор на слой за загуба на съдържание… и т.н. И всеки път, когато промените или изображението на съдържанието, или изображението на стила, ще откриете, че предишната ви настройка не винаги генерира добър резултат, много често ще трябва да отделите време, за да настроите тези хиперпараметри отново. Методът всъщност не е общоприето решение за приложение.

Благодаря за четенето!