开源 AI 食谱文档

使用 Optuna 和 Transformers 进行超参数优化

Hugging Face's logo
加入 Hugging Face 社区

并获得增强的文档体验

开始使用

Open In Colab

使用 Optuna 和 Transformers 进行超参数优化

作者:Parag Ekbote

问题:

找到最佳超参数,以在 IMDB 数据集的一个子集上对轻量级 BERT 模型进行文本分类微调。

概述:

本指南演示了如何使用自动化搜索技术系统地优化基于 Transformer 的文本分类模型的超参数。您将学习如何使用 Optuna 实现 HPO,以找到 BERT 在情感分析任务上进行微调的最佳学习率和权重衰减值。

何时使用本指南:

  • 您需要对预训练语言模型进行分类任务的微调。

  • 您的模型性能趋于平稳,需要参数优化。

  • 您希望实施系统化、可重现的超参数优化。

注意事项

!pip install -q datasets evaluate transformers optuna wandb scikit-learn nbformat matplotlib

准备数据集和设置模型

在训练和评估情感分析模型之前,您需要准备数据集。本节确保您的数据结构化,并且您的模型已准备好从头开始学习或在 BERT 的情况下进行微调。

1. 加载 IMDB 数据集

首先选择一个专注于情感分类的数据集。IMDB 是一个著名的基准数据集,其中包含被标记为正面或负面的电影评论。

2. 选择输入和输出列

只关注必需项

  • text 列作为输入(评论内容)
  • label 列作为目标(0 表示负面,1 表示正面情感)

3. 定义训练/验证拆分

通过选择以下内容来选择一致的采样策略

  • 2000 个示例用于训练
  • 1000 个示例用于验证
    打乱数据时使用固定的随机种子,以确保跨会话的可重现性。

4. 对数据集进行分词

应用与您计划使用的模型兼容的分词器。分词将原始文本转换为数字格式,以便模型能够有效吸收。使用批处理使此步骤高效。

5. 加载评估指标

选择“准确率”作为主要评估指标——它简单有效,适用于此处的二分类任务。它将在以后帮助衡量您的模型在区分正面和负面情感方面的学习效果。

6. 初始化预训练 BERT 模型

选择一个针对序列分类任务预训练的 BERT 模型。将输出类别数设置为 2(正面和负面),以与您的情感标签对齐。该模型将作为整个训练过程中的学习器。

from datasets import load_dataset
import evaluate

from transformers import AutoModelForSequenceClassification
from transformers import AutoTokenizer
from transformers import set_seed

set_seed(42)


train_dataset = load_dataset("imdb", split="train").shuffle(seed=42).select(range(2500))
valid_dataset = load_dataset("imdb", split="test").shuffle(seed=42).select(range(1000))

model_name = "prajjwal1/bert-tiny"
tokenizer = AutoTokenizer.from_pretrained(model_name)


def tokenize(batch):
    return tokenizer(batch["text"], padding="max_length", truncation=True, max_length=512)


tokenized_train = train_dataset.map(tokenize, batched=True).select_columns(["input_ids", "attention_mask", "label"])
tokenized_valid = valid_dataset.map(tokenize, batched=True).select_columns(["input_ids", "attention_mask", "label"])

metric = evaluate.load("accuracy")


def model_init():
    return AutoModelForSequenceClassification.from_pretrained(model_name, num_labels=2)

使用 Optuna 定义存储

为了确保您的超参数优化实验可跟踪、可重现且易于随时间分析,使用持久化存储后端至关重要。Optuna 通过其 `RDBStorage` 机制提供了强大的解决方案,该机制允许使用 SQLite 数据库在多个会话中保存试验数据。

1. 选择持久化存储格式

选择 SQLite 数据库作为存储介质。它轻量、可移植,非常适合本地实验,同时仍可结构化访问所有试验数据。

2. 启用 Optuna 的 RDBStorage

RDBStorage(关系型数据库存储)是 Optuna 将试验结果以一致且可查询的格式保存的方式。这弥合了短期实验和长期分析之间的鸿沟。

3. 保留跨会话的试验历史

通过设置持久化存储,您可以确保每次超参数试验都被记录下来。您可以暂停并恢复研究,稍后添加更多试验,或者在训练结束后很久仍能分析结果。

4. 促进可重现分析

通过集中存储试验数据,您可以重新访问早期结果、重新生成可视化或比较不同的优化运行。这使得您的工作流程透明、协作且科学严谨。

5. 支持可视化和监控工具

持久化存储试验数据使您能够接入可视化工具——如 Optuna 内置的绘图工具或外部仪表板——以检查性能趋势并迭代地优化您的搜索空间。

import optuna
from optuna.storages import RDBStorage

# Define persistent storage
storage = RDBStorage("sqlite:///optuna_trials.db")

study = optuna.create_study(
    study_name="transformers_optuna_study", direction="maximize", storage=storage, load_if_exists=True
)

初始化训练器并设置可观察性

现在您的超参数搜索空间已经到位,下一步是将所有内容连接起来进行优化和跟踪。此设置不仅确保了以准确性为驱动的调优,还通过 Weights & Biases (W&B) 完全可视化训练过程。

1. 定义度量函数

首先指定如何衡量模型性能。该度量函数在每个验证步骤后评估预测,以计算准确率、F1 分数或损失。它成为指导每个试验学习进度的反馈循环。

2. 构建目标函数

这是超参数优化的核心。它封装了您的训练循环,并根据所选指标(如验证准确率)返回一个标量分数。Optuna 将使用它来比较试验并决定哪些设置能产生最佳结果。

3. 设置 Weights & Biases 以实现可观察性

配置您的环境,将实验指标和超参数配置记录到 W&B。该平台提供仪表板、绘图和实验比较,以跟踪进度并发现问题。

4. 登录以获取日志访问权限

使用您的个人 API 密钥登录 W&B。此步骤将您的训练会话连接到您的在线帐户,以便正确跟踪和存储所有指标和试验详细信息。

5. 定义训练器参数

为您的训练管理器(例如 Hugging Face `Trainer`)准备一个配置。包括以下设置:

  • 评估策略(例如每个 epoch 后)

  • 检查点频率和保存条件

  • 日志间隔

  • 超参数搜索方法和目标

    这确保了训练的鲁棒性、可重复性以及易于恢复或分析。

import wandb
from transformers import Trainer, TrainingArguments


def compute_metrics(eval_pred):
    predictions = eval_pred.predictions.argmax(axis=-1)
    labels = eval_pred.label_ids
    return metric.compute(predictions=predictions, references=labels)


def compute_objective(metrics):
    return metrics["eval_accuracy"]


wandb.init(project="hf-optuna", name="transformers_optuna_study")

training_args = TrainingArguments(
    output_dir="./results",
    eval_strategy="epoch",
    save_strategy="epoch",
    load_best_model_at_end=True,
    logging_strategy="epoch",
    num_train_epochs=3,
    report_to="wandb",  # Logs to W&B
    logging_dir="./logs",
    run_name="transformers_optuna_study",
)


trainer = Trainer(
    model_init=model_init,
    args=training_args,
    train_dataset=tokenized_train,
    eval_dataset=tokenized_valid,
    processing_class=tokenizer,
    compute_metrics=compute_metrics,
)

定义搜索空间并启动试验

在开始模型训练之前,仔细定义模型组成部分和探索策略至关重要。此步骤为使用 Optuna 进行超参数优化奠定了基础,您将系统地探索学习率、权重衰减和批大小等训练参数的组合。

1. 设计搜索空间

首先概述您要优化的超参数。为每个参数选择合理的下限和上限:

  • 学习率——控制优化过程中的步长。
  • 权重衰减——增加正则化以减少过拟合。
  • 批大小——影响内存使用和收敛稳定性。

2. 设置优化方向

决定您的目标是最小化(例如损失)还是最大化(例如准确率、F1 分数)评估指标。这将引导搜索引擎朝着正确的方向前进。

3. 选择 Optuna 作为后端

Optuna 将处理搜索过程——智能地选择、评估和迭代超参数组合。

4. 指定试验次数

定义您希望 Optuna 尝试的单独运行(“试验”)次数。更多试验可以更好地探索空间,但需要更多时间。

5. 定义目标函数

此函数计算每次试验中要优化的指标。它封装了模型如何训练以及在测试每种配置后如何评估性能。

6. 为持久性命名研究

为您的研究指定一个名称,以便稍后可以恢复或引用它。这在跨多个会话或机器运行实验时特别有用。

7. 设置持久化存储

选择我们之前设置的存储后端,以便您可以在以后继续研究,分析结果,甚至在系统重启后也能可视化指标。

>>> def optuna_hp_space(trial):
...     return {
...         "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", [16, 32, 64, 128]),
...         "weight_decay": trial.suggest_float("weight_decay", 0.0, 0.3),
...     }


>>> best_run = trainer.hyperparameter_search(
...     direction="maximize",
...     backend="optuna",
...     hp_space=optuna_hp_space,
...     n_trials=5,
...     compute_objective=compute_objective,
...     study_name="transformers_optuna_study",
...     storage="sqlite:///optuna_trials.db",
...     load_if_exists=True,
... )

>>> print(best_run)
BestRun(run_id='0', objective=0.764, hyperparameters={'learning_rate': 7.23655165533393e-05, 'per_device_train_batch_size': 16, 'weight_decay': 0.013798094328723032}, run_summary=None)

可视化结果

一旦您的 Optuna 研究完成其试验,就该揭开层层谜团并解释所发生的一切了。可视化可以清晰地展示超参数如何影响结果,并揭示可能隐藏在原始数据中的模式。

1. 跟踪优化进度

使用优化历史记录查看目标分数在试验中如何演变。这有助于您了解性能是稳步提高、趋于平稳还是波动。它是您了解搜索过程的速度和轨迹的窗口。

2. 通过中间值检查训练行为

如果您的模型在训练期间报告评估指标(例如每个 epoch),中间值图可让您实时监控每个试验的性能。这对于早期停止决策和评估学习稳定性特别有价值。

3. 通过重要性排名揭示关键超参数

参数重要性图表揭示了哪些超参数真正重要——调整学习率是否起作用,还是批大小是关键?了解这一点可以帮助您简化或优化未来的搜索空间。

>>> import optuna
>>> from optuna.visualization.matplotlib import (
...     plot_optimization_history,
...     plot_intermediate_values,
...     plot_param_importances,
... )
>>> import matplotlib.pyplot as plt

>>> # Load the study from RDB storage
>>> storage = optuna.storages.RDBStorage("sqlite:///optuna_trials.db")

>>> study = optuna.load_study(study_name="transformers_optuna_study", storage=storage)

>>> # Plot optimization history
>>> ax1 = plot_optimization_history(study)
>>> plt.show()
>>> ax1.figure.savefig("optimization_history.png")

>>> # Plot intermediate values (if using pruning and intermediate reports)
>>> ax2 = plot_intermediate_values(study)
>>> plt.show()
>>> ax2.figure.savefig("intermediate_values.png")

>>> # Plot parameter importances
>>> ax3 = plot_param_importances(study)
>>> plt.show()
>>> ax3.figure.savefig("param_importances.png")

执行最终训练

一旦您使用 Optuna 完成超参数优化,就该利用您的最佳发现并执行最后一轮训练了。

1. 获取您的“配方”

访问在调优过程中确定的最佳超参数集。

2. 配置训练参数

将这些超参数值插入到您的训练设置中。这可能包括调整学习率、批大小、时期数、 dropout 率以及其他影响训练行为的模型特定参数。

3. 融入模型设置

将优化后的值应用于初始化和配置您的模型。这确保了您的最终训练运行由通过试错发现的最有效设置指导。

4. 微调您的训练管道

使用最佳参数设置您的优化器、损失函数和数据加载器。从模型学习速度到它一次处理的数据量,一切都应该反映您的优化配置。

5. 运行完整训练

开始使用整个训练数据集(或至少您在 HPO 期间使用的训练/验证拆分)训练您的模型。此过程应反映您在不进行探索性变异的情况下学习数据模式的最佳尝试。

from datasets import load_dataset
from transformers import AutoTokenizer

# Load IMDb dataset
dataset = load_dataset("imdb")

# Load tokenizer
tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")


# Tokenize the text
def tokenize_function(example):
    return tokenizer(example["text"], padding="max_length", truncation=True)


# Apply tokenization
tokenized_dataset = dataset.map(tokenize_function, batched=True)

# Clean up columns
tokenized_dataset = tokenized_dataset.remove_columns(["text"])
tokenized_dataset = tokenized_dataset.rename_column("label", "labels")

# Set PyTorch format
tokenized_dataset.set_format("torch", columns=["input_ids", "attention_mask", "labels"])

# Subset for quick testing (optional)
train_dataset = tokenized_dataset["train"].shuffle(seed=42).select(range(2000))
valid_dataset = tokenized_dataset["test"].shuffle(seed=42).select(range(500))
from transformers import AutoModelForSequenceClassification, TrainingArguments, Trainer

# Define the model
model = AutoModelForSequenceClassification.from_pretrained("bert-base-uncased", num_labels=2)

# Load best hyperparameters (already defined earlier as best_hparams)
training_args = TrainingArguments(
    output_dir="./final_model",
    learning_rate=best_hparams["learning_rate"],
    per_device_train_batch_size=best_hparams["per_device_train_batch_size"],
    weight_decay=best_hparams["weight_decay"],
    eval_strategy="epoch",
    save_strategy="epoch",
    load_best_model_at_end=True,
    logging_strategy="epoch",
    num_train_epochs=3,
    report_to="wandb",
    run_name="final_run_with_best_hparams",
)

# Create Trainer
trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=train_dataset,
    eval_dataset=valid_dataset,
    processing_class=tokenizer,
    compute_metrics=lambda eval_pred: {"accuracy": (eval_pred.predictions.argmax(-1) == eval_pred.label_ids).mean()},
)

# Train
trainer.train()

# Save the model
trainer.save_model("./final_model")

上传到 Hugging Face Hub

您已成功训练出一个强大且优化的模型,现在是时候将其提供给世界了。在 Hugging Face Hub 上共享您的模型不仅使其可重用且可用于推理,而且还为开源社区做出了贡献。

1. 庆祝优化成果

经过严格的调优和最终训练,您的模型现在能够更高效、更稳定地运行。这些改进使其成为情感分析等实际任务的理想选择,例如分类电影评论以微调内容推荐。

2. 在本地保存您的工作

在共享之前,请在本地系统上保存训练好的模型——包括权重、配置、分词器(如果适用)和训练工件。此步骤可确保您的模型设置可重现并已准备好上传。

3. 使用 Hugging Face Hub 进行身份验证

要上传您的模型,您需要登录 Hugging Face Hub。无论是通过终端还是 Notebook 界面,身份验证都将您的环境连接到平台上的个人或组织空间,从而实现推送访问。

4. 上传并共享

将您保存的模型推送到 Hugging Face Hub。这使得模型可以公开访问(或私有),并允许其他人加载、使用和微调它。您还将创建一个模型卡,解释模型的功能、预期用例和性能基准。

📌 重要性:

  • 集中式模型存储鼓励版本控制、可重现性和透明度。
  • 该 Hub 通过与 `transformers` 兼容的 API 简化了下游任务的集成。
  • 共享模型有助于建立您的个人资料并支持机器学习社区内的协作。
from transformers import AutoTokenizer, AutoModelForSequenceClassification

# Load your saved model from the path
model = AutoModelForSequenceClassification.from_pretrained("./final_model")
tokenizer = AutoTokenizer.from_pretrained("./final_model")

# Push to your repository on the hub
model.push_to_hub("AINovice2005/bert-imdb-optuna-hpo")
tokenizer.push_to_hub("AINovice2005/bert-imdb-optuna-hpo")
< > 在 GitHub 上更新