這一篇博客整理用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》