本文将从两个常见的大模型翻车问题入手解析这些问题背后体现的大模型技术原理,并解释了为什么会导致这些问题,接着我们利用CoT(思维链)方法解决这些问题并基于上述原理试图剖析CoT方法起作用的可能原因,最后提出【理由先行】风格这一简单有效的Prompt Trick。
作为大模型落地相关的算法工程师,随着大模型能力的快速发展,Prompt工程成为了越发重要的一项技能。
在2023年下半年,我们遵循着Prompt调优—微调训练调优的方式进行着大模型落地的探索,也探索出了RAG/NL2SQL等多种较为适合大模型落地的场景;而到了2024年上半年,Agent从概念逐渐演变成了切实可行的落地场景,而在通用大模型能力的不断突破下,微调调优在算力、时间、精力上的重投入显得更不讨喜,尤其是大部分项目仍处于需要快速验证想法的POC阶段,由此我们改换成了Prompt调优—Agent工作流搭建的方式,可以迅速验证大模型在各行各业场景的效果。
显然,无论处于哪个阶段,Prompt工程都将是最初也最为重要的一个步骤,Prompt是你与大模型之间沟通的唯一桥梁,也是大模型带领你云游四海的船票。
所以,我想开设一个专题【Way To Prompt】,用于记录前沿学习、项目实践过程中接触到的Prompt方法、结构化框架以及各类Tricks,梳理各类在实际业务中可以发挥价值的Prompt及其背后的生效原理。
本文简述:本文将从两个常见的大模型翻车问题入手解析这些问题背后体现的大模型技术原理(Tokenization与预测下一个Token),并解释了为什么会导致这些问题,接着我们利用CoT(思维链)方法解决这些问题并基于上述原理试图剖析CoT方法起作用的可能原因,最后提出【理由先行】风格这一简单有效的Prompt Trick。
前段时间,我们经常能在互联网上看到有关大模型“翻车”的常见问题,其中最为典型的莫过于“Strawberry有几个r数不对”以及“9.9和9.11比大小”。
这些问题一度引发了互联网上大模型社区的广泛讨论,大家一定很好奇,为什么大模型可以完成个别复杂的推理任务,甚至可以在数学、法律等各类行业的高等级考试中获得优异成绩,却在如此简单的问题上接连翻车呢?
那么本篇文章便试着从这一角度入手,理解大模型为什么在该类任务上的表现不佳,以及基于这一原因所引发的思考与更适合的Prompt实践方式。
首先,我们来验证大模型是否真的无法解决上述的两个问题:
通义千问:
GPT-4o:
Claude3 Sonnet:
可以看到,截止到目前三者之间仅有Claude3回答正确,而GPT-4o和千问依旧无法数对r的数量,并且千问的解释也很奇怪,它明明可以看到rr里面有两个r,为什么却数不对呢?
并且值得一提的是,测试Claude3.5 Sonnet时我们发现它也数不对。
通义千问:
GPT-4o:
Claude3 Sonnet:
上一个问题Claude3答对了,而这个问题则是无一例外地全部翻车,那么大模型究竟为什么无法解决这些对于人类来说如此简单的问题呢?
这两类大模型会“犯蠢”的问题背后,实际上是大模型的技术原理天然带来的“副作用”,比如我们先从“Strawberry有几个r数不对”这一问题入手。
大家对这件事的普遍定论是,这是文本的Tokenization处理所带来的问题。
所谓的Tokenization,即是将文本处理为Token的过程,很多时候也翻译为“分词”,不过需要注意的是,“分词”这一翻译本身具有一定的误导性,这里分好的“词”并不能直接对应我们“人类视角”的【单词/短语/词组】,而是“大模型视角”的【Token】,这一部分我会基于之前内部培训时我所制作的PPT进行说明。
由于大模型无法直接处理文本,因此需要将文本转化为大模型可以处理的数字,转换过程可以简化为以下几步:输入文本序列,对序列进行切分成为Token序列,再构建词典将每个Token映射为整数型的index。
这一过程中,主要的难点在于,如何理想地对文本序列进行切分。如果我们切分的粒度太细,比如水果被切分为水和果,那么水果这一词语在大模型眼里就丧失了其作为一个词语的整体语义。如果我们切分的粒度太粗,比如我喜欢吃水果整体是一个token,那么这样切分所得到的字典规模可想而知是相当大的。所以一个好的切分应该是,使得文本中的每个Token都拥有正确的表义,且不会存在字典容易过大或在字典中找不到相应token的问题。
根据以上描述可知,“大模型视角”下的文本与我们“人类视角”并不一样。为了验证这一点,我们可以在DashScope提供的Token计算器页面观察“大模型视角”下的文本是如何拆解的。
以Strawberry为例,其被拆分为【“str”, “aw”, “berry”】,自然也就不难理解为什么通义千问对于这个问题的回答是“两个r”了,毕竟对于它来说,这三者均是作为一个整体存在的,而不是从字母维度切分的。
为了让人们更好地理解“大模型视角”与“人类视角”之间的差异,前OpenAI创始成员与研究科学家Karpathy在X上放出了一个脚本,用于将Token转换为Emoji表情,从而更加直观地向大家展示大模型眼中的文本是什么样的。
(脚本网址:https://x.com/karpathy/status/1816637781659254908)
这里依旧以“Strawberry里有几个r?”问题为例,可以看到在大模型的眼中,这个问题其实是这样的:
“数r”这一问题从人类视角显而易见,但在大模型视角却是有难度的。由此也就很容易理解,为什么大模型在缺乏引导的情况下无法完成“数r”这种对于人类来说无比简单的任务。
而另一个比大小问题,我们则要从大模型的生成原理角度去考虑。
我们知道,大模型(Large Language Model,LLM)的含义为大型语言模型。
“大型”很好理解,而所谓的“语言模型”,也就是用于计算文本序列概率的模型,更通俗地说,用于计算一个文本序列是一句“人话”的概率。那么换个角度来说,“语言模型”也就可以在给定上文的情况下,计算下一个Token取各种值的概率,所以大模型的生成过程本质上便是在“根据上文预测下一个Token”,而这个概率分布即是在模型训练过程中从大量的文本数据中学习到的,使得大模型学习到了语言的基本知识与模式。
在理解上述原理的基础上,“9.9和9.11比大小”翻车的原因也就容易理解了,若是训练时存在着许多版本号相关的数据,那么从版本号的角度来看,9.11确实要比9.9大,大模型也就很可能会根据这种概率分布得出“9.11比9.9要更大”的结论。
其实解决这两类问题的方法也并不复杂,只需要在Prompt中加入一定的引导就能获得理想答案,比如说我们可以利用CoT思维链的方式编写Prompt,引导大模型逐步思考并解决问题。
CoT为思维链(Chain-of-Thought)的缩写简称,是提示工程领域最为重要的提示方法之一,它的核心思路在于通过引导模型逐步展示其推理过程,从而提高其理解和解决复杂问题的能力。在Few-shot(少样本)设置下表现为 在提供的样例中解释推理过程,引导大模型回答时也解释推理过程;而在Zero-shot(零样本)设置下表现为 加入类似“让我们一步步思考(Let’s think step by step)”的引导话术。
而对于本文提到的两个翻车问题,我们可以尝试这样编写Prompt:
Strawberry里有几个r?请你先将单词拆分成一个个字母,再用0和1分别标记字母列表中非r和r的位置,数一下一共有几个r
请一步步思考,以逐级复杂的原则思考问题,最后才得到答案。9.9和9.11谁大?
可以看到,通过CoT方法编写Prompt后,可以引导大模型输出正确结果。
有趣的是,第二个问题的Prompt虽然可以帮助大模型获得正确的结果,但却不是始终有效的,多次尝试很可能会获得不同的结论。而将“9.9和9.11谁大?”切换成英文的“9.11 and 9.9, which is bigger?”时大模型更容易答对,这里本质上也体现了中英文训练数据的差别对概率分布的影响以及大模型预测下一个Token是一个采样过程。
然而随之而来的问题是,为什么这么做可以提升大模型的表现呢?这个问题想必大家也十分好奇,基于上面所述的大模型原理,其实也不难理解。
如果说,大模型每一次生成是在“根据上文预测下一个Token”的话,那么CoT的作用便在于,它可以引导大模型先行生成有力的推理过程,比如引导其生成Strawberry拆解后的字母列表,或者数字的逐位比较,这样模型在生成最终结论的时候,会受到上文推理过程的影响,将下一个Token的概率分布限定在某个范围内,从而大大增加输出正确答案的概率。
可以将该过程想象成下图:
试想,当你遇到一个问题,随着你的不断思考,你为获得正确答案建立了更多的“论据”,从而不断地增加你对某一个答案的置信度;而这与你直接在脑中海量的答案池中进行一次选择或预测相比显然要更加可靠。
而若是不用CoT加以限制的的情况下,大模型倾向于先给出一个答案,再给出理由,那么很自然地变成了,先根据“问题”预测一个最有可能的“答案”,再为这个“答案”编造相应的“理由”,毕竟大模型的生成过程是逐步进行的,而不是全局性的,它在答案之后输出的理由永远只可能是为了圆“答案”这个谎而存在的。
这是人类语言的重要特性,也即时序性与线性,说到这不禁让我想到电影《降临》中生活在四维空间的七肢桶,它们的“字”是一笔写就的,包含一段完整的语义,前因后果均包含在这一个“字”内,也就不像人类的文字一般带有前与后的时序属性,而我们的语言方式天然蕴含着逻辑与时序关系,这一点在大模型身上也会有所体现。同时,我们人类在思考的时候,通常也是会先有推理过程,再有结论,只不过在诉诸于语言时,可能会选择 先抛出结论表明立场,再给出解释 的表达方式,而思考过程对外界是不可见的,但是对于大模型而言,它的语言本身即是思考,并不存在诉诸于语言之前的思考过程,所以我们也需要引导它像人类一样先思考再判断,将思考过程以语言的方式表达出来。
回归正题,从这个角度来看很自然地可以想到,在大模型推理的时候,我们可以限制大模型 先输出理由,再输出答案,让大模型根据“深度思考”获得的理由与推理过程来预测“答案”,从而大大提升其表现,我认为这也是为什么CoT会有效果的一个关键因素。
与这一想法不谋而合的是,东京大学的一项研究《A Better LLM Evaluator for Text Generation: The Impact of Prompt Output Sequencing and Optimization》中也验证了类似的观点,这篇论文的理念为利用大模型评估AI的生成文本质量,而在论文的实验部分他们发现,要求大模型先给出评分理由,再给出分数 与 要求大模型先给出分数,再给出评分理由两种做法的结果大不相同,前者所给出的分数普遍高于后者,他们认为这与LLM的自回归生成特性有关,当模型先给出理由时,它能够更全面地考虑输入的提示和自己生成的理由,从而做出更加深思熟虑的评分。
换句话说,“理由先行”的输出风格或许有助于大模型给出更加可靠的答案,这一点与我们之前的推论也相吻合,符合大模型预测下一个Token的原理。
基于以上原因,我在实际的项目实践当中,也广泛采用了“理由先行”的输出风格,比如下方的Prompt是我为某个物流行业公司的业务场景所编写的,该场景实测发现,“理由先行”的方式确实更有助于大模型更有效地进行思考与预测,取得更优异的表现。
Qwen-Max/Plus默认参数Prompt
ori_system_prompt = dedent("""\
# 角色
你是一位高效的物流分配专家,擅长根据客户提供的地址信息,精确匹配至相应的地址分组,具备快速识别地址模式与分组规则对应的能力。
## 技能
### 技能1: 地址匹配逻辑
- **提供订单分组时的分组判断**:当订单分组和签收分组同时提供时,请对比客户地址与两个分组中的每个地址记录,寻找完全匹配或最接近匹配的条目,判断客户地址更可能属于哪个分组,若不属于任一分组则判断为“无”;
- **未提供订单分组时的分组判断**:若订单分组未给定,请对比客户地址与签收分组中的每个地址记录,判断客户地址是否属于该签收分组,若不属于则判断为“无”;
- **输出判断置信度**:当你给出一个判断时,请你根据你对该判断的确定程度给出一个置信度,置信度的范围为[0,1];
### 技能2: 结构化输出处理
- 根据分组判断,**请解释你的推理过程,并以JSON格式输出对应的分组名称以及该判断的置信度**
## 约束
- 请以JSON格式返回分组名称,示例为```json{"group_name": "***", "confidence": "***"}```。
- 分组判断严格基于地址文字的匹配,不考虑地理临近性。
- 忽略地址中的无关细节,如门牌号差异,专注于街道、社区等关键信息匹配。
- 若客户地址同时与订单分组和签收分组较为接近,请分别分析订单分组与签收分组中地址的主要特点,基于这些特点判断客户地址最可能属于哪一分组。
## 输入输出样例:
客户地址:
[客户地址]
订单分组:
[分组名称]
[地址示例]
签收分组:
[分组名称]
[地址示例]
输出:
**我的推理过程是:** 对订单分组中的地址分析可知...
因此,分组结果为```json{"group_name": "[分组名称]", "confidence": "[置信度]"}```
现在,请根据以上规则判断以下客户地址的分组情况。
""")
可以看到,我对大模型强调“请解释你的推理过程,并以JSON格式输出对应的分组名称以及该判断的置信度”,并在示例处让其先输出“我的推理过程是:”,从而保证让其先输出推理过程以及充足的判断理由,再输出最终的JSON答案。
当然,这只是一种实现思路,只要掌握该思想,可以通过各种方式融入到自己的业务Prompt当中。
建议大家在Prompt的编写与调优过程当中,也可以试着践行“理由先行”的风格,让大模型先输出判断的理由,再输出最终的答案,防止模型陷入瞎编答案再圆谎的恶性循环当中,或许会有奇效。
以上,即为本篇文章的全部内容,感谢您的耐心阅读!
参考
文章原始思路源于机器之心文章,在此基础上进一步联想深挖,将【预测下一个Token、CoT、理由先行】等概念串联起来。主要参考文献如下:
https://arxiv.org/html/2406.09972v1