TensorFlow之DNN(三):神經網絡的正則化方法(Dropout、L2正則化、早停和數據增強)


這一篇博客整理用TensorFlow實現神經網絡正則化的內容。

深層神經網絡往往具有數十萬乃至數百萬的參數,可以進行非常復雜的特征變換,具有強大的學習能力,因此容易在訓練集上過擬合。緩解神經網絡的過擬合問題,一般有兩種思路,一種是用正則化方法,也就是限制模型的復雜度,比如Dropout、L1和L2正則化、早停和權重衰減(Weight Decay),一種是增大訓練樣本量,比如數據增強(Data Augmentation)。這些方法的原理闡述可以看我之前整理的文章《深度學習之正則化方法》。

下面用TensorFlow來實現這些正則化方法。

一、Dropout正則化

首先來實現Dropout正則化。這種方法實在是太優秀太流行了,是一個比微信紅包更天才的想法,對於提高模型的准確性有立竿見影的效果。

1、Dropout怎么做的呢?

其實很簡單,在每個訓練步驟中,輸入層、隱藏層(不包括輸出層)的神經元以p的概率被隨機丟棄(諧音記憶法:神經元被抓爆了),然后在這次訓練中,被丟棄的神經元不再起作用,但是在接下來的訓練中,之前被“抓爆”的神經元可能繼續被丟棄,也可能重新加入訓練。

這個p叫做dropout rate(丟棄率),一般設置為0.5,也可以相機抉擇。如果發現模型可能發生嚴重的過擬合問題,可以增大dropout rate,比如大型神經網絡模型,而如果模型發生過擬合的風險較小,那么可以減小dropout rate,比如小型神經網絡模型。

此外,與Batch Normalization有點類似,Dropout在訓練階段和測試階段的做法不一樣。訓練階段就是按照上面所說的去做,而測試階段不需要做神經元的丟棄,這就是導致一個問題:假設p=0.5,那么測試階段每個神經元的輸入信號大約是訓練階段的兩倍。為此,我們需要把神經元的輸入連接權重乘以保持概率0.5(keep prop,也就是1-dropout rate),讓輸入信號減小一倍,與訓練階段大概保持一致。

2、為什么同是正則化方法,Dropout卻如此優秀呢?

第一個原因是如果某個神經元相鄰的伙伴被丟棄了,那么這個神經元就需要學會和更遠的神經元進行配合,同時它自己也要一個頂倆,讓自己更有用。 模型不會依賴於某些神經元,每個神經元都會受到特殊關注,從而使網絡變得更強大。最終模型對輸入的微小變化不再敏感,這等價於測試階段輸入前所未見的樣本時,模型也能很好地進行預測。

第二個原因是Dropout可以看成是一種集成學習方法。每次對神經元進行隨機丟棄,都會產生一個不同的神經網絡結構,那么訓練完畢后所得到的最終的神經網絡,就可以看作是所有這些小型神經網絡的集成

於是Dropout正則化就像國發鄧紫棋一樣優秀了。

3、用TensorFlow實現Dropout

使用TensorFlow實現Dropout,可以將tf.layers.dropout()這個函數應用於輸入層和每個隱藏層。在訓練期間,此函數隨機丟棄一些神經元(將它們的輸出設置為0)。訓練結束后,這個函數就不再發揮作用了。

好,下面還是用MINIST數據集來構建一個神經網絡模型,用Dropout來正則化,除了把優化器設為Momentum外沒有使用上一篇博客中整理的其他加速方法。

第一步還是先准備小批量樣本用於訓練,以及准備驗證集和測試集。

import tensorflow as tf
import numpy as np
from functools import partial
import time
from datetime import timedelta

# 記錄訓練花費的時間
def get_time_dif(start_time):
    end_time = time.time()
    time_dif = end_time - start_time
    #timedelta是用於對間隔進行規范化輸出,間隔10秒的輸出為:00:00:10    
    return timedelta(seconds=int(round(time_dif)))

# 定義輸入層、輸出層和中間隱藏層的神經元數量
n_inputs = 28 * 28  # MNIST
n_hidden1 = 300
n_hidden2 = 100
n_outputs = 10

# 准備訓練數據集、驗證集和測試集,並生成小批量樣本
(X_train, y_train), (X_test, y_test) = tf.keras.datasets.mnist.load_data()

X_train = X_train.astype(np.float32).reshape(-1, 28*28) / 255.0
X_test = X_test.astype(np.float32).reshape(-1, 28*28) / 255.0
y_train = y_train.astype(np.int32)
y_test = y_test.astype(np.int32)
X_valid, X_train = X_train[:5000], X_train[5000:]
y_valid, y_train = y_train[:5000], y_train[5000:]

def shuffle_batch(X, y, batch_size):
    rnd_idx = np.random.permutation(len(X))
    n_batches = len(X) // batch_size
    for batch_idx in np.array_split(rnd_idx, n_batches):
        X_batch, y_batch = X[batch_idx], y[batch_idx]
        yield X_batch, y_batch

第二步是在構建網絡層時,運用Dropout正則化。

下面的代碼已經進行了注釋,這里再說明一點,在隱藏層的神經元中,對輸入值是先激活再去做Dropout,也就是對f(WX+b)進行丟棄,而不是對WX+b。

X = tf.placeholder(tf.float32, shape=(None, n_inputs), name="X")
y = tf.placeholder(tf.int32, shape=(None), name="y")

# 和Batch Norm的操作有點類似,先定義一個trianing
training = tf.placeholder_with_default(False, shape=(), name='training')

# 設置丟棄率率
dropout_rate = 0.5  # == 1 - keep_prob

with tf.name_scope("dnn"):
    
    # 在輸入層對輸入數據先進行Dropout,便於應用到隱藏層
    X_drop = tf.layers.dropout(X, dropout_rate, training=training)
    # 在隱藏層先進行激活,得到激活之后的值
    hidden1 = tf.layers.dense(X_drop, n_hidden1, activation=tf.nn.relu,
                              name="hidden1")
    # 把Dropout應用到隱藏層,對激活值進行隨機丟棄,所以這里沒有激活函數了
    hidden1_drop = tf.layers.dropout(hidden1, dropout_rate, training=training)
    hidden2 = tf.layers.dense(hidden1_drop, n_hidden2, activation=tf.nn.relu,
                              name="hidden2")
    hidden2_drop = tf.layers.dropout(hidden2, dropout_rate, training=training)
    logits = tf.layers.dense(hidden2_drop, n_outputs, name="outputs")

第三步定義模型其他部分,然后進行訓練和測試。

這里再提醒一點,那就是在sess.run()中,別忘了傳入feed_dict={training: True}

取batch size = 50,訓練耗時1分08秒,測試精度為97.41%。

with tf.name_scope("loss"):
    xentropy = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=y, logits=logits)
    loss = tf.reduce_mean(xentropy, name="loss")

learning_rate = 0.01    
with tf.name_scope("train"):
    optimizer = tf.train.MomentumOptimizer(learning_rate, momentum=0.9)
    training_op = optimizer.minimize(loss)    

with tf.name_scope("eval"):
    correct = tf.nn.in_top_k(logits, y, 1)
    accuracy = tf.reduce_mean(tf.cast(correct, tf.float32))
    
init = tf.global_variables_initializer()
saver = tf.train.Saver()

n_epochs = 40
batch_size = 50

with tf.Session() as sess:
    init.run()
    start_time = time.time()
    for epoch in range(n_epochs):
        for X_batch, y_batch in shuffle_batch(X_train, y_train, batch_size):
            sess.run(training_op,
                     feed_dict={training: True, X: X_batch, y: y_batch})
        if epoch % 5 ==0 or epoch == 39:   
            accuracy_batch = accuracy.eval(feed_dict={X: X_batch, y: y_batch})
            accuracy_val = accuracy.eval(feed_dict={X: X_valid, y: y_valid})
            print(epoch, "Batch accuracy:", accuracy_batch,"Validation accuracy:", accuracy_val)
    time_dif = get_time_dif(start_time)
    print("\nTime usage:", time_dif)
    save_path = saver.save(sess, "./my_model_final_dropout.ckpt")
    
with tf.Session() as sess:
    saver.restore(sess, "./my_model_final_dropout.ckpt") # or better, use save_path
    X_test_20 = X_test[:20]
    # 得到softmax之前的輸出
    Z = logits.eval(feed_dict={X: X_test_20})
    # 得到每一行最大值的索引
    y_pred = np.argmax(Z, axis=1)
    print("Predicted classes:", y_pred)
    print("Actual calsses:   ", y_test[:20])
    # 評估在測試集上的正確率
    acc_test = accuracy.eval(feed_dict={X: X_test, y: y_test})
    print("\nTest_accuracy:", acc_test)
0 Batch accuracy: 0.96 Validation accuracy: 0.9316
5 Batch accuracy: 0.94 Validation accuracy: 0.965
10 Batch accuracy: 1.0 Validation accuracy: 0.9728
15 Batch accuracy: 0.96 Validation accuracy: 0.9728
20 Batch accuracy: 1.0 Validation accuracy: 0.9764
25 Batch accuracy: 0.96 Validation accuracy: 0.9768
30 Batch accuracy: 0.96 Validation accuracy: 0.9768
35 Batch accuracy: 0.98 Validation accuracy: 0.979
39 Batch accuracy: 0.96 Validation accuracy: 0.977

Time usage: 0:01:08
INFO:tensorflow:Restoring parameters from ./my_model_final_dropout.ckpt
Predicted classes: [7 2 1 0 4 1 4 9 6 9 0 6 9 0 1 5 9 7 3 4]
Actual calsses:    [7 2 1 0 4 1 4 9 5 9 0 6 9 0 1 5 9 7 3 4]

Test_accuracy: 0.9741

二、L1和L2正則化

實現了優秀的Dropout正則化,我們再把傳統機器學習領域中比較通用的L1和L2正則化方法運用到神經網絡的訓練中。

1、如何做L1和L2正則化?

L1和L2正則化方法就是把權重的L1范數或L2范數加入到經驗風險最小化的損失函數中(或者把二者同時加進去),用來約束神經網絡的權重,讓部分權重為0(L1范數的效果)或讓權重的值非常小(L2范數的效果),從而讓模型變得簡單,減少過擬合。得到的損失函數為結構風險最小化的損失函數

公式如下,p∈{1,2},λ是正則化系數,如果模型可能發生嚴重的過擬合問題,那就選擇比較大的λ,比如訓練一個非常龐大的網絡,否則可以設置得小一些。

2、L1正則化和L2正則化的區別?

L1正則化會使得最終的權重參數是稀疏的,也就是說權重矩陣中很多值為0;而L2正則化會使得最終的權重值非常小,但不會等於0。這么說來L1正則化有點像Dropout的另一種不常見的方式——丟棄連接邊。在實際操作中一般來說選擇L2正則化,因為L1范數在取得最小值處是不可導的,這會給后續求梯度帶來麻煩。

用TensorFlow來做L1正則化或L2正則化,就要在構建網絡時傳入相應的函數:tf.contrib.layers.l1_regularizer(),tf.contrib.layers.l2_regularizer() 或者 tf.contrib.layers.l1_l2_regularizer()。並且在定義損失時,把L1正則化或L2正則化的損失添加到經驗風險損失中去。這一部分的代碼如下:

X = tf.placeholder(tf.float32, shape=(None, n_inputs), name="X")
y = tf.placeholder(tf.int32, shape=(None), name="y")

# 這是正則化系數,由於這是小型網絡模型,過擬合的可能比較小,所以這個系數設置得小一些。
scale = 0.001

# 構造這個全連接模塊,傳入各層都相同的參數,便於復用。
my_dense_layer = partial(
    tf.layers.dense, activation=tf.nn.relu,
    # 在這里傳入了L2正則化函數,並在函數中傳入正則化系數。
    kernel_regularizer=tf.contrib.layers.l2_regularizer(scale))

with tf.name_scope("dnn"):
    hidden1 = my_dense_layer(X, n_hidden1, name="hidden1")
    hidden2 = my_dense_layer(hidden1, n_hidden2, name="hidden2")
    logits = my_dense_layer(hidden2, n_outputs, activation=None,
                            name="outputs")
    
with tf.name_scope("loss"):                                     
    xentropy = tf.nn.sparse_softmax_cross_entropy_with_logits(  
        labels=y, logits=logits) 
    # 經驗風險損失
    base_loss = tf.reduce_mean(xentropy, name="avg_xentropy")   
    # L2正則化損失
    reg_losses = tf.get_collection(tf.GraphKeys.REGULARIZATION_LOSSES)
    # 必須將正規化損失添加到基本損失中,構成結構風險損失。不過沒有明白為啥把base_loss放在列表里。
    loss = tf.add_n([base_loss] + reg_losses, name="loss")

完整的代碼如下,下面的代碼和Dropout正則化的代碼還是有不少差異,需要注意。

前面說了選擇L2正則化可能效果更好,為了驗證這個說法,我選擇batch size = 50,對神經網絡分別做L1正則化和L2正則化,得到的測試精度分別為94.95%和98.17%,可見L2正則化的確效果更好。

import tensorflow as tf
import numpy as np
from functools import partial
import time
from datetime import timedelta

# 記錄訓練花費的時間
def get_time_dif(start_time):
    end_time = time.time()
    time_dif = end_time - start_time
    #timedelta是用於對間隔進行規范化輸出,間隔10秒的輸出為:00:00:10    
    return timedelta(seconds=int(round(time_dif)))

# 定義輸入層、輸出層和中間隱藏層的神經元數量
n_inputs = 28 * 28  # MNIST
n_hidden1 = 300
n_hidden2 = 100
n_outputs = 10

# 准備訓練數據集、驗證集和測試集,並生成小批量樣本
(X_train, y_train), (X_test, y_test) = tf.keras.datasets.mnist.load_data()

X_train = X_train.astype(np.float32).reshape(-1, 28*28) / 255.0
X_test = X_test.astype(np.float32).reshape(-1, 28*28) / 255.0
y_train = y_train.astype(np.int32)
y_test = y_test.astype(np.int32)
X_valid, X_train = X_train[:5000], X_train[5000:]
y_valid, y_train = y_train[:5000], y_train[5000:]

def shuffle_batch(X, y, batch_size):
    rnd_idx = np.random.permutation(len(X))
    n_batches = len(X) // batch_size
    for batch_idx in np.array_split(rnd_idx, n_batches):
        X_batch, y_batch = X[batch_idx], y[batch_idx]
        yield X_batch, y_batch
        
X = tf.placeholder(tf.float32, shape=(None, n_inputs), name="X")
y = tf.placeholder(tf.int32, shape=(None), name="y")

# 這是正則化系數,由於這是小型網絡模型,過擬合的可能比較小,所以這個系數設置得小一些。
scale = 0.001

# 構造這個全連接模塊,傳入各層都相同的參數,便於復用。
my_dense_layer = partial(
    tf.layers.dense, activation=tf.nn.relu,
    # 在這里傳入了L2正則化函數,並在函數中傳入正則化系數。
    kernel_regularizer=tf.contrib.layers.l2_regularizer(scale))

with tf.name_scope("dnn"):
    hidden1 = my_dense_layer(X, n_hidden1, name="hidden1")
    hidden2 = my_dense_layer(hidden1, n_hidden2, name="hidden2")
    logits = my_dense_layer(hidden2, n_outputs, activation=None,
                            name="outputs")
    
with tf.name_scope("loss"):                                     
    xentropy = tf.nn.sparse_softmax_cross_entropy_with_logits(  
        labels=y, logits=logits) 
    # 經驗風險損失
    base_loss = tf.reduce_mean(xentropy, name="avg_xentropy")   
    # L2正則化損失
    reg_losses = tf.get_collection(tf.GraphKeys.REGULARIZATION_LOSSES)
    # 必須將正規化損失添加到基本損失中,構成結構風險損失。不過沒有明白為啥把base_loss放在列表里。
    loss = tf.add_n([base_loss] + reg_losses, name="loss")

learning_rate = 0.01    
with tf.name_scope("train"):
    optimizer = tf.train.MomentumOptimizer(learning_rate, momentum=0.9)
    training_op = optimizer.minimize(loss)    

with tf.name_scope("eval"):
    correct = tf.nn.in_top_k(logits, y, 1)
    accuracy = tf.reduce_mean(tf.cast(correct, tf.float32))
    
init = tf.global_variables_initializer()
saver = tf.train.Saver()

n_epochs = 40
batch_size = 50

with tf.Session() as sess:
    init.run()
    start_time = time.time()
    for epoch in range(n_epochs):
        for X_batch, y_batch in shuffle_batch(X_train, y_train, batch_size):
             sess.run(training_op,feed_dict
={X: X_batch, y: y_batch}) if epoch % 5 ==0 or epoch == 39: accuracy_batch = accuracy.eval(feed_dict={X: X_batch, y: y_batch}) accuracy_val = accuracy.eval(feed_dict={X: X_valid, y: y_valid}) print(epoch, "Batch accuracy:", accuracy_batch,"Validation accuracy:", accuracy_val) time_dif = get_time_dif(start_time) print("\nTime usage:", time_dif) save_path = saver.save(sess, "./my_model_final_L2.ckpt") with tf.Session() as sess: saver.restore(sess, "./my_model_final_L2.ckpt") # or better, use save_path X_test_20 = X_test[:20] # 得到softmax之前的輸出 Z = logits.eval(feed_dict={X: X_test_20}) # 得到每一行最大值的索引 y_pred = np.argmax(Z, axis=1) print("Predicted classes:", y_pred) print("Actual calsses: ", y_test[:20]) # 評估在測試集上的正確率 acc_test = accuracy.eval(feed_dict={X: X_test, y: y_test}) print("\nTest_accuracy:", acc_test)

三、早停

早停(Early Stopping) 一種比較好的防止過擬合的方法,意思是在模型訓練的過程中,如果觀察到模型經過很多次迭代后,在驗證集上的性能仍然不再提高甚至下降時,就強行中止訓練,並把之前保存的驗證結果最好的模型作為最終訓練好的模型。

原因是隨着迭代次數的增加,模型在訓練集上的預測誤差一般都是不斷下降的,但是在驗證集上會有不同。驗證誤差一開始也會下降,一定迭代次數后會停止下降,反而開始上升,形成一個偏U型的曲線,這表明該模型從谷底處開始過擬合了。

使用TensorFlow實現此功能的一種方法是記錄迭代的總步數,然后定期(例如,每10步)在驗證集上評估模型,並與之前的最好結果進行對比。

  • 如果它優於之前的最好結果,就把這次的模型驗證精度記為模型的最好驗證結果。

  • 如果模型經過了N步以后(比如2000步),驗證精度一直沒有超過之前的最好記錄,那就中止訓練。

  • 把之前已經保存好的最優模型作為最終的模型,在測試集上進行預測。

好,接下來我們不用任何的加速優化方法和其他的正則化方法,構建一個簡單的全連接神經網絡,來看看怎么用早停來緩解過擬合問題。關鍵步驟是在模型的訓練和保存階段,設定為每10步就在驗證集上評估一次模型,如果2000步以后驗證結果還沒有提升,就中止訓練。下面的代碼已經非常清楚了。

# 定義好訓練輪次和batch-size
n_epochs = 40
batch_size = 50

with tf.Session() as sess:
    init.run()
    start_time = time.time()
    
    # 記錄總迭代步數,一個batch算一步
    # 記錄最好的驗證精度
    # 記錄上一次驗證結果提升時是第幾步。
    # 如果迭代2000步后結果還沒有提升就中止訓練。
    total_batch = 0
    best_acc_val = 0.0
    last_improved = 0
    require_improvement = 2000
    
    flag = False
    for epoch in range(n_epochs):
        for X_batch, y_batch in shuffle_batch(X_train, y_train, batch_size):
            
            sess.run(training_op, feed_dict={X: X_batch, y: y_batch})
            
            # 每次迭代10步就驗證一次
            if total_batch % 10 == 0:
                acc_batch = accuracy.eval(feed_dict={X: X_batch, y: y_batch})
                acc_val = accuracy.eval(feed_dict={X: X_valid, y: y_valid})
                
                # 如果驗證精度提升了,就替換為最好的結果,並保存模型
                if acc_val > best_acc_val:
                    best_acc_val = acc_val
                    last_improved = total_batch
                    save_path = saver.save(sess, "./my_model_stop.ckpt")
                    improved_str = 'improved!'
                else:
                    improved_str = ''
                
                # 記錄訓練時間,並格式化輸出驗證結果,如果提升了,會在后面提示:improved!
                time_dif = get_time_dif(start_time)
                msg = 'Epoch:{0:>4}, Iter: {1:>6}, Acc_Train: {2:>7.2%}, Acc_Val: {3:>7.2%}, Time: {4} {5}'
                print(msg.format(epoch, total_batch, acc_batch, acc_val, time_dif, improved_str))
            
            # 記錄總迭代步數    
            total_batch += 1
            
            # 如果2000步以后還沒提升,就中止訓練。
            if total_batch - last_improved > require_improvement:
                print("No optimization for ", require_improvement," steps, auto-stop in the ",total_batch," step!")
                # 跳出這個輪次的循環
                flag = True
                break
        # 跳出所有訓練輪次的循環
        if flag:
            break        

 完整的代碼如下。

import tensorflow as tf
import numpy as np
import time
from datetime import timedelta

# 記錄訓練花費的時間
def get_time_dif(start_time):
    end_time = time.time()
    time_dif = end_time - start_time
    #timedelta是用於對間隔進行規范化輸出,間隔10秒的輸出為:00:00:10    
    return timedelta(seconds=int(round(time_dif)))


n_inputs = 28*28  
n_hidden1 = 300
n_hidden2 = 100
n_outputs = 10

(X_train, y_train), (X_test, y_test) = tf.keras.datasets.mnist.load_data()

X_train = X_train.astype(np.float32).reshape(-1, 28*28) / 255.0
X_test = X_test.astype(np.float32).reshape(-1, 28*28) / 255.0
y_train = y_train.astype(np.int32)
y_test = y_test.astype(np.int32)
X_valid, X_train = X_train[:5000],X_train[5000:]
y_valid, y_train = y_train[:5000],y_train[5000:]

# 打亂數據,並生成batch
def shuffle_batch(X, y, batch_size):
    # permutation不直接在原來的數組上進行操作,而是返回一個新的打亂順序的數組,並不改變原來的數組。
    rnd_idx = np.random.permutation(len(X))
    n_batches = len(X) // batch_size
    # 把rnd_idx這個一位數組進行切分
    for batch_idx in np.array_split(rnd_idx, n_batches):
        X_batch, y_batch = X[batch_idx], y[batch_idx]
        yield X_batch, y_batch
        
X = tf.placeholder(tf.float32, shape=(None, n_inputs), name="X")
y = tf.placeholder(tf.int32, shape=(None), name="y")

with tf.name_scope("dnn"):
    hidden1 = tf.layers.dense(X, n_hidden1, name="hidden1",
                              activation=tf.nn.relu)
    hidden2 = tf.layers.dense(hidden1, n_hidden2, name="hidden2",
                              activation=tf.nn.relu)
    logits = tf.layers.dense(hidden2, n_outputs, name="outputs")
    y_proba = tf.nn.softmax(logits)
    
# 定義損失函數和計算損失
with tf.name_scope("loss"):

    xentropy = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=y,
                                                              logits=logits)
    loss = tf.reduce_mean(xentropy, name="loss")
    
# 定義優化器
learning_rate = 0.01
with tf.name_scope("train"):
    optimizer = tf.train.GradientDescentOptimizer(learning_rate)
    training_op = optimizer.minimize(loss)
    
# 評估模型,使用准確性作為我們的績效指標
with tf.name_scope("eval"):
    # logists最大值的索引在0-9之間,恰好就是被預測所屬於的類,因此和y進行對比,相等就是True,否則為False
    correct = tf.nn.in_top_k(logits, y, 1)
    accuracy = tf.reduce_mean(tf.cast(correct, tf.float32))
    
init = tf.global_variables_initializer()
saver = tf.train.Saver()

# 定義好訓練輪次和batch-size
n_epochs = 40
batch_size = 50

with tf.Session() as sess:
    init.run()
    start_time = time.time()
    
    # 記錄總迭代步數,一個batch算一步
    # 記錄最好的驗證精度
    # 記錄上一次驗證結果提升時是第幾步。
    # 如果迭代2000步后結果還沒有提升就中止訓練。
    total_batch = 0
    best_acc_val = 0.0
    last_improved = 0
    require_improvement = 2000
    
    flag = False
    for epoch in range(n_epochs):
        for X_batch, y_batch in shuffle_batch(X_train, y_train, batch_size):
            
            sess.run(training_op, feed_dict={X: X_batch, y: y_batch})
            
            # 每次迭代10步就驗證一次
            if total_batch % 10 == 0:
                acc_batch = accuracy.eval(feed_dict={X: X_batch, y: y_batch})
                acc_val = accuracy.eval(feed_dict={X: X_valid, y: y_valid})
                
                # 如果驗證精度提升了,就替換為最好的結果,並保存模型
                if acc_val > best_acc_val:
                    best_acc_val = acc_val
                    last_improved = total_batch
                    save_path = saver.save(sess, "./my_model_stop.ckpt")
                    improved_str = 'improved!'
                else:
                    improved_str = ''
                # 記錄訓練時間,並格式化輸出驗證結果。
                time_dif = get_time_dif(start_time)
                msg = 'Epoch:{0:>4}, Iter: {1:>6}, Acc_Train: {2:>7.2%}, Acc_Val: {3:>7.2%}, Time: {4} {5}'
                print(msg.format(epoch, total_batch, acc_batch, acc_val, time_dif, improved_str))
# 記錄總迭代步數 total_batch += 1 # 如果2000步以后還沒提升,就中止訓練。 if total_batch - last_improved > require_improvement: print("No optimization for ", require_improvement," steps, auto-stop in the ",total_batch," step!") # 跳出這個輪次的循環 flag = True break # 跳出所有訓練輪次的循環 if flag: break with tf.Session() as sess: saver.restore(sess, "./my_model_stop.ckpt") # or better, use save_path X_test_20 = X_test[:20] # 得到softmax之前的輸出 Z = logits.eval(feed_dict={X: X_test_20}) # 得到每一行最大值的索引 y_pred = np.argmax(Z, axis=1) print("Predicted classes:", y_pred) print("Actual calsses: ", y_test[:20]) # 評估在測試集上的正確率 acc_test = accuracy.eval(feed_dict={X: X_test, y: y_test}) print("\nTest_accuracy:", acc_test)

模型最后一次在驗證集上提升的輸出結果如下。驗證結果的最后一次提升發生在第23000步,驗證精度為97.62%,然后在第25000步時中止了訓練。在測試集上的測試精度為97.13%

Epoch:  20, Iter:  22970, Acc_Train:  94.00%, Acc_Val:  97.28%, Time: 0:00:54 
Epoch:  20, Iter:  22980, Acc_Train: 100.00%, Acc_Val:  97.32%, Time: 0:00:54 
Epoch:  20, Iter:  22990, Acc_Train: 100.00%, Acc_Val:  97.30%, Time: 0:00:54 
Epoch:  20, Iter:  23000, Acc_Train: 100.00%, Acc_Val:  97.62%, Time: 0:00:55 improved!
Epoch:  20, Iter:  23010, Acc_Train: 100.00%, Acc_Val:  97.52%, Time: 0:00:55 
Epoch:  20, Iter:  23020, Acc_Train: 100.00%, Acc_Val:  97.52%, Time: 0:00:55 
Epoch:  20, Iter:  23030, Acc_Train:  98.00%, Acc_Val:  97.48%, Time: 0:00:55 

 四、其他

神經網絡模型中還有一些其他的正則化方法,下面只提一下大概的思路,就不再去實現了。

1、數據增強

緩解過擬合問題的另一種思路是擴增樣本量,但是這需要付出較大的成本搜集數據和標注數據。而數據增強是從現有的樣本數據集中人為地生成一些數據,比如對圖片進行旋轉、縮放、裁剪等變換,一般用在圖像處理領域。不過有意思的是,自然語言處理領域也有一些數據增強的方法,今年年初有篇論文《EDA: Easy Data Augmentation Techniques for Boosting Performance on Text Classification Tasks》介紹了四種NLP數據增強方法,用在文本分類任務中。這四種方法分別是

(1)同義詞替換:不考慮停用詞,從句子中隨機抽取n個詞,然后從同義詞詞典中隨機抽取同義詞,並進行替換。

(2)隨機插入:隨機抽取一個詞,然后在該詞的同義詞集合中隨機選擇一個,插入原句子中的隨機位置。

(3)隨機交換:在一個句子中隨機選擇兩個詞,交換位置。

(4)隨機刪除:以概率p隨機刪除句子中的每個詞。

實驗結果發現,運用四種NLP數據增強方法后,僅僅使用50%的訓練數據,就能夠達到原始模型使用100%訓練數據的效果,還是比較強大的。

2、權重衰減

神經網絡中還有一種正則化方法叫權重衰減,用隨機梯度下降算法進行優化時,L2正則化可以看作就是在做權重衰減。

3、最大范數正則化

這個方法很簡單,就是讓權重的L2范數小於一個數r,直截了當地約束權重的大小,類似於梯度截斷,還可以幫助緩解梯度消失/爆炸問題。

 

 

參考資料:

1、《Hands On Machine Learning with Scikit-Learn and TensorFlow》

2、Jason W. Wei, Kai Zou :《EDA: Easy Data Augmentation Techniques for Boosting Performance on Text Classification Tasks》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM