前情回顾
经过前三天的努力,我们已经成功掌握了如何将各种类型的文档(纯文本、代码、Markdown)"优雅地"切分成高质量的文本块(Chunks)。可以说,我们为RAG系统准备好了最优质的"原材料"。
但是,新的问题紧接着就来了:
当用户提出一个问题(比如:"RAG的核心组件是什么?")时,我们如何在成千上万,甚至数百万个Chunks中,快速、准确地找到与这个问题最相关的那几个呢?
你可能会想到传统的关键词搜索。但关键词搜索有巨大的局限性。比如用户问"苹果公司的创始人",包含"乔布斯"的文本块可能就匹配不到;用户问"AI的未来",包含"人工智能的前景"的文本块也可能被错过。
我们需要一种能理解语义相似度 ,而不仅仅是字面匹配的方法。而实现这一切的核心技术,就是我们今天要探索的RAG系统的"灵魂"------Embedding。
什么是Embedding?
简单来说,Embedding(嵌入)就是一种将文本(或其他任何东西,如图片、声音)转换成一串数字(即"向量")的技术。
这串数字(向量)并不是随机的,它会尽可能地捕捉原始文本的语义信息。
我们可以打一个比方:
想象一张世界地图。我们可以用经纬度(一个二维向量)来表示任何一个城市的位置。在地图上,"巴黎"、"罗马"、"马德里"这些欧洲城市会聚集在一起,而"北京"、"东京"则在另一个区域,它们之间的距离反映了地理上的关系。
Embedding做的事情类似,但它是在一个更高维度的"语义空间"里,为每个词、每个句子、每个文本块分配一个"语义坐标"(一个高维向量)。在这个空间里:
- "国王"和"女王"的"坐标"会非常接近。
- "我喜欢夏天"和"炎热的天气真棒"的"坐标"也会很接近。
- 而"科技新闻"和"美食菜谱"的"坐标"则会相距甚远。
通过这种方式,"文本相似度"这个模糊的概念,就被转化成了可以精确计算的"向量空间距离"。计算机不擅长理解语言,但极其擅长数学运算。这就是Embedding的魔力所在。
上手实践:将文本转化为向量
理论说完了,我们马上动手实践。我们将使用一个非常流行的开源Embedding模型库 sentence-transformers
和一个在中文领域表现优异的开源模型 m3e-base
。
首先,确保你已经安装了必要的库:
bash
pip install sentence-transformers
然后,我们用代码来感受一下文本是如何变成向量的。
python
from sentence_transformers import SentenceTransformer
# 加载一个预训练好的中文Embedding模型
# 第一次运行时,它会自动从HuggingFace下载模型文件
model = SentenceTransformer('m3e-base')
# 准备几个待转换的句子
sentences = [
"我喜欢吃苹果",
"我喜欢吃香蕉",
"今天天气真好",
"我讨厌上班"
]
# 使用模型将句子编码为向量
embeddings = model.encode(sentences)
# 我们来看看结果
for sentence, embedding in zip(sentences, embeddings):
print("句子:", sentence)
# 打印向量的前5个维度和向量的总维度
print(f"向量 (前5维): {embedding[:5]}")
print(f"向量维度: {len(embedding)}")
print("-" * 20)
输出结果(你运行的结果中,向量的具体数值可能略有不同,但维度是固定的):
markdown
句子: 我喜欢吃苹果
向量 (前5维): [ 0.01391807 -0.01953284 0.01596547 -0.01229419 -0.00160986]
向量维度: 768
--------------------
句子: 我喜欢吃香蕉
向量 (前5维): [ 0.01850123 -0.01908993 0.00392336 -0.01168233 -0.00832363]
向量维度: 768
--------------------
句子: 今天天气真好
向量 (前5维): [ 0.00445524 -0.03813957 0.01150338 -0.0321528 -0.03158003]
向量维度: 768
--------------------
句子: 我讨厌上班
向量 (前5维): [-0.00890695 -0.03367128 0.03842103 0.0210134 -0.01174621]
向量维度: 768
--------------------
看,每一个句子都被成功转换成了一个包含768个数字的向量!这就是它们的"语义坐标"。
魔法时刻:计算语义相似度
有了向量坐标,我们就可以计算它们之间的相似度了。最常用的方法是余弦相似度 (Cosine Similarity),它测量两个向量在方向上的接近程度。结果范围在-1到1之间,1表示完全相同,0表示完全无关,-1表示完全相反。
sentence-transformers
库也为我们提供了便捷的工具。
python
from sentence_transformers import util
# 计算"我喜欢吃苹果"和其它所有句子之间的余弦相似度
query_embedding = embeddings[0]
other_embeddings = embeddings[1:]
# util.cos_sim会返回一个张量(tensor),包含查询向量和其它所有向量的相似度
cosine_scores = util.cos_sim(query_embedding, other_embeddings)
print(f"查询句子: '{sentences[0]}'")
for i in range(len(other_embeddings)):
print(f"与 '{sentences[i+1]}' 的相似度: {cosine_scores[0][i]:.4f}")
输出结果:
arduino
查询句子: '我喜欢吃苹果'
与 '我喜欢吃香蕉' 的相似度: 0.9038
与 '今天天气真好' 的相似度: 0.5847
与 '我讨厌上班' 的相似度: 0.6120
结果一目了然!
- "我喜欢吃苹果"和"我喜欢吃香蕉"的语义极其相似(都关于喜欢吃某种水果),所以相似度高达 0.90。
- 而与"天气"和"上班"的句子相比,虽然都包含"我",但主题完全不同,所以相似度就低了很多。
这就是Embedding和语义搜索的威力!
总结与预告
今日小结:
- Embedding 是将文本转换为数字向量(语义坐标)的过程,是RAG系统实现语义检索的基石。
- 通过计算向量间的余弦相似度,我们可以准确地判断文本间的语义关联程度。
- 我们可以使用
sentence-transformers
这样的开源库和m3e-base
这样的模型,轻松地实现文本的Embedding。
现在,我们已经能将所有的文本块(Chunks)都转换成向量了。但一个新的、非常工程化的问题又摆在了面前:
当我们的知识库有数百万个Chunks,也就意味着有数百万个向量。当用户提出一个问题时,难道我们要把用户问题的向量,和这数百万个向量逐一计算余弦相似度,然后排序找分最高的吗?
这太慢了!在实际应用中是绝对无法接受的。我们需要一种能从海量向量中进行高效相似度搜索的专门技术。
明天预告:RAG 每日一技(五):大海捞针第一步,亲手构建你的向量索引!
明天,我们将正式踏入向量数据库 的世界,并亲手用一个流行的库(如FAISS
)来构建我们的第一个向量索引,体验什么叫"瞬间"从百万向量中找到你的目标!