深度学习:基于Qwen复现DeepSeek R1的推理能力

原文链接: 0元!使用魔搭免费算力,基于Qwen基座模型,复现DeepSeek-R1

DeepSeek-R1-Zero通过GRPO算法,将强化学习从绝对奖励驱动 转变为相对偏好优化,结合分组对比学习和多步推理奖励机制,使基础模型在复杂任务中展现出更强的逻辑推理能力。

DeepSeek R1-Zero训练流程如下:

输入问题 → 模型生成多个答案 → 规则系统评分 → GRPO计算相对优势 → 更新模型。

DeepSeek R1-Zero的打分规则如下:

1.准确性奖励:准确性奖励模型评估响应是否正确。对了就加分,错了扣分。评价方法十分简单:例如,在具有确定性结果的数学问题中,模型需要以指定格式(如<answer>和</answer>间)提供最终答案;对于编程问题,可以使用编译器根据预定义的测试用例生成反馈。

2.格式奖励:格式奖励模型强制要求模型将其思考过程置于<think>和</think>标签之间。没这么做就扣分,做了就加分。

参考文章: 一文读懂|DeepSeek新模型大揭秘,为何它能震动全球AI圈_腾讯新闻

DeepSeek R1-Zero使用纯强化学习为模型带来了推理能力,省去了传统的SFT环境和RLHF中的奖励模型(Reward Model)

GRPO算法的核心思想

  • 相对偏好优化 :传统RL(如PPO)依赖绝对奖励值优化策略,但推理任务中奖励可能稀疏或难以量化。GRPO改为对模型生成的不同答案组(Group)进行相对比较,通过对比学习强化高质量推理路径。

  • 分组对比学习:将模型对同一问题的多个候选回答划分为不同组(如正确/错误、优/劣答案),通过组间对比学习,引导模型识别逻辑更严谨的解决方案。

  • 稳定优化目标 :通过对比策略的相对优势(Relative Advantage)而非绝对值,缓解奖励函数设计偏差带来的训练不稳定问题。

基于规则的奖励系统为模型生成的每个候选答案(或推理轨迹)分配一个分数后,这些分数在 GRPO 中主要有两方面作用:

(1) 计算相对优势(Relative Advantage)
  • 定义:相对优势衡量某个答案相对于其他答案的偏好程度。

  • 计算方式

    • 对同一问题生成的多个候选答案,根据规则系统分配的分数进行分组(如正例组和负例组)。

    • 计算某个答案yi的相对优势:

    • 作用:通过相对优势,GRPO 能够更稳定地优化策略,避免对绝对奖励值的过度依赖

(2) 策略优化目标
  • 基于相对优势,GRPO 构建损失函数来更新策略模型:
  • 第一项:基于相对优势的策略梯度,鼓励模型生成高相对优势的答案。

  • 第二项:KL 散度约束,防止策略偏离预训练基础模型太远。

更多关于GRPO的细节可以参考这篇文章: 4000字!DeepSeek-R1 核心强化学习算法 GRPO 详解_算法_小马不会过河-DeepSeek技术社区

使用ModelScope平台提供的免费算力,结合Qwen提供的基座模型和GRPO算法,可以迅速使模型获得推理能力。

复现流程

**第一步:**安装依赖

其他的依赖在ModelScope的notebook镜像预装好,本次仅需要升级vllm和trl到最新版本,安装后请重启ipynb环境。

复制代码
!pip install vllm -U
!pip install trl -U

**第二步:**定义prompt的结构,需要包含Reasoning tag<think>标签对

python 复制代码
# 定义prompt结构,包含特制的推理标签
import re
import torch
from modelscope.msdatasets import MsDataset
from modelscope import AutoTokenizer, AutoModelForCausalLM
from trl import GRPOConfig, GRPOTrainer

SYSTEM_PROMPT = '''
A conversation between User and Assistant, 
The user asks a question, and the Assistant solves it.
The assistant first thinks about the reasoning process in the mind and then provides the user with the answer. 
The reasoning process and answer are enclosed within <think> </think> and <answer> </answer> tags, respectively, 
i.e., <think> reasoning process here </think> <answer> answer here </answer>. 
User: prompt. Assistant:
'''

XML_COT_FORMAT = '''
<think>
{think}
</think>
<answer>
{answer}
</answer>
'''

注:ModelScope原文中采用<reasoning>标签对。而DeepSeek R1-Zero则是采用<think>标签对。

图:DeepSeek R1-Zero使用的System Prompt模板

**第三步:**加载数据集,重构它以适应对话prompt的结构

**数据集:**GSM8K 是一个由 OpenAI 创建的高质量小学数学问题数据集,包含 8.5K 个语言多样化的小学数学应用题。这些题目主要涉及需要 2 到 8 个步骤解决的数学问题,涵盖了算术、代数等基础数学领域

数据集分为 7.5K 个训练问题和 1K 个测试问题,旨在评估模型在数学推理和逻辑能力方面的表现。

python 复制代码
# 加载数据集
def extract_xml_answer(text: str) -> str:
    '''
    从xml模版中,去除<answer>标签,获取回答纯文本的函数
    '''
    answer = text.split('<answer>')[-1]
    answer = answer.split('</answer>')[0]
    return answer.strip()

def extract_hash_answer(text: str) -> str | None:
    '''从字符串中提取 #### 后的内容。'''
    if '####' not in text:
        return None
    return text.split('####')[1].strip()

def get_gsm8k_questions(split = 'train') -> MsDataset:
    '''加载gsm8k数据集,并映射为输入模板'''
    data = MsDataset.load('modelscope/gsm8k', subset_name='main', split=split)
    data = data.map(lambda x: { # type: ignore
        'prompt': [
            {'role': 'system', 'content': SYSTEM_PROMPT },
            {'role': 'user', 'content': x['question'] }
        ],
        'answer': extract_hash_answer(x['answer'])
    }) # type: ignore
    return data # type: ignore

dataset = get_gsm8k_questions()

**第四步:**设置奖励规则

python 复制代码
# 自定义Rewarding函数
def correctness_reward_func(prompts, completions, answer, **kwargs) -> list[float]:
    """
    奖励函数:检查模型生成的回答是否与标准答案一致。
    
    参数:
        prompts (list): 包含问题的提示列表。
        completions (list): 模型生成的回答列表。
        answer (list): 标准答案列表。
        **kwargs: 其他可选参数。
    
    返回:
        list[float]: 每个回答对应的奖励值(2.0 表示正确,0.0 表示错误)。
    """
    responses = [completion[0]['content'] for completion in completions]
    q = prompts[0][-1]['content']
    extracted_responses = [extract_xml_answer(r) for r in responses]
    
    print('-'*20, f'Question:\n{q}', f'\nAnswer:\n{answer[0]}', f'\nResponse:\n{responses[0]}', f'\nExtracted:\n{extracted_responses[0]}')
    
    return [2.0 if r == a else 0.0 for r, a in zip(extracted_responses, answer)]

def int_reward_func(completions, **kwargs) -> list[float]:
    """
    奖励函数:验证模型生成的回答是否为整数。
    
    参数:
        completions (list): 模型生成的回答列表。
        **kwargs: 其他可选参数。
    
    返回:
        list[float]: 每个回答对应的奖励值(0.5 表示是整数,0.0 表示不是整数)。
    """
    responses = [completion[0]['content'] for completion in completions]
    extracted_responses = [extract_xml_answer(r) for r in responses]
    return [0.5 if r.isdigit() else 0.0 for r in extracted_responses]

def strict_format_reward_func(completions, **kwargs) -> list[float]:
    """
    奖励函数:检查模型生成的回答是否严格符合 <think> 和 <answer> 的 XML 格式。
    
    参数:
        completions (list): 模型生成的回答列表。
        **kwargs: 其他可选参数。
    
    返回:
        list[float]: 每个回答对应的奖励值(0.5 表示格式正确,0.0 表示格式错误)。
    """
    pattern = r'^<think>\n.*?\n</think>\n<answer>\n.*?\n</answer>\n$'
    responses = [completion[0]['content'] for completion in completions]
    matches = [re.match(pattern, r) for r in responses]
    return [0.5 if match else 0.0 for match in matches]

def soft_format_reward_func(completions, **kwargs) -> list[float]:
    """
    奖励函数:检查模型生成的回答是否宽松地包含 <think> 和 <answer> 的 XML 标签。
    
    参数:
        completions (list): 模型生成的回答列表。
        **kwargs: 其他可选参数。
    
    返回:
        list[float]: 每个回答对应的奖励值(0.5 表示格式正确,0.0 表示格式错误)。
    """
    pattern = r'<think>.*?</think>\s*<answer>.*?</answer>'
    responses = [completion[0]['content'] for completion in completions]
    matches = [re.match(pattern, r) for r in responses]
    return [0.5 if match else 0.0 for match in matches]

def count_xml(text) -> float:
    """
    辅助函数:统计文本中 XML 标签的出现次数,并根据规则计算分数。
    
    参数:
        text (str): 输入文本。
    
    返回:
        float: 根据 XML 标签出现次数计算的分数。
    """
    count = 0.0
    if text.count('<think>\n') == 1:
        count += 0.125
    if text.count('\n</think>\n') == 1:
        count += 0.125
    if text.count('\n<answer>\n') == 1:
        count += 0.125
        count -= len(text.split("\n</answer>\n")[-1])*0.001
    if text.count('\n</answer>') == 1:
        count += 0.125
        count -= (len(text.split('\n</answer>')[-1]) - 1) * 0.001
    return count

def xmlcount_reward_func(completions, **kwargs) -> list[float]:
    contents = [completion[0]['content'] for completion in completions]
    return [count_xml(c) for c in contents]

**第五步:**设置训练参数

python 复制代码
# 设置训练参数
model_name = "Qwen/Qwen2.5-0.5B-Instruct"

output_dir = "outputs/Qwen-0.5B-GRPO"
run_name = "Qwen-0.5B-GRPO-gsm8k"

training_args = GRPOConfig(
    output_dir=output_dir,
    run_name=run_name,
    learning_rate=5e-6,
    adam_beta1 = 0.9,
    adam_beta2 = 0.99,
    weight_decay = 0.1,
    warmup_ratio = 0.1,
    lr_scheduler_type='cosine',
    logging_steps=1,
    bf16=True,
    per_device_train_batch_size=8,
    gradient_accumulation_steps=4,
    num_generations=8,
    max_prompt_length=256,
    max_completion_length=200,
    num_train_epochs=1,
    save_steps=100,
    max_grad_norm=0.1,
    log_on_each_node=False,
    use_vllm=True,
    vllm_gpu_memory_utilization=.2,
    vllm_device="cuda:0",
    report_to="none" #I'm disabling Wandb.
)

model = AutoModelForCausalLM.from_pretrained(
    model_name,
    torch_dtype=torch.bfloat16,
    device_map=None
).to('cuda')

tokenizer = AutoTokenizer.from_pretrained(model_name)
tokenizer.pad_token = tokenizer.eos_token

**第六步:**配置Trainer进行训练

python 复制代码
# 构造trainer开始训来呢
trainer = GRPOTrainer(
    model=model,
    processing_class=tokenizer,
    reward_funcs=[
        xmlcount_reward_func,
        soft_format_reward_func,
        strict_format_reward_func,
        int_reward_func,
        correctness_reward_func
    ],
    args=training_args,
    train_dataset=dataset,
)

trainer.train()

第七步:使用微调后的模型进行推理

python 复制代码
from modelscope import AutoModelForCausalLM, AutoTokenizer

model_name = "/mnt/workspace/outputs/Qwen-0.5B-GRPO/checkpoint-***"

model = AutoModelForCausalLM.from_pretrained(
    model_name,
    torch_dtype="auto",
    device_map="auto"
)
tokenizer = AutoTokenizer.from_pretrained(model_name)

prompt = "Natalia is riding a bicycle for the cycling competition. On Monday she rode 40 kilometers and on Tuesday 50 kilometers. On Wednesday she rode 50% fewer kilometers than the day before. On Thursday she rode as many as the sum of the kilometers from Monday and Wednesday. How many kilometers did Natalie ride in total? "
messages = [
    {"role": "system", "content": SYSTEM_PROMPT},
    {"role": "user", "content": prompt}
]
text = tokenizer.apply_chat_template(
    messages,
    tokenize=False,
    add_generation_prompt=True
)
model_inputs = tokenizer([text], return_tensors="pt").to(model.device)

generated_ids = model.generate(
    **model_inputs,
    max_new_tokens=512
)
generated_ids = [
    output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)
]

response = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
print(response)

训练1700步后,可以看到模型掌握了一定的推理能力,但效果还不是特别好,可以通过继续训练以及换用更好的基座模型以提升效果。

相关推荐
说私域40 分钟前
基于开源AI智能名片链动2+1模式S2B2C商城小程序的超级文化符号构建路径研究
人工智能·小程序·开源
永洪科技42 分钟前
永洪科技荣获商业智能品牌影响力奖,全力打造”AI+决策”引擎
大数据·人工智能·科技·数据分析·数据可视化·bi
shangyingying_11 小时前
关于小波降噪、小波增强、小波去雾的原理区分
人工智能·深度学习·计算机视觉
书玮嘎2 小时前
【WIP】【VLA&VLM——InternVL系列】
人工智能·深度学习
猫头虎2 小时前
猫头虎 AI工具分享:一个网页抓取、结构化数据提取、网页爬取、浏览器自动化操作工具:Hyperbrowser MCP
运维·人工智能·gpt·开源·自动化·文心一言·ai编程
要努力啊啊啊2 小时前
YOLOv2 正负样本分配机制详解
人工智能·深度学习·yolo·计算机视觉·目标跟踪
CareyWYR2 小时前
大模型真的能做推荐系统吗?ARAG论文给了我一个颠覆性的答案
人工智能
特立独行的猫a3 小时前
百度AI文心大模型4.5系列开源模型评测,从安装部署到应用体验
人工智能·百度·开源·文心一言·文心一言4.5
SKYDROID云卓小助手3 小时前
无人设备遥控器之自动调整编码技术篇
人工智能·嵌入式硬件·算法·自动化·信号处理