AI系统性学习01- Prompt Engineering

发布于:2024-03-15 ⋅ 阅读:(74) ⋅ 点赞:(0)

面向开发者的Prompt Engineering

简介

随着LLM的发展,其大致可以分为两种类型:基础LLM指令微调LLM

  • 基础LLM:假设你以“从前有一只独角兽”作为提示,基础的LLM可能会继续预测“生活在一个与所有的独角兽朋友的神奇森林中”。但是如果以"法国的首都是什么?",其回答可能为"法国的人口是多少?法国最大的城市是什么?"。因为其训练的语料可能是有关法国国家的问答题目列表。
  • 指令微调LLM:指的是被训练来能够遵循特定指令的LLM。而这样的模型通常是在基础LLMs上,使用输入的指令、输出是其应该返回的结果来对其进行微调,要求它遵循这些指令。而通常使用一种称为RLHF(reinforcement learning from human feedback,人类反馈强化学习)的技术进一步改进,使系统能够有帮助得遵循命令。
    在这里插入图片描述

Prompt设计原则

1 环境配置

针对基本的开发环境在这里不强调,主要说明一哈这里的chatgpt的api key【可以淘宝购买】。

  • 设置api-key
 openai.api_key = "REPLASE BY YOUR API KEY"
  • 下载三方包
pip install openai
  • 加载基本的代码
# 一个封装 OpenAI 接口的函数,参数为 Prompt,返回对应结果
def get_completion(prompt, model="gpt-3.5-turbo"):
    '''
    prompt: 对应的提示
    model: 调用的模型,默认为 gpt-3.5-turbo(ChatGPT),有内测资格的用户可以选择 gpt-4
    '''
    messages = [{"role": "user", "content": prompt}]
    response = openai.ChatCompletion.create(
        model=model,
        messages=messages,
        temperature=0, # 模型输出的温度系数,控制输出的随机程度
    )
    # 调用 OpenAI 的 ChatCompletion 接口
    return response.choices[0].message["content"]

2.两个基本原则

2.1 原则1:编写清晰、具体的指令

提供清晰具体的指示,避免无关或不正确响应,不要混淆写清晰和写简短,更长的提示可以提供更多清晰度和上下文信息,导致更详细和相关的输出。

2.1.1 策略一:分割

使用分割符清晰得表示输入的不同部分,分割符号可以是:```,””,<>,<tag>,<\tag>等

实际操作中,可以使用任何明显的的标点符号将特点的文本部分与其提示词分开。这可以可以使模型明确知道这是一个单独部分的标记。同时这样分割避免了输入里面可能包含其他的指令,会覆盖掉自己的指令。以下是一个具体的例子:

  • 不使用分割策略
    # 消息本身
    text = "今天天气不错,我出去玩一会儿我有错吗?"
    # 嵌入前面消息的Prompt
    expanding_prompt = "请扩展下面表达{}".format(text)
    # 获取模型返回
    response = get_completion(expanding_prompt)
    print(response)
    # 今天天气真是太好了,阳光明媚,微风轻拂,我实在忍不住想出去玩一会儿。我有错吗?
  • 使用分割策略
    # 消息本身
    text = "今天天气不错,我出去玩一会儿我有错吗?"
    # 嵌入前面消息的Prompt
    expanding_prompt = "请扩展下面引号号内的表达`{}`".format(text)
    # 获取模型返回
    response = get_completion(expanding_prompt)
    print(response)
    # "今天天气不错,我出去玩一会儿我有错吗?生活就是要享受,不是吗?我需要放松一下,让自己的心情变得更加愉快。"
2.1.2 策略2:结构化输出

要求模型结构输出,可以是JSON、HTML、Markdown等格式

使用结构输出的列子更容易满足用户的输出要求,避免模型遗忘某些重要的细节,如下例子(后面有完整的代码):

def prompt_strategy_02():
    # 消息本身
    text = "我正在完成一道中国传统美食——鱼香茄子的制作,你能否给出一些建议?"
    # 要求输出格式
    format_prompt = "针对括号内制作的细节以json格式输出[{}]".format(text)
    return format_prompt
# {
#  "细节": {
#    "1": "茄子切块后要用盐腌制一段时间,使其出水,减少油炸时吸油量",
#   "2": "油炸茄子时要掌握好油温,避免茄子吸油过多",
#    "3": "炒茄子时要先炒至金黄,再加入调料翻炒均匀",
#    "4": "调味料要根据个人口味适量调整,以保持鱼香味道的平衡",
#    "5": "可以适量加入葱姜蒜等调料,提升菜品的香气和口感"
#  }
#}
2.1.3 策略3:模型检测

要求模型检查是否满足条件

在某种情况下,比如场景可能包含多种情形,则可以告知模型针对不同情形(条件检测)如何来对场景进行描述。比如如下的一个例子:

def prompt_strategy_03():
    """
    策略3:模型检查条件
    :return:
    """
    # 场景描述
    text = f"""
            泡一杯茶很容易。首先,需要把水烧开。\
            在等待期间,拿一个杯子并把茶包放进去。\
            一旦水足够热,就把它倒在茶包上。\
            等待一会儿,让茶叶浸泡。几分钟后,取出茶包。\
            如果你愿意,可以加一些糖或牛奶调味。\
            就这样,你可以享受一杯美味的茶了。
            """
    # 设计prompt
    prompt = f"""
    您将获得由括号括起来的文本。\
    如果它包含一系列的指令,则需要按照以下格式重新编写这些指令:

    第一步 - ...
    第二步 - …
    …
    第N步 - …

    如果文本中不包含一系列的指令,则直接写“未提供步骤“[{text}] """

    return prompt

输出如下

第一步 - 把水烧开。
第二步 - 拿一个杯子并把茶包放进去。
第三步 - 把烧开的水倒在茶包上。
第四步 - 等待一会儿,让茶叶浸泡。几分钟后,取出茶包。
第五步 - 如果你愿意,可以加一些糖或牛奶调味。
第六步 - 就这样,你可以享受一杯美味的茶了。
2.1.4 策略4:提供示例

提供一些帮助模型理解的例子可以使得输出结果更好

比如一个经典的回答中是要求回复的风格,看下面的例子:

def prompt_strategy_04():
    """
    策略04:提供示例
    :return:
    """
    # 消息本身
    text = "天气真没!"
    # 嵌入到提示词中
    prompt = f"""
    你的任务是以一致的风格回答问题。

    <Q>: 你人真好!

    <A>: 人好不一定其心好,心怀不代表人不好!

    <Q>: {text}"""
    return prompt

输出如下:

<A>: 天气不好不代表心情不好,心情不好也不代表天气不好!愿你心情常好,无论天气如何!
2.2 原则二:给模型足够时间思考

在很多情况下,为了保证速度,模型并不能拥有充足的时间去思考,这跟人的思考是相同的,因此提供给模型足够的思考时间,也许能获得更好的结果,但是也面临着消耗更多的技术资源。

2.2.1 策略5:指定步骤

指定完成任务所需的步骤


def prompt_strategy_05():
    """
    策略5:指定任务所需的步骤
    :return: 
    """
    text = f"""
    在一个迷人的村庄里,兄妹杰克和吉尔出发去一个山顶井里打水。\
    他们一边唱着欢乐的歌,一边往上爬,\
    然而不幸降临——杰克绊了一块石头,从山上滚了下来,吉尔紧随其后。\
    虽然略有些摔伤,但他们还是回到了温馨的家中。\
    尽管出了这样的意外,他们的冒险精神依然没有减弱,继续充满愉悦地探索。
    """
    # example 1
    prompt = f"""
    执行以下操作:
    1-用一句话概括下面用三个双引号括起来的文本。
    2-将摘要翻译成法语。
    3-在法语摘要中列出每个人名。
    4-输出一个 JSON 对象,其中包含以下键:French_summary,num_names。

    请用换行符分隔您的答案。

    Text:
    \"\"\"{text}\"\"\"
    """
    return prompt

其输出如下:

1- 兄妹在山顶井里打水时发生意外,但他们的冒险精神依然不减。
2- Dans un charmant village, les frère et sœur Jack et Jill partent chercher de l'eau dans un puits au sommet d'une montagne.
3- Jack, Jill
4- 
{
  "French_summary": "Dans un charmant village, les frère et sœur Jack et Jill partent chercher de l'eau dans un puits au sommet d'une montagne.",
  "num_names": 2
}

事实上,这里的prompt可以更好,如下:

   prompt_better = f"""
    1-用一句话概括下面用<>括起来的文本。
    2-将摘要翻译成英语。
    3-在英语摘要中列出每个名称。
    4-输出一个 JSON 对象,其中包含以下键:English_summary,num_names。

    请使用以下格式:
    文本:<要总结的文本>
    摘要:<摘要>
    翻译:<摘要的翻译>
    名称:<英语摘要中的名称列表>
    输出 JSON:<带有 English_summary 和 num_names 的 JSON>

    Text: <{text}>
    """

新的提示词输出如下:

摘要:在一个迷人的村庄里,兄妹杰克和吉尔在去山顶井打水的途中遭遇意外,但他们的冒险精神依然坚定。

翻译:In a charming village, siblings Jack and Jill encounter an accident on their way to fetch water from a well on the mountaintop, but their adventurous spirit remains strong.

名称:Jack, Jill

输出 JSON:{"English_summary": "In a charming village, siblings Jack and Jill encounter an accident on their way to fetch water from a well on the mountaintop, but their adventurous spirit remains strong.", "num_names": 2}
2.2.2 策略6:结论之前找出一个自己的办法

指导模型下结论之前找到一个自己的方法
有时候,在明确指导模型在做决策之前要思考自己的解决方案,我们可能会得到一个更好的结果。如下的例子:

def prompt_strategy_06():
    prompt = f"""
    判断学生的解决方案是否正确。

    问题:
    我正在建造一个太阳能发电站,需要帮助计算财务。

        土地费用为 100美元/平方英尺
        我可以以 250美元/平方英尺的价格购买太阳能电池板
        我已经谈判好了维护合同,每年需要支付固定的10万美元,并额外支付每平方英尺10美元
        作为平方英尺数的函数,首年运营的总费用是多少。

    学生的解决方案:
    设x为发电站的大小,单位为平方英尺。
    费用:

        土地费用:100x
        太阳能电池板费用:250x
        维护费用:100,000美元+100x
        总费用:100x+250x+100,000美元+100x=450x+100,000美元
    """
    return prompt

其输出如下:

学生的解决方案是正确的。总费用的计算是正确的,即总费用为450x + 100,000美元。

但是实际上,学生的方案是有问题的。在接下来这个 Prompt 中,我们要求模型先自行解决这个问题,再根据自己的解法与学生的解法进行对比,从而判断学生的解法是否正确。同时,我们给定了输出的格式要求。通过明确步骤,让模型有更多时间思考,有时可以获得更准确的结果。在这个例子中,学生的答案是错误的,但如果我们没有先让模型自己计算,那么可能会被误导以为学生是正确的。

def prompt_strategy_06():
    # prompt = f"""
    # 判断学生的解决方案是否正确。
    #
    # 问题:
    # 我正在建造一个太阳能发电站,需要帮助计算财务。
    #
    #     土地费用为 100美元/平方英尺
    #     我可以以 250美元/平方英尺的价格购买太阳能电池板
    #     我已经谈判好了维护合同,每年需要支付固定的10万美元,并额外支付每平方英尺10美元
    #     作为平方英尺数的函数,首年运营的总费用是多少。
    #
    # 学生的解决方案:
    # 设x为发电站的大小,单位为平方英尺。
    # 费用:
    #
    #     土地费用:100x
    #     太阳能电池板费用:250x
    #     维护费用:100,000美元+100x
    #     总费用:100x+250x+100,000美元+100x=450x+100,000美元
    # """

    prompt = f"""
    请判断学生的解决方案是否正确,请通过如下步骤解决这个问题:

    步骤:

        首先,自己解决问题。
        然后将你的解决方案与学生的解决方案进行比较,并评估学生的解决方案是否正确。在自己完成问题之前,请勿决定学生的解决方案是否正确。

    使用以下格式:

        问题:问题文本
        学生的解决方案:学生的解决方案文本
        实际解决方案和步骤:实际解决方案和步骤文本
        学生的解决方案和实际解决方案是否相同:是或否
        学生的成绩:正确或不正确

    问题:

        我正在建造一个太阳能发电站,需要帮助计算财务。 
        - 土地费用为每平方英尺100美元
        - 我可以以每平方英尺250美元的价格购买太阳能电池板
        - 我已经谈判好了维护合同,每年需要支付固定的10万美元,并额外支付每平方英尺10美元
        作为平方英尺数的函数,首年运营的总费用是多少。

    学生的解决方案:

        设x为发电站的大小,单位为平方英尺。
        费用:
        1. 土地费用:100x
        2. 太阳能电池板费用:250x
        3. 维护费用:100,000+100x
        总费用:100x+250x+100,000+100x=450x+100,000

    实际解决方案和步骤:
    """
    return prompt

其输出为:

    首先,计算土地费用:100美元/平方英尺 * x平方英尺 = 100x美元
    然后,计算太阳能电池板费用:250美元/平方英尺 * x平方英尺 = 250x美元
    接着,计算维护费用:10万美元 + 10美元/平方英尺 * x平方英尺 = 10万 + 10x美元
    最后,计算总费用:100x + 250x + 10万 + 10x = 360x + 10万美元

    学生的解决方案和实际解决方案是否相同:否
    学生的成绩:不正确

3. 局限性

因为LLM并不能感知信息的边界,同时其接触了大量知识,因为在很多某些时候其会编造一些信息。

本文含有隐藏内容,请 开通VIP 后查看