强化学习动手实践:初探 DeepSeek R1 顿悟现象
在人工智能训练过程中,模型能力的跃迁往往并非线性提升,而是伴随着潜移默化的积累与突现的顿悟。本文以小模型为例,尝试通过强化学习探索 DeepSeek R1 中“顿悟现象”的再现与分析,揭示智能系统从量变到质变的过程。
一、实践目的
我们实践的目标包括:
- 探索语言模型在强化学习中表现出来的“顿悟”(Aha Moment)现象,即模型能力出现显著提升的瞬间。
- 理解模型训练过程中每个代码模块的作用,并实际动手完成一次训练。
本教程基于 Philipp Schmid 在 GitHub 上公开的 Mini-R1: Reproduce Deepseek R1 „aha moment“ a RL tutorial 项目修改而来。代码和实验结果可以从 Github 或者 Gitee 下载。
二、强化学习任务介绍
强化学习的问题设定:
我们使用的数据集为 Jiayi-Pan/Countdown-Tasks-3to4,包含大量以数字运算为核心的数学问题。具体来说,每个题目都会提供:
- 一组数字(例如
80, 93, 62
) - 一个目标数字(例如
49
)
任务是:
使用给定的数字,每个数字且只能使用一次,通过基本的数学运算(加、减、乘、除)组合成一个算式,得到题目给出的 目标数字。
举例说明:
给定数字:80, 93, 62
,目标:49
生成的 completion 样例如下:
<think>
We need to use the numbers 80, 93, and 62 exactly once with basic arithmetic operations to get 49. Let's try different combinations:
:
- 93 - 80 - 62 = -49(not 49)
- 80 + 62 - 93 = 49(this works)
</think>
<answer>80 + 62 - 93</answer>
模型需要先在 <think>
标签内展示思考推导过程,然后在 <answer>
标签内给出最终正确的算式。
三、实践框架概览
实践过程可分为以下几个主要阶段:
- 环境配置
- 数据准备与格式化(Prompt构建)
- 定义奖励函数用于评估生成结果
- 模型加载并运行 GRPO 训练
- 训练结果分析与顿悟现象识别
四、具体实践步骤
步骤 1:环境配置
本实践是在 Spader.AI 上的 8 卡 A100 上完成的。按照 Github 或者 Gitee 上的安装指南准备环境。
以下代码片段均来自 scripts/run_r1_grpo.py
。
步骤2:数据准备与 Prompt 格式化
从 Hugging Face 数据集Jiayi-Pan/Countdown-Tasks-3to4
中抽取数据后,使用如下代码格式化成 Prompt:
# 从 Hugging Face Hub 下载数据集
dataset = load_dataset(script_args.dataset_id_or_path, split=script_args.dataset_splits)
# 从中随机挑选 5 万个样本
dataset = dataset.shuffle(seed=42).select(range(50000))
#####################
# 准备并且格式化数据
#####################
# 转换成 DeepSeek R1 prompt 格式, 即带有 <think> 标签开头的 prompt
def generate_r1_prompt(numbers, target):
r1_prefix = [{
"role": "system",
"content": "You are a helpful assistant. You first thinks about the reasoning process in the mind and then provides the user with the answer."
},
{
"role": "user",
"content": f"Using the numbers {numbers}, create an equation that equals {target}. You can use basic arithmetic operations (+, -, *, /) one or multiple times but each number can only be used once. Show your work in <think> </think> tags. And return the final equation in <answer> </answer> tags, for example <answer> (1 + 2) / 3 </answer>. Think step by step inside <think> tags."
},
{
"role": "assistant",
"content": "Let me solve this step by step.\n<think>"
}]
return {"prompt": tokenizer.apply_chat_template(r1_prefix, tokenize=False, continue_final_message=True), "target": target, "nums": numbers}
# 把待训练的数据转换成 DeepSeek R1 格式的 prompt
dataset = dataset.map(lambda x: generate_r1_prompt(x["nums"], x["target"]))
# 切分 10% 的数据作为测试集
train_test_split = dataset.train_test_split(test_size=0.1)
train_dataset = train_test_split["train"]
test_dataset = train_test_split["test"]
格式化后的 Prompt 具体内容包含:
- 清晰的系统指令,要求模型先 思考后作答。
- 用户问题明确要求模型用给定数字构造等于目标的算式,并明确要求在
<think>
标签内展示推导过程、<answer>
标签内给出最终算式。 - Assistant 回复的开头固定为
Let me solve this step by step.<think>
以引导模型的推理流程。
步骤3:奖励函数定义(评估模型生成结果)
format_reward_func
用于检查输出是否满足<think>
和<answer>
格式要求。equation_reward_func
用于检查算式的数学正确性、数字是否用尽且仅使用一次,以及是否等于目标值。
奖励值为 1 表示成功,0 表示失败。奖励函数帮助模型学习生成满足要求的高质量答案。
步骤4:GRPO 模型训练
#########################
# 初始化 GRPO trainer
#########################
trainer = GRPOTrainer(
model=model_args.model_name_or_path,
reward_funcs=[format_reward_func, equation_reward_func],
args=training_args,
train_dataset=train_dataset,
eval_dataset=test_dataset,
peft_config=get_peft_config(model_args), # 此实践没有用到 Lora
)
###############
# Training loop
###############
train_result = trainer.train(resume_from_checkpoint=last_checkpoint)