是谁~还不会优雅的构建fewshot!

2024-04-18 21:19:31 浏览数 (2)

大型语言模型的few-shot能力指的是它们在只提供极少量样本或示例时,就能够理解并执行特定任务的能力。这种能力使得模型能够在新的上下文中进行推理和表达新任务,而无需大量的训练数据。

我们在实践中能体验到LLM是具有强大的few-shot学习能力的。它的few-shot能力可以让在用户提供一个或几个示例就能指导模型执行新的任务,而无需额外的训练;可以在有badcase时提供几个示例来指导模型进行校正然后正确执行任务,而无需额外的训练。但是这种能力在省力的同时也是相当费时费钱。因为随着需求的增加或者badcase的增加,fewshot会变的越来越不few,prompt越长也就意味着预测越耗时,token(钱)越多。

所以我们需要一种方案:我们可以有moreshot,接受到用户问题后,从moreshot中召回和用户问题相关的fewshot加入到prompt中再给模型。我现在一看见召回就想到RAG,RAG的主流框架langchain,虽然我是越用越头疼,但是它功能是真的全,fewshot的问题它早就给大家提供方案了~

今天给大家介绍的就是langchain框架中的SemanticSimilarityExampleSelector

SemanticSimilarityExampleSelector-语义相似度示例选择器,看这个类的名字都能猜到它的功能和实现的流程:

  1. 将所有示例进行向量化,存储到向量化数据库db中
  2. 对用户问题进行向量化q_embedding
  3. q_embedding和db中向量进行相似度计算,检索到距离top个实例
  4. 作为fewshot加入到prompt模版

下面我们就用一个翻译示例实战,看看怎么构建自己的fewshot_message。

1. 思路验证

我们先看看SemanticSimilarityExampleSelector类的核心源码:

代码语言:javascript复制
class SemanticSimilarityExampleSelector(BaseExampleSelector, BaseModel):
    ...
    def select_examples(self, input_variables: Dict[str, str]) -> List[dict]:
        """Select which examples to use based on semantic similarity."""
        # Get the docs with the highest similarity.
        if self.input_keys:
            input_variables = {key: input_variables[key] for key in self.input_keys}
        vectorstore_kwargs = self.vectorstore_kwargs or {}
        query = " ".join(sorted_values(input_variables))
        example_docs = self.vectorstore.similarity_search(
            query, k=self.k, **vectorstore_kwargs
        )
        # Get the examples from the metadata.
        # This assumes that examples are stored in metadata.
        examples = [dict(e.metadata) for e in example_docs]
        # If example keys are provided, filter examples to those keys.
        if self.example_keys:
            examples = [{k: eg[k] for k in self.example_keys} for eg in examples]
        return examples

select_examples计算query和vectorstore中示例的相似度,返回top个examples。

果然和我们想的一样的流程~

这么个小功能,干脆从langchain里将相关源码抠出来得了,没想到代码是越抠越多,langchain,langchian_community,langchain_core三个目录是一个没有逃过。。langchain真是让人又爱又恨。

2. 编写示例

创建一个examples.json文件,将你的所有示例按如下格式写入。

这里只是举例,类别区分并不大~

代码语言:javascript复制
[
  {
    "input": "人工智能的快速发展为解决复杂问题、改善生活质量和推动科学研究提供了新的机遇。",
    "output": "The rapid development of artificial intelligence provides new opportunities for solving complex problems, improving the quality of life, and advancing scientific research.."
  },
  {
    "input": "人工智能伦理和隐私问题引起了广泛关注,社会需要建立适当的法规和政策来保障公众权益。。",
    "output": "Ethical and privacy concerns related to artificial intelligence have garnered widespread attention, and there is a societal need to establish appropriate regulations and policies to safeguard public interests."
  },
  {
    "input": "聊天机器人的普及使得在网站和应用中提供实时客户支持变得更加便捷和高效。",
    "output": "The prevalence of chatbots makes it more convenient and efficient to provide real-time customer support on websites and applications."
  },
  {
    "input": "医学研究表明,定期锻炼和健康饮食对于预防慢性疾病至关重要。",
    "output": "Medical research indicates that regular exercise and a healthy diet are crucial for preventing chronic diseases.."
  },
  {
    "input": "全球范围内的医疗团队正在共同努力,以应对新型冠状病毒的传播,并寻找有效的治疗方法。",
    "output": "Medical teams worldwide are collaborating to combat the spread of the novel coronavirus and explore effective treatment methods.."
  },
  {
    "input": "在医疗保健系统中,科技创新已经推动了患者护理的改善,使得诊断和治疗更加精准和高效。",
    "output": "In the healthcare system, technological innovation has driven improvements in patient care, making diagnostics and treatments more precise and efficient."
  },
  ...
]

3. 创建SemanticSimilarityExampleSelector对象

创建SemanticSimilarityExampleSelector很简单:

代码语言:javascript复制
example_selector = SemanticSimilarityExampleSelector.from_examples(
        input_keys=["input"],
        examples=examples,
        embeddings=MyEmbeddings(),
        vectorstore_cls=Chroma,
        k=1,
        )

但是这里需要注意几个点:

  1. input_keys是很重要的参数,在匹配用户问题时不要将整个示例都丢给embedding模型进行计算,不仅耗时还影响效果,只需要传入input字段(需要和用户问题去匹配的字段);
  2. embedding模型如果是自己的API或者本地模型,需要自定义Embedding类,实现embed_documents和embed_query接口即可,其中embed_query就可以调用自己的API,返回对应的embedding结果。
代码语言:javascript复制
class MyEmbeddings(Embeddings, BaseModel):
    """Interface for embedding models."""    
    def embed_documents(self, texts: List[str]) -> List[List[float]]:
        """Embed search docs."""
        embeddings = []
        for text in texts:
            embedding = self.embed_query(text)
            embeddings.append(embedding)
        return embeddings
             
    def embed_query(self, text: str) -> List[float]:
        """自定义embedding接口"""
        return your_response

4. 创建Messages

通过以上步骤检索到问题相关的prompt后,还需要构建Message给大模型,以下是openAI的Messages格式,如果你是自定义LLM,可以参考ChatMessagePromptTemplate来构建自己的消息格式。

(目前主流LLM都会去兼容openAI格式...如果你是自研LLM,也建议不要在这些格式问题上特立独行,加大后面使用的门槛)

代码语言:javascript复制
few_shot_prompt = FewShotChatMessagePromptTemplate(
                input_variables=["input"],
                example_selector=example_selector,
                example_prompt=(
                    HumanMessagePromptTemplate.from_template("中文:{input}")
                      AIMessagePromptTemplate.from_template("英文:{output}")
                ),
            )
final_prompt = (
                SystemMessagePromptTemplate.from_template(
                    "你是一个专业的英文翻译。请根据以下格式回答用户问题:"
                )
                  few_shot_prompt
                  HumanMessagePromptTemplate.from_template("中文:{input}")
            )
result = final_prompt.format_messages(input=query)
return result

5. 优化数据库实例

大家在运行上面代码的时候不知道有没有发现什么问题,这种构造方法会在每次运行都要将所有示例给到数据库db进行一次向量化。

数据库如果不更新的话,是不需要每次都重新生成db的。即使数据库更新,我们也只需要离线更新数据库,不需要在运行时去生成数据库。

所以我们需要把逻辑优化一把:

代码语言:javascript复制
def get_vectorstore(examples_path="examples.json", input_keys=["input"]):
    """_summary_

    Args:
        examples_path (str, optional): 示例文件,Defaults to "examples.json".
        input_keys (list, optional): 需要过滤的key. Defaults to ["input"].

    Returns:
        _type_: 向量数据库
    """
    with open(examples_path, 'r', encoding='utf-8') as json_file:
        examples = json.load(json_file)
    to_vectorize=[]
    if input_keys:
        to_vectorize = [
                " ".join(sorted_values({k: eg[k] for k in input_keys}))
                for eg in examples
            ]
    else:
        to_vectorize = [
            " ".join(example.values())
            for example in examples
            ]
    embeddings = MyEmbeddings()
    vectorstore = Chroma.from_texts(
        to_vectorize, embeddings, metadatas=examples
        )
    return vectorstore
vectorstore = get_vectorstore(examples_path)

def test_smei_fewshot_prompt_v2(examples_path, query):
    """ 根据相似度获取fewshot
    """
    # vectorstore单例生产一次就行
    example_selector = SemanticSimilarityExampleSelector(
        input_keys=["input"],
        vectorstore=vectorstore,
        k=1,
        )
        ...

最后看看效果~

现在我们有了带fewshot的prompt和Messages,剩下的问题就交给大模型吧~

我正在参与2024腾讯技术创作特训营最新征文,快来和我瓜分大奖!

0 人点赞