一:基础组件知识回顾
- Pipeline
- 流水线,用于模型推理,封装了完整的推理逻辑,包括数据预处理、模型预测及后处理。
- Tokenizer
- 分词器,用于数据预处理,将原始文本输入转换为模型的输入,包括inputs_ids、attention_mask等
- Model
- 模型,用于加载、创建、保存模型,对pytorch中的模型进行了封装,同时更好的支持预训练模型。
- Datasets
- 数据集,用于数据集加载与预处理,支持加载在线与本地的数据集,提供了数据集层面的处理方法。
- Evaluate
- 评估函数,用于对模型的结果进行评估,支持多种任务的评估函数。
- Trainer
- 训练器,用于模型训练、评估,支持丰富的配置选项,快速启动模型训练流程。
二:基于transformers的NLP解决方案
以文本分类为例:
-
Step1 导入相关包
from transformers import AutoTokenizer, AutoModelForSequenceClassification, Trainer, TrainingArguments from datasets import load_dataset
-
step2 加载数据集 ——>Datasets
dataset = load_dataset("csv", data_files="./ChnSentiCorp_htl_all.csv", split="train") dataset = dataset.filter(lambda x: x["review"] is not None)
-
step3 数据集划分 ——>Datasets
datasets = dataset.train_test_split(test_size=0.1)
-
step4 数据集预处理 ——>Tokenizer + Datasets
import torchtokenizer = AutoTokenizer.from_pretrained("hfl/chinese-macbert-large")def process_function(examples):tokenized_examples = tokenizer(examples["review"], max_length=128, truncation=True, padding="max_length")tokenized_examples["labels"] = examples["label"]return tokenized_examplestokenized_datasets = datasets.map(process_function, batched=True, remove_columns=datasets["train"].column_names)
-
step5 创建模型 ——>Model
model = AutoModelForSequenceClassification.from_pretrained("hfl/chinese-macbert-large")
-
step6 设置评估函数 ——>Evaluate
import evaluateacc_metric = evaluate.load("accuracy") f1_metric = evaluate.load("f1")def eval_metric(eval_predict):predictions, labels = eval_predictpredictions = predictions.argmax(axis=-1)acc = acc_metric.compute(predictions=predictions, references=labels)f1 = f1_metirc.compute(predictions=predictions, references=labels)acc.update(f1)return acc
-
step7 配置训练参数 ——>TrainingArguments
train_args = TrainingArguments(output_dir="./checkpoints", # 输出文件夹per_device_train_batch_size=32, # 训练时的batch_sizeper_device_eval_batch_size=32, # 验证时的batch_sizenum_train_epochs=1, # 训练轮数logging_steps=10, # log 打印的频率evaluation_strategy="epoch", # 评估策略save_strategy="epoch", # 保存策略save_total_limit=3, # 最大保存数learning_rate=2e-5, # 学习率weight_decay=0.01, # weight_decaymetric_for_best_model="f1", # 设定评估指标load_best_model_at_end=True) # 训练完成后加载最优模型
-
step8 创建训练器 ——>Trainer + Data Collator
from transformers import DataCollatorWithPadding trainer = Trainer(model=model, args=train_args, train_dataset=tokenized_datasets["train"], eval_dataset=tokenized_datasets["test"], data_collator=DataCollatorWithPadding(tokenizer=tokenizer),compute_metrics=eval_metric)
-
step9 模型训练、评估、预测(数据集) ——>Trainer
trainer.train() trainer.evaluate(tokenized_datasets["test"]) trainer.predict(tokenized_datasets["test"])
-
step10 模型预测(单条) ——>Pipeline
from transformers import pipelinesen = "我觉得这家酒店不错,饭很好吃!" id2_label = {0: "差评!", 1: "好评!"} model.config.id2label = id2_label pipe = pipeline("text-classification", model=model, tokenizer=tokenizer, device=0) pipe(sen)
三:显存优化策略,4G显存也能跑BERT-Large
-
显存占用简单分析
-
模型权重
- 4Bytes * 模型参数量
-
优化器状态
- 8Bytes * 模型参数量,对于常用的AdamW优化器而言
-
梯度
- 4Bytes * 模型参数量
-
前向激活值
- 取决于序列长度、隐层维度、Bath大小等多个因素
-
-
显存优化策略
- hfl/chinese-macbert-large, 330M
- hfl/chinese-macbert-large, 330M
- Baseline:batchsize=32, maxlegth=128。代码:上文step1-10.
- + Gradient Accumulation:batchsize=1, gradient_accumulation_steps=32。
Gradient Accumulation工作原理:
- 小批次训练:在训练过程中,通常会将数据分成小批次(mini-batches)。每个小批次的大小可能受到内存限制的影响。
- 累积梯度:在每个小批次上进行前向传播和反向传播,但不立即更新模型参数。相反,计算得到的梯度会被累积到一个变量中。
- 更新参数:累积的梯度达到预定的步数(例如,经过 N 个小批次)时,才进行一次参数更新。这意味着您可以使用较小的批次大小来模拟较大的批次大小。
修改step7代码:
train_args = TrainingArguments(output_dir="./checkpoints", # 输出文件夹per_device_train_batch_size=1, # 训练时的batch_sizeper_device_eval_batch_size=1, # 验证时的batch_sizegradient_accumulation_steps=32 # *** 梯度累加 ***num_train_epochs=1, # 训练轮数logging_steps=10, # log 打印的频率evaluation_strategy="epoch", # 评估策略save_strategy="epoch", # 保存策略save_total_limit=3, # 最大保存数learning_rate=2e-5, # 学习率weight_decay=0.01, # weight_decaymetric_for_best_model="f1", # 设定评估指标load_best_model_at_end=True) # 训练完成后加载最优模型
- + Gradient Checkpoint: gradient_checkpointing=True
Gradient Checkpoint工作原理:
- 前向传播:在正常的前向传播中,所有中间激活值都会被保存,以便在反向传播时使用。这会消耗大量内存,尤其是在深层网络中。
- 选择性保存:使用梯度检查点技术,您可以选择性地保存某些中间激活值,而不是保存所有激活值。未保存的激活值在反向传播时会被重新计算。
- 反向传播:在反向传播过程中,对于未保存的激活值,系统会重新执行前向传播以计算这些值。这会增加计算开销,但可以显著减少内存使用。
修改step7代码:
train_args = TrainingArguments(output_dir="./checkpoints", # 输出文件夹per_device_train_batch_size=1, # 训练时的batch_sizeper_device_eval_batch_size=1, # 验证时的batch_sizegradient_accumulation_steps=32 # 梯度累加gradient_checkpointing=True # *** 梯度检查点 ***num_train_epochs=1, # 训练轮数logging_steps=10, # log 打印的频率evaluation_strategy="epoch", # 评估策略save_strategy="epoch", # 保存策略save_total_limit=3, # 最大保存数learning_rate=2e-5, # 学习率weight_decay=0.01, # weight_decaymetric_for_best_model="f1", # 设定评估指标load_best_model_at_end=True) # 训练完成后加载最优模型
- + Adafactor Optimizer: optim="adafactor"
Adafactor背景:
- 自适应学习率:Adafactor 继承了 Adam 优化器的自适应学习率特性,根据每个参数的历史梯度动态调整学习率。
- 内存效率:与 Adam 不同,Adafactor 通过使用低秩矩阵来存储和更新二阶矩估计,从而显著减少内存使用。
修改step7代码:
train_args = TrainingArguments(output_dir="./checkpoints", # 输出文件夹per_device_train_batch_size=1, # 训练时的batch_sizeper_device_eval_batch_size=1, # 验证时的batch_sizegradient_accumulation_steps=32 # 梯度累加gradient_checkpointing=True # 梯度检查点optim="adafactor", # *** adafactor优化器 ***num_train_epochs=1, # 训练轮数logging_steps=10, # log 打印的频率evaluation_strategy="epoch", # 评估策略save_strategy="epoch", # 保存策略save_total_limit=3, # 最大保存数learning_rate=2e-5, # 学习率weight_decay=0.01, # weight_decaymetric_for_best_model="f1", # 设定评估指标load_best_model_at_end=True) # 训练完成后加载最优模型
- + Freeze Model
Freeze Model工作原理:
- 冻结层:在训练过程中,您可以选择冻结模型的某些层。这意味着这些层的权重不会在反向传播过程中更新。冻结层通常是模型的底层或特征提取层,因为它们已经在大规模数据集上训练得很好。
- 更新层:只有未冻结的层的权重会在训练过程中更新。这通常是模型的顶部层或分类层,因为这些层需要根据特定任务进行调整。
修改step8代码: 只需要在创建训练器Trainer之前,将部分层的参数冻结即可。这样不仅节约了内存,还节省了时间。(冻结:参数不需要计算梯度,没有梯度就不会进行更新,从而达到参数冻结的目的)
from transformers import DataCollatorWithPadding# *** 参数冻结 ***
for name, param in model.bert.named_parameters():param.requires_grad = Falsetrainer = Trainer(model=model, args=train_args, tokenizer=tokenizer,train_dataset=tokenized_datasets["train"], eval_dataset=tokenized_datasets["test"], data_collator=DataCollatorWithPadding(tokenizer=tokenizer),compute_metrics=eval_metric)
- + Data Length : maxlegth=32
工作原理:
通过减小数据长度,可以减少每个批次的内存占用,从而降低显存使用。这使得在有限的显存条件下能够训练更大的模型或使用更大的批次。
修改step4代码:
import torchtokenizer = AutoTokenizer.from_pretrained("hfl/chinese-macbert-large")# *** 只需要将max_length的值设为32 ***
def process_function(examples):tokenized_examples = tokenizer(examples["review"], max_length=32, truncation=True, padding="max_length")tokenized_examples["labels"] = examples["label"]return tokenized_examplestokenized_datasets = datasets.map(process_function, batched=True, remove_columns=datasets["train"].column_names)