Изменение шага learning rate в TensorFlow Keras

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

opt = keras.optimizers.Adam(learning_rate=0.01)

Однако в большинстве случаев постоянное значение learning_rate не является оптимальным. Существуют несколько способов изменять это значение во время обучения.

 Расписание в оптимизаторе

Вместо указания конкретного значения можно подать объект расписания tf.keras.optimizers.schedules.LearningRateSchedule. Например, так:

steps_per_epoch = 200

boundaries = [steps_per_epoch * n for n in [25,50,75]]

values = [1e-3,1e-4,1e-5,1e-6]

lr_sched = optimizers.schedules.PiecewiseConstantDecay(boundaries, values)

optimizer = optimizers.Adam(lr_sched)

Здесь оптимизатор будет изменять значения 3 раза - на 25, 50 и 75 шагах. Начальное значение learning_rate = 1e-3

 Изменение learning_rate, реализованное в виде callback

Второй способ реализовать изменения learning_rate - завести callback. Это можно сделать, например, следующим образом:

def lrfn(current_step, num_warmup_steps, lr_max, num_cycles=0.50, num_training_steps=N_EPOCHS):   

    if current_step < num_warmup_steps:

        if WARMUP_METHOD == 'log':

            return lr_max * 0.10 ** (num_warmup_steps - current_step)

        else:

            return lr_max * 2 ** -(num_warmup_steps - current_step)

    else:

        progress = float(current_step - num_warmup_steps) / float(max(1, num_training_steps - num_warmup_steps))

         return max(0.0, 0.5 * (1.0 + math.cos(math.pi * float(num_cycles) * 2.0 * progress))) * lr_max

LR_SCHEDULE = [lrfn(step, num_warmup_steps=N_WARMUP_EPOCHS, lr_max=LR_MAX, num_cycles=0.50) for step in range(N_EPOCHS)]

lr_callback = tf.keras.callbacks.LearningRateScheduler(lambda step: LR_SCHEDULE[step], verbose=1)

После чего call_back заводится при обучении модели:

history = model.fit(

    x=INPUTS,

    steps_per_epoch=STEPS,

    epochs=EPOCHS,   

    batch_size=BATCH_SIZE,

    validation_data=validation_data,

    callbacks=[

        lr_callback,

        WeightDecayCallback(),

    ],

    verbose = VERBOSE,

)

Конкретно в этом случае значение learning_rate меняется на каждом шаге. Это можно посмотреть по графику:

2023-04-08_15-51-18

Эта изображение было получено с помощью следующего кода:

def plot_lr_schedule(lr_schedule, epochs):

    fig = plt.figure(figsize=(20, 10))

    plt.plot([None] + lr_schedule + [None])

    # X Labels

    x = np.arange(1, epochs + 1)

    x_axis_labels = [i if epochs <= 40 or i % 5 == 0 or i == 1 else None for i in range(1, epochs + 1)]

    plt.xlim([1, epochs])

    plt.xticks(x, x_axis_labels) # set tick step to 1 and let x axis start at 1

    # Increase y-limit for better readability

    plt.ylim([0, max(lr_schedule) * 1.1])

    # Title

    schedule_info = f'start: {lr_schedule[0]:.1E}, max: {max(lr_schedule):.1E}, final: {lr_schedule[-1]:.1E}'

    plt.title(f'Step Learning Rate Schedule, {schedule_info}', size=18, pad=12)   

    # Plot Learning Rates

    for x, val in enumerate(lr_schedule):

        if epochs <= 40 or x % 5 == 0 or x is epochs - 1:

            if x < len(lr_schedule) - 1:

                if lr_schedule[x - 1] < val:

                    ha = 'right'

                else:

                    ha = 'left'

            elif x == 0:

                ha = 'right'

            else:

                ha = 'left'

            plt.plot(x + 1, val, 'o', color='black');

            offset_y = (max(lr_schedule) - min(lr_schedule)) * 0.02

            plt.annotate(f'{val:.1E}', xy=(x + 1, val + offset_y), size=12, ha=ha)

   

    plt.xlabel('Epoch', size=16, labelpad=5)

    plt.ylabel('Learning Rate', size=16, labelpad=5)

    plt.grid()

    plt.show()

plot_lr_schedule(LR_SCHEDULE, epochs=N_EPOCHS)