Files
2026-07-13 21:36:42 +08:00

562 lines
15 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 嵌入模型
---
## 模型对比矩阵
| 模型 | 维度 | 最大 Token | 优势 | 提供商 |
|-------|------------|------------|-----------|----------|
| **text-embedding-3-large** | 3072(或 256-3072 | 8191 | 质量最佳,维度灵活 | OpenAI |
| **text-embedding-3-small** | 1536(或 256-1536 | 8191 | 性价比高,质量良好 | OpenAI |
| **embed-english-v3.0** | 1024 | 512 | 压缩效果优秀,速度快 | Cohere |
| **embed-multilingual-v3.0** | 1024 | 512 | 支持 100+ 种语言 | Cohere |
| **voyage-large-2** | 1536 | 16000 | 长上下文,代码感知 | Voyage AI |
| **voyage-code-2** | 1536 | 16000 | 代码检索专用 | Voyage AI |
| **BGE-large-en-v1.5** | 1024 | 512 | 开源,质量高 | BAAI |
| **BGE-M3** | 1024 | 8192 | 多语言、多粒度 | BAAI |
| **E5-large-v2** | 1024 | 512 | 基准测试表现强劲 | Microsoft |
| **GTE-large** | 1024 | 512 | 通用性良好 | Alibaba |
| **all-MiniLM-L6-v2** | 384 | 256 | 快速、轻量 | Sentence Transformers |
| **nomic-embed-text-v1.5** | 768 | 8192 | 长上下文,开源权重 | Nomic AI |
---
## 各模型适用场景
### OpenAI text-embedding-3-large
```
最佳用途:
- 对精度要求最高的生产级 RAG
- 有质量 SLA 的企业级应用
- 维度要求灵活(可降低维度以节省成本)
- 英文及主要语言
避免场景:
- 对成本敏感的高量级应用
- 气隙或离线部署环境
- 无微调预算的专有领域
```
### OpenAI text-embedding-3-small
```
最佳用途:
- 性价比优先的生产部署
- 良好的质量价格比
- 通用检索任务
- 利用 API 简洁性快速原型开发
避免场景:
- 需要最高精度
- 专有技术领域
- 必须使用开源方案
```
### Cohere embed-v3
```
最佳用途:
- 多语言应用(100+ 种语言)
- 面向搜索优化的检索(search_document/search_query 类型)
- 内置压缩(int8/binary 量化)
- 有成本约束的生产环境
避免场景:
- 超长文档(512 token 限制)
- 代码密集的检索任务
```
### Voyage AI
```
最佳用途:
- 代码检索与技术文档
- 长上下文文档(16K tokens
- 领域特定微调选项
- 法律/金融专用模型
避免场景:
- 预算受限的项目
- 简单的通用检索
```
### BGE / E5(开源)
```
最佳用途:
- 自托管部署
- 气隙环境
- 零成本(无 API 费用)
- 自定义领域微调
避免场景:
- 团队没有 GPU 基础设施
- 需要零维护
- 需要最高的开箱即用质量
```
---
## OpenAI 嵌入
```python
from openai import OpenAI
client = OpenAI(api_key="your-api-key")
def get_embedding(
text: str,
model: str = "text-embedding-3-small",
dimensions: int | None = None
) -> list[float]:
"""获取嵌入向量,可选维度缩减。"""
params = {"input": text, "model": model}
if dimensions:
params["dimensions"] = dimensions
response = client.embeddings.create(**params)
return response.data[0].embedding
# 单个嵌入
embedding = get_embedding("How do I install the software?")
# 批量嵌入(更高效)
def get_embeddings_batch(
texts: list[str],
model: str = "text-embedding-3-small",
dimensions: int | None = None
) -> list[list[float]]:
"""批量嵌入多个文本。"""
params = {"input": texts, "model": model}
if dimensions:
params["dimensions"] = dimensions
response = client.embeddings.create(**params)
# 按索引排序以保持顺序
return [item.embedding for item in sorted(response.data, key=lambda x: x.index)]
embeddings = get_embeddings_batch(["text1", "text2", "text3"])
# 维度缩减(节省成本/存储空间)
# text-embedding-3-large: 3072 -> 1024(节省 66% 存储空间)
reduced_embedding = get_embedding(
"Installation guide...",
model="text-embedding-3-large",
dimensions=1024 # 从 3072 缩减
)
```
### 维度权衡
| 原始维度 | 缩减后 | 质量损失 | 存储节省 |
|----------|---------|--------------|-----------------|
| 3072 | 1536 | ~1-2% | 50% |
| 3072 | 1024 | ~2-4% | 67% |
| 3072 | 512 | ~5-8% | 83% |
| 3072 | 256 | ~10-15% | 92% |
---
## Cohere 嵌入
```python
import cohere
co = cohere.Client(api_key="your-api-key")
# 文档嵌入(用于索引)
doc_embeddings = co.embed(
texts=["Installation guide content...", "Configuration steps..."],
model="embed-english-v3.0",
input_type="search_document", # 用于被索引的文档
truncate="END"
).embeddings
# 查询嵌入(用于搜索)
query_embedding = co.embed(
texts=["how to install"],
model="embed-english-v3.0",
input_type="search_query", # 用于搜索查询
).embeddings[0]
# 多语言
multilingual_embedding = co.embed(
texts=["Comment installer le logiciel?"], # 法语
model="embed-multilingual-v3.0",
input_type="search_query"
).embeddings[0]
# 压缩嵌入(int8
compressed = co.embed(
texts=["Document content..."],
model="embed-english-v3.0",
input_type="search_document",
embedding_types=["int8"] # 比 float32 小 4 倍
).embeddings
```
### Cohere 输入类型
| 类型 | 用途 |
|------|----------|
| `search_document` | 待索引到向量数据库中的文档 |
| `search_query` | 用户搜索查询 |
| `classification` | 文本分类任务 |
| `clustering` | 文档聚类 |
---
## Voyage AI 嵌入
```python
import voyageai
vo = voyageai.Client(api_key="your-api-key")
# 通用嵌入
result = vo.embed(
texts=["Installation guide for the software..."],
model="voyage-large-2",
input_type="document"
)
embeddings = result.embeddings
# 代码嵌入(专用)
code_result = vo.embed(
texts=[
"def install_package(name):\n subprocess.run(['pip', 'install', name])",
"How do I install packages in Python?"
],
model="voyage-code-2",
input_type="document" # 搜索时使用 "query"
)
# 长上下文(最高 16K tokens
long_doc_embedding = vo.embed(
texts=[very_long_document], # 最高 16K tokens
model="voyage-large-2",
input_type="document"
).embeddings[0]
```
---
## 开源模型(Sentence Transformers
```python
from sentence_transformers import SentenceTransformer
# 加载模型(首次使用时会下载)
model = SentenceTransformer("BAAI/bge-large-en-v1.5")
# 单个嵌入
embedding = model.encode("How do I install the software?")
# 批量编码(GPU 加速)
embeddings = model.encode(
["doc1", "doc2", "doc3"],
batch_size=32,
show_progress_bar=True,
convert_to_numpy=True,
normalize_embeddings=True # 用于余弦相似度
)
# BGE 要求对查询使用指令前缀
query_embedding = model.encode(
"Represent this sentence for searching relevant passages: How do I install?"
)
# GPU 加速
model = SentenceTransformer("BAAI/bge-large-en-v1.5", device="cuda")
# 多 GPU 编码
pool = model.start_multi_process_pool()
embeddings = model.encode_multi_process(
sentences=large_corpus,
pool=pool,
batch_size=64
)
model.stop_multi_process_pool(pool)
```
### BGE-M3(多语言、多粒度)
```python
from FlagEmbedding import BGEM3FlagModel
model = BGEM3FlagModel("BAAI/bge-m3", use_fp16=True)
# 一次调用获取稠密、稀疏和 colbert 嵌入
output = model.encode(
["Installation guide in English", "Guide d'installation en francais"],
return_dense=True,
return_sparse=True,
return_colbert_vecs=True
)
dense_embeddings = output["dense_vecs"]
sparse_embeddings = output["lexical_weights"]
colbert_embeddings = output["colbert_vecs"]
```
---
## 嵌入模型微调
### 何时微调
| 场景 | 建议 |
|----------|----------------|
| 领域专属术语(法律、医学) | 在领域语料上微调 |
| 检索精度较低(<80%) | 使用难负样本进行微调 |
| 查询分布外 | 使用查询-文档对进行微调 |
| 成本优化 | 微调较小模型以匹配较大模型 |
### 使用 Sentence Transformers 微调
```python
from sentence_transformers import SentenceTransformer, InputExample, losses
from torch.utils.data import DataLoader
# 准备训练数据
train_examples = [
InputExample(
texts=["query: how to install", "doc: Installation guide content..."],
label=1.0 # 相关性分数
),
InputExample(
texts=["query: how to install", "doc: Unrelated content..."],
label=0.0 # 负样本
),
]
# 加载基础模型
model = SentenceTransformer("BAAI/bge-base-en-v1.5")
# 创建数据加载器
train_dataloader = DataLoader(train_examples, shuffle=True, batch_size=16)
# 用于相似度学习的对比损失
train_loss = losses.CosineSimilarityLoss(model)
# 微调
model.fit(
train_objectives=[(train_dataloader, train_loss)],
epochs=3,
warmup_steps=100,
output_path="./fine-tuned-model"
)
# 或使用 Multiple Negatives Ranking Loss(检索效果更佳)
train_examples_mnrl = [
InputExample(texts=["query", "positive_doc", "negative_doc1", "negative_doc2"])
]
train_loss = losses.MultipleNegativesRankingLoss(model)
```
### 难负样本挖掘
```python
from sentence_transformers import SentenceTransformer
from sentence_transformers.util import semantic_search
import torch
def mine_hard_negatives(
queries: list[str],
positives: list[str],
corpus: list[str],
model: SentenceTransformer,
top_k: int = 10
) -> list[InputExample]:
"""从语料库中为每个查询-正样本对挖掘难负样本。"""
query_embeddings = model.encode(queries, convert_to_tensor=True)
corpus_embeddings = model.encode(corpus, convert_to_tensor=True)
positive_set = set(positives)
examples = []
for i, query in enumerate(queries):
# 查找与正样本相似但不相同的文档
hits = semantic_search(
query_embeddings[i:i+1],
corpus_embeddings,
top_k=top_k + 1
)[0]
hard_negatives = [
corpus[hit["corpus_id"]]
for hit in hits
if corpus[hit["corpus_id"]] not in positive_set
][:3] # 取前 3 个难负样本
examples.append(InputExample(
texts=[query, positives[i]] + hard_negatives
))
return examples
```
---
## 嵌入流水线最佳实践
### 文本预处理
```python
import re
from typing import Callable
def clean_for_embedding(text: str) -> str:
"""在嵌入前清理文本。"""
# 移除多余空白
text = re.sub(r'\s+', ' ', text)
# 移除无意义的特殊字符
text = re.sub(r'[^\w\s\.\,\!\?\-\:\;\(\)]', '', text)
# 截断到合理长度(取决于模型)
text = text[:8000] # 为分词扩展预留空间
return text.strip()
def preprocess_for_embedding(
text: str,
prefix: str = "",
max_length: int = 8000
) -> str:
"""预处理并可选添加前缀(适用于指令微调模型)。"""
cleaned = clean_for_embedding(text)
prefixed = f"{prefix}{cleaned}" if prefix else cleaned
return prefixed[:max_length]
# 针对查询的 BGE 风格前缀
query_text = preprocess_for_embedding(
"how to install",
prefix="Represent this sentence for searching relevant passages: "
)
```
### 嵌入缓存
```python
import hashlib
import json
from functools import lru_cache
from pathlib import Path
class EmbeddingCache:
"""基于磁盘的嵌入缓存。"""
def __init__(self, cache_dir: str = ".embedding_cache"):
self.cache_dir = Path(cache_dir)
self.cache_dir.mkdir(exist_ok=True)
def _hash_key(self, text: str, model: str) -> str:
content = f"{model}:{text}"
return hashlib.sha256(content.encode()).hexdigest()
def get(self, text: str, model: str) -> list[float] | None:
key = self._hash_key(text, model)
cache_file = self.cache_dir / f"{key}.json"
if cache_file.exists():
return json.loads(cache_file.read_text())
return None
def set(self, text: str, model: str, embedding: list[float]) -> None:
key = self._hash_key(text, model)
cache_file = self.cache_dir / f"{key}.json"
cache_file.write_text(json.dumps(embedding))
# 使用示例
cache = EmbeddingCache()
def get_embedding_cached(text: str, model: str = "text-embedding-3-small") -> list[float]:
cached = cache.get(text, model)
if cached:
return cached
embedding = get_embedding(text, model) # 调用 API
cache.set(text, model, embedding)
return embedding
```
### 批处理策略
```python
from typing import Iterator
import asyncio
from openai import AsyncOpenAI
def batch_texts(texts: list[str], batch_size: int = 100) -> Iterator[list[str]]:
"""生成文本批次。"""
for i in range(0, len(texts), batch_size):
yield texts[i:i + batch_size]
async def get_embeddings_async(
texts: list[str],
model: str = "text-embedding-3-small",
batch_size: int = 100,
max_concurrent: int = 5
) -> list[list[float]]:
"""带并发控制的异步批量嵌入。"""
client = AsyncOpenAI()
semaphore = asyncio.Semaphore(max_concurrent)
async def embed_batch(batch: list[str]) -> list[list[float]]:
async with semaphore:
response = await client.embeddings.create(
input=batch,
model=model
)
return [item.embedding for item in sorted(response.data, key=lambda x: x.index)]
batches = list(batch_texts(texts, batch_size))
results = await asyncio.gather(*[embed_batch(b) for b in batches])
# 展平结果
return [emb for batch_result in results for emb in batch_result]
```
---
## 模型选择流程图
```
开始
├─ 需要离线/自托管?
│ └─ 是 → BGE-large 或 E5-large(开源)
├─ 需要多语言?
│ └─ 是 → Cohere embed-multilingual-v3 或 BGE-M3
├─ 代码/技术文档?
│ └─ 是 → Voyage-code-2
├─ 长文档(超过 8K tokens)?
│ └─ 是 → Voyage-large-2 或 nomic-embed-text
├─ 成本是首要考虑?
│ └─ 是 → text-embedding-3-small(缩减维度)
├─ 需要最高质量?
│ └─ 是 → text-embedding-3-large
└─ 默认 → text-embedding-3-small(最佳平衡)
```
---
## 快速参考
| 任务 | 推荐模型 |
|------|----------------|
| 生产级 RAG(英文) | text-embedding-3-small/large |
| 多语言 | Cohere embed-multilingual-v3 |
| 代码检索 | Voyage-code-2 |
| 自托管 | BGE-large-en-v1.5 |
| 长文档 | Voyage-large-2、nomic-embed-text |
| 原型开发 | all-MiniLM-L6-v2(快速、免费) |
| 最高质量 | text-embedding-3-large |
| 成本优化 | text-embedding-3-small @ 512 维 |
## 相关技能
- **RAG 架构师** - 向量数据库集成
- **Python 高手** - 异步嵌入流水线
- **ML 流水线** - 嵌入模型部署
- **微调专家** - 自定义嵌入训练