本文试图分析程序结构和代码,解释序列转换生成模型的微调训练。为了篇幅不要过长,分两篇文章解读,本文解读训练代码。框架概述请看前篇文章:GPT实战系列-P-Tuning本地化训练ChatGLM2等LLM模型,到底做了什么?(一)
对相关的内容感兴趣,也可以到GPT专栏里的文章看看。如果没有你感兴趣的,请留言,后续一起讨论。
GPT专栏:https://blog.csdn.net/alex_starsky/category_12467518.html
下面解读ptuning目录下的main.py代码。
训练环节中,通过命令行配置参数(sh脚本),然后通过HfArgumentParser方法,把配置参数指定到模型,数据和训练的参数类中。通过参数解析器parser,进一步分类为模型参数,数据参数和训练超参数。
# 新版本的transformers中的ner没有采用传统的parser模块,利用HfArgumentParser方法,将参数类转化为argparse参数,以便于在命令行中指定他们。
# ModelArguments类为model/config/tokenizer涉及的参数。ModelArguments类 中包含的是关于模型的属性,如model_name,config_name,tokenizer_name等,类在run.py文件中定义;
# DataTrainingArguments类 为训练、测试数据集操作涉及到的参数。DataTrainingArguments类 中包含的是关于微调数据的属性,如task_name,data_dir等,类在transformers/data/datasets/glue.py文件中定义;
# TrainingArguments中包含的是关于微调过程的参数,如batch_size,learning_rate等参数,类在transformers/training_args.py中定义。
parser = HfArgumentParser((ModelArguments, DataTrainingArguments, Seq2SeqTrainingArguments))
# 通过参数解析器parser,进一步分类为模型参数,数据参数和训练超参数。
# 训练时输入python run_ner.py *******.json,即从.json中读取参数。
# 当sys.argv参数为2时,此时sys.argv[0]为自己本身,即"run_ner.py",sys.argv[1]为json文件
if len(sys.argv) == 2 and sys.argv[1].endswith(".json"):
model_args, data_args, training_args = parser.parse_json_file(json_file=os.path.abspath(sys.argv[1]))
else:
model_args, data_args, training_args = parser.parse_args_into_dataclasses()
如果参数过多,或者需要保存参数,采用json文件格式,比如:
{
"do_train": true,
"train_file": "dataset/AdvertiseGen/train.json",
"validation_file": "dataset/AdvertiseGen/dev.json",
"preprocessing_num_workers": 10,
"prompt_column": "content",
"response_column": "summary",
"overwrite_cache": true,
"model_name_or_path": "/Users/andy/Desktop/LLM/model/chatglm2-6b",
"output_dir": "output/adgen-chatglm2-6b-lora_version",
"overwrite_output_dir": true,
"max_source_length": 64,
"max_target_length": 128,
"per_device_train_batch_size": 1,
"per_device_eval_batch_size": 1,
"gradient_accumulation_steps": 16,
"predict_with_generate": true,
"max_steps": 3000,
"logging_steps": 10,
"save_steps": 100,
"learning_rate": 2e-5,
"lora_r": 32,
"model_parallel_mode": true,
"output_dir": "output"
}
训练过程的日志排版,打印信息等相关设置
#设置日志相关,排版格式,日期格式
# Setup logging
logging.basicConfig(
format="%(asctime)s - %(levelname)s - %(name)s - %(message)s",
datefmt="%m/%d/%Y %H:%M:%S",
handlers=[logging.StreamHandler(sys.stdout)],
)
#设置打印信息
if training_args.should_log:
# The default of training_args.log_level is passive, so we set log level at info here to have that default.
transformers.utils.logging.set_verbosity_info()
#设置日志信息等级,transformer日志配置
log_level = training_args.get_process_log_level()
logger.setLevel(log_level)
# datasets.utils.logging.set_verbosity(log_level)
transformers.utils.logging.set_verbosity(log_level)
transformers.utils.logging.enable_default_handler()
transformers.utils.logging.enable_explicit_format()
训练前的准备工作,包括预处理,训练数据集、验证数据集、测试数据集等参数配置
训练数据准备
# 加载数据集,训练文件,验证文件,测试文件,文件名,文件类型(扩展名)
# Load dataset
data_files = {}
if data_args.train_file is not None:
data_files["train"] = data_args.train_file
extension = data_args.train_file.split(".")[-1]
if data_args.validation_file is not None:
data_files["validation"] = data_args.validation_file
extension = data_args.validation_file.split(".")[-1]
if data_args.test_file is not None:
data_files["test"] = data_args.test_file
extension = data_args.test_file.split(".")[-1]
# 加载训练、验证、测试数据文件,cache目录
raw_datasets = load_dataset(
extension,
data_files=data_files,
cache_dir=model_args.cache_dir,
use_auth_token=True if model_args.use_auth_token else None,
)
训练模型的配置
预训练模型加载,序列长度,序列前缀处理
# 加载模型配置(模型架构),预训练模型
# Load pretrained model and tokenizer
config = AutoConfig.from_pretrained(model_args.model_name_or_path, trust_remote_code=True)
config.pre_seq_len = model_args.pre_seq_len
config.prefix_projection = model_args.prefix_projection
加载分词器,对自然语言序列进行分解,分词处理
# 分词器
tokenizer = AutoTokenizer.from_pretrained(model_args.model_name_or_path, trust_remote_code=True)
预训练模型的加载,有两种方式,一种是把过程加载方式,checkpoint,另一种是整合为一个模型bin格式,然后直接加载。
# 含有训练过程信息的checkpoint模型的加载模式,如ptuning训练中间结果,需要从pytorch_model.bin加载字典,
# 或者直接加载模型
if model_args.ptuning_checkpoint is not None:
# Evaluation
# Loading extra state dict of prefix encoder
model = AutoModel.from_pretrained(model_args.model_name_or_path, config=config, trust_remote_code=True)
prefix_state_dict = torch.load(os.path.join(model_args.ptuning_checkpoint, "pytorch_model.bin"))
new_prefix_state_dict = {}
for k, v in prefix_state_dict.items():
if k.startswith("transformer.prefix_encoder."):
new_prefix_state_dict[k[len("transformer.prefix_encoder."):]] = v
model.transformer.prefix_encoder.load_state_dict(new_prefix_state_dict)
else:
model = AutoModel.from_pretrained(model_args.model_name_or_path, config=config, trust_remote_code=True)
训练量化精度配置
加载模型后,考虑到模型大小,占用显存大小,以及训练速度,需要配置模型数据精度,一般分为float32为浮点,f16半浮点精度,bf16半浮点精度,及int8,int4等整型8,4位精度。
# 量化处理,f16半浮点精度,即将pytorch默认的32位浮点型都改成16位浮点型。
if model_args.quantization_bit is not None:
print(f"Quantized to {model_args.quantization_bit} bit")
model = model.quantize(model_args.quantization_bit)
if model_args.pre_seq_len is not None:
# P-tuning v2
model = model.half()
model.transformer.prefix_encoder.float() #前缀编码器
else:
# Finetune
model = model.float()
数据预处理
数据集预处理,包括训练数据集,验证数据集,测试数据集。
prefix = data_args.source_prefix if data_args.source_prefix is not None else ""
# 数据集预处理,分词处理
# Preprocessing the datasets.
# We need to tokenize inputs and targets.
if training_args.do_train:
column_names = raw_datasets["train"].column_names
elif training_args.do_eval:
column_names = raw_datasets["validation"].column_names
elif training_args.do_predict:
column_names = raw_datasets["test"].column_names
else:
logger.info("There is nothing to do. Please pass `do_train`, `do_eval` and/or `do_predict`.")
return
# 输入数据(prompt),标注目标数据(response),上下文历史会话(history)
# Get the column names for input/target.
prompt_column = data_args.prompt_column
response_column = data_args.response_column
history_column = data_args.history_column
# 最大模板长度
# Temporarily set max_target_length for training.
max_target_length = data_args.max_target_length
训练数据集参数配置,最大训练样本数,数据预处理函数,GPU显卡数,批处理,cache处理等配置
# 训练前配置和数据集处理,训练,训练数据,最大训练样本数,
if training_args.do_train:
if "train" not in raw_datasets:
raise ValueError("--do_train requires a train dataset")
train_dataset = raw_datasets["train"]
if data_args.max_train_samples is not None:
max_train_samples = min(len(train_dataset), data_args.max_train_samples)
train_dataset = train_dataset.select(range(max_train_samples))
with training_args.main_process_first(desc="train dataset map pre-processing"):
train_dataset = train_dataset.map(
preprocess_function_train,
batched=True,
num_proc=data_args.preprocessing_num_workers,
remove_columns=column_names,
load_from_cache_file=not data_args.overwrite_cache,
desc="Running tokenizer on train dataset",
)
print_dataset_example(train_dataset[0])
同样方式,配置验证数据集,测试数据集参数。
# 训练前验证评价的配置和数据集处理,验证评价
if training_args.do_eval:
max_target_length = data_args.val_max_target_length
if "validation" not in raw_datasets:
raise ValueError("--do_eval requires a validation dataset")
eval_dataset = raw_datasets["validation"]
#最大验证样本数
if data_args.max_eval_samples is not None:
max_eval_samples = min(len(eval_dataset), data_args.max_eval_samples)
eval_dataset = eval_dataset.select(range(max_eval_samples))
#
with training_args.main_process_first(desc="validation dataset map pre-processing"):
eval_dataset = eval_dataset.map(
preprocess_function_eval,
batched=True,
num_proc=data_args.preprocessing_num_workers,
remove_columns=column_names,
load_from_cache_file=not data_args.overwrite_cache,
desc="Running tokenizer on validation dataset",
)
print_dataset_example(eval_dataset[0])
# 训练前预测的配置和数据集处理,预测
if training_args.do_predict:
max_target_length = data_args.val_max_target_length
if "test" not in raw_datasets:
raise ValueError("--do_predict requires a test dataset")
predict_dataset = raw_datasets["test"]
#最大预测样本
if data_args.max_predict_samples is not None:
max_predict_samples = min(len(predict_dataset), data_args.max_predict_samples)
predict_dataset = predict_dataset.select(range(max_predict_samples))
#数据集重映射
with training_args.main_process_first(desc="prediction dataset map pre-processing"):
predict_dataset = predict_dataset.map(
preprocess_function_eval,
batched=True,
num_proc=data_args.preprocessing_num_workers,
remove_columns=column_names,
load_from_cache_file=not data_args.overwrite_cache,
desc="Running tokenizer on prediction dataset",
)
print_dataset_example(predict_dataset[0])
数据分词,类型转换,pad等数据预处理操作
#训练准备,模型,数据加载
# Data collator
label_pad_token_id = -100 if data_args.ignore_pad_token_for_loss else tokenizer.pad_token_id
data_collator = DataCollatorForSeq2Seq(
tokenizer,
model=model,
label_pad_token_id=label_pad_token_id,
pad_to_multiple_of=None,
padding=False
)
序列训练参数配置,生成的length,beams配置,
# 序列训练参数配置,如有配置则重置解码参数
# 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.val_max_target_length
)
training_args.generation_num_beams = (
data_args.num_beams if data_args.num_beams is not None else training_args.generation_num_beams
)
训练器参数配置,预训练模型,训练超参数,相关数据集。分词器,训练度量器等过程参数配置。
#初始化训练参数,配置模型,训练参数,训练数据,分词器,数据加载工具,过程度量
# Initialize our Trainer
trainer = Seq2SeqTrainer(
model=model,
args=training_args,
train_dataset=train_dataset if training_args.do_train else None,
eval_dataset=eval_dataset if training_args.do_eval else None,
tokenizer=tokenizer,
data_collator=data_collator,
compute_metrics=compute_metrics if training_args.predict_with_generate else None,
save_changed=model_args.pre_seq_len is not None
)
执行训练过程,模型保存方式,过程度量及状态保存。
# 训练执行过程
# Training
if training_args.do_train:
checkpoint = None
if training_args.resume_from_checkpoint is not None:
checkpoint = training_args.resume_from_checkpoint
# elif last_checkpoint is not None:
# checkpoint = last_checkpoint
model.gradient_checkpointing_enable()
model.enable_input_require_grads()
train_result = trainer.train(resume_from_checkpoint=checkpoint)
# trainer.save_model() # Saves the tokenizer too for easy upload
metrics = train_result.metrics
max_train_samples = (
data_args.max_train_samples if data_args.max_train_samples is not None else len(train_dataset)
)
metrics["train_samples"] = min(max_train_samples, len(train_dataset))
trainer.log_metrics("train", metrics)
trainer.save_metrics("train", metrics)
trainer.save_state()
训练后,数据集评估模型效果,预测处理。
#训练结束后评估
# Evaluation
results = {}
max_seq_length = data_args.max_source_length + data_args.max_target_length + 1
# 训练评估
if training_args.do_eval:
logger.info("*** Evaluate ***")
metrics = trainer.evaluate(metric_key_prefix="eval", do_sample=True, top_p=0.7, max_length=max_seq_length, temperature=0.95)
max_eval_samples = data_args.max_eval_samples if data_args.max_eval_samples is not None else len(eval_dataset)
metrics["eval_samples"] = min(max_eval_samples, len(eval_dataset))
trainer.log_metrics("eval", metrics)
trainer.save_metrics("eval", metrics)
# 训练预测
if training_args.do_predict:
logger.info("*** Predict ***")
predict_results = trainer.predict(predict_dataset, metric_key_prefix="predict", max_length=max_seq_length, do_sample=True, top_p=0.7, temperature=0.95)
metrics = predict_results.metrics
max_predict_samples = (
data_args.max_predict_samples if data_args.max_predict_samples is not None else len(predict_dataset)
)
metrics["predict_samples"] = min(max_predict_samples, len(predict_dataset))
trainer.log_metrics("predict", metrics)
trainer.save_metrics("predict", metrics)
if trainer.is_world_process_zero():
if training_args.predict_with_generate:
predictions = tokenizer.batch_decode(
predict_results.predictions, skip_special_tokens=True, clean_up_tokenization_spaces=True
)
predictions = [pred.strip() for pred in predictions]
labels = tokenizer.batch_decode(
predict_results.label_ids, skip_special_tokens=True, clean_up_tokenization_spaces=True
)
labels = [label.strip() for label in labels]
output_prediction_file = os.path.join(training_args.output_dir, "generated_predictions.txt")
with open(output_prediction_file, "w", encoding="utf-8") as writer:
for p, l in zip(predictions, labels):
res = json.dumps({"labels": l, "predict": p}, ensure_ascii=False)
writer.write(f"{res}\n")
中文分词器,中文评价指标,数据集管理
pip install rouge_chinese nltk jieba datasets
依赖模块解读
transformers提供数千个预先训练好的模型来执行不同模式的任务,如文本、视觉和音频。
transformer提供api,可以快速下载,并在给定文本上使用这些预训练的模型,在您自己的数据集上对它们进行微调,然后在Huggingface的模型中心上与社区共享。同时,每个定义架构的python模块都是完全独立的,可以进行修改以进行快速研究实验。
# Transformers 工具已经帮我们封装了用于训练文本生成模型的 Seq2SeqTrainer 类,无需我们自己再去定义损失函数与优化方法了。
import transformers
from transformers import (
AutoConfig,
AutoModel,
AutoTokenizer,
DataCollatorForSeq2Seq,
HfArgumentParser,
Seq2SeqTrainingArguments,
set_seed,
)
Trainer 所有模型都是标准的torch.nn.Module,因此可以在任何典型的训练循环中使用它们。虽然您可以编写自己的训练迭代器,但Transformers为PyTorch提供了Trainer类,其中包含基本的训练循环,并添加了额外的功能,如分布式训练、混合精度等。
# Seq2SeqTrainingArguments 设置训练参数。
# 例如,下面设置训练参数,配置学习率为2e-5,训练时batch大小为8,验证时为batch大小32,训练轮数为5,权重衰减大小为0.01,输出文件夹为model_for_seq2seqlm,日志记录的步长为10,即10个batch记录一次;评估策略为训练完一个epoch之后进行评估,模型保存策略同上,设置训练完成后加载最优模型,并指定最优模型的评估指标为rougeL,最后,需要指定predict_with_generate参数值为True。
# training_args = Seq2SeqTrainingArguments(
# learning_rate=3e-5,
# per_device_train_batch_size=8,
# per_device_eval_batch_size=32,
# num_train_epochs=5,
# weight_decay=0.01,
# output_dir="model_for_seq2seqlm",
# logging_steps=10,
# evaluation_strategy = "epoch",
# save_strategy = "epoch",
# load_best_model_at_end=True,
# metric_for_best_model="rougeL",
# predict_with_generate=True # 训练最后会调用generate方法进行生成
# )
from trainer_seq2seq import Seq2SeqTrainer
#ModelArguments类为model/config/tokenizer涉及的参数
#DataTrainingArguments类为数据涉及到的参数
from arguments import ModelArguments, DataTrainingArguments
ADGEN数据集任务的数据形式,输入(content),生成输出(summary)
{
"content": "类型#上衣*版型#宽松*版型#显瘦*图案#线条*衣样式#衬衫*衣袖型#泡泡袖*衣款式#抽绳",
"summary": "这件衬衫的款式非常的宽松,利落的线条可以很好的隐藏身材上的小缺点,穿在身上有着很好的显瘦效果。领口装饰了一个可爱的抽绳,漂亮的绳结展现出了十足的个性,配合时尚的泡泡袖型,尽显女性甜美可爱的气息。"
}
觉得有用 点个赞 + 收藏 吧
End
GPT专栏文章:
GPT实战系列-P-Tuning本地化训练ChatGLM2等LLM模型,到底做了什么?(一)
GPT实战系列-ChatGLM3本地部署CUDA11+1080Ti+显卡24G实战方案
GPT实战系列-ChatGLM2部署Ubuntu+Cuda11+显存24G实战方案
决策引擎专栏:
Falcon构建轻量级的REST API服务