Обучение, без сомнения, является наиболее важной частью разработки приложения для машинного обучения. Это когда вы начинаете понимать, стоит ли ваша модель того, как должны выглядеть ваши гиперпараметры и что вам нужно изменить в вашей архитектуре. В целом, большинство инженеров по машинному обучению тратят довольно много времени на обучение, экспериментируя с различными моделями, настраивая свою архитектуру и находя лучшие показатели и потери для своей задачи.
В этой статье мы продолжаем серию статей «Глубокое обучение в производстве». создание модели тренера для нашего примера сегментации мы используем до сих пор. Я подумал, что было бы неплохо на этот раз вместо того, чтобы обрисовывать основные темы и принципы разработки программного обеспечения, шаг за шагом пройти весь жизненный цикл разработки. Итак, мы запрограммируем весь класс Trainer так, как мы это делаем в нашей повседневной работе. Также это отличная возможность применить все лучшие практики, о которых мы говорили в серии. Мы собираемся изучить, как мы можем создавать высокопроизводительное и простое в сопровождении программное обеспечение в режиме реального времени.
Так что будьте готовы к большому количеству кода на этот раз. Без дальнейших церемоний, давайте начнем.
Построение тренировочного цикла в Tensorflow
Перво-наперво. Давайте вспомним наш код до сих пор. Все, что у нас есть в блокноте colab, — это шаблонный код Keras, который включает в себя компиляцию и подгонку модели.
self.model.compile(optimizer=self.config.train.optimizer.type,
loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
metrics=self.config.train.metrics)
LOG.info('Training started')
model_history = self.model.fit(self.train_dataset, epochs=self.epoches,
steps_per_epoch=self.steps_per_epoch,
validation_steps=self.validation_steps,
validation_data=self.test_dataset)
return model_history.history('loss'), model_history.history('val_loss')
Некоторые вещи, на которые следует обратить внимание, прежде чем мы продолжим. Поскольку мы получаем большинство наших гиперпараметров из файла конфигурации, я думаю, было бы полезно точно знать, что мы здесь используем. В частности, мы выбираем «SparseCategoricalCrossentropy» в качестве нашей потери, оптимизатор Adam и «SparseCategoricalAccuracy» в качестве нашей основной метрики.
Также обратите внимание, что я использую Python 3.7 и Tensorflow 2.0. Чтобы увидеть мою полную настройку и следовать ей, вы можете пересмотреть первую статью серии, в которой мы настроили наш ноутбук и кратко изложили нашу проблему и конечную цель.
Для простоты я думаю, что мы можем заменить приведенный выше код в функции поезда внутри нашего модельного класса кодом ниже:
optimizer = tf.keras.optimizers.Adam()
loss = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)
metrics = tf.keras.metrics.SparseCategoricalAccuracy()
trainer = Trainer(self.model, self.train_dataset, loss, optimizer, metrics, self.epoches)
trainer.train()
Все, что я здесь делаю, это определяю оптимизатор, потери, метрики и передаю их вместе с моделью и набором данных в класс тренера под названием «Тренер». Как только мы создадим новый экземпляр класса, мы можем вызвать метод train, запускающий начало нашего обучения.
Хорошей практикой является попытка держать класс в неведении обо всех других компонентах приложения, таких как модель и данные. Каждый класс должен иметь единственную цель и выполнять только одну функцию, не завися от других элементов.
Это то, что мы называем разделение интересов и это жизненно важная концепция для обеспечения ремонтопригодности и масштабируемости нашего программного обеспечения.
Итак, давайте определим наш класс в отдельном файле. Обычно у меня есть папка с именем executors, и я включаю в нее все основные функции ML, такие как обучение, проверка и прогнозирование, но вы, очевидно, можете делать все, что хотите. Каждый трейнер зависит только от шести вещей: модели, входных данных, функции потерь, оптимизатора, метрики и количества эпох.
class Trainer:
def __init__(self, model, input, loss_fn, optimizer, metric, epoches):
self.model = model
self.input = input
self.loss_fn = loss_fn
self.optimizer = optimizer
self.metric = metric
self.epoches = epoches
Как видите, здесь нет ничего особенного. Внутри класса Trainer нам также нужен функция поезда, который будет иметь общую обучающую функциональность, и функция train_step который будет содержать только один шаг обучения.
В большинстве случаев предпочтительнее иметь собственный цикл обучения, а не полагаться на API высокого уровня, такие как Keras, потому что мы можем настроить каждую мелочь и иметь полный контроль над процессом.
В функции train_step мы выполняем фактическое обучение одной партии. Во-первых, нам нужно получить обучающие переменные, также известные как веса модели, и извлечь входные данные и метку из пакета.
trainable_variables = self.model.trainable_variables
inputs, labels = batch
Затем нам нужно передать входные данные в модель и рассчитать потери на основе меток и прогноза Unet.
with tf.GradientTape() as tape:
predictions = self.model(inputs)
step_loss = self.loss_fn(labels, predictions)
Мы используем «tf.GradientTape()» от Tensorflow для захвата градиентов на этом этапе. Затем мы можем применить их в оптимизаторе и соответствующим образом изменить веса.
grads = tape.gradient(step_loss, trainable_variables)
self.optimizer.apply_gradients(zip(grads, trainable_variables))
По сути, мы запускаем алгоритм обратного распространения, используя API-интерфейсы, предоставляемые Tensorflow. Для очень низкого уровня понимания того, как работает обратное распространение, вы можете прочитать нашу статью о построении нейронной сети с нуля.
Наконец, нам нужно обновить нашу метрику и вернуть потерю шага и прогнозы, которые будут использоваться функцией обучения.
self.metric.update_state(labels, predictions)
return step_loss, predictions
И, наконец, у нас есть что-то вроде этого:
def train_step(self, batch):
trainable_variables = self.model.trainable_variables
inputs, labels = batch
with tf.GradientTape() as tape:
predictions = self.model(inputs)
step_loss = self.loss_fn(labels, predictions)
grads = tape.gradient(step_loss, trainable_variables)
self.optimizer.apply_gradients(zip(grads, trainable_variables))
self.metric.update_state(labels, predictions)
return step_loss, predictions
Теперь перейдем к методу поезда. Метод обучения будет просто циклом for, который перебирает количество эпох, и вторичным циклом for внутри, который обучает каждую партию (это наш этап обучения).
def train(self):
for epoch in range(self.epoches):
LOG.info(f'Start epoch {epoch}')
for step, training_batch in enumerate(self.input):
step_loss, predictions = self.train_step(training_batch)
LOG.info("Loss at step %d: %.2f" % (step, step_loss))
train_acc = self.metric.result()
LOG.info(f'Saved checkpoint: {save_path}')
Как я упоминал ранее, у нас просто есть два цикла for и много логов. Предоставление журналов жизненно важно, чтобы мы могли иметь четкое представление о том, что происходит внутри вычислительного графа. Таким образом, мы можем остановить / продолжить обучение на основе информации, которую мы получили от них, и сразу же распознать ошибки и ошибки. Переменная LOG — это константа, определенная в верхней части файла, которая инициализирует утилиту ведения журнала.
LOG = get_logger('trainer')
Для получения дополнительной информации об этом и о том, как использовать журналы в приложении для глубокого обучения, см. нашу предыдущую статью из серии, посвященной отладке и ведению журналов.
Несколько замечаний, прежде чем мы продолжим: прежде всего, входными данными является набор данных tensorflow (tf.data), и, как вы можете видеть, мы можем перебирать его, как мы делаем для обычного массива или списка. Мы также рассмотрели это в предыдущей статье о предварительной обработке данных. Во-вторых, я уверен, вы заметили, что мы фиксируем как потери, так и точность на протяжении всей программы. Это нужно не только для предоставления журналов, но и для визуализации нашего обучения с использованием Tensorboard. Подробнее об этом чуть позже.
В-третьих, нам нужен способ периодически сохранять состояние нашего обучения, потому что модели глубокого обучения могут обучаться в течение длительного времени. И я имею в виду много времени. Чтобы избежать потери веса и иметь возможность впоследствии повторно использовать обученную модель, нам нужно включить какие-то контрольные точки. К счастью, для этого уже есть встроенная функция.
Контрольные точки обучения
Сохранить текущее состояние в контрольных точках на самом деле довольно просто. Все, что нам нужно сделать, это определить менеджера контрольной точки и начальную контрольную точку, чтобы продолжить обучение оттуда. Если мы впервые обучаем модель, она будет пустой, иначе она будет загружена из внешней папки. Так что в нашем в этом функция, мы имеем:
self.checkpoint = tf.train.Checkpoint(optimizer=optimizer, model=model)
self.checkpoint_manager = tf.train.CheckpointManager(self.checkpoint, './tf_ckpts')
Контрольная точка определяется оптимизатором и моделью, а диспетчер контрольных точек – исходной контрольной точкой и папкой для их сохранения. И чтобы сохранить текущее состояние, все, что нам нужно сделать, это:
save_path = self.checkpoint_manager.save()
LOG.info(f'Saved checkpoint: {save_path}')
Мы помещаем это обычно в конце каждой эпохи или после завершения их подмножества.
Сохранение обученной модели
После завершения всего обучения мы хотим сохранить нашу обученную модель, чтобы мы могли использовать ее для обслуживания реальных пользователей. Опять же, это довольно просто в Tensorflow и может быть сделано в пару строк:
self.model_save_path = 'saved_models/'
save_path = os.path.join(self.model_save_path, "unet/1/")
tf.saved_model.save(self.model, save_path)
И, конечно же, загрузить сохраненную модель тоже не составит труда:
model = tf.saved_model.load(save_path)
Визуализация обучения с помощью Tensorboard
Если вы визуал, как и я, журналы — не самая простая вещь, на которую нужно смотреть, пытаясь понять, как проходит ваше обучение.. Было бы намного лучше иметь способ визуализировать процесс и смотреть на графики, а не на строки и строки журналов. Если вы не знаете о таком инструменте, Tensorboard — это способ отображать метрики, полученные во время обучения, и создавать для них красивые графики. Это также очень хороший способ увидеть вычислительный граф и получить общее представление о всей нашей архитектуре. И это также легко использовать (удивительно, да?)
tf.резюме — это очень элегантный метод записи наших метрик в журналы, которые впоследствии могут быть использованы Tensorboard. Мы можем создать средство записи сводки, используя внешний путь, например:
self.train_log_dir = 'logs/gradient_tape/'
self.train_summary_writer = tf.summary.create_file_writer(self.train_log_dir)
И в конце каждой эпохи мы можем использовать этот модуль записи для сохранения текущих метрик, как в примере ниже:
def _write_summary(self, loss, epoch):
with self.train_summary_writer.as_default():
tf.summary.scalar('loss', loss, step=epoch)
tf.summary.scalar('accuracy', self.metric.result(), step=epoch)
Некоторые вещи, на которые следует обратить внимание:
-
tf.summary.scalar создает файл и записывает метрики под капотом
-
Журналы на самом деле сохраняются в двоичном формате, поэтому мы не можем их прочитать.
-
Мы можем либо передать метрику в качестве аргумента функции (как мы делаем для потери), либо мы можем использовать tf.metric, который хранит состояние внутри вычислительного графа.
Для последнего мы можем обновить метрику внутри графика, используя
self.metric.update_state(labels, predictions)
А затем получить текущее состояние:
self.metric.result()
где:
self.metric = tf.keras.metrics.SparseCategoricalAccuracy()
Как только мы напишем сводку, мы можем развернуть Tensorboard на нашем локальном хосте, выполнив следующую команду:
$ tensorboard --logdir logs/gradient_tape
И вот:
Tensorboard продолжит печатать метрики во время обучения. Я настоятельно рекомендую потратить некоторое время и поиграть с ним, чтобы вы могли получить четкое представление обо всех удивительных вещах, которые мы можем сделать.. Поверьте, это будет очень полезно для ваших будущих проектов.
Прежде чем мы закончим эту статью, я хотел бы упомянуть одну вещь, которую мы не затронули. И это проверка. В большинстве случаев после того, как мы проводим какое-либо обучение, мы применяем процедуру проверки, чтобы быть уверенными, что наши результаты верны, а их модель не переобучена. Здесь применяются точно такие же принципы, о которых мы говорили до сих пор. У нас по-прежнему будут тест и функция test_step, и мы по-прежнему будем использовать tf.metrics, журналы и Tensorboard. На самом деле, в 90% случаев это мало чем отличается от тренировки. Единственная разница в том, что нам не нужно вычислять градиенты.
Заключение
Мы увидели, как создать собственный тренажер с нуля, а также некоторые передовые методы, чтобы поддерживать его в сопровождении и расширять. Параллельно мы погрузились в некоторые приемы Tensorflow, чтобы сделать код намного меньше и проще. Позвольте мне сказать вам, что это именно тот мыслительный процесс, которому я бы следовал в реальном проекте, и код, который будет выполняться в рабочей среде, будет почти идентичен этому. Я надеюсь, что вы найдете этот тип статьи полезным, и, пожалуйста, дайте нам знать, если вы хотели бы видеть больше из них. Конечно, всегда есть способы и методы для еще большей оптимизации, но я думаю, что вы уже поняли суть. Для получения полного кода не стесняйтесь проверить наш Репозиторий на гитхабе
Следующие две статьи также будут посвящены обучению и тому, как его оптимизировать. Мы увидим, как распределить наше обучение на графических процессорах и нескольких машинах. Затем мы представим полный пример того, как запустить задание обучения в облаке.
Я только что понял, что это седьмая статья из серии, и я не думаю, что она скоро закончится. Нам еще многое предстоит обсудить, прежде чем наша модель будет развернута и будет использоваться миллионами пользователей. Я надеюсь, что ты все еще будешь со мной тогда.
А пока развлекайтесь и наслаждайтесь изучением ИИ.
* Раскрытие информации: Обратите внимание, что некоторые из приведенных выше ссылок могут быть партнерскими ссылками, и без дополнительной оплаты для вас мы будем получать комиссию, если вы решите совершить покупку после перехода по ссылке.