0. 前言

我们已经使用经典 Tansformer 模型解决了许多任务,但我们可以通过利用特定的技术来进一步提高模型性能。有多种方法可以提升 Transformer 模型的性能,在节中,我们将介绍如何通过超参数优化技术将模型性能提升到超越普通训练流程的水平。超参数优化 (Hyperparameter Optimization, HPO) 是一种在深度学习的各个领域中广泛使用的技术,旨在实现最佳性能,在训练深度学习模型时,超参数无法在训练过程中学习,超参数优化用于找到这些参数的最佳值。

1. 超参数

深度学习中的超参数 (hyperparameters) 是在模型训练前需要手动设置的参数,影响模型学习过程但不通过梯度下降等算法自动更新的配置参数;它们决定了模型的容量、学习速度以及泛化能力,而与之对应的参数 (parameters) 则是在训练过程中由模型学习得到的权重和偏置。

1.1 超参数分类

模型超参数 (model hyperparameters):定义模型结构,如层数、每层神经元数、激活函数类型等。
算法超参数 (algorithm hyperparameters):影响训练过程,如学习率、优化器内部动量系数、批大小、正则化强度等。

1.2 常见超参数

类别 超参数 常见取值范围 作用
训练控制 学习率 (learning rate) 1e-5 – 1e-1 决定参数更新步长,影响收敛速度与稳定性
批大小 (batch size) 16 – 512 影响梯度估计方差和显存需求
训练轮数 (epochs) 10 – 1000 决定完整遍历数据集的次数
结构配置 网络层数 (layers) 1 – 100+ 决定模型深度与表达能力
隐藏单元数 (hidden units) 64 – 4096+ 决定每层神经元数量
正则化 Dropout 率 0.0 – 0.5 随机丢弃神经元,防止过拟合
权重衰减 (weight decay) 1e-6 – 1e-2 L2 正则化强度
优化器相关 动量 (momentum) 0.0 – 0.99 加速收敛、减少振荡
Adam β₁/β₂、ε (0.9, 0.999), 1e-8 控制一阶、二阶矩估计与数值稳定性

Transformer 性能同样依赖于超参数的精心设置,除了以上通用超参数外,通常还包含以下超参数:

  • 模型维度 (d_model):通常取值 2565121024,决定各层特征表示维度,值越大表示能力越强但计算量也越大
  • 注意力头数 (num_heads):常见为 81216,用于并行计算多种注意力表示
  • 编码器/解码器层数 (num_layers):通常取值 61224,层数越多模型越深但易过拟合
  • 前馈网络内部维度 (d_ff):典型为 20484096,加深模型非线性,提升表达能力
  • 最大序列长度 (max_position_embeddings):决定位置编码支持的最大长度,一般取 5121024
  • 学习率调度 (learning rate schedule):常见“先增后降”策略,或使用余弦退火等

在模型训练过程中,我们多数情况下直接使用了默认超参数。接下来,我们将探讨使用更高效的超参数选择策略。

2. 超参数优化

超参数是深度学习模型在训练过程中无法自动学习的参数,因此我们需要通过一定的方式来指定它们。常用的方法是遵循最佳实践,例如,微调 BERT 模型的 epoch 值大约是 3。手动调整这些超参数,需要同时监控模型的表现,特别是监控验证损失。
此外,还可以使用算法系统地确定最佳的参数集。实现这一目标的一种方法是通过在预定义的超参数集合中进行搜索,探索所有可能性,这种方法称为网格搜索 (Grid Search)。然而,类似网格搜索的方法在训练深度学习模型时并不实用,且可能非常繁琐,因为训练 Transformer 模型是一个耗时的过程。另一种选择是使用随机搜索 (Random Search),它通过特定的随机化实现类似的结果,同时减少了网格搜索的复杂性,这种方法随机采样搜索空间,直到满足停止标准为止。
有多种不同的超参数优化 (Hyperparameter Optimization, HPO) 算法,如网格搜索、贝叶斯优化和粒子群优化等。其中一种算法是树结构 Parzen 估计器 (Tree-structured Parzen Estimator, TPE) 方法,它是一种贝叶斯优化算法,使用概率模型来评估不同超参数的表现。具体来说,它基于历史测量值依次构建模型,然后选择新的超参数。

3. 通过超参数优化提升 Transformer 模型性能

在本节,我们将使用 Optuna 框架实现超参数优化 (Hyperparameter Optimization, HPO)。Optuna 是一个专为 HPO 设计的框架,包含了各种优化方法。我们将使用树结构 Parzen 估计器 (Tree-structured Parzen Estimator, TPE) 方法,它是 Optuna 框架的默认选项。

(1) 首先使用 pip 命令安装所需库:

pip install datasets transformers optuna

(2) 加载 IMDB 数据集,选择 4000 个样本作为训练集,2000 个样本作为测试集,2000 个样本作为验证集:

from datasets import load_dataset
imdb_train = load_dataset("imdb", split="train[:2000]+train[-2000:]")
imdb_test = load_dataset("imdb", split="test[:500]+test[-500:]")
imdb_val = load_dataset("imdb", split="test[500:1000]+test[-1000:-500]")
imdb_train.shape, imdb_test.shape, imdb_val.shape

(3) 定义所需函数 compute_metrics() 用于评估模型,函数 tokenize_it() 用于分词:

def compute_metrics(pred):
    labels = pred.label_ids
    preds = pred.predictions.argmax(-1)
    precision, recall, f1, _ = precision_recall_fscore_support(labels,preds, average='macro')
    acc = accuracy_score(labels, preds)
    return {
    'Accuracy': acc,
    'F1': f1,
    'Precision': precision,
    'Recall': recall
    }

def tokenize_it(e):
    return tokenizer(e["text"], padding=True, truncation=True)

(4) 由于我们已经优化了 bert-base-uncased 模型,将其用于对数据集进行分词:

from transformers import BertTokenizerFast, BertForSequenceClassification
from sklearn.metrics import (accuracy_score, precision_recall_fscore_support)
import pandas as pd

model_path = "bert-base-uncased"
tokenizer = BertTokenizerFast.from_pretrained(model_path)

enc_train = imdb_train.map(tokenize_it, batched=True, batch_size=1000)
enc_test = imdb_test.map(tokenize_it, batched=True, batch_size=1000)
enc_val = imdb_val.map(tokenize_it, batched=True, batch_size=1000)

我们可以优化多个超参数。对 Transformer 模型来说,最重要的超参数包括:

  • 学习率
  • 训练 epoch
  • 批大小
  • 优化算法
  • 调度器类型 (Scheduler type)

为了简单起见,我们仅以学习率和批大小为例,应用 HPO。我们将使用 trial 对象来创建超参数采样,可以利用以下 Optuna 功能:

  • 类别型:optuna.trial.Trial.suggest_categorical()
  • 整数型:optuna.trial.Trial.suggest_int()
  • 浮点型:optuna.trial.Trial.suggest_float()

(5) 我们将在对数域内搜索学习率 (learning_rate),范围为 [1e-6 – 1e-4],这意味着可能的值是 1e-61e-51e-4。如果 log 值为 false (默认值),即在线性域中搜索,则需要指定步长进行离散化。对于批大小 (batch_size),提供的是一个列表,而不是一个范围:

def hp_space(trial):
    hp = {
        "learning_rate": trial.suggest_float("learning_rate", 1e-6, 1e-4, log=True),
        "per_device_train_batch_size": trial.suggest_categorical(
            "per_device_train_batch_size", [8, 16]
        ),
    }
    return hp

返回的对象 hp 是一个字典类型,用于表示搜索空间,Trainer 对象将使用它来进行优化。

(6)bert-base-uncased 模型进行微调:

from transformers import AutoModelForSequenceClassification

model_path = "bert-base-uncased"

def model_init():
    return AutoModelForSequenceClassification.from_pretrained(
        model_path,
        num_labels=2,
)
from transformers import DataCollatorForLanguageModeling, TrainingArguments, Trainer

model_path= "bert-base-uncased"

model = BertForSequenceClassification.from_pretrained(
    model_path, id2label={0: "NEG", 1: "POS"}, label2id={"NEG": 0, "POS": 1}
)

training_args = TrainingArguments(output_dir='./model_file',  
                                  do_train=True, 
                                  do_eval=True, 
                                  num_train_epochs=3, 
                                  per_device_train_batch_size=8,
                                  per_device_eval_batch_size=8,
                                  fp16=True,
                                  load_best_model_at_end=True,
                                  save_strategy='epoch',
                                  eval_strategy='epoch')

trainer = Trainer(
    model_init=model_init,
    args=training_args,
    train_dataset=enc_train,
    eval_dataset=enc_val,
    compute_metrics= compute_metrics)

(7) 运行 trainer,调用 trainer.hyperparameter_search()

best_run= trainer.hyperparameter_search(
        direction="maximize",
        hp_space=hp_space)

通过 direction 参数,我们的目标是最大化 compute_metric() 函数。在运行代码时,会看到微调过程被重复多次。最终,Optuna 会给出优化后的超参数。添加更多待优化的超参数并增加搜索空间时,整个过程可能会花费更多时间。

(78 通过检查 best_run 来了解 Optuna 优化了哪些内容:

best_run

输出结果如下所示:

BestRun(run_id='11', objective=3.6560251216738364, hyperparameters={'learning_rate': 1.2797682586830825e-05, 'per_device_train_batch_size': 8}, run_summary=None)

优化后的学习率和批大小分别为 1.2797682586830825e-058。而默认的学习率和批大小分别为 5e-58。我们从任务性能的角度进行比较,我们使用默认参数和优化参数训练模型。

不使用 HPO,默认学习率为 5e-05,性能如下所示:

输出结果
使用 HPO,学习率为 5.10e-05,性能如下所示:

输出结果
当我们查看测试数据的结果时,进行 HPO 的性能略优于使用默认参数的训练,但差异非常小。所有的指标都有略微提高,约为 0.2,这个差异非常微小,需要进行统计检验。例如,可以使用 McNemar 检验t 检验5 × 2 交叉验证来比较两个机器学习模型。但为了快速实验,本节的 HPO 非常简单,如果我们优化更多超参数,可能会在统计显著性方面获得更好的结果。

小结

本文首先介绍了的超参数与训练中自动学习的参数之间的区别,并列举了常见的模型结构超参数和训练控制超参数。接着概述了超参数调优的三种策略:网格搜索、随机搜索和基于概率模型的贝叶斯优化。最后,以 Optuna 框架默认的 TPE 算法为例,演示了如何定义学习率和批大小的搜索空间、配置 Trainer 并调用 hyperparameter_search 执行超参数优化。

系列链接

Transformer实战(1)——词嵌入技术详解
Transformer实战(2)——循环神经网络详解
Transformer实战(3)——从词袋模型到Transformer:NLP技术演进
Transformer实战(4)——从零开始构建Transformer
Transformer实战(5)——Hugging Face环境配置与应用详解
Transformer实战(6)——Transformer模型性能评估
Transformer实战(7)——datasets库核心功能解析
Transformer实战(8)——BERT模型详解与实现
Transformer实战(9)——Transformer分词算法详解
Transformer实战(10)——生成式语言模型 (Generative Language Model, GLM)
Transformer实战(11)——从零开始构建GPT模型
Transformer实战(12)——基于Transformer的文本到文本模型
Transformer实战(13)——从零开始训练GPT-2语言模型
Transformer实战(14)——微调Transformer语言模型用于文本分类
Transformer实战(15)——使用PyTorch微调Transformer语言模型
Transformer实战(16)——微调Transformer语言模型用于多类别文本分类
Transformer实战(17)——微调Transformer语言模型进行多标签文本分类
Transformer实战(18)——微调Transformer语言模型进行回归分析
Transformer实战(19)——微调Transformer语言模型进行词元分类
Transformer实战(20)——微调Transformer语言模型进行问答任务
Transformer实战(21)——文本表示(Text Representation)
Transformer实战(22)——使用FLAIR进行语义相似性评估
Transformer实战(23)——使用SBERT进行文本聚类与语义搜索
Transformer实战(24)——通过数据增强提升Transformer模型性能

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐