Повышение скорости обучения с косинусным распадом в Keras/TensorFlow PlatoBlockchain Data Intelligence. Вертикальный поиск. Ай.

Разогрев скорости обучения с косинусным затуханием в Keras/TensorFlow

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

$$
вес_{t+1} = вес_t – lr * frac{derror}{dweight_t}
$$

Со скоростью обучения 0, обновленный вес возвращается к самому себе – весt. Скорость обучения — это, по сути, ручка, которую мы можем повернуть, чтобы включить или отключить обучение, и она оказывает большое влияние на объем обучения, напрямую контролируя степень обновлений веса.

Разные оптимизаторы используют скорость обучения по-разному, но основная концепция остается неизменной. Излишне говорить, что скорость обучения была объектом многих исследований, статей и практических тестов.

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

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

Разминка скорости обучения

Идея разогрева скорости обучения проста. На самых ранних этапах тренировки – веса далеки от идеального состояния. Это означает большие обновления по всем направлениям, которые можно рассматривать как «чрезмерные исправления» для каждого веса — когда резкое обновление другого может свести на нет обновление какого-либо другого веса, делая начальные этапы тренировки более нестабильными.

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

Это разминка скорости обучения! Начиная с низкой (или 0) скорости обучения и увеличивая до начальной скорости обучения (с чего бы вы все равно начали). На самом деле это увеличение может следовать любой функции, но обычно оно линейно.

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

В этом руководстве мы реализуем прогрев скорости обучения в Keras/TensorFlow в качестве keras.optimizers.schedules.LearningRateSchedule подкласс и keras.callbacks.Callback перезвонить. Скорость обучения будет увеличена с 0 в target_lr и примените косинусное затухание, так как это очень распространенный вторичный график. Как обычно, Keras позволяет легко внедрять гибкие решения различными способами и поставлять их вместе с вашей сетью.

Примечание: Реализация является общей и вдохновлена Реализация Тони Кераса приемов, описанных в «Набор хитростей для классификации изображений с помощью сверточных нейронных сетей».

Скорость обучения с обратными вызовами Keras

Самый простой способ реализовать любой график скорости обучения — создать функцию, которая принимает lr параметр (float32), пропускает его через некоторое преобразование и возвращает. Затем эта функция передается LearningRateScheduler обратный вызов, который применяет функцию к скорости обучения.

Теперь, tf.keras.callbacks.LearningRateScheduler() передает номер эпохи функции, которую он использует для расчета скорости обучения, что довольно грубо. Разминка LR должна выполняться на каждом шаг (партия), а не эпоха, поэтому нам нужно получить global_step (во все эпохи), чтобы вместо этого рассчитать скорость обучения и создать подкласс Callback class для создания пользовательского обратного вызова, а не просто передачи функции, поскольку нам нужно будет передавать аргументы при каждом вызове, что невозможно при простой передаче функции:

def func():
    return ...
    
keras.callbacks.LearningRateScheduler(func)

Этот подход удобен, когда вам не нужен высокий уровень настройки и вы не хотите вмешиваться в то, как Keras обрабатывает lr, и особенно если вы хотите использовать такие обратные вызовы, как ReduceLROnPlateau() так как он может работать только с плавающей точкой lr. Давайте реализуем прогрев скорости обучения, используя обратный вызов Keras, начиная с удобной функции:

def lr_warmup_cosine_decay(global_step,
                           warmup_steps,
                           hold = 0,
                           total_steps=0,
                           start_lr=0.0,
                           target_lr=1e-3):
    
    learning_rate = 0.5 * target_lr * (1 + np.cos(np.pi * (global_step - warmup_steps - hold) / float(total_steps - warmup_steps - hold)))

    
    warmup_lr = target_lr * (global_step / warmup_steps)

    
    
    if hold > 0:
        learning_rate = np.where(global_step > warmup_steps + hold,
                                 learning_rate, target_lr)
    
    learning_rate = np.where(global_step < warmup_steps, warmup_lr, learning_rate)
    return learning_rate

На каждом шаге мы рассчитываем скорость обучения и скорость обучения на разогреве (оба элемента расписания) по отношению к start_lr и target_lr. start_lr обычно начинается в 0.0, В то время target_lr зависит от вашей сети и оптимизатора – 1e-3 может быть не очень хорошим значением по умолчанию, поэтому обязательно установите цель запуска LR при вызове метода.

Если же линия индикатора global_step в обучении выше, чем warmup_steps мы установили – используем график косинусного затухания LR. Если нет, значит, мы все еще разогреваемся, поэтому используется прогревочный LR. Если hold аргумент установлен, мы будем держать target_lr для этого количества шагов после прогрева и до затухания косинуса. np.where() обеспечивает отличный синтаксис для этого:

np.where(condition, value_if_true, value_if_false)

Вы можете визуализировать функцию с помощью:

steps = np.arange(0, 1000, 1)
lrs = []

for step in steps:
  lrs.append(lr_warmup_cosine_decay(step, total_steps=len(steps), warmup_steps=100, hold=10))
plt.plot(lrs)

Теперь мы хотим использовать эту функцию как часть обратного вызова и передать шаг оптимизатора как global_step а не элемент произвольного массива — или вы можете выполнить вычисление внутри класса. Давайте подкласс Callback учебный класс:

from keras import backend as K

class WarmupCosineDecay(keras.callbacks.Callback):
    def __init__(self, total_steps=0, warmup_steps=0, start_lr=0.0, target_lr=1e-3, hold=0):

        super(WarmupCosineDecay, self).__init__()
        self.start_lr = start_lr
        self.hold = hold
        self.total_steps = total_steps
        self.global_step = 0
        self.target_lr = target_lr
        self.warmup_steps = warmup_steps
        self.lrs = []

    def on_batch_end(self, batch, logs=None):
        self.global_step = self.global_step + 1
        lr = model.optimizer.lr.numpy()
        self.lrs.append(lr)

    def on_batch_begin(self, batch, logs=None):
        lr = lr_warmup_cosine_decay(global_step=self.global_step,
                                    total_steps=self.total_steps,
                                    warmup_steps=self.warmup_steps,
                                    start_lr=self.start_lr,
                                    target_lr=self.target_lr,
                                    hold=self.hold)
        K.set_value(self.model.optimizer.lr, lr)

Ознакомьтесь с нашим практическим руководством по изучению Git с рекомендациями, принятыми в отрасли стандартами и прилагаемой памяткой. Перестаньте гуглить команды Git и на самом деле изучить это!

Во-первых, мы определяем конструктор класса и отслеживаем его поля. В каждой завершившейся партии мы будем увеличивать глобальный шаг, принимать к сведению текущий LR и добавлять его в список LR на данный момент. В начале каждой партии – мы будем рассчитывать LR, используя lr_warmup_cosine_decay() и установите этот LR как текущий LR оптимизатора. Это делается с помощью бэкенда set_value().

После этого просто рассчитайте общее количество шагов (длина/размер_пакета*эпохи) и возьмите часть этого числа для своего warmup_steps:


total_steps = len(train_set)*config['EPOCHS']



warmup_steps = int(0.05*total_steps)

callback = WarmupCosineDecay(total_steps=total_steps, 
                             warmup_steps=warmup_steps,
                             hold=int(warmup_steps/2), 
                             start_lr=0.0, 
                             target_lr=1e-3)

Наконец, создайте свою модель и предоставьте обратный вызов в fit() вызов:

model = keras.applications.EfficientNetV2B0(weights=None, 
                                            classes=n_classes, 
                                            input_shape=[224, 224, 3])
  
model.compile(loss="sparse_categorical_crossentropy",
                  optimizer='adam',
                  jit_compile=True,
                  metrics=['accuracy'])

В конце обучения вы можете получить и визуализировать измененные LR через:

lrs = callback.lrs 
plt.plot(lrs)

Повышение скорости обучения с косинусным распадом в Keras/TensorFlow PlatoBlockchain Data Intelligence. Вертикальный поиск. Ай.

Если вы построите историю модели, обученной с разминкой LR и без нее, вы увидите отчетливую разницу в стабильности обучения:

Повышение скорости обучения с косинусным распадом в Keras/TensorFlow PlatoBlockchain Data Intelligence. Вертикальный поиск. Ай.

Скорость обучения с подклассом LearningRateSchedule

Альтернативой созданию обратного вызова является создание LearningRateSchedule подкласс, который не манипулирует LR — он заменяет его. Этот подход позволяет вам немного больше погрузиться в бэкэнд Keras/TensorFlow, но при использовании его нельзя комбинировать с другими обратными вызовами, связанными с LR, такими как ReduceLROnPlateau(), который обрабатывает LR как числа с плавающей запятой.

Кроме того, использование подкласса потребует, чтобы вы сделали его сериализуемым (перегрузка get_config()), так как он становится частью модели, если вы хотите сохранить вес модели. Следует также отметить, что класс будет работать исключительно с tf.Tensorс. К счастью, единственная разница в том, как мы работаем, будет звонить tf.func() вместо np.func() поскольку API-интерфейсы TensorFlow и NumPy удивительно похожи и совместимы.

Давайте перепишем удобство lr_warmup_cosine_decay() функция для использования вместо этого операций TensorFlow:

def lr_warmup_cosine_decay(global_step,
                           warmup_steps,
                           hold = 0,
                           total_steps=0,
                           start_lr=0.0,
                           target_lr=1e-3):
    
    
    learning_rate = 0.5 * target_lr * (1 + tf.cos(tf.constant(np.pi) * (global_step - warmup_steps - hold) / float(total_steps - warmup_steps - hold)))

    
    warmup_lr = target_lr * (global_step / warmup_steps)

    
    
    if hold > 0:
        learning_rate = tf.where(global_step > warmup_steps + hold,
                                 learning_rate, target_lr)
    
    learning_rate = tf.where(global_step < warmup_steps, warmup_lr, learning_rate)
    return learning_rate

С помощью функции удобства мы можем создать подкласс LearningRateSchedule учебный класс. На каждого __call__() (пакетный), мы рассчитаем LR с помощью функции и вернем его. Вы можете, естественно, упаковать вычисление в подкласс.

Синтаксис чище, чем Callback подкласса, в первую очередь потому, что мы получаем доступ к step поля, вместо того, чтобы отслеживать его самостоятельно, но и несколько усложняет работу со свойствами класса — в частности, затрудняет извлечение lr от tf.Tensor() в любой другой тип для отслеживания в списке. Технически это можно обойти, запустив в нетерпеливом режиме, но это доставляет неудобство при отслеживании LR в целях отладки, и его лучше избегать:

class WarmUpCosineDecay(keras.optimizers.schedules.LearningRateSchedule):
    def __init__(self, start_lr, target_lr, warmup_steps, total_steps, hold):
        super().__init__()
        self.start_lr = start_lr
        self.target_lr = target_lr
        self.warmup_steps = warmup_steps
        self.total_steps = total_steps
        self.hold = hold

    def __call__(self, step):
        lr = lr_warmup_cosine_decay(global_step=step,
                                    total_steps=self.total_steps,
                                    warmup_steps=self.warmup_steps,
                                    start_lr=self.start_lr,
                                    target_lr=self.target_lr,
                                    hold=self.hold)

        return tf.where(
            step > self.total_steps, 0.0, lr, name="learning_rate"
        )

Параметры те же, и их можно рассчитать почти так же, как и раньше:


total_steps = len(train_set)*config['EPOCHS']



warmup_steps = int(0.05*total_steps)

schedule = WarmUpCosineDecay(start_lr=0.0, target_lr=1e-3, warmup_steps=warmup_steps, total_steps=total_steps, hold=warmup_steps)

А обучающий конвейер отличается только тем, что мы устанавливаем LR оптимизатора равным schedule:

model = keras.applications.EfficientNetV2B0(weights=None, 
                                            classes=n_classes, 
                                            input_shape=[224, 224, 3])
  
model.compile(loss="sparse_categorical_crossentropy",
                  optimizer=tf.keras.optimizers.Adam(learning_rate=schedule),
                  jit_compile=True,
                  metrics=['accuracy'])

history3 = model.fit(train_set,
                    epochs = config['EPOCHS'],
                    validation_data=valid_set)

Если вы хотите сохранить модель, WarmupCosineDecay расписание должно быть переопределено get_config() Метод:

    def get_config(self):
        config = {
          'start_lr': self.start_lr,
          'target_lr': self.target_lr,
          'warmup_steps': self.warmup_steps,
          'total_steps': self.total_steps,
          'hold': self.hold
        }
        return config

Наконец, при загрузке модели вам нужно будет передать WarmupCosineDecay как пользовательский объект:

model = keras.models.load_model('weights.h5', 
                                custom_objects={'WarmupCosineDecay', WarmupCosineDecay})

Заключение

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

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

Отметка времени:

Больше от Стекабьюс