问题与目标
关键词检索依赖字面重合,“设备离线”和“终端失去连接”可能无法互相命中。Embedding 把文本映射为固定维度向量,使语义接近的文本在向量空间中更接近。
本篇先用可重复的本地字符哈希向量跑通批量嵌入、归一化、缓存和 Top-k 搜索。这个实现只用于理解数据流,不代表生产级语义质量;接入真实 Embedding 模型后,索引与查询必须使用同一模型和预处理版本。
核心概念
- 维度:每个向量的元素数量,同一索引必须一致。
- 余弦相似度:比较方向,常用于归一化后的文本向量。
- 点积:向量归一化后与余弦相似度等价;未归一化时还受模长影响。
- 欧氏距离:比较空间距离,分数方向与相似度相反。
- 批处理:减少网络往返,但要受单次 Token、条数和限流约束。
不同模型即使维度相同,坐标含义也不同,不能把模型 A 建的索引交给模型 B 查询。
可运行实现
python
import hashlib
import math
from functools import lru_cache
def tokens(text: str) -> list[str]:
compact = "".join(text.lower().split())
return [compact[i:i + 2] for i in range(max(1, len(compact) - 1))]
@lru_cache(maxsize=2048)
def embed(text: str, dimensions: int = 128) -> tuple[float, ...]:
vector = [0.0] * dimensions
for token in tokens(text):
digest = hashlib.sha256(token.encode("utf-8")).digest()
index = int.from_bytes(digest[:4], "big") % dimensions
sign = 1.0 if digest[4] % 2 == 0 else -1.0
vector[index] += sign
norm = math.sqrt(sum(value * value for value in vector)) or 1.0
return tuple(value / norm for value in vector)
def cosine(left: tuple[float, ...], right: tuple[float, ...]) -> float:
if len(left) != len(right):
raise ValueError("embedding_dimension_mismatch")
return sum(a * b for a, b in zip(left, right))
documents = [
"AX-3 终端失去网络连接",
"AX-8 电池电量低于20%",
"AX-5 已切换备用线路并恢复在线",
]
matrix = [embed(text) for text in documents] # 批量接口应一次提交多条
query = embed("设备断网后怎样恢复")
ranking = sorted(
[(cosine(query, vector), text) for text, vector in zip(documents, matrix)],
reverse=True,
)
print(ranking)
缓存键至少应包含 model_id + model_revision + preprocessing_version + text_hash。示例用 lru_cache 只演示同一进程去重,生产环境需要持久缓存、容量限制和失效策略。
常见问题与排查
相似度高就代表答案正确
Embedding 只表示模型空间中的接近程度。片段可能主题相关却不包含答案,因此仍需人工标注的查询—相关 Chunk 集合评估召回。
查询和文档使用不同前缀或模型
部分模型要求查询与文档采用不同指令前缀。按模型说明统一处理,并把配置写入索引版本,禁止静默混用。
忘记归一化却使用点积
先确认向量库的距离定义和是否自动归一化。不要把余弦距离、余弦相似度和点积分数用同一阈值解释。
一次提交全部文档
按条数和 Token 分批,记录失败批次并有限重试。使用稳定文档 ID,重试时避免重复写入。
小结
Embedding 让检索从字面匹配扩展到向量相似,但它不自动带来正确答案。模型、预处理、维度、归一化和缓存共同构成向量契约;任何一项改变,都要把索引视为新版本重新验证。
许可协议:CC BY-NC 4.0
更新于 1 小时前
觉得文章有帮助?点个赞吧!
0 条评论


