Въведение

Това е втора частот поредица за използване на CLIP от нулата за оценка и манипулиране на изображения чрез сравняването им с текстови подкани. Част първа можете да намерите тук:



В последната публикация демонстрирах как да сравнявам текстова подкана в набор от цветове и да визуализирам колко добре всеки отделен нюанс съответства на текстовата подкана. В този урок ще демонстрирам как можем да оптимизираме цвят, за да съответства възможно най-добре на текста. За да направим това, ще напишем персонализиран Модул с помощта на PyTorch.

Можете да следвате този бележник на Colab, за да работите с кода интерактивно, и можете също да опитате модела в действие в това Hugging Face Space, което създадох с помощта на Gradio. В тази публикация ще дам някои коментари и обяснения за кода, необходим за написване на модела и обучителния цикъл.



Подклас от torch.nn.Module:

Първото нещо, което правим, е да създадем нов клас, RGBModel, като подклас на класа Module на PyTorch. Ако не сте запознати с идеята за класове и наследяване в Python (или друг език), това е като да създадем собствена рецепта за модел чрез адаптиране от някои основни градивни елементи.

Класът Module се грижи за много функционалности на ниско ниво в PyTorch и ние просто добавяме няколко персонализирани неща върху него.

class RGBModel(torch.nn.Module):
    pass

Дефинирайте __init__ метод:

Първо, трябва да дефинираме нашия инициализатор, който се извиква всеки път, когато създаваме нов екземпляр на този клас, т.е. когато пишем нещо като model = RGBModel().

class RGBModel(torch.nn.Module):
    def __init__(self, device):
      # Call nn.Module.__init__() to instantiate typical torch.nn.Module stuff
      super(RGBModel, self).__init__()
			color = torch.ones(size=(1,3,1,1), device=device) / 2 
      self.color = torch.nn.Parameter(color)

Първото нещо, което нашият __init__ метод прави, е да извика стандартния __init__ метод от torch.nn.Module, който е нашият „родителски“ клас или суперклас. Това прави super(RGBModel, self).__init__(). Това обработва всички видове стандартни неща за инициализация на PyTorch, които трябва да започнем.

След това дефинираме Parameter за нашия модел. Това ще запази RGB стойността, която оптимизираме в тренировъчния цикъл. Първо създаваме тензор от всички единици и с форма (1,3,1,1), като използваме torch.ones. Не забравяйте, че PyTorch обикновено очаква изображения във формат NCHW. Това означава, че задаваме нашия тензор като стек от изображения, съдържащи едно RGB изображение с ширина и височина от един пиксел. Можем да се справим с преоформянето на този параметър по-късно, но това ще бъде по-удобно за нас надолу по веригата, когато дойде времето да преоразмерим пиксела до входната разделителна способност за енкодера на изображения на CLIP.

След това предаваме този тензор в torch.nn.Parameter и съхраняваме този обект като атрибут. По този начин той ще се запази с течение на времето и можем да получим достъп до него чрез други методи.

Дефинирайте forward пропуск:

class RGBModel(torch.nn.Module):
    def __init__(self, device):
      # Call nn.Module.__init__() to instantiate typical torch.nn.Module stuff
      super(RGBModel, self).__init__()
			color = torch.ones(size=(1,3,1,1), device=device) / 2 
      self.color = torch.nn.Parameter(color)
    def forward(self):
      # Clamp numbers to the closed interval [0,1]
      self.color.data = self.color.data.clamp(0,1)
      return self.color

След това дефинираме какво всъщност прави моделът, когато бъде извикан. Ако __init__ е това, което се случва, когато напишем model = RGBModel(), тогава forward диктува какво се случва, когато след това извикаме model(). В много случаи можем да мислим за това като за стъпка на „прогноза“ или „генериране“, но в крайна сметка това е, което моделът всъщност извежда.

За нас подаването напред е доста просто. Моделът трябва просто да изведе цвета си. Ние не искаме forward да се справи с превръщането на този цвят в изображение или нещо подобно. Единственото нещо, което трябва да направим, е да гарантираме, че цветът ни остава в подходящ диапазон по време на тренировъчния процес. Като такъв, ние пишем self.color.data = self.color.data.clamp(0, 1), за да ограничим нашия модел до затворения интервал [0, 1].

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

Искате ли да видите еволюцията на арт проекти, генерирани от AI? Посетете нашия публичен проект, за да видите изтичане на времето, еволюции на експерименти и много повече!

Създайте оптимизатор

След като нашият модел е готов за работа, е време да създадем обект за оптимизиране. Ще използваме оптимизатора AdamW. За повече информация, тази публикация в блог е страхотно обобщение на алгоритъма AdamW и неговия предшественик, Adam.

# Create optimizer
opt = torch.optim.AdamW([rgb_model()],
											   r=adam_learning_rate,
												 weight_decay=adam_weight_decay)

По принцип това, което трябва да знаем е, че AdamW дефинира стратегия за изпълнение на постепенни, итеративни актуализации на нашия color параметър по време на процеса на обучение.

Тук предоставяме два хиперпараметъра на оптимизатора, когато го създаваме: скорост на обучение и стойност на затихване на теглото. Най-общо казано, скоростта на обучение описва мащаба на актуализациите, които всяка стъпка на обучение трябва да направи (по-висока скорост = по-големи стъпки), а намаляването на теглото задвижва процес, чрез който тези стъпки на актуализация се свиват с времето.

В контекста на нашия модел, оптимизаторът ще ни помогне да ни каже нещо от рода на „ако искате да накарате вашето color да съответства на тази подкана, трябва да увеличите червената стойност.' Или по-конкретно, ще ни каже нещо като „ако добавите нещо към цвета си в посоката, да речем, (0.1, -0.1, 0.1), това ще увеличи сходството най-бързо.“ След това скоростта на обучение влиза в игра, като променя колко голямо е това увеличение. С течение на времето искаме да предприемем по-малки, по-прецизни стъпки, така че оптимизаторът прилага отслабване на теглото, за да направи точно това.

Генериране на целево вграждане

Имаме модел и оптимизатор. Към какво оптимизираме? Нека настроим нашата цел.

# Create target embedding
with torch.no_grad():
    tokenized_text = clip.tokenize(text_prompt).to(device=DEVICE)
    target_embedding = model.encode_text(tokenized_text).detach().clone()

Това трябва да изглежда познато, ако сте чели „първа част“ от тази поредица. Но искам да отбележа незадължителна стъпка, която предприехме тук, като изчислихме този кодиран текст с помощта на torch.no_grad манипулатор на контекст. За какво става въпрос?

По принцип PyTorch и други библиотеки за дълбоко обучение използват нещо, наречено автоматично диференциране, за да следят градиентите/производните на тензорите, докато се движат през изчислителна графика. Автоматичното диференциране опростява много изчисления, когато е необходимо, но използва повече памет в процеса.

Абсолютно необходимо е това да бъде активирано за параметъра color на нашия RGBModel, тъй като трябва да изчислим градиента на (все още недефинираната) функция за загуба, за да актуализираме цвета по време на обучение. Не е необходимо обаче да вземаме градиента на каквото и да било по отношение на нашата цел, така че можем да спестим малко памет, като я създадем в блок с отстъп под with torch.no_grad().

За толкова прост модел почти сигурно не сме толкова загрижени колко памет имаме, но това ще бъде полезен трик в бъдещи проекти, когато започнем да разширяваме границите на нашите машини.

Определете стъпката на обучение

Сега дефинираме действителния процес на обучение. Какво се случва по време на всяка итерация на нашия тренировъчен цикъл? В основата на това трябва да кодираме нашия цвят като изображение, след което да сравним неговото вграждане в CLIP с вграждането за нашата текстова подкана. Но тук се случват още няколко неща, които може да сте виждали или не преди.

def training_step():
	# Clear out any existing gradients
  opt.zero_grad()
  
	# Get color parameters from rgb model instance
	color = rgb_model()
  color_img = resizer(color)
  image_embedding = model.encode_image(color_img)
  
	# Using negative cosine similarity as loss
	loss = -1 * torch.cosine_similarity(target_embedding, image_embedding, dim=-1)
 
  # Compute the gradient of the loss function and backpropagate to other tensors
	loss.backward()
  # Perform parameter update on parameters defined in optimizer
	opt.step()

Notes: opt.zero_grad()

Искаме да изчислим градиента за всяка стъпка от тренировъчния цикъл поотделно, което е стандартният начин за правене на нещата, но не и единственият начин. Оказва се, че оптимизаторите на PyTorch съхраняват или натрупват градиенти, докато не изтрием тези стойности с opt.zero_grad().

Може да изглежда, че тази стъпка трябва да бъде автоматична след извършване на актуализация, но има много техники, които се възползват от натрупването на градиенти. Създаването на този процес наръчник в PyTorch ни дава много прозрачност и гъвкавост при определянето на това как се обучават моделите.

Notes: loss.backward()

Изчисляваме нашия тензор на загубите loss като подобие на отрицателния косинус между вгражданията на CLIP на нашата текстова подкана и на текущия параметър color на нашия модел. С функциите на загуба искаме нещо, при което по-малкото е по-добро, поради което използваме отрицателно косинус подобие.

След като изчислим загубата, трябва да изчислим нейния градиент. Не се заблуждавайте; въпреки термина „автоматично разграничаване“, това всъщност не се случва автоматично!

Автоматичното диференциране се отнася до натрупването на символни стъпки, които могат да се комбинират с помощта на верижното правило, за да се получи градиент на функция/тензор. По този начин извикването на loss.backward() ще изчисли градиента по отношение на листата на графиката (в този случай параметъра color на нашия модел), така че оптимизаторът да може да го използва.

Notes: opt.step()

Така че сега имаме нашата загуба и сме изчислили нейния градиент по отношение на color. Време е да актуализираме цвета си. Обаждането на opt.step() ще направи точно това. Ако оставим това, тогава color никога няма да се промени.

Какво следва?

В тази публикация използвахме CLIP, за да стимулираме директната оптимизация на RGB стойностите, за да съответстват на текстовите подкани. По пътя покрихме някои основи на PyTorch, работейки с класа Module за създаване на модели и разопаковане на някои аспекти на процеса на обучение. Как да строим оттук?

Можем да повторим тази работа по много начини. От една страна, можем да преминем към оптимизиране на повече от един пиксел наведнъж. Може би се опитваме директно да оптимизираме 8x8 RGB изображение с CLIP. Ако просто използваме косинусово сходство, управлявано от CLIP, като наша функция на загуба, ще открием, че получаваме все по-нестабилни резултати, ако просто се опитаме да оптимизираме стойностите на пикселите директно. Вместо това можем да опитаме да сменим нашия RGBModel с друг механизъм за генериране на изображения. Например, можем да използваме генератора от GAN и да използваме CLIP, за да оптимизираме латентните вектори, имплицитно улавяйки промени в характеристики, които се простират отвъд отделните пиксели. Всъщност това изглежда е най-популярният подход при генериране на изображения, ръководено от CLIP. Не сте сигурни какво означава всичко това? След това останете на линия, за да научите повече в следващата инсталация от тази серия.

Засега можете да изпробвате този модел на живо в Hugging Face Spaces и също така да прочетете кода, който управлява демонстрацията. Можете също да научите повече за Hugging Face и Gradio тук.

Бележка на редактора: Heartbeat е онлайн публикация и общност, ръководена от сътрудници, посветена на предоставянето на първокласни образователни ресурси за наука за данни, машинно обучение и практици в дълбокото обучение. Поели сме ангажимент да подкрепяме и вдъхновяваме разработчици и инженери от всички сфери на живота.

Редакционно независим, Heartbeat е спонсориран и публикуван от Comet, MLOps платформа, която позволява на учените по данни и екипите на ML да проследяват, сравняват, обясняват и оптимизират своите експерименти. Ние плащаме на нашите сътрудници и не продаваме реклами.

Ако искате да допринесете, преминете към нашата покана за сътрудници. Можете също така да се регистрирате, за да получавате нашите седмични бюлетини (Deep Learning Weekly и Бюлетин на Comet), да се присъедините към нас в Slack и да следвате Comet в Twitter и LinkedIn за ресурси, събития и много повече, което ще ви помогне да изградите по-добри ML модели, по-бързо.