LLM-03入门11 min

向量 Embedding:让文字进入向量空间

Embedding 是把离散文字变成连续向量的技术,是一切语义搜索、推荐系统、RAG 的基础。本文从余弦相似度出发,配合代码解释 Embedding 的训练逻辑和在跨境电商场景的实际用法。

Embedding向量检索余弦相似度BERTGPT

什么是 Embedding

文字、图片、声音——这些对模型来说都是"离散"的符号。Embedding 的作用是把它们映射到一个连续的向量空间,让语义上相近的东西在空间中距离也相近。

举个例子:模型不知道"跑鞋"和"跑步鞋"是近义词,但通过训练,它学会了这两个词在高维空间中的向量很接近——点积高,余弦相似度高。模型不知道"苹果"和"水果"的关系,但它的 embedding 让它们在空间中距离近。

Embedding 的维度:真实模型的 embedding 维度通常在 256 到 4096 之间。维度越高,表达能力越强,但存储和计算成本也越高。OpenAI 的 text-embedding-3-small 是 1536 维,text-embedding-3-large 是 3072 维。

余弦相似度:衡量语义距离

在向量空间中,"相似"通常用余弦相似度来衡量。公式:

cosine(A, B) = (A · B) / (||A|| × ||B||)

余弦相似度的取值范围是 [-1, 1]:1 表示完全相同方向,0 表示正交(无关),-1 表示完全相反。

cosine_similarity.py
import numpy as np

def cosine_similarity(a, b):
    """计算两个向量的余弦相似度"""
    a = np.array(a)
    b = np.array(b)
    dot = np.dot(a, b)
    norm_a = np.linalg.norm(a)
    norm_b = np.linalg.norm(b)
    return dot / (norm_a * norm_b)

# 模拟电商产品标题的简化 embedding(4维)
sneakers = np.array([0.8, 0.3, -0.2, 0.1])      # 运动鞋
running_shoes = np.array([0.7, 0.4, -0.1, 0.2])  # 跑步鞋
high_heels = np.array([-0.3, 0.1, 0.8, -0.2])   # 高跟鞋
coffee = np.array([-0.5, 0.2, 0.3, 0.6])          # 咖啡

print("语义相似度:")
print(f"  运动鞋 vs 跑步鞋: {cosine_similarity(sneakers, running_shoes):.3f}")  # 高
print(f"运动鞋 vs 高跟鞋: {cosine_similarity(sneakers, high_heels):.3f}")    # 低
print(f"  运动鞋 vs 咖啡:   {cosine_similarity(sneakers, coffee):.3f}")       # 很低

# 用向量检索找相似产品:计算与"运动鞋"最接近的向量
products = {
    "运动鞋": sneakers,
    "跑步鞋": running_shoes,
    "高跟鞋": high_heels,
    "咖啡": coffee,
    "跑步训练服": np.array([0.6, 0.5, 0.1, 0.3]),
    "登山靴": np.array([0.4, 0.2, 0.6, 0.0]),
}
query = sneakers
similarities = {name: cosine_similarity(query, vec) for name, vec in products.items()}
ranked = sorted(similarities.items(), key=lambda x: x[1], reverse=True)

print("\n与'运动鞋'最相似的产品:")
for name, sim in ranked:
    bar = "█" * int(sim * 20)
    print(f"  {name:<10}: {sim:.3f} {bar}")

Embedding 模型是怎么训练的

早期的 Word2Vec 通过"预测邻居词"来训练:给定一个中心词,预测它周围的词。如果两个词经常在相似上下文中出现,它们的向量就会在空间中靠近。

现代模型(BERT、GPT)用的是自监督学习:把句子中某些 token mask 掉,让模型预测;或者用下一句预测、对比学习等方法。训练目标不是特定的分类任务,而是通用的语义表示。

关键理解:Embedding 不是人工标注的,是从海量文本中自动学出来的。它捕捉的是"什么样的词可以替换什么词,什么样的上下文可以出现在什么上下文"这种统计规律。

contrastive_embedding.py
import numpy as np

def contrastive_loss(anchor, positive, negative, margin=0.5):
    """
    简化的对比学习 loss:
    - anchor 和 positive 应该距离近(similarity 高)
    - anchor 和 negative 应该距离远(similarity 低)
    """
    pos_sim = cosine_similarity(anchor, positive)
    neg_sim = cosine_similarity(anchor, negative)
    loss = max(0, neg_sim - pos_sim + margin)
    return loss

# 电商场景:anchor="运动鞋",positive="跑步鞋",negative="高跟鞋"
anchor = np.array([0.8, 0.3, -0.2, 0.1])
positive = np.array([0.7, 0.4, -0.1, 0.2]) # 语义相近
negative = np.array([-0.3, 0.1, 0.8, -0.2])    # 语义无关

loss = contrastive_loss(anchor, positive, negative)
pos_sim = cosine_similarity(anchor, positive)
neg_sim = cosine_similarity(anchor, negative)

print(f"anchor-positive相似度: {pos_sim:.3f}")
print(f"anchor-negative 相似度: {neg_sim:.3f}")
print(f"对比学习 loss: {loss:.3f}")
# loss = max(0, neg_sim - pos_sim + margin)
# 如果 pos_sim 明显高于 neg_sim,loss = 0(理想状态)

主流 Embedding 模型对比

不同的预训练模型产生不同质量的 Embedding。选择取决于你的场景:

BERT 系列:双向编码器,同时看上下文左右两侧。适合理解任务(如产品分类、情感分析),但不适合生成任务。中文代表:BERT-wwm、RoBERTa-wwm-ext-large。

GPT 系列:单向解码器,训练的是"给定前文预测下一个 token"。它的 Embedding 更适合生成相关的任务。GPT-4 的 embedding 模型是 text-embedding-3 系列。

E5 / BGE:专门优化过的 embedding 模型,开源可商用,在 MTEB 榜单上表现优秀。BAAI/bge-m3 支持多语言(包含中文),非常适合跨境电商场景。

embedding_model_comparison.py
# 主流 Embedding 模型参数对比
models = {
    "text-embedding-3-small (OpenAI)": {"dim": 1536, "multilingual": False},
    "text-embedding-3-large (OpenAI)": {"dim": 3072, "multilingual": False},
    "bge-m3 (BAAI)":                  {"dim": 1024, "multilingual": True},
    "m3-base (BAAI)":                 {"dim": 1024, "multilingual": True},
    "Jina Embeddings v3":             {"dim": 1024, "multilingual": True},
    "Qwen2.5-7B Embedding":          {"dim": 3584, "multilingual": True},
}

print("模型 | 维度 | 多语言 | 备注")
print("-" * 55)
for name, info in models.items():
    print(f"{name:<25} | {info['dim']:>4} | "
          f"{'是' if info['multilingual'] else '否':^5} | "
          f"{'跨境电商首选' if info['multilingual'] and info['dim'] <= 1024 else ''}")

跨境电商的特殊需求:如果你同时卖英语、德语、法语、西班牙语产品,必须选多语言 embedding 模型。单语言模型(如 OpenAI text-embedding-3)会把不同语言映射到不同区域,跨语言检索效果极差。BGE-m3 和 Jina v3 是目前跨境场景最实用的选择。

向量数据库:海量 Embedding 的存储与检索

当你有上万条产品 embedding,需要快速找到和用户查询最接近的 K 条——这就需要向量数据库。原理是把 embedding 建立索引,通过近似最近邻(ANN)算法加速检索,不用逐个计算余弦相似度。

主流选择:

PGVector:PostgreSQL 插件,部署最简单,适合初创阶段。如果你的数据已经在 Postgres 里,直接加插件就能用。缺点是性能不如专用向量数据库。

Milvus / Zilliz Cloud:专用向量数据库,支持十亿级向量检索。性能强,但多一个系统要维护。

Qdrant:Rust 写的,性能好,支持过滤条件检索(hybrid search 的基础),有云服务。

pgvector_demo.py
# PGVector 相似度检索示例(概念代码)
# 实际需要 psycopg2 + pgvector 扩展

query_embedding = [0.8, 0.3, -0.2, 0.1]  # 用户搜索词的 embedding

# SQL 等效(简化)
sql = """
SELECT product_id, product_name,
      1 - (embedding <=> %s) AS similarity  -- <=> 是 PGVector 的余弦距离操作符
FROM products
WHERE is_active = true
ORDER BY embedding <=> %s
LIMIT 10;
"""
# 注意:PGVector 用 1 - cosine_distance,所以 similarity 越大越相似
# embedding <=> query 计算的是余弦距离(1 - cosine_similarity)
# 所以 1 - 距离 = 相似度

Hybrid Search:向量 + 关键词

纯向量检索有个问题:特定关键词(如品牌名"NIKE"、型号"Air Max")的精确匹配反而被模糊化了。Hybrid Search 结合关键词检索(BM25)和向量检索,两路结果用 Reciprocal Rank Fusion(RRF)合并。

rrf_fusion.py
def reciprocal_rank_fusion(results_lists, k=60):
    """
    Reciprocal Rank Fusion (RRF)
    合并多个检索结果列表,按 RRF 分数排序
    k 是平滑参数,通常取 60
    """
    scores = {}
    for results in results_lists:
        for rank, doc_id in enumerate(results):
            rrf_score = 1 / (k + rank + 1)
            scores[doc_id] = scores.get(doc_id, 0) + rrf_score
    return sorted(scores.items(), key=lambda x: x[1], reverse=True)

# 模拟两路检索结果
vector_results = ["prod_001", "prod_003", "prod_007", "prod_010"]  # 向量检索 Top 4
bm25_results = ["prod_001", "prod_005", "prod_003", "prod_012"]  # BM25 Top 4

fused = reciprocal_rank_fusion([vector_results, bm25_results])
print("RRF 合并后的排序:")
for rank, (doc_id, score) in enumerate(fused, 1):
    print(f"  #{rank}: {doc_id} (RRF score={score:.4f})")
# prod_001 在两路都出现,上升到 #1
# prod_003 两路都出现,继续靠前

跨境电商的 Embedding 实战场景

场景一:以图搜图:用户上传一张产品图,提取 CLIP image embedding,在向量库中搜索相似商品。适合"看到别人发的图片想买同款"的场景。

场景二:语义搜索:用户搜"夏天透气轻便跑鞋",不包含精确关键词但语义相关的产品("夏季薄款减震跑步鞋")也能被召回。Embedding 把语义相近的 query 和 product title 映射到相近的空间。

场景三:产品去重:同款产品在不同平台有不同标题,计算 embedding 后余弦相似度 > 0.95 可以认为疑似重复,自动进入审核队列。

product_dedup.py
import numpy as np

def cosine_similarity(a, b):
    a = np.array(a)
    b = np.array(b)
    return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))

#模拟同款产品的不同标题 embedding
product_a = np.array([0.7, 0.4, -0.1, 0.2, 0.3, 0.1, -0.2, 0.5])
product_b = np.array([0.68, 0.38, -0.12, 0.22, 0.28, 0.12, -0.18, 0.48])
product_c = np.array([0.2, 0.5, 0.3, -0.1, 0.6, 0.3, 0.4, 0.1])

sim_ab = cosine_similarity(product_a, product_b)
sim_ac = cosine_similarity(product_a, product_c)

print(f"A vs B 相似度: {sim_ab:.4f} -> {'疑似重复' if sim_ab &gt; 0.95 else '独立产品'}")
print(f"A vs C 相似度: {sim_ac:.4f} -> {'疑似重复' if sim &gt; 0> 0.95 else '独立产品'}")
# A vs B: 0.9995 -> 疑似重复(同一款产品的不同写法)
# A vs C: 0.6812 -> 独立产品(不同商品)

Embedding 质量决定一切:RAG 和语义搜索的下游效果,80% 取决于 embedding 的质量。再好的 LLM,如果召回的内容 embedding偏差大,最终答案也会跑偏。上线前用标注数据集测一下 retrieval recall@k(通常取 k=5 或 k=10),这是最直接的指标。

关键结论

· Embedding 是语义的可计算化:把文字变成向量,让语义距离可以被数学计算
· 余弦相似度是最常用的度量:范围 [-1, 1],越接近 1 越相似
· 多语言跨境场景要用多语言模型:BGE-m3、Jina v3 等,避免跨语言检索失效
· 向量数据库是检索基础设施:PGVector 最简单,Milvus/Qdrant 适合规模化
· Hybrid Search 结合精确与语义:BM25 + 向量检索 + RRF 融合是电商搜索的最佳实践

理解了 Embedding,下一篇文章你会明白为什么"给例子"能让模型表现更好——这就是 Few-shot Learning 和 Prompt Engineering 的底层原理。