Embedding 降级方案:Hashing Embedding
用字符 n-gram + SHA256 hash 生成确定性向量的降级方案:零模型下载、零 API 调用,适用于开发阶段和离线环境。讨论语义质量和适用边界。
#type / concept
#status / growing
#tech / ai
[!info] related notes
- 前置: Embedding 模型
- 应用: RAG 知识库设计
Embedding 降级方案:Hashing Embedding
核心问题
开发 RAG 系统时,embedding 的选择是个前置问题:
| 方案 | 优点 | 缺点 |
|---|---|---|
| OpenAI API | 质量最好 | 需要 API Key、网络、有成本 |
| sentence-transformers | 质量好、免费 | 需要下载模型(几百 MB)、首次慢 |
| Hashing | 零依赖、零成本、确定性 | 语义质量差 |
Hashing embedding 的定位是开发阶段的降级方案:让你在没有 API Key、没有 GPU、没有网络的情况下也能跑通 RAG 管线。
算法原理
def hashing_embedding(text: str, dimension: int = 1536) -> list[float]:
# 1. 归一化
text = " ".join(text.lower().split())
# 2. 提取特征:完整词 + 字符 n-gram
features = []
for token in text.split():
features.append((token, 1.0)) # 完整词
for n in (1, 2, 3): # unigram, bigram, trigram
for i in range(len(text) - n + 1):
gram = text[i:i+n]
features.append((gram, 1.0 / n)) # n 越大权重越低
# 3. SHA256 hash 映射到向量桶
vector = [0.0] * dimension
for feature, weight in features:
digest = hashlib.sha256(feature.encode()).digest()
bucket = int.from_bytes(digest[:8], "big") % dimension
sign = 1.0 if digest[8] % 2 == 0 else -1.0
vector[bucket] += sign * weight
# 4. L2 归一化
norm = math.sqrt(sum(v*v for v in vector))
return [v/norm for v in vector] if norm > 0 else vector
核心思路:每个字符 n-gram 通过 SHA256 hash 映射到向量的一个位置,正负号随机。相同文本永远映射到相同位置(确定性)。
语义质量对比
| 能力 | Hashing | sentence-transformers | OpenAI |
|---|---|---|---|
| 相同文本匹配 | ✅ 完全一致 | ✅ | ✅ |
| 相似文本区分 | ⚠️ 有一定区分度 | ✅ 好 | ✅ 很好 |
| 同义词识别 | ❌ | ✅ | ✅ |
| 跨语言 | ❌ | ✅(multilingual) | ✅ |
| 语序理解 | ❌ | 部分 | ✅ |
关键限制:“肩膀疼” 和 “肩部疼痛” 在 hashing 下可能完全不同,但在真实模型下很接近。
什么时候用
| 场景 | 推荐方案 |
|---|---|
| 开发调试、跑通管线 | Hashing |
| 知识库术语集中、查询和文档用同一套词 | Hashing 勉强够用 |
| 需要同义词、跨语言 | sentence-transformers |
| 生产环境 | 至少 sentence-transformers,最好 OpenAI |
切换策略
EMBEDDING_PROVIDER=hashing # 开发:零下载
EMBEDDING_PROVIDER=local_transformer # 本地模型
EMBEDDING_PROVIDER=openai # API 调用
代码中统一接口:
async def generate(self, text: str) -> list[float]:
if self.provider == "hashing":
return self._hash_embedding(text)
if self.provider == "local_transformer":
return self._local_model.encode([text])[0].tolist()
return await self._api_generate(text)
常见错误
生产环境还在用 hashing
# ❌ 部署时忘了切到真实模型
EMBEDDING_PROVIDER=hashing # 检索质量很差
hashing 和真实模型的 embedding 混用
# ❌ 用 hashing 入库,用 OpenAI 检索(或反过来)
# 向量空间完全不同,相似度没有意义
切换模型后必须重新生成所有 embedding。