博客文章到一百篇之后,我自己都找不到写过什么了。上 Algolia 有点重,于是花了一个下午写了个够用的。

索引怎么建

构建期把每篇文章切成词,建成倒排索引,输出一个 JSON。我这个站六十篇文章,索引文件压缩后 180KB,完全可以一次性加载。

为什么是 BM25

TF-IDF 的问题是不考虑文档长度,长文章天然占优。BM25 加了长度归一化和词频饱和,实测在中文短查询上明显更准。

function score(query: string[], doc: Doc, avgLen: number) {
  const k1 = 1.2;
  const b = 0.75;
  return query.reduce((sum, term) => {
    const tf = doc.terms[term] ?? 0;
    const df = index.df[term] ?? 0;
    const idf = Math.log(1 + (index.n - df + 0.5) / (df + 0.5));
    const norm = tf * (k1 + 1) / (tf + k1 * (1 - b + b * doc.len / avgLen));
    return sum + idf * norm;
  }, 0);
}

中文分词

没上分词库,用二元组(bigram)切分。中文这种简单粗暴的做法效果意外地好,而且零依赖、零维护成本。