不再只会调包:手撸 Embedding 算法实现向量搜索优化
前言:万物皆可向量化
在 RAG (Retrieval-Augmented Generation) 大火的今天,大家都在谈论 Vector Database(向量数据库) 和 Embedding。你可能熟练使用 sentence-transformers 或者 OpenAI 的 text-embedding-3 接口,把一句话变成一个 1536 维的数组,然后扔进 ChromaDB 里做相似度搜索。
但是,这串数字背后的魔法到底是什么?为什么 King - Man + Woman = Queen?
既然本博客主打“折腾”和“手搓”,今天我们就不调包,尝试从最基础的 Word2Vec 思想出发,用 Python + NumPy 手写一个简易的 Embedding 训练和搜索系统。只有亲手实现了它,你才能真正理解所谓的“语义空间”长什么样。
1. 词向量的直觉
计算机不认识字,只认识数字。
最简单的表示法是 One-Hot Encoding:
- Dictionary: [I, drink, coffee, love]
- I: [1, 0, 0, 0]
- coffee: [0, 0, 1, 0]
问题是:这种表示法里,“coffee”和“drink”的向量是正交的(点积为0),没有任何关联。但在人类逻辑里,喝咖啡是很搭配的词。
我们希望有一个 Dense Vector(稠密向量),比如:
- coffee: [0.8, 0.1] (假设维度1是饮料属性,维度2是固体属性)
- tea: [0.75, 0.05]
- stone: [0.01, 0.9]
这样,coffee 和 tea 在空间距离上就很近。
2. 训练的核心:Skip-Gram 模型
Word2Vec 的 Skip-Gram 模型思想简单粗暴:一个词的含义,由它周围的词(Context)决定。
“如果你经常出现在‘喝’和‘杯子’中间,那你大概率是一种饮料。”
我们需要训练一个神经网络:
- 输入:中间词 (Today is sunny day) -> ‘sunny’
- 输出:预测上下文词 -> ‘is’, ‘day’
定义模型结构
我们不用 PyTorch,直接用 NumPy 手写前向传播和反向传播。
1 | |
看到了吗?训练过程极其类似一个分类任务。我们在不断调整 W1,使得它能基于当前词,最大概率地预测出上下文。当训练收敛时,W1 中的每一行,就是这个词的 Vector!
3. 实战:训练并可视化
假设我们有一小段语料:
“I like embedding”, “I like deep learning”, “deep learning is fun”
经过简单的预处理和训练(省略千字数据清洗代码):
1 | |
训练完成后,我们将 model.W1 里的向量画在二维坐标系上。
graph LR
axis_x((X-axis)) --> axis_y((Y-axis))
%% 这里模拟训练后的向量位置
like((like))
deep((deep))
learning((learning))
embedding((embedding))
style like fill:#f96
style deep fill:#69f
style learning fill:#69f
%% 我们可以看到 deep 和 learning 靠得很近
%% embedding 可能也是个技术名词,离它们不远
deep --- learning
learning --- embedding
like -.-> deep
你会发现神奇的现象:deep 和 learning 这种经常共现的词,向量距离会非常近。
4. 向量搜索与 Cosine Similarity
有了向量,怎么做搜索?比如我想搜“AI”,系统应该返回“Deep Learning”。
我们使用 余弦相似度 (Cosine Similarity) 来衡量距离。
$$ \text{Cosine}(A, B) = \frac{A \cdot B}{|A| |B|} $$
1 | |
5. 从 Demo 到生产环境
当然,我们手写的这个只是玩具:
- Context Window 也太小了:现代模型用 Transformer,能看 128k 甚至 1M 的 context。
- 静态 Embedding:Word2Vec 里,“Apple” (苹果) 和 “Apple” (公司) 的向量是一样的。BERT/GPT 解决了多义词问题(Dynamic Embedding)。
- 计算效率:当你有 10 亿个向量时,一个个算 Cosine 简直是自杀。你需要 HNSW (Hierarchical Navigable Small World) 算法或者 Faiss 这样的库来做近似最近邻搜索 (ANN)。
但无论技术如何迭代,核心思想从未改变:将人类的离散符号映射到连续的数学空间,计算距离。
下次当你直接调用 OpenAI 的 API 时,希望你能透过那枯燥的 JSON 响应,看到向量空间里那些璀璨的星辰。
这里是 QixYuan’s Tech Blog,我们下期见。