4、向量空间模型:向量化表示、余弦相似度计算、文档排序机制

好,咱们今天聊向量空间模型。说实话,这个模型在信息检索领域算是元老级的存在了,但你别因为它老就小看它。我在做法律案例检索系统的时候,发现很多新潮的深度学习模型在某些场景下反而干不过它。为什么?因为它的逻辑足够透明,出了问题你能一眼看出是哪里的锅。

4.1 向量化表示:把法律文书变成数学

先说说向量化。说白了,就是把一篇法律判决书,变成一串数字。这串数字就是向量。比如一篇关于“合同纠纷”的判决书,里面高频出现“违约金”、“解除合同”、“不可抗力”这些词,那它在这些维度上的数值就高。

我习惯用TF-IDF来做向量化。TF是词频,IDF是逆文档频率。TF告诉你这个词在文档里重不重要,IDF告诉你这个词在整个语料库里是不是烂大街的货。两者一乘,就能过滤掉那些“的”、“了”、“是”之类的废话。

核心公式:
TF-IDF(t, d) = TF(t, d) × IDF(t)
其中 IDF(t) = log(N / df(t)),N是文档总数,df(t)是包含词t的文档数。

举个例子。假设我们有1000份法律判决书,“违约金”这个词出现在其中50份里。那它的IDF就是log(1000/50) ≈ 2.3。如果某篇判决书里“违约金”出现了5次,总词数是200,那TF就是5/200=0.025。最终TF-IDF值就是0.025 × 2.3 ≈ 0.0575。

嗯,这里要注意:向量维度就是词典的大小。如果词典有10万个词,那每个文档就是一个10万维的向量。听着吓人,但实际计算时大部分维度都是0——因为一篇判决书不可能用全所有词。这就是所谓的“稀疏向量”。

4.2 余弦相似度计算:衡量两篇文书的“距离”

向量化之后,怎么判断两篇文书是不是相似?我最早做项目时用的是欧氏距离,结果发现效果很差。为什么?因为欧氏距离对向量的长度太敏感了。两篇长判决书和短判决书,哪怕内容高度相关,欧氏距离也会很大。

后来我换成了余弦相似度。它只看方向,不看长度。你想想看,两篇关于“民间借贷”的判决书,一篇写了5000字,一篇写了2000字,但核心关键词分布差不多,那它们的向量方向就接近,余弦值就高。

我的经验:余弦相似度的值域是[-1, 1]。在法律检索场景下,我一般把阈值设在0.6以上才算“相关”。低于0.4的基本可以认为是无关文档。这个阈值是我调了三个月参数试出来的,你可以参考,但最好根据你的语料库微调。

公式其实很简单:

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

其中A·B是点积,||A||是向量的模长。我写代码时习惯用NumPy,一行搞定:

import numpy as np

def cosine_sim(vec_a, vec_b):
    dot_product = np.dot(vec_a, vec_b)
    norm_a = np.linalg.norm(vec_a)
    norm_b = np.linalg.norm(vec_b)
    return dot_product / (norm_a * norm_b)

我曾经踩过一个坑:零向量问题。如果某篇文档的所有词都不在词典里,那它的向量全是0,模长也是0,除零直接报错。所以一定要加个判空处理:

if norm_a == 0 or norm_b == 0:
    return 0.0

4.3 文档排序机制:把最相关的案例顶上去

有了相似度,接下来就是排序。用户输入一个查询,比如“股东知情权纠纷”,系统先把这个查询也向量化,然后跟库里所有文档算一遍余弦相似度,最后按分数从高到低排。

听起来简单,但实际工程里有个大坑:计算量。假设库里有100万份判决书,每次查询都要算100万次余弦相似度,这谁顶得住?我早期做的原型系统,一次查询要等5秒钟,被业务部门骂得狗血淋头。

后来我用了倒排索引来加速。倒排索引就是“词 → 文档列表”的映射。比如“违约金”这个词,只出现在50份文档里,那我只需要算这50份文档的相似度就行,其他950份直接跳过。这招能把计算量降低两个数量级。

避坑指南:我曾经犯过一个错误——直接用全量计算,结果线上服务OOM了。后来我加了一层过滤:先用倒排索引召回候选集(比如Top 1000),再对这1000篇做精确的余弦相似度计算和排序。这样既保证了精度,又控制了内存。

排序的最终输出,我一般会带上分数和文档ID:

results = [
    {"doc_id": "12345", "score": 0.92, "title": "某某公司诉某某股东知情权纠纷案"},
    {"doc_id": "67890", "score": 0.87, "title": "某某与某某公司股东知情权纠纷二审案"},
    ...
]

嗯,这里有个细节:分数不要直接展示给用户。用户看到0.92和0.87,会问“为什么差0.05?”你解释起来很麻烦。我一般只展示排名,或者用“高度相关”、“相关”、“一般”这样的标签。

4.4 知识体系总览

下面这张图是我自己画的,把向量空间模型的核心逻辑串了一遍。你看一眼就能明白整个流程:

向量空间模型核心流程 文档预处理 分词 → 去停用词 → 词干化 TF-IDF向量化 词频 × 逆文档频率 向量索引库 稀疏向量存储 用户查询 输入关键词或自然语言 查询向量化 同TF-IDF方法处理 余弦相似度计算 cos(θ) = (A·B) / (||A|| × ||B||) 排序输出 Top-K 结果

4.5 实际项目中的调优经验

最后分享几个我在法律检索项目里踩过的坑和优化技巧:

  • 停用词表要定制:通用停用词表会把“原告”、“被告”、“法院”这些词也去掉,但法律场景下这些词恰恰是重要特征。我后来自己建了一个法律专用停用词表,只去掉“的”、“了”、“在”这类虚词。
  • 同义词扩展:法律术语有很多同义表达,比如“违约金”和“违约赔偿金”。我加了一个同义词映射表,查询时自动扩展,召回率提升了15%。
  • 分字段加权:判决书的“裁判要旨”部分比“当事人信息”部分重要得多。我给不同字段设了不同的权重,比如“裁判要旨”权重2.0,“事实认定”权重1.5,“当事人信息”权重0.3。
一个小技巧:如果你用的是Python,推荐用scikit-learn的TfidfVectorizer,它内置了L2归一化,省得你手动算模长。但要注意,它默认的analyzer是word,如果你处理的是中文,记得先分词,或者用jieba配合自定义tokenizer。

好了,向量空间模型这部分就聊到这儿。说白了,它就是一套把文本变成数学、再用数学做比较的方法论。虽然现在有BERT、GPT这些大模型,但向量空间模型在可解释性和计算效率上依然有不可替代的优势。尤其是法律这种对可解释性要求极高的领域,它依然是很多系统的基石。

专注资料整理