大模型提示工程(Prompt Engineering)

目录

1、大模型的涌现能力

2、大模型应用的关键技术—提示工程(Prompt Engineering)

2.1、One-shot & Few-shot提示学习法

2.2、通过思维链提示法提升模型推理能力

2.2.1、Zero-shot-CoT提示方法

2.2.2、Few-shot-CoT提示方法

3、CoT改良方法:LEAST-TO-MOST PROMPTING(LtM提示法)

3.1、Least-to-Most Prompting基本概念

3.2、Zero-shot-MtL提示过程

1、大模型的涌现能力

        大语言模型有一个有趣的现象:只有当模型达到一定规模时,某些能力才会显现。多模型在规模达到一定程度时,在准确度等某些性能指标上会出现急剧的变化,甚至模型会突然有能力解决在规模较小时完全无法解决的问题。这种现象被称为涌现(emergence)。

  GPT-4是目前“涌现能力”相对最好的大语言模型,即哪怕模型未经特定任务的训练,但在适当的提示下,仍然能够解决某些特定领域的问题。例如大语言模型可以解答数学问题、辅助进行编程、甚至是进行问答等,其实都属于模型的涌现能力。而为何类似数学能力是模型的“涌现能力”,其实原因也并不复杂——作为概率模型,大语言模型甚至不知道数字代表的真实含义,模型只是在学习了无数的语料之后,发现了一些数学结论之间的潜在概率关系,才最终涌现出了数学运算或者复杂推理的能力。可能很难想象,能够进行问答,其实也是大语言的涌现能力。大语言模型的训练目标目标是生成或预测文本,而不是进行问答。

2、大模型应用的关键技术—提示工程(Prompt Engineering)

  不过需要注意的是,大模型的这种“涌现能力”其实并不稳定,在不修改模型本身参数(微调)的情况下,模型涌现能力极度依赖对模型的提示过程,即对同样一个模型,不同的提示方法将获得质量完全不同的结果。而一个完整的用户和大语言模型的交互流程,也被称为大语言模型的提示工程(Prompt Engineering),根据此前的描述我们不难理解,提示工程是激发模型涌现能力(激发模型潜力)的非常关键的技术。同时,由于我们对大语言模型“涌现能力”的应用要求是远远多于简单的使用大模型进行文本创建的(毕竟哪怕是对话任务都属于大模型涌现能力的范畴),因此提示工程这一专门用于激发大语言模型涌现能力的技术就变得尤其重要。这也是为何自GPT大模型爆火之后,提示工程便成了非常热门的科研方向,同时提示工程技术也成了大模型应用工程师必不可少的技能。

  从技术角度来说,提示工程其实是一个易学习门槛很低、但同时技术难度上限又很高的技术。提示工程简单的应用的话,只需要添加一些提示词后缀、或者把问题描述的更加详细即可,而复杂的提示工程,则会涉及多段嵌套提示和极具创造力的围绕中间结果的问答设计等。

  • 提示工程内容侧重点:解决复杂语义理解问题

  在正式介绍提示工程方法之前需要说明的是,围绕不同类型的问题,其实是有不同种类的提示工程方法的,例如创造AIGC内容的提示策略(例如批量写稿)和提升模型复杂语义能力的提示策略(例如进行更好的问答、编写SQL代码、理解具体场景的业务关系等)就是两类差别较大的提示工程方法,前者侧重于引导模型创建更加符合要求的文本,而后者则要求模型具备一定的学习和推理能力。课程中我们将重点介绍提升模型复杂语义理解能力的提示工程方法,当然,提升模型复杂语义理解能力,相信也是大多数数据技术相关从业者最急迫的需求。

有一个和提示工程类似的概念名为提示学习(Prompt Learning),提示学习本身也是一种提示工程的手段,和普通的提示工程有所不同的是,提示学习我们不仅提供目标任务的提示,还让模型自我学习生成有效的提示。在训练过程中,模型将尝试生成各种不同的提示,并通过它们产生的结果来评估这些提示的有效性。最终,模型将学习到哪些提示可以生成最准确的答案。

  • 复杂语义理解能力的试金石——解决推理问题

  而如何验证模型是否具备理解复杂语义的能力?有个非常简单的方法,即观察模型是否能解决复杂逻辑推理问题。若通过模型能够在提示工程引导下,解决原始状态下无法解决的推理问题,则说明提示工程能够提升模型的推理能力,并且越有效的提示工程对模型推理能力提升幅度越大,这点能够通过设置不同复杂程度的推理问题来进行验证。

import os
from openai import OpenAI
client = OpenAI(
    api_key=os.environ.get("OPENAI_API_KEY")
)

推理题1:

prompt_1 = '罗杰有五个网球,他又买了两盒网球,每盒有3个网球,请问他现在总共有多少个网球?'
response = client.chat.completions.create(
    messages=[
        {
            "role": "user",
            "content": prompt_1,
        }
    ],
    model="gpt-3.5-turbo",
    max_tokens=1000,
)
print(response.choices[0].message.content)
output:'罗杰原来有5个网球,又买了2盒,每盒有3个网球,所以总共有5 + 2 × 3 = 11个网球。'

能够发现,此时模型推理得到了正确的结果,罗杰目前总共由5+2×3=11个网球。

推理题2:

prompt_2 = '食堂总共有23个苹果,如果他们用掉20个苹果,然后又买了6个苹果,请问现在食堂总共有多少个苹果?'
response = client.chat.completions.create(
    messages=[
        {
            "role": "user",
            "content": prompt_2,
        }
    ],
    model="gpt-3.5-turbo",
    max_tokens=1000,
)
print(response.choices[0].message.content)
output:'食堂现在总共有多少个苹果 = 23 - 20 + 6 = 9\n\n所以,食堂现在总共有9个苹果。'

第二个逻辑题比第一个逻辑题稍微复杂一些,复杂之处在于逻辑上稍微转了个弯,即食堂不仅增加了6个苹果,而且还消耗了20个苹果。有增有减,大模型也可以做出了正确判断。

推理题3:

prompt_3 = '杂耍者可以杂耍16个球。其中一半的球是高尔夫球,其中一半的高尔夫球是蓝色的。请问总共有多少个蓝色高尔夫球?'
response = client.chat.completions.create(
    messages=[
        {
            "role": "user",
            "content": prompt3,
        }
    ],
    model="gpt-3.5-turbo",
    max_tokens=1000,
)
print(response.choices[0].message.content)
output:'假设一共有16个球,其中一半是高尔夫球,也就是8个高尔夫球。又因为一半的高尔夫球是蓝色的,所以蓝色高尔夫球的数量是8个的一半,即4个。\n\n所以总共有4个蓝色高尔夫球。'
response_3 = openai.Completion.create(
            model="gpt-3.5-turbo-instruct",
            prompt=prompt_3,
            max_tokens=1000,
            )
print(response_3["choices"][0]["text"].strip())
output:'总共有8个蓝色高尔夫球。'

第三个逻辑题的数学计算过程并不复杂,但却设计了一个语言陷阱,即一半的一半是多少。能够发现,gpt-3.5-turbo模型已经可以很好的分析这个问题,但是上一个版本的gpt-3.5-turbo-instruct模型无法围绕这个问题进行准确的判断(这个模型现在已经无法调用只保留历史数据作参考,其调用格式也已经过时,下同),正确答案应该是16*0.5*0.5=4个蓝色高尔夫球。

推理题4:

prompt_4 = '艾米需要4分钟才能爬到滑梯顶部,她花了1分钟才滑下来,水滑梯将在15分钟后关闭,请问在关闭之前她能滑多少次?'
response = client.chat.completions.create(
    messages=[
        {
            "role": "user",
            "content": prompt_4,
        }
    ],
    model="gpt-3.5-turbo",
    max_tokens=1000,
)
print(response.choices[0].message.content)
output:'在滑梯关闭前,她能够滑4次。因为她滑下来后需要等3分钟(15分钟-1分钟-4分钟=10分钟),她可以在这段时间内再次爬上滑梯并滑下来。所以在滑梯关闭前,她能滑4次。'
response_4 = openai.Completion.create(
            model="gpt-3.5-turbo-instruct",
            prompt=prompt4,
            max_tokens=1000,
            )
print(response_4["choices"][0]["text"].strip())
output:'她能滑14次。'

第四个逻辑题是这些逻辑题里数学计算过程最复杂的,涉及多段计算以及除法运算。正确的计算过程应该是先计算艾米一次爬上爬下总共需要5分钟,然后滑梯还有15分钟关闭,因此关闭之前能够再滑15 / 5 = 3次。

  综上来看,"gpt-3.5-turbo-instruct"在Zero-shot的情况下,逻辑推理能力较弱,只能围绕相对简单的、只有线性运算过程的推理问题进行很好的解答,总的来看模型只正确回答了第一个问题,其他问题都答错了,模型的推理能力堪忧。"gpt-3.5-turbo"在Zero-shot的情况下,逻辑推理能力尚可,可以很好的解答简单的推理问题,当需要进行复杂判断的问题时会出现错误。

2.1、One-shot & Few-shot提示学习法

  首先,最为简单的提示工程的方法就是通过输入一些类似问题和问题答案,让模型参考学习,并在同一个prompt的末尾提出新的问题,依次提升模型的推理能力。这种方法也被称为One-shot或者Few-shot提示方法。One-shot和Few-shot最早由OpenAI研究团队在论文《Language Models are Few-Shot Learners》中率先提出,这篇论文也是提示工程方法开山鼻祖,不仅介绍了提示工程的两大核心方法,同时也详细介绍这么做背后的具体原因。

注,由于One-shot和Few-shot的区别仅在于提示词中包含的示例个数,但本质上都是先在提示词中输入示例,然后让模型仿造示例解答当前的问题,因此为了表达方面,后续课程中不再区分One-shot和Few-shot,而是统一称呼为Few-shot。这也是近两年来业内逐渐统一的称呼规范。

  不过就具体的应用来说,Few-shot提示方法并不复杂,我们只需要将一些类似的问题的问题+答案作为prompt的一部分进行输入即可。例如我们首先把模型能够正确回答的第一个例子作为提示词输入,查看能否顺利推理出第二个问题:

prompt_Few_shot_1 = 'Q:“罗杰有五个网球,他又买了两盒网球,每盒有3个网球,请问他现在总共有多少个网球?” \
                  A:“现在罗杰总共有11个网球。” \
                  Q:“食堂总共有23个苹果,如果他们用掉20个苹果,然后又买了6个苹果,请问现在食堂总共有多少个苹果?” \
                  A:'
print(prompt_Few_shot_1)
output:'Q:“罗杰有五个网球,他又买了两盒网球,每盒有3个网球,请问他现在总共有多少个网球?”         
 A:“现在罗杰总共有11个网球。”                   
 Q:“食堂总共有23个苹果,如果他们用掉20个苹果,然后又买了6个苹果,请问现在食堂总共有多少个苹果?”                   
 A:'

注意这里的进行Few-shot时候提示的编写格式,当我们需要输入多段问答作为提示词时,往往以Q作为问题的开头、A作为回答的开头(这里也可以换成“问题”、“答案”),并且不同的问答对话需要换行以便于更加清晰的展示,具体方法是通过转义符+换行来完成,这样换行之后仍然还在一个字符串内。

response = client.chat.completions.create(
    messages=[
        {
            "role": "user",
            "content": prompt_Few_shot_1,
        }
    ],
    model="gpt-3.5-turbo",
    max_tokens=1000,
)
print(response.choices[0].message.content)
output:'"现在食堂总共有9个苹果。"'
response_Few_shot_1 = openai.Completion.create(
                     model="gpt-3.5-turbo-instruct",
                     prompt=prompt_Few_shot_1,
                     max_tokens=1000,
                     )
print(response_Few_shot_1["choices"][0]["text"].strip())
output:'”现在食堂总共有9个苹果。”'

虽然无法确定模型预测过程发生了何种变化,但在学习了第一个例子之后,模型确实能够对第二个问题做出准确判断。能够发现Few-shot在提升模型逻辑推理能力方面能够起到一定作用。

  接下来我们将上面两个例子的问答都作为提示词进行输入,并查看模型是否能正确回答第三个问题:

prompt_Few_shot_2 = 'Q:“罗杰有五个网球,他又买了两盒网球,每盒有3个网球,请问他现在总共有多少个网球?” \
                  A:“现在罗杰总共有11个网球。” \
                  Q:“食堂总共有23个苹果,如果他们用掉20个苹果,然后又买了6个苹果,请问现在食堂总共有多少个苹果?” \
                  A:“现在食堂总共有9个苹果。” \
                  Q:“杂耍者可以杂耍16个球。其中一半的球是高尔夫球,其中一半的高尔夫球是蓝色的。请问总共有多少个蓝色高尔夫球?” \
                  A:'
print(prompt_Few_shot_2)
output:'Q:“罗杰有五个网球,他又买了两盒网球,每盒有3个网球,请问他现在总共有多少个网球?”                   
A:“现在罗杰总共有11个网球。”                   
Q:“食堂总共有23个苹果,如果他们用掉20个苹果,然后又买了6个苹果,请问现在食堂总共有多少个苹果?”                   
A:“现在食堂总共有9个苹果。”                   
Q:“杂耍者可以杂耍16个球。其中一半的球是高尔夫球,其中一半的高尔夫球是蓝色的。请问总共有多少个蓝色高尔夫球?”                  A:'
response = client.chat.completions.create(
    messages=[
        {
            "role": "user",
            "content": prompt_Few_shot_2,
        }
    ],
    model="gpt-3.5-turbo",
    max_tokens=1000,
)
print(response.choices[0].message.content)
output:'“总共有4个蓝色高尔夫球。”'
response_Few_shot_2 = openai.Completion.create(
                     model="gpt-3.5-turbo-instruct",
                     prompt=prompt_Few_shot_2,
                     max_tokens=1000,
                     )
print(response_Few_shot_2["choices"][0]["text"].strip())
output:'”总共有8个蓝色高尔夫球。”'

能够发现"gpt-3.5-turbo-instruct"模型对第三个问题仍然回答错误。接下来尝试把前两个问题作为提示词的一部分,让模型回答第四个问题:

prompt_Few_shot_3 = 'Q:“罗杰有五个网球,他又买了两盒网球,每盒有3个网球,请问他现在总共有多少个网球?” \
                  A:“现在罗杰总共有11个网球。” \
                  Q:“食堂总共有23个苹果,如果他们用掉20个苹果,然后又买了6个苹果,请问现在食堂总共有多少个苹果?” \
                  A:“现在食堂总共有9个苹果。” \
                  Q:“艾米需要4分钟才能爬到滑梯顶部,她花了1分钟才滑下来,水滑梯将在15分钟后关闭,请问在关闭之前她能滑多少次?” \
                  A:'
print(prompt_Few_shot_3)
output:'Q:“罗杰有五个网球,他又买了两盒网球,每盒有3个网球,请问他现在总共有多少个网球?”                   
A:“现在罗杰总共有11个网球。”                   
Q:“食堂总共有23个苹果,如果他们用掉20个苹果,然后又买了6个苹果,请问现在食堂总共有多少个苹果?”                   
A:“现在食堂总共有9个苹果。”                   
Q:“艾米需要4分钟才能爬到滑梯顶部,她花了1分钟才滑下来,水滑梯将在15分钟后关闭,请问在关闭之前她能滑多少次?”                A:'
response = client.chat.completions.create(
    messages=[
        {
            "role": "user",
            "content": prompt_Few_shot_3,
        }
    ],
    model="gpt-3.5-turbo",
    max_tokens=1000,
)
print(response.choices[0].message.content)
output:'“在关闭之前,艾米能滑3次。”'
response_Few_shot_3 = openai.Completion.create(
                     model="gpt-3.5-turbo-instruct",
                     prompt=prompt_Few_shot_3,
                     max_tokens=1000,
                     )
print(response_Few_shot_3["choices"][0]["text"].strip())
output:'”在关闭之前,艾米可以滑13次“'

"gpt-3.5-turbo-instruct"第四个问题也回答错误,但是"gpt-3.5-turbo"模型已经可以完全答对以上问题了。这说明Few-shot提示方法能够一定程度提高模型推理能力,但提升的幅度有限,对于稍微复杂些的推理问题,模型仍然无法做出准确的回答。

  不过这里需要注意的是,尽管看似Few-shot的使用方法较为简单,但实际上Few-shot有非常多的变种方法—其中一类非常重要的变种方法就是围绕提示的示例进行修改,即在示例中不仅提供问题 + 答案,同时还会增加一些辅助思考和判断的“提示”。因此后面的我们介绍的很多方法,尽管提示的内容各有不同,但基本都可以从Few-shot角度进行理解。

2.2、通过思维链提示法提升模型推理能力

2.2.1、Zero-shot-CoT提示方法
  • Zero-shot-CoT实现过程

  那有什么办法能够通过更好的提示来提高模型的推理能力呢?最简单的一类办法就是借助思维链(也被称为思考链,Chain of Thought,CoT)提示法来解决这个问题。在这些方法中,最为简单的思维链的实现方法是在提示词尾部追加一句“Let’s think step by step”,即可大幅提高模型推理能力。这种方法最早由东京大学和谷歌在论文《Large Language Models are Zero-Shot Reasoners》中提出。由于只需要修改提示词而无需手动编写推导的成功示例(无需编写思维链样本),因此这种方法也被称为Zero-shot-CoT。

从论文标题能看出,该论文有叫板OpenAI论文《Language Models are Few-Shot Learners 》的嫌疑。

  接下来我们尝试借助Zero-shot-CoT解决之前的推理问题。这里需要注意,“Let’s think step by step”其实是一句“具有魔法”的语句,是一个经过很多次尝试最终总结出来的、对大模型推理能力提升效果最为显著的提示词后缀,因此在将词句翻译为中文的时候,我们也仿造原论文进行了海量的尝试和实验,最终判断将其翻译为“请一步步进行推理并得出结论”,对提升模型推理能力最为有效。这里我们尝试以“请一步步进行推理并得出结论”为提示词后缀,查看能否解决此前的推理问题,由于"gpt-3.5-turbo"在之前问题中已经表现良好,下面仅以"gpt-3.5-turbo-instruct"模型进行实验,以下是历史实验数据:

prompt_Zero_shot_CoT_1 = '罗杰有五个网球,他又买了两盒网球,每盒有3个网球,请问他现在总共有多少个网球?请一步步进行推理并得出结论。'
response_Zero_shot_CoT_1 = openai.Completion.create(
                          model="gpt-3.5-turbo-instruct",
                          prompt=prompt_Zero_shot_CoT_1,
                          max_tokens=1000,
                          )
print(response_Zero_shot_CoT_1["choices"][0]["text"].strip())
output:'首先,罗杰有5个网球。\n然后,他又买了两盒网球,每盒有3个网球,所以一共有2 x 3 = 6个网球。\n加上之前的5个网球,总共有5 + 6 = 11个网球。 \n结论:罗杰现在总共有11个网球。'

第一题回答正确,接下来看第二题:

prompt_Zero_shot_CoT_2 = '食堂总共有23个苹果,如果他们用掉20个苹果,然后又买了6个苹果,请问现在食堂总共有多少个苹果?请一步步进行推理并得出结论。'
response_Zero_shot_CoT_2 = openai.Completion.create(
                          model="gpt-3.5-turbo-instruct",
                          prompt=prompt_Zero_shot_CoT_2,
                          max_tokens=1000,
                          )
print(response_Zero_shot_CoT_2["choices"][0]["text"].strip())
output:'1. 首先,我们知道食堂一开始有23个苹果。\n2. 然后,他们用掉了20个苹果,所以剩余23-20=3个苹果。\n3. 接下来,他们又买了6个苹果,所以现在总共有3+6=9个苹果。\n4. 综上,现在食堂总共有9个苹果。'

第二题回答正确,接下来看第三题:

prompt_Zero_shot_CoT_3 = '杂耍者可以杂耍16个球。其中一半的球是高尔夫球,其中一半的高尔夫球是蓝色的。请问总共有多少个蓝色高尔夫球?请一步步进行推理并得出结论。'
response_Zero_shot_CoT_3 = openai.Completion.create(
                          model="gpt-3.5-turbo-instruct",
                          prompt=prompt_Zero_shot_CoT_3,
                          max_tokens=1000,
                          )
print(response_Zero_shot_CoT_3["choices"][0]["text"].strip())
output:'首先,假设所有杂耍者都可以杂耍16个球,也就是说每个人手里都有16个球。而根据题目给出的条件,高尔夫球占一半,也就是8个球。\n\n其次,根据题目给出的条件,高尔夫球中一半为蓝色,也就是4个球。由此可得出结论,所有蓝色高尔夫球的总数为4个。'

第三题也回答正确,接下来看第四题:

prompt_Zero_shot_CoT_4 = '艾米需要4分钟才能爬到滑梯顶部,她花了1分钟才滑下来,水滑梯将在15分钟后关闭,请问在关闭之前她能滑多少次?请一步步进行推理并得出结论。'
response_Zero_shot_CoT_4 = openai.Completion.create(
            model="gpt-3.5-turbo-instruct",
            prompt=prompt_Zero_shot_CoT_4,
            max_tokens=1000,
            )
print(response_Zero_shot_CoT_4["choices"][0]["text"].strip())
output:'1. 首先计算出在15分钟内,艾米可以爬多少次滑梯顶部。\n由题意可知,每次滑梯的时间是4分钟,而总共有15分钟,因此可以计算出艾米最多可以爬3次滑梯顶部(15÷4=3.75,舍去小数部分得到3)。\n\n2. 推断在第3次滑下来时,离关闭还有多长时间。\n第1次滑下来耗时1分钟,第2次滑下来耗时4分钟,第3次滑下来耗时4分钟,加起来共耗时9分钟,因此离关闭还有6分钟。\n\n3. 推断在剩下6分钟内,艾米还能滑几次。\n由步骤1可知,艾米最多可以爬3次滑梯顶部,已经滑了3次,因此剩下6分钟内无法再滑了。\n\n4. 得出结论:在关闭之前,艾米能滑3次滑梯。'

这里需要注意,尽管第四题答案正确,但推理过程并不正确,至于总共能滑3.5次云云,更是不符合逻辑。因此第四题其实仍然回答错误。不难发现,第四题的逻辑推导其实是最难的。

  综上,经过四个逻辑推导题的验证,我们发现相比Few-shot,Zero-shot-CoT确实更加有效,即能够通过更加简洁的提示来大幅提高模型推理能力。当然我们这里只列举了四个例子用于验证模型推理能力,更加严谨的、在海量推理场景中验证的Zero-shot-CoT有效性的结论,我们可以参考《Large Language Models are Zero-Shot Reasoners》论文中的相关说明。

  • 《Large Language Models are Zero-Shot Reasoners》重点结论解读

  根据原论文描述,作者在测试Zero_shot_CoT方法时曾尝试过多组不同的提示词尾缀,并在一个机器人指令数据集上进行测试,最终发现“Let’s think step by step”效果最好,其他指令及各指令准确率排名如下:

3f518f011f3ea3af088b67e4512b32b

类似的,就该指令“Let’s think step by step”的中文翻译而言,“请一步步进行推理并得出结论”要远远好于“请让我们一步步进行思考”等类似的提示词语句。这一客观情况也给大模型使用者非常深刻的启发,那就是大模型的“思考过程”是黑箱模型,哪怕表意近似的提示词对模型来说实际的影响力可能会有非常大的区别,围绕提示词的开发需要进行大量的尝试,这个过程也非常类似于掘金的过程。而对于实际使用者而言,则需要平日多注重提示词的积累和尝试。

  其次,论文中首次提出了利用大模型进行两阶段推理的设想,即第一个阶段先进行问题的拆分并分段解答问题(Reasoning Extraction),然后第二阶段再进行答案的汇总(Answer Extraction)。这一设想尽管没有在论文中进行大量验证,但却给之后的一种名为LEAST-TO-MOST(LtM)的提示方法给予了启发,该方法将在后文中进行介绍。

56bfaaf380a3efc0e47bc54472c7020

论文中第三个非常重要的结论,就是将Zero-shot-CoT方法和Few-shot-CoT方法进行了比较,根据论文中结论,在实际使用过程中,Zero-shot-CoT要略弱于Few-shot-CoT方法,而后者则是一种CoT方法和Few-shot方法的结合,我们稍后会进行介绍。

6087bb98316b2908cfa38fe01f1bbad

同时需要注意的是,论文明确表示,模型越大CoT效果越好,换而言之就是模型越大,CoT对模型“涌现能力”的激发效果越好。并且GPT-3在GSM8K数据集上能达到55%左右准确率,而后者则是一个非常著名的小学数学应用题组成的数据集,往往用于测试模型的推理能力。后续其他模型的推理能力比较也将用到这个数据集。

2.2.2、Few-shot-CoT提示方法
  • Few-shot-CoT实现过程

  哪怕是CoT方法,即然可以Zero-shot-CoT,自然也可以Few-shot-CoT。需要注意的是,Zero-shot-CoT是零样本提示的情况下通过修改提示词后缀激发模型的思维链,而Few-shot-CoT则是通过通过编写思维链样本作为提示词,让模型学会思维链的推导方式,从而更好的完成推导任务。该方法最早由谷歌大脑团队在论文《Chain-of-Thought Prompting Elicits Reasoning in Large Language Models》中首次提出,也是在这篇论文中思维链的概念被首次提出,因此该论文可以说是思维链的开山鼻祖之作。

这里需要注意,从诞生时间上来说,Few-shot-CoT诞生时间要早于Zero-shot-CoT,课程中是按照先易后难的顺序编排的内容,所以先介绍Zero-shot-CoT、后介绍Few-shot-CoT。

其实相比于Few-shot,Few-shot-CoT的不同之处只是在于需要在提示样本中不仅给出问题的答案、还同时需要给出问题推导的过程(即思维链),从而让模型学到思维链的推导过程,并将其应用到新的问题中。例如,围绕上述四个推理问题,第一个问题是比较好解决的,我们可以手动写一个思维链作为Few-shot的示例,以下也均以"gpt-3.5-turbo-instruct"模型的历史数据作为参考:

'Q:“罗杰有五个网球,他又买了两盒网球,每盒有3个网球,请问他现在总共有多少个网球?” \
A:“罗杰一开始有五个网球,又购买了两盒网球,每盒3个,共购买了6个网球,因此现在总共由5+6=11个网球。因此答案是11。” '

当然类似这种思维链,也可以借助此前的Zero-shot-CoT来完成创建。

  在获得了一个思维链示例后,我们即可以此作为样本进行Few-shot-CoT来解决第二个推理问题,具体执行过程如下

prompt_Few_shot_CoT_2 = 'Q:“罗杰有五个网球,他又买了两盒网球,每盒有3个网球,请问他现在总共有多少个网球?” \
                        A:“罗杰一开始有五个网球,又购买了两盒网球,每盒3个,共购买了6个网球,因此现在总共由5+6=11个网球。因此答案是11。” \
                        Q:“食堂总共有23个苹果,如果他们用掉20个苹果,然后又买了6个苹果,请问现在食堂总共有多少个苹果?” \
                        A:'
response_Few_shot_CoT_2 = openai.Completion.create(
                         model="gpt-3.5-turbo-instruct",
                         prompt=prompt_Few_shot_CoT_2,
                         max_tokens=1000,
                         )
print(response_Few_shot_CoT_2["choices"][0]["text"].strip())
output:'”食堂一开始有23个苹果,用掉了20个,剩余3个,之后又买了6个,因此现在总共有3+6=9个苹果。因此答案是9。”'

能够发现,模型能够非常好的回答第二个问题,接下来我们稍微做些调整,即把每个做出成功预测的思维链都作为例子写入Few-shot-CoT中,增加大模型学习样本,从而更好的解决之后的问题。例如我们可以把前两个问题的思维链作为提示词输入,来引导模型解决第三个问题:

prompt_Few_shot_CoT_3 = 'Q:“罗杰有五个网球,他又买了两盒网球,每盒有3个网球,请问他现在总共有多少个网球?” \
                        A:“罗杰一开始有五个网球,又购买了两盒网球,每盒3个,共购买了6个网球,因此现在总共由5+6=11个网球。因此答案是11。” \
                        Q:“食堂总共有23个苹果,如果他们用掉20个苹果,然后又买了6个苹果,请问现在食堂总共有多少个苹果?” \
                        A:“食堂最初有23个苹果,用掉20个,然后又买了6个,总共有23-20+6=9个苹果,答案是9。” \
                        Q:“杂耍者可以杂耍16个球。其中一半的球是高尔夫球,其中一半的高尔夫球是蓝色的。请问总共有多少个蓝色高尔夫球?” \
                        A:'
response_Few_shot_CoT_3 = openai.Completion.create(
                         model="gpt-3.5-turbo-instruct",
                         prompt=prompt_Few_shot_CoT_3,
                         max_tokens=1000,
                         )
print(response_Few_shot_CoT_3["choices"][0]["text"].strip())
output:'”由于杂耍者可以杂耍16个球,其中一半是高尔夫球。因此一共有16/2=8个高尔夫球。而其中一半是蓝色的,因此一共有8/2=4个蓝色高尔夫球。因此答案是4个。”'

能够发现第三个问题也能够被顺利解决。接下来是最后一个问题:

prompt_Few_shot_CoT_4 = 'Q:“罗杰有五个网球,他又买了两盒网球,每盒有3个网球,请问他现在总共有多少个网球?” \
                        A:“罗杰一开始有五个网球,又购买了两盒网球,每盒3个,共购买了6个网球,因此现在总共由5+6=11个网球。因此答案是11。” \
                        Q:“食堂总共有23个苹果,如果他们用掉20个苹果,然后又买了6个苹果,请问现在食堂总共有多少个苹果?” \
                        A:“食堂最初有23个苹果,用掉20个,然后又买了6个,总共有23-20+6=9个苹果,答案是9。” \
                        Q:“杂耍者可以杂耍16个球。其中一半的球是高尔夫球,其中一半的高尔夫球是蓝色的。请问总共有多少个蓝色高尔夫球?” \
                        A:“总共有16个球,其中一半是高尔夫球,也就是8个,其中一半是蓝色的,也就是4个,答案是4个。” \
                        Q:“艾米需要4分钟才能爬到滑梯顶部,她花了1分钟才滑下来,水滑梯将在15分钟后关闭,请问在关闭之前她能滑多少次?” \
                        A:'
response_Few_shot_CoT_4 = openai.Completion.create(
                         model="gpt-3.5-turbo-instruct",
                         prompt=prompt_Few_shot_CoT_4,
                         max_tokens=1000,
                         )
print(response_Few_shot_CoT_4["choices"][0]["text"].strip())
output:'"在15分钟内,艾米可以滑3次水滑梯,因为她每次滑下来后都需要4分钟爬到顶部,而水滑梯将在15分钟后关闭,答案是3次。"'

能够发现,第四个问题果然还是最难的问题,哪怕输入了前三个问题的思维链作为提示词样本,第四个问题"gpt-3.5-turbo-instruct"模型仍然无法得到正确的解答。在本次的回答中,过程正确,但模型却算错了(15分钟÷(4分钟+1分钟)),从中也能发现大语言模型的能力瓶颈——无法真正意义上理解文本和数字的含义。当然,在有限的四个推理示例中,Few-shot-CoT效果和Zero-shot-CoT准确率相同,但根据《Large Language Models are Zero-Shot Reasoners》论文中的结论,上从海量数据的测试结果来看,Few-shot-CoT比Zero-shot-CoT准确率更高。

  • 《Chain-of-Thought Prompting Elicits Reasoning in Large Language Models》重点结论解读

  由于《Chain-of-Thought Prompting Elicits Reasoning in Large Language Models》是思维链的开山之作,因此论文中提出了大量的关于思维链的应用场景——除了可以用于解决上述推理问题外,思维链还可以被广泛应用于复杂语义理解、符号映射、连贯文本生成等领域。论文中给出了一系列结论,用于论证思维链在这些领域应用的有效性。

6dca198423a3022cdc8b305829d3795

此外,论文中还重点强调了模型体量和思维链效果之间的关系,简而言之就是,模型越大、Few-shot-CoT应用效果越好。论文同样以GSM8K数据集为例进行了说明,能够看出模型效果LaMDA(137B)< GPT-3(175B) < PaLM(540B)。和Zero-shot-CoT类似,模型越大、CoT对模型潜在能力激发效果越好。

38ceffed0bdcb0357932f37241660cc

这里PaLM具体得分为57分,而Prior supervised best(单独训练的最佳有监督学习模型)得分为55分。

3、CoT改良方法:LEAST-TO-MOST PROMPTING(LtM提示法)

3.1、Least-to-Most Prompting基本概念

  就在谷歌大脑提出的CoT被实际验证能够大幅提升大语言模型的推理能力不久,来自谷歌大脑的另一个团队在此基础上发表了另一篇重量级论文《LEAST-TO-MOST PROMPTING ENABLES COMPLEX REASONING IN LARGE LANGUAGE MODELS》,并在其中提出了一种名为Least-to-Most(LtM)的提示方法,将大语言模型的推理能力进一步提高。这种名为LtM的提示方法不仅能够将模型在GSM8K上的表现提高至62%,甚至在某些特殊语义解读场景下能够达到3倍于CoT的效果。不得不说,该方法也是截至目前围绕模型推理能力提升的最为有效的提示学习方法。

  LtM提示方法提出的初衷是为了解决CoT提示方法泛化能力不足的问题—即通过人工编写的思维链提示样本可能并不能够很好的迁移到别的问题当中去,换而言之,就是解决问题的流程迁移能力不足,即泛化能力不够。而这种泛化能力不足则会导致“新的问题”无法使用“老的模板”进行解决。例如此前的第四个推理问题就是如此。那即然要找到更加普适的解决问题的流程会非常复杂,那能否“千人千面”让大模型自己找到解决当前问题的思维链呢?答案是肯定的,谷歌大脑基于这个思路开发了一种全新的提示流程,即先通过提示过程让模型找到解决该问题必须要分步解决哪几个问题,然后再通过依次解决这些问题来解决最原始的问题。

  不难看出整个提示过程会分为两个阶段进行,第一个阶段是自上而下的分解问题(Decompose Question into subquestion),第二个阶段是自下而上的依次解决问题(Sequentially Solve Subquestion),而整个依次回答问题的过程,其实就可以看成是CoT的过程,只不过LtM会要求模型根据每个不同的问题,单独生成解决问题的链路,以此做到解决问题流程的“千人千面”,从而能够更加精准的解决复杂推理问题。而整个过程问题的由少变多,则是LEAST-TO-MOST一词的来源。

3.2、Zero-shot-MtL提示过程

  • Least-to-Most Prompting示例

  这里我们可以通过论文中提出的示例来理解LtM提示过程,具体提示过程如下。这里的例子就是此前我们尝试解决的第四个推理问题,即艾米滑水滑梯的问题,论文中通过提示模板“To solve __, we need ti first solve:”来引导模型创建子问题。而模型则会根据原始问题提出子问题“艾米一次爬上滑梯+滑下滑梯总共用时多少”,然后先解决这个子问题,再解决原始问题,不难发现,这其实是一个非常简单的两阶段解决问题的过程——第一个阶段只额外分解了一个子问题(即总共分两个问题作答)。

5f282682ac823817560bd7287b12107

而根据论文中给出的结果不难发现,第一阶段模型能够非常顺利的回答“艾米一次爬上滑梯+滑下滑梯总共用时多少”——5分钟,然后据此顺利回答出在滑梯关闭之前艾米还能玩三次的准确结论。

  不过需要注意的是,第二个阶段、即Sequentially Solve Subquestion并不是简单的依次解决两个问题,而是在解决了子问题之后,将原问题、子问题和问题和答案三部分都作为prompt输入给大语言模型,让其对原始问题进行回答。所以不难发现,MtL的核心并不仅仅在于引导模型拆分问题,还在于及时将子问题的问题和答案回传给模型,以便更好的围绕原始问题进行回答。理论上整个过程会有三次调用大模型的过程,问答流程如下:

4173ae0afb29936452104fc0281a9f4

接下来我们就尝试该提示方法,借助'text-davinci-003',观察是否能够顺利得到准确答案:

prompt_Zero_shot_MtL_4 = 'Q:“艾米需要4分钟才能爬到滑梯顶部,她花了1分钟才滑下来,水滑梯将在15分钟后关闭,请问在关闭之前她能滑多少次?”\
                         A:为了解决“在关闭之前她能滑多少次?”这个问题,我们首先要解决的问题是'
response_Zero_shot_MtL_4 = openai.Completion.create(
                          model="gpt-3.5-turbo-instruct",
                          prompt=prompt_Zero_shot_MtL_4,
                          max_tokens=1000,
                          )
print(response_Zero_shot_MtL_4["choices"][0]["text"].strip())
output:'在15分钟内她能进行多少次爬到顶部和滑下来的操作。由于每次爬到顶部和滑下来总共需要5分钟(4分钟爬到顶部+1分钟滑下来),所以在15分钟内她可以进行3次完整的爬到顶部和滑下来的操作。因此,在关闭之前她能滑2次。'
​
response = client.chat.completions.create(
    messages=[
        {
            "role": "user",
            "content": prompt_Zero_shot_MtL_4,
        }
    ],
    model="gpt-3.5-turbo",
    max_tokens=1000,
)
print(response.choices[0].message.content)
output:'计算在15分钟内艾米可以滑多少次。 \n\n在15分钟内,艾米每次需要4分钟爬到滑梯顶部,再花1分钟滑下来,共需5分钟。所以,在一次关闭前,艾米最多可以滑(15/5=3)次。所以,在关闭之前,她能滑3次。'

能够发现MtL提示过程能够非常好的解决这个推理问题。并且,在实际测试过程中,模型不仅能够拆解任务,而且还能够自动根据拆解的子问题答案回答原始问题,最终做到在一个提示语句中对原始问题进行准确回答。而正因为在一个提示中能够同时拆解任务+回答子任务+回答原始任务,才使得该提示过程能够更加便捷的使用,而不用像原始论文中展示的那样需要重复将拆分的子问题提交模型来进行回答、然后再将子问题的答案作为提示传递给模型、再围绕原始问题进行提问。并且我们可以发现如此提示后的"gpt-3.5-turbo-instruct"返回的答案和"gpt-3.5-turbo"默认输出的答案十分相似,可以有理由相信在后续系列的大模型中均已内嵌了类似的提示工程(或微调训练)。

这里需要注意的是,能够一次性拆解问题和回答全部问题的能力只有text-davinci-003模型才有,原论文最开始是基于code-davinci-002模型进行的实验,code-davinci-002是gpt-3的基座模型,文本补全能力并不如text-davinci-003模型。不过code-davinci-002并未对外开放,因此目前无法在code-davinci-002进行实验。

另外,‘为了解决“__”这个问题,我们首先要解决的问题是__’也是经过我们验证的、最为恰当、同时也最能够得到准确回答的提示词模板,建议经常使用,并验证其功能。

  • Least-to-Most Prompting效果验证

  接下来我们继续尝试借助MtL提示解决剩余三个推理问题,以下仍是"gpt-3.5-turbo-instruct"的历史实验数据:

prompt_Zero_shot_MtL_1 = 'Q:“罗杰有五个网球,他又买了两盒网球,每盒有3个网球,请问他现在总共有多少个网球?”\
                         A:为了解决“罗杰总共又多少个网球?”这个问题,我们首先要解决的问题是'
prompt_Zero_shot_MtL_1 = openai.Completion.create(
                        model="gpt-3.5-turbo-instruct",
                        prompt=prompt_Zero_shot_MtL_1,
                        max_tokens=1000,
                        )
print(prompt_Zero_shot_MtL_1["choices"][0]["text"].strip())
output:'“罗杰原先有多少个网球?”,然后再加上买的两盒网球的数量。根据问题中的信息,我们知道罗杰原先有五个网球,再加上两盒网球,每盒3个,所以他总共有5+2*3=11个网球。'
prompt_Zero_shot_MtL_2 = 'Q:“食堂总共有23个苹果,如果他们用掉20个苹果,然后又买了6个苹果,请问现在食堂总共有多少个苹果?”\
                         A:为了解决“现在食堂总共有多少个苹果”这个问题,我们首先要解决的问题是'
prompt_Zero_shot_MtL_2 = openai.Completion.create(
                        model="gpt-3.5-turbo-instruct",
                        prompt=prompt_Zero_shot_MtL_2,
                        max_tokens=1000,
                        )
print(prompt_Zero_shot_MtL_2["choices"][0]["text"].strip())
output:':食堂用掉了20个苹果之后,还剩下多少个苹果?我们可以用23减去20,得到食堂剩下的苹果数为3个。\n            接着,我们再解决“食堂又买了6个苹果之后,总共有多少个苹果”的问题。我们可以把前面剩下的3个苹果与新买的6个苹果相加,得到最终的答案:食堂总共有9个苹果。'
prompt_Zero_shot_MtL_3 = 'Q:“杂耍者可以杂耍16个球。其中一半的球是高尔夫球,其中一半的高尔夫球是蓝色的。请问总共有多少个蓝色高尔夫球?”\
                         A:为了解决“总共有多少个蓝色高尔夫球”这个问题,我们首先要解决的问题是'
prompt_Zero_shot_MtL_3 = openai.Completion.create(
                        model="gpt-3.5-turbo-instruct",
                        prompt=prompt_Zero_shot_MtL_3,
                        max_tokens=1000,
                        )
print(prompt_Zero_shot_MtL_3["choices"][0]["text"].strip())
output:'“杂耍者可以杂耍多少个高尔夫球”。根据题目可知,杂耍者可以杂耍16个球,其中有一半是高尔夫球,即8个。同时,题目还给出了高尔夫球中蓝色球的数量,也就是一半的球是蓝色的,即4个。因此,总共有4个蓝色高尔夫球。'

能够发现,MtL提示过程能够非常好的帮助模型解决上述问题,这也说明MtL对大语言模型的推理能力提升效果非常显著,可以说是截至目前,我们尝试过的(解决推理问题方面)最有效的一类提示方法。

  至此,我们就完整介绍了目前最为有效的一系列用于提升模型推理能力的提示方法。下一小节我们将进一步围绕一项用于测试模型组合泛化能力的指令翻译任务进行建模,并在实战中进一步介绍嵌套提示流程设计方法。

  • 11
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值