TF-IDF + 余弦相似度:RAG 的祖先
对应 6.1 · 需要先装 scikit-learn
import numpy as np
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
# 示例文档
documents = [
"The sky is blue and beautiful.",
"Love this blue and beautiful sky!",
"The quick brown fox jumps over the lazy dog.",
"A king's breakfast has sausages, ham, bacon, eggs, toast, and beans",
"I love green eggs, ham, sausages and bacon!",
"The brown fox is quick and the blue dog is lazy!",
"The sky is very blue and the sky is very beautiful today",
"The dog is lazy but the brown fox is quick!"
]
# 第一步:用 TF-IDF 把文档变成向量
vectorizer = TfidfVectorizer()
X = vectorizer.fit_transform(documents)
# 第二步:把向量存进一个极简的「向量数据库」(这里就是个列表)
vector_database = X.toarray()
# 第三步:余弦相似度检索
def cosine_similarity_search(query, database, vectorizer, top_n=5):
query_vec = vectorizer.transform([query]).toarray()
similarities = cosine_similarity(query_vec, database)[0]
top_indices = np.argsort(-similarities)[:top_n] # 取前 n 个下标
return [(idx, similarities[idx]) for idx in top_indices]
# 交互式检索循环
while True:
query = input("输入检索词(输入 exit 退出):")
if query.lower() == 'exit':
break
top_n = int(input("想看前几条结果?"))
search_results = cosine_similarity_search(query, vector_database, vectorizer, top_n)
print("最匹配的文档:")
for idx, score in search_results:
print(f"- {documents[idx]} (相似度: {score:.4f})")
print("\n")
注意这里的文档我没翻译,因为 TF-IDF 是按词统计的—— 文档是英文,你就得用英文词去查才有命中。这是个小预告: 检索的语言必须和语料的语言对上,下一节这件事会变成大问题。
TF-IDF 只会数词:"blue sky" 和 "azure heavens"
在它眼里毫无关系,因为一个词都没重合。
而真正的 embedding 模型知道这俩意思差不多。
那为什么还要学它?因为「向量化 → 算夹角 → 取最近的几个」这套骨架, 和下一节的 Chroma 是一模一样的。区别只在于向量是怎么来的。 先在 44 行里把骨架看清楚,再去用黑盒子。
顺带一提:while True: input() 意味着这个脚本会一直等你敲字。
本章好几个文件都是这样,自动化里跑会挂住。