为超参数选择合适的值是优化深度学习模型的一个重要方面。与在训练过程中学习到的模型参数(如权重 $w_i$ 和偏置 $b$)不同,超参数是在训练开始前指定的配置设定。它们决定了网络的整体结构和训练过程本身。你接触过的超参数示例包括:梯度下降中使用的学习率 ($\alpha$)。优化算法的选择 (SGD, Adam, RMSprop)。网络中隐藏层的数量。每个隐藏层中神经元的数量。激活函数的选择 (ReLU, Tanh, Sigmoid)。L1 或 L2 正则化的强度 ($\lambda$)。Dropout 比率(要舍弃的神经元比例)。小批量梯度下降中使用的批量大小。训练轮数。找到好的超参数组合可以显著影响模型表现。学习率选择不当可能会阻碍收敛,而不合适的网络架构可能难以学习数据中的潜在模式。系统地搜索最佳超参数组合的过程称为超参数调优或超参数优化。手动凭直觉调整超参数既耗时又常不够理想。需要更结构化的方法。在此,我们介绍两种超参数搜索的基本策略:网格搜索和随机搜索。网格搜索网格搜索或许是超参数调优最直接的方法。它通过为每个要调优的超参数定义一个具体的列表或值范围来工作。然后,该算法穷举评估这些值的每个可能组合。假设你想调优两个超参数:学习率和单个隐藏层中的单元数量。你可能会指定以下离散值进行测试:学习率: [0.1, 0.01, 0.001]隐藏单元数量: [32, 64, 128]然后,网格搜索将为每个组合训练并评估一个独立的模型:(学习率=0.1, 隐藏单元=32)(学习率=0.1, 隐藏单元=64)(学习率=0.1, 隐藏单元=128)(学习率=0.01, 隐藏单元=32)(学习率=0.01, 隐藏单元=64)(学习率=0.01, 隐藏单元=128)(学习率=0.001, 隐藏单元=32)(学习率=0.001, 隐藏单元=64)(学习率=0.001, 隐藏单元=128)每个模型配置通常使用独立的验证数据集上的性能指标(如准确率或损失)进行评估。产生最佳验证表现的组合被选为搜索到的最佳超参数组合。digraph G { rankdir=LR; node [shape=point, width=0.1, height=0.1]; edge [style=invis]; subgraph cluster_0 { label = "学习率"; bgcolor="#e9ecef"; style=filled; p1 [pos="1,1!", label=""]; p2 [pos="1,2!", label=""]; p3 [pos="1,3!", label=""]; p1 -> p2 -> p3; l1 [shape=plaintext, label="0.1", pos="0.5,1!"]; l2 [shape=plaintext, label="0.01", pos="0.5,2!"]; l3 [shape=plaintext, label="0.001", pos="0.5,3!"]; } subgraph cluster_1 { label = "隐藏单元"; bgcolor="#e9ecef"; style=filled; p4 [pos="2,0.5!", label=""]; p5 [pos="3,0.5!", label=""]; p6 [pos="4,0.5!", label=""]; p4 -> p5 -> p6; h1 [shape=plaintext, label="32", pos="2,0!"]; h2 [shape=plaintext, label="64", pos="3,0!"]; h3 [shape=plaintext, label="128", pos="4,0!"]; } subgraph cluster_2 { label = "网格搜索组合"; node [shape=circle, style=filled, color="#4263eb", width=0.2, height=0.2, label=""]; bgcolor="#dee2e6"; c11 [pos="2,1!"]; c12 [pos="3,1!"]; c13 [pos="4,1!"]; c21 [pos="2,2!"]; c22 [pos="3,2!"]; c23 [pos="4,2!"]; c31 [pos="2,3!"]; c32 [pos="3,3!"]; c33 [pos="4,3!"]; } }网格搜索在超参数值的交点定义的每个点上评估性能。优点:系统性: 检查所有指定的组合。简单: 易于理解和实现。可并行化: 每个模型训练可以独立运行。缺点:维度灾难: 组合数量随着超参数的数量和每个超参数测试值的数量呈指数增长。如果你有 $N$ 个超参数,并且每个测试 $k$ 个值,你需要训练 $k^N$ 个模型。这很快就会变得计算上不可行。低效: 它可能会浪费大量计算去探究对性能影响很小的超参数值,同时如果网格分辨率太粗,可能对重要的超参数采样不足。它还可能错过位于指定网格点之间的最佳值。网格搜索最实用是在只调优少量(通常是 2 或 3 个)超参数时,或者当你拥有强大的先验知识表明可能存在一个狭窄的最佳值范围时。随机搜索随机搜索提供了一种不同的方法。它不是定义离散的值网格,而是为每个超参数定义一个分布或范围(例如,学习率为 0.0001 到 0.01 之间的均匀分布,或隐藏单元从 [32, 64, 128, 256] 中选择)。然后,该算法从这些分布中随机抽取预定数量的组合并评估它们。例如,与其像网格搜索示例那样测试 9 个特定组合,你可能决定运行随机搜索 9 次迭代。在每次迭代中,它将:随机抽取一个学习率(例如,从 0.001 到 0.1 之间的对数均匀分布中)。随机抽取一个隐藏单元数量(例如,从 [32, 64, 128, 256] 中均匀选择)。使用这个随机选择的组合训练并评估一个模型。在 9 次迭代后,它会选择在测试过的组合中产生最佳验证表现的组合。{ "data": [ { "x": [0.08, 0.015, 0.002, 0.05, 0.008, 0.02, 0.001, 0.09, 0.03], "y": [45, 110, 32, 70, 200, 150, 90, 50, 100], "mode": "markers", "type": "scatter", "marker": {"color": "#be4bdb", "size": 10} } ], "layout": { "xaxis": {"title": "学习率(采样)", "range": [0, 0.11], "dtick": 0.02}, "yaxis": {"title": "隐藏单元(采样)", "range": [0, 270], "dtick": 50}, "title": "随机搜索采样(9 次迭代)", "margin": {"l": 50, "r": 20, "t": 40, "b": 40} } }随机搜索从超参数空间中采样点,在固定预算内可能比网格搜索更有效地发现有希望的区域。Bergstra 和 Bengio 的研究(《超参数优化的随机搜索》,2012)表明,随机搜索通常比网格搜索更高效,特别是当某些超参数远比其他超参数更有影响力时(这在深度学习中很常见)。网格搜索花费相同的精力评估所有组合,包括那些不重要的超参数被改变而重要的超参数保持不变的组合。随机搜索通过独立采样,在有限的评估预算内更有可能找到重要超参数的良好值。优点:更高效: 在相同的计算预算内,通常能找到比网格搜索更好的超参数,特别是在更高维度中。灵活: 轻松处理连续和离散超参数。不需要定义固定的网格点。简单性: 仍然相对容易实现。缺点:不穷举: 不能保证找到指定范围内的绝对最佳组合。随机性: 由于随机采样,结果可能因运行而异。在实际中,随机搜索在调优深度学习模型时通常优于网格搜索,特别是在处理超过几个超参数或调优的计算预算有限时。超参数搜索的实用考量定义合理的范围: 为每个超参数选择合理的范围或分布。例如,学习率通常在对数尺度上采样(例如,$10^{-4}$ 到 $10^{-1}$),因为它们的影响通常是乘法性的。网络大小可以选择为 2 的幂(例如,32, 64, 128, 256)。使用验证集: 始终在独立的验证集上评估超参数组合,而不是在测试集上。测试集应只在最后使用一次,以估计所选模型的最终泛化性能。先粗后细: 你可以进行初步的宽泛随机搜索,以识别超参数空间中有希望的区域,然后在那区域内进行更集中的搜索(可以是随机搜索或网格搜索)。使用库: scikit-learn 等框架(GridSearchCV、RandomizedSearchCV)提供方便的实现。Optuna、Ray Tune 或 KerasTuner 等专门库提供了比简单网格和随机搜索更高级的算法(例如,贝叶斯优化),它们可以更高效,但超出了本介绍的范围。计算预算: 根据时间和资源,决定你可以负担多少组合(网格搜索)或迭代(随机搜索)。即使少量随机搜索试验(例如,10-20 次)也通常可以比默认设置产生显著改进。Here's a Python snippet illustrating the difference in iteration logic:# --- 网格搜索 --- learning_rates = [0.1, 0.01, 0.001] hidden_unit_options = [32, 64, 128] results = {} print("开始网格搜索...") for lr in learning_rates: for hidden_units in hidden_unit_options: config = {'lr': lr, 'hidden': hidden_units} print(f" 测试配置: {config}") # performance = train_and_evaluate(config) # 占位符 # results[tuple(config.items())] = performance print("网格搜索完成。") # --- 随机搜索 --- import random import math num_iterations = 9 # 与网格搜索的组合数量匹配 results_random = {} print("\n开始随机搜索...") for i in range(num_iterations): # Sample learning rate log-uniformly between 1e-3 and 1e-1 log_lr = random.uniform(math.log10(0.001), math.log10(0.1)) lr = 10**log_lr # Sample hidden units uniformly from choices hidden_units = random.choice([32, 64, 128, 256]) config = {'lr': lr, 'hidden': hidden_units} print(f" 第 {i+1}/{num_iterations} 次迭代: 测试配置: {config}") # performance = train_and_evaluate(config) # 占位符 # results_random[tuple(config.items())] = performance print("随机搜索完成。") # 在实际情况中,你会比较 'results' 或 'results_random' # 以找到表现最佳的配置。找到好的超参数通常是一个迭代过程,它将这些结构化搜索方法与从监控训练和评估模型表现中获得的见解结合起来。尽管不是万能药,但系统化的超参数调优是提升深度学习模型性能上限的重要工具。