如何按照全局步骤在 Keras 中实现指数衰减学习率

How to implement exponentially decay learning rate in Keras by following the global steps

看下面的例子

# encoding: utf-8
import numpy as np
import pandas as pd
import random
import math
from keras import Sequential
from keras.layers import Dense, Activation
from keras.optimizers import Adam, RMSprop
from keras.callbacks import LearningRateScheduler

X = [i*0.05 for i in range(100)]

def step_decay(epoch):
    initial_lrate = 1.0
    drop = 0.5
    epochs_drop = 2.0
    lrate = initial_lrate * math.pow(drop, 
    math.floor((1+epoch)/epochs_drop))
    return lrate

def build_model():
    model = Sequential()
    model.add(Dense(32, input_shape=(1,), activation='relu'))
    model.add(Dense(1, activation='linear'))
    adam = Adam(lr=0.5)
    model.compile(loss='mse', optimizer=adam)
    return model

model = build_model()
lrate = LearningRateScheduler(step_decay)
callback_list = [lrate]

for ep in range(20):
    X_train = np.array(random.sample(X, 10))
    y_train = np.sin(X_train)
    X_train = np.reshape(X_train, (-1,1))
    y_train = np.reshape(y_train, (-1,1))
    model.fit(X_train, y_train, batch_size=2, callbacks=callback_list, 
              epochs=1, verbose=2)

在此示例中,LearningRateSchedule 根本不会改变学习率,因为在 epepoch=1 的每次迭代中。因此学习率只是常数(1.0,根据 step_decay)。事实上,我不是直接设置 epoch>1,而是像示例中那样做外层循环,而在每个循环的内部,我只是 运行 1 个纪元。 (当我实施深度强化学习而不是监督学习时就是这种情况)。

我的问题是如何在我的示例中设置指数衰减学习率以及如何在 ep.

的每次迭代中获得学习率

您实际上可以将 两个参数 传递给 LearningRateScheduler。 根据Keras documentation,调度器是

a function that takes an epoch index as input (integer, indexed from 0) and current learning rate and returns a new learning rate as output (float).

所以,基本上,只需将 initial_lr 替换为函数参数,如下所示:

def step_decay(epoch, lr):
    # initial_lrate = 1.0 # no longer needed
    drop = 0.5
    epochs_drop = 2.0
    lrate = lr * math.pow(drop,math.floor((1+epoch)/epochs_drop))
    return lrate

您实现的实际函数不是指数衰减(正如您在标题中提到的那样),而是 阶梯函数

此外,您提到您的学习率在循环内不会改变。这是真的,因为您同时设置了 model.fit(..., epochs=1,...)epochs_drop = 2.0。我不确定这是否是您想要的情况。您正在提供一个玩具示例,在这种情况下尚不清楚。

我想添加 更常见的情况 ,其中您不将 for 循环与 fit() 混合使用,只需提供一个不同的 epochs 参数在你的 fit() 函数中。在这种情况下,您有以下选择:

  1. 首先,keras 通过预定义的优化器提供衰减功能本身。例如在你的情况下 Adam() actual code is:

    lr = lr * (1. / (1. + self.decay * K.cast(self.iterations, K.dtype(self.decay))))

这也不完全是指数级的,它与 tensorflow's one 在某种程度上有所不同。此外,它仅在 decay > 0.0 时使用,因为它很明显。

  1. 要遵循指数衰减的张量流约定,您应该实现:

    decayed_learning_rate = learning_rate * ^ (global_step / decay_steps)

根据您的需要,您可以选择实现 Callback subclass 并在其中定义一个函数(请参阅下面的第 3 个项目符号)或使用 LearningRateScheduleractually exactly this with some checking:一个Callback子class,它在每个epoch结束时更新学习率

  1. 如果你想更好地处理你的学习率策略(例如每批次),你必须实施你的 subclass 因为据我所知没有实施 subclass为了这个任务。好的部分是它超级简单:

创建子class

class LearningRateExponentialDecay(Callback):

并添加 __init__() 函数,它将使用所有需要的参数初始化您的实例,并创建一个 global_step 变量来跟踪迭代(批次):

   def __init__(self, init_learining_rate, decay_rate, decay_steps):
      self.init_learining_rate = init_learining_rate
      self.decay_rate = decay_rate
      self.decay_steps = decay_steps
      self.global_step = 0

最后,在class里面添加实际的函数:

def on_batch_begin(self, batch, logs=None):
    actual_lr = float(K.get_value(self.model.optimizer.lr))
    decayed_learning_rate = actual_lr * self.decay_rate ^ (self.global_step / self.decay_steps)
    K.set_value(self.model.optimizer.lr, decayed_learning_rate)
    self.global_step += 1

真正酷的部分是,如果您希望上面的子 class 更新每个纪元,您可以使用 on_epoch_begin(self, epoch, logs=None),它很好地将纪元作为其签名的参数。这种情况甚至更容易,因为您可以完全跳过全局步骤(除非您想要一种更奇特的方式来应用衰减,否则现在不需要跟踪它)并在其位置使用 epoch