跳到主要内容

两类索引后端:稠密 ANN 与稀疏 scoring

30 秒导读: 上一章(02-vectorization)把文本变成了数字。这一章讲数字如何被组织成"能搜"的数据结构。txtai 有两条完全独立的路:稠密向量塞进 ANN(近似最近邻,靠几何距离找相似)、词和稀疏维度塞进 scoring(倒排表 + BM25 加权,靠词命中打分)。两者各有一堆可插拔后端。这一章只讲"索引这一层长什么样、每种后端怎么选";怎么把两条腿的结果拼成一次搜索,留给 04-search-and-fusion


1. 这是什么(零基础也能懂)

先建立一个最朴素的画面。你有一百万条文本,已经变成了一百万个东西。现在有人拿一条查询进来,要你从一百万里挑出最相关的 10 条

最笨的办法: 把查询和每一条逐个比一遍,排序,取前 10。一百万次比较——慢,而且每加一条数据就更慢。

索引(index)就是让这一步变快的数据结构。 它提前把数据组织好,查询进来时不用扫全库,只碰"可能相关"的一小撮。这一章讲的就是 txtai 里这层数据结构。

两种"相关",两种索引

txtai 对"相关"有两套完全不同的定义,于是有两套索引:

稠密索引(ANN)稀疏索引(scoring)
数据长什么样一条数据 = 一个几百维的稠密向量(每维都有值)一条数据 = 一袋,或一个绝大多数维度为 0 的稀疏向量
"相关"的定义向量在空间里离得近(几何距离/夹角小)查询的词命中了文档,且命中的是"稀有而重要"的词
擅长语义近似("汽车"≈"轿车",哪怕不同字)精确关键词、专有名词、罕见术语
代表实现Faiss、HNSW、AnnoyBM25 倒排表、Postgres 全文检索
本章对应目录src/python/txtai/ann/src/python/txtai/scoring/

一句话直觉:稠密索引像"按含义找",稀疏索引像"按字眼找"。 前者是几何问题(谁离我近),后者是记账问题(哪些文档里出现过这个词、出现了几次)。

为什么要两套

因为它们互相补短。稠密向量抓得住"意思对但字不一样"(近义、改写),但对精确的编号、人名、罕见词反而钝;稀疏倒排恰恰在这些精确词上最锐利。所以严肃的检索系统往往两条腿都要——这也是为什么 txtai 把它们做成并列的两个子系统,而不是二选一。

这一章我们只把每条腿单独讲透。两条腿的分数怎么合成一个排序,是 04 的事。


2. 顶层全景(它大概怎么转)

两个子系统的形状惊人地一致:一个抽象基类定义"索引该有哪些动作",一个工厂按配置挑后端,后面挂着一排可插拔的具体后端

┌───────────────────────────────┐
稠密向量 (dense) ───▶ │ ann/base.py : ANN (抽象基类) │
│ index/append/search/delete │
│ save/load/count/setting │
└───────────────┬───────────────┘
│ ANNFactory 按 config["backend"] 选
┌────────────┬───────────┬────────┼────────┬──────────┬───────────┐
faiss hnsw annoy numpy torch pgvector sqlite ...
(IVF/Flat) (图) (树) (暴力点积) (GPU) (Postgres) (sqlite-vec)

┌───────────────────────────────┐
词 / 稀疏向量 ───▶ │ scoring/base.py : Scoring │
│ insert/index/upsert/weights │
│ search/batchsearch │
│ issparse/isnormalized/isbayes│
└───────────────┬───────────────┘
│ ScoringFactory 按 config["method"] 选
┌────────────┬───────────┬────────┼────────┬───────────────────────┐
bm25 tfidf sif sparse pgtext
(倒排+BM25) (倒排+TFIDF) (词频) (稀疏向量) (PG 全文检索)

稀疏向量后端复用 ann/sparse/
┌─────────┴─────────┐
ivfsparse pgsparse

怎么读这张图:从上到下三层——抽象接口 → 工厂路由 → 具体后端。 左半(ann)和右半(scoring)是两个平行子系统,唯一的交叉点在右下角:scoringsparse 方法把稀疏向量交给 ann/sparse/ 去建索引。

各部件一句话职责:

部件干什么在哪
ANN稠密索引的抽象契约ann/base.py:11
ANNFactorybackend 名造稠密索引ann/dense/factory.py:18
Scoring稀疏/关键词索引的抽象契约scoring/base.py:6
ScoringFactorymethod 名造 scoring 索引scoring/factory.py:14
SparseANNFactory造稀疏向量 ANN(供 sparse 用)ann/sparse/factory.py:11
Normalize把原始分数拉到 0–1 可比刻度scoring/normalize.py:11

3. 稠密侧:ANN 抽象与后端

3.1 ANN 基类——所有稠密后端的共同契约

先看接口,再看实现,是理解这一层最省力的顺序。ann/base.py:11ANN 类没有任何算法,它只规定"一个稠密索引必须会做哪几件事":

方法契约位置
index(embeddings)从零建索引ann/base.py:41
append(embeddings)往已有索引追加ann/base.py:51
search(queries, limit)批量查,返回 [(id, score)]ann/base.py:71
delete(ids)删除若干 idann/base.py:61
count()有效条数(排除已删)ann/base.py:85
save(path) / load(path)落盘 / 读盘ann/base.py:95:31
setting(name, default)读后端专属配置ann/base.py:112
metadata(settings)盖构建元数据(时间/版本)ann/base.py:131

两个非抽象的公共方法值得记住,它们在每个后端里被反复用:

setting 从"后端自己的那段配置"里取参数。注意它是嵌套查找——先用 config["backend"](比如 "faiss")取出该后端的子配置,再从里面找 name:

# ann/base.py:112 setting —— 示意,简化自源码
def setting(self, name, default=None):
backend = self.config.get(self.config["backend"]) # 取 config["faiss"] 这一段
setting = backend.get(name) if backend else None
return setting if setting or (backend and name in backend) else default

metadata 在每次 index/append 后盖一个时间戳和构建环境(Python 版本、系统、txtai 版本),写进 config["build"]config["update"](ann/base.py:131)。这让保存下来的索引能自描述"我是什么时候、用什么建的"。

一个贯穿所有后端的共同约定:id 就是向量在数组里的位置index 时用 0..N-1 当 id,append 时用 config["offset"] 当起点继续往后编号(见 faiss.py:75hnsw.py:66)。这样 ANN 只管几何,真正的"内部 id ↔ 外部 id ↔ 文档内容"映射交给 Embeddings 编排层去维护(见 01-embeddings-database)。

3.2 ANNFactory——一个 if/elif 决定用哪种几何

工厂本身极简:读 config["backend"],if/elif 分派到具体类。关键是默认值——有 Faiss 就用 Faiss,否则退回纯 NumPy 暴力搜索:

# ann/dense/factory.py:37 —— 默认后端选择
backend = config.get("backend", "faiss" if FAISS else "numpy")

认不出的名字会走 Resolver 尝试按类路径动态加载(ann/dense/factory.py:66),所以你也能挂自定义后端。

3.3 各后端一句话对比

这些后端解决的是同一个问题——在稠密向量空间里找最近邻——但在"精确 vs 近似""内存 vs 磁盘/DB""要不要 GPU""要不要量化压缩"上做了不同取舍:

后端底层库核心数据结构一句话定位位置
faissFaiss小库 Flat 暴力,大库自动 IVF 倒排单元默认首选;规模自适应,支持标量/二值量化ann/dense/faiss.py:34
hnswhnswlib分层可导航小世界高召回、快查询,删除靠打标记ann/dense/hnsw.py:22
annoyAnnoy随机投影森林只读友好、内存映射;不支持增量 append/deleteann/dense/annoy.py:17
numpyNumPy一整块二维数组无依赖的精确暴力点积,基准线ann/dense/numpy.py:17
torchPyTorch张量(可上 GPU)numpy 的 GPU 版,支持 4/8 bit 量化矩阵乘ann/dense/torch.py:31
pgvectorpgvectorPostgres 表 + HNSW 索引索引存进关系库,和内容同库ann/dense/pgvector.py:28
sqlitesqlite-vecSQLite vec0 虚拟表单文件、可嵌入、支持 int8/二值量化ann/dense/sqlite.py:19
ggmlggml/GGUFGGUF 张量用 ggml 后端做相似度,产物是 GGUF 文件ann/dense/ggml.py:25
turbovecturbovecIdMap 量化索引位宽可调(默认 4bit)的压缩向量索引ann/dense/turbovec.py:21

torch 直接继承 numpy(ann/dense/torch.py:31class Torch(NumPy)),只把数组函数换成 torch 版并加量化;pgsparse 也继承 pgvector。这种"继承复用"在两个子系统里都很常见。

3.4 一条贯穿所有后端的暗线:内积 = 余弦

上一章说过 txtai 的向量默认归一化。这里就是它的回报:在归一化向量上,内积(点积)恰好等于余弦相似度。于是几乎每个后端都直接用内积当距离,省掉一次归一化除法。这句注释在源码里出现了一遍又一遍:

  • Faiss:用 METRIC_INNER_PRODUCT 建索引(ann/dense/faiss.py:182);
  • HNSW:self.config["metric"] = "ip"(ann/dense/hnsw.py:40);
  • Annoy:self.config["metric"] = "dot"(ann/dense/annoy.py:35);
  • NumPy:self.dot(queries, self.backend.T) 直接点积(ann/dense/numpy.py:75);
  • pgvector:max_inner_product(ann/dense/pgvector.py:285,createindex 用 HNSW 见 :133)。

score 的口径也被统一成"越大越相似、约在 0–1"。 各后端在返回前做一次小转换,把底层库的原始输出拉到这个约定上:

  • HNSW 拿到的是距离,返回前转成 1 - distance(ann/dense/hnsw.py:95);
  • pgvector 因为 Postgres 只能升序扫索引,存的是负内积,score() 再取反成正(ann/dense/pgvector.py:307);
  • 量化(二值)索引统一返回汉明相似度 1.0 - 汉明距离/总位数(faiss.py:234numpy.py:177hammingscore)。

记住这个"分数口径"很重要——它是下一章能把稠密分和稀疏分放在一起融合的前提。

3.5 Faiss 的规模自适应(挑一个后端看细节)

Faiss 后端最能体现"索引不是一种,而是随数据规模换结构"。configure 方法按条数决定用哪种 Faiss 组件字符串:

  • 小库(≤5000 条) → 直接 Flat(暴力全扫,精确),外面套个 IDMap 记 id(ann/dense/faiss.py:151);
  • 大库 → 切成 IVF{x} 倒排单元:先把向量聚成 x 个簇,查询时只扫最近的几个簇。

x(簇数)由 cells 算:min(4·√n, n/39)——注释点明 Faiss 要求每簇至少 39 个点(ann/dense/faiss.py:184)。查询时扫几个簇由 nprobe 控制(ann/dense/faiss.py:220):簇越多、探得越广,召回越高但越慢。这就是"近似"两个字的来源——用"只看几个簇"换速度,牺牲一点点召回。


4. 稀疏侧:scoring 抽象与后端

现在换另一条腿。稀疏侧的接口叫 Scoring,比 ANN 多几个方法,因为它既要管倒排索引,又要能给词算权重,还要用一组布尔标志自我描述分数性质。

4.1 Scoring 基类

方法 / 属性契约位置
insert(documents, ...)收原始文档(id、文本/词、tags)scoring/base.py:30
index(documents)收尾建索引(算 IDF 等)scoring/base.py:52
upsert(documents)语义等同 index,便于 API 一致scoring/base.py:64
weights(tokens)给每个词算权重(供向量化用)scoring/base.py:74
search / batchsearch单查 / 批量查scoring/base.py:87:101

四个自描述标志是稀疏侧独有的,它们让上层不必知道具体是 BM25 还是 pgtext,就能判断"这个分数能不能直接和别的分数比":

标志含义默认逻辑位置
issparse()是否挂着真正的稀疏/倒排索引各后端自答scoring/base.py:160
isweighted()是否只做词权重(没有倒排索引)定义为 not issparse()scoring/base.py:170
isnormalized()返回的分数是否已归一化到 0–1各后端自答scoring/base.py:180
isbayes()是否用贝叶斯(BB25)校准分数各后端自答scoring/base.py:190

这四个标志就是给融合层看的"分数说明书"。举例:isweighted() 为真意味着"我只是个词权重表,不是个能被检索的索引"(TFIDF 不带 terms 时就是这样,见 tfidf.py:242terms is not None 回答 issparse)。isnormalized / isbayes 则告诉下一章的融合器:这条腿的分数是不是已经在 0–1、是不是概率语义,决定要不要再拉一次刻度。

4.2 ScoringFactory

同样是 if/elifmethod 分派,默认 bm25:

# scoring/factory.py:40 —— 默认 method
method = config.get("method", "bm25")

工厂还有个静态判断 issparse(config)(scoring/factory.py:61):pgtext/sparse 永远是稀疏索引,或者只要配了 terms 也算——上层用它在建索引前就知道该不该走稀疏路。

各后端一句话:

method打分方式索引结构一句话定位位置
bm25BM25SQLite 倒排 terms默认;关键词检索的黄金标准scoring/bm25.py:13
tfidfTF-IDFSQLite 倒排 termsBM25 的父类,更朴素的加权scoring/tfidf.py:24
sif平滑逆频率词频统计主要用于给词做权重(句向量)scoring/sif.py:13
sparse学习式稀疏向量ann/sparse/把稀疏向量交给稀疏 ANNscoring/sparse.py:15
pgtextPostgres ts_rankPG tsvector + GIN全文检索外包给 Postgresscoring/pgtext.py:23

4.3 TF-IDF / BM25:倒排 + 加权的核心

TFIDF 是这条腿的主干,BM25 只是继承它、换两个公式。看懂 TFIDF,就看懂了 80%。

建索引时,index() 扫一遍语料,算全局统计:总词数、平均文档长度 avgdl、每个词的文档频率 docfreq,然后调 computeidf 算每个词的 IDF(逆文档频率——词越稀有,IDF 越高):

# scoring/tfidf.py:263 computeidf —— TF-IDF 版
def computeidf(self, freq):
return np.log((self.total + 1) / (freq + 1)) + 1
# scoring/tfidf.py:277 score —— TF-IDF 版
def score(self, freq, idf, length):
return idf * np.sqrt(freq) * (1 / np.sqrt(length))

BM25 只覆写这两个方法,换成更讲究的公式——IDF 用带 0.5 平滑的概率形式,score 引入两个可调参数 k1(词频饱和)和 b(文档长度惩罚):

# scoring/bm25.py:25,29 —— BM25 覆写
def computeidf(self, freq):
return np.log(1 + (self.total - freq + 0.5) / (freq + 0.5))

def score(self, freq, idf, length):
k = self.k1 * ((1 - self.b) + self.b * length / self.avgdl)
return idf * (freq * (self.k1 + 1)) / (freq + k)

k1b 默认 1.2 / 0.75(scoring/bm25.py:22)。直觉:同一个词出现越多分越高但会饱和(k1),长文档天生词多要打折(b),稀有词更值钱(idf)。 这就是关键词检索几十年的看家公式。

SIF 也继承 TFIDF,但用途不同——它的 score全库词频算平滑逆频率(scoring/sif.py:29),主要给"把一句话压成一个向量"当词权重用,不做倒排检索。

4.4 倒排索引本体:Terms

前面的公式要落到一个真正的数据结构上才能快查,那就是 scoring/terms.pyTerms——一张 SQLite 里的词→文档倒排表。schema 一目了然:每个词一行,idsfreqs 是两个二进制打包的数组,记"这个词出现在哪些文档、各出现几次"(scoring/terms.py:30):

CREATE TABLE terms (term TEXT PRIMARY KEY, ids BLOB, freqs BLOB)

查询是"逐词累加"的。 search(scoring/terms.py:164)把查询拆成词,对每个词取出它的倒排数组、乘上权重,累加到一个 scores 数组上:

# scoring/terms.py:203 —— 核心累加,示意
scores[uids] += freq * weights # 该词命中的文档各加一份权重

一个取自 Lucene 的巧思:词分"稀有"和"常见"两趟处理。稀有词(命中文档数 ≤ cutoff=10% 语料,scoring/terms.py:201)先算,常见词(如 "the")先跳过、只在候选集确定后再合并进来(merge,scoring/terms.py:512)。这样常见词不用对全库累加,省一大笔计算。词权重还包了 lru_cache(scoring/terms.py:453),热门词的权重数组直接命中缓存。

weights() 方法(scoring/tfidf.py:136)则是另一个出口:不做检索,只给一串词各算一个权重值——这正是上一章稀疏/加权向量化会调用的接口。

4.5 pgtext:把倒排外包给 Postgres

PGText 不自己维护倒排,而是用 Postgres 的全文检索:建一个 tsvector 计算列 + GIN 索引(scoring/pgtext.py:150),查询用 ts_rank(vector, plainto_tsquery(...)) 打分(scoring/pgtext.py:74)。它永远 issparse()=Trueisnormalized()=Trueisbayes()=False(scoring/pgtext.py:115)——干净地自报家门。


5. 稀疏 ANN:当"稀疏"也变成向量

scoringsparse 方法是条特殊的路:它不用倒排表,而是用学习式稀疏向量(如 SPLADE——每个文档是个几万维、但绝大多数为 0 的向量)。这种向量既不适合稠密 ANN(维度太高),也不是传统倒排,于是 txtai 专门做了 ann/sparse/ 子系统。

Sparse 类(scoring/sparse.py:15)把文档批量编码成稀疏向量后,交给 SparseANNFactory 建索引(scoring/sparse.py:83)。工厂默认 ivfsparse(ann/sparse/factory.py:30):

稀疏 ANN 后端结构定位位置
ivfsparseIVF 倒排单元 + 稀疏块默认;仿 Faiss 的稀疏版聚类检索ann/sparse/ivfsparse.py:30
pgsparsePostgres sparsevec稀疏向量存进 PG,继承 PGVectorann/sparse/pgsparse.py:24

IVFSparse 的思路和 Faiss 的 IVF 同源: 用 k-means 把稀疏向量聚成若干簇(build,ann/sparse/ivfsparse.py:195),每个簇存成一块稀疏矩阵。查询时先用簇质心找最近的几个簇(nprobe),只在这些簇里做稀疏点积(safe_sparse_dot,ann/sparse/ivfsparse.py:266),多线程并行扫块(scan,:278)。数据量小(≤5000)时退化成单簇精确搜索(nlist,:311)。同样是"用少扫几个簇换速度"的近似逻辑,只是运算换成稀疏矩阵乘法。

PGSparse 直接继承稠密的 PGVector,只把列类型换成 SPARSEVEC、操作换成 sparsevec_ip_ops(ann/sparse/pgsparse.py:44),并处理 pgvector "稀疏向量最多 1000 个非零值"的限制——超了就截断到 top-1000(ann/sparse/pgsparse.py:50)。


6. 归一化:让两条腿的分数可比

这是本章通向下一章的关键伏笔。稠密分(内积/余弦)、BM25 分(可以到几十甚至上百)、稀疏点积分——量纲完全不同,不拉到同一把尺子上就没法融合scoring/normalize.pyNormalize 就干这件事。

它有两种模式:

默认线性归一化(scoring/normalize.py:63):用"最高分 + 平均分"估一个上界,把所有分压到 0–1:

# scoring/normalize.py:76 —— 默认归一化,示意
maxscore = min(scores[0][1] + avgscore, 6 * avgscore)
return [(uid, min(score / maxscore, 1.0)) for uid, score in scores]

贝叶斯 / BB25 校准(scoring/normalize.py:81):把分数当证据,用 sigmoid 映射成概率。它只在正分候选上估计统计量(中位数当 beta、标准差缩放 alpha),再算 1/(1+e^-logits),输出严格是 0–1 的概率语义。别名 bayes/bb25/bayesian 触发(scoring/normalize.py:39)。

这两种模式对应的正是 isnormalizedisbayes 两个标志。 TFIDF/BM25 在 search 里,若配了 normalize 就调 Normalizer(scoring/tfidf.py:174),并让 isbayes() 如实反映用没用贝叶斯(scoring/tfidf.py:247)。Sparse 后端也一样:线性模式用 max 分缩放(scale 默认 30.0,scoring/sparse.py:226),贝叶斯模式走 sigmoid 校准(scoring/sparse.py:222)。

为什么这很重要:下一章的融合器读这些标志来决定怎么合。一条已是 0–1 概率的腿和一条原始 BM25 分的腿不能直接相加——归一化标志就是那份"这条腿的分能不能直接用"的元信息。索引层在这里把接力棒递给了融合层。


7. 巧妙之处(可带走的设计)

  • 接口/工厂/后端三段式,两个子系统完全对称。 认识了 ANN+ANNFactory,几乎不用重学就懂 Scoring+ScoringFactory。加新后端只是加一个类 + 一个 elif(ann/dense/factory.py:40scoring/factory.py:42)。

  • 归一化向量让"内积=余弦"贯穿全栈。 一句上游约定,省掉每个后端里的归一化除法,还统一了距离度量(ann/dense/faiss.py:65 等处反复出现的同一句注释)。

  • 规模自适应索引。 小库精确暴力、大库自动 IVF 聚类——Faiss(faiss.py:151)和 IVFSparse(ivvsparse.pynlist,ann/sparse/ivfsparse.py:311)用同一套 min(4√n, n/39) 逻辑,把"精确 vs 近似"的切换藏进配置里,用户无感。

  • 常见词延迟合并(Lucene 式)。 Terms.search 先算稀有词、跳过常见词,候选定了再补常见词分(scoring/terms.py:512),避免对 "the" 这种词做全库累加。

  • 用布尔标志做"分数说明书"。 issparse/isweighted/isnormalized/isbayes 让融合层不必知道后端细节就能安全组合分数——把"分数的语义"显式化,是这套设计能优雅融合的根。

  • 继承复用。 Torch(NumPy)BM25(TFIDF)SIF(TFIDF)PGSparse(PGVector)——大量后端靠"继承 + 覆写两三个方法"实现,新后端成本极低。


8. 边界与局限

  • 不做融合。 本章两条腿各自独立;把它们的分数合成一个排序是 04-search-and-fusion 的职责。归一化标志是留给它的接口。

  • ANN 不存内容,只存几何。 id 就是数组下标(ann/base.py 注释),真正的"内部 id ↔ 外部 id ↔ 文本"映射由 Embeddings/database 层维护(见 0105-sql-and-database)。

  • 部分后端能力不全。 Annoy 没有 append/delete(类上标了 # pylint: disable=W0223,ann/dense/annoy.py:16,构建后只读);HNSW/TurboVec 的删除是"打标记"而非真删(ann/dense/hnsw.py:72),count 要减去删除计数。

  • "近似"就是会漏。 IVF 类索引只扫 nprobe 个簇,天生可能漏掉边界上的真近邻——召回和速度的权衡由 nprobe/efsearch 等参数控制(ann/dense/faiss.py:220ann/dense/hnsw.py:84)。

  • PG/稀疏后端各有硬限制。 pgvector 稀疏向量最多 1000 个非零值,超了截断(ann/sparse/pgsparse.py:50);pgtext/pgvector 需要外部 Postgres 实例。


9. 代码地图(导航索引)

主题文件关键符号
稠密 ANN 抽象契约src/python/txtai/ann/base.pyANNANN.settingANN.metadata
稠密后端选择src/python/txtai/ann/dense/factory.pyANNFactory.create
Faiss(IVF/量化/自适应)src/python/txtai/ann/dense/faiss.pyFaiss.configureFaiss.cellsFaiss.nprobe
HNSW(图)src/python/txtai/ann/dense/hnsw.pyHNSW.indexHNSW.search
Annoy(树,只读)src/python/txtai/ann/dense/annoy.pyAnnoy
NumPy(精确暴力)src/python/txtai/ann/dense/numpy.pyNumPy.searchNumPy.hammingscore
Torch(GPU/量化)src/python/txtai/ann/dense/torch.pyTorchTorch.matmul8bit
pgvector / sqlite / ggml / turbovecsrc/python/txtai/ann/dense/*.pyPGVectorSQLiteGGMLTurboVec
稀疏 ANN 后端选择src/python/txtai/ann/sparse/factory.pySparseANNFactory.create
IVF 稀疏聚类检索src/python/txtai/ann/sparse/ivfsparse.pyIVFSparse.buildIVFSparse.scanIVFSparse.nlist
稀疏向量存 Postgressrc/python/txtai/ann/sparse/pgsparse.pyPGSparse.prepare
稀疏/关键词抽象契约src/python/txtai/scoring/base.pyScoringissparseisnormalizedisbayes
scoring 后端选择src/python/txtai/scoring/factory.pyScoringFactory.createScoringFactory.issparse
TF-IDF 主干src/python/txtai/scoring/tfidf.pyTFIDF.computeidfTFIDF.scoreTFIDF.weights
BM25 公式src/python/txtai/scoring/bm25.pyBM25.computeidfBM25.score
SIF 词权重src/python/txtai/scoring/sif.pySIF.score
倒排索引本体src/python/txtai/scoring/terms.pyTerms.searchTerms.weightsTerms.merge
学习式稀疏向量 scoringsrc/python/txtai/scoring/sparse.pySparse.batchsearchSparse.normalize
Postgres 全文检索src/python/txtai/scoring/pgtext.pyPGText.search
分数归一化 / 贝叶斯校准src/python/txtai/scoring/normalize.pyNormalize.defaultNormalize.bayes

上一章: 02-vectorization(文本如何变成向量) · 下一章: 04-search-and-fusion(把这两类索引的结果融合成一次搜索) · 返回 index