embedding 是什么

把文本变成一个几百到几千维的向量。语义相近的文本 → 向量距离近。

"猫"      → [0.1, 0.7, -0.2, ...]   (1024 维)
"狗"      → [0.15, 0.65, -0.18, ...] (向量很近)
"汽车"    → [-0.4, 0.1, 0.8, ...]    (距离远)

用 OpenAI embedding API

from openai import OpenAI
client = OpenAI()

resp = client.embeddings.create(
    model="text-embedding-3-small",       # 1536 维,便宜
    input=["猫坐在垫子上", "狗趴在地板上", "汽车在路上跑"],
)

vectors = [d.embedding for d in resp.data]    # 3 个向量
print(len(vectors[0]))                          # 1536

用本地中文 embedding(推荐)

OpenAI 对中文支持一般。中文场景用 bge-large-zh

pip install sentence-transformers
from sentence_transformers import SentenceTransformer

model = SentenceTransformer("BAAI/bge-large-zh-v1.5")

texts = ["猫坐在垫子上", "狗趴在地板上", "汽车在路上跑"]
vectors = model.encode(texts, normalize_embeddings=True)
print(vectors.shape)        # (3, 1024)

normalize_embeddings=True 之后,向量已归一化——后面用点积就等价余弦相似度。

算相似度

import numpy as np

def cosine(a, b):
    return float(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)))

cosine(vectors[0], vectors[1])    # 0.85   猫和狗很像
cosine(vectors[0], vectors[2])    # 0.30   猫和汽车不太像

或归一化后直接 np.dot(a, b)

实战:语义搜索

docs = [
    "Python 是一种解释型编程语言",
    "天空今天很蓝,适合出去玩",
    "机器学习是 AI 的子领域",
    "我喜欢吃苹果",
    "深度学习用神经网络处理复杂任务",
]
doc_vecs = model.encode(docs, normalize_embeddings=True)

query = "什么是神经网络?"
q_vec = model.encode([query], normalize_embeddings=True)[0]

scores = doc_vecs @ q_vec     # 一个矩阵乘法搞定所有相似度
top_idx = scores.argsort()[::-1][:3]
for i in top_idx:
    print(f"{scores[i]:.3f} - {docs[i]}")

输出会把"神经网络"相关的文档排前面——这就是语义搜索,比关键字搜索强得多。

切块:长文本要分段

def chunk_text(text, size=300, overlap=50):
    chunks = []
    i = 0
    while i < len(text):
        chunks.append(text[i:i+size])
        i += size - overlap
    return chunks

更智能的做法:按句子 / 段落 / Markdown 标题切。langchain 有现成的 RecursiveCharacterTextSplitter

模型对比(2026 主流)

模型 维度 中英 备注
text-embedding-3-small 1536 中等 OpenAI,便宜
text-embedding-3-large 3072 OpenAI,贵
voyage-3 1024 Anthropic 推荐配合 Claude
bge-large-zh-v1.5 1024 中文最强 本地免费
bge-m3 1024 多语言+长文 长文档好

余弦距离 vs 欧氏距离 vs 点积

向量归一化后三者等价——大多数库默认归一化,只关心结果别纠结公式。

缓存:embedding 也要钱

embedding 不变就别重复算。用 lru_cache / Redis / 文件缓存:

from functools import lru_cache

@lru_cache(maxsize=10000)
def embed(text):
    return tuple(model.encode([text], normalize_embeddings=True)[0])

下一篇讲向量数据库——把 embedding 存起来快速搜索。