项目实训7-SFM、RM、PPO部分的实现

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 数量,则优先使用它;否则,使用训练参数中的值。

而在模型建立上采用了与继承自之前的PeftTrainerSeq2SeqPeftTrainer类,主要用于计算生成任务的评估指标,如 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这个方法用于在生成的预测结果中移除提示部分的令牌。调用父类 PeftTrainerprediction_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 进行自定义处理,进一步控制生成文本的质量和内容。

RM部分

奖励模型(Reward Model) 是在强化学习中用于评估和打分模型输出的模型,根据输出的质量给予奖励或惩罚,从而指导模型进行优化。

功能

  • 评估输出质量:奖励模型根据预定义的标准评估模型的输出质量,给予高质量输出高奖励,低质量输出低奖励或惩罚。
  • 指导优化方向:通过奖励机制,模型能够了解哪些输出是符合预期的,从而在训练过程中逐步提高输出质量。
# coding=utf-8
# Implements parameter-efficient training of reward models.
# This code is inspired by:
# https://github.com/lvwerra/trl/blob/main/examples/summarization/scripts/reward_summarization.py
# https://github.com/CarperAI/trlx/blob/main/examples/summarize_rlhf/reward_model/train_reward_model_gptj.py


from utils import (
    PairwiseDataCollatorWithPadding,
    PairwisePeftTrainer,
    LogCallback,
    load_pretrained,
    prepare_args,
    prepare_data,
    preprocess_data,
    compute_accuracy,
    plot_loss
)

def main():

    # Prepare pretrained model and dataset
    model_args, data_args, training_args, finetuning_args = prepare_args(stage="rm")
    dataset = prepare_data(model_args, data_args)
    model, tokenizer = load_pretrained(model_args, finetuning_args, training_args.do_train, stage="rm")
    dataset = preprocess_data(dataset, tokenizer, data_args, training_args, stage="rm")
    data_collator = PairwiseDataCollatorWithPadding(tokenizer)

    training_args.remove_unused_columns = False # important for pairwise dataset

    # 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 = PairwisePeftTrainer(
        finetuning_args=finetuning_args,
        model=model,
        args=training_args,
        tokenizer=tokenizer,
        data_collator=data_collator,
        callbacks=[LogCallback()],
        compute_metrics=compute_accuracy,
        **trainer_kwargs
    )

    # 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")
        trainer.log_metrics("eval", metrics)
        trainer.save_metrics("eval", metrics)


def _mp_fn(index):
    # For xla_spawn (TPUs)
    main()


if __name__ == "__main__":
    main()

该代码的结构与PT_Train的结构相似,无非是将模型的选择替换为继承自PeftTrainerPairwisePeftTrainer,并且将training_args.remove_unused_columns 设置为 False,这是为了保留数据集中所有的列,对于成对数据集来说很重要。而在模型方面,PairwisePeftTrainer的主要功能是实现成对损失(pairwise loss)的计算,用于训练奖励模型。在奖励模型的训练中,成对损失用于比较两个候选文本的好坏,并根据比较结果调整模型参数。此外与PeftTrainer 无异。

class PairwisePeftTrainer(PeftTrainer):
    r"""
    Inherits PeftTrainer to compute pairwise loss.
    """

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.can_return_loss = True # override property to return eval_loss

    def compute_loss(self, model, inputs, return_outputs=False):
        r"""
        Computes pairwise loss. The first n examples are chosen and the last n examples are rejected.

        We use score on the EOS token to represent reward of the whole sentence.

        Subclass and override to inject custom behavior. It should not be directly used by external scripts.

        Note that the first element will be removed from the output tuple.

        See: https://github.com/huggingface/transformers/blob/v4.30.2/src/transformers/trainer.py#L3509
        """
        batch_size = inputs["input_ids"].size(0) // 2
        _, _, values = model(**inputs)
        r_accept, r_reject = values[:, -1].split(batch_size, dim=0)
        loss = -torch.log(torch.sigmoid(r_accept - r_reject)).mean()
        return (loss, [loss, r_accept, r_reject]) if return_outputs else loss

PPO部分

近端策略优化(Proximal Policy Optimization) 是一种强化学习算法,用于优化策略,使得在保持策略更新稳定性的同时,提升模型性能。

策略优化:PPO通过对策略进行限制性的更新,防止策略发生过大的变化,从而保持训练的稳定性。

提高样本效率:PPO通过多次使用采样的数据进行优化,提高样本的使用效率。

稳定训练:通过引入KL散度(Kullback-Leibler divergence)或剪切策略(Clipped Surrogate Objective),控制策略更新的幅度,防止训练不稳定。

# coding=utf-8
# Implements parameter-efficient PPO training of fine-tuned models.
# This code is inspired by:
# https://github.com/lvwerra/trl/blob/main/examples/sentiment/scripts/gpt-neox-20b_peft/gpt-neo-20b_sentiment_peft.py

import math

from torch.optim import AdamW
from transformers.optimization import get_scheduler
from trl import PPOConfig

from utils import (
    DynamicDataCollatorWithPadding,
    PPOPeftTrainer,
    LogCallback,
    load_pretrained,
    prepare_args,
    prepare_data,
    preprocess_data,
    plot_loss
)


def main():

    # Prepare pretrained model and dataset
    model_args, data_args, training_args, finetuning_args = prepare_args(stage="ppo")
    dataset = prepare_data(model_args, data_args)
    model, tokenizer = load_pretrained(model_args, finetuning_args, training_args.do_train, stage="ppo")
    dataset = preprocess_data(dataset, tokenizer, data_args, training_args, stage="ppo")
    data_collator = DynamicDataCollatorWithPadding(tokenizer)

    ppo_config = PPOConfig(
        model_name=model_args.model_name_or_path,
        learning_rate=training_args.learning_rate,
        mini_batch_size=training_args.per_device_train_batch_size,
        batch_size=training_args.per_device_train_batch_size,
        gradient_accumulation_steps=training_args.gradient_accumulation_steps,
        ppo_epochs=1,
        max_grad_norm=training_args.max_grad_norm
    )

    optimizer = AdamW(filter(lambda p: p.requires_grad, model.parameters()), lr=ppo_config.learning_rate)
    total_train_batch_size = \
        training_args.per_device_train_batch_size * training_args.gradient_accumulation_steps * training_args.world_size
    lr_scheduler = get_scheduler(
        training_args.lr_scheduler_type,
        optimizer=optimizer,
        num_warmup_steps=training_args.warmup_steps,
        num_training_steps=(training_args.num_train_epochs * math.ceil(len(dataset) / total_train_batch_size))
    )

    # Initialize our Trainer
    ppo_trainer = PPOPeftTrainer(
        training_args=training_args,
        finetuning_args=finetuning_args,
        callbacks=[LogCallback()],
        config=ppo_config,
        model=model,
        ref_model=None,
        tokenizer=tokenizer,
        dataset=dataset,
        data_collator=data_collator,
        optimizer=optimizer,
        lr_scheduler=lr_scheduler
    )

    ppo_trainer.ppo_train(max_target_length=data_args.max_target_length)
    ppo_trainer.save_model()
    ppo_trainer.save_state() # must be after save_model
    if ppo_trainer.is_world_process_zero() and model_args.plot_loss:
        plot_loss(training_args.output_dir, keys=["loss", "reward"])


def _mp_fn(index):
    # For xla_spawn (TPUs)
    main()


if __name__ == "__main__":
    main()

前置部分与其他训练策略的前置部分基本一致,读取各自参数与数据集并处理后分割数据集,并且划分交叉验证等操作。随后调用PPOPeftTrainer来实现训练器的定义。

class PPOPeftTrainer(PPOTrainer, PeftTrainer):
    r"""
    Inherits PPOTrainer.
    """

    def __init__(
            self,
            training_args: Seq2SeqTrainingArguments,
            finetuning_args: FinetuningArguments,
            callbacks: List[LogCallback],
            **kwargs
    ):
        PPOTrainer.__init__(self, **kwargs)
        self.args = training_args
        self.finetuning_args = finetuning_args
        self.log_callback = callbacks[0]
        self.state = TrainerState()
        self.data_collator = self.accelerator.prepare(kwargs["data_collator"]) # override the data collator of PPOTrainer

    def ppo_train(self, max_target_length: int) -> None:
        r"""
        Implements training loop for the PPO stage, like _inner_training_loop() in Huggingface's Trainer.
        """
        total_train_batch_size = self.config.batch_size * self.config.gradient_accumulation_steps * self.args.world_size
        len_dataloader = len(self.dataloader)
        num_steps_per_epoch = max(len_dataloader // self.config.gradient_accumulation_steps, 1)
        num_examples = len(self.dataset)
        num_train_epochs = self.args.num_train_epochs
        max_steps = math.ceil(num_train_epochs * num_steps_per_epoch)

        self.state.max_steps = max_steps
        self.state.num_train_epochs = num_train_epochs
        self.state.is_local_process_zero = self.is_local_process_zero()
        self.state.is_world_process_zero = self.is_world_process_zero()

        if self.is_world_process_zero():
            logger.info("***** Running training *****")
            logger.info(f"  Num examples = {num_examples}")
            logger.info(f"  Num Epochs = {num_train_epochs}")
            logger.info(f"  Instantaneous batch size per device = {self.config.batch_size}")
            logger.info(f"  Total train batch size (w. parallel, distributed & accumulation) = {total_train_batch_size}")
            logger.info(f"  Gradient Accumulation steps = {self.config.gradient_accumulation_steps}")
            logger.info(f"  Total optimization steps = {max_steps}")
            logger.info(f"  Number of trainable parameters = {sum(p.numel() for p in self.model.parameters() if p.requires_grad)}")

        # Keyword arguments for `model.generate`
        gen_kwargs = {
            "top_k": 0.0,
            "top_p": 1.0,
            "do_sample": True,
            "pad_token_id": self.tokenizer.pad_token_id,
            "eos_token_id": self.tokenizer.eos_token_id,
            "logits_processor": get_logits_processor()
        }
        output_length_sampler = LengthSampler(max_target_length // 2, max_target_length)
        unwrapped_model: PreTrainedModel = self.accelerator.unwrap_model(self.model)

        dataiter = iter(self.dataloader)
        steps_trained = 0
        loss_meter = AverageMeter()
        reward_meter = AverageMeter()

        for step in tqdm(range(max_steps), disable=not self.is_world_process_zero()):

            for _ in range(self.config.gradient_accumulation_steps):

                batch = next(dataiter)
                steps_trained += 1

                unwrapped_model.gradient_checkpointing_disable()
                unwrapped_model.config.use_cache = True

                # Get response from model
                query_tensors: torch.Tensor = batch["input_ids"]
                response_tensors = self.generate(batch, length_sampler=output_length_sampler, return_prompt=False, **gen_kwargs)

                queries: List[torch.Tensor] = []
                responses: List[torch.Tensor] = []
                for i in range(len(query_tensors)):
                    query_length = (query_tensors[i] != self.tokenizer.pad_token_id).nonzero()[0]
                    response_length = (response_tensors[i] != self.tokenizer.pad_token_id).nonzero()[-1] + 1
                    queries.append(query_tensors[i, query_length:]) # remove padding from left
                    if response_length < 2: # make response have at least 2 tokens
                        responses.append(response_tensors.new_empty(2).fill_(self.tokenizer.eos_token_id))
                    else:
                        responses.append(response_tensors[i, :response_length]) # remove padding from right

                # Compute rewards
                replace_model(unwrapped_model, target="reward")
                _, _, values = self.model(**self.prepare_model_inputs(queries, responses))
                rewards = [reward for reward in values[:, -1].to(torch.float32)] # use float32 type
                replace_model(unwrapped_model, target="default") # make sure the model is default at the end

                # Run PPO step
                unwrapped_model.gradient_checkpointing_enable()
                unwrapped_model.config.use_cache = False

                stats = self.step(queries, responses, rewards)

                loss_meter.update(stats["ppo/loss/total"], n=len(rewards))
                reward_meter.update(torch.stack(rewards).mean().item(), n=len(rewards))

                if steps_trained == len_dataloader:
                    dataiter = iter(self.dataloader)
                    steps_trained = 0

            if self.is_world_process_zero() and (step+1) % self.args.logging_steps == 0:
                logs = {
                    "loss": round(loss_meter.avg, 4),
                    "reward": round(reward_meter.avg, 4),
                    "learning_rate": stats["ppo/learning_rate"],
                    "epoch": round(step / num_steps_per_epoch, 2)
                }
                print(logs)
                logs["step"] = step
                self.state.log_history.append(logs)
                self.log_callback.on_log(self.args, self.state, None)
                loss_meter.reset()
                reward_meter.reset()

            if (step+1) % self.args.save_steps == 0: # save checkpoint
                self.save_model(os.path.join(self.args.output_dir, f"checkpoint-{step+1}"))

    @torch.no_grad()
    def generate(
            self,
            inputs: Dict[str, torch.Tensor],
            length_sampler: Optional[Callable] = None,
            return_prompt: Optional[bool] = True,
            **generation_kwargs,
    ) -> torch.Tensor:
        r"""
        Generates model's responses given queries.

        Subclass and override to inject custom behavior.
        """
        self.model, layer_norm_params = cast_layernorm_dtype(self.model)

        if length_sampler is not None:
            generation_kwargs["max_new_tokens"] = length_sampler()

        unwrapped_model = self.accelerator.unwrap_model(self.model)

        response = unwrapped_model.generate(**inputs, **generation_kwargs)

        # Temporary hack to ensure the generation config is not initialized for each iteration of the evaluation loop
        # Inspired by: https://github.com/huggingface/transformers/blob/v4.28.1/src/transformers/trainer_seq2seq.py#L273
        if unwrapped_model.pretrained_model.generation_config._from_model_config:
            unwrapped_model.pretrained_model.generation_config._from_model_config = False

        self.model, _ = cast_layernorm_dtype(self.model, layer_norm_params)

        if not return_prompt and not self.is_encoder_decoder:
            return response[:, inputs["input_ids"].size(1):]
        return response

    def save_model(self, output_dir: Optional[str] = None) -> None:
        r"""
        Saves model checkpoint.

        Subclass and override to inject custom behavior.
        """
        if self.args.should_save:
            self._save(output_dir)

PPOPeftTrainer 类继承了 PPOTrainerPeftTrainer,用于实现参数高效的近端策略优化。

__init__ 方法:初始化 PPOTrainer、设置训练参数、微调参数和日志回调函数、覆盖 PPOTrainer 的数据填充器,确保其适用于当前训练环境。

ppo_train 方法:计算总的训练批次大小和训练步数、初始化训练状态、日志记录初始化信息(仅主进程)、配置生成的关键词参数(gen_kwargs)、创建数据迭代器和指标记录器、在训练循环中(生成模型的响应、计算奖励、执行PPO步骤、更新损失和奖励指标、每个周期结束时重置迭代器、定期记录日志和保存检查点)

generate 方法:设置层归一化参数类型、根据 length_sampler 调整最大生成标记数、生成模型的响应、返回生成的响应。

PPOTrainer 是 Hugging Face 的 transformers 库中用于实现 Proximal Policy Optimization(PPO)算法的训练器。PPO 是一种常用的强化学习算法,特别适用于序列生成任务,如文本生成、对话系统等。

PPOTrainer 的主要功能

  1. 强化学习训练PPOTrainer 实现了 PPO 算法的训练过程,适用于基于策略梯度的方法来优化模型。
  2. 策略优化:通过 PPO 算法,优化模型生成的策略,使得生成的文本或动作在给定的奖励函数下表现更好。
  3. 梯度更新:使用剪切的概率比来更新策略,确保更新稳定性。
  4. 奖励建模:计算生成文本的奖励,指导模型的学习过程。

optimizer = AdamW(filter(lambda p: p.requires_grad, model.parameters()), lr=ppo_config.learning_rate):AdamW:这是一个优化器,基于 Adam 优化器,但在计算权重更新时使用了权重衰减(Weight Decay),这有助于正则化和防止过拟合。filter(lambda p: p.requires_grad, model.parameters()):这个部分用于过滤模型参数,只保留那些需要梯度更新的参数。

  • model.parameters():返回模型的所有参数。
  • lambda p: p.requires_grad:这是一个匿名函数,检查参数 p 是否需要梯度更新(即 requires_grad 属性为 True)。
  • filter(…):应用过滤条件,只返回需要梯度更新的参数。

lr=ppo_config.learning_rate:指定优化器的学习率,使用 PPO 配置中的学习率。

AdamW 是一种改进的 Adam 优化器,通过引入权重衰减(Weight Decay)来正则化模型,防止过拟合。与传统的 L2 正则化不同,AdamW 在每次参数更新时直接减去权重衰减项。

权重衰减:帮助控制模型复杂度,防止模型参数变得过大,有助于提升模型的泛化能力。

过滤需要梯度更新的参数

  • 梯度更新:在训练过程中,只有那些需要更新的参数才会参与反向传播计算和梯度更新。通过过滤参数,只保留 requires_grad=True 的参数,可以避免不必要的计算,提升训练效率。
  • 冻结参数:在某些情况下(如微调预训练模型时),我们可能希望冻结部分参数,只更新特定层的参数。这个过滤操作可以确保只有需要更新的参数会被优化器处理。

设置学习率

  • 学习率是优化器的重要超参数,决定了每次参数更新的步长。合适的学习率可以加速模型收敛,并提升最终的模型性能。

  • PPO 配置中的学习率:确保优化器使用与 PPO 配置一致的学习率参数,有助于统一管理训练超参数。

lr_scheduler部分:get_scheduler:这是 transformers 库中的一个函数,用于根据指定的类型创建学习率调度器。

training_args.lr_scheduler_type:这是学习率调度器的类型,通常是一个字符串,指定要使用的调度器类型,如 “linear”、“cosine”、“cosine_with_restarts” 等。

optimizer:优化器对象,之前创建的 AdamW 优化器。

num_warmup_steps:预热步骤数,在这段时间内学习率从 0 线性增加到设定的最大值。

num_training_steps:总训练步骤数,这是通过训练轮数和每轮的训练步骤数计算得到的。

  • training_args.num_train_epochs:训练的总轮数。
  • math.ceil(len(dataset) / total_train_batch_size):每轮的训练步骤数,等于数据集的大小除以总的训练批次大小。

其具体作用是:

动态调整学习率

  • 学习率调度器在训练过程中根据预定义的策略动态调整学习率。调整学习率可以帮助模型更好地收敛,提高训练效果。
  • 例如,在训练的初期,学习率通常较高,以便快速靠近最优解;而在训练的后期,学习率通常较低,以便在最优解附近精细调整。

预热步骤

  • 预热步骤(warmup steps)是指在训练开始时,学习率从 0 线性增加到设定的最大值。这有助于防止模型在训练初期由于过大的梯度更新而导致的不稳定。
  • 通过设置 num_warmup_steps,可以控制预热的长度,通常在训练初期使用较低的学习率,逐步增加到设定的最大值。

总训练步骤数

  • 总训练步骤数是训练过程中的关键参数,决定了调度器如何调整学习率。通过设置 num_training_steps,可以确保学习率调整策略覆盖整个训练过程。
  • 总训练步骤数的计算方式为 训练轮数 * 每轮的训练步骤数,其中每轮的训练步骤数等于数据集的大小除以总的训练批次大小(每个设备的批次大小乘以设备数和梯度累积步数)。

这段代码实现了一个用于PPO训练的参数高效训练器,通过 PPOPeftTrainer 实现。代码涵盖了从数据准备、模型加载、数据预处理、优化器和学习率调度器设置、PPO配置到训练和评估的完整流程。该训练器特别适用于需要强化学习的NLP任务,如文本生成或对话系统,通过PPO方法优化模型性能。

  • 29
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值