这个仓库现在有两条可直接运行的检索链路:
scripts/query_word2vec_terms.py- 从
title + description训练出的gensim word2vec模型里扩展相关词
- 从
scripts/duckdb_bm25_search.py- 从
data/acfun.videoinfo.20260307.full.flattened.parquet建 DuckDB BM25 索引 - 主要索引字段:
tags、title、description、payload_channel_parentName、payload_channel_name
- 从
项目使用 pixi:
pixi run python --version当前模型文件:
data/acfun.videoinfo.20260307.full.word2vec.modeldata/acfun.videoinfo.20260307.full.word2vec.kv
查询相关词:
pixi run python scripts/query_word2vec_terms.py 原神只输出可直接拿去检索的关键词:
pixi run python scripts/query_word2vec_terms.py 原神 --topn 8 --min-score 0.7 --keywords-only导出给 Neo4j / Bloom 用的关键词关系图:
pixi run python scripts/export_word2vec_neo4j.py 原神 --topn 8 --min-score 0.7 --depth 1默认会写出:
data/neo4j_word2vec/nodes.csvdata/neo4j_word2vec/edges.csv
如果你想导出一个更大的通用词图,而不是围绕种子词扩展:
pixi run python scripts/export_word2vec_neo4j.py --max-vocab 5000 --topn 8 --min-score 0.75首次需要从 flattened parquet 建一个 DuckDB 库:
pixi run python scripts/duckdb_bm25_search.py build \
--input data/acfun.videoinfo.20260307.full.flattened.parquet \
--db data/acfun.videoinfo.20260307.full.bm25.duckdb \
--overwrite说明:
- 脚本会先对
title/description/tags/两个分区做中日文分词 - 然后把分词结果写入 DuckDB 表
- 最后用 DuckDB
fts扩展创建 BM25 索引
直接查:
pixi run python scripts/duckdb_bm25_search.py query 原神 枫丹 草神 --tokenize-query --top-k 20如果想让所有词都必须命中:
pixi run python scripts/duckdb_bm25_search.py query 原神 枫丹 草神 \
--tokenize-query \
--conjunctive \
--top-k 20默认字段权重:
tags = 4.0title = 3.0channel = 2.0parent_channel = 1.5description = 1.0
可以手动调整,例如更强调标题:
pixi run python scripts/duckdb_bm25_search.py query 原神 枫丹 草神 \
--tokenize-query \
--title-weight 5 \
--tags-weight 4 \
--description-weight 0.5把 BM25 结果导出成 parquet,给后续 pipeline 用:
pixi run python scripts/duckdb_bm25_search.py query 原神 \
--tokenize-query \
--top-k 2000 \
--output-parquet data/tmp/yuanshen.bm25.parquet默认导出的 parquet 至少包含:
idrankquery_textquery_termstotal_scoretitle_scoredescription_scoretags_scoreparent_scorechannel_score
如果你还想把标题、描述、tags、频道这些便于人工查看的字段也一起写进去:
pixi run python scripts/duckdb_bm25_search.py query 原神 \
--tokenize-query \
--top-k 2000 \
--output-parquet data/tmp/yuanshen.bm25.parquet \
--include-info-columns说明:
parent_score/channel_score为null是正常的- 这表示该视频在
parent_terms/channel_terms这两个字段上没有命中查询词 - 总分不会漏算,因为脚本内部在计算
total_score时已经对它们做了coalesce(..., 0)
现在正式 pipeline 是:
- BM25 查询输出一个
id parquet score脚本读取这个id parquet- 脚本再去
data/acfun.videoinfo.20260307.full.flattened.parquet回表拿互动指标和时间字段 - 输出候选打分 parquet 和最终入选 parquet
打分脚本:
scripts/score_videos_from_ids.py
最小输出模式:只保留 id + 打分字段 + 透传的 BM25 分数字段
pixi run python scripts/score_videos_from_ids.py data/tmp/yuanshen.bm25.parquet \
--top-k 1000 \
--selection-mode time-quota \
--time-bucket month \
--min-per-bucket 1 \
--output-parquet data/tmp/yuanshen.scored.parquet \
--selected-output-parquet data/tmp/yuanshen.selected.parquet这时输出 parquet 默认包含:
idscore_before_decaytime_decayscoreage_daystime_bucket- 如果输入的
id parquet里有这些字段,也会一起透传:rankquery_textquery_termstotal_scoretitle_scoredescription_scoretags_scoreparent_scorechannel_score
如果你想把标题、作者、发布时间、播放、点赞、评论、banana、弹幕、收藏、分享这些信息列也带上,便于人工查看:
pixi run python scripts/score_videos_from_ids.py data/tmp/yuanshen.bm25.parquet \
--top-k 1000 \
--selection-mode time-quota \
--time-bucket month \
--min-per-bucket 1 \
--include-info-columns \
--output-parquet data/tmp/yuanshen.scored.with_info.parquet \
--selected-output-parquet data/tmp/yuanshen.selected.with_info.parquet当前打分默认使用的互动字段:
payload_viewCountpayload_likeCountpayload_commentCountRealValuepayload_bananaCountpayload_danmakuCountpayload_stowCountpayload_shareCount
时间相关逻辑:
- 默认有时间衰减:
score = score_before_decay * time_decay time_decay = 0.5 ** (age_days / half_life_days)- 默认
half_life_days = 540
时间分布控制:
--selection-mode time-quota- 不是单纯按分数取前
k - 而是先算分,再按时间桶占比分配名额
- 不是单纯按分数取前
--time-bucket month- 以月为单位控制时间分布
--min-per-bucket 1- 只要某个月在候选集里出现过,就至少给这个月 1 个名额
推荐先用 word2vec 扩词,再把多个词一起丢给 BM25:
pixi run python scripts/query_word2vec_terms.py 原神 --topn 8 --min-score 0.7 --keywords-only可能得到:
原神 真境 剧诗 草神 枫丹 那维
然后用这些词做 BM25:
pixi run python scripts/duckdb_bm25_search.py query 原神 真境 剧诗 草神 枫丹 那维 \
--tokenize-query \
--top-k 50这个流程的核心就是:
- 用
word2vec从种子词找语义相关词 - 把相关词并成一组关键词
- 用 DuckDB BM25 在
tags/title/description/分区上联合检索
scripts/train_word2vec.py- 训练
word2vec
- 训练
scripts/query_word2vec_terms.py- 查询相似词
scripts/export_word2vec_neo4j.py- 导出
Neo4j/Bloom可导入的关键词节点和相似关系
- 导出
scripts/duckdb_bm25_search.pybuild建索引query查 BM25- 支持导出 BM25 结果 parquet
scripts/score_videos_from_ids.py- 读取
id parquet - 回表
flattened parquet - 输出综合打分 parquet 和最终入选 parquet
- 读取