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