RAG-搞懂嵌入向量的生成

上文我们讲到如何对文档进行分块,那文档分块后就能直接放入向量数据库中并检索了吗?答案是否定的,文档分块后需要通过嵌入模型将数据转成向量表示。所以本文主要讲述如何将数据转成向量以及选择合适的嵌入模型。

在文档切块后,通过embedding将文本计算成向量表示,并存入向量库

代码实现
相似度匹配

通过openAItext-embedding-3-small嵌入模型实现:查询哪位用户最关注"浙江某科技有限公司"。将文本转换成稠密向量(Dense),通过语义相似度,找到这位用户。

流程如下:

企业财务报表.csv

实现:

ini 复制代码
import os
from dotenv import load_dotenv
load_dotenv()
import pandas as pd
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity
from openai import OpenAI
​
​
# 客户端
client = OpenAI(
    base_url=os.getenv("PROXY_AI_BASE_URL"),
    api_key=os.getenv("PROXY_AI_API_KEY")
)
​
# ==============================
# user_id, company_name
# ==============================
df = pd.read_csv("企业财务报表.csv", encoding="utf-8")
​
# ==============================
# 生成向量函数
# ==============================
def get_embedding(text):
    rsp = client.embeddings.create(input=[text], model="text-embedding-3-small")
    return np.array(rsp.data[0].embedding)
​
# ==============================
# 为每个企业生成一个简单的文本
# ==============================
company_names = df["company_name"].unique()
company_texts = {c: f"企业名称:{c}" for c in company_names}
​
# ==============================
# 生成企业向量
# ==============================
company_vectors = {}
for c in company_names:
    vec = get_embedding(company_texts[c])
    company_vectors[c] = vec
    print(f"\n企业:{c}")
    print("向量前10个值:", vec[:10])  # <-- 打印真实向量
​
# ==============================
# 生成用户向量(关注企业的平均向量)
# ==============================
user_vectors = {}
for user_id, group in df.groupby("user_id"):
    vecs = [company_vectors[row["company_name"]] for _, row in group.iterrows()]
    user_vectors[user_id] = np.mean(vecs, axis=0)
​
# ==============================
# 随便选一个目标企业
# ==============================
target_company = "浙江某科技有限公司"
target_vec = company_vectors[target_company]
​
# ==============================
# 计算相似度
# ==============================
results = []
for uid, uvec in user_vectors.items():
    sim = cosine_similarity([uvec], [target_vec])[0][0]
    results.append((uid, sim))
​
result_df = pd.DataFrame(results, columns=["user_id", "similarity"])
result_df = result_df.sort_values(by="similarity", ascending=False)
​
print("\n======================================")
print(f"最可能关注 {target_company} 的用户")
print(result_df.head())

输出结果:

从结果上看user011是最关注"浙江某科技有限公司"的用户。

关键词检索

用BM25算法,把关键词文本变成稀疏向量。

其流程:财务文本->分词(营业收入、净利润、资产负债率...)->统计词频->计算BM25分数(重要关键词=高分)->输出【稀疏向量】{编号:分数}

实现:

ini 复制代码
from collections import Counter
import math
​
# ======================
# 企业财务知识文本(你的项目场景)
# ======================
finance_texts = [
    "2024年公司营业收入12.5亿元,同比增长18%,净利润1.8亿元",
    "企业资产负债率22%,流动比率1.98,偿债能力处于健康水平",
    "研发投入1.9亿元,占总营收比例15.2%,同比提升2.3个百分点",
    "经营活动现金流净额3.2亿元,比去年同期增加1.1亿元",
    "公司毛利率14.6%,期间费用率控制在8.3%,盈利能力稳定"
]
​
# 超参数(BM25标准)
k1 = 1.5
b = 0.75
​
# ======================
# 构建财务词表
# ======================
vocabulary = set(word for text in finance_texts for word in text.split(","))
vocab_to_idx = {word: idx for idx, word in enumerate(vocabulary)}
​
# ======================
# 计算 IDF
# ======================
N = len(finance_texts)
df = Counter(word for text in finance_texts for word in set(text.split(",")))
idf = {word: math.log((N - df[word] + 0.5) / (df[word] + 0.5) + 1) for word in vocabulary}
​
# ======================
# 平均文本长度
# ======================
avg_text_len = sum(len(text.split(",")) for text in finance_texts) / N
​
# ======================
# BM25 稀疏嵌入(关键词权重)
# ======================
def bm25_sparse_embedding(text):
    tf = Counter(text.split(","))
    text_len = len(text.split(","))
    embedding = {}
    for word, freq in tf.items():
        if word in vocabulary:
            idx = vocab_to_idx[word]
            score = idf[word] * (freq * (k1 + 1)) / (freq + k1 * (1 - b + b * text_len / avg_text_len))
            embedding[idx] = score
    return embedding
​
# ======================
# 输出每个财务文本的稀疏向量
# ======================
for i, text in enumerate(finance_texts):
    sparse_emb = bm25_sparse_embedding(text)
    print(f"\n===== 财务文本 {i+1} =====")
    print(f"内容:{text}")
    print(f"BM25稀疏嵌入(关键词权重):{sparse_emb}")

结果:

混合检索

通过bge-m3 生成三种向量,用于RAG混合检索。

实现:

python 复制代码
import os
import numpy as np
from dotenv import load_dotenv
from openai import OpenAI
​
load_dotenv()
​
# 线上BGE-M3 API配置
client = OpenAI(
    base_url=os.getenv("PROXY_AI_BASE_URL"),
    api_key=os.getenv("PROXY_AI_API_KEY")
)
​
def get_bge_m3_three_vectors(text: str):
    """
    调用线上BGE-M3 API,同时返回 dense / sparse / colbert 三种向量
    """
    response = client.embeddings.create(
        input=text,
        model="BAAI/bge-m3",
        encoding_format="float",
        extra_body={
            "return_sparse": True,
            "return_colbert": True
        }
    )
​
    data = response.data[0]
    return {
        "dense": np.array(data.embedding, dtype=np.float32),
        "sparse": data.sparse_embedding,  # dict: {index: weight}
        "colbert": np.array(data.colbert_embedding, dtype=np.float32)
    }
​
def main():
    finance_text = "2024年公司营业收入12.5亿元,同比增长18%,净利润1.8亿元"
    vectors = get_bge_m3_three_vectors(finance_text)
​
    print("==== 财务文本 =====")
    print(finance_text)
    print("=" * 60)
​
    print("\n✅ 稠密向量 Dense")
    print(f"维度: {vectors['dense'].shape}")
    print(f"前8个值: {np.round(vectors['dense'][:8], 4)}")
​
    print("\n✅ 稀疏向量 Sparse")
    print(f"非零元素: {len(vectors['sparse'])}")
    print("前5个权重:", dict(list(vectors['sparse'].items())[:5]))
​
    print("\n✅ Colbert多向量")
    print(f"形状: {vectors['colbert'].shape} (token数 × 1024)")
    print("第一个token向量前5个值:", np.round(vectors['colbert'][0][:5], 4))
​
if __name__ == "__main__":
    main()
  • 稠密向量 (Dense) → 语义检索
  • 稀疏向量 (Sparse) → 关键词匹配
  • 多向量 (Colbert) → 细粒度匹配
向量的表示形式
稠密向量(dense)

一串固定维度的浮点数组(如384/768/1024/1536维),每一维几乎都有值。

代表:BGEOpenAI EmbeddingJina

举例:

csharp 复制代码
[0.123, -0.456, 0.789, ..., 0.222]
  • 优点:表达语义强,适合" 语义相近 " 检索
  • 缺点:对精确匹配(编号、ID、专有名词)可能不如关键词检索稳定
稀疏向量(sparse)

大多元素是0,维度巨大。但是非常稀疏,常用 "(词频/特征)->权重 " 字典表示。

代表:BM25TF-IDFbge-m3 sparse

举例:

csharp 复制代码
[0,0,0,2.3,0,0,1.8,...0,3.1]
  • 优点:精确匹配强,特别适合编号、ID、专有名词
  • 缺点:对语义改写的泛化能力弱
多向量(Multi-vector)

一个文本多套向量表示,检索时做更细粒度匹配

代表:ColBERTBGE-M3

  • 优点:检索精度高、混合检索,跨语言检索 缺点:索引更大、检索更慢、工程复杂
嵌入模型的选择

不同场景下的嵌入模型选择:

场景 embedding 类型 模型 什么时候选它传
纯文本回答 稠密 dense OpenAI:text-embedding-3-small Jina:jina-embeddings-v3 用户提问是"说法各不相同",要靠语义找到相关的段落
关键词/专有名词精确命中 稀疏 sparse / BM25 BM25 精确命中关键词
通常工业级RAG(中文) 混合hybrid(dense+sparse) BAAI/bge-m3 绝大多数情况下这是更稳的选择
多语言(中文) 支持多语言/中文更强的 dense bge-m3 / Jina /OpenAI 不同模型中文检索质量差异很大,建议用你自己的小评测集验证
MTEB排行榜

衡量文本嵌入模型的参考,下面的图会告诉你,在不同参数规模下,哪个模型最快,哪个模型最准,哪个最省资源。

地址:huggingface.co/spaces/mteb...

Y轴 Mean (Task) :代表模型在所有基准任务上的平均得分。分数越高、语义理解能力越强,检索越准。

X轴 Number of Active Parameters:代表模型的激活参数量。数值越大、模型越重、能力越强。

气泡颜色/大小:颜色越深/越大,表示允许输入/生成的token上限越高、就是上下文长度越长。

点到气泡上可以看到具体的模型配置/版本。

所以根据上图:想要低成本快速上线的,选择最左边的模型;想要长上下文的,选择最上面的模型。想要超长文档处理的,选择最右边的模型。

总结

文档分块后不能直接检索,必须通过嵌入模型转为向量,再存入向量数据库,才能实现高效语义 / 关键词匹配。

相关推荐
java1234_小锋2 小时前
Java高频面试题:Redis是单线程还是多线程?
java·redis·面试
哈里谢顿2 小时前
服务器部署应用全流程指南
面试
MonkeyKing2 小时前
iOS Runtime 深度解析
前端·面试
山栀shanzhi2 小时前
深入C++之:一个类有几张虚函数表?
c++·面试
阿钱真强道2 小时前
02 SDXL:环境安装、模型下载与图片生成实战 ARM + Ubuntu 24 + RTX 4090
aigc·huggingface·sdxl·stablediffusion·diffusers·rtx4090
程序员库里2 小时前
第 3 章:Tiptap 与 React 集成
前端·javascript·面试
斯坦SteinY2 小时前
Git Worktree + Claude Code同时开发多个功能
人工智能·chatgpt·prompt·aigc·claude·并行开发
我叫黑大帅2 小时前
如何设计应用层 ACK 来补充 TCP 的不足?
后端·面试·go
怕浪猫3 小时前
第10章 RAG(检索增强生成)系统构建(LangChain实战)
langchain·aigc·ai编程