深度神經網路使用隨機梯度下降和可微分的誤差向後傳遞更新神經網路中所有的可訓引數 (trainable parameters

\theta

),從而學到一個層級化的表示,此表示相當於一個函式 f, 能近似的將輸入 x 對映到輸出 y, 即

f(x, \theta) \rightarrow y

,只要 x 與 y 之間存在連續的幾何變換。在此過程中,梯度下降演算法至關重要,引數

\theta

的調節過程可用一個簡單的公式表示,

\theta = \theta - lr * \frac{\partial loss}{\partial \theta}

其中

lr

表示學習率 (learning rate)。

\frac{\partial loss}{\partial \theta}

是誤差對引數

\theta

的梯度。

如果梯度為正,表示增大

\theta

,loss 也會隨之增大,此時可以將

\theta

減去一個正數,即

\theta = \theta - lr \times 正數

,達到減小誤差 (loss) 的目的。

如果梯度為負,表示增大

\theta

,loss 會隨之減小,此時可以將

\theta

減去一個負數,即

\theta = \theta - lr \times 負數

,達到減小誤差的目的。

注意:對於給定的神經網路輸入,

\frac{\partial loss}{\partial \theta}

是正是負可以透過鏈式法則解析算出。

在此公式中,沒有理論告訴大家學習率

lr

應該取多大。所有人都知道,如果

lr

取的太大,就會發散;取的太小,學習進度緩慢;取得合適,才會得到比較好的預測精度和泛化能力。我之前用視覺化的方式介紹過各種梯度下降對引數的依賴,沒看過的可以點進去,

但我之蜜糖,你之砒霜。不同的任務,需要不同的學習率 #FormatImgID_17# !

對於 SGD,

lr

不會自動適配的調整,煉丹師必須經過多次嘗試,尋找合適的

lr

對於 Adam,結果對初始

lr

不是非常敏感,為大部分人的最愛。但據說最後的泛化能力不如煉丹師手工打造的 SGD+Momentum (沒有驗證 AdamW 是否完全解決了這個問題)。

那如何才能尋找到最優的 #FormatImgID_21# 呢?這篇文章展示如何在 Keras 中實現 Learning Rate Range Test 演算法,尋找最合適的學習率區間。

Keras 的

callbacks 中有 ReduceLROnPlateau() 和 LearningRateScheduler() 函式可以動態的調整學習率。但前者只在驗證誤差停止衰減的時候減小學習率,後者只能在每個 Epoch 開始或結束的時候,改變學習率。LR Range Test 演算法將初始的學習率 lr 設為非常小的值 (比如10的負7次方),然後在每個

Batch

結束的時候增大學習率,看訓練誤差 loss 隨學習率的變化。一般來說,最開始因為 lr 太小,loss 變化緩慢,當 lr 增大到一個臨界值,loss 會迅速減小,再增大 lr ,又會出現發散。

需要注意的是,keras 提供的 LearningRateScheduler() 函式是在每個 Epoch 開始或結束的時候更改學習率。為了在 Keras 中使用 LR Range Test 演算法中,我們修改 LearningRateScheduler() ,定義一個新函式,BatchLearningRateScheduler()。這個函式在每個Batch開始或結束的時候,修改 lr。同時,為了在最後記錄訓練歷史, 我們定義新的回撥函式 LossHistory,整個程式碼如下,可以 Copy 儲存為 lr_range。py, 在訓練模組裡呼叫。

#LR Range Test in Keras, by lgpang

#Save this script as lr_range。py

import

keras

import

keras。backend

as

K

import

numpy

as

np

class

BatchLearningRateScheduler

keras

callbacks

Callback

):

“”“Learning rate scheduler for each batch

# Arguments

schedule: a function that takes an batch index as input

(integer, indexed from 0) and current learning rate

and returns a new learning rate as output (float)。

verbose: int。 0: quiet, 1: update messages。

”“”

def

__init__

self

schedule

verbose

=

0

):

super

BatchLearningRateScheduler

self

__init__

()

self

schedule

=

schedule

self

verbose

=

verbose

def

on_batch_begin

self

batch

logs

=

None

):

if

not

hasattr

self

model

optimizer

‘lr’

):

raise

ValueError

‘Optimizer must have a “lr” attribute。’

lr

=

float

K

get_value

self

model

optimizer

lr

))

try

# new API

lr

=

self

schedule

batch

lr

except

TypeError

# old API for backward compatibility

lr

=

self

schedule

batch

if

not

isinstance

lr

float

np

float32

np

float64

)):

raise

ValueError

‘The output of the “schedule” function ’

‘should be float。’

K

set_value

self

model

optimizer

lr

lr

if

self

verbose

>

0

print

\n

batch

%05d

: LearningRateScheduler setting learning ’

‘rate to

%s

。’

%

batch

+

1

lr

))

def

on_batch_end

self

batch

logs

=

None

):

logs

=

logs

or

{}

logs

‘lr’

=

K

get_value

self

model

optimizer

lr

def

LR_Range_Test_Schedule

batch

):

‘’‘increase lr by a small amount per batch’‘’

initial_lrate

=

1。0E-7

speed

=

1。0E-7

lr

=

initial_lrate

+

batch

*

speed

return

lr

class

LossHistory

keras

callbacks

Callback

):

def

on_train_begin

self

logs

=

{}):

self

losses

=

[]

self

lr

=

[]

def

on_batch_end

self

batch

logs

=

{}):

self

losses

append

logs

get

‘loss’

))

self

lr

append

LR_Range_Test_Schedule

len

self

losses

)))

loss_history

=

LossHistory

()

reduced_lr

=

BatchLearningRateScheduler

LR_Range_Test_Schedule

callbacks_list

=

loss_history

reduced_lr

在訓練模組裡,呼叫 callbacks_list, 並將結果儲存,

from lr_range import callbacks_list

。。。

model。fit(x_train, y_train,

validation_split=0。1,

epochs=epochs,

batch_size=256,

callbacks = callbacks_list,

verbose=1)

### save the loss and learning rate decay

loss_hist = callbacks_list[0]

np。savetxt(“loss_vs_batch_cumsum。csv”, loss_hist。losses)

np。savetxt(“lr_vs_batch_cumsum。csv”, loss_hist。lr)

使用 LR range test 在我的任務上的效果如下,對於 SGD 梯度下降演算法,在 lr 小於 0。01 時,loss 下降緩慢,在 0。01 與 0。1 之間,下降速度最快。在 0。1 到 70左右,下降緩慢。預期在更大的 lr,會發散。

Keras 實現最優學習率尋找(LR Range Test)

經測試,如果直接使用 lr=0。01, 兩個 Epoch 的驗證誤差為 0。0029; 如果直接使用 lr = 1, 訓練兩個 Epoch, 驗證誤差為 0。0012。如果 lr = 100, 則訓練誤差直接為 nan。將 lr 減小到 50,訓練誤差仍然為 nan,減小到 10,訓練穩定。所以上圖這種從小學習率開始,warm up到大學習率的方法,可以使的神經網路在非常大的 lr 穩定。如果直接使用 LR Range Test 尋找到的最大 lr 做固定 lr 的學習,則系統大機率會發散掉。

如果使用 Adam 最佳化演算法,LR Range Test 建議的 lr 區間為 [1E-6, 1E-4]。當然,這個時候 lr 已經是人為控制,Adam 還叫不叫 Adam 也是問題。

Keras 實現最優學習率尋找(LR Range Test)

總結:

LR Range Test 可以幫助尋找合適的學習率區間。如果不使用 LR Range Test,我絕對不會想到對於這個神經網路+資料組合,在 SGD 下,lr=10 是穩定的!本文中貼的 Keras 指令碼可以簡單的修改,定製各種各樣的動態變化學習率,比如 Step Decay,Exponential Decay,Warm Up,Cyclical LR,SGDR,

1Cycle 等等。

注:這篇知文受下面這篇部落格啟發