文章目录
- 前言
- 一、huggingface的load_dataset
- 1、含义
- 2、数据加载json文件格式
- 3、load_dataset的简单demo
- 4、load_dataset的cache简单demo
- 二、huggingface的concatenate_datasets
- 三、huggingface的train_test_split数据划分
- 1、直接划分
- 2、使用合并的数据划分
- 四、huggingface的map的参数给定
- 1、使用fn_kwargs方法给定
- 2、使用partial方法给定
- 五、huggingface相关数据处理
- 1、input_ids与labels处理
- 2、huggingface的Data Collator方法
- 方法简单说明
- huggingface的DataCollatorForSeq2Seq使用
- 六、huggingface的data_loader方法
- 1、修改数据处理函数
- 2、DataCollatorForSeq2Seq构建类似collate_fn方法
- 3、完整源码
前言
清华智普的GLM-4v-9b模型,作为优化的多模态大模型,特别适用于国内应用场景,解决了国外模型本地化不足的问题。本专栏提供环境安装、数据处理、视觉与语言模型源码理解,并基于Hugging Face重构GLM模型搭建教程,帮助理解、修改和应用GLM墨西哥,指导搭建多模态大模型,帮助读者自由搭建与修改大模型。本节是对第二节数据处理理论补充与实列应用教程。
第一节:GLM-4v-9B大模型安装、推理与训练详细教程
第二节:GLM-4v-9B数据加载源码解读
第三节:GLM-4v-9B数据加载之huggingface数据加载方法教程(通用大模型数据加载实列)
第四节:GLM-4v-9b模型的tokenizer源码解读
第五节:GLM-4v-9b模型model加载源码解读(模型相关参数方法解读)
第六节:GLM-4v-9b模型加载源码解读(模型加载方法解读)
第七节:GLM-4v-9b模型的视觉模型源码解读
第八节:GLM-4v-9b模型的大语言模型源码解读(ChatGLMForConditionalGeneration)
第九节:通过Debug解析ChatGLMForConditionalGeneration的数据流,理解GLM-4v-9b模型架构
第十节:通过Debug解析ChatGLMModel的数据流,理解视觉与语言模型结合架构
第十一节:利用huggingface重构GLM-4v-9B模型数据处理代码Demo
第十二节:利用huggingface重构GLM-4v-9B训练模型代码Demo
第十一、十二节是在理解GLM-4v-9B模型后,使用huggignface重新构建/搭建GLM-4v-9B模型,使读者能自由构建多模态大模型!
本节基于huggingface训练模式构造自己的数据,类似与torch的dataset与dataloader方式来构建大语言模型数据作为列子,使其透彻理解与后面模型搭建做铺垫。本节内容包含load_dataset载入数据、dataset.map映射数据、concatenate_datasets连接多个数据文件方法、train_test_split如何分割成训练与验证集方法,以及如何构建类似torch的dataloader方式中的collate_fn函数等方法。
一、huggingface的load_dataset
1、含义
load_dataset 是 Hugging Face Datasets 库中的一个函数,用于加载各种格式的数据集。这个函数可以处理多种数据源,包括本地文件、远程 URL、CSV 文件、JSON 文件等,并且支持自动下载和缓存数据集。
from datasets import load_dataset
# 加载本地或远程的 JSON 文件
dataset = load_dataset("json", data_files="path/to/your/file.json")
参数说明:
path: 可以是一个文件路径(本地或远程),也可以是一个包含多个文件路径的字典(例如 train: "train.json", test: "test.json")。
split: 指定要从数据集中加载哪个子集(默认为 'train')。
features: 可以定义数据集的特征结构,这对于类型转换非常有用。
其他参数:还有一些其他参数可以用来控制加载过程,如缓存策略等。
示例:使用 JSON 文件创建数据集
2、数据加载json文件格式
假设你有一个 JSON 文件 data.json,其内容如下:
[{"instruction": "鸡柳是鸡身上哪个部位啊?","input": "","output": "鸡柳其实并不是指鸡禽类的一个自然解剖部位,而是对鸡胸肉或者是部分鸡里肌肉进行切割、腌制和加工后形成的一种肉条食品。它口感细腻,富有弹性,常见于快餐、烤肉和各类小吃中。"},{"instruction": "樟脑丸是我吃过最难吃的硬糖有奇怪的味道怎么还有人买","input": "","output": "樟脑丸其实不是用来吃的,它是一种驱虫剂,常用于衣柜和衣物的防蛀。樟脑有一种特殊的味道,用于驱赶蛾类和其他害虫,以保护衣物免受虫害。因此,所谓的“樟脑丸是硬糖”是一种误解,是因为它的外形类似于糖果而已。误食樟脑丸是危险的,会引起中毒,所以千万不要将它误认为糖果。"},...
]
3、load_dataset的简单demo
接下来,你可以使用以下 Python 代码来加载并处理这个 JSON 文件:
import datasets
def process_example(example):# 在这里可以对每一条数据进行预处理,比如添加更多的字段或者做一些转换# example是数据每条记录,我这里简要处理下内容input_ids = example['instruction']+example['input']+example['output']labels = input_idsreturn {"input_ids": input_ids, "label":labels}def llama3_load_dataset_demo():'''数据格式:{"instruction": "鸡柳是鸡身上哪个部位啊?","input": "","output": "鸡柳其实并不是指鸡禽类的一个自然解剖部位,而是对鸡胸肉或者是部分鸡里肌肉进行切割、腌制和加工后形成的一种肉条食品。它口感细腻,富有弹性,常见于快餐、烤肉和各类小吃中。"},'''data_path = '/Chinese-LLaMA-Alpaca-3-main/data/ruozhiba_qa2449_gpt4o.json'dataset = datasets.load_dataset("json", data_files=data_path)# 使用 map 方法处理数据processed_dataset = dataset.map(process_example)# 输出前三条结果print(processed_dataset['train'][:1])
在这个例子中,我们首先加载了一个 JSON 文件,并将其转换为 Hugging Face 的 Dataset 对象。然后我们定义了一个函数 process_example 来处理每一条数据。最后使用 map 方法将这个函数应用到数据集中的每一个样本上。
如果你的 JSON 文件很大,你还可以通过设置 batched=True 来批量处理数据,这样可以更高效地进行数据转换。
希望这可以帮助你理解如何使用 load_dataset 函数以及如何进一步处理数据集!如果有任何具体问题,请随时告诉我。
输出结果:
4、load_dataset的cache简单demo
同样使用上面的json,其代码如下:
def llama3_cache_load_dataset_demo():data_path = '/Chinese-LLaMA-Alpaca-3-main/data/ruozhiba_qa2449_gpt4o.json' cache_dir = "./data_out_dir"os.makedirs(cache_dir, exist_ok=True)dataset = datasets.load_dataset("json", data_files=data_path, cache_dir=cache_dir)# 使用 map 方法处理数据processed_dataset = dataset.map(process_example,load_from_cache_file=True # 控制是否从缓存加载数据)# 保存处理后的数据集到磁盘processed_dataset.save_to_disk(cache_dir)# 如果需要从缓存加载数据集cached_dataset = datasets.load_from_disk(cache_dir)
注:不使用processed_dataset.save_to_disk(cache_dir)也会保存。
其结果如下:
代码说明:
1、使用 map 方法处理数据, map 方法将处理函数应用到数据集上的每个样本。其中:
cache_file_name 参数指定了缓存文件的名称。
load_from_cache_file 参数设置为 True 表示如果缓存文件存在,则直接从缓存加载数据,否则执行处理函数并将结果保存到缓存文件中。
2、关于缓存机制,当你第一次运行 map 方法时,处理后的数据会被保存到指定的缓存文件中。下次运行相同的 map 方法时,如果缓存文件存在并且 load_from_cache_file 设置为 True,则会直接从缓存文件中加载数据,从而避免重新执行处理函数。
3、如果你修改了处理函数或者需要更新缓存文件,可以通过设置 load_from_cache_file=False 来强制重新处理数据。
cache特点:
缓存机制是 Hugging Face Datasets 库中的一个重要特性,它可以显著提高数据处理和模型训练的速度,特别是在处理大型数据集时。
以下是缓存机制的一些主要优势:1、减少重复计算,当你对数据集执行相同的操作时,缓存机制可以避免重复执行相同的计算任务,而是直接从缓存中加载已处理的数据。这对于耗时的操作特别有用,例如对文本进行预处理或标记化。2、加速数据加载,数据集一旦被处理并缓存,下一次加载时可以直接从磁盘读取已处理的数据,而不是重新执行数据处理逻辑。这对于大型数据集尤其重要,
因为读取已处理的数据比从原始文件中读取并处理数据要快得多。
3、节省计算资源,由于减少了重复计算,因此也减少了计算资源的消耗。这有助于降低运行成本,尤其是在云端环境或高性能计算集群中。
### ④、简化工作流程
缓存机制使得数据处理步骤更加自动化和无缝连接。
你不需要手动管理中间结果,也不需要担心数据集的状态管理。
4、易于调试和版本控制,通过缓存机制,你可以更容易地回溯到数据集的特定版本,这对于调试和实验非常有帮助。如果你需要重现某个实验结果,可以从缓存中加载对应的数据集版本。
5、支持分布式处理,当数据集非常大时,可以使用分布式系统来处理数据集的不同部分。缓存机制允许你在不同的机器上处理数据集的一部分,然后将结果合并到一起。
6、便于数据共享,缓存的数据集可以在不同的用户之间共享,而不需要每个人都重新处理数据。这对于协作项目尤其有用,团队成员可以快速获取已处理的数据集。7、如何使用缓存,当使用 map, filter, sort, group_by 等方法时,可以指定 cache_file_name 和 load_from_cache_file 参数来启用缓存。
cache_file_name 指定了缓存文件的名称。特别地,load_from_cache_file 如果设为 True,则会在缓存文件存在时直接从该文件加载数据;如果设为 False,则会重新处理数据并覆盖缓存文件。
缓存机制是 Hugging Face Datasets 库的一个强大特性,能够极大地提高数据处理的效率和便利性。希望这些信息对你有所帮助!如果有更多问题或需要进一步的解释,请随时提问。
二、huggingface的concatenate_datasets
concatenate_datasets 是 Hugging Face Datasets 库中的一个函数,用于合并两个或多个数据集。当你需要将多个数据集合并成一个更大的数据集时,这个函数非常有用。
接下来我写一个train的数据合并方法,其代码如下:
def llama3_cat_load_dataset_demo():data_path1 = '/Chinese-LLaMA-Alpaca-3-main/data/ruozhiba_qa2449_gpt4o.json' data_path2='/Chinese-LLaMA-Alpaca-3-main/data/ruozhiba_qa2449_gpt4t.json'cache_dir = "./data_out_dir"os.makedirs(cache_dir, exist_ok=True)dataset1 = datasets.load_dataset("json", data_files=data_path1, cache_dir=cache_dir)dataset2 = datasets.load_dataset("json", data_files=data_path2, cache_dir=cache_dir)# 使用 map 方法处理数据processed_dataset1 = dataset1.map(process_example,load_from_cache_file=True # 控制是否从缓存加载数据)processed_dataset2 = dataset1.map(process_example,load_from_cache_file=True # 控制是否从缓存加载数据)# 只合并train训练的数据集merged_dataset = datasets.concatenate_datasets([processed_dataset1["train"], processed_dataset2["train"]])# 保存处理后的数据集到磁盘merged_dataset.save_to_disk(cache_dir)# 如果需要从缓存加载数据集cached_dataset = datasets.load_from_disk(cache_dir)print(cached_dataset)
其结果如下:
三、huggingface的train_test_split数据划分
1、直接划分
使用train_test_split直接划分代码如下:
def llama3_split_direct_load_dataset_demo():data_path = '/Chinese-LLaMA-Alpaca-3-main/data/ruozhiba_qa2449_gpt4o.json' cache_dir = "./data_out_dir"os.makedirs(cache_dir, exist_ok=True)dataset = datasets.load_dataset("json", data_files=data_path, cache_dir=cache_dir)# 使用 map 方法处理数据processed_dataset = dataset.map(process_example,load_from_cache_file=True # 控制是否从缓存加载数据)split_dataset = processed_dataset['train'].train_test_split(test_size=0.2)print(split_dataset['train'][0])
注:使用是processed_dataset['train']的train!!!
结果如下:
2、使用合并的数据划分
合并后划分数据代码如下:
def llama3_split_load_dataset_demo():data_path1 = '/Chinese-LLaMA-Alpaca-3-main/data/ruozhiba_qa2449_gpt4o.json' data_path2='/Chinese-LLaMA-Alpaca-3-main/data/ruozhiba_qa2449_gpt4t.json'cache_dir = "./data_out_dir"os.makedirs(cache_dir, exist_ok=True)dataset1 = datasets.load_dataset("json", data_files=data_path1, cache_dir=cache_dir)dataset2 = datasets.load_dataset("json", data_files=data_path2, cache_dir=cache_dir)# 使用 map 方法处理数据processed_dataset1 = dataset1.map(process_example,load_from_cache_file=True # 控制是否从缓存加载数据)processed_dataset2 = dataset1.map(process_example,load_from_cache_file=True # 控制是否从缓存加载数据)# 只合并train训练的数据集merged_dataset = datasets.concatenate_datasets([processed_dataset1["train"], processed_dataset2["train"]])split_dataset = merged_dataset.train_test_split(test_size=0.2)train_dataset,val_dataset = split_dataset['train'],split_dataset['test']print(train_dataset[0])print(val_dataset[0] )
注:使用是datasets.concatenate_datasets([processed_dataset1["train"], processed_dataset2["train"]])的train!!!
结果如下图:
四、huggingface的map的参数给定
1、使用fn_kwargs方法给定
当然,数据处理也可能需要某些传入参数,那么可使用以下方法给定,代码如下:
def llama3_split_param_load_dataset_demo():data_path = '/extend_disk/disk3/tj/language_model/Chinese-LLaMA-Alpaca-3-main/data/ruozhiba_qa2449_gpt4o.json' cache_dir = "./data_out_dir"os.makedirs(cache_dir, exist_ok=True)dataset = datasets.load_dataset("json", data_files=data_path, cache_dir=cache_dir)def process_example(example,other_param=None):# 在这里可以对每一条数据进行预处理,比如添加更多的字段或者做一些转换# example是数据每条记录,我这里简要处理下内容input_ids = example['instruction']+example['input']+example['output']labels = input_idsif other_param is not None:return {"input_ids": input_ids, "label":labels,'other_param':other_param}return {"input_ids": input_ids, "label":labels}# 使用 map 方法处理数据processed_dataset = dataset.map(process_example,fn_kwargs={"other_param": '存在其它参数'},load_from_cache_file=True # 控制是否从缓存加载数据)split_dataset = processed_dataset['train'].train_test_split(test_size=0.2)print(split_dataset['train'][0])
其结果如下:
2、使用partial方法给定
其示例代码如下:
preprocess_function = partial(preprocess_function_, tokenizer=tokenizer,max_source_length=max_source_length,max_target_length=max_target_length,prompt_column='q',response_column='a',history_column=None,ignore_pad_token_for_loss=-100)
dataset = dataset.map(function=preprocess_function,batched=True,desc="Running tokenizer on train dataset",remove_columns=['q', 'a'],num_proc=10)
五、huggingface相关数据处理
1、input_ids与labels处理
这里我以语言模型为例,huggingface训练输入input_ids与labels对应的id是一样的,只是再计算loss时候,模型输出减少了最后一位,而labesl去掉开始一个位置,以此达到了错位计算loss的目的,具体可查看如下图示。
2、huggingface的Data Collator方法
方法简单说明
huggingface的Data Collator官网链接: https://huggingface.co/docs/transformers/v4.44.0/en/main_classes/data_collator#transformers.DataCollatorForLanguageModeling
Data collators are objects that will form a batch by using a list of dataset elements as input. These elements are of the same type as the elements of train_dataset or eval_dataset.
在使用 Hugging Face 的 Trainer 进行训练时,数据的 padding(填充)通常是在数据集的 collate_fn 中实现的。这是因为 Trainer 需要将一批数据集样本聚合在一起作为一个批次,而这些样本可能具有不同的长度,因此需要对较短的序列进行填充以使它们达到相同的长度。
huggingface的DataCollatorForSeq2Seq使用
假如使用下面方法做类似datasset内容,其代码如下:
def process_data_function(example,tokenizer,max_length):input_ids = example['instruction']+example['input']+example['output']input_ids = tokenizer.encode(input_ids)attention_mask = [1]*len(input_ids)labels = input_idsmodel_input={'input_ids':input_ids,"attention_mask":attention_mask,"labels":labels}return model_input
model_input={'input_ids':input_ids,"attention_mask":attention_mask,"labels":labels}可提供attention_mask也可不提供
再结合以下代码来调用,也可实现data_loader方法,其代码如下:
os.makedirs(cache_dir, exist_ok=True)
dataset = datasets.load_dataset("json", data_files=data_path, cache_dir=cache_dir)
max_length=1024
# 使用 map 方法处理数据
train_dataset = dataset.map(process_data_function,fn_kwargs={"tokenizer": tokenizer,"max_length":max_length},load_from_cache_file=True # 控制是否从缓存加载数据)# 创建数据整理器data_collator = DataCollatorForSeq2Seq(tokenizer=tokenizer,model=model,padding=True, # 默认为True,就是多个batch时候选用最大作为长度来填充max_length=max_length, # 最大长度,貌似没啥用# pad_to_multiple_of=None, # 不需要填充到某个数的倍数label_pad_token_id=-100, # 标签中用来表示填充的标记ID)
我们这里采用huggingface库中DataCollatorForSeq2Seq方法做data_loader,可以说类似collate_fn函数功能。
此时,你需要采用huggingface的trainer方法来调用,其代码如下:
trainer = Trainer(model=model,args=training_args,train_dataset=train_dataset['train'] ,tokenizer=tokenizer,data_collator=data_collator,)train_result = trainer.train()
而最终结果如下:
当然上面因数据未能与llama3输入1024个token对齐,会报错,但数据处理没啥问题的,为适应llama3模型,我下面我将继续修改数据载入方法。
六、huggingface的data_loader方法
1、修改数据处理函数
为使用DataCollatorForSeq2Seq
方法,为适应llama3,我们需要修改dataset数据处理方式,其代码如下:
def process_data_function(example,tokenizer,max_length):input_ids = example['instruction']+example['input']+example['output'] # 把对话文本连接起来input_ids = tokenizer.encode(input_ids)text_length=len(input_ids)attention_mask = [1]*text_length+[0]*(max_length-text_length) if max_length>text_length else ([1]*text_length)[:max_length]input_ids =input_ids+[0]*(max_length-text_length) if max_length>text_length else input_ids[:max_length]labels = input_ids+[-100]*(max_length-text_length) if max_length>text_length else input_ids[:max_length]model_input={'input_ids':input_ids,"attention_mask":attention_mask,"labels":labels}return model_input
2、DataCollatorForSeq2Seq构建类似collate_fn方法
随后,与上面类似,我将直接给出代码,不解释了,如下:
data_path = '/Chinese-LLaMA-Alpaca-3-main/data/ruozhiba_qa2449_gpt4o.json'
cache_dir = "./data_out_dir"
os.makedirs(cache_dir, exist_ok=True)
dataset = datasets.load_dataset("json", data_files=data_path, cache_dir=cache_dir)max_length=1024
# 使用 map 方法处理数据
train_dataset = dataset.map(process_data_function,fn_kwargs={"tokenizer": tokenizer,"max_length":max_length},load_from_cache_file=True # 控制是否从缓存加载数据)# 创建数据整理器
data_collator = DataCollatorForSeq2Seq(tokenizer=tokenizer,model=model,padding=True, # 默认为True,就是多个batch时候选用最大作为长度来填充max_length=max_length, # 最大长度,貌似没啥用# pad_to_multiple_of=None, # 不需要填充到某个数的倍数label_pad_token_id=-100, # 标签中用来表示填充的标记ID)# 训练设置部分梯度,这部分为了实验而写的
model.requires_grad_(False)
for i,param in enumerate(model.parameters()):if i in [8,9,10]:param.requires_grad_(True)# 设置训练参数trainer = Trainer(model=model,args=training_args,train_dataset=train_dataset['train'] ,tokenizer=tokenizer,data_collator=data_collator,
)trainer.train()
注:特别说明Trainer中的data_collator=data_collator,参数可以是一个函数,就看成是collate_fn函数,也是对batch做处理的,而dataset.map的数据处理process_data_function函数就是对每个列子做处理。
3、完整源码
import logging
import numpy as np
import math
import os
import sys
from dataclasses import dataclass, field
from itertools import chain
from typing import Optional, List, Dict, Any, Mapping
from pathlib import Path
import datasets
import torch
from datasets import load_dataset, concatenate_datasetsimport transformers
from transformers import (CONFIG_MAPPING,MODEL_FOR_CAUSAL_LM_MAPPING,AutoConfig,AutoModelForCausalLM,AutoTokenizer,HfArgumentParser,Trainer,TrainingArguments,is_torch_xla_available,set_seed,BitsAndBytesConfig,DataCollatorForSeq2Seq
)
from transformers.testing_utils import CaptureLogger
from transformers.trainer_utils import get_last_checkpoint
from transformers.utils import check_min_version, send_example_telemetry
from transformers.utils.versions import require_version
from sklearn.metrics import accuracy_score
from peft import LoraConfig, TaskType, get_peft_model, PeftModel, prepare_model_for_kbit_training@dataclass
class ModelArguments:"""Arguments pertaining to which model/config/tokenizer we are going to fine-tune, or train from scratch."""model_name_or_path: Optional[str] = field(default=None,metadata={"help": ("The model checkpoint for weights initialization. Don't set if you want to train a model from scratch.")},)tokenizer_name_or_path: Optional[str] = field(default=None,metadata={"help": ("The tokenizer for weights initialization.Don't set if you want to train a model from scratch.")},)model_type: Optional[str] = field( default=None )config_overrides: Optional[str] = field(default=None,metadata={"help": ("Override some existing default config settings when a model is trained from scratch. Example: ""n_embd=10,resid_pdrop=0.2,scale_attn_weights=false,summary_type=cls_index")},)config_name: Optional[str] = field(default=None, metadata={"help": "Pretrained config name or path if not the same as model_name"})tokenizer_name: Optional[str] = field(default=None, metadata={"help": "Pretrained tokenizer name or path if not the same as model_name"})cache_dir: Optional[str] = field(default=None,metadata={"help": "Where do you want to store the pretrained models downloaded from huggingface.co"},)use_fast_tokenizer: bool = field(default=False,metadata={"help": "Whether to use one of the fast tokenizer (backed by the tokenizers library) or not."},)model_revision: str = field(default="main",metadata={"help": "The specific model version to use (can be a branch name, tag name or commit id)."},)use_auth_token: bool = field(default=False,metadata={"help": ("Will use the token generated when running `huggingface-cli login` (necessary to use this script ""with private models).")},)torch_dtype: Optional[str] = field(default=None,metadata={"help": ("Override the default `torch.dtype` and load the model under this dtype. If `auto` is passed, the ""dtype will be automatically derived from the model's weights."),"choices": ["auto", "bfloat16", "float16", "float32"],},)low_cpu_mem_usage: bool = field(default=False,metadata={"help": ("It is an option to create the model as an empty shell, then only materialize its parameters when the pretrained weights are loaded. ""set True will benefit LLM loading time and RAM consumption.")},)def __post_init__(self):if self.config_overrides is not None and (self.config_name is not None or self.model_name_or_path is not None):raise ValueError("--config_overrides can't be used in combination with --config_name or --model_name_or_path")@dataclass
class DataTrainingArguments:"""Arguments pertaining to what data we are going to input our model for training and eval."""dataset_dir: Optional[str] = field(default=None, metadata={"help": "The name of the dataset to use (via the datasets library)."})dataset_config_name: Optional[str] = field(default=None, metadata={"help": "The configuration name of the dataset to use (via the datasets library)."})train_file: Optional[str] = field(default=None, metadata={"help": "The input training data file (a text file)."})validation_file: Optional[str] = field(default=None,metadata={"help": "An optional input evaluation data file to evaluate the perplexity on (a text file)."},)max_train_samples: Optional[int] = field(default=None,metadata={"help": ("For debugging purposes or quicker training, truncate the number of training examples to this ""value if set.")},)max_eval_samples: Optional[int] = field(default=None,metadata={"help": ("For debugging purposes or quicker training, truncate the number of evaluation examples to this ""value if set.")},)streaming: bool = field(default=False, metadata={"help": "Enable streaming mode"})block_size: Optional[int] = field(default=None,metadata={"help": ("Optional input sequence length after tokenization. ""The training dataset will be truncated in block of this size for training. ""Default to the model max input length for single sentence inputs (take into account special tokens).")},)overwrite_cache: bool = field(default=False, metadata={"help": "Overwrite the cached training and evaluation sets"})validation_split_percentage: Optional[float] = field(default=0.05,metadata={"help": "The percentage of the train set used as validation set in case there's no validation split"},)preprocessing_num_workers: Optional[int] = field(default=None,metadata={"help": "The number of processes to use for the preprocessing."},)keep_linebreaks: bool = field(default=True, metadata={"help": "Whether to keep line breaks when using TXT files or not."})data_cache_dir: Optional[str] = field(default="./", metadata={"help": "The datasets processed stored"})def __post_init__(self):if self.streaming:require_version("datasets>=2.0.0", "The streaming feature requires `datasets>=2.0.0`")@dataclass
class TrainingArguments(TrainingArguments):trainable : Optional[str] = field(default="q_proj, v_proj") # lora 训练参数lora_rank : Optional[int] = field(default=8)lora_dropout : Optional[float] = field(default=0.1)lora_alpha : Optional[float] = field(default=32.)modules_to_save : Optional[str] = field(default=None)debug_mode : Optional[bool] = field(default=False)peft_path : Optional[str] = field(default=None)use_flash_attention_2 : Optional[bool] = field(default=False)double_quant: Optional[bool] = field(default=True)quant_type: Optional[str] = field(default="nf4")load_in_kbits: Optional[int] = field(default=16)full_finetuning : Optional[bool] = field(default=False)use_lora : Optional[bool] = field(default=True)logger = logging.getLogger(__name__)def process_data_function(example,tokenizer,max_length):input_ids = example['instruction']+example['input']+example['output']input_ids = tokenizer.encode(input_ids) text_length=len(input_ids) attention_mask = [1]*text_length+[0]*(max_length-text_length) if max_length>text_length else ([1]*text_length)[:max_length]input_ids =input_ids+[0]*(max_length-text_length) if max_length>text_length else input_ids[:max_length]labels = input_ids+[-100]*(max_length-text_length) if max_length>text_length else input_ids[:max_length]model_input={'input_ids':input_ids,"attention_mask":attention_mask,"labels":labels}return model_inputdef train():set_seed(seed=1) # 设置seed parser = HfArgumentParser((ModelArguments, DataTrainingArguments, TrainingArguments)) # 获得参数 model_args, data_args, training_args = parser.parse_args_into_dataclasses()# Setup logginglogging.basicConfig(format="%(asctime)s - %(levelname)s - %(name)s - %(message)s",datefmt="%m/%d/%Y %H:%M:%S",level=logging.INFO, # if training_args.local_rank in [-1, 0] else logging.WARN,handlers=[logging.StreamHandler(sys.stdout)],)device_map = {"":int(os.environ.get("LOCAL_RANK") or 0)}config = AutoConfig.from_pretrained(model_args.model_name_or_path)model = AutoModelForCausalLM.from_pretrained(model_args.model_name_or_path,from_tf=bool(".ckpt" in model_args.model_name_or_path),config=config,cache_dir=model_args.cache_dir,revision=model_args.model_revision,use_auth_token=True if model_args.use_auth_token else None,torch_dtype=torch.float16 ,low_cpu_mem_usage=model_args.low_cpu_mem_usage,device_map=device_map,quantization_config=None,attn_implementation="flash_attention_2" if training_args.use_flash_attention_2 else "sdpa")tokenizer = AutoTokenizer.from_pretrained(model_args.model_name_or_path)tokenizer.add_special_tokens({'pad_token': '[PAD]'})tokenizer.pad_token_id = 0#**********************************处理数据*************************************data_path = '/Chinese-LLaMA-Alpaca-3-main/data/ruozhiba_qa2449_gpt4o.json' cache_dir = "./data_out_dir"os.makedirs(cache_dir, exist_ok=True)dataset = datasets.load_dataset("json", data_files=data_path, cache_dir=cache_dir)max_length=1024# 使用 map 方法处理数据train_dataset = dataset.map(process_data_function,fn_kwargs={"tokenizer": tokenizer,"max_length":max_length},load_from_cache_file=True # 控制是否从缓存加载数据)# 创建数据整理器data_collator = DataCollatorForSeq2Seq(tokenizer=tokenizer,model=model,padding=True, # 默认为True,就是多个batch时候选用最大作为长度来填充max_length=max_length, # 最大长度,貌似没啥用# pad_to_multiple_of=None, # 不需要填充到某个数的倍数label_pad_token_id=-100, # 标签中用来表示填充的标记ID)# 训练设置部分梯度,这部分为了实验而写的model.requires_grad_(False)for i,param in enumerate(model.parameters()):if i in [8,9,10]:param.requires_grad_(True)# 设置训练参数trainer = Trainer(model=model,args=training_args,train_dataset=train_dataset['train'] ,tokenizer=tokenizer,data_collator=data_collator,)trainer.train()