Используйте этот шаблон для быстрого написания собственных алгоритмов TensorFlow

Индивидуальные тренировочные циклы обеспечивают большую гибкость. Вы можете быстро добавить новые функции и лучше понять, как работает ваш алгоритм. Однако настраивать собственные алгоритмы снова и снова утомительно. Общая планировка часто одинакова; меняются только крошечные детали.

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

Общий вид пользовательских распределенных шлейфов

Пользовательский цикл обучения - в отличие от вызова model.fit () - это механизм, который выполняет итерацию по наборам данных, обновляет веса модели и вычисляет любые метрики.

Перед повторением любого набора данных, будь то поезд, проверка или тестовое разделение, набор данных должен быть подготовлен к распространению. Это делается с помощью объектов стратегии распространения TensorFlow.

Сначала мы создаем наш объект стратегии, который отвечает за все распределенные вычисления. Выбирая различные стратегии распределения, мы можем использовать наш алгоритм во всех видах вычислительных сред. Этот факт делает пользовательские петли очень гибкими.

В строке 2 после создания нашей стратегии мы готовим распространение нашего набора данных; TensorFlow обрабатывает все внутренние детали.

С нашим распределенным набором данных мы можем затем перебрать его, используя подход «для i в x»:

Этот цикл одинаков для всех подмножеств (обучение, проверка, тест). Основное отличие - это вызываемый шаг. Выше я в качестве примера назвал distribution_train_step, который обрабатывает распределение наших данных по всем ускорителям. Но не волнуйтесь, TensorFlow обрабатывает большую часть внутренней связи между устройствами. У нас есть только то, что мы знаем, что хотим делать. Как сделано для нас.

В рамках этапов обучения, тестирования или проверки мы также обновляем любые метрики, которые хотим отслеживать во время обучения. Мы должны сделать это вручную, как показано ниже:

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

Резюмируя общий план:

  • Подготовьте необходимые объекты (модели, наборы данных, оптимизаторы, метрики) для распространения.
  • Перебирать наборы данных
  • Вызовите этапы распределенного поезда / тестирования / проверки, чтобы обновить модель и вычислить метрики.

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

Шаблон для распределенного индивидуального обучения

Начнем с необходимого импорта и некоторых глобальных определений:

Для этого шаблона я решил сделать глобальными все объекты и переменные, используемые на этапах обучения, тестирования и проверки. Таким образом, нам не придется передавать их все время, что делает код более чистым. Однако это только один способ сделать это, и я уверен, что есть даже лучшие.

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

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

и приводит к основному методу:

Основной метод

Метод main начинается с использования большинства глобальных объектов, которые мы представили ранее. До сих пор они были только заполнителями, поэтому мы сейчас их создаем. Мы начинаем со стратегии распределения (строка 13) и следуем за потерями (строка 16), метриками (строка 19) и моделями (строка 22). Вскоре мы подробно рассмотрим все вызываемые методы.

В этом шаблоне я пропустил часть создания набора данных. Для этого существует слишком много способов, и вы знаете, что лучше всего подходит для ваших задач. Таким образом, начиная со строки 26, я использовал только None в качестве начального значения; замените эту часть на создание и распространение ваших наборов данных (используя стратегию, показанную в предыдущем фрагменте кода)

Начиная со строки 31, мы сначала обучаем модель с использованием распределенных наборов данных для поездов и проверки, а затем оцениваем ее на удерживаемом наборе тестовых данных.

Убытки

Первый метод, create_loss_objects, отвечает за любые убытки, которые мы используем. Это показано ниже:

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

Метрики

Код для создания показателей, используемых для отслеживания прогресса нашей модели, следует по той же схеме:

В рамках стратегии распространения мы инстанцируем все метрики. В шаблоне это только точность обучения. Чего не хватает, так это каких-либо показателей для проверки (во время обучения) и тестирования (после обучения). Я рекомендую давать понятные имена для идентификации показателей при печати. Как и раньше, зарегистрируйте все возвращаемые метрики глобально. В методе main я сделал это с помощью train_metric1 в строке 9.

После создания экземпляров показателей мы сосредоточимся на модели (ах) и оптимизаторе (ах). Для этого используется шаблонный метод, следующий за двумя предыдущими:

Как и прежде, мы создаем все, что угодно, в рамках выбранной стратегии. Этот шаг является обязательным для подготовки внутренних переменных модели и дистрибутива оптимизаторов. В примере кода я не выбрал какую-либо конкретную модель или оптимизатор; измените что-нибудь из строки 8 в соответствии с вашей задачей. Кроме того, не забудьте зарегистрировать возвращаемые объекты глобально, как я сделал с model и optimizer в строке 9 функции main.

Наборы данных

После того, как все необходимые объекты созданы и готовы к распространению, пора создавать наборы данных. Я намеренно оставил это поле пустым, поскольку существует несколько способов создания набора данных: от объектов tf.data.Dataset до настраиваемых генераторов или гибридных подходов. Вы можете найти обзор здесь, но, поскольку этот шаблон ориентирован на более опытных программистов, я сомневаюсь, что он вам понадобится. В любом случае, после того, как вы создали набор данных, вы должны распространить его, используя

Когда наборы данных готовы, мы можем перейти к циклу обучения (строка 31 в main). Нам нужны наборы данных для обучения и проверки для используемого нами шаблона, но вы можете отказаться от последнего.

Цикл обучения и проверки

Цикл обучения лежит в основе нашего распределенного алгоритма:

В строке 12 мы начинаем с перебора наборов данных в течение эпох раз. Затем, со строки 15 по строку 20, мы перебираем обучающие данные и передаем каждый пакет методу distribution_train_step. После завершения итерации мы вычисляем потери на обучение и повторяем процесс для данных проверки.

После этого мы запрашиваем у всех (глобальных) метрик и объектов потерь их текущее значение, которое мы печатаем и сбрасываем для следующей эпохи (строки с 34 по 39). Как указано в комментариях, измените этот код, чтобы учесть любые показатели или объекты потерь. Кроме того, если вы не сбрасываете метрики после каждой эпохи (строка 37), они будут отслеживать прогресс по всем эпохам, что приведет к неправильным значениям для каждой эпохи.

Распределенный шаг поезда

Цикл обучения (строка 17 и далее) вызывает внутри себя распределенный шаг поезда. Код для этого

Здесь нам особо нечего делать: мы получаем пакет данных (dataset_inputs), говорим стратегии, что нужно выполнить один шаг поезда и уменьшить возвращаемые потери. А почему бы нам не позвонить в train_step напрямую? Потому что мы работаем с распределенными данными. Вызов strategy.run в строке 5 учитывает это; он вызывается для каждого вычислительного устройства. Так, например, если у нас подключено 5 графических процессоров, TensorFlow автоматически вызовет шаг поезда пять раз.

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

Шаг поезда

Если бы мы не распределяли нашу рабочую нагрузку, мы могли бы напрямую вызвать фактический шаг поезда. Однако, поскольку мы используем распределенную установку, мы должны сообщить об этом TensorFlow. Мы сделали это с помощью предыдущего метода, который использует strategy.run для обработки распределения. Внутри этого вызова мы сказали, что фактическим методом обучения является метод train_step, который показан ниже:

Поскольку TensorFlow обрабатывает разделение пакета, мы получаем один пакет. В строке 9 мы распаковываем его по функциям и меткам - это только пример, адаптируйте код под свои нужды. Следующие строки следуют стандартному настраиваемому циклу: мы вычисляем потери (строка 15), вычисляем и применяем градиенты (строки 18 и 19) и обновляем любую метрику обучения (строка 22). Как я отметил с помощью комментариев TODO, вы должны адаптировать это, чтобы учесть все модели, потери и метрики, которые вы используете.

Вычислить (тренировочные) потери

Для расчета потерь при обучении мы используем следующий короткий шаблон:

Мы запрашиваем наш объект потерь в строке 10, что дает нам потерю на реплику. Позвольте мне объяснить это: наш собственный алгоритм работает на нескольких вычислительных устройствах или репликах. На каждом устройстве шаг поезда вызывается независимо, что в целом приводит к n потерям. Каждая потеря теперь используется для расчета градиентов, которые синхронизируются между репликами путем суммирования. Если бы мы не масштабировали убытки, результат был бы преувеличен.

Все еще не уверены? На одной машине потери делятся на количество образцов в мини-партии. В настройке с несколькими графическими процессорами нам не нужно делить потери на локальный размер пакета, а на глобальный размер пакета. Например, размер локального пакета может быть 8, а размер глобального пакета может быть 32 (= 4 * 8 для четырех графических процессоров). Если бы мы разделили на 8, мы бы предположили, что общее количество выборок, которые мы видели в прямом проходе, равно 8, что неверно. Поэтому в строке 14 мы усредняем потери, чтобы учесть глобальный размер партии.

Это для процедуры обучения. Подводя итог: сначала мы вызываем distribution_train_step, который обрабатывает распределение вычислений для каждого рабочего. Затем для каждого рабочего вызывается train_step, который берет один пакет и обновляет веса модели.

Шаг проверки

Процедура валидации очень похожа на процедуру обучения:

Есть лишь небольшие отличия: мы не обновляем веса модели (строка 11), а обновляем наши показатели проверки (строка 13). Измените эти части в соответствии со своими потребностями. Distributed_validation_step следует той же схеме, что и formed_train_step: получить масштабированные потери для каждой реплики и агрегировать их (строки с 25 по 28).

Тестовый цикл

Тестовый цикл аналогичен двум предыдущим:

Мы перебираем распределенный набор тестовых данных (строка 11), суммируем потери (строка 12) и усредняем их (строка 14). После этого мы собираем результаты по всем метрикам. Я не включил какие-либо отдельные тестовые метрики; измените эту часть в соответствии с вашими потребностями (строки 16 и 17).

Как только мы взглянем на distribution_test_step (строка 12), мы заметим, что он снова очень похож:

В строке 32 мы собираем масштабированные потери и усредняем их в строке 36. Фактический test_step (строка 2) берет одну партию, распаковывает ее (строка 9) и вычисляет потери (строка 16). и любые тестовые метрики (строка 18). В этот фрагмент я не включил какие-либо отдельные тестовые метрики. Вы должны адаптировать его к вашей проблеме. Как правило, все показатели можно создавать в методе create_metrics. Не забудьте зарегистрировать их глобально, чтобы иметь к ним легкий доступ.

Резюме

Мы рассмотрели основные части настраиваемого и распределенного алгоритма. Мы можем использовать его для всех видов вычислительных сред, выбрав подходящую стратегию распространения, в том числе для конфигураций с одним графическим процессором. Как правило, все места, которые необходимо изменить, отмечены комментариями TODO. Хотя шаблон должен охватывать большинство случаев использования, он служит только отправной точкой. Такие специалисты, как вы и я, должны адаптировать важнейшие части в соответствии со своими требованиями. Полный код доступен на GitHub.