Обучение внедрению сложных математических формул в документах по глубокому обучению в эффективный код PyTorch за 3 шага.

Введение

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

Книги и сообщения в блогах могут помочь вам начать кодирование и изучить основы ML/DL, но изучив несколько из них и хорошо справившись с рутинными задачами в этой области, вы вскоре поймете, что вы сами по себе в этой сфере. обучения, и вы обнаружите, что большинство онлайн-ресурсов скучны и слишком поверхностны. Тем не менее, я считаю, что если вы можете изучать новые статьи по глубокому обучению по мере их публикации и понимать необходимые математические элементы в них (не обязательно все математические доказательства, лежащие в основе теорий авторов), и вы являетесь способным программистом, который может реализовать их в эффективный код, ничто не может помешать вам оставаться в курсе последних событий и изучать новые идеи.

Реализация контрастных потерь

Я представлю свою процедуру и шаги, которые я выполняю, чтобы реализовать математику в документах по глубокому обучению, используя нетривиальный пример: Contrastive Loss в документе SimCLR.

Вот математическая формулировка потерь:

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

Шаги по реализации математики в коде

Моя процедура внедрения математики в статьях в эффективный код PyTorch выглядит следующим образом:

  1. Поймите математику, объясните ее простыми словами
  2. Реализуйте первоначальную версию, используя простые Python-циклы for, пока никаких причудливых матричных умножений.
  3. Преобразуйте свой код в эффективный удобный для матриц код PyTorch

Хорошо, давайте сразу к первому шагу.

Шаг 1: Понимание математики и объяснение ее простыми словами

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

Вернемся к нашим делам. Поскольку приведенный выше абзац формулы добавляет больше контекста, в стратегии обучения SimCLR вы начинаете с N изображений и трансформируете каждое из них 2 раза, чтобы получить расширенные представления этих изображений (теперь 2 * N изображений). Затем вы пропускаете эти 2 * N изображений через модель, чтобы получить векторы встраивания для каждого из них. Теперь вы хотите сделать векторы вложения двух расширенных видов одного и того же изображения (положительной пары) ближе в пространстве вложения (и сделать то же самое для всех других положительных пар). Один из способов измерить, насколько похожи (близки, в одном и том же направлении) два вектора, — использовать Косинусное сходство, которое определяется как sim(u, v) (посмотрите определение на изображении выше). .

Проще говоря, формула описывает формулу для каждого элемента в нашем пакете, который представляет собой встраивание одного из расширенных представлений изображения (помните: пакет содержит все вложения расширенных представлений различных изображений → если начиная с изображений N, пакет имеет размер 2 * N), мы сначала находим вложение другого расширенного представления этого изображения, чтобы создать положительную пару. Затем мы вычисляем косинусное сходство этих двух вложений и возводим его в степень (числитель формулы). Затем мы вычисляем степень косинусного сходства всех остальных пар, которые мы можем построить с нашим первым вектором вложения, с которого мы начали (за исключением пары с самим собой, это то, что означает 1[k!=i] в формуле ), и мы суммируем их, чтобы построить знаменатель. Теперь мы можем разделить числитель на знаменатель, взять натуральный логарифм и перевернуть знак! Теперь у нас есть потеря первого элемента в нашей партии. Нам нужно просто повторить тот же процесс для всех других элементов в пакете, а затем взять среднее значение, чтобы иметь возможность вызывать метод .backward() PyTorch для расчета градиентов.

Шаг 2: Реализация с помощью простого кода Python с наивными циклами for!

Давайте пройдемся по коду. Допустим, у нас есть два изображения: A и B. Переменная aug_views_1 содержит вложения (каждое размером 3) одного расширенного представления этих двух изображений (A1 и B1), так же, как и aug_views_2 (A2 и B2); Итак, первый элемент обеих матриц связан с изображением A, а второй элемент обеих матриц связан с изображением B. Мы объединяем две матрицы в матрицу projections (в которой 4 вектора). : А1, В1, А2, В2).

Чтобы сохранить отношение векторов в матрице проекций, мы определяем словарь pos_pairs для хранения того, какие два элемента связаны в объединенной матрице. (скоро я объясню, что такое F.normalize()!)

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

После построения нашего числителя я нахожу все остальные индексы векторов в пакете (кроме того же индекса i), чтобы вычислить сходство косинусов, составляющих знаменатель. Наконец, я вычисляю потери, деля числитель на знаменатель, применяя логарифмическую функцию и меняя знак. Обязательно поиграйте с кодом, чтобы понять, что происходит в каждой строке.

Шаг 3. Преобразование его в эффективный код PyTorch, удобный для работы с матрицами.

Проблема с предыдущей реализацией Python заключается в том, что она слишком медленная для использования в нашем конвейере обучения; нам нужно избавиться от медленных циклов for и преобразовать их в умножение матриц и манипуляции с массивами, чтобы использовать возможности распараллеливания.

Давайте посмотрим, что происходит в этом фрагменте кода. На этот раз я представил тензоры labels_1 и labels_2 для кодирования произвольных классов, к которым принадлежат эти изображения, поскольку нам нужен способ кодирования отношения A1, A2 и изображения B1, B2. Неважно, выберете ли вы метки 0 и 1 (как я) или скажем 5 и 8.

После объединения вложений и меток мы начинаем с создания sim_matrix, содержащего косинусное сходство всех возможных пар.

Приведенная выше визуализация — это все, что вам нужно :), чтобы понять, как работает код и почему мы выполняем в нем шаги. Рассматривая первую строку sim_matrix, мы можем рассчитать потери для первого предмета в партии (A1) следующим образом: нам нужно разделить A1A2 (возведение в степень) на сумму A1B1, A1A2 и A1B2 (каждое из которых возведено в степень первым) и сохранить результат в первом элементе тензора, в котором хранятся все потери. Итак, нам нужно сначала сделать маску, чтобы найти зеленые клетки на визуализации выше. Две строки кода, определяющие переменную mask, делают именно это. Числитель вычисляется путем умножения нашей sim_matrix на только что созданную маску, а затем суммирования элементов каждой строки (после маскирования в каждой строке будет только один ненулевой элемент, то есть зеленые ячейки). Для вычисления знаменателя нам нужно суммировать по каждой строке, игнорируя оранжевые клетки по диагонали. Для этого мы будем использовать метод .diag() тензоров PyTorch. Остальное понятно!

Бонус: использование помощников ИИ (ChatGPT, Copilot, …) для реализации формулы

В нашем распоряжении есть отличные инструменты, которые помогут нам понять и реализовать математику в документах по глубокому обучению. Например, вы можете попросить ChatGPT (или другие подобные инструменты) реализовать код в PyTorch, предоставив ему формулу из статьи. По моему опыту, ChatGPT может быть наиболее полезным и предоставлять наилучшие окончательные ответы с меньшим количеством следов и ошибок, если вы каким-то образом сможете перейти к этапу реализации pythonic-for-loop. Дайте эту наивную реализацию ChatGPT и попросите преобразовать ее в эффективный код PyTorch, который использует только матричные умножения и тензорные манипуляции; вы будете удивлены ответом :)

Дальнейшее чтение

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

  1. Контролируемая потеря контраста, Гийом Эрхард
  2. SupContrast, Юнлун Тянь

Обо мне

Я Моейн Шариатния, разработчик машинного обучения и студент-медик, специализируюсь на использовании решений глубокого обучения для приложений медицинской визуализации. Мое исследование в основном посвящено изучению возможности обобщения глубоких моделей при различных обстоятельствах. Не стесняйтесь обращаться ко мне по электронной почте, Twitter или LinkedIn.