向量搜索从查询开始,但如果您没有查询呢?
组织往往会积累大量文档,例如支持工单、法律文件、新闻资讯和研究论文;在提出正确的问题之前,首先需要了解这些文档里都包含哪些内容。没有标签或训练数据,手动审查数千份文档是不切实际的。当您不知道要搜索什么时,传统搜索无济于事。
本文将介绍一种 Elasticsearch 原生方法,用于无监督文档集群和时序故事追踪,帮助解决这一发现难题。读完本文后,您就可以像这样跨天追踪故事脉络:

您将发现:
- 为什么当您希望在没有查询的情况下进行主题发现时,集群嵌入(而非检索嵌入)至关重要。
- 如何借助 Elasticsearch 的 k 近邻 (kNN) 和批量
msearch,通过密度探测质心分类按主题对文档进行分组。 significant_text如何自动为集群添加标签,让主题在无需训练模型的情况下也能清晰呈现。- 时间故事链如何将每日集群联系起来,展示主题如何逐日演变。
本文由可运行的 Jupyter Notebook 生成。您在文中看到的内联输出均来自该管道的真实运行结果。克隆配套笔记本,即可自行运行。
该管道以来自 BBC News 和 The Guardian 的约 8,500 篇 2025 年 2 月文章作为测试语料库。新闻之所以适合作为示例,是因为它具有清晰的时间演化特征;而在任何文档发现至关重要的场景中,这种模式同样适用,例如法律审查、合规监控、研究整合和客户支持分流。
技术栈:
- Jina v5 集群嵌入:用于主题分组的任务专用低秩自适应 (LoRA) 适配器。Jina 已加入 Elastic,其模型可通过 Elastic Inference Service (EIS) 原生调用。
- Elasticsearch:可扩展的 kNN、
significant_text标签生成和向量存储。 - DiskBBQ:一种基于磁盘的向量索引格式,结合了 Better Binary Quantization (BBQ) 与分层 k-means 分区,以加速近似最近邻 (ANN)。这种索引分区是向量搜索的内部机制,与本文使用的密度探测集群算法相互独立。与
bbq_hnsw相比,bbq_disk将量化向量存储在磁盘上,并仅在堆内存中保留分区元数据,在保持高召回率的同时,大幅降低了资源需求。 - 全局集群 + 每日时间链接:发现与故事演变。
您需要:
- Elasticsearch 部署(Elastic Cloud、Elasticsearch Serverless 或 Elastic 自托管 8.18+/9.0+):
bbq_disk需要 8.18 或更高版本。可选的 diversify retriever 部分需要 9.3+ 或 serverless。 - Jina API 密钥:免费层级包含 1,000 万个 token,足以覆盖核心集群管道的需求(约 425 万个 token)。可选的 retrieval-versus-clustering 对比需要进行第二轮嵌入计算。
- Guardian API 密钥(免费)。
设置
安装所需软件包:
可选(仅当您从此仓库运行抓取帮助程序时):
然后在项目根目录的 .env 文件中配置 API 密钥:
此笔记本调用 load_dotenv(override=True),因此本地 .env 值优先。
第 1 部分:发现式集群 —— 为什么要使用集群嵌入?
大多数向量搜索都会使用经过训练的检索嵌入来将查询与相关文档进行匹配。这对于搜索非常合适,但并不适合用于发现。当您希望在没有任何查询的情况下找到语料库中的主题时,您需要使用能将相似文档组合在一起的嵌入。
Jina v5 通过面向特定任务的低秩适配 (LoRA) 适配器解决了这个问题。LoRA 在保持大部分基础模型权重冻结的同时,对目标内部层添加小幅低秩更新,使模型行为转向特定任务,而无需完全重新训练。同一基模型根据 task 参数产生不同的嵌入:
| 任务 | 训练用于 | 用例 |
|---|---|---|
| 检索.段落 | 查询-文档匹配 | 搜索,检索增强生成 (RAG) |
| 聚类 | 主题分组(针对紧密集群进行优化) | 发现与分类 |
集群适配器经过训练,使相同主题的文档在嵌入空间中更接近,而不同主题的文档则更远离。下面的可视化对比会更直观地呈现这种差异。
检索与集群:可视化对比
为了展示这种差异,我们分别使用两种任务类型对文档样本进行嵌入。集群在原始 1024 维嵌入空间中执行;Uniform Manifold Approximation and Projection (UMAP) 仅用于将这些嵌入投影到 2D 进行可视化。UMAP 保留局部邻域结构,因此可用于比较集群的分离程度。
下图展示了同一组 480 篇文档样本分别采用两种任务类型进行嵌入后,再通过 UMAP 投影到 2D 的结果。请观察集群面板中那些更紧密、彼此分离更明显的颜色分组。

检索嵌入(左)会更分散地铺开各个主题;集群嵌入(右)则会基于相同文档形成更紧密、彼此分离更明显的分组。
集群嵌入能够形成更紧密、视觉上也更清晰的分组。检索嵌入会更均匀地分布各个主题,因此非常适合搜索(细粒度相似度);但对发现来说,更关键的是紧密的主题集群。
这就是为什么在本演练的其余部分中使用 task="clustering" 的原因。
加载数据集
该语料库结合了 2025 年 2 月的两个新闻来源:
- BBC News通过RealTimeData/bbc_news_alltimeHuggingFace 数据集。
- The Guardian 通过 Guardian Open Platform API。
纳入多个来源,有助于验证集群识别出的是真正的主题,而不是某个来源特有的写作风格。
使用集群任务进行嵌入
在调用 Jina v5 API 处理所有文档时均会传入 task="clustering"。嵌入会缓存到磁盘,因此后续运行会完全跳过 API。
API 调用很简单。task 参数是与典型嵌入使用的关键区别:
以下时间反映的是缓存命中情况。第一次对 API 运行时间更长,具体取决于语料库大小。
索引到单个 Elasticsearch 索引
对于发现式集群,整个月的数据都会写入同一个索引 (docs-clustering-all)。每日分区会在后续阶段进行,用于实现时间故事链接。
索引映射对向量字段使用 bbq_disk:
1024 维 float32 向量大小为 4 KB。 bbq_disk 使用分层 k-means 将向量划分为小集群,对其进行二进制量化,并将全精度向量存储在磁盘上以便进行二次评分。只有分区元数据保留在堆内存中,因此即使面对大型语料库,内存需求仍然较低。对于能够承受更多堆内存的工作负载,bbq_hnsw 构建分层可导航小世界 (HNSW) 图,以实现更快的查找,但资源消耗更高。
dense_vector 字段类型支持多种量化策略:bbq_disk 和 bbq_hnsw 最适合高维嵌入,例如此处使用的 1,024 维向量。
集群:基于密度探测的质心分类
传统的集群算法(如 HDBSCAN)假设您可以将完整的 N×d 向量矩阵保存在内存中,并运行重复的完整遍历更新。对于 8,495 篇 1024 维文档而言,这一规模尚可管理(约 35 MB);但如果没有额外基础设施,这种方法就无法扩展到数百万篇文档。
从概念上看,该算法类似于采用 Voronoi 分配和噪声底限的 KMeans++ 初始化;但它将 Elasticsearch kNN 搜索作为计算原语,因此几乎所有工作都在服务器端完成。
- 抽取 5% 的文件 作为密度探针(随机抽样,至少 50 个)。
- 通过批量
msearchkNN 查询探测密度。每个探针发出 kNN 查询,并记录其邻居的平均相似度。高平均相似度 = 嵌入空间中的稠密区域。msearch在单个 HTTP 调用中发送多个搜索请求,这一点至关重要:密度探测生成数百个 kNN 查询,批量处理可避免每个请求的开销。 - 通过多样化策略选择高密度种子:将密度高于中位数的候选种子按密度从高到低排序,只有当它与每个现有种子的余弦相似度都低于分离阈值时,才按贪婪策略予以接受。这是唯一的客户端计算(8k 文档约 0.01 秒)。
- 通过
msearchkNN 按质心对所有文档进行分类:每个种子都作为一个质心,kNN 搜索会检索相似度高于阈值的邻近文档。每个文档都会被分配给返回该文档且得分最高的质心。小集群被归为噪声。
Elasticsearch 负责处理核心计算:使用 msearch 进行密度探测和分类,并使用 significant_text 生成标签。对于该语料库(8,495 个文档),5% 的密度探针样本会发起 425 个 kNN 探针查询,msearch 会将其批量处理为 9 次 HTTP 调用(批大小为 50),从而避免每个探针单独发起一次请求的开销。再结合 bbq_disk ANN 查找,整个集群阶段便能兼顾速度与可扩展性。在集群过程中,kNN 查询会使用尽可能小的 num_candidates 值来提升速度;而在生产环境的搜索查询中,则应使用更高的 num_candidates 值,以牺牲一定延迟为代价换取更高的召回率。
集群的自然大小由每个质心周围的嵌入空间密度决定,而非硬性的 k 上限。主题越密集的区域,形成的集群就越大;而越小众的主题,则会形成更小的集群。
为什么不选择 KMeans 或 HDBSCAN?
KMeans 假设集群为球形,并需要将完整的 N×d 矩阵加载到内存中。对于适合内存的语料库,HDBSCAN 是一个强有力的替代方案。它既可以处理任意形状的集群,也具备更易理解的密度语义。
密度探测质心方法面向的是另一类场景:您希望在同一系统中完成存储、检索和集群,或者数据规模已经大到使客户端矩阵运算变得不切实际。它使用 Elasticsearch kNN 作为计算原语,处理任意大小的集群,并将几乎所有计算保留在服务器端。
理解噪声率
约 28% 的噪声率是有意为之,并不意味着系统出现了故障。在配置的 similarity_threshold 下,不属于任何密集集群的文档将保持未分配状态,而不是被强制匹配到不合适的集群中。这相当于一道质量门槛:评论专栏、短文和一次性报道往往难以形成集群,因为它们缺乏构成连贯分组所需的主题密度。
阈值可调:降低 similarity_threshold 会产生更激进的集群(分配更多文档,但集群更松散),提高则会收紧集群并增加噪声比例。对于这种包含混合新闻内容的语料库,约 30% 的噪声比例是一个合理的平衡点。生产部署应根据特定领域的质量标准调整阈值。
使用 significant_text 自动添加标签
现在,每个集群都需要一个便于人工理解的标签。Elasticsearch 的 significant_text 聚合会找出在前景集(集群)中出现异常频繁、而在背景集(完整语料库)中不常见的词项。
其底层采用统计启发式方法(默认为 JLH 分数),平衡了绝对频率与相对频率的变化,无需机器学习,也无需调用大语言模型 (LLM)。例如,一个关于英国政治的集群,可能会浮现出 starmer、labour、downing 等词项,因为与整体新闻语料库相比,这些词项在该集群中出现得异常频繁。
在这一全局处理阶段,标签直接基于 docs-clustering-all 计算,因此前景集和背景集都取自整个月的数据。在第 2 部分中,标签会使用每日索引模式 (docs-clustering-*)。这是一个通配符,可让查询同时覆盖所有匹配的索引,从而为 significant_text 提供更广泛的背景,以获得更好的对比效果。
一个最小查询形状如下所示:
significant_text significant_text 也可作为一道质量门槛:未产生任何显著词项的集群,说明其缺乏可区分的词汇特征。这类分组本身并不连贯,因此应归为噪声,而不应赋予带有误导性的标签。
一个轻量级的确定性清理步骤会移除噪声较大的标签词项(如数字 token 和通用词),并在必要时回退到代表性标题。这样既保留了 Elasticsearch 原生标签的特点,也提升了可读性。
集群可视化
下方的可视化结果展示了全局集群阶段的发现,包括按日期划分的集群文档与噪声文档分布、整个月的 UMAP 投影,以及用于验证集群反映的是主题而非来源的来源构成图。

2025 年 2 月期间,集群文档与噪声文档的每日分布情况。

全月 UMAP 投影:每个彩色岛屿都是一个主题集群,灰点为噪声

仅限聚类文档:去除噪声能更清晰地揭示主题结构

该聚焦视图突出展示了一个集群(英超足球)与其他所有集群之间的对比。

各集群的来源构成:BBC 和 Guardian 都出现在每个主要集群中,这说明分组依据的是主题,而非来源。
UMAP 中的每个彩色岛屿都代表一个集群:一组关于同一主题的文章,纯粹是通过嵌入相似性而发现的。灰色噪声点则是未能明确归入任何集群的文章(通常是短篇文章、观点文章或一次性报道)。
来源细分图表确认,集群中的文章同时来自 BBC News 和 The Guardian。集群找到的是主题,而非来源,这正是无监督发现应该产生的结果。
使用 diversify retriever 探索集群的广度
普通 kNN 返回与集群质心(密集核心)最相似的文档。但真实的集群往往还会涵盖多个子主题。diversify retriever 使用最大边际相关性 (MMR),呈现既与质心相关、彼此之间又有所差异的文档。
关键参数是λ(lambda):
- λ = 1.0 → 纯相关性(与普通 kNN 相同)。
- λ = 0.0 → 纯多样性(结果最大程度分散)。
- λ = 0.5 → 均衡:既与主题保持相关,又能覆盖不同角度。
版本说明:diversify retriever 可用于 Elastic Cloud Serverless 和 Elasticsearch 自托管 9.3+。在较早版本中,您仍然可以照常完成集群和时间链接部分;只有这一探索步骤需要 diversify retriever。
最简 retriever 请求结构如下:
在 diversify 层级,type、field 和 query_vector 参数均为必需:field 用于告知 MMR 应使用哪个 dense_vector 字段来计算结果之间的相似度,而 query_vector 则提供相关性评分的参考向量。
这可以让您回答:“这个集群到底涵盖了什么?”而不仅仅是“它的中心是什么?”
普通 kNN 的结果往往集中在主题的某一个侧面,也就是那些与质心最相似、彼此之间也最相似的文档。diversify retriever 则会展示同一集群的不同侧面,包括子主题、不同来源和多样化视角。
多样性指标定量证实了这一点:diversify retriever 结果的平均两两相似度较低,意味着返回的文档覆盖范围更广。
这适用于:
- 理解一个集群实际涵盖的范围,不仅要关注其中心,还要关注其边缘。
- 生成摘要。多样化且有代表性的文档为 LLM 提供了更好的素材。
- 寻找代表性示例,用于人工审核或下游标签生成。
- 质量检查。如果多样化结果看起来不够连贯,就说明这个集群可能需要进一步拆分。
第 2 部分:时间故事链
跨天追踪故事
第 1 部分对整个月的数据进行了全局集群,以发现其中的主题。为了呈现时间演化,同样的密度探测质心分类会按天在每日索引上独立运行,再将相邻日期的集群连接起来。请注意,每日集群与第 1 部分中的全局集群相互独立;每天都会生成自己的集群分配和标签,并根据当天的内容进行调整。
链接方法:采样与查询
对于第 A 天的每个集群:
- 采样几个代表性文档。
- 对 B 天的索引运行 kNN。
- 统计落入 B 天每个集群的命中数量。
- 如果命中比例超过阈值(kNN 比例 ≥ 0.4),则记录一条链接。
这速度很快(每个集群只查询少量文档,不是全部),并且使用 Elasticsearch 的原生 kNN,无需外部工具。
kNN 比例达到 100% 表示源集群中的所有采样文档都落入同一个目标集群,也就是强度最高的跨日关 流水以上大多数关联都与足球相关,这很合理:英超联赛的报道每天都有,且主题一致性很高。
score | operator | gedling → league | striker | season 链接是一个小众本地足球集群(Gedling 是一家非联赛俱乐部)在第二天被吸收到更广泛的英超联赛集群中的一个例子,这是每日以不同粒度重新集群的自然效果。
构建故事链
故事链是由连续多天的关联集群组成的序列。
单个配对链接可以显示周一与周二“英国政治”集群之间的关联。故事链则能揭示完整的发展脉络:一个故事从周一开始,在一周内持续发展,并在周五逐渐淡出。
链通过贪婪策略构建,所依据的是 kNN 比例 ≥ 0.4 的关联;这意味着源集群中至少有 40% 的采样文档会落入同一个目标集群。算法从最早出现的集群开始,并始终沿着最强的出向关联继续延伸。
最长的链条连续 19 天追踪乌克兰–俄罗斯相关报道。考虑到 2025 年 2 月持续紧张的地缘政治局势,这并不令人意外。其次是贯穿当月 19 天的英超足球报道。更短的链条则对应于颁奖季(电影/颁奖,6 天)、六国橄榄球赛(10 天)以及英国政治领导层相关报道(7 天)。每条链都代表一条故事轨迹,这些轨迹完全是基于每日索引之间的嵌入相似性自动发现的。
Sankey:可视化故事流
Sankey 图是一种流向可视化图表,其中连线宽度表示连接强度。在这里,每个垂直条带代表一天,每个节点代表一个每日集群(大小由文档数量决定),每条彩色路径则描绘出一条跨时间延展的故事链。链接宽度表示 kNN 重叠强度:更粗的链接意味着更多采样文档落入目标集群。每条链都使用统一颜色,因此从左到右的一条同色路径就代表一个故事的发展过程。
例如,乌克兰-俄罗斯链(作为较长路径之一清晰可见)从 2 月初一直延续到第三周;其链接始终较粗,表明该主题在不同日期之间具有很强的连续性。

时间故事链贯穿 2025 年 2 月。每条彩色路径都代表一个跨天延续的故事;连线宽度表示 kNN 重叠强度。
这种方法的成果
本文完整介绍了基于 Elasticsearch 构建的无监督文档集群管道:
- 集群嵌入:Jina v5 的任务专用适配器可生成针对主题分组优化的嵌入,而不仅仅是用于查询-文档匹配。
- 全局发现式集群:在一个索引中对整个月的数据进行集群,可最大限度地发掘跨日主题。
- 密度探测质心分类:取样 5%,通过
msearchkNN 探测密度,选择不同的高密度种子,再根据这些质心对所有文档进行分类。Elasticsearch 负责处理大部分计算任务;客户端仅负责耗时极短(约 0.01 秒)的种子选择工作。 significant_text标签生成:无需借助 ML 模型或人工标注,显著性检验就能生成有意义的集群标签。无法产生任何显著词项的集群,说明其内部缺乏连贯性,因此会被降为噪声——这也是一种内置的质量控制机制。- 时间故事链接:借助每日索引以及跨索引的采样与查询 kNN,追踪故事如何随时间演变。
关键要点:
- 嵌入任务类型至关重要:集群嵌入能够形成明显更紧密的主题分组。
- 借助 kNN 搜索,Elasticsearch 既可以充当存储层,也可以充当集群引擎。
- 密度探测质心分类几乎将所有计算保留在服务器端,并生成由嵌入空间密度决定的自然大小的集群。
significant_text该方法速度快、可解释性强,在自动标注和质量门控方面同样十分有效。
这种方法适用的场景:
- 您拥有带有时间戳的文本,且希望在无标注训练数据的情况下进行主题发现。
- 您希望使用同一套技术栈完成存储、向量搜索、标注和时间关联。
还可以进一步探索的扩展方向:
- 多周期集群(如按周、按月汇总)
- 通过增量集群分配进行实时摄取。
- 以 significant_text 词项为种子生成 LLM 集群摘要。
- 在更大规模下,采样得到的 KMeans 质心可以作为基于密度的集群算法的热启动种子,从而降低探测阶段的成本。
亲自试用
您可以将其替换为自己的带时间戳文档语料库;任何包含日期信息的文本集合都适用于这一管道。完整的笔记本和支持代码可在 配套仓库中找到。
- 开始免费试用 Elastic Cloud:几分钟内即可启动一个支持
bbq_disk的托管集群。 - 试用 Elasticsearch Serverless:无需管理集群,可自动扩展,并支持本演练涵盖的全部内容。




