Введение

Это вторая часть из серии статей об использовании CLIP с нуля для оценки изображений и управления ими путем сравнения их с текстовыми подсказками. Часть первую можно найти здесь:



В последнем посте я продемонстрировал, как сравнивать текстовое приглашение в диапазоне цветов и визуализировать, насколько хорошо каждый отдельный оттенок соответствует текстовому приглашению. В этом уроке я покажу, как мы можем оптимизировать цвет, чтобы он максимально соответствовал тексту. Для этого мы напишем собственный модуль с помощью PyTorch.

Вы можете следить за этим блокнотом Colab для интерактивной работы с кодом, а также можете попробовать модель в действии в этом Пространстве обнимающего лица, которое я построил с помощью Градио. В этом посте я приведу некоторые комментарии и пояснения к коду, необходимому для написания модели и цикла обучения.



Подкласс от 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 во время обучения, но это игрушечная модель, поэтому пока мы ее игнорируем.

Хотите увидеть эволюцию художественных проектов, созданных искусственным интеллектом? Посетите наш публичный проект, чтобы увидеть таймлапсы, эксперименты с эволюцией и многое другое!

Создать оптимизатор

Когда наша модель готова к работе, пришло время создать объект оптимизатора. Мы будем использовать оптимизатор 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 для создания моделей и раскрывая некоторые аспекты процесса обучения. Как мы строим отсюда?

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

На данный момент вы можете попробовать эту модель вживую на Hugging Face Spaces, а также прочитать код, управляющий демонстрацией. Вы также можете узнать больше о Hugging Face и Gradio здесь.

Примечание редактора. Heartbeat — это интернет-издание и сообщество, созданное участниками и посвященное предоставлению лучших образовательных ресурсов для специалистов по науке о данных, машинному обучению и глубокому обучению. Мы стремимся поддерживать и вдохновлять разработчиков и инженеров из всех слоев общества.

Независимая от редакции, Heartbeat спонсируется и публикуется Comet, платформой MLOps, которая позволяет специалистам по данным и командам машинного обучения отслеживать, сравнивать, объяснять и оптимизировать свои эксперименты. Мы платим нашим авторам и не продаем рекламу.

Если вы хотите внести свой вклад, перейдите к нашему призыву к участию. Вы также можете подписаться на получение наших еженедельных информационных бюллетеней (Еженедельник глубокого обучения и Информационный бюллетень Comet), присоединиться к нам в Slack и следить за Comet в Twitter и LinkedIn, чтобы получать ресурсы, события и многое другое, что поможет вам быстрее создавать лучшие модели машинного обучения.