Введение
Это вторая часть из серии статей об использовании 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, чтобы получать ресурсы, события и многое другое, что поможет вам быстрее создавать лучшие модели машинного обучения.