大型语言模型(LLM)是先进的人工智能系统,可以回答各种问题。尽管它们在已知的主题上提供了有用的回答,但在陌生的主题上并不总是准确。这种现象被称为幻觉。
# 什么是幻觉?
在我们看一个LLM幻觉的例子之前,让我们先考虑一下维基百科对“幻觉”的定义:
“幻觉是在没有外部刺激的情况下产生的具有真实感的感知。”
此外:
“幻觉是生动的、实质性的,并被认为位于外部客观空间中。”
换句话说,幻觉是对某个真实或具体事物的错误(或虚假)感知。例如,ChatGPT(OpenAI的一种著名大型语言模型)被问到LLM幻觉是什么,它的回答是:
因此,问题是,我们如何改进(或修复)这个结果?简明的答案是在问题中添加事实,例如在提问之前或之后提供LLM的定义。
例如:
LLM是大型语言模型(Large Language Model),一种模拟人类口语和书写方式的人工神经网络。请告诉我,LLM幻觉是什么?
ChatGPT对这个问题的公共领域回答是:
注意:
第一句话“对我之前回答中的混淆表示抱歉”的原因是我们在给ChatGPT提问“LLM幻觉是什么”之前,给它了第二个提示:“LLM是…”。
这些补充提高了答案的质量。至少它不再认为LLM幻觉是“晚期生活偏头痛的伴随症状”!😆
# 外部知识减少幻觉
在这个关键时刻,非常重要的一点是,LLM并不是无所不能、无所不知的终极权威。LLM是在大量数据上进行训练的,它学习语言中的模式,但它可能并不总是能够获得最新的信息或对复杂主题有全面的理解。
那么现在怎么办?如何增加减少LLM幻觉的机会?
解决这个问题的方法是在查询(或提示)中包含支持文档,以引导LLM获得更准确和明智的回答。就像人类一样,它需要从这些文档中学习,以准确正确地回答您的问题。
有许多来源可以提供有用的文档,包括Google或Bing等搜索引擎以及Arxiv等数字图书馆,它们提供了一个搜索相关段落的接口。使用数据库也是一个不错的选择,提供了更灵活和私密的查询接口。
从来源中检索到的知识必须与问题/提示相关。有几种检索相关文档的方法,包括:
- **基于关键词:**在纯文本中搜索关键词,适用于对术语进行精确匹配。
- **基于向量搜索:**搜索更接近嵌入的记录,有助于搜索适当的释义或常规文档。
如今,向量搜索非常流行,因为它可以解决释义问题并计算段落的含义。向量搜索不是一种一刀切的解决方案;它应该与特定的过滤器配对使用,以保持其性能,特别是在搜索大量记录时。例如,如果您只想检索与物理学(作为一个学科)相关的知识,您必须过滤掉所有关于其他学科的信息。这样,LLM就不会被来自其他学科的知识所困惑。
# 使用SQL自动化整个过程...和向量搜索
在回答问题之前,LLM还应该学会从数据源中查询数据,自动化整个过程。实际上,LLM已经能够编写SQL查询并遵循指令。
SQL功能强大,可以用于构建复杂的搜索查询。它支持许多不同的数据类型和函数。它允许我们使用ORDER BY
和LIMIT
编写向量搜索的SQL查询,将嵌入之间的相似度分数视为列distance
。非常简单明了,不是吗?
有关如何构建向量SQL查询的更多信息,请参见下一节向量SQL的样子。
使用向量SQL构建复杂的搜索查询有很多好处,包括:
- 增加了对数据类型和函数的灵活性支持
- 提高了效率,因为SQL在数据库内部高度优化和执行
- 人类可读且易于学习,因为它是标准SQL的扩展
- 对LLM友好
注意:
互联网上有很多SQL示例和教程。LLM熟悉标准SQL以及一些方言。
除了MyScale之外,许多SQL数据库解决方案(如Clickhouse和PostgreSQL)正在将向量搜索添加到其现有功能中,允许用户使用向量SQL和LLM回答关于复杂主题的问题。同样,越来越多的应用程序开发人员开始将向量搜索与SQL集成到他们的应用程序中。
# 向量SQL的样子
向量结构化查询语言(Vector SQL)旨在教会LLM如何查询向量SQL数据库,并包含以下额外函数:
DISTANCE(column, query_vector)
: 此函数比较列向量和查询向量之间的距离,可以是精确匹配或近似匹配。NeuralArray(entity)
: 此函数将实体(例如图像或文本片段)转换为嵌入。
使用这两个函数,我们可以扩展标准SQL以进行向量搜索。例如,如果您想搜索与词flower
相关的10个相关记录,可以使用以下SQL语句:
SELECT * FROM table ORDER BY DISTANCE(vector, NeuralArray(flower)) LIMIT 10
Copied!
DISTANCE
函数由以下内容组成:
- 内部函数
NeuralArray(flower)
将词flower
转换为嵌入。 - 然后将此嵌入序列化并注入
DISTANCE
函数中。
向量SQL是SQL的扩展版本,需要根据所使用的向量数据库进行进一步的转换。例如,许多实现对DISTANCE
函数有不同的名称。在MyScale中称为distance
,在Clickhouse中称为L2Distance
或CosineDistance
。此外,根据数据库的不同,此函数名称将有不同的翻译。
# 如何教LLM编写向量SQL
现在我们了解了向量SQL的基本原理和其独特的函数,让我们使用LLM来帮助我们编写向量SQL查询。
# 1. 教LLM标准的向量SQL是什么
首先,我们需要教LLM标准的向量SQL是什么。我们的目标是确保LLM在编写向量SQL查询时能够自动执行以下三个操作:
- 从我们的问题/提示中提取关键词。它可以是一个对象、一个概念或一个主题。
- 决定使用哪个列执行相似性搜索。它应该始终选择一个用于相似性的向量列。
- 将我们问题的其余约束条件转换为有效的SQL。
# 2. 设计LLM提示
确定LLM构建向量SQL查询所需的确切信息后,我们可以设计提示,如下所示:
# 这是一个向量SQL提示的示例 _prompt = f"""您是一个MyScale专家。给定一个输入问题,首先创建一个语法正确的MyScale查询来运行,然后查看查询的结果并返回输入问题的答案。 MyScale查询具有一个名为`DISTANCE(column, array)`的向量距离函数,用于计算与用户问题的相关性,并通过该相关性对特征数组列进行排序。 当查询要求获取{top_k}个最接近的行时,您必须使用此距离函数计算到实体的数组在向量列上的距离,并按照距离排序以检索相关行。 *注意*:`DISTANCE(column, array)`只接受数组列作为其第一个参数,并接受一个`NeuralArray(entity)`作为其第二个参数。您还需要一个名为`NeuralArray(entity)`的用户定义函数来检索实体的数组。 除非用户在问题中指定了要获取的示例数量,否则使用LIMIT子句查询最多{top_k}个结果,正如MyScale所要求的那样。您只能按照距离函数的顺序排序。 永远不要查询表中的所有列。您必须只查询需要回答问题的列。将每个列名用双引号(")括起来,以将其标识为定界符。 注意只使用您在下面的表中看到的列名。注意不要查询不存在的列。还要注意哪个列在哪个表中。 如果问题涉及“今天”,请注意使用today()函数获取当前日期。`ORDER BY`子句应始终位于`WHERE`子句之后。不要在SQL的末尾添加分号。注意表模式中的注释。 使用以下格式: ======== 表信息 ======== <一些表信息> 问题:“问题在这里” SQL查询:“要运行的SQL查询” 让我们开始: ======== 表信息 ======== {table_info} 问题:{input} SQL查询:
Copied!
这个提示应该能够完成其工作。但是,您添加的示例越多,构建正确的向量SQL查询的LLM过程就会越好,就像使用以下向量SQL到文本对作为提示:
SQL表创建语句:
------ 表模式 ------ CREATE TABLE "ChatPaper" ( abstract String, id String, vector Array(Float32), categories Array(String), pubdate DateTime, title String, authors Array(String), primary_category String ) ENGINE = ReplicatedReplacingMergeTree() ORDER BY id PRIMARY KEY id
Copied!
问题和答案:
问题:什么是PaperRank?这些作品的贡献是什么?使用具有超过2个类别的论文。 SQL查询:SELECT ChatPaper.title, ChatPaper.id, ChatPaper.authors FROM ChatPaper WHERE length(categories) > 2 ORDER BY DISTANCE(vector, NeuralArray(PaperRank contribution)) LIMIT {top_k}
Copied!
您添加的相关示例越多,LLM构建正确的向量SQL查询的能力就越好。
最后,以下是一些设计提示时的额外提示:
- 覆盖可能出现在任何问题中的所有可能函数。
- 避免单调的问题。
- 修改表模式,例如添加/删除/修改名称和数据类型。
- 对齐提示的格式。
# 一个真实世界的例子:使用MyScale
让我们现在构建一个真实世界的例子 (opens new window),按照以下步骤进行:
# 准备数据库
我们已经为您准备了一个游乐场,其中有超过200万篇论文可以查询。您可以通过将以下Python代码添加到您的应用程序中来访问这些数据。
from sqlalchemy import create_engine MYSCALE_HOST = "msc-950b9f1f.us-east-1.aws.myscale.com" MYSCALE_PORT = 443 MYSCALE_USER = "chatdata" MYSCALE_PASSWORD = "myscale_rocks" engine = create_engine(f'clickhouse://{MYSCALE_USER}:{MYSCALE_PASSWORD}@{MYSCALE_HOST}:{MYSCALE_PORT}/default?protocol=https')
Copied!
如果您愿意,您可以跳过接下来的步骤,其中我们使用MyScale控制台创建表并插入数据,并跳到我们使用向量SQL和创建SQLDatabaseChain
来查询数据库的部分。
创建数据库表:
CREATE TABLE default.ChatArXiv ( `abstract` String, `id` String, `vector` Array(Float32), `metadata` Object('JSON'), `pubdate` DateTime, `title` String, `categories` Array(String), `authors` Array(String), `comment` String, `primary_category` String, CONSTRAINT vec_len CHECK length(vector) = 768) ENGINE = ReplacingMergeTree ORDER BY id SETTINGS index_granularity = 8192
Copied!
插入数据:
INSERT INTO ChatArXiv SELECT abstract, id, vector, metadata, parseDateTimeBestEffort(JSONExtractString(toJSONString(metadata), 'pubdate')) AS pubdate, JSONExtractString(toJSONString(metadata), 'title') AS title, arrayMap(x->trim(BOTH '"' FROM x), JSONExtractArrayRaw(toJSONString(metadata), 'categories')) AS categories, arrayMap(x->trim(BOTH '"' FROM x), JSONExtractArrayRaw(toJSONString(metadata), 'authors')) AS authors, JSONExtractString(toJSONString(metadata), 'comment') AS comment, JSONExtractString(toJSONString(metadata), 'primary_category') AS primary_category FROM s3( 'https://myscale-demo.s3.ap-southeast-1.amazonaws.com/chat_arxiv/data.part*.zst', 'JSONEachRow', 'abstract String, id String, vector Array(Float32), metadata Object(''JSON'')', 'zstd' ); ALTER TABLE ChatArXiv ADD VECTOR INDEX vec_idx vector TYPE MSTG('metric_type=Cosine');
Copied!
# 创建VectorSQLDatabaseChain
您将需要LangChain实验性包来使用VectorSQLDatabaseChain
。您可以通过执行以下安装脚本来安装它:
python3 -m venv .venv source .venv/bin/activate pip3 install langchain langchain-experimental --upgrade
Copied!
安装了此功能后,下一步是使用它来查询数据库,如下所示的Python代码:
from sqlalchemy import create_engine MYSCALE_HOST = "msc-950b9f1f.us-east-1.aws.myscale.com" MYSCALE_PORT = 443 MYSCALE_USER = "chatdata" MYSCALE_PASSWORD = "myscale_rocks" # 创建与数据库的连接 engine = create_engine(f'clickhouse://{MYSCALE_USER}:{MYSCALE_PASSWORD}@{MYSCALE_HOST}:{MYSCALE_PORT}/default?protocol=https') from langchain.embeddings import HuggingFaceInstructEmbeddings from langchain.callbacks import StdOutCallbackHandler from langchain.llms import OpenAI from langchain.utilities.sql_database import SQLDatabase from langchain_experimental.sql.prompt import MYSCALE_PROMPT from langchain_experimental.sql.vector_sql import VectorSQLDatabaseChain from langchain_experimental.sql.vector_sql import VectorSQLRetrieveAllOutputParser # 此解析器将`NeuralArray()`转换为嵌入 output_parser = VectorSQLRetrieveAllOutputParser( model=HuggingFaceInstructEmbeddings(model_name='hkunlp/instructor-xl') ) # 使用上述提示 PROMPT = PromptTemplate( input_variables=["input", "table_info", "top_k"], template=_prompt, ) # 将元数据绑定到SqlAlchemy引擎 metadata = MetaData(bind=engine) # 创建SQLDatabaseChain query_chain = VectorSQLDatabaseChain.from_llm( # GPT-3.5生成更好的有效SQL llm=OpenAI(openai_api_key=OPENAI_API_KEY, temperature=0), # 使用预定义的提示,根据需要更改 prompt=PROMPT, # 返回前10个相关文档 top_k=10, # 直接使用数据库中的结果 return_direct=True, # 使用我们的数据库进行检索 db=SQLDatabase(engine, None, metadata), # 将`NeuralArray()`转换为嵌入 sql_cmd_parser=output_parser) # 启动链!并将所有链调用跟踪到标准输出 query_chain.run("介绍一些在2019年左右发表的使用生成对抗网络的论文。", callbacks=[StdOutCallbackHandler()])
Copied!
# 使用 RetrievalQAwithSourcesChain
进行提问
您还可以将这个VectorSQLDatabaseChain
作为检索器使用。您可以将其插入到一些检索QA链中,就像LangChain中的其他检索器一样。
from langchain_experimental.retrievers.vector_sql_database \ import VectorSQLDatabaseChainRetriever from langchain.chains.qa_with_sources.map_reduce_prompt import combine_prompt_template OPENAI_API_KEY = "sk-***" # 定义如何序列化来自数据库的结构化数据 document_with_metadata_prompt = PromptTemplate( input_variables=["page_content", "id", "title", "authors", "pubdate", "categories"], template="内容:\n\t标题:{title}\n\t摘要:{page_content}\n\t" + "作者:{authors}\n\t出版日期:{pubdate}\n\t类别:{categories}\n来源:{id}" ) # 定义您用于询问LLM的提示 COMBINE_PROMPT = PromptTemplate( template=combine_prompt_template, input_variables=["summaries", "question"]) # 使用SQLDatabaseChain创建一个检索器 retriever = VectorSQLDatabaseChainRetriever( sql_db_chain=query_chain, page_content_key="abstract") # 最后,使用检索器创建一个问答链 ask_chain = RetrievalQAWithSourcesChain.from_chain_type( ChatOpenAI(model_name='gpt-3.5-turbo-16k', openai_api_key=OPENAI_API_KEY, temperature=0.6), retriever=retriever, chain_type='stuff', chain_type_kwargs={ 'prompt': COMBINE_PROMPT, 'document_prompt': document_with_metadata_prompt, }, return_source_documents=True) # 运行链!并从LLM获取结果 ask_chain("介绍一些在2019年左右发表的使用生成对抗网络的论文。", callbacks=[StdOutCallbackHandler()])
Copied!
我们还在huggingface (opens new window)上提供了一个实时演示,代码在GitHub (opens new window)上可用!我们使用了自定义的检索QA链 (opens new window)来最大化我们的搜索和提问管道在LangChain中的性能!
# 总结
在现实中,大多数LLM都会产生幻觉。减少其出现的最实际的方法是在问题中添加额外的事实(外部知识)。外部知识对于提高LLM系统的性能至关重要,可以实现高效准确地检索答案。每个词都很重要,您不希望浪费金钱来检索通过不准确的查询获得的未使用的信息。
如何做到?
这就是向量SQL的用武之地,它允许您执行精细粒度的向量搜索,以定位和检索所需的信息。
向量SQL功能强大且易于人和机器学习。您可以使用许多数据类型和函数创建复杂的查询。LLM也喜欢向量SQL,因为它的训练数据集包含许多参考资料。
最后,可以根据不同的嵌入模型将向量SQL转换为许多向量数据库。我们相信这是向量数据库的未来。
对我们正在做的事情感兴趣吗?立即加入我们的discord (opens new window)!