Salton VSM 1975 — 把文档变成向量再用余弦比相似度
是什么
Salton 向量空间模型(VSM)是这样一句话:把一篇文档想象成一根箭头,箭头从原点射向多维空间里的某个点;要找哪篇文档跟你的查询最像,就比较哪两根箭头夹角最小。
日常类比:你在大型图书馆找书,每个词(“猫”、“狗”、“航海”)都被分配一根坐标轴。一本《猫的百科》在”猫”这根轴上指得很远,在”航海”那根轴上几乎是 0。你的查询”我想读猫”也被画成箭头。比夹角小的就是答案。
这是 1975 年 Salton-Wong-Yang 在 CACM 上提出的方案,只有 8 页,但它是后面 50 年所有”用相似度找东西”系统的源头。
为什么重要
VSM 之前,检索系统几乎都是布尔匹配:你输入 “cat AND mouse”,系统只能回 “包含” 或 “不包含”——结果没有顺序,要么 100 条全返回,要么 0 条。VSM 第一次给检索引入了”像得多还是像得少”这个连续度量,于是结果可以排序。
不理解 VSM,下面这些事都解释不了:
- 为什么 Google 能把 10 亿个网页按相关度排顺序返回(背后是 TF-IDF + PageRank,TF-IDF 是 VSM 的加权升级版)
- 为什么 ChatGPT 的 RAG(检索增强)要先把文档”嵌入”成向量、再查最近邻——稠密 embedding 是 VSM 的现代换皮
- 为什么 Faiss / Milvus / Pinecone 这些”向量数据库”都用余弦或内积做相似度——VSM 立的规矩
核心要点
VSM 把抽象问题压成 三步:
- 建坐标系:词表里每个不同的词当一根坐标轴。10 万个词 → 10 万维空间。
- 算坐标:每篇文档在每根轴上的坐标 = 这个词在这篇文档里的权重(最简单是词频)。文档变成一根 10 万维的向量。
- 比夹角:查询也按同样方式变成向量,用余弦相似度算它和每篇文档的夹角,越接近 1 越像。
数学表达(不用怕,分子是点积,分母是两个向量的长度乘积):
sim(d, q) = (d · q) / (|d| × |q|)整套办法在论文里叫 “Vector Space Model”。
实践案例
案例 1:三篇文档怎么变成三根向量
假设词表只有三个词 [猫, 狗, 航海]:
| 文档 | 猫 | 狗 | 航海 |
|---|---|---|---|
| D1《猫的百科》 | 8 | 1 | 0 |
| D2《狗的训练》 | 1 | 9 | 0 |
| D3《航海日记》 | 0 | 0 | 7 |
查询 “猫 狗” → q = [1, 1, 0]。
算余弦(手算可行):
- sim(D1, q) ≈ 0.71(猫的书匹配度高)
- sim(D2, q) ≈ 0.78(狗的书也高,因为有点猫的内容)
- sim(D3, q) = 0(完全无关)
结果按分数从高到低排:D2 > D1 > D3。有了顺序,这就是排序检索的雏形。
案例 2:现代 RAG 里你看不见的同一招
from sentence_transformers import SentenceTransformermodel = SentenceTransformer('all-MiniLM-L6-v2')docs = ["猫的百科", "狗的训练", "航海日记"]query = "我想读关于猫的书"
doc_vectors = model.encode(docs) # 每篇 → 384 维向量query_vector = model.encode(query) # 查询 → 384 维向量# 后面的 dot / cosine 排序,跟 1975 年那篇论文一模一样差别只有:1975 年是 10 万维稀疏(大部分是 0),2024 年是 384 维稠密。比夹角找最像这一步从未变过。
案例 3:为什么纯词频不够,需要加权
只看词频:常见词 “the”、“是”、“的”出现非常多,它们会主导夹角,把所有文档算得”差不多像”。Salton 在论文里就提了 term discrimination value——一个词如果到处都出现,区分度低,应该降低它的权重。这条线直接演化成 1988 年的 TF-IDF:词频高 × 文档频率低 = 权重高。
踩过的坑
-
高维稀疏的代价:词表 10 万维,但每篇文档只在几百维上有非零值。直接算
|d|要遍历 10 万维很慢——实践中要用稀疏矩阵存储,只算非零项。 -
同义词盲:
car和automobile是两根不同的轴,VSM 看夹角就以为它们没关系。这个问题直到 90 年代 LSA / LSI(用 SVD 降维)才有缓解,再到 2013 年 word2vec 才彻底转成稠密表示。 -
词形不归一:
cat/cats/cat-like在原始 VSM 里是三根独立的轴。实际系统都要先做词干提取(stemming)或词形还原(lemmatization)。 -
维度灾难:维度太高时,所有点之间的余弦相似度会被压在一个窄区间,区分度退化。这是 90 年代后期推动稠密 embedding 的主要动力之一。
-
长文档天然占便宜:文档越长,命中查询词的机会越大,向量长度也越大。如果只看点积不归一,长文档会一直排在前面。余弦做了归一所以缓解了这个问题,但 BM25 后来又专门加了文档长度惩罚项。
-
查询太短统计不稳:一两个词的查询向量只有一两维非零,余弦对这种极稀疏向量很敏感。实际系统会做查询扩展(query expansion)或伪相关反馈来补足。
适用 vs 不适用场景
适用:
- 文本检索的入门基线,几十行代码就能实现
- 文档不太多(百万级以下)、词表中等、能容忍线性扫描
- 想理解 TF-IDF / BM25 / 现代稠密 embedding 之前,必须先懂 VSM
不适用:
- 需要语义近似(“汽车”和”轿车”应该算像)→ 用 word2vec / BERT embedding
- 文档数量上亿、维度上千 → 用近邻索引(Faiss / HNSW)
- 强调多字段、复杂查询语法 → 用 Lucene / Elasticsearch(其实它们底层还是 VSM 思想 + BM25 加权)
历史小故事(可跳过)
- 1965 年:Salton 在 Harvard 启动 SMART 系统(Salton Magical Automatic Retriever of Text),后来搬到 Cornell。
- 1975 年:CACM 发出这篇 8 页论文,是 SMART 项目把”文档当向量”这件事第一次系统化、写给计算机届看。
- 1988 年:Salton 和 Buckley 推出 TF-IDF 加权——VSM 的权重升级版。
- 1995 年:Salton 去世。同年 Robertson 提出 BM25,把概率模型嫁接到向量结构上。
- 2013 年:Mikolov 的 word2vec 把高维稀疏向量换成低维稠密向量,但”用余弦比相似度”这一步纹丝不动。
- 2020s:所有 RAG 系统、向量数据库(Faiss / Milvus / Pinecone)都是 VSM 的徒孙。
一个有意思的细节:Salton 1927 年生于纽伦堡,二战时随家人到美国,1958 年拿到哈佛博士。他被叫作”信息检索之父”,但 1995 年去世前一直在 Cornell 教书、写代码、和学生跑 SMART 系统。这条研究线从 1965 启动到 1995 他离世,整整 30 年。
学到什么
- 把对象表示成向量、用几何度量它们的相似度——这个思路是过去 50 年信息检索最重要的一个抽象
- 词频 → 权重 → 稠密:从 1975 的原始词频,到 1988 TF-IDF,到 2013 word2vec,每一次都是同一个框架在换”如何给坐标赋值”
- 排序比布尔强:从 0/1 命中跳到连续相似度分数,用户体验提升一个数量级
- 8 页论文 50 年红利:好想法不在长,在能不能被复用、被加权、被改进
延伸阅读
- 论文 8 页 PDF:Salton-Wong-Yang 1975 CACM
- 教材:Manning, Raghavan, Schütze, Introduction to Information Retrieval, 第 6 章 “Scoring, term weighting and the vector space model”
- bm25-okapi —— BM25 是 VSM 加权方案的概率版升级
- colbert-v2 —— 现代神经检索还在沿用 “query-doc 向量相似度” 这个骨架
- hnsw-2018 —— 高维近邻搜索,专为大规模 VSM 服务
- product-quantization-2011 —— 把稠密向量压缩,让 VSM 跑得起亿级数据
关联
- bm25-okapi —— 同一个向量框架,把权重换成基于概率的打分
- hnsw-2018 —— 当向量数十亿时,余弦比对靠近邻图加速
- faiss-2017 —— Facebook 开源的向量检索库,VSM 的工业级容器
- colbert-v2 —— 神经检索代表,token 级稠密向量再做余弦
- rrf-cormack-2009 —— 多个 VSM 排序融合的经典方法
- diskann-2019 —— 当向量库装不进内存,磁盘版近邻索引
- ann-benchmarks —— 各种近邻算法在 VSM 场景的横评
反向链接
- anh-moffat-2005 —— Anh-Moffat 2005 — 让倒排表压到接近熵下限还能 SIMD 解码
- ann-benchmarks —— ANN-Benchmarks — 近似最近邻算法的统一擂台
- block-max-wand-2011 —— Block-Max WAND — 给倒排索引加分块上界,跳过算不过 top-k 的整块
- bpr-2009 —— BPR — 用『i 比 j 更受欢迎』替代『i 是正例 j 是负例』
- colbert-v2 —— ColBERTv2 — 让向量检索既精又能扛百万文档
- diskann-2019 —— DiskANN — 单机十亿向量近邻检索(图存 SSD)
- faiss-2017 —— FAISS 2017 — 用 GPU 在十亿向量里找最近邻
- google-1998 —— Google 1998 — 把整个网络爬下来、压扁、再用一秒查到
- hnsw-2018 —— HNSW — 多层近邻图让向量检索从 O(N) 降到近似 O(log N)
- indri-2005 —— Indri 2005 — 把语言模型、推断网络、结构化查询拼成一个搜索引擎
- okapi-bm25-1994 —— Robertson-Walker 1994 — 把 2-Poisson 压成一行能算的公式
- product-quantization-2011 —— Product Quantization — 把向量切碎再压成几个字节
- rrf-cormack-2009 —— RRF — 把多个搜索结果列表合并成一个的最简单办法
- simrank-2002 —— SimRank — 两个节点相似当且仅当它们的邻居相似
- slim-2011 —— SLIM — 让数据自己学一张稀疏的”看了又看”权重表