使用 MLflow 对 Transformers 进行微调以增强模型管理
欢迎来到我们的深入教程,介绍如何使用 MLflow 对 Transformers 模型进行微调并增强模型管理。
您将在此教程中学到的内容
- 理解微调 Transformers 模型的过程。
- 学习如何使用 MLflow 有效地记录和管理训练周期。
- 掌握如何在 MLflow 中单独记录训练好的模型。
- 深入了解如何将训练好的模型用于实际推理任务。
我们的方法将提供对模型微调和管理的全面理解,确保您能够胜任项目中类似的任务。
强调微调
微调预训练模型是机器学习中的常见做法,尤其在自然语言处理领域。它涉及调整预训练模型,使其更适合特定任务。此过程至关重要,因为它利用了模型中已有的知识,显著提高了模型在特定数据集或任务上的性能。
MLflow 在模型生命周期中的作用
在此过程中集成 MLflow 对于以下方面至关重要
- 训练周期日志记录:详细记录训练周期,包括参数、指标和中间结果。
- 模型日志记录和管理:单独记录训练好的模型,追踪其版本,并在训练后管理其生命周期。
- 推理和部署:使用记录好的模型进行推理,确保从训练到部署的轻松过渡。
# Disable tokenizers warnings when constructing pipelines
%env TOKENIZERS_PARALLELISM=false
import warnings
# Disable a few less-than-useful UserWarnings from setuptools and pydantic
warnings.filterwarnings("ignore", category=UserWarning)
env: TOKENIZERS_PARALLELISM=false
准备数据集和环境进行微调
本节中的关键步骤
- 加载数据集:利用
sms_spam
数据集进行垃圾邮件检测。 - 分割数据集:将数据集按 80/20 的比例分割为训练集和测试集。
- 导入必要的库:导入
evaluate
、mlflow
、numpy
等库以及transformers
库中的必要组件。
在深入微调过程之前,设置我们的环境并准备数据集至关重要。此步骤包括加载数据集,将其分割为训练集和测试集,以及初始化 Transformers 库的关键组件。这些准备步骤为高效的微调过程奠定了基础。
此设置确保我们为模型微调奠定了坚实的基础,拥有所有必要的数据和工具。在接下来的 Python 代码中,我们将执行这些步骤,开始我们的模型微调之旅。
import evaluate
import numpy as np
from datasets import load_dataset
from transformers import (
AutoModelForSequenceClassification,
AutoTokenizer,
Trainer,
TrainingArguments,
pipeline,
)
import mlflow
# Load the "sms_spam" dataset.
sms_dataset = load_dataset("sms_spam")
# Split train/test by an 8/2 ratio.
sms_train_test = sms_dataset["train"].train_test_split(test_size=0.2)
train_dataset = sms_train_test["train"]
test_dataset = sms_train_test["test"]
Found cached dataset sms_spam (/Users/benjamin.wilson/.cache/huggingface/datasets/sms_spam/plain_text/1.0.0/53f051d3b5f62d99d61792c91acefe4f1577ad3e4c216fb0ad39e30b9f20019c)
0%| | 0/1 [00:00<?, ?it/s]
分词和数据集准备
在下一个代码块中,我们将对文本数据进行分词,为模型的微调过程做准备。
加载并分割数据集后,下一步是为模型准备文本数据。这包括对文本进行分词,这是自然语言处理中的关键过程,它将文本转换为模型能够理解和使用的格式。
分词过程
- 加载分词器:使用
transformers
库中的AutoTokenizer
加载distilbert-base-uncased
模型的对应分词器。 - 定义分词函数:创建一个函数来分词文本数据,包括填充和截断。
- 对数据集应用分词:处理训练集和测试集,以便模型就绪。
分词是为自然语言处理任务准备文本数据的关键步骤。它确保数据以模型可以处理的格式存在,并通过处理填充和截断等问题,确保数据集的一致性,这对于训练的稳定性和模型性能至关重要。
# Load the tokenizer for "distilbert-base-uncased" model.
tokenizer = AutoTokenizer.from_pretrained("distilbert-base-uncased")
def tokenize_function(examples):
# Pad/truncate each text to 512 tokens. Enforcing the same shape
# could make the training faster.
return tokenizer(
examples["sms"],
padding="max_length",
truncation=True,
max_length=128,
)
seed = 22
# Tokenize the train and test datasets
train_tokenized = train_dataset.map(tokenize_function)
train_tokenized = train_tokenized.remove_columns(["sms"]).shuffle(seed=seed)
test_tokenized = test_dataset.map(tokenize_function)
test_tokenized = test_tokenized.remove_columns(["sms"]).shuffle(seed=seed)
Map: 0%| | 0/4459 [00:00<?, ? examples/s]
Map: 0%| | 0/1115 [00:00<?, ? examples/s]
模型初始化和标签映射
接下来,我们将设置标签映射并初始化模型,用于我们的文本分类任务。
准备好数据后,下一个关键步骤是初始化模型并设置标签映射。这涉及明确定义数据集中标签与模型中对应表示之间的关系。
设置标签映射
- 定义标签映射:创建整数标签和文本表示("ham" 和 "spam")之间的双向映射。
初始化模型
- 模型选择:选择
distilbert-base-uncased
模型,因为它在性能和效率之间取得了平衡。 - 模型配置:使用定义的标签映射为序列分类配置模型。
适当的模型初始化和标签映射是确保模型准确理解和处理当前任务的关键。通过明确定义这些映射并选择合适的预训练模型,我们为有效和高效的微调奠定了基础。
# Set the mapping between int label and its meaning.
id2label = {0: "ham", 1: "spam"}
label2id = {"ham": 0, "spam": 1}
# Acquire the model from the Hugging Face Hub, providing label and id mappings so that both we and the model can 'speak' the same language.
model = AutoModelForSequenceClassification.from_pretrained(
"distilbert-base-uncased",
num_labels=2,
label2id=label2id,
id2label=id2label,
)
Some weights of DistilBertForSequenceClassification were not initialized from the model checkpoint at distilbert-base-uncased and are newly initialized: ['classifier.weight', 'pre_classifier.bias', 'pre_classifier.weight', 'classifier.bias'] You should probably TRAIN this model on a down-stream task to be able to use it for predictions and inference.
设置评估指标
接下来,我们将重点关注定义和计算评估指标,以准确衡量模型的性能。
初始化模型后,下一个关键步骤是定义如何评估其性能。准确的评估是了解模型学习和执行任务情况的关键。
选择和加载指标
- 指标选择:选择 'accuracy' 作为评估指标。
- 加载指标:利用
evaluate
库加载 'accuracy' 指标。
定义指标计算函数
- 指标计算函数:创建一个函数
compute_metrics
,用于在模型评估期间计算准确率。 - 处理预测结果:处理预测结果中的 logits 和标签以计算准确率。
正确设置评估指标使我们能够客观地衡量模型的性能。通过使用标准化指标,我们可以将模型的性能与基准或其他模型进行比较,确保我们的微调过程有效且方向正确。
# Define the target optimization metric
metric = evaluate.load("accuracy")
# Define a function for calculating our defined target optimization metric during training
def compute_metrics(eval_pred):
logits, labels = eval_pred
predictions = np.argmax(logits, axis=-1)
return metric.compute(predictions=predictions, references=labels)
配置训练环境
在此步骤中,我们将配置我们的 Trainer,通过使用 TrainingArguments
API 提供重要的训练配置。
模型和指标准备就绪后,下一个重要步骤是配置训练环境。这包括设置训练参数并初始化 Trainer,Trainer 是协调模型训练过程的组件。
训练参数配置
- 定义输出目录:我们指定
training_output_dir
,在训练期间模型检查点将保存在此处。这有助于管理和存储训练不同阶段的模型状态。 - 指定训练参数:我们创建一个
TrainingArguments
实例来定义各种训练参数,例如输出目录、评估策略、训练和评估的批处理大小、日志记录频率以及训练轮数。这些参数对于控制模型的训练和评估方式至关重要。
初始化 Trainer
- 创建 Trainer 实例:我们使用 Transformers 库中的 Trainer 类,向其提供我们的模型、先前定义的训练参数、用于训练和评估的数据集以及计算指标的函数。
- Trainer 的作用:Trainer 处理模型训练和评估的所有方面,包括执行训练循环、处理数据批处理和调用计算指标函数。它简化了训练过程,使其更加流畅高效。
正确训练配置的重要性
正确设置训练环境对于有效的模型训练至关重要。适当的配置可确保模型在最佳条件下进行训练,从而获得更好的性能和更可靠的结果。
在以下代码块中,我们将配置训练环境并初始化 Trainer,为实际训练过程奠定基础。
# Checkpoints will be output to this `training_output_dir`.
training_output_dir = "/tmp/sms_trainer"
training_args = TrainingArguments(
output_dir=training_output_dir,
evaluation_strategy="epoch",
per_device_train_batch_size=8,
per_device_eval_batch_size=8,
logging_steps=8,
num_train_epochs=3,
)
# Instantiate a `Trainer` instance that will be used to initiate a training run.
trainer = Trainer(
model=model,
args=training_args,
train_dataset=train_tokenized,
eval_dataset=test_tokenized,
compute_metrics=compute_metrics,
)
# If you are running this tutorial in local mode, leave the next line commented out.
# Otherwise, uncomment the following line and set your tracking uri to your local or remote tracking server.
# mlflow.set_tracking_uri("http://127.0.0.1:8080")
集成 MLflow 进行实验追踪
在开始训练过程之前的最后一个准备步骤是集成 MLflow 进行实验追踪。
MLflow 是我们工作流程中的关键工具,它使我们能够记录、监控和比较模型训练的不同运行。
设置 MLflow 实验
- 命名实验:我们使用
mlflow.set_experiment
创建一个新实验或将当前运行分配到现有实验。在本例中,我们将实验命名为 "Spam Classifier Training"。此名称应具有描述性并与当前任务相关,有助于稍后组织和识别实验。 - MLflow 在训练中的作用:通过设置 MLflow 实验,我们可以追踪模型训练的各个方面,例如参数、指标和输出。此追踪对于比较不同模型、调整超参数和维护实验记录非常宝贵。
实验追踪的优势
利用 MLflow 进行实验追踪具有以下几个优势
- 组织性:使您的训练运行井井有条且易于访问。
- 可比性:可以轻松比较不同的训练运行,以了解参数或数据更改的影响。
- 可复现性:通过记录所有必要的详细信息来增强实验的可复现性。
设置好 MLflow 后,我们就可以开始训练过程了,在此过程中跟踪所有重要方面。
在下一个代码片段中,我们将设置 MLflow 实验来追踪垃圾邮件分类模型的训练过程。
# Pick a name that you like and reflects the nature of the runs that you will be recording to the experiment.
mlflow.set_experiment("Spam Classifier Training")
<Experiment: artifact_location='file:///Users/benjamin.wilson/repos/mlflow-fork/mlflow/docs/source/llms/transformers/tutorials/fine-tuning/mlruns/258758267044147956', creation_time=1701291176206, experiment_id='258758267044147956', last_update_time=1701291176206, lifecycle_stage='active', name='Spam Classifier Training', tags={}>
使用 MLflow 开始训练过程
在此步骤中,我们启动微调训练运行,利用原生自动日志记录功能记录训练过程中使用的参数和计算的损失指标。
模型、训练参数和 MLflow 实验都已设置好,我们现在可以开始实际的训练过程了。此步骤涉及启动 MLflow 运行,该运行将封装所有训练活动和指标。
启动 MLflow 运行
- 开始 MLflow 运行:我们使用
mlflow.start_run()
开始新的 MLflow 运行。此函数创建一个新的运行上下文,所有训练操作和日志记录都将在此上下文中发生。 - 训练模型:在 MLflow 运行上下文中,我们调用
trainer.train()
开始训练模型。此函数将运行训练循环,分批处理数据,更新模型参数,并评估模型。
监控训练进度
训练期间,Trainer
对象将输出日志,提供有关训练进度的宝贵见解
- 损失 (Loss):表示模型性能,值越低性能越好。
- 学习率 (Learning Rate):显示训练期间使用的当前学习率。
- 周期进度 (Epoch Progress):显示当前周期的进度。
这些日志对于监控模型的学习过程和进行任何必要的调整至关重要。通过在 MLflow 运行中追踪这些指标,我们可以维护训练过程的全面记录,提高可复现性和分析能力。
在下一个代码块中,我们将启动 MLflow 运行并开始训练模型,密切观察输出以衡量训练进度。
with mlflow.start_run() as run:
trainer.train()
0%| | 0/1674 [00:00<?, ?it/s]
{'loss': 0.4891, 'learning_rate': 4.9761051373954604e-05, 'epoch': 0.01} {'loss': 0.2662, 'learning_rate': 4.95221027479092e-05, 'epoch': 0.03} {'loss': 0.1756, 'learning_rate': 4.92831541218638e-05, 'epoch': 0.04} {'loss': 0.107, 'learning_rate': 4.90442054958184e-05, 'epoch': 0.06} {'loss': 0.0831, 'learning_rate': 4.8805256869773e-05, 'epoch': 0.07} {'loss': 0.0688, 'learning_rate': 4.8566308243727596e-05, 'epoch': 0.09} {'loss': 0.0959, 'learning_rate': 4.83273596176822e-05, 'epoch': 0.1} {'loss': 0.0831, 'learning_rate': 4.80884109916368e-05, 'epoch': 0.11} {'loss': 0.1653, 'learning_rate': 4.78494623655914e-05, 'epoch': 0.13} {'loss': 0.1865, 'learning_rate': 4.7610513739546e-05, 'epoch': 0.14} {'loss': 0.0887, 'learning_rate': 4.73715651135006e-05, 'epoch': 0.16} {'loss': 0.1009, 'learning_rate': 4.71326164874552e-05, 'epoch': 0.17} {'loss': 0.1017, 'learning_rate': 4.6893667861409805e-05, 'epoch': 0.19} {'loss': 0.0057, 'learning_rate': 4.66547192353644e-05, 'epoch': 0.2} {'loss': 0.0157, 'learning_rate': 4.6415770609319e-05, 'epoch': 0.22} {'loss': 0.0302, 'learning_rate': 4.61768219832736e-05, 'epoch': 0.23} {'loss': 0.0013, 'learning_rate': 4.59378733572282e-05, 'epoch': 0.24} {'loss': 0.0863, 'learning_rate': 4.56989247311828e-05, 'epoch': 0.26} {'loss': 0.1122, 'learning_rate': 4.54599761051374e-05, 'epoch': 0.27} {'loss': 0.1092, 'learning_rate': 4.5221027479092e-05, 'epoch': 0.29} {'loss': 0.0853, 'learning_rate': 4.49820788530466e-05, 'epoch': 0.3} {'loss': 0.1852, 'learning_rate': 4.4743130227001195e-05, 'epoch': 0.32} {'loss': 0.0913, 'learning_rate': 4.4504181600955796e-05, 'epoch': 0.33} {'loss': 0.0232, 'learning_rate': 4.42652329749104e-05, 'epoch': 0.34} {'loss': 0.0888, 'learning_rate': 4.402628434886499e-05, 'epoch': 0.36} {'loss': 0.195, 'learning_rate': 4.378733572281959e-05, 'epoch': 0.37} {'loss': 0.0198, 'learning_rate': 4.3548387096774194e-05, 'epoch': 0.39} {'loss': 0.056, 'learning_rate': 4.3309438470728796e-05, 'epoch': 0.4} {'loss': 0.1656, 'learning_rate': 4.307048984468339e-05, 'epoch': 0.42} {'loss': 0.0032, 'learning_rate': 4.283154121863799e-05, 'epoch': 0.43} {'loss': 0.1277, 'learning_rate': 4.259259259259259e-05, 'epoch': 0.44} {'loss': 0.0029, 'learning_rate': 4.2353643966547194e-05, 'epoch': 0.46} {'loss': 0.1007, 'learning_rate': 4.2114695340501795e-05, 'epoch': 0.47} {'loss': 0.0038, 'learning_rate': 4.1875746714456396e-05, 'epoch': 0.49} {'loss': 0.0035, 'learning_rate': 4.1636798088411e-05, 'epoch': 0.5} {'loss': 0.0015, 'learning_rate': 4.13978494623656e-05, 'epoch': 0.52} {'loss': 0.1423, 'learning_rate': 4.115890083632019e-05, 'epoch': 0.53} {'loss': 0.0316, 'learning_rate': 4.0919952210274794e-05, 'epoch': 0.54} {'loss': 0.0012, 'learning_rate': 4.0681003584229395e-05, 'epoch': 0.56} {'loss': 0.0009, 'learning_rate': 4.0442054958183996e-05, 'epoch': 0.57} {'loss': 0.1287, 'learning_rate': 4.020310633213859e-05, 'epoch': 0.59} {'loss': 0.0893, 'learning_rate': 3.996415770609319e-05, 'epoch': 0.6} {'loss': 0.0021, 'learning_rate': 3.972520908004779e-05, 'epoch': 0.62} {'loss': 0.0031, 'learning_rate': 3.9486260454002395e-05, 'epoch': 0.63} {'loss': 0.0022, 'learning_rate': 3.924731182795699e-05, 'epoch': 0.65} {'loss': 0.0008, 'learning_rate': 3.900836320191159e-05, 'epoch': 0.66} {'loss': 0.1119, 'learning_rate': 3.876941457586619e-05, 'epoch': 0.67} {'loss': 0.0012, 'learning_rate': 3.8530465949820786e-05, 'epoch': 0.69} {'loss': 0.2618, 'learning_rate': 3.829151732377539e-05, 'epoch': 0.7} {'loss': 0.0018, 'learning_rate': 3.805256869772999e-05, 'epoch': 0.72} {'loss': 0.0736, 'learning_rate': 3.781362007168459e-05, 'epoch': 0.73} {'loss': 0.0126, 'learning_rate': 3.7574671445639184e-05, 'epoch': 0.75} {'loss': 0.2125, 'learning_rate': 3.7335722819593785e-05, 'epoch': 0.76} {'loss': 0.0018, 'learning_rate': 3.7096774193548386e-05, 'epoch': 0.77} {'loss': 0.1386, 'learning_rate': 3.685782556750299e-05, 'epoch': 0.79} {'loss': 0.0024, 'learning_rate': 3.661887694145759e-05, 'epoch': 0.8} {'loss': 0.0016, 'learning_rate': 3.637992831541219e-05, 'epoch': 0.82} {'loss': 0.0011, 'learning_rate': 3.614097968936679e-05, 'epoch': 0.83} {'loss': 0.0307, 'learning_rate': 3.590203106332139e-05, 'epoch': 0.85} {'loss': 0.0007, 'learning_rate': 3.566308243727599e-05, 'epoch': 0.86} {'loss': 0.005, 'learning_rate': 3.542413381123059e-05, 'epoch': 0.87} {'loss': 0.0534, 'learning_rate': 3.518518518518519e-05, 'epoch': 0.89} {'loss': 0.0155, 'learning_rate': 3.494623655913979e-05, 'epoch': 0.9} {'loss': 0.0136, 'learning_rate': 3.4707287933094385e-05, 'epoch': 0.92} {'loss': 0.1108, 'learning_rate': 3.4468339307048986e-05, 'epoch': 0.93} {'loss': 0.0017, 'learning_rate': 3.422939068100359e-05, 'epoch': 0.95} {'loss': 0.0009, 'learning_rate': 3.399044205495819e-05, 'epoch': 0.96} {'loss': 0.0008, 'learning_rate': 3.375149342891278e-05, 'epoch': 0.97} {'loss': 0.0846, 'learning_rate': 3.3512544802867384e-05, 'epoch': 0.99}
0%| | 0/140 [00:00<?, ?it/s]
{'eval_loss': 0.03877367451786995, 'eval_accuracy': 0.9919282511210762, 'eval_runtime': 5.0257, 'eval_samples_per_second': 221.862, 'eval_steps_per_second': 27.857, 'epoch': 1.0} {'loss': 0.109, 'learning_rate': 3.3273596176821985e-05, 'epoch': 1.0} {'loss': 0.0084, 'learning_rate': 3.3034647550776586e-05, 'epoch': 1.02} {'loss': 0.0014, 'learning_rate': 3.279569892473118e-05, 'epoch': 1.03} {'loss': 0.0008, 'learning_rate': 3.255675029868578e-05, 'epoch': 1.05} {'loss': 0.0006, 'learning_rate': 3.231780167264038e-05, 'epoch': 1.06} {'loss': 0.0005, 'learning_rate': 3.207885304659498e-05, 'epoch': 1.08} {'loss': 0.0004, 'learning_rate': 3.183990442054958e-05, 'epoch': 1.09} {'loss': 0.0518, 'learning_rate': 3.160095579450418e-05, 'epoch': 1.1} {'loss': 0.0005, 'learning_rate': 3.136200716845878e-05, 'epoch': 1.12} {'loss': 0.149, 'learning_rate': 3.112305854241338e-05, 'epoch': 1.13} {'loss': 0.0022, 'learning_rate': 3.0884109916367984e-05, 'epoch': 1.15} {'loss': 0.0013, 'learning_rate': 3.0645161290322585e-05, 'epoch': 1.16} {'loss': 0.0051, 'learning_rate': 3.0406212664277183e-05, 'epoch': 1.18} {'loss': 0.0005, 'learning_rate': 3.016726403823178e-05, 'epoch': 1.19} {'loss': 0.0026, 'learning_rate': 2.9928315412186382e-05, 'epoch': 1.2} {'loss': 0.0005, 'learning_rate': 2.9689366786140983e-05, 'epoch': 1.22} {'loss': 0.0871, 'learning_rate': 2.9450418160095584e-05, 'epoch': 1.23} {'loss': 0.0004, 'learning_rate': 2.921146953405018e-05, 'epoch': 1.25} {'loss': 0.0004, 'learning_rate': 2.897252090800478e-05, 'epoch': 1.26} {'loss': 0.0003, 'learning_rate': 2.873357228195938e-05, 'epoch': 1.28} {'loss': 0.0003, 'learning_rate': 2.8494623655913982e-05, 'epoch': 1.29} {'loss': 0.0003, 'learning_rate': 2.8255675029868577e-05, 'epoch': 1.3} {'loss': 0.0478, 'learning_rate': 2.8016726403823178e-05, 'epoch': 1.32} {'loss': 0.0002, 'learning_rate': 2.777777777777778e-05, 'epoch': 1.33} {'loss': 0.0002, 'learning_rate': 2.753882915173238e-05, 'epoch': 1.35} {'loss': 0.0003, 'learning_rate': 2.7299880525686978e-05, 'epoch': 1.36} {'loss': 0.0002, 'learning_rate': 2.706093189964158e-05, 'epoch': 1.38} {'loss': 0.0005, 'learning_rate': 2.682198327359618e-05, 'epoch': 1.39} {'loss': 0.0002, 'learning_rate': 2.6583034647550775e-05, 'epoch': 1.41} {'loss': 0.0003, 'learning_rate': 2.6344086021505376e-05, 'epoch': 1.42} {'loss': 0.0002, 'learning_rate': 2.6105137395459977e-05, 'epoch': 1.43} {'loss': 0.0002, 'learning_rate': 2.586618876941458e-05, 'epoch': 1.45} {'loss': 0.0002, 'learning_rate': 2.5627240143369173e-05, 'epoch': 1.46} {'loss': 0.0007, 'learning_rate': 2.5388291517323774e-05, 'epoch': 1.48} {'loss': 0.1336, 'learning_rate': 2.5149342891278375e-05, 'epoch': 1.49} {'loss': 0.0004, 'learning_rate': 2.4910394265232977e-05, 'epoch': 1.51} {'loss': 0.0671, 'learning_rate': 2.4671445639187578e-05, 'epoch': 1.52} {'loss': 0.0004, 'learning_rate': 2.4432497013142176e-05, 'epoch': 1.53} {'loss': 0.1246, 'learning_rate': 2.4193548387096777e-05, 'epoch': 1.55} {'loss': 0.1142, 'learning_rate': 2.3954599761051375e-05, 'epoch': 1.56} {'loss': 0.002, 'learning_rate': 2.3715651135005976e-05, 'epoch': 1.58} {'loss': 0.002, 'learning_rate': 2.3476702508960574e-05, 'epoch': 1.59} {'loss': 0.0009, 'learning_rate': 2.3237753882915175e-05, 'epoch': 1.61} {'loss': 0.0778, 'learning_rate': 2.2998805256869773e-05, 'epoch': 1.62} {'loss': 0.0007, 'learning_rate': 2.2759856630824374e-05, 'epoch': 1.63} {'loss': 0.0008, 'learning_rate': 2.2520908004778972e-05, 'epoch': 1.65} {'loss': 0.0009, 'learning_rate': 2.2281959378733573e-05, 'epoch': 1.66} {'loss': 0.1032, 'learning_rate': 2.2043010752688174e-05, 'epoch': 1.68} {'loss': 0.0014, 'learning_rate': 2.1804062126642775e-05, 'epoch': 1.69} {'loss': 0.001, 'learning_rate': 2.1565113500597373e-05, 'epoch': 1.71} {'loss': 0.1199, 'learning_rate': 2.132616487455197e-05, 'epoch': 1.72} {'loss': 0.0009, 'learning_rate': 2.1087216248506572e-05, 'epoch': 1.73} {'loss': 0.0011, 'learning_rate': 2.084826762246117e-05, 'epoch': 1.75} {'loss': 0.0007, 'learning_rate': 2.060931899641577e-05, 'epoch': 1.76} {'loss': 0.0006, 'learning_rate': 2.037037037037037e-05, 'epoch': 1.78} {'loss': 0.0004, 'learning_rate': 2.013142174432497e-05, 'epoch': 1.79} {'loss': 0.0005, 'learning_rate': 1.989247311827957e-05, 'epoch': 1.81} {'loss': 0.1246, 'learning_rate': 1.9653524492234173e-05, 'epoch': 1.82} {'loss': 0.0974, 'learning_rate': 1.941457586618877e-05, 'epoch': 1.84} {'loss': 0.0003, 'learning_rate': 1.9175627240143372e-05, 'epoch': 1.85} {'loss': 0.0007, 'learning_rate': 1.893667861409797e-05, 'epoch': 1.86} {'loss': 0.1998, 'learning_rate': 1.869772998805257e-05, 'epoch': 1.88} {'loss': 0.0426, 'learning_rate': 1.845878136200717e-05, 'epoch': 1.89} {'loss': 0.002, 'learning_rate': 1.821983273596177e-05, 'epoch': 1.91} {'loss': 0.0009, 'learning_rate': 1.7980884109916368e-05, 'epoch': 1.92} {'loss': 0.0027, 'learning_rate': 1.774193548387097e-05, 'epoch': 1.94} {'loss': 0.0004, 'learning_rate': 1.7502986857825567e-05, 'epoch': 1.95} {'loss': 0.0003, 'learning_rate': 1.7264038231780168e-05, 'epoch': 1.96} {'loss': 0.1081, 'learning_rate': 1.702508960573477e-05, 'epoch': 1.98} {'loss': 0.0005, 'learning_rate': 1.678614097968937e-05, 'epoch': 1.99}
0%| | 0/140 [00:00<?, ?it/s]
{'eval_loss': 0.014878345653414726, 'eval_accuracy': 0.9973094170403587, 'eval_runtime': 4.0209, 'eval_samples_per_second': 277.3, 'eval_steps_per_second': 34.818, 'epoch': 2.0} {'loss': 0.0005, 'learning_rate': 1.6547192353643968e-05, 'epoch': 2.01} {'loss': 0.0005, 'learning_rate': 1.630824372759857e-05, 'epoch': 2.02} {'loss': 0.0004, 'learning_rate': 1.6069295101553167e-05, 'epoch': 2.04} {'loss': 0.0005, 'learning_rate': 1.5830346475507768e-05, 'epoch': 2.05} {'loss': 0.0004, 'learning_rate': 1.5591397849462366e-05, 'epoch': 2.06} {'loss': 0.0135, 'learning_rate': 1.5352449223416964e-05, 'epoch': 2.08} {'loss': 0.0014, 'learning_rate': 1.5113500597371565e-05, 'epoch': 2.09} {'loss': 0.0003, 'learning_rate': 1.4874551971326165e-05, 'epoch': 2.11} {'loss': 0.0003, 'learning_rate': 1.4635603345280766e-05, 'epoch': 2.12} {'loss': 0.0002, 'learning_rate': 1.4396654719235364e-05, 'epoch': 2.14} {'loss': 0.0002, 'learning_rate': 1.4157706093189965e-05, 'epoch': 2.15} {'loss': 0.0003, 'learning_rate': 1.3918757467144564e-05, 'epoch': 2.16} {'loss': 0.0008, 'learning_rate': 1.3679808841099166e-05, 'epoch': 2.18} {'loss': 0.0002, 'learning_rate': 1.3440860215053763e-05, 'epoch': 2.19} {'loss': 0.0002, 'learning_rate': 1.3201911589008365e-05, 'epoch': 2.21} {'loss': 0.0003, 'learning_rate': 1.2962962962962962e-05, 'epoch': 2.22} {'loss': 0.0002, 'learning_rate': 1.2724014336917564e-05, 'epoch': 2.24} {'loss': 0.0002, 'learning_rate': 1.2485065710872163e-05, 'epoch': 2.25} {'loss': 0.0002, 'learning_rate': 1.2246117084826763e-05, 'epoch': 2.27} {'loss': 0.0006, 'learning_rate': 1.2007168458781362e-05, 'epoch': 2.28} {'loss': 0.0875, 'learning_rate': 1.1768219832735962e-05, 'epoch': 2.29} {'loss': 0.0002, 'learning_rate': 1.1529271206690561e-05, 'epoch': 2.31} {'loss': 0.0003, 'learning_rate': 1.129032258064516e-05, 'epoch': 2.32} {'loss': 0.0002, 'learning_rate': 1.1051373954599762e-05, 'epoch': 2.34} {'loss': 0.0002, 'learning_rate': 1.0812425328554361e-05, 'epoch': 2.35} {'loss': 0.0003, 'learning_rate': 1.0573476702508961e-05, 'epoch': 2.37} {'loss': 0.0006, 'learning_rate': 1.033452807646356e-05, 'epoch': 2.38} {'loss': 0.0002, 'learning_rate': 1.009557945041816e-05, 'epoch': 2.39} {'loss': 0.0002, 'learning_rate': 9.856630824372761e-06, 'epoch': 2.41} {'loss': 0.0002, 'learning_rate': 9.61768219832736e-06, 'epoch': 2.42} {'loss': 0.0002, 'learning_rate': 9.37873357228196e-06, 'epoch': 2.44} {'loss': 0.0002, 'learning_rate': 9.13978494623656e-06, 'epoch': 2.45} {'loss': 0.0002, 'learning_rate': 8.90083632019116e-06, 'epoch': 2.47} {'loss': 0.0002, 'learning_rate': 8.661887694145759e-06, 'epoch': 2.48} {'loss': 0.0002, 'learning_rate': 8.42293906810036e-06, 'epoch': 2.49} {'loss': 0.0909, 'learning_rate': 8.18399044205496e-06, 'epoch': 2.51} {'loss': 0.0002, 'learning_rate': 7.945041816009559e-06, 'epoch': 2.52} {'loss': 0.0788, 'learning_rate': 7.706093189964159e-06, 'epoch': 2.54} {'loss': 0.0003, 'learning_rate': 7.467144563918758e-06, 'epoch': 2.55} {'loss': 0.0002, 'learning_rate': 7.228195937873358e-06, 'epoch': 2.57} {'loss': 0.0011, 'learning_rate': 6.989247311827957e-06, 'epoch': 2.58} {'loss': 0.0003, 'learning_rate': 6.7502986857825566e-06, 'epoch': 2.59} {'loss': 0.0002, 'learning_rate': 6.511350059737156e-06, 'epoch': 2.61} {'loss': 0.0002, 'learning_rate': 6.2724014336917564e-06, 'epoch': 2.62} {'loss': 0.0003, 'learning_rate': 6.033452807646357e-06, 'epoch': 2.64} {'loss': 0.0003, 'learning_rate': 5.794504181600956e-06, 'epoch': 2.65} {'loss': 0.0002, 'learning_rate': 5.555555555555556e-06, 'epoch': 2.67} {'loss': 0.0002, 'learning_rate': 5.316606929510155e-06, 'epoch': 2.68} {'loss': 0.0002, 'learning_rate': 5.077658303464755e-06, 'epoch': 2.7} {'loss': 0.0002, 'learning_rate': 4.838709677419355e-06, 'epoch': 2.71} {'loss': 0.0002, 'learning_rate': 4.599761051373955e-06, 'epoch': 2.72} {'loss': 0.0002, 'learning_rate': 4.360812425328554e-06, 'epoch': 2.74} {'loss': 0.0002, 'learning_rate': 4.121863799283155e-06, 'epoch': 2.75} {'loss': 0.0002, 'learning_rate': 3.882915173237754e-06, 'epoch': 2.77} {'loss': 0.0002, 'learning_rate': 3.643966547192354e-06, 'epoch': 2.78} {'loss': 0.0002, 'learning_rate': 3.405017921146954e-06, 'epoch': 2.8} {'loss': 0.0429, 'learning_rate': 3.1660692951015535e-06, 'epoch': 2.81} {'loss': 0.0002, 'learning_rate': 2.927120669056153e-06, 'epoch': 2.82} {'loss': 0.0002, 'learning_rate': 2.688172043010753e-06, 'epoch': 2.84} {'loss': 0.0002, 'learning_rate': 2.449223416965353e-06, 'epoch': 2.85} {'loss': 0.0761, 'learning_rate': 2.2102747909199524e-06, 'epoch': 2.87} {'loss': 0.0007, 'learning_rate': 1.971326164874552e-06, 'epoch': 2.88} {'loss': 0.0002, 'learning_rate': 1.7323775388291518e-06, 'epoch': 2.9} {'loss': 0.0002, 'learning_rate': 1.4934289127837516e-06, 'epoch': 2.91} {'loss': 0.0003, 'learning_rate': 1.2544802867383513e-06, 'epoch': 2.92} {'loss': 0.0003, 'learning_rate': 1.015531660692951e-06, 'epoch': 2.94} {'loss': 0.0144, 'learning_rate': 7.765830346475508e-07, 'epoch': 2.95} {'loss': 0.0568, 'learning_rate': 5.376344086021506e-07, 'epoch': 2.97} {'loss': 0.0001, 'learning_rate': 2.9868578255675034e-07, 'epoch': 2.98} {'loss': 0.0002, 'learning_rate': 5.973715651135006e-08, 'epoch': 3.0}
0%| | 0/140 [00:00<?, ?it/s]
{'eval_loss': 0.026208847761154175, 'eval_accuracy': 0.9937219730941704, 'eval_runtime': 4.0835, 'eval_samples_per_second': 273.052, 'eval_steps_per_second': 34.285, 'epoch': 3.0} {'train_runtime': 244.4781, 'train_samples_per_second': 54.717, 'train_steps_per_second': 6.847, 'train_loss': 0.0351541918909871, 'epoch': 3.0}
使用微调模型创建管道
本节中,我们将创建一个包含我们微调模型的管道。
完成训练过程后,下一步是使用微调模型创建一个用于推理的管道。此管道将使我们能够轻松地使用模型进行预测。
设置推理管道
- 创建管道:我们使用 Transformers 库中的
pipeline
函数创建一个推理管道。此管道配置用于文本分类任务。 - 集成模型:我们将微调模型(
trainer.model
)集成到管道中。这确保管道使用我们新训练的模型进行推理。 - 配置管道:我们在管道配置中设置批处理大小和分词器。此外,我们指定设备类型,这对于性能考虑至关重要。
不同平台的设备配置
- Apple Silicon (M1/M2) 设备:对于使用 Apple Silicon(例如 M1 或 M2 芯片)的用户,我们将管道中的设备类型设置为
"mps"
。这利用了 Apple 的 Metal Performance Shaders 以优化这些设备上的性能。 - 其他设备:如果您使用的设备不是带有 Apple Silicon 的 MacBook Pro,您需要调整设备设置以匹配您的硬件(例如,NVIDIA GPU 使用
"cuda"
或仅 CPU 推理使用"cpu"
)。
定制管道的重要性
使用微调模型创建定制管道可以轻松高效地进行推理,并根据我们的特定任务和硬件进行定制。此步骤对于从模型训练过渡到实际应用至关重要。
在以下代码块中,我们将使用微调模型设置管道并为我们的设备进行配置。
# If you're going to run this on something other than a Macbook Pro, change the device to the applicable type. "mps" is for Apple Silicon architecture in torch.
tuned_pipeline = pipeline(
task="text-classification",
model=trainer.model,
batch_size=8,
tokenizer=tokenizer,
device="mps",
)
验证微调模型
在下一步中,我们将验证我们的微调训练是否有效,然后将微调后的模型记录到我们的运行中。
在将模型记录到 MLflow 中最终确定之前,验证其性能至关重要。此验证步骤可确保模型符合我们的期望并已准备好部署。
模型验证的重要性
- 评估模型性能:我们需要在实际场景中评估模型的性能,以确保其行为符合预期。这有助于在模型记录并可能部署之前发现模型中的任何问题或不足。
- 避免代价高昂的返工:鉴于 Transformer 模型体积庞大且训练需要计算资源,提前验证模型至关重要。如果模型性能不佳,我们就不应该记录它,以免以后不得不删除运行和记录的工件。
使用测试查询进行评估
- 测试查询:我们将向调优后的管道传递一个实际的测试查询,以查看模型的性能。此查询应代表模型在实际场景中预期处理的输入类型。
- 观察输出:通过分析模型针对此查询的输出,我们可以衡量其对复杂情况的理解和响应。这提供了对模型微调后能力的实际洞察。
在记录到 MLflow 之前进行验证
- 理由:此验证步骤的原因是确保我们记录到 MLflow 的模型是高质量的,并为部署或共享等后续步骤做好准备。记录一个性能不佳的模型会导致不必要的复杂性,特别是考虑到这些模型的庞大体积和复杂性。
验证模型并确保性能令人满意后,我们可以放心地在 MLflow 中记录它,知道它已为实际应用做好准备。
在下一个代码块中,我们将通过我们的微调模型运行一个测试查询,以评估其性能,然后再继续将其记录到 MLflow 中。
# Perform a validation of our assembled pipeline that contains our fine-tuned model.
quick_check = (
"I have a question regarding the project development timeline and allocated resources; "
"specifically, how certain are you that John and Ringo can work together on writing this next song? "
"Do we need to get Paul involved here, or do you truly believe, as you said, 'nah, they got this'?"
)
tuned_pipeline(quick_check)
[{'label': 'ham', 'score': 0.9985793828964233}]
模型配置和签名推断
在下一步中,我们将为管道生成签名,以准备进行日志记录。
验证模型的性能后,下一个关键步骤是准备将其记录到 MLflow。这包括设置模型的配置并推断其签名,这些都是模型管理过程中的重要方面。
配置用于 MLflow 的模型
- 设置模型配置:我们定义一个
model_config
字典来指定配置参数,例如批处理大小和设备类型(例如,Apple Silicon 使用"mps"
)。此配置对于确保模型在不同环境中正常运行至关重要。
推断模型签名
- 签名推断的目的:模型签名定义了模型的输入和输出 schema。推断此签名至关重要,因为它有助于 MLflow 理解模型期望和产生的数据类型和形状。
- 使用 mlflow.models.infer_signature:我们使用此函数自动推断模型签名。我们向函数提供示例输入和输出数据,函数会分析它们以确定合适的 schema。
- 包含模型参数:除了输入和输出外,我们还将
model_config
包含在签名中。这确保了关于模型应该如何运行的所有相关信息都被捕获。
签名推断的重要性
推断签名是准备模型进行日志记录和未来部署的关键步骤。它确保了稍后使用模型(无论是用于进一步开发还是生产环境)的任何人都能清楚地了解期望的数据格式,从而使模型更加健壮和用户友好。
设置好模型配置并推断出其签名后,我们现在可以准备将模型记录到 MLflow 中了。这将是我们的下一步,确保我们的模型得到妥善管理并准备好进行部署。
# Define a set of parameters that we would like to be able to flexibly override at inference time, along with their default values
model_config = {"batch_size": 8}
# Infer the model signature, including a representative input, the expected output, and the parameters that we would like to be able to override at inference time.
signature = mlflow.models.infer_signature(
["This is a test!", "And this is also a test."],
mlflow.transformers.generate_signature_output(
tuned_pipeline, ["This is a test response!", "So is this."]
),
params=model_config,
)
将微调模型记录到 MLflow
在本节中,我们将把我们验证过的管道记录到训练运行中。
模型配置和签名准备就绪后,模型训练和验证过程的最后一步是将模型记录到 MLflow。此步骤对于系统地跟踪和管理模型至关重要。
访问用于训练的现有运行
- 启动 MLflow 运行:我们使用
mlflow.start_run()
在 MLflow 中启动一个新的运行。这个新的运行专门用于记录模型,与训练运行分开。
在 MLflow 中记录模型
-
使用 mlflow.transformers.log_model:我们使用此函数记录微调模型。它专门设计用于记录 Transformers 库中的模型,使过程更加简化和高效。
-
指定模型信息:我们向日志记录函数提供几个信息
- transformers_model:微调的模型管道。
- artifact_path:模型工件的存储路径。
- signature:模型的推断签名,包括输入和输出 schema。
- input_example:示例输入,让用户了解模型期望的输入是什么。
- model_config:模型的配置参数。
模型日志记录的重要性
在 MLflow 中记录模型有多种用途
- 版本控制:它有助于跟踪模型的不同版本。
- 模型管理:促进模型生命周期的管理,从训练到部署。
- 可复现性和共享:增强可复现性,并使与他人共享模型更加容易。
通过在 MLflow 中记录模型,我们确保它有详细记录、版本控制,并为将来使用做好准备,无论是用于进一步开发还是部署。
# Log the pipeline to the existing training run
with mlflow.start_run(run_id=run.info.run_id):
model_info = mlflow.transformers.log_model(
transformers_model=tuned_pipeline,
artifact_path="fine_tuned",
signature=signature,
input_example=["Pass in a string", "And have it mark as spam or not."],
model_config=model_config,
)
2023/11/30 12:17:11 WARNING mlflow.utils.environment: Encountered an unexpected error while inferring pip requirements (model URI: /var/folders/cd/n8n0rm2x53l_s0xv_j_xklb00000gp/T/tmp77_imuy9/model, flavor: transformers), fall back to return ['transformers==4.34.1', 'torch==2.1.0', 'torchvision==0.16.0', 'accelerate==0.21.0']. Set logging level to DEBUG to see the full traceback.
从 MLflow 加载和测试模型
将微调模型记录到 MLflow 后,我们现在将加载并测试它。
从 MLflow 加载模型
- 使用 mlflow.transformers.load_model:我们使用此函数加载存储在 MLflow 中的模型。这演示了如何在训练后检索和利用模型,确保它们可用于将来使用。
- 检索模型 URI:我们使用从记录模型到 MLflow 中获得 的
model_uri
。此 URI 是我们记录模型的唯一标识符,使我们能够准确地检索它。
使用验证文本测试模型
- 准备验证文本:我们使用精心编写的文本来测试模型的性能。此文本旨在模仿典型的垃圾邮件,这与我们模型的垃圾邮件分类训练相关。
- 评估模型输出:通过将此文本传递给加载的模型,我们可以在实际场景中观察其性能和有效性。此步骤对于确保模型在实际环境中按预期工作至关重要。
在从 MLflow 加载模型后进行测试至关重要,原因如下:
- 验证日志记录过程:它确认模型已正确记录和加载。
- 实际性能评估:提供模型性能的真实世界评估,这对于部署决策至关重要。
- 展示端到端工作流程:展示了从训练、日志记录、加载到使用模型的完整工作流程,这对于理解整个模型生命周期至关重要。
在下一个代码块中,我们将从 MLflow 加载模型,并使用验证文本对其进行测试,以评估其真实世界性能。
# Load our saved model in the native transformers format
loaded = mlflow.transformers.load_model(model_uri=model_info.model_uri)
# Define a test example that we expect to be classified as spam
validation_text = (
"Want to learn how to make MILLIONS with no effort? Click HERE now! See for yourself! Guaranteed to make you instantly rich! "
"Don't miss out you could be a winner!"
)
# validate the performance of our fine-tuning
loaded(validation_text)
2023/11/30 12:17:11 INFO mlflow.transformers: 'runs:/e3260e8511c94c38aafb7124509240a4/fine_tuned' resolved as 'file:///Users/benjamin.wilson/repos/mlflow-fork/mlflow/docs/source/llms/transformers/tutorials/fine-tuning/mlruns/258758267044147956/e3260e8511c94c38aafb7124509240a4/artifacts/fine_tuned' 2023/11/30 12:17:11 WARNING mlflow.transformers: Could not specify device parameter for this pipeline type
[{'label': 'spam', 'score': 0.9873914122581482}]
结论:掌握微调和 MLflow 集成
祝贺您完成此关于微调 Transformers 模型并将其与 MLflow 集成的全面教程!让我们回顾一下您在此旅程中获得的必要技能和知识。
关键要点
- 微调 Transformers 模型:您已经学会了如何微调 Transformers 库中的基础模型。此过程展示了使高级预训练模型适应特定任务的能力,从而根据独特需求调整其性能。
- 微调的便捷性:我们亲身见证了微调这些高级大型语言模型 (LLM) 是多么直接。借助正确的工具和理解,微调可以显著增强模型在特定任务上的性能。
- 性能的特异性:微调 LLM 的能力开辟了各种可能性,使我们能够创建在特定领域或任务中表现出色的模型。这种性能的特异性对于在需要专业理解的实际场景中部署模型至关重要。
集成 MLflow 与 Transformers
- 跟踪和管理微调过程:本教程的一个重要部分专门用于使用 MLflow 进行实验跟踪、模型日志记录和管理。您已经了解了 MLflow 如何简化这些方面,使机器学习工作流程更易于管理和高效。
- MLflow 在微调中的优势:MLflow 在确保可复现性、管理模型版本和简化部署过程方面发挥着关键作用。它与 Transformers 微调过程的集成展示了高级模型训练技术和生命周期管理工具之间的协同潜力。