OpenAI o1 复现——过程奖励模型(PRM)

o1 作为 OpenAI 在推理领域的最新模型,大幅度提升了 GPT-4o 在推理任务上的表现,甚至超过了平均人类水平。

o1 背后的技术到底是什么?o1 隐藏的长思维链是如何生成的?

o1 的长思维链是什么样子?

我们先来看一下 o1 的长思维链长什么样子,下面是一个具体例子。

问题:设 n 是一个正偶数。设 p 是一个次数为 2n 的多项式,定义为:

对于某些实系数 a_0,…,a_{2n-1} ,假设 ,其中 。

求解所有的实数 x ,满足

图1:o1 长思维链

从图 1 中可以看出,除了常见的连接词如 “and” 和 “so” 之外。还出现了"wait", Alternatively" 等特殊的关键词,"像 “wait” (表示反思)和 “Alternatively”(表示探索不同路径) 这样的关键词是模型能够进行反思和自我纠正的重要指标。这表明模型具有更深入的理解和更细致的推理方法,因为模型不仅仅是遵循线性路径,还能够基于反思重新考虑和完善其方法。

总结而言,长思维数据具有以下特征:

  • 迭代式问题解决:模型首先定义函数,然后逐步探索相关表达式,将复杂方程分解为更简单的组成部分,反映了一种结构化和有条理的方法。

  • 关键思维指标:使用 “Therefore” 表示结论,“Alternatively” 探索不同路径,“Wait” 表示反思,以及 “Let me compute” 过渡到计算,突出了模型的推理阶段。

  • 递归和反思方法:模型经常重新评估和验证中间结果,使用递归结构确保一致性,这在严谨的数学推理中很典型。

  • 假设探索:模型测试不同的假设,随着获得更多信息而调整其方法,展示了推理过程中的灵活性

  • 结论和验证:最后,模型解方程并验证结果,强调在完成之前验证结论的重要性。

如何构建长思维链?

在了解了 o1 的长思维链长什么样子之后,接下来的问题是如何构建长思维链?

一种思路是基于 LLM 和奖励的树搜索

长思维链最显著的特征是在推理产生错误时或遇到冗余的推理步骤时尝试反思和回溯。这类似于在推理树上搜索问题的解决方案,在错误节点处回溯,直到找到正确的解决路径。为实现这一点,需要构建一棵推理树,其中根节点代表问题,其他每个节点代表一个推理步骤。从根到任何节点的路径代表从问题到该结论的推理过程。此外,回溯和反思必须基于错误的推理步骤,这需要一个更细粒度的奖励模型(即过程级) 来指示树中每个节点的正确性。通过在具有过程级奖励的推理树上执行搜索算法,可以将错误步骤整合到思维链中,从而构建包含回溯和反思等行为的长思维。

而为了得到这样的细粒度的奖励模型,就需要训练一个过程奖励模型 (PRM)。PRM 能够对模型生成的每一步进行打分。

在过程奖励模型 (PRM) 中,主要目的是判断解决方案的步骤是否在正确的轨道上。因此,PRM 会输出一个 0 到 1 之间的分数,作为当前解决过程的正确性指标。

具体来说,给定一个问题 q 及其解决步骤序列 ,PRM 会为每一步计算出一个分数,这个分数代表了当前问题解决过程的正确性。因此,问题被重新框定为 ,这可以视为一个二元分类任务。PRM 通过在大模型上进行 SFT 来训练,将正确或错误的判定作为分类标签。然后,使用 LLM 来预测每一步的下一个步骤是否正确。

下图是一个 PRM 的打分示意图:

图 2: 使用 PRM 对每个步骤进行打分

从图 2 可以看到, PRM 会为每个步骤打分(中间过程的打分没有做 softmax),最后输出的得分为[0.622, 0.349],表示正确的概率为 0.622。

接下来,我们就详细介绍下 PRM 是如何训练的。

如何训练 PRM

验证大模型结果的好坏,一般有两种不同的验证器:结果奖励模型 ORM 和过程奖励模型 PRM

ORM 对整个解决方案进行评分,而 PRM 对推理过程中的每个步骤进行评分。

ORM 目标函数

对于 ORM,给定一个数学问题和其解,ORM( )为分配一个单一实数值,已表明 是否正确。ORM 通常使用交叉熵损失进行训练:

其中是标签,表示 是否正确,是 ORM 模型分配给的 sigmoid 分数。

PRM 目标函数

PRM 更进一步,PRM 为 s 的每个推理步骤分配一个分数,通常使用以下方法进行训练:

其中是步骤 的标签,表示步骤是否正确;是 PRM 为步骤 分配的 sigmoid 分数。

PRM 训练数据

为了训练 PRM,我们需要一份为每个步骤分类(正确或错误)的标签数据。与 ORM 相比,PRM 可以提供更详细和可靠的反馈。但是 PRM 对数据要求极高,需要为每个步骤构建标签,非常耗时耗力。

目前开源的主要是 OpenAI 2023 年基于 MATH 构建的样本 PRM800K,包含了 800K 个步骤级别的正确性标签,这些标签针对的是 MATH 数据集中问题的解决方案。

另外一份数据是北京大学开源的数据集 Math-Shepherd,包含了 400k 个步骤级别的正确性标签,这些标签针对的是 MATH 和 GSM8K 数据集中问题的解决方案。

需要强调的是,PRM800K 都是人工标注的,而 MATH-Shepherd 是机器标注的。

下面是经过处理后的数据格式:

{       'question': 'Three pencils and a jumbo eraser cost $\\$1.24$. Five pencils and a jumbo eraser cost $\\$1.82$. No prices include tax. In cents, what is the cost of a pencil?',       'process': "Let's call the price of a pencil p and the price of a jumbo eraser e. Then we can write two equations. \n\n\n\n\n The first equation is $3p+e=124$. \n\n\n\n\n To solve this system, let's subtract the first equation from the second equation. This will eliminate e. \n\n\n\n\n $5p+e-3p-e=1.82-1.24$. \n\n\n\n\n This simplifies to $2p=0.58$. So $p=0.29$. \n\n\n\n\n We could also solve this system by substitution. \n\n\n\n\n",        'label': ['+', '-', '+', '+', '+', '+']   }

其中 “\n\n\n\n\n” 表示 step 的分隔符,label 中 “+“表示步骤正确,”-” 表示步骤错误。

有了训练数据后,接下来就是的问题就是如何训练 PRM 模型。

PRM 训练细节

将输入数据转化为模型输入(token id)

假设输入的一个样本为:

step_tag = '\n\n\n\n\n' #   step_tag_id = 76325  # 步骤标签   pog_id = 488 # "+" 表示正例,对应的 token id 是 488   neg_id = 481 # "-" 表示负例,对应的 token id 是 481      query = "1 + 1 + 2=?"   output = "Step 1: 1 + 1 = 2. \n\n\n\n\n Step2: 2 + 2 = 5 \n\n\n\n\n"   input = "1 + 1 + 2=? Step 1: 1 + 1 = 2. \n\n\n\n\n Step2: 2 + 2 = 5. \n\n\n\n\n"

其中 “\n\n\n\n\n” 是步骤分隔符,input 是将 query 和 output 拼接后的结果。

将 input 转化为 token id:

tokenized_inputs = {       'input_ids': [101, 102, 101, 103, 104, 105, 106, 101, 102, 101, 103, 76325, 103, 102, 103, 104, 107, 76325],     }

接下来是最重要的步骤,构建 label,具体做法是在步骤标签的位置设置为正例或负例的 id, 其他位置保持为-100,表示只计算标签位置的 loss:

# 在步骤标签的位置被设置为正例或负例的id,其他位置保持为-100,表示只计算标签位置的loss   tokenized_inputs['labels'] = [-100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, 488, -100, -100, -100, -100, -100, 481]        indices = [11,  17]  # 这些是 step_tag_id 在 input_ids 中的索引   candidate_tokens = [488, 481]   # 表示步骤 1 和 步骤 2 的 label id,其中 488 表示正例,481 表示负例

接下来是构建 attention_mask,需要将标签位置 mask 掉,使得模型在计算注意力时忽略这些位置。

具体原因?

attention_mask 经过处理后的结果如下,其中位置为 0 的部分是标签位置。

`tokenized_inputs['attention_mask'] = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0]` 

在得到 input_id, label, attention_mask 之后,模型输入的完整数据如下:

tokenized_inputs = {       'input_ids': [101, 102, 101, 103, 104, 105, 106, 101, 102, 101, 103, 76325, 103, 102, 103, 104, 107, 76325],         'labels': [-100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, 488, -100, -100, -100, -100, -100, 481],       'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0]    }

整个数据处理的代码如下所示:

good_token = '+'   bad_token = '-'   step_tag = '\n\n\n\n\n' #ки   step_tag2 = '\n\n'      def preprocess_function(example):       input = f"{example['question']} {example['process']}"       tokenized_inputs = tokenizer(           input,            truncation=True,            padding='max_length',            # padding=True,           max_length=2048,       )              def find_all_indices(lst, element):           return [i for i, x in enumerate(lst) if x == element]              length = len(tokenized_inputs['input_ids'])       # print(length)       indices = find_all_indices(tokenized_inputs['input_ids'],step_tag_id)              if len(indices) != len(example['label']):           # print(example)           example['label'] = example['label'][:len(indices)]              assert len(indices) == len(example['label'])              tokenized_inputs['labels'] = [-100] * length       # tokenized_inputs['attention_mask'] = [1] *length       # print(len(indices))       for i in range(len(indices)):           if example['label'][i] == '+' or example['label'][i] == 1:               tokenized_inputs['labels'][indices[i]] = candidate_tokens[0]           elif example['label'][i] == '-' or example['label'][i] == 0:               tokenized_inputs['labels'][indices[i]] = candidate_tokens[1]           else:               raise ValueError('label is wrong')           tokenized_inputs['attention_mask'][indices[i]] = 0       # tokenized_inputs['labels'] = [-100] *(length-1) + tokenized_inputs['input_ids'][length-1:]              return tokenized_inputs

在将输入数据转化为模型输入之后,就可以训练 PRM。训练完成之后,就可以用来对每个步骤进行打分。

接下来介绍下 PRM 是如何对每个步骤进行打分。

利用 PRM 对每个步骤打分

假设模型的 logits 输出如下所示:

gold = tensor([1, 0]) # 如果标签是488,则gold为1,否则为0   logits = torch.tensor([       # steps1       [           [0.1, 0.2, 0.3, ...],  # 第一个位置的logits           [0.4, 0.5, 0.6, ...],  # 第二个位置的logits           [0.7, 0.8, 0.9, ...]   # 第三个位置的logits           ...       ],       # steps2       [           [0.1, 0.2, 0.3, ...],  # 第一个位置的logits           [0.4, 0.5, 0.6, ...],  # 第二个位置的logits           [0.7, 0.8, 0.9, ...]   # 第三个位置的logits,          ...       ]   ])   # (batch_size, sequence_length, vocab_size)

接下来需要找到标签位置的 logits,结果如下:

`# 找到标签位置的logits   logits = tensor([       [logits[0, 11, 481], logits[0, 11, 488]],  # 第一个是为负的概率(token id=481),第二个是为正的概率(token id=482)11表示步骤标签在input中的位置       [logits[0, 17, 481], logits[0, 17, 488]]  # 第一个是为负的概率,第二个是为正的概率。17表示步骤标签在input中的位置   ])`  

获取到标签位置的 logits 输出后,接下来就可以计算每个步骤的正确概率:

# 计算softmax概率:   prob = torch.softmax(logits, dim=-1)  # prob = tensor([[0.3, 0.7], [0.8, 0.2]])  # 计算softmax后的概率      # 返回概率和gold标签:   return prob[:, 1]  # (tensor([0.7, 0.8]), tensor([0.8, 0.2]))  # 返回每个步骤的 PRM 打分

因此,对于前面的例子,PRM 对步骤的打分为 0.8,对步骤的打分为 0.2。

下面是具体的代码实现:

def preprocess_logits_for_metrics(logits,labels):       # return logits,labels       labels_index = torch.argwhere(torch.bitwise_or(labels == candidate_tokens[0], labels == candidate_tokens[1]))       gold = torch.where(labels[labels_index[:, 0], labels_index[:, 1]] == candidate_tokens[1], 0, 1)       # labels_index[: , 1] = labels_index[: , 1] - 1       logits = logits[labels_index[:, 0], labels_index[:, 1]][:, [candidate_tokens[1], candidate_tokens[0]]]       prob = torch.softmax(logits, dim=-1)       return prob[:, 1], gold

3.5 PRM 如何推理

PRM 训练好后,接下来的问题是如何推理,为每个步骤进行打分。下面是一个具体例子:

question = "Janet\u2019s ducks lay 16 eggs per day. She eats three for breakfast every morning and bakes muffins for her friends every day with four. She sells the remainder at the farmers' market daily for $2 per fresh duck egg. How much in dollars does she make every day at the farmers' market?"   output1 = "Step 1: Janet's ducks lay 16 eggs per day. \n\n\n\n\n Step 2: She eats three for breakfast every morning, so she has 16 - 3 = 13 eggs left. \n\n\n\n\n Step 3: She bakes muffins for her friends every day with four eggs, so she has 13 - 4 = 9 eggs left. \n\n\n\n\n Step 4: She sells the remainder at the farmers' market daily for $2 per fresh duck egg, so she makes 9 * $2 = $18 every day at the farmers' market. The answer is: 18 \n\n\n\n\n" # 18 is right   output2 = "Step 1: Janet's ducks lay 16 eggs per day. \n\n\n\n\n Step 2: She eats three for breakfast every morning, so she has 16 - 3 = 13 eggs left. \n\n\n\n\n Step 3: She bakes muffins for her friends every day with four eggs, so she has 13 - 4 = 9 eggs left. \n\n\n\n\n Step 4: She sells the remainder at the farmers' market daily for $2 per fresh duck egg, so she makes 9 * $2 = $17 every day at the farmers' market. The answer is: 17 \n\n\n\n\n" # 17 is wrong         for output in [output1,output2]:   # for output in [output1, output2,output3]:       input_for_prm = f"{question} {output}"       input_id = torch.tensor([tokenizer.encode(input_for_prm)])       # print(input_id)          with torch.no_grad():           logits = model(input_id).logits[:,:,candidate_tokens]           # print(logits)           scores = logits.softmax(dim=-1)[:,:,0]            # print(scores)           step_scores = scores[input_id == step_tag_id]                      print(step_scores)          # tensor([0.9955, 0.9958, 0.9983, 0.9957])   # tensor([0.9955, 0.9958, 0.9983, 0.0240])

OpenAI 评估 PRM 效果

OpenAI 在论文 Let’s Verify Step by Step 中详细地评估了 PRM 的效果,并和 ORM 以及 Majority Vote 做了对比。下面是评测结果(其中 OOD 表示评测数据是训练数据分布之外):

|
| ORM | PRM | Majority Vote |
| — | — | — | — |
| % Solved (Best-of-1860) | 72.4 | 78.2 | 69.6 |
| 微积分(OOD) | 68.9 | 86.7 | 80.0 |
| 化学 | 68.9 | 80.0 | 71.7 |
| 物理 | 77.8 | 86.7 | 82.2 |
| ACM | 49.1 | 53.2 | 32.8 |

PRM 在各项评测上超过了 ORM,而且在分布之外显著优于 ORM,表明了 PRM 良好的泛化性。

如何学习大模型 AI ?

由于新岗位的生产效率,要优于被取代岗位的生产效率,所以实际上整个社会的生产效率是提升的。

但是具体到个人,只能说是:

“最先掌握AI的人,将会比较晚掌握AI的人有竞争优势”。

这句话,放在计算机、互联网、移动互联网的开局时期,都是一样的道理。

我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。

我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在人工智能学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多互联网行业朋友无法获得正确的资料得到学习提升,故此将并将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。

在这里插入图片描述

第一阶段(10天):初阶应用

该阶段让大家对大模型 AI有一个最前沿的认识,对大模型 AI 的理解超过 95% 的人,可以在相关讨论时发表高级、不跟风、又接地气的见解,别人只会和 AI 聊天,而你能调教 AI,并能用代码将大模型和业务衔接。

  • 大模型 AI 能干什么?
  • 大模型是怎样获得「智能」的?
  • 用好 AI 的核心心法
  • 大模型应用业务架构
  • 大模型应用技术架构
  • 代码示例:向 GPT-3.5 灌入新知识
  • 提示工程的意义和核心思想
  • Prompt 典型构成
  • 指令调优方法论
  • 思维链和思维树
  • Prompt 攻击和防范

第二阶段(30天):高阶应用

该阶段我们正式进入大模型 AI 进阶实战学习,学会构造私有知识库,扩展 AI 的能力。快速开发一个完整的基于 agent 对话机器人。掌握功能最强的大模型开发框架,抓住最新的技术进展,适合 Python 和 JavaScript 程序员。

  • 为什么要做 RAG
  • 搭建一个简单的 ChatPDF
  • 检索的基础概念
  • 什么是向量表示(Embeddings)
  • 向量数据库与向量检索
  • 基于向量检索的 RAG
  • 搭建 RAG 系统的扩展知识
  • 混合检索与 RAG-Fusion 简介
  • 向量模型本地部署

第三阶段(30天):模型训练

恭喜你,如果学到这里,你基本可以找到一份大模型 AI相关的工作,自己也能训练 GPT 了!通过微调,训练自己的垂直大模型,能独立训练开源多模态大模型,掌握更多技术方案。

到此为止,大概2个月的时间。你已经成为了一名“AI小子”。那么你还想往下探索吗?

  • 为什么要做 RAG
  • 什么是模型
  • 什么是模型训练
  • 求解器 & 损失函数简介
  • 小实验2:手写一个简单的神经网络并训练它
  • 什么是训练/预训练/微调/轻量化微调
  • Transformer结构简介
  • 轻量化微调
  • 实验数据集的构建

第四阶段(20天):商业闭环

对全球大模型从性能、吞吐量、成本等方面有一定的认知,可以在云端和本地等多种环境下部署大模型,找到适合自己的项目/创业方向,做一名被 AI 武装的产品经理。

  • 硬件选型
  • 带你了解全球大模型
  • 使用国产大模型服务
  • 搭建 OpenAI 代理
  • 热身:基于阿里云 PAI 部署 Stable Diffusion
  • 在本地计算机运行大模型
  • 大模型的私有化部署
  • 基于 vLLM 部署大模型
  • 案例:如何优雅地在阿里云私有部署开源大模型
  • 部署一套开源 LLM 项目
  • 内容安全
  • 互联网信息服务算法备案

学习是一个过程,只要学习就会有挑战。天道酬勤,你越努力,就会成为越优秀的自己。

如果你能在15天内完成所有的任务,那你堪称天才。然而,如果你能完成 60-70% 的内容,你就已经开始具备成为一名大模型 AI 的正确特征了。

这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值