超参数调优:方法、示例与实践
一、获取函数介绍
在优化问题中,有多种获取函数可供使用,这里主要介绍上置信界(Upper Confidence Bound,UCB)获取函数的两种变体。
1.1 第一种 UCB 获取函数
其表达式为:
[ a_{UCB}(x) = \widetilde{\mathbb{E}}[f(x)] + \eta\sigma(x) ]
其中,(\widetilde{\mathbb{E}}[f(x)]) 是代理函数在问题的 (x) 范围内的“期望”值,即给定 (x) 范围内函数的平均值;(\sigma(x)) 是代理函数在点 (x) 处的方差;(\eta > 0) 是一个权衡参数。该获取函数主要选择方差最大的点,使得近似效果越来越好。
1.2 第二种 UCB 获取函数
表达式为:
[ \widetilde{a}_{UCB}(x) = \widetilde{f}(x) + \eta\sigma(x) ]
这种获取函数会在选择代理函数最大值附近的点和方差最大的点之间进行权衡。第二种方法更适合快速找到 (f) 的最大值的良好近似,而第一种方法倾向于在整个 (x) 范围内对 (f) 进行良好的近似。
二、示例:复杂三角函数的优化
2.1 目标与准备
考虑一个复杂的三角函数,(x) 范围为 ([0, 40]),目标是找到其最大值并近似该函数。为了方便编码,定义两个函数:一个用于评估代理函数,另一个用于评估新的点。
2.2 代码实现
import numpy as np
# 假设 K 函数已定义
def K(x, param1, param2):
# 这里需要根据实际情况实现 K 函数
return np.exp(-(x**2)/(2*param2))
# 评估代理函数
def get_surrogate(randompoints):
ybayes_ = []
sigmabayes_ = []
for x in xsampling:
f1 = f(randompoints)
sigm_ = np.std(f1)**2
f_ = (f1 - np.average(f1))
k = K(x - randompoints, 2.0, sigm_)
C = np.zeros([randompoints.shape[0], randompoints.shape[0]])
Ctilde = np.zeros([randompoints.shape[0]+1, randompoints.shape[0]+1])
for i1, x1_ in np.ndenumerate(randompoints):
for i2, x2_ in np.ndenumerate(randompoints):
C[i1, i2] = K(x1_ - x2_, 2.0, sigm_)
Ctilde[0, 0] = K(0, 2.0)
Ctilde[0, 1:randompoints.shape[0]+1] = k.T
Ctilde[1:, 1:] = C
Ctilde[1:randompoints.shape[0]+1, 0] = k
mu = np.dot(np.dot(np.transpose(k), np.linalg.inv(C)), f_)
sigma2 = K(0, 2.0, sigm_) - np.dot(np.dot(np.transpose(k), np.linalg.inv(C)), k)
ybayes_.append(mu)
sigmabayes_.append(np.abs(sigma2))
ybayes = np.asarray(ybayes_) + np.average(f1)
sigmabayes = np.asarray(sigmabayes_)
return ybayes, sigmabayes
# 评估新的点
def get_new_point(ybayes, sigmabayes, eta):
idxmax = np.argmax(np.average(ybayes) + eta*np.sqrt(sigmabayes))
newpoint = xsampling[idxmax]
return newpoint
# 初始化参数
xmax = 40.0
numpoints = 6
xsampling = np.arange(0, xmax, 0.2)
eta = 1.0
np.random.seed(8)
randompoints1 = np.random.random([numpoints]) * xmax
# 获取代理函数
ybayes1, sigmabayes1 = get_surrogate(randompoints1)
# 获取新的点
newpoint = get_new_point(ybayes1, sigmabayes1, eta)
randompoints2 = np.append(randompoints1, newpoint)
ybayes2, sigmabayes2 = get_surrogate(randompoints2)
2.3 结果分析
初始时,由于选择的点较少,代理函数的近似效果不太好,方差较大。随着不断添加新的点,代理函数对原函数的近似效果越来越好。通过比较两种 UCB 获取函数的表现,可以发现它们在不同方面具有优势,应根据具体的近似策略选择合适的获取函数。
三、对数尺度采样
3.1 问题提出
在网格搜索中,当尝试一个参数的大范围可能值时,可能会出现采样点分布不合理的情况。例如,在寻找模型学习率的最佳值时,测试值从 (10^{-4}) 到 (1),但已知最佳值可能在 (10^{-3}) 到 (10^{-4}) 之间。如果采样 (1000) 个点,会得到如下分布:
| 范围 | 点数 |
| ---- | ---- |
| (10^{-4}) 到 (10^{-3}) | 0 |
| (10^{-3}) 到 (10^{-2}) | 8 |
| (10^{-1}) 到 (10^{-2}) | 89 |
| (1) 到 (10^{-1}) | 899 |
可以看到,在不太感兴趣的范围内有很多点,而在需要的范围内没有点。
3.2 解决方案
可以采用对数尺度采样,确保在不同的 (10) 的幂次方范围内采样相同数量的点。具体代码如下:
r = - np.arange(0, 1, 0.001) * 4.0
points2 = 10**r
这样可以得到一个均匀分布的采样点数组,保证在所需范围内有足够的点进行优化。
四、Zalando 数据集的超参数调优
4.1 数据准备
使用 Zalando 数据集,首先加载必要的库:
import pandas as pd
import numpy as np
import tensorflow as tf
%matplotlib inline
import matplotlib
import matplotlib.pyplot as plt
from random import *
然后加载数据并进行预处理:
# 加载数据
data_train = pd.read_csv('fashion-mnist_train.csv', header = 0)
data_dev = pd.read_csv('fashion-mnist_test.csv', header = 0)
# 分离标签和特征
labels = data_train['label'].values.reshape(1, 60000)
labels_ = np.zeros((60000, 10))
labels_[np.arange(60000), labels] = 1
labels_ = labels_.transpose()
train = data_train.drop('label', axis=1).transpose()
# 对验证集做同样的处理
labels_dev = data_dev['label'].values.reshape(1, 10000)
labels_dev_ = np.zeros((10000, 10))
labels_dev_[np.arange(10000), labels_dev] = 1
labels_dev_ = labels_dev_.transpose()
dev = data_dev.drop('label', axis=1).transpose()
# 归一化特征
train = np.array(train / 255.0)
dev = np.array(dev / 255.0)
labels_ = np.array(labels_)
labels_dev_ = np.array(labels_dev_)
4.2 模型构建与训练
考虑一个单层网络,以准确率作为评估指标,超参数为隐藏层的神经元数量。定义构建模型和训练模型的函数:
def build_model(number_neurons):
n_dim = 784
tf.reset_default_graph()
# 各层神经元数量
n1 = number_neurons # 隐藏层神经元数量
n2 = 10 # 输出层神经元数量
cost_history = np.empty(shape=[1], dtype = float)
learning_rate = tf.placeholder(tf.float32, shape=())
X = tf.placeholder(tf.float32, [n_dim, None])
Y = tf.placeholder(tf.float32, [10, None])
W1 = tf.Variable(tf.truncated_normal([n1, n_dim], stddev=.1))
b1 = tf.Variable(tf.constant(0.1, shape = [n1,1]) )
W2 = tf.Variable(tf.truncated_normal([n2, n1], stddev=.1))
b2 = tf.Variable(tf.constant(0.1, shape = [n2,1]))
# 构建网络
Z1 = tf.nn.relu(tf.matmul(W1, X) + b1)
Z2 = tf.matmul(W2, Z1) + b2
y_ = tf.nn.softmax(Z2, 0)
cost = - tf.reduce_mean(Y * tf.log(y_) + (1 - Y) * tf.log(1 - y_))
optimizer = tf.train.GradientDescentOptimizer(learning_rate).minimize(cost)
init = tf.global_variables_initializer()
return optimizer, cost, y_, X, Y, learning_rate
def model(minibatch_size, training_epochs, features, classes, logging_step = 100, learning_r = 0.001, number_neurons = 15):
opt, c, y_, X, Y, learning_rate = build_model(number_neurons)
sess = tf.Session()
sess.run(tf.global_variables_initializer())
cost_history = []
for epoch in range(training_epochs + 1):
for i in range(0, features.shape[1], minibatch_size):
X_train_mini = features[:, i:i + minibatch_size]
y_train_mini = classes[:, i:i + minibatch_size]
sess.run(opt, feed_dict = {X: X_train_mini, Y: y_train_mini, learning_rate: learning_r})
cost_ = sess.run(c, feed_dict={X: features, Y: classes, learning_rate: learning_r})
cost_history = np.append(cost_history, cost_)
if (epoch % logging_step == 0):
print("Reached epoch", epoch, "cost J =", cost_)
correct_predictions = tf.equal(tf.argmax(y_, 0), tf.argmax(Y, 0))
accuracy = tf.reduce_mean(tf.cast(correct_predictions, "float"))
accuracy_train = accuracy.eval({X: train, Y: labels_, learning_rate: learning_r}, session = sess)
accuracy_dev = accuracy.eval({X: dev, Y: labels_dev_, learning_rate: learning_r}, session = sess)
return accuracy_train, accuracy_dev, sess, cost_history
4.3 超参数调优
4.3.1 单层网络网格搜索
选择一些不同的神经元数量进行网格搜索:
nn = [1, 5, 10, 15, 25, 30, 50, 150, 300, 1000, 3000]
for nn_ in nn:
acc_train, acc_test, sess, cost_history = model(minibatch_size = 50,
training_epochs = 50,
features = train,
classes = labels_,
logging_step = 50,
learning_r = 0.001,
number_neurons = nn_)
print('Number of neurons:', nn_, 'Acc. Train:', acc_train, 'Acc. Test', acc_test)
结果表明,更多的神经元通常会带来更好的准确率,且没有出现过拟合的迹象。
4.3.2 四层网络随机搜索
为了提高训练速度,考虑一个四层网络,使用随机搜索来调整学习率、小批量大小、每层神经元数量和训练轮数。
neurons_ = np.random.randint(low=35, high=60.0, size=(10))
r = -np.random.random([10]) * 3.0 - 1
learning_ = 10**r
mb_size_ = np.random.randint(low=20, high=80, size = 10)
epochs_ = np.random.randint(low = 40, high = 100, size = (10))
for i in range(len(neurons_)):
acc_train, acc_test, sess, cost_history = model_layers(minibatch_size = mb_size_[i],
training_epochs = epochs_[i],
features = train,
classes = labels_,
logging_step = 50,
learning_r = learning_[i],
number_neurons = neurons_[i], debug = False)
print('Epochs:', epochs_[i], 'Number of neurons:', neurons_[i], 'learning rate:', learning_[i], 'mb size', mb_size_[i],
'Acc. Train:', acc_train, 'Acc. Test', acc_test)
通过随机搜索,找到了一组参数,使得在验证集上的准确率达到了 (0.86),且训练时间仅为 (2.5) 分钟,比单层 (3000) 神经元的模型快了 (14) 倍,准确率提高了 (6\%)。
综上所述,超参数调优是提高模型性能的重要手段,不同的获取函数和采样方法在不同的场景下具有不同的优势,应根据具体情况进行选择和应用。
五、超参数调优方法总结与对比
5.1 不同获取函数的对比
| 获取函数 | 特点 | 适用场景 |
|---|---|---|
| (a_{UCB}(x)) | 主要选择方差最大的点,倾向于在整个 (x) 范围内对函数进行良好近似 | 需要对整个函数范围进行全面近似时 |
| (\widetilde{a}_{UCB}(x)) | 在选择代理函数最大值附近的点和方差最大的点之间进行权衡,适合快速找到函数最大值的良好近似 | 更关注快速找到函数最大值时 |
5.2 网格搜索与随机搜索对比
| 搜索方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 网格搜索 | 能全面搜索参数空间,确保找到全局最优解(在参数空间较小时) | 计算量大,尤其是参数空间较大时;可能在不感兴趣的区域浪费大量计算资源 | 参数空间较小,且希望全面搜索时 |
| 随机搜索 | 计算效率高,能在较少的评估次数内找到较好的参数组合;可以通过对数尺度采样等方法优化采样点分布 | 不能保证找到全局最优解 | 需要快速找到较好的参数组合,且参数空间较大时 |
5.3 调优流程总结
以下是一个超参数调优的基本流程:
1.
定义目标
:明确要优化的目标,如函数的最大值、模型的准确率等。
2.
选择获取函数
:根据目标和具体场景选择合适的获取函数,如 (a_{UCB}(x)) 或 (\widetilde{a}_{UCB}(x))。
3.
选择搜索方法
:根据参数空间的大小和计算资源选择网格搜索或随机搜索。
4.
准备数据
:对数据进行加载、预处理和划分,确保数据的质量和可用性。
5.
构建模型
:根据问题的特点构建合适的模型,并定义模型的结构和参数。
6.
调优参数
:使用选择的搜索方法对超参数进行调优,记录不同参数组合下的评估结果。
7.
评估模型
:使用验证集或交叉验证等方法评估模型的性能,选择最优的参数组合。
8.
验证结果
:使用测试集对最终的模型进行验证,确保模型的泛化能力。
graph LR
A[定义目标] --> B[选择获取函数]
B --> C[选择搜索方法]
C --> D[准备数据]
D --> E[构建模型]
E --> F[调优参数]
F --> G[评估模型]
G --> H[验证结果]
六、注意事项与建议
6.1 对数尺度采样的应用
在处理参数范围较大且已知最优值可能在某个特定范围时,应优先考虑使用对数尺度采样。这样可以确保在感兴趣的区域有足够的采样点,提高调优的效率和准确性。
6.2 计算资源的管理
在进行超参数调优时,尤其是使用网格搜索时,要注意计算资源的消耗。可以通过减少参数的取值范围、使用随机搜索等方法来降低计算成本。同时,合理安排训练时间和硬件资源,避免长时间的等待和资源浪费。
6.3 模型复杂度与调优
随着模型复杂度的增加,超参数的数量也会增多,调优的难度和计算量会显著增加。在选择模型时,要根据问题的实际需求和计算资源合理选择模型的复杂度。对于复杂模型,可以采用分阶段调优的方法,先固定部分参数,调优其他参数,逐步优化模型性能。
6.4 多次实验与结果分析
超参数调优的结果可能会受到随机因素的影响,因此建议进行多次实验,并对实验结果进行统计分析。通过多次实验可以更准确地评估不同参数组合的性能,找到更稳定和可靠的最优参数。
七、结语
超参数调优是机器学习和深度学习中不可或缺的环节,它直接影响着模型的性能和泛化能力。通过合理选择获取函数、搜索方法和采样策略,可以在有限的计算资源下快速找到最优的超参数组合,提高模型的准确率和效率。
在实际应用中,要根据具体问题的特点和需求,灵活运用各种调优方法,并不断总结经验,以达到最佳的调优效果。同时,随着技术的不断发展,新的调优方法和工具也在不断涌现,我们需要持续学习和探索,以适应不断变化的需求。
希望本文介绍的方法和示例能为你在超参数调优方面提供一些帮助和启示,让你在模型优化的道路上取得更好的成果。
超级会员免费看
473

被折叠的 条评论
为什么被折叠?



