以下内容将从以下几个方面进行展开:
- 什么是大模型的「幻觉」
- 为何大模型幻觉无法被彻底解决
- 降低幻觉出现的常见技术手段与实践要点
- 一个示例项目结构(输出目录)及相关示例代码
1. 什么是大模型的「幻觉」
在自然语言处理(NLP)中,“大模型的幻觉”(Hallucination of Large Language Models)指的是大型预训练语言模型(LLM,如GPT-3、GPT-4 等)在生成文本时,给出了不符合事实或胡编乱造的信息,包括:
- 引用并不存在的文献;
- 虚构根本没有的 API、函数;
- 为问题提供完全不实的解释等。
这类现象并不是模型偶然产生的随机错误,而是模型在生成文字时,在上下文不充分或语义理解不完善的情况下,仍然会生成“貌似正确”但并不真实的回答。
2. 为何大模型幻觉无法被彻底解决
2.1 语言模型的生成机制
目前多数大型语言模型的底层原理是基于统计和概率分布的文本生成:
- 它们会根据已有的大规模训练语料,总结出语词序列之间的联合概率分布;
- 在推断阶段,模型仅能根据输入上下文和内部参数来预测下一个最可能的词或词序列;
- 由于语言模型并不真正理解所有事实知识,而是以参数的形式“记忆”或者“近似记忆”了训练集中的信息,模型会在缺少确凿事实基础时,依然按照语言模式生成看似合理但并不一定真实的回答。
2.2 训练数据与现实世界之间的差距
- 训练数据不可避免地包含不准确、不一致甚至矛盾的信息;
- 现实世界是不断演化的,训练完毕后出现的新信息无法被模型获取;
- 模型“知道”的知识并非全部来自权威或高质量数据。
2.3 大模型自身的知识检索和验证机制不完备
- 目前绝大多数大模型在推理时并不会内置“知识检索和验证”步骤;
- 即使内置了检索(如检索增强生成 RAG 技术),或使用插件去查询外部数据库、搜索引擎,也需要完善的查询、验证和纠错策略;
- 其背后也是复杂的自然语言理解与知识图谱或数据库技术,并无法保证百分百真实。
基于以上原因,“大模型幻觉”是模型在生成上的内在缺陷,无法被彻底杜绝。但我们可以通过额外的策略与技术,在实际应用中尽量将它的影响降到最低。
3. 降低幻觉出现的常见技术手段与实践要点
下面列举几种常见的技术手段并给出说明:
-
检索增强生成(Retrieval-Augmented Generation,RAG)
- 将模型的上下文与外部知识库(如向量数据库、搜索引擎、知识图谱等)进行结合,先检索出事实信息,再将检索到的结果注入模型输入;
- 可以在生成阶段通过将检索到的文档、证据或知识片段提供给模型,帮助它基于真实资料进行回答,从而降低幻觉概率。
-
引导式提示(Prompt Engineering)
- 通过系统提示 (System Prompt) 和用户提示 (User Prompt) 对模型进行更细致的指令;
- 明确要求模型在回答时给出来源、证据或理由;
- 设置“如果不能确认,请说明无法确认”等提示,让模型倾向于在不确定的情况下拒绝或质疑,而不是编造。
-
链式推理(Chain-of-Thought, CoT)
- 要求模型给出中间推理步骤,并通过检查这些推理步骤是否合理、是否有依据来辅助审查;
- 这种方法在一定程度上也有助于减小幻觉几率,但并不能从根本上消除幻觉。
-
后处理验证(Post-hoc Verification)
- 对模型的答案进行检测和过滤,如果发现疑似幻觉或与已知事实冲突的地方,进行二次查询或在外部知识库中查证;
- 可以用另一种模型或同一个模型的不同提示来进行交叉验证,也可以用自定义的规则(如正则匹配、知识图谱验证等)进行验证。
-
模型裁剪与小模型微调
- 如果应用场景有非常固定、明确的知识范围,可以在该细分领域内对大模型进行微调或蒸馏,以便模型“专精”于某个领域的知识;
- 这样可减小开放域带来的幻觉风险,尽管仍无法完全避免。
4. 示例项目结构及示例代码
下面给出一个示例性的项目结构以及相关的部分 Python 代码片段,展示如何使用检索增强的方式降低大模型幻觉。本例仅作演示用途,实际落地时可根据自己的需求加以修改和扩展。
4.1 项目结构(输出目录)
假设我们的项目目录如下所示:
my_llm_project/
├── data/
│ ├── documents/
│ │ ├── doc1.txt
│ │ ├── doc2.txt
│ │ └── ...
│ └── knowledge_base.json
├── embeddings/
│ └── build_index.py
├── main.py
├── requirements.txt
├── README.md
└── utils/
└── retriever.py
data/documents/
:存放要用于检索的文本或文档。data/knowledge_base.json
:可选的额外结构化知识库(比如一些 JSON 格式的 FAQ)。embeddings/build_index.py
:负责将本地文档进行文本拆分、向量化、构建索引;main.py
:主入口示例代码,通过命令行或接口调用大模型及检索功能;requirements.txt
:项目的 Python 包依赖列表;utils/retriever.py
:实现检索逻辑的工具函数,比如调用向量数据库或搜索引擎;README.md
:简单的项目说明。
4.2 准备环境
在 requirements.txt
(或直接命令行)中列出所需主要依赖,比如:
openai==0.27.0
faiss-cpu==1.7.2
numpy==1.23.5
tqdm==4.64.1
如果使用别的向量数据库(如 Milvus、Chroma 等),需要安装对应的 Python SDK。
4.3 构建向量索引:build_index.py
下面是一个简单示例,演示如何将 data/documents/
下的若干文档进行向量化并用 FAISS 构建索引(仅作演示,具体实现可依据实际需求变动):
# embeddings/build_index.py
import os
import faiss
import numpy as np
from tqdm import tqdm
from openai import OpenAIEmbeddings # 示例:使用OpenAI的embeddings,请根据真实代码改写
# 假设你自己封装好的获取文本向量的类
class EmbeddingClient:
def __init__(self, api_key: str):
self.api_key = api_key
# 如果使用OpenAI官方API,则需要设置 openai.api_key = api_key
# openai.api_key = api_key
def embed_text(self, text: str) -> np.ndarray:
# 使用OpenAI提供的接口或其它第三方向量化
# 返回 1536 维的embedding,示例用随机数代替
# 真实的场景需要替换为 openai.Embedding.create(...) 等逻辑
return np.random.rand(1536).astype(np.float32)
def build_index(data_folder: str, output_index_path: str, api_key: str):
documents = []
file_paths = []
for filename in os.listdir(data_folder):
file_path = os.path.join(data_folder, filename)
if os.path.isfile(file_path) and file_path.endswith('.txt'):
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
documents.append(content)
file_paths.append(file_path)
# 初始化 Embedding 客户端
embedding_client = EmbeddingClient(api_key)
# 计算文档向量
embeddings = []
for doc in tqdm(documents, desc="Embedding documents"):
vec = embedding_client.embed_text(doc)
embeddings.append(vec)
embeddings = np.stack(embeddings, axis=0)
# 建立 FAISS Index
dimension = embeddings.shape[1] # 1536
index = faiss.IndexFlatL2(dimension)
index.add(embeddings)
# 为了后续能找到具体的文本,可以再额外保存映射关系
faiss.write_index(index, output_index_path)
# 这里可以保存文档id-文本的映射(比如使用numpy的save或json)
np.save(output_index_path + "_mapping.npy", file_paths)
if __name__ == "__main__":
api_key = "your_openai_api_key" # 在实际使用时替换为真实的 Key
data_folder = "../data/documents"
output_index_path = "../data/faiss_index.bin"
build_index(data_folder, output_index_path, api_key)
4.4 检索模块:retriever.py
在模型生成回答前,可以先检索最相关的文档,减少无根据回答:
# utils/retriever.py
import faiss
import numpy as np
class Retriever:
def __init__(self, index_path: str):
self.index = faiss.read_index(index_path)
self.file_paths = np.load(index_path + "_mapping.npy", allow_pickle=True)
def retrieve(self, query_embedding: np.ndarray, top_k: int = 3):
# 向量检索
distances, indices = self.index.search(query_embedding, top_k)
results = []
for dist, idx in zip(distances[0], indices[0]):
doc_path = self.file_paths[idx]
results.append((doc_path, dist))
return results
4.5 主入口:main.py
最后在主文件中演示如何先检索再让大模型回答:
# main.py
import os
import openai
import numpy as np
from utils.retriever import Retriever
from embeddings.build_index import EmbeddingClient
openai.api_key = "your_openai_api_key"
def generate_answer_with_retrieval(question: str, retriever: Retriever, embedding_client: EmbeddingClient):
"""
先对问题做向量化检索,再将检索到的文档内容注入prompt给大模型。
"""
# 1. 获取问题向量
query_vec = embedding_client.embed_text(question).reshape(1, -1)
# 2. 检索最相关的文档
top_k_docs = retriever.retrieve(query_vec, top_k=3)
# 3. 读取文档内容,构造提示
context_texts = []
for doc_path, distance in top_k_docs:
with open(doc_path, 'r', encoding='utf-8') as f:
doc_content = f.read()
context_texts.append(doc_content)
# 4. 构造系统提示/上下文
context_prompt = ""
for i, txt in enumerate(context_texts):
context_prompt += f"文档{i+1}内容:\n{txt}\n"
prompt = f"""
你是一名智能问答助手。请根据以下提供的文档内容回答用户问题,如果你不确定答案或文档没有包含答案,请直接说明你不确定或无法找到答案。
{context_prompt}
请回答用户的问题: {question}
"""
# 5. 调用大模型生成回答
response = openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=[
{"role": "system", "content": "你是一名可靠且有依据的问答助手。"},
{"role": "user", "content": prompt}
],
temperature=0.2
)
answer = response["choices"][0]["message"]["content"]
return answer
if __name__ == "__main__":
# 初始化retriever和embedding_client
index_path = "data/faiss_index.bin"
retriever = Retriever(index_path)
embedding_client = EmbeddingClient(api_key=openai.api_key)
while True:
user_question = input("请输入你的问题(输入exit退出): ")
if user_question.lower() == "exit":
break
answer = generate_answer_with_retrieval(user_question, retriever, embedding_client)
print("答复:", answer)
关键说明
generate_answer_with_retrieval
函数中,在给大模型下达最终指令时,明确告知“若文档没有答案,请直说不确定”,在一定程度上可以降低幻觉出现的概率。- 通过检索到的文档内容和 user prompt 的结合,模型可以根据更准确的上下文回答,而不是完全依赖训练参数中的“记忆”。
- 当然,这种方式也取决于文档内容是否权威、完备,如果本身提供的文档就存在错误或含混不清的地方,模型可能仍然产生幻觉。
总结
- 大模型幻觉是基于概率分布的语言生成机制所带来的一种常见现象,难以彻底消除。
- 通过检索增强(RAG)、Prompt Engineering、链式推理和后处理验证等手段,可以在许多实际应用中有效减少幻觉发生率。
- 在生产环境中,通常会使用多重验证(交叉验证、结构化检索、事实检测等)结合专业领域的知识库来进一步提高准确性。
核心思路是:
- 通过精心构造的上下文或检索机制,给模型提供充分且精准的信息;
- 如果模型不确定,诱导其“拒答”或“质疑”,而非编造;
- 对模型输出进行后续验证,结合外部工具、数据或其他模型再行确认。
这样可以最大限度减少幻觉带来的负面影响,并提高大模型应用的可靠性。