1. 程式人生 > >MNIST手寫數字識別(二)幾種模型優化方式介紹

MNIST手寫數字識別(二)幾種模型優化方式介紹

本篇的主要內容有:

  • 動態衰減法設定可變學習率
  • 為損失函式新增正則項
  • 滑動平均模型介紹

為了讓MNIST數字識別模型更準確,學習幾種常用的模型優化手段:

學習率的優化

學習率的設定一定程度上也會影響模型的訓練,如果學習率過小,那麼將會經過很長時間才會收斂到想要的結果,反之,學習率過大則可能會導致不收斂的結果,比如:優化 J(x) = x^{2}, 如果設定學習率為 1,那麼整個過程:

在這裡插入圖片描述

可以看到整個過程不管迭代多少次,都不會改變引數值了。這裡多提醒一句,我前幾天剛剛接觸到線性模型也遇到了這個問題,之前處理資料,我使用的學習率都是0.2, 當時遇到的資料集是這樣的:

train_x = np.array([3.3, 4.4,5.5,6.71,6.93,4.168,9.779,6.182,7.59,2.167,
                    7.042,10.791,5.313,7.997,5.654,9.27,3.1])
train_y = np.array([1.7,2.76,2.09,3.19,1.694,1.573,3.366,2.596,2.53,1.221,
                    2.827,3.465,1.65,2.904,2.42,2.94,1.3])

結果使用0.2 的學習率的使用就出問題了,模型無法收斂到一個正確的範圍,當時糾結了好一會才意識到是學習率設定錯了,畢竟資料的範圍就已經很小了,再使用0.2就相對大了,所以一定要根據具體的資料進行設定學習率!

言歸正傳,在TensorFlow中,為了解決固定學習率的這種弊端,提供了一種很靈活的學習率設定方法–指數衰減法,使用這個方法可以先用較大的學習率得到一個比較好的位置(也就是影象上由比較“陡峭”的地方快速來到比較“平緩”的位置),隨著迭代的過程,學習率會逐漸減小,從而使模型變得更穩定。

TensorFlow中實現了指數衰減法的函式是 tf.train.exponential_decay,這個函式可以指數級地減小學習率,實現功能如下:

decay_learning_rate = learning_rate * decay_rate ^(global_step / decay_steps)
引數:
    decayed_learning_rate: 每一輪結束後更新地學習率
    learning_rate: 最初設定地學習率
    decay_rate: 衰減係數
    decay_steps: 衰減速度

樣例

global_step = tf.Variable(0)

learning_rate = tf.train.exponential_decay(
    0.1, global_step, 100, 0.96, staircase=
True) learning_step = tf.train.GradientDescentOptimizer(learning_rate). minimize(loss, global_step=globalstep)

我們應用指數衰減法解決一個簡單地線性迴歸問題: 首先在沒有動態設定學習率的時候:

# 線性迴歸模型
import tensorflow as tf
import numpy as np

x_data = np.random.rand(100)
noise = np.random.normal(0.0, 0.02, x_data.shape)
y_data = 1.5 * x_data + 2.3 + noise     # 定義 w: 1.5  b: 2.3

# 定義線性模型
k = tf.Variable(np.random.rand(1))
b = tf.Variable(np.random.rand(1))
y = k * x_data + b

# 損失函式以及優化器定義
learning_rate = 0.2
loss = tf.reduce_mean(tf.square(y - y_data))
optimizer = tf.train.GradientDescentOptimizer(learning_rate)
train = optimizer.minimize(loss)

init_op = tf.global_variables_initializer()

with tf.Session() as sess:
    sess.run(init_op)
    for i in range(201):   
        sess.run(train)
        if i % 10 == 0:   # 10次就輸出一組 方便比較兩次的收斂速度
            print(sess.run([k, b]), learning_rate)

這時候的輸出

[array([0.83753064]), array([1.47330253])] 0.2
[array([1.39016995]), array([2.3620908])] 0.2
[array([1.41398799]), array([2.3490679])] 0.2
[array([1.4323059]), array([2.33850712])] 0.2
[array([1.44657744]), array([2.33027885])] 0.2
[array([1.45769655]), array([2.32386812])] 0.2
[array([1.46635957]), array([2.31887346])] 0.2
[array([1.47310902]), array([2.31498206])] 0.2
[array([1.47836758]), array([2.31195023])] 0.2
[array([1.48246459]), array([2.3095881])] 0.2
[array([1.48565661]), array([2.30774774])] 0.2
[array([1.48814355]), array([2.30631389])] 0.2
[array([1.49008115]), array([2.30519677])] 0.2   w 更新到1.49
[array([1.49159075]), array([2.3043264])] 0.2
[array([1.4927669]), array([2.30364829])] 0.2
[array([1.49368325]), array([2.30311997])] 0.2
[array([1.49439719]), array([2.30270835])] 0.2
[array([1.49495343]), array([2.30238765])] 0.2
[array([1.4953868]), array([2.30213779])] 0.2
[array([1.49572444]), array([2.30194312])] 0.2
[array([1.4959875]), array([2.30179145])] 0.2

使用指數衰減法之後,修改損失函式部分:

# 損失函式以及優化器定義
global_steps = tf.Variable(0)
learning_rate = tf.train.exponential_decay(
    0.2, global_steps, 100, 0.96, staircase=True)
loss = tf.reduce_mean(tf.square(y - y_data))
optimizer = tf.train.GradientDescentOptimizer(learning_rate)
train = optimizer.minimize(loss, global_step=global_steps)

使用指數衰減法之後的輸出:

[array([1.24186946]), array([1.37994397]), 0.2]
[array([1.64187716]), array([2.22352439]), 0.2]
[array([1.60681132]), array([2.24353267]), 0.2]
[array([1.58028094]), array([2.25804498]), 0.2]
[array([1.56040553]), array([2.26891649]), 0.2]
[array([1.54551588]), array([2.27706087]), 0.2]
[array([1.53436132]), array([2.28316223]), 0.2]
[array([1.52600488]), array([2.28773306]), 0.2]
[array([1.51974467]), array([2.29115729]), 0.2]   w 更新到1.51左右
[array([1.51505484]), array([2.29372254]), 0.2]
[array([1.51155375]), array([2.29563758]), 0.192]
[array([1.50901135]), array([2.29702823]), 0.192]
[array([1.50708427]), array([2.29808231]), 0.192]
[array([1.50562359]), array([2.29888128]), 0.192]
[array([1.50451642]), array([2.29948688]), 0.192]
[array([1.50367722]), array([2.29994591]), 0.192]
[array([1.50304112]), array([2.30029384]), 0.192]
[array([1.50255898]), array([2.30055757]), 0.192]
[array([1.50219352]), array([2.30075746]), 0.192]
[array([1.50191652]), array([2.30090898]), 0.192]
[array([1.50170729]), array([2.30102343]), 0.18432]

可以看到,設定了動態學習率之後,模型更快地收斂到了同樣準確率的地方。

過擬合問題的優化

過擬合現象與欠擬合現象相對,欠擬合是模型太弱,不能很好地對資料進行處理,過擬合則是模型學習能力太強,以至於記住了模型中的噪音部分,這樣模型不具有“泛化”的能力,為了避免過擬合,一種非常常用的方法是正則化(regularization),簡單來說就是修改原來的損失函式,加入模型複雜度衡量的指標,這時候,進行優化就是 J(θ)+λR(w)J(\theta) + \lambda R(w),通常使用的這個 R(w) 有兩種,一種是 L1 正則化,計算方式是: R(w)=w1=w R(w) = \left \| w \right \|_{1} = \sum\left | w \right |

另一種是L2 正則化,計算方式: R(w)=w12=w2 R(w)=\left \| w \right \|_{1}^{2} = \sum\left | w \right |^{2} 兩種方式都是通過限制權重的大小,使得模型不能任意地擬合訓練資料中地隨機噪音,其中L1正則化方法會使得引數變得稀疏(也就是會有更多地引數變為0)並且不能求導,所以感覺大部分使用地都是L2正則化方式,在TensorFlow中使用方式如下:

loss = tf.reduce_mean(tf.square(y - y_data)) + tf.contrib.layers.l2_regularizer(lambda)(w)
                                               # 這就是定義的 l2 正則化
                                               # lambda 是正則化係數  w 是 需要計算正則化的引數
                                               # 如果手工計算一下某個權重陣列的 L2 正規化 會與這個函式的 
                                               # 輸出不一樣 因為這個函式會自動除以2 方便求導

滑動平均模型

滑動平均模型穩定模型的原理很簡單,變數變化的同時會有一個與之對應的 shadow_variable 逼近變化後的變數值,這樣,通過記錄過去一段時間的平均值,消除數值波動對引數變化的影響,從而使得模型的泛化能力更好(提高 robust)。

在TensorFlow中提供了使用滑動平均的函式:

tf.train.ExponentialMovingAverage
# 呼叫結構:
tensorflow.python.training.moving_averages.ExponentialMovingAverage def __init__(self,
             decay: Any,    # 衰減率 用來控制模型更新的速度
             num_updates: Any = None,    # 模型迭代的輪數
             zero_debias: bool = False,
             name: str = "ExponentialMovingAverage") -> None

滑動平均模型對每一個變數都會維護一個影子變數(shadow_variable),或者稱它為滑動平均,這個shadow_variable的初始值就是對應的變數的初始值,在每次執行時,shadow_variable的更新公式是:

shadow_variable = decay * shadow_variable + (1 - decay) * variable

從這個更新公式中可以看出來,decay 決定了模型的更新速度,decay較大的模型趨於穩定,在實際使用的時候一般會將decay設定為比較大(0.99左右),這樣可以使模型前期更新較快,而後期趨於穩定,這就需要一個動態的decay,所以,函式還提供了num_undates 引數,可以用來動態設定decay,動態衰減率的更新公式是: decay=min{decay,1+numupdates10+numupdates} decay =min \begin{Bmatrix} decay ,& \frac{1+num_updates}{10+num_updates} \end{Bmatrix} 很顯然,在上面的公式中在 num_updates 比較小的時候, decay會選擇後者,之後隨著訓練次數的增加,decay會變大,也就使得模型趨於穩定。

下面看一個例子,演示滑動平均模型如何使用:

import tensorflow as tf

# 定義計算滑動平均的變數 變數的型別必須是 實數
v1 = tf.Variable(0, dtype=tf.float32)

# 模擬神經網路中迭代的輪數的變數
step = tf.Variable(10, trainable=False)

# 定義一個滑動平均的類 初始化的衰減率為0.99, 衰減率的變數是step
ema = tf.train.ExponentialMovingAverage(0.99, step)

# 定義每次滑動平均所更新的列表 這裡
maintrain_average_op = ema.apply([v1])


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

    # 更新 v1 的滑動平均值
    # 衰減率為 min(0.99, (1+step) / (10 + step)=0.1) = 0.1
    sess.run(maintrain_average_op)

    # 下面演示三種情況下的滑動平均輸出
    # 1
    # 剛開始滑動平均 與 原變數 值一樣
    print("1: ", sess.run([v1, ema.average(v1)]))


    # 2
    # 只變量更新為5 step保持不變
    sess.run(tf.assign(v1, 5))
    # 此時執行 decay會更新為 min(0.99, (1+step)/ (10 + step)=0.1) = 0.1
    sess.run(maintrain_average_op)
    print("2: ", sess.run([v1, ema.average(v1)]))


    # 3
    # 這裡模擬訓練中更為一般的情況:
    # step v1 都改變
    sess.run(tf.assign(v1, 10))
    sess.run(tf.assign(step, 10000))
    # 這時候計算的 decay 應該是 0.99 了
    print("3: ",sess.run([v1, ema.average(v1)]))

輸出:

1:  [0.0, 0.0]
2:  [5.0, 2.25]
3:  [10.0, 2.25]

以上~