为什么Embedding比以前更重要?
2026年,Embedding已经成为AI应用的基础设施。无论是RAG系统、语义搜索、推荐系统,还是内容去重、相似度计算,背后都离不开高质量的向量化技术。
但很多工程师对Embedding的理解还停留在"把文本变成向量"这个粗浅层面。本文将深入讲解Embedding技术的演进、不同场景下的选型策略,以及生产环境的工程实践。
一、Embedding技术演进简史
1.1 第一代:词袋模型(Bag of Words)
最早的文本表示方法,将文本转为词频向量:
from sklearn.feature_extraction.text import TfidfVectorizer
corpus = [
"大模型改变了AI应用开发",
"RAG系统需要高质量的向量化",
"Embedding是语义搜索的基础"
]
# TF-IDF向量化
vectorizer = TfidfVectorizer()
X = vectorizer.fit_transform(corpus)
print(X.shape) # (3, N_词汇数)
缺点:无法捕捉语义,"大模型"和"LLM"是完全不同的向量。
1.2 第二代:Word2Vec/GloVe(词级静态向量)
2013年Word2Vec的出现革命性地引入了语义向量空间:
- "国王" - "男人" + "女人" ≈ "女王"
- 相似词在向量空间中距离相近
缺点:一词一个向量,无法处理多义词("苹果"在不同语境含义不同)。
1.3 第三代:BERT系列(上下文动态向量)
BERT引入了上下文感知的词向量,同一个词在不同句子中有不同的向量表示。
但BERT的句子级向量(通常取[CLS]标记)并不擅长语义相似度任务。
1.4 第四代:Sentence Transformers(语义向量)
专为句子级语义相似度优化的模型,使用对比学习训练:
from sentence_transformers import SentenceTransformer
model = SentenceTransformer('paraphrase-multilingual-mpnet-base-v2')
sentences = [
"如何提高RAG系统的准确率?",
"RAG系统精度优化方法有哪些?", # 语义相似
"今天天气怎么样?" # 语义无关
]
embeddings = model.encode(sentences)
similarities = model.similarity(embeddings, embeddings)
print(similarities)
# 前两句相似度接近1.0,与第三句相似度接近0
1.5 第五代:大规模通用Embedding模型(当前主流)
OpenAI text-embedding-3系列、Cohere Embed v3、阿里云通义Embedding等,用数十亿文本对训练,泛化能力极强。
二、2026年主流Embedding模型横评
2.1 商业API
| 模型 | 维度 | 最大Token | 中文支持 | 价格(/1M tokens) |
|---|---|---|---|---|
| text-embedding-3-large | 3072 | 8192 | 优秀 | $0.13 |
| text-embedding-3-small | 1536 | 8192 | 良好 | $0.02 |
| Cohere embed-v3 | 1024 | 512 | 良好 | $0.10 |
| 通义千问 text-embedding-v3 | 1024/2048 | 8192 | 极好 | ¥0.0007/千token |
| 智谱 embedding-3 | 2048 | 8192 | 极好 | ¥0.0005/千token |
2.2 开源模型(本地部署)
# 使用sentence-transformers加载开源模型
# 中文最佳选择
from sentence_transformers import SentenceTransformer
# BAAI/bge-m3:多语言,支持稠密+稀疏+多粒度检索
model = SentenceTransformer("BAAI/bge-m3")
# 专门优化中文的模型
model_zh = SentenceTransformer("BAAI/bge-large-zh-v1.5")
# 代码语义搜索专用
model_code = SentenceTransformer("Salesforce/codesearch-distilroberta-base")
# 多模态(文本+图像)
from transformers import CLIPModel, CLIPProcessor
clip_model = CLIPModel.from_pretrained("openai/clip-vit-large-patch14")
2.3 如何选择?
需要高精度 + 不在意成本 → text-embedding-3-large
需要低成本 + 可接受略低精度 → text-embedding-3-small
中文业务 + 成本敏感 → 通义/智谱 Embedding
私有化部署 + 中文 → BAAI/bge-large-zh-v1.5
代码搜索 → code-search-babbage
多模态(图文混合) → CLIP或通用多模态模型
三、核心工程实践
3.1 批量Embedding(生产必备)
import asyncio
from openai import AsyncOpenAI
from typing import AsyncGenerator
client = AsyncOpenAI()
async def embed_batch(
texts: list[str],
batch_size: int = 100,
model: str = "text-embedding-3-small"
) -> list[list[float]]:
"""批量向量化,自动处理并发和重试"""
all_embeddings = []
for i in range(0, len(texts), batch_size):
batch = texts[i:i + batch_size]
# 清理文本:去除多余空白、控制长度
cleaned_batch = [
text.strip()[:8000] # 留一些buffer
for text in batch
if text.strip()
]
try:
response = await client.embeddings.create(
input=cleaned_batch,
model=model,
encoding_format="float"
)
batch_embeddings = [item.embedding for item in sorted(
response.data,
key=lambda x: x.index
)]
all_embeddings.extend(batch_embeddings)
except Exception as e:
print(f"批次 {i//batch_size} 失败: {e}")
# 单个文本重试
for text in cleaned_batch:
try:
resp = await client.embeddings.create(
input=[text], model=model
)
all_embeddings.append(resp.data[0].embedding)
except:
# 失败用零向量占位
all_embeddings.append([0.0] * 1536)
return all_embeddings
3.2 维度压缩(降本提速)
text-embedding-3系列支持Matryoshka表示学习,可以直接截取前N维:
# 原始3072维 → 压缩到512维,质量损失约5%,存储和计算成本降低6倍
response = await client.embeddings.create(
input=texts,
model="text-embedding-3-large",
dimensions=512 # 指定输出维度
)
或者使用PCA离线压缩:
import numpy as np
from sklearn.decomposition import PCA
def train_dimension_reducer(sample_embeddings: np.ndarray, target_dim: int = 256):
"""训练维度压缩器(用样本数据拟合PCA)"""
pca = PCA(n_components=target_dim, random_state=42)
pca.fit(sample_embeddings)
explained_variance = pca.explained_variance_ratio_.sum()
print(f"保留方差比例: {explained_variance:.2%}")
return pca
def compress_embeddings(embeddings: np.ndarray, pca: PCA) -> np.ndarray:
"""使用训练好的PCA压缩向量"""
return pca.transform(embeddings)
3.3 查询优化:HyDE(假设文档扩展)
对于用户查询很短而知识库文档很长的情况,直接用查询向量检索效果较差。HyDE方法先让LLM生成一个假设答案,用答案的向量去检索:
async def hyde_retrieve(
query: str,
retriever,
llm,
top_k: int = 5
) -> list:
"""HyDE:用假设答案向量检索,提升召回率"""
# 生成假设答案
hypothetical_answer = await llm.generate(
f"假设你要回答以下问题,写一段详细的技术说明(200字):\n{query}"
)
# 用假设答案做检索(而不是原始查询)
results = await retriever.retrieve(
hypothetical_answer, # 关键:用生成的答案而非原始查询
top_k=top_k
)
return results
# 与普通检索对比:
# 原始查询:"RAG的chunk size怎么设置?"(14字)
# 假设答案:"RAG系统中chunk size的设置需要考虑多个因素。
# 过小的chunk可能导致上下文信息不完整,
# 过大则会引入噪声并降低检索精度。
# 通常建议将chunk size设置在256-512 tokens之间..."(150字)
3.4 混合检索(稀疏+稠密)
纯向量检索在精确词汇匹配上弱于BM25,混合检索两者取长补短:
from qdrant_client import QdrantClient
from qdrant_client.models import SparseVector, NamedSparseVector
def hybrid_search(
query: str,
dense_embedding: list[float],
sparse_embedding: dict[int, float], # BM25/SPLADE生成的稀疏向量
collection: str,
top_k: int = 10,
dense_weight: float = 0.7,
sparse_weight: float = 0.3
) -> list:
"""混合检索:向量检索 + 关键词检索"""
from qdrant_client.models import Prefetch, FusionQuery, Fusion
results = client.query_points(
collection_name=collection,
prefetch=[
Prefetch(
query=dense_embedding,
using="dense",
limit=20
),
Prefetch(
query=NamedSparseVector(
name="sparse",
vector=SparseVector(
indices=list(sparse_embedding.keys()),
values=list(sparse_embedding.values())
)
),
using="sparse",
limit=20
)
],
query=FusionQuery(fusion=Fusion.RRF), # 倒数排名融合
limit=top_k
)
return results.points
四、多模态Embedding
4.1 图文混合知识库
from PIL import Image
import torch
from transformers import CLIPModel, CLIPProcessor
class MultimodalEmbedder:
def __init__(self):
self.model = CLIPModel.from_pretrained("openai/clip-vit-large-patch14")
self.processor = CLIPProcessor.from_pretrained("openai/clip-vit-large-patch14")
def embed_text(self, texts: list[str]) -> np.ndarray:
inputs = self.processor(text=texts, return_tensors="pt", padding=True)
with torch.no_grad():
features = self.model.get_text_features(**inputs)
return features.numpy()
def embed_image(self, images: list[Image.Image]) -> np.ndarray:
inputs = self.processor(images=images, return_tensors="pt")
with torch.no_grad():
features = self.model.get_image_features(**inputs)
return features.numpy()
def cross_modal_similarity(
self,
text_query: str,
images: list[Image.Image]
) -> list[float]:
"""用文字查询找最相关的图片"""
text_emb = self.embed_text([text_query])
image_embs = self.embed_image(images)
# 归一化
text_emb = text_emb / np.linalg.norm(text_emb, axis=1, keepdims=True)
image_embs = image_embs / np.linalg.norm(image_embs, axis=1, keepdims=True)
similarities = (text_emb @ image_embs.T)[0]
return similarities.tolist()
五、Embedding质量评估
from sklearn.metrics.pairwise import cosine_similarity
import pandas as pd
def evaluate_embedding_quality(
model_name: str,
test_pairs: list[tuple[str, str, float]] # (文本A, 文本B, 人工相似度标注)
) -> dict:
"""评估Embedding模型在特定业务数据上的质量"""
texts_a = [p[0] for p in test_pairs]
texts_b = [p[1] for p in test_pairs]
human_scores = [p[2] for p in test_pairs]
# 获取向量
embs_a = get_embeddings(texts_a, model=model_name)
embs_b = get_embeddings(texts_b, model=model_name)
# 计算余弦相似度
model_scores = [
cosine_similarity([a], [b])[0][0]
for a, b in zip(embs_a, embs_b)
]
# 计算与人工标注的相关性(斯皮尔曼相关)
from scipy.stats import spearmanr
correlation, p_value = spearmanr(human_scores, model_scores)
return {
"model": model_name,
"spearman_correlation": correlation,
"p_value": p_value,
"avg_cosine_for_similar": np.mean([s for s, h in zip(model_scores, human_scores) if h > 0.7]),
"avg_cosine_for_dissimilar": np.mean([s for s, h in zip(model_scores, human_scores) if h < 0.3])
}
六、生产部署要点
- 向量归一化:入库时统一做L2归一化,检索时用内积代替余弦(等价但更快)
- 索引类型选择:
- < 100万向量:HNSW(高精度)
-
1000万向量:IVF_PQ(量化压缩,牺牲少量精度换存储)
- 增量更新:大多数向量数据库支持增量插入,无需全量重建索引
- 分片策略:超大规模(亿级)考虑按业务域分集合,避免单集合过大
Embedding技术在2026年已经非常成熟,选对模型、做好工程化,是构建高质量语义检索系统的核心前提。
本文代码示例基于openai-python 1.x、sentence-transformers 3.x、qdrant-client 1.9+