第四阶段 审查模块开发 微调-SFT部分
SFT部分
监督微调(Supervised Fine-Tuning)是指在标注数据集上对预训练模型进行微调,使模型能够适应特定的任务需求。
功能:
- 特定任务优化:通过在标注数据集上进行微调,模型能够调整其参数以更好地适应特定任务,如分类、回归、序列标注等。
- 提高性能:微调阶段通过优化特定任务的损失函数,提高模型在该任务上的性能。
# coding=utf-8
# Implements several parameter-efficient supervised fine-tuning method.
# This code is inspired by
# https://github.com/huggingface/transformers/blob/v4.29.2/examples/pytorch/summarization/run_summarization.py
# import torch
# torch.distributed.init_process_group(backend="gloo")
from utils import (
DynamicDataCollatorWithPadding,
Seq2SeqPeftTrainer,
ComputeMetrics,
LogCallback,
load_pretrained,
prepare_args,
prepare_data,
preprocess_data,
get_logits_processor,
plot_loss
)
def main():
# Prepare pretrained model and dataset
model_args, data_args, training_args, finetuning_args = prepare_args(stage="sft")
dataset = prepare_data(model_args, data_args)
model, tokenizer = load_pretrained(model_args, finetuning_args, training_args.do_train, stage="sft")
dataset = preprocess_data(dataset, tokenizer, data_args, training_args, stage="sft")
data_collator = DynamicDataCollatorWithPadding(
tokenizer=tokenizer,
ignore_pad_token_for_loss=(data_args.ignore_pad_token_for_loss and not training_args.predict_with_generate)
)
# Override the decoding parameters of Seq2SeqTrainer
training_args.generation_max_length = training_args.generation_max_length if \
training_args.generation_max_length is not None else data_args.max_target_length
training_args.generation_num_beams = data_args.eval_num_beams if \
data_args.eval_num_beams is not None else training_args.generation_num_beams
# Split the dataset
if training_args.do_train:
if data_args.dev_ratio > 1e-6:
dataset = dataset.train_test_split(test_size=data_args.dev_ratio)
trainer_kwargs = {"train_dataset": dataset["train"], "eval_dataset": dataset["test"]}
else:
trainer_kwargs = {"train_dataset": dataset}
else: # do_eval or do_predict
trainer_kwargs = {"eval_dataset": dataset}
# Initialize our Trainer
trainer = Seq2SeqPeftTrainer(
finetuning_args=finetuning_args,
model=model,
args=training_args,
tokenizer=tokenizer,
data_collator=data_collator,
callbacks=[LogCallback()],
compute_metrics=ComputeMetrics(tokenizer) if training_args.predict_with_generate else None,
**trainer_kwargs
)
# Keyword arguments for `model.generate`
gen_kwargs = {
"do_sample": True,
"top_p": 0.7,
"max_new_tokens": data_args.max_target_length + 1,
"temperature": 0.95,
"logits_processor": get_logits_processor()
}
# Training
if training_args.do_train:
train_result = trainer.train()
trainer.log_metrics("train", train_result.metrics)
trainer.save_metrics("train", train_result.metrics)
trainer.save_state()
trainer.save_model()
if trainer.is_world_process_zero() and model_args.plot_loss:
plot_loss(training_args.output_dir, keys=["loss", "eval_loss"])
# Evaluation
if training_args.do_eval:
metrics = trainer.evaluate(metric_key_prefix="eval", **gen_kwargs)
if training_args.predict_with_generate: # eval_loss will be wrong if predict_with_generate is enabled
metrics.pop("eval_loss", None)
trainer.log_metrics("eval", metrics)
trainer.save_metrics("eval", metrics)
# Predict
if training_args.do_predict:
predict_results = trainer.predict(dataset, metric_key_prefix="predict", **gen_kwargs)
if training_args.predict_with_generate: # predict_loss will be wrong if predict_with_generate is enabled
predict_results.metrics.pop("predict_loss", None)
trainer.log_metrics("predict", predict_results.metrics)
trainer.save_metrics("predict", predict_results.metrics)
trainer.save_predictions(predict_results)
def _mp_fn(index):
# For xla_spawn (TPUs)
main()
if __name__ == "__main__":
main()
整个前置过程中实现与上一篇博客中的pt中的实现基本一致,但是不同的是在 DynamicDataCollatorWithPadding 引入了 ignore_pad_token_for_loss=(data_args.ignore_pad_token_for_loss and not training_args.predict_with_generate)目的是设置是否在计算损失时忽略填充标记。在自然语言处理(NLP)任务中,输入和输出序列通常需要进行填充(padding)以适应批处理操作。填充值(pad token)用于填充较短的序列,使它们具有相同的长度。在计算损失时,这些填充值不应该对损失的计算产生影响,因为它们不是真实的训练数据。在训练阶段,通常希望忽略填充值的损失计算,因为填充值并不代表有效的训练数据。这可以防止填充值对模型参数的更新产生不必要的影响,从而提高模型的训练效果。在生成文本的任务中(如预测或评估阶段),有时需要对整个序列进行处理,包括填充值。这是因为在生成过程中,模型可能需要考虑整个输入序列的结构,包括填充值的影响。这有助于在训练过程中提高模型性能,同时在生成文本时保留必要的序列信息。
training_args.generation_max_length 是用于控制生成的最大文本长度,而 data_args.max_target_length 则是从数据参数中获取的默认目标长度。如果没有指定生成的最大长度,则使用默认的目标长度。
training_args.generation_num_beams 控制生成时的 Beam 数量,而 data_args.eval_num_beams 则是评估时使用的 Beam 数量。如果评估参数中指定了 Beam 数量,则优先使用它;否则,使用训练参数中的值。
而在模型建立上采用了与继承自之前的PeftTrainer的Seq2SeqPeftTrainer类,主要用于计算生成任务的评估指标,如 BLEU 和 ROUGE。它通过重写 prediction_step 和添加 save_predictions 方法,实现了自定义的预测步骤和保存预测结果的功能。
class Seq2SeqPeftTrainer(PeftTrainer):
r"""
Inherits PeftTrainer to compute generative metrics such as BLEU and ROUGE.
用于计算生成任务的评估指标
"""
def prediction_step(
self,
model: nn.Module,
inputs: Dict[str, Union[torch.Tensor, Any]],
prediction_loss_only: bool,
ignore_keys: Optional[List[str]] = None,
) -> Tuple[Optional[float], Optional[torch.Tensor], Optional[torch.Tensor]]:
r"""
Removes the prompt part in the generated tokens.
Subclass and override to inject custom behavior.
"""
input_ids = inputs["input_ids"]
loss, generated_tokens, labels = super().prediction_step(
model, inputs, prediction_loss_only=prediction_loss_only, ignore_keys=ignore_keys
)
generated_tokens = generated_tokens[:, input_ids.size(-1):] if generated_tokens is not None else None
return (loss, generated_tokens, labels)
def save_predictions(
self,
predict_results: PredictionOutput
) -> None:
r"""
Saves model predictions to `output_dir`.
A custom behavior that not contained in Seq2SeqTrainer.
"""
if not self.is_world_process_zero():
return
output_prediction_file = os.path.join(self.args.output_dir, "generated_predictions.jsonl")
logger.info(f"Saving prediction results to {output_prediction_file}")
preds = np.where(predict_results.predictions != IGNORE_INDEX, predict_results.predictions, self.tokenizer.pad_token_id)
labels = np.where(predict_results.label_ids != IGNORE_INDEX, predict_results.label_ids, self.tokenizer.pad_token_id)
decoded_preds = self.tokenizer.batch_decode(preds, skip_special_tokens=True, clean_up_tokenization_spaces=True)
decoded_labels = self.tokenizer.batch_decode(labels, skip_special_tokens=True, clean_up_tokenization_spaces=True)
with open(output_prediction_file, "w", encoding="utf-8") as writer:
res: List[str] = []
for pred, label in zip(decoded_preds, decoded_labels):
res.append(json.dumps({"label": label, "predict": pred}, ensure_ascii=False))
writer.write("\n".join(res))
prediction_step这个方法用于在生成的预测结果中移除提示部分的令牌。调用父类 PeftTrainer 的 prediction_step 方法获取损失、生成的令牌和标签。移除生成的令牌中与输入长度相同的部分,保留生成的文本。返回损失、处理后的生成令牌和标签。
save_predictions这个方法用于将模型的预测结果保存到指定的输出目录中。检查当前进程是否为主进程,如果不是,则直接返回。构建输出文件路径,并记录保存预测结果的日志信息。将预测结果中的 IGNORE_INDEX 替换为填充值(pad token)。解码预测结果和标签,将它们转换为可读的文本。将解码后的预测结果和标签保存到 JSON Lines 格式的文件中。
设置了用于调用 model.generate 方法的关键字参数 gen_kwargs,这些参数对生成文本的行为和质量有直接影响。设置这些参数的意义如下:
- 多样性:通过启用采样(
do_sample=True)和设置核采样(top_p=0.7),可以确保生成的文本具有较高的多样性,而不是每次生成相同的文本。这对于对话生成、故事生成等任务非常重要。 - 长度控制:通过设置
max_new_tokens,可以控制生成的文本长度,确保生成的文本不超过预期长度,避免生成过长的文本带来的问题。 - 随机性控制:通过设置温度参数(
temperature=0.95),可以调节生成过程的随机性,平衡生成文本的质量和多样性。 - 自定义处理:通过
logits_processor,可以对生成过程中间的 logits 进行自定义处理,进一步控制生成文本的质量和内容。
浙公网安备 33010602011771号