神经网络中,除了寻找最优的权重和偏置等参数,设定合适的超参数也同样重要。比如各层神经元的数量、batch_size的取值、参数更新时的学习率、权值衰减系数或学习的epoch等。超参数寻找过程一般会伴随很多试错,所以用尽可能高效的方法找到超参数非常重要。
下面介绍一种实践性的方法,说实话是有一些实践者经验的感觉。
步骤0
设定超参数的范围。
说明:超参数的范围只要“大致的指定”就可以了。也就是类似于,确定量级即可。
步骤1
从设定的超参数范围中随机采样。
例如:权重衰减系数和学习率随机采样
weight_decay = 10 ** np.random.uniform(-8, -4)
lr = 10 ** np.random.uniform(-6, -2)
步骤2
使用步骤1中采样到的超参数的值进行学习,通过验证数据评估识别精度(但是要将epoch设置的很小)。
Q&A:
1、什么是验证数据?
验证数据是用来调整超参数的数据。根据不同的数据集,有的会事先分成训练数据、验证数据、测试数据三部分,有的只分成训练数据和测试数据两部分,有的不进行分割。例如mnist数据集分为训练数据和测试数据。在这种情况下获得验证数据最简单的方法就是从训练数据中事先分割20%作为验证数据。
# 分离验证数据
validation_rate = 0.20
validation_num = int(x_train.shape[0] * validation_rate)
# 数据可能存在排列,所以要打乱
x_train, t_train = shuffle_dataset(x_train, t_train)
# 验证数据
x_val = x_train[:validation_num]
t_val = t_train[:validation_num]
# 训练数据
x_train = x_train[validation_num:]
t_train = t_train[validation_num:]
2、为什么不用测试数据而用验证数据?
因为如果使用测试数据调整超参数,超参数的值会对测试数据发生过拟合,只拟合测试数据,模型的泛化能力低。
3、epoch为什么要设置的很小?
深度学习往往要花费很多时间,因此,在超参数的寻找过程中,需要尽早放弃那些不符合逻辑的超参数。于是,在超参数的最优化中,减少学习的epoch,缩短一次评估所需的时间是一个不错的办法。
步骤3
重复步骤1和步骤2(100次等),根据它们的识别精度的结果,缩小超参数范围。
反复进行上述操作,不断缩小超参数的范围,在缩小到一定程度时,从该范围选出一个超参数的值。这是其中一种方法。在超参数的最优化中,如果需要更精炼的方法,可以使用贝叶斯最优化。参考论文:Practical Bayesian Optimization of Machine Learning Algorithms.
python实现:
import numpy as np
import matplotlib.pyplot as plt
from dataset.mnist import load_mnist
from common.multi_layer_net import MultiLayerNet
from common.util import shuffle_dataset
from common.trainer import Trainer
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True)
# 为了提高训练数据的速度只取500张图片
x_train = x_train[:500]
t_train = t_train[:500]
# 分离验证数据
validation_rate = 0.20
validation_num = int(x_train.shape[0] * validation_rate)
x_train, t_train = shuffle_dataset(x_train, t_train)
# 验证数据
x_val = x_train[:validation_num]
t_val = t_train[:validation_num]
# 训练数据
x_train = x_train[validation_num:]
t_train = t_train[validation_num:]
def __train(lr, weight_decay, epocs=50):
network = MultiLayerNet(input_size=784, hidden_size_list=[100, 100, 100, 100, 100, 100],
output_size=10, weight_decay_lambda=weight_decay)
trainer = Trainer(network, x_train, t_train, x_val, t_val,
epochs=epocs, mini_batch_size=100,
optimizer='sgd', optimizer_param={'lr': lr}, verbose=False)
trainer.train()
return trainer.test_acc_list, trainer.train_acc_list
# 超大参数随机搜索======================================
epoch = 100
results_val = {}
results_train = {}
for _ in range(epoch):
# 指定搜索到的超参数的范围===============
weight_decay = 10 ** np.random.uniform(-8, -4)
lr = 10 ** np.random.uniform(-6, -2)
# ================================================
# 返回验证数据和训练数据的精度
val_acc_list, train_acc_list = __train(lr, weight_decay)
print("val acc:" + str(val_acc_list[-1]) + " | lr:" + str(lr) + ", weight decay:" + str(weight_decay))
key = "lr:" + str(lr) + ", weight decay:" + str(weight_decay)
results_val[key] = val_acc_list
results_train[key] = train_acc_list
# 图表的绘制========================================================
print("=========== Hyper-Parameter Optimization Result ===========")
graph_draw_num = 20
col_num = 5
row_num = 4
i = 0
for key, val_acc_list in sorted(results_val.items(), key=lambda x:x[1][-1], reverse=True):
print("Best-" + str(i+1) + "(val acc:" + str(val_acc_list[-1]) + ") | " + key)
plt.subplot(row_num, col_num, i+1)
plt.title("Best-" + str(i+1))
plt.ylim(0.0, 1.0)
x = np.arange(len(val_acc_list))
plt.plot(x, val_acc_list)
plt.plot(x, results_train[key], "--")
i += 1
if i >= graph_draw_num:
break
plt.show()
实验结果:
从这个结果可以看出,前七张图片学习都进行的很顺利。因此可以将学习率的范围定在0.001到0.01、权值衰减系数在之间。然后在这个缩小的范围中重复相同的操作。这样就能缩小到合适的超参数的存在范围,然后在某个阶段,选择一个最终的超参数的值。