让AI更聪明:零基础理解AI检索技术

内容分享8小时前发布
0 0 0

想知道如何让 AI 像图书管理员一样高效?老白将在本文中,深入浅出地讲解了 RAG 的核心原理,包括嵌入、类似性计算和生成技术,协助你快速掌握这项强劲的 AI 技术。

什么是 RAG?

想象一下,我们有一个超级机智的助手(就是 AI),但这个助手只知道它训练时学到的知识。如果我们想让它回答关于你自己文档的问题,它就需要先”阅读”这些文档。RAG(检索增强生成) 就是让 AI 能够先从我们的文档中找到相关内容,然后再生成回答的技术。

简单来说,RAG 的工作流程就像是:

  1. 把你的文档切成小段
  2. 把每段文字转换成”数字指纹”(嵌入)
  3. 当有人提问时,找到最相关的段落
  4. 把这些段落交给 AI,让它生成回答

让AI更机智:零基础理解AI检索技术

什么是嵌入?

用生活中的例子理解

想象我们要整理一个巨大的图书馆。如果只是随意放书,找书会很难。但如果我们按照”主题”来分类——把所有烹饪书放在一起,所有科幻小说放在一起——找书就容易多了。

嵌入就是给文字做类似的分类,但更准确。它把每段文字转换成一串数字(向量),这串数字就像是文字的”指纹”或”坐标”。意思相近的文字,它们的”指纹”也会很类似。

列如说:

  • “开心” 和 “快乐” 的指纹会超级接近
  • “开心” 和 “悲伤” 的指纹会相距很远

简单的例子

假设我们只用两个数字来表明单词(实际上会用几百个数字):

# 用两个数字表明每个词的"位置"
king  = [0.25, 0.75]   # 国王
queen = [0.23, 0.77]   # 王后
man   = [0.15, 0.80]   # 男人
woman = [0.13, 0.82]   # 女人

有趣的是,这些数字保留了词语之间的关系:

# 国王 - 男人 + 女人 = ?
king - man + woman
= [0.25, 0.75] - [0.15, 0.80] + [0.13, 0.82]
= [0.23, 0.77]
 queen   # 结果接近"王后"!

这说明嵌入能够捕捉到词语之间的关系!

可视化理解

我们可以把这些词放到一个坐标系里看看它们的位置:

让AI更机智:零基础理解AI检索技术

实际的嵌入更复杂

上面的例子只用了 2 个维度,但实际的嵌入模型会用 几百甚至上千个维度!列如:

  • Word2Vec 使用 300 维
  • BERT 使用 768 维

更多的维度意味着能够捕捉更丰富的语义信息。


如何判断两段文字有多类似?

余弦类似度:看两个箭头的”夹角”

当我们有了文字的”数字指纹”后,就可以用数学方法计算它们有多类似。最常用的方法是余弦类似度

让AI更机智:零基础理解AI检索技术

想象两支箭从同一点出发,指向不同方向:

  • 如果两支箭指向几乎一样的方向(夹角很小),它们很类似
  • 如果两支箭指向相反的方向(夹角很大),它们很不类似

让AI更机智:零基础理解AI检索技术

其他计算方法

除了余弦类似度,还有:

  • 欧几里得距离:直接测量两点之间的直线距离,距离越小越类似
  • 点积:另一种数学计算方法,需要先把向量标准化

RAG 如何找到最相关的内容?

第一步:计算类似度

当用户提出问题时,RAG 系统会:

  1. 把用户的问题也转换成嵌入
  2. 计算这个问题和每一段文档的类似度

让AI更机智:零基础理解AI检索技术

第二步:选择最相关的 k 个段落

系统会按类似度排序,然后选择最类似的几个段落(一般是 2-10 个),这就是所谓的 Top-k 检索

加速搜索:近似最近邻(ANN)

当文档许多时,逐一比较太慢了。近似最近邻搜索是一种机智的方法,它不追求100%准确,但能快许多,而且结果一般也足够好。


实际代码演示

下面是一个简单的 RAG 系统示例代码:

from openai import AzureOpenAI
import os
from langchain_community.document_loaders import TextLoader
from langchain_openai import AzureOpenAIEmbeddings
from langchain_community.vectorstores import FAISS
from langchain_core.documents import Document
from langchain_text_splitters import RecursiveCharacterTextSplitter

client = AzureOpenAI(
            api_key="your-azure-openai-key",               # API Key
            api_version=os.getenv("AZURE_OPENAI_API_VERSION", "2024-12-01-preview"), # 默认使用 2024-02-01
            azure_endpoint="https://your-resource-name.openai.azure.com/"  # 资源地址
        )

# 使用 Azure OpenAI Embeddings
embeddings = AzureOpenAIEmbeddings(
    deployment="your-embedding-deployment-name",   # 部署名称
    model="text-embedding-ada-002",                # 模型名称
    api_key="your-azure-openai-key",               # API Key
    azure_endpoint="https://your-resource-name.openai.azure.com/"  # 资源地址
)

# 加载文档
text_folder = "RAG files"

documents = []
for filename in os.listdir(text_folder):
    if filename.lower().endswith(".txt"):
        file_path = os.path.join(text_folder, filename)
        loader = TextLoader(file_path)
        documents.extend(loader.load())

# 把文档切成小段(每段约1000字,段与段之间有100字重叠)
splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=100)
split_docs = []
for doc in documents:
    chunks = splitter.split_text(doc.page_content)
    for chunk in chunks:
        split_docs.append(Document(page_content=chunk))

documents = split_docs

# 创建向量数据库(存储所有文档的嵌入)
vector_store = FAISS.from_documents(documents, embeddings)
retriever = vector_store.as_retriever()

def main():
    print("欢迎使用 RAG 助手。输入 'exit' 退出。
")

    while True:
        user_input = input("你:").strip()
        if user_input.lower() == "exit":
            print("退出…")
            break

        # 找到与问题最相关的文档段落
        relevant_docs = retriever.invoke(user_input)
        retrieved_context = "

".join([doc.page_content for doc in relevant_docs])

        # 告知 AI 只能用这些内容来回答
        system_prompt = (
            "你是一个乐于助人的助手。"
            "只能使用下面的内容来回答问题。"
            "如果答案不在内容中,请说你不知道。

"
            f"参考内容:
{retrieved_context}"
        )

        # 准备发送给 AI 的消息
        messages = [
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": user_input}
        ]

        # AI 生成回答
        response = client.chat.completions.create(
            model="gpt-4",
            messages=messages,
        )
       assistant_message = response.choices[0].message
        print(f"
助手:{assistant_message}
")

if __name__ == "__main__":
    main()

代码工作流程图

让AI更机智:零基础理解AI检索技术


使用余弦类似度替代默认方法

默认情况下,代码使用 L2 距离(欧几里得距离)来查找类似内容。如果想改用余弦类似度,需要先对向量进行归一化(把向量长度统一为1):

# 归一化函数:把向量的长度统一为1
import numpy as np
def normalize(vectors):
    vectors = np.array(vectors)
    norms = np.linalg.norm(vectors, axis=1, keepdims=True)
    return vectors / norms

# 归一化所有文档的嵌入
doc_texts = [doc.page_content for doc in documents]
doc_embeddings = embeddings.embed_documents(doc_texts)
doc_embeddings = normalize(doc_embeddings)

# 创建使用点积(内积)的索引
import faiss
dimension = doc_embeddings.shape[1]
index = faiss.IndexFlatIP(dimension)  # IP = Inner Product(内积)
index.add(doc_embeddings)

查询时也要归一化:

# 把用户问题的嵌入也归一化
query_embedding = embeddings.embed_query(user_input)
query_embedding = normalize([query_embedding])

# 在索引中搜索最类似的 k=2 个段落
D, I = index.search(query_embedding, k=2)

# D 包含类似度分数,I 包含段落的索引号

参数的重大性

不同的参数设置会影响 RAG 的效果:

参数

说明

影响

k 值

检索多少个相关段落

k 太小可能漏掉重大信息,k 太大可能引入噪音

chunk_size

每段文字的大小

太小会丢失上下文,太大会不够准确

chunk_overlap

段落之间的重叠

有助于保持段落间的连贯性

类似度阈值

最低类似度要求

过滤掉不够相关的内容

让AI更机智:零基础理解AI检索技术


总结

RAG 的核心就是三步:

  1. 嵌入:把文字变成数字,保留语义信息
  2. 类似度计算:用数学方法找到最相关的内容
  3. 生成:让 AI 基于找到的内容来回答

就像一个机智的图书管理员,先帮你找到最相关的几本书,然后再根据书中内容回答你的问题。如果大家有任何问题,欢迎评论区留言提问。老白也会在后续的章节中继续发布大模型相关内容,欢迎阅读!

相关阅读:

大模型RAG入门教程:从零开始理解(附含示例)

大模型RAG教程:如何轻松驾驭小文件到西游记巨著

© 版权声明

相关文章

暂无评论

none
暂无评论...