ChatGLM-Tuning/finetune.py 源码解析 ChatGLM LoRA tuning

from transformers.integrations import TensorBoardCallback
from torch.utils.tensorboard import SummaryWriter
from transformers import TrainingArguments
from transformers import Trainer, HfArgumentParser
from transformers import AutoTokenizer, AutoModel
import torch
import torch.nn as nn
from peft import get_peft_model, LoraConfig, TaskType
from dataclasses import dataclass, field
import datasets
import os


tokenizer = AutoTokenizer.from_pretrained("THUDM/chatglm-6b", trust_remote_code=True)


@dataclass
class FinetuneArguments:
    dataset_path: str = field(default="data/alpaca")
    model_path: str = field(default="output")
    lora_rank: int = field(default=8)


class CastOutputToFloat(nn.Sequential):
    def forward(self, x):
        return super().forward(x).to(torch.float32)


def data_collator(features: list) -> dict:
    len_ids = [len(feature["input_ids"]) for feature in features]
    longest = max(len_ids)
    input_ids = []
    labels_list = []
    for ids_l, feature in sorted(zip(len_ids, features), key=lambda x: -x[0]):
        ids = feature["input_ids"]
        seq_len = feature["seq_len"]
        labels = (
            [-100] * (seq_len - 1) + ids[(seq_len - 1) :] + [-100] * (longest - ids_l)
        )
        ids = ids + [tokenizer.pad_token_id] * (longest - ids_l)
        _ids = torch.LongTensor(ids)
        labels_list.append(torch.LongTensor(labels))
        input_ids.append(_ids)
    input_ids = torch.stack(input_ids)
    labels = torch.stack(labels_list)
    return {
        "input_ids": input_ids,
        "labels": labels,
    }


class ModifiedTrainer(Trainer):
    def compute_loss(self, model, inputs, return_outputs=False):
        return model(
            input_ids=inputs["input_ids"],
            labels=inputs["labels"],
        ).loss

    def save_model(self, output_dir=None, _internal_call=False):
        from transformers.trainer import TRAINING_ARGS_NAME

        os.makedirs(output_dir, exist_ok=True)
        torch.save(self.args, os.path.join(output_dir, TRAINING_ARGS_NAME))
        saved_params = {
            k: v.to("cpu") for k, v in self.model.named_parameters() if v.requires_grad
        }
        torch.save(saved_params, os.path.join(output_dir, "adapter_model.bin"))


def main():
    writer = SummaryWriter()
    finetune_args, training_args = HfArgumentParser(
        (FinetuneArguments, TrainingArguments)
    ).parse_args_into_dataclasses()

    # init model
    model = AutoModel.from_pretrained(
        "THUDM/chatglm-6b", load_in_8bit=True, trust_remote_code=True, device_map="auto"
    )
    model.gradient_checkpointing_enable()
    model.enable_input_require_grads()
    model.is_parallelizable = True
    model.model_parallel = True
    model.lm_head = CastOutputToFloat(model.lm_head)
    model.config.use_cache = (
        False  # silence the warnings. Please re-enable for inference!
    )

    # setup peft
    peft_config = LoraConfig(
        task_type=TaskType.CAUSAL_LM,
        inference_mode=False,
        r=finetune_args.lora_rank,
        lora_alpha=32,
        lora_dropout=0.1,
    )
    model = get_peft_model(model, peft_config)

    # load dataset
    dataset = datasets.load_from_disk(finetune_args.dataset_path)
    print(f"\n{len(dataset)=}\n")

    # start train
    trainer = ModifiedTrainer(
        model=model,
        train_dataset=dataset,
        args=training_args,
        callbacks=[TensorBoardCallback(writer)],
        data_collator=data_collator,
    )
    trainer.train()
    writer.close()
    # save model
    model.save_pretrained(training_args.output_dir)


if __name__ == "__main__":
    main()

1.from transformers.integrations import TensorBoardCallback

transformers.integrations代表什么

在Hugging Face的Transformers库中,transformers.integrations模块提供了与其他库集成的工具和函数。这些集成工具可以帮助用户更好地使用Transformers库,例如更简洁地构建训练流程,更好地监控训练过程等。

到我知识库更新为止(2021年9月),transformers.integrations模块中主要包含了与一些深度学习库的集成工具,比如与TensorBoard、PyTorch Lightning、Weights & Biases等的集成。

具体到这段代码中的TensorBoardCallback,这是一个回调函数,用于在训练过程中将模型的训练情况记录到TensorBoard中。这样,用户可以在TensorBoard的可视化界面中实时观察到模型训练过程中的各项指标,如损失函数值的变化情况、各项评估指标的变化情况等。

2.from torch.utils.tensorboard import SummaryWriter 里面的SummaryWriter是做什么的

SummaryWriter 是 PyTorch 的 torch.utils.tensorboard 模块中的一个类,它主要用于将模型训练过程中的信息(如损失值、准确率、参数分布等)写入到 TensorBoard 的日志文件中。TensorBoard 是一个可视化工具,可以用来查看模型训练过程中的各种信息。

SummaryWriter 的主要方法有:

  • add_scalar(tag, scalar_value, global_step=None, walltime=None): 添加一个标量(scalar)到 summary。

  • add_histogram(tag, values, global_step=None, bins='tensorflow', walltime=None, max_bins=None): 添加一个直方图到 summary。

  • add_image(tag, img_tensor, global_step=None, walltime=None, dataformats='CHW'): 添加一张图片到 summary。

  • add_graph(model, input_to_model=None, verbose=False): 添加一个模型的计算图到 summary。

在训练过程中,可以使用 SummaryWriter 将每一步或每个 epoch 的训练损失、验证损失、准确率等信息写入到 TensorBoard 的日志文件中。然后,在训练过程中或训练完成后,可以使用 TensorBoard 工具来查看这些信息,以便于理解模型训练的过程和效果,调整模型的参数和训练策略。

@dataclass 这个装饰器是做什么的

在Python中,@dataclass是一个装饰器,它用于自动生成一些特殊方法,如__init__()__repr__()__eq__()等,这些方法是在类中经常需要手动实现的。通过使用@dataclass装饰器,我们可以省略这些方法的编写,让代码更简洁、更易读。

具体来说,@dataclass装饰器作用于一个类,这个类的成员变量通常被称为“字段”。每个字段都由一个公有的类变量来定义,可以带有可选的类型注解和初始化值。

例如,下面的类定义:

 
 

pythonCopy code @dataclassclass Point: x: int y: int

在没有使用@dataclass装饰器的情况下,需要编写更多的代码:

 
 

pythonCopy code class Point: def __init__(self, x: int, y: int): self.x = x self.y = y

@dataclass装饰器会自动帮我们生成__init__()方法,大大减少了类定义的工作量。

另外,@dataclass装饰器还会生成__repr__()方法,这个方法返回一个字符串,描述这个类的实例。例如:

 
 

pythonCopy code @dataclassclass Point: x: int y: int p = Point(1, 2) print(p) # 输出:Point(x=1, y=2)

@dataclass装饰器还可以生成__eq__()方法,该方法用于比较两个类实例是否相等:

 
 

pythonCopy code @dataclassclass Point: x: int y: int p1 = Point(1, 2) p2 = Point(1, 2) print(p1 == p2) # 输出:True

以上,就是@dataclass装饰器的基本作用和用法。

class CastOutputToFloat(nn.Sequential): 这句代码是什么意思

这句代码定义了一个继承自nn.Sequential的新类CastOutputToFloat

在PyTorch中,nn.Sequential是一个包含多个网络层的容器,数据会按照这些层定义的顺序依次通过这些层。nn.Sequential的主要用途是简化网络结构的定义,让代码更加清晰。

在这个例子中,CastOutputToFloat类没有在初始化函数中添加任何层,所以它其实就是一个空的nn.Sequential

然后,这个类重写了父类的forward方法。forward方法定义了模型的前向传播过程。在这个forward方法中,它首先调用父类的forward方法(通过super().forward(x))进行前向传播,然后把输出转换为torch.float32类型。这就意味着,这个类的作用就是把通过它的数据转换为浮点类型。

for ids_l, feature in sorted(zip(len_ids, features), key=lambda x: -x[0]): 这里的for循环是什么意思,完成了什么功能

这里的for循环对输入的数据features进行遍历处理,生成模型训练所需的输入和标签数据

这个for循环首先通过zip(len_ids, features)len_ids(一个列表,包含所有特征的“input_ids”的长度)和features(一个列表,包含所有的特征)进行配对。

然后,通过sorted函数对这些配对进行排序。key=lambda x: -x[0]表示排序的关键字是每个配对的第一个元素的负数,也就是-len_ids[i]。这样,排序后的配对列表的元素会按照len_ids中的元素的大小逆序排列。

因此,这个for循环的作用是对features按照其对应的“input_ids”的长度从大到小进行遍历。对于每个特征feature,它执行以下步骤:

  • 提取feature的“input_ids”和“seq_len”;

  • 计算标签labels,标签的长度与最长的“input_ids”的长度相同,标签的前seq_len - 1个元素为-100,接下来的元素是feature["input_ids"][(seq_len - 1):],剩余的元素为-100;

  • feature的“input_ids”进行填充,使其长度与最长的“input_ids”的长度相同,填充的元素是tokenizer.pad_token_id

  • 将填充后的“input_ids”和对应的标签转换为PyTorch的LongTensor类型,并添加到对应的列表中。

通过这个for循环,我们可以得到两个列表input_idslabels,这两个列表中的元素分别是所有特征的填充后的“input_ids”和对应的标签,它们都是PyTorch的LongTensor类型,长度都与最长的“input_ids”的长度相同。这两个列表可以直接作为模型的输入和标签,用于模型的训练

猜你喜欢

转载自blog.csdn.net/sinat_37574187/article/details/131351286