博客文章到一百篇之后,我自己都找不到写过什么了。上 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)切分。中文这种简单粗暴的做法效果意外地好,而且零依赖、零维护成本。