Когда ReLU впервые представлен в Главе 4 фастбука, Джереми Ховард делает следующий комментарий:

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

Этот комментарий застал меня в нужное время. За последние недели я понял, насколько пугающим и неприступным я считаю нынешнее состояние глубокого обучения с его бесчисленным множеством незнакомых терминов. Поскольку оказалось, что ReLU — это всего лишь функция .max(0), к концу этого образовательного приключения я постараюсь определить следующие термины, используя гораздо более простые:

  • Си-Эн-Эн
  • РНН
  • Кодер
  • Декодер
  • Автоэнкодер
  • Трансформер
  • seq2seq
  • Q-обучение
  • Внимание
  • Распространение

И я оставляю за собой право дополнять этот список!

Множество способов содрать шкуру с кошки

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

preds = f(time, params)
loss = mse(preds, speed)
loss.backward()
params.data -= lr * params.grad.data
params.grad = None
for p in params:
    p.data -= p.grad*lr
    p.grad.zero_()
with torch.no_grad():
    coeffs.sub_(coeffs.grad * lr)

Это было отличным напоминанием о нескольких основных идеях:

  • простой для понимания код — лучший код
  • есть несколько способов сделать одно и то же с разной степенью эффективности реализации
  • использование стандартной структуры решает обе эти проблемы за вас

PyTorch против NumPy

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

Создание нейронной сети с нуля

Я начал воспроизводить шаги урока по созданию нейросети с нуля. Я настроил среду графического процессора в Vertex AI Workbench в Google Cloud Platform, которую я выбрал для работы вместо AWS SageMaker, просто чтобы разнообразить свое взаимодействие с платформами.

Когда я раскрутил пустой блокнот и, не желая заглядывать в решение, я задумался: «Подождите минутку, какие шаги мне нужно предпринять?» Для каждого основного шага я создал заголовок раздела Markdown и несколько пустых блоков кода. Я знал, что мне нужно:

  1. Загрузите набор данных Kaggle Titantic и предварительно обработайте его в рабочем формате.
  2. Инициализировать набор параметров (веса и смещения)
  3. Вычислить прямой проход по параметрам для вычисления выходных данных
  4. Определите функцию потерь и рассчитайте потери для обучающей эпохи
  5. Определите метрику и вычислите эту метрику в наборе проверки
  6. Реализуйте обратный проход как средство обновления параметров на основе градиентов функции потерь по отношению к параметрам.

При этом я понял, что заново сформулировал подход параллельной распределенной обработки (PDP) 1950-х годов, изложенный в Главе 1 книги fast.ai.

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

Вы можете увидеть мои работы в этой тетради.

Урок выучен

Моим самым большим камнем преткновения при реализации с нуля был час, потраченный на отладку моего кода, пытаясь найти ошибку, которой не было. Я был убежден, что ошибка препятствует обучению, потому что моя функция потерь была одинаковой для каждой эпохи. В конце концов я понял, что моя скорость обучения была слишком мала! Я установил его произвольно на lr=1e-5 и обнаружил, что обучение работает только после того, как поднял его до lr=50 .