Embedding技术全景2026:从Word2Vec到多模态向量化的工程指南

4 阅读1分钟

为什么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-large30728192优秀$0.13
text-embedding-3-small15368192良好$0.02
Cohere embed-v31024512良好$0.10
通义千问 text-embedding-v31024/20488192极好¥0.0007/千token
智谱 embedding-320488192极好¥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])
    }

六、生产部署要点

  1. 向量归一化:入库时统一做L2归一化,检索时用内积代替余弦(等价但更快)
  2. 索引类型选择
    • < 100万向量:HNSW(高精度)
    • 1000万向量:IVF_PQ(量化压缩,牺牲少量精度换存储)

  3. 增量更新:大多数向量数据库支持增量插入,无需全量重建索引
  4. 分片策略:超大规模(亿级)考虑按业务域分集合,避免单集合过大

Embedding技术在2026年已经非常成熟,选对模型、做好工程化,是构建高质量语义检索系统的核心前提。


本文代码示例基于openai-python 1.x、sentence-transformers 3.x、qdrant-client 1.9+