iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 20
0
Google Developers Machine Learning

How to Train Your Model 訓模高手:我的 Tensorflow 個人使用經驗系列文系列 第 20

【20】tensorflow 訓練技巧:觀念一次就搞懂 Gradient Descent, Momentum, Adagrad, RMSProp, Adam 五種 optimizer 差異篇

在模型更新時,我們可以利用 損失函數 (cost function) 來得到誤差,再來我們會根據這個函數的微分去做權重更新,而權重值更新的策略如何,就是看你使用了怎麼樣的優化器 (optimizer),那這個優化器在 tensorflow 裡又該如何使用呢?且每個不同的優化器個代表什麼意義呢?今天來為大家介紹!

首先,我們假定要處理一個分類問題,所以會用 tf.nn.sparse_softmax_cross_entropy_with_logits ,作為損失函數,最前面程式碼我這樣規劃。

global_step = global_step = tf.train.get_or_create_global_step()

x = tf.placeholder(shape=[None, 2], dtype=tf.float32, name='x')
y = tf.placeholder(shape=[None], dtype=tf.int32, name='y')

with tf.variable_scope('backend'):
   net = tf.layers.dense(x, 64, activation=tf.nn.relu6, 
                               kernel_initializer=WEIGHT_INIT,
                               bias_initializer=BIAS_INIT,
                               kernel_regularizer=REGULARIZER, 
                               bias_regularizer=REGULARIZER, 
                               name='dense_1')
   net = tf.layers.dense(net, 64, activation=tf.nn.relu6,
                               kernel_initializer=WEIGHT_INIT,
                               bias_initializer=BIAS_INIT,
                               kernel_regularizer=REGULARIZER,
                               bias_regularizer=REGULARIZER, 
                               name='dense_2')
   logits = tf.layers.dense(net, 2, kernel_initializer=WEIGHT_INIT,
                               bias_initializer=BIAS_INIT,
                               kernel_regularizer=REGULARIZER,
                               bias_regularizer=REGULARIZER, 
                               name='final_dense')

loss = tf.reduce_mean(
   tf.nn.sparse_softmax_cross_entropy_with_logits(
       logits=logits, labels=y), name='inference_loss')

然後特別寫了一個方法來決定要使用哪種優化器。

def get_optimizer(opt_type):
   if opt_type == 'gd':
       return tf.train.GradientDescentOptimizer(learning_rate=0.1)
   if opt_type == 'momentum':
       return tf.train.MomentumOptimizer(learning_rate=0.1, momentum=0.9)
   if opt_type == 'ada_grad':
       return tf.train.AdagradOptimizer(learning_rate=0.1, initial_accumulator_value=0.1)
   if opt_type == 'rmsp':
       return tf.train.RMSPropOptimizer(learning_rate=0.1, decay=0.9, momentum=0)
   if opt_type == 'adam':
       return tf.train.AdamOptimizer(learning_rate=0.1, beta1=0.9, beta2=0.99)

在選定完優化器後,我們可以幫此優化器綁定 global_step,如此只要 train_op 執行過一次,不只會更新權重,同時也會自動幫 global_step 加一。

opt = get_optimizer(opt_type)

grads = opt.compute_gradients(loss)
train_op = opt.apply_gradients(grads, global_step=global_step)

再來,就是這次的資料集,為了方便 demo ,我寫了一個座標位置 xor 的方法來當測試,內容很簡單,他會產生 batch 為 16 的二維坐標,其座標值在 -1~1 之間 (遇到 0 就算了),然後在第一和第三象限的座標結果值為 0,二和四象限的座標結果值為 1,兩行搞定。

def get_xor_data():
   x = (np.random.rand(16, 2) - 0.5) * 2
   y = [0 if 0 < x1 * x2 else 1 for x1, x2 in x]

   return x, y

我們訓練 100 次,並記錄所花費的時間,大家就可以自己比較各個不同優化器的收斂速度。

with tf.Session() as sess:
   sess.run(tf.global_variables_initializer())
   sess.run(tf.local_variables_initializer())

   start = timeit.default_timer()
   for _ in range(0, 100):
       x_v, y_v = get_xor_data()
       _, count = sess.run([train_op, global_step], feed_dict={x: x_v, y: y_v})

       print(f'iter: {count}')

   print(f'done. cost {timeit.default_timer() - start} sec.')

結果圖:
https://ithelp.ithome.com.tw/upload/images/20190928/20107299lUqHbxByur.png

tensorflow 幫我們做了那麼多種優化器,用起來是很方便,但是你真的懂每個優化器使用時機嗎?沒錯,這次我也想挑戰以自己的話來解釋這幾種優化器的差異!

一、GradientDescentOptimizer

tf.train.GradientDescentOptimizer(learning_rate=0.1)

https://ithelp.ithome.com.tw/upload/images/20190928/20107299qkKwFK9w0o.png
這個應該大家都知道,即最基本的 gradient descent,所需參數僅需要學習速度 learning rate。

對 cost function (以 J 表示) 的微分式子如下,t 表示某 step,theta 表示權重值。
https://ithelp.ithome.com.tw/upload/images/20190928/20107299geZQIGLiab.png

所以權重的更新就可以表示成(式子的 alpha 就是 learning rate):

二、MomentumOptimizer

tf.train.MomentumOptimizer(learning_rate=0.1, momentum=0.9)

https://ithelp.ithome.com.tw/upload/images/20190928/20107299ZiQOnQ2mdA.png
這個方法多了 momentum 這個參數,這個值通常設為 0.9 或 0.99,代表這次的更新量有多少部分是參考前一次的值,從公式上來理解可以比較清楚。
https://ithelp.ithome.com.tw/upload/images/20190928/20107299hQiPn3BU5x.png
mu (很像 u 的符號) 表示 momentum 的參數值,v 表示目前更新值的大小,因此當模型開始訓練時,每次的更新量會被之前的更新量牽制,比較不容易忽大忽小,以增加穩定性。

三、AdagradOptimizer

tf.train.AdagradOptimizer(learning_rate=0.1, initial_accumulator_value=0.1)

https://ithelp.ithome.com.tw/upload/images/20190928/20107299MmLD8VgiaP.png

這是以另一種想法來更新權重,我們用了一個會慢慢累加的快取 (cache),來實作,我們將這個 cache 初始值設為 0.1 ,我們來看公式:

https://ithelp.ithome.com.tw/upload/images/20190928/20107299hMBXwPdBHV.png

由於 gradient 的值會平方,所以保證 cache 一定是正數,可以放心開根號,但有些框架會預設把 cache 起始值設為0,那第一次迭代時,就有可能發生對0開根號的窘狀,所以會在公式中補個很小的 epsilon 來避免。
仔細想想,這樣的公式前其後和其會有什麼差異?訓練前期時,因為 cache 很小 又放在分母, gradient 除上這個值後會增大,因此更新量也大,訓練到後期時,cache 增大,gradient 除上大數字後,會讓更新量變小,因此牽制了後期的訓練量,使它越來越穩定。

四、RMSPropOptimizer

tf.train.RMSPropOptimizer(learning_rate=0.1, decay=0.9, momentum=0)

https://ithelp.ithome.com.tw/upload/images/20190928/201072991s5q1fJLLz.png

rmsprop 其實很簡單,它只比上面 adagrad 多了decay 的概念,所以有了 decay 這個參數,通常為 0.9 或 0.99,公式如下:

https://ithelp.ithome.com.tw/upload/images/20190928/20107299kC83ZHMpJY.png

公式和 adagrad 幾乎一樣,只是多了一個 decay 值(這邊表示為 rho 很像 p 的符號),那這個 rho 代表什麼意義呢?沒錯,它跟 momentum 很像,都代表著這次的 cache 值用多少部分的前一次的 cache 值加上這次的 gradient 值,設成 0.99 就是要用 99% 的前一次 cache 來更新。

而 tensorflow 的 API中多了 momentum 參數可以指定,其意義和前面介紹的 momentum 意思相同,但這邊為了專注解釋 rmsprop 所以就不多贅述。

五、AdamOptimizer

tf.train.AdamOptimizer(learning_rate=0.1, beta1=0.9, beta2=0.99)

https://ithelp.ithome.com.tw/upload/images/20190928/20107299P8UY3V1kKo.png

如果讀者之前有讀過其他 adam 有關的教學,八成都會提到 adam 是 momentum 加 rmsprop 兩者的方法,呃...在這邊我想說的是,這並不那麼精確,如果是這兩種方法的總和,那上面 tensorflow 的 RMSPropOptimizer 就已經有 momentum 可以用了。那話說回來,這和 adam 有什麼不一樣的地方呢?那就是 adam 多了 bias corrections 這個用法,我們慢慢分解公式。

https://ithelp.ithome.com.tw/upload/images/20190928/20107299nmqLC7PWRk.png

前面提到的 momentum 和 rmsprop 個別都有參數 mu 和 rho 的 decay 概念來控制,在 adam 中,我們個別將其改名 beta1 和 beta2,然後又因為這個公式很剛好又可以被表示成統計學的一二級動差,所以我們換成 mean (公式中的m)和 var (公式中的v)來表示。

https://ithelp.ithome.com.tw/upload/images/20190928/20107299sSsAOYKbLF.png

上面戴帽子m 和帽子v 就是經過 bias corrections 的參數,如果你觀察 bias corrections 的公式,可以發現初期 t 很小時,beta1 和 beta2 會把 m 和 v 校正成比較大的參數,以便放大初期的學習效果,等到訓練到後期時,t 慢慢變大,因為 beta1 和 beta2 兩個都是小於1的參數,經過多次方後會超級小,最後根本帽子m 和帽子v 等同於原本的m 和 v (即不校正),所以更新又會趨緩讓模型變得穩定。

看完以上五種方法,大家應該有發現不管哪種方法,都是希望訓練初期可以讓模型走大步一點以加速收斂,直到訓練中後期再慢慢趨緩,避免損失震盪。

呼!為了這篇文,我在 google doc 打了好久的公式,這是我很用心的一篇,自己也花了不少時間把網路上零散的知識一次整理好,如果喜歡還請按個讚,也歡迎提問!

github原始碼


上一篇
【19】tensorflow 訓練技巧:用 tf.GraphKeys.TRAINABLE_VARIABLES 評估模型效能篇
下一篇
【21】tensorflow 訓練技巧:使用 tf.summary 搭配 tensorboard 儀表板篇
系列文
How to Train Your Model 訓模高手:我的 Tensorflow 個人使用經驗系列文31

尚未有邦友留言

立即登入留言