近端策略优化(Proximal Policy Optimization, PPO)是一种基于策略梯度的强化学习算法,由OpenAI在2017年提出。PPO算法在保持训练稳定性的同时,能够获得较好的样本效率和性能表现。PPO的核心思想是通过限制策略更新的幅度,避免过大的策略变化导致性能崩溃。
PPO算法有两种主要变体:PPO-Penalty和PPO-Clip。在实际应用中,PPO-Clip因其实现简单且性能优越而被广泛采用。
传统的策略梯度方法中,在强化学习中,目标函数 表示策略 的期望累积回报。具体形式如下:
这里:
虽然这个函数可以通过梯度上升进行优化,但存在两个关键问题:
PPO-Clip通过引入一个新的目标函数解决了这些问题,称为"裁剪代理目标函数"(Clipped Surrogate Objective):
其中:
这个目标函数的关键特点是:它通过剪裁比率,限制了新旧策略之间的差异。这确保了策略更新不会太大,从而提高了训练的稳定性。
为了更好地理解PPO-Clip的工作原理,我们分析几种不同情况:
当优势时:
当优势时:
通过这种方式,PPO算法实现了策略的保守更新,避免了性能的剧烈波动。
在LLaMA-Factory项目中,PPO算法被实现为CustomPPOTrainer
类,继承自trl
库的PPOTrainer
和Trainer
类。下面是算法实现的关键部分:
pythonppo_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=backward_batch_size * finetuning_args.ppo_buffer_size,
gradient_accumulation_steps=training_args.gradient_accumulation_steps,
ppo_epochs=finetuning_args.ppo_epochs,
max_grad_norm=training_args.max_grad_norm,
seed=training_args.seed,
optimize_device_cache=True,
target=finetuning_args.ppo_target,
use_score_scaling=finetuning_args.ppo_score_norm,
use_score_norm=finetuning_args.ppo_score_norm,
whiten_rewards=finetuning_args.ppo_whiten_rewards,
accelerator_kwargs={"step_scheduler_with_optimizer": False},
log_with=training_args.report_to[0] if training_args.report_to else None,
project_kwargs={"logging_dir": training_args.logging_dir},
)
pythondef ppo_train(self, resume_from_checkpoint: Optional[str] = None) -> None:
"""Implement training loop for the PPO stage, like _inner_training_loop() in Huggingface's Trainer."""
# ... 准备阶段省略 ...
for step in tqdm(range(max_steps), disable=not self.is_local_process_zero()):
try:
batch = next(dataiter)
except StopIteration:
dataiter = iter(self.dataloader)
batch = next(dataiter)
# 获取输入
self.model.eval()
queries, responses, rewards = [], [], []
for idx in range(0, self.config.batch_size, self.config.mini_batch_size):
mini_batch = {
"input_ids": batch["input_ids"][idx : idx + self.config.mini_batch_size],
"attention_mask": batch["attention_mask"][idx : idx + self.config.mini_batch_size],
}
mini_batch_queries, mini_batch_responses = self.get_inputs(mini_batch)
mini_batch_rewards = self.get_rewards(mini_batch_queries, mini_batch_responses)
queries.extend(mini_batch_queries)
responses.extend(mini_batch_responses)
rewards.extend(mini_batch_rewards)
# 执行PPO步骤
self.model.train()
stats = self.step(queries, responses, rewards)
loss_meter.update(float(stats["ppo/loss/total"]), n=len(rewards))
reward_meter.update(torch.stack(rewards).mean().item(), n=len(rewards))
# ... 后续步骤省略 ...
python@torch.no_grad()
def get_rewards(self, queries: list["torch.Tensor"], responses: list["torch.Tensor"]) -> list["torch.Tensor"]:
"""Compute scores using given reward model."""
if self.finetuning_args.reward_model_type == "api":
token_ids = [torch.cat((q, r), dim=-1).tolist() for q, r in zip(queries, responses)]
messages = self.tokenizer.batch_decode(token_ids, skip_special_tokens=False)
return get_rewards_from_server(self.reward_model, messages)
batch: dict[str, torch.Tensor] = self.prepare_model_inputs(queries, responses)
unwrapped_model: AutoModelForCausalLMWithValueHead = self.accelerator.unwrap_model(self.model)
if self.finetuning_args.reward_model_type == "lora":
replace_model(unwrapped_model, target="reward")
reward_model = self.model
else:
reward_model = self.reward_model
with unwrap_model_for_generation(reward_model, self.accelerator), self.amp_context:
values: torch.Tensor = reward_model(**batch, return_dict=True, use_cache=False)[-1]
if self.finetuning_args.reward_model_type == "lora":
replace_model(unwrapped_model, target="default")
rewards = values.gather(dim=-1, index=(batch["attention_mask"].sum(dim=-1, keepdim=True) - 1))
return rewards.float().detach()
重要性采样比率:
裁剪代理目标函数:
完整的PPO目标函数 (包含值函数损失和熵正则化):
其中:
广义优势估计 (GAE):
其中
下面是LLaMA-Factory项目中PPO算法的核心实现部分:
pythonclass CustomPPOTrainer(PPOTrainer, Trainer):
"""继承自PPOTrainer的自定义实现"""
def __init__(
self,
model_args: "ModelArguments",
training_args: "Seq2SeqTrainingArguments",
finetuning_args: "FinetuningArguments",
generating_args: "GeneratingArguments",
callbacks: Optional[list["TrainerCallback"]],
model: "AutoModelForCausalLMWithValueHead",
reward_model: Optional["AutoModelForCausalLMWithValueHead"],
ref_model: Optional["AutoModelForCausalLMWithValueHead"],
tokenizer: "PreTrainedTokenizer",
processor: Optional["ProcessorMixin"],
data_collator: "DataCollatorWithPadding",
train_dataset: Optional["Dataset"] = None,
eval_dataset: Optional["Dataset"] = None,
) -> None:
# ... 初始化阶段省略 ...
def ppo_train(self, resume_from_checkpoint: Optional[str] = None) -> None:
"""PPO训练循环的实现"""
# ... 训练循环实现省略 ...
@torch.no_grad()
def get_inputs(self, batch: dict[str, "torch.Tensor"]) -> tuple[list["torch.Tensor"], list["torch.Tensor"]]:
"""获取输入和输出,用于PPO训练"""
# ... 实现省略 ...
@torch.no_grad()
def get_rewards(self, queries: list["torch.Tensor"], responses: list["torch.Tensor"]) -> list["torch.Tensor"]:
"""计算奖励"""
# ... 实现省略 ...
@override
@PPODecorators.empty_device_cache()
def batched_forward_pass(
self,
model: "AutoModelForCausalLMWithValueHead",
queries: "torch.Tensor",
responses: "torch.Tensor",
model_inputs: dict[str, Any],
return_logits: bool = False,
response_masks: Optional["torch.Tensor"] = None,
) -> tuple["torch.Tensor", Optional["torch.Tensor"], "torch.Tensor", "torch.Tensor"]:
"""批量前向传播"""
# ... 实现省略 ...
稳定性更好:通过限制策略更新的幅度,避免了大的策略变化导致的性能崩溃。
实现简单:相比于TRPO等其他信任区域方法,PPO算法实现更加简单,只需要一阶优化方法。
样本效率高:通过重要性采样和多次迭代,PPO可以多次利用同一批样本,提高了样本效率。
性能优越:在多种强化学习任务中,PPO展示了与SOTA算法相当甚至更好的性能。
适用性广:PPO适用于连续动作空间和离散动作空间,能够应用于各种不同类型的强化学习问题。
PPO是一种强大而实用的强化学习算法,通过限制新旧策略之间的差异,实现了训练过程的稳定性和高效性。在LLaMA-Factory项目中,PPO被实现为CustomPPOTrainer
类,用于训练大型语言模型。该实现包括PPO配置、训练循环、奖励计算等关键组件,提供了一个完整的PPO训练框架。
通过近端策略优化,模型可以在保证稳定性的同时,有效地从经验中学习改进策略,最终达到更好的性能表现。
本文作者:Dong
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC。本作品采用《知识共享署名-非商业性使用 4.0 国际许可协议》进行许可。您可以在非商业用途下自由转载和修改,但必须注明出处并提供原作者链接。 许可协议。转载请注明出处!