简介:这是 asreview 官方发布的 Python 库源码包,面向需要本地部署、离线安装或二次开发的 Python 开发者与技术人员。压缩包共含一百五十二个文件,整体约二点二三兆字节,其中八十八个 Python 脚本构成核心功能模块,字体、矢量图、样式表和脚本等前端资源用于支撑界面交互,配置、文档与打包文件则便于理解工程结构。从打包配置文件与清单文件可看出该库采用了标准的发布流程,而编译后的样式和脚本说明其自带构建好的前端应用,便于直接嵌入使用。目前已有 107 人学习下载,适合借此掌握该库的模块划分、前后端组织方式,也可作为学习 Python 打包配置和开源项目目录设计的参考样例。拿到完整源码后,可自行离线安装,或针对具体需求调整、扩展已有功能,也能帮助深入理解开源库从源码到分发的主要环节。
1. asreview-0.13.tar.gz:一个把「看 10000 条题录」变成「看 2000 条」的 Python 库
做过系统综述或 meta 分析的人都有过这种体验:筛文献筛到第 3000 条,已经记不清上一条为什么排除,头尾发麻。asreview-0.13.tar.gz 这个 Python 库,就是给这件事准备的。ASReview 用主动学习来辅助文献筛选,tar.gz 后缀是它在 PyPI 上的源码发行格式,可以直接 pip 安装,也能解包二次开发。它的路径很反直觉:不是把上万条题录按相关度排序再读,而是先让研究员标注少量文献,模型不断学习并推荐下一批「最值得你判断」的候选,人只做是否相关这种二元判断。个人用到这一版,命令行和 API 都比较收敛,适合做综述的研究者、有内部文献库的数据分析团队,以及想了解主动学习工程化落地的人。
2. ASReview 的主动学习机制:0.13 版本里模型、查询与状态是怎么咬合的
2.1 筛选不是排序,而是「标注-训练-查询」的三步循环
常规文本分类的流程是离线训练一个模型,然后对全库打分排序,研究员从分最高的开始读。这种做法的隐患在于:排序是一次性的,模型最大不确定性集中在决策边界附近,而这部分样本恰恰不会出现在高分区间;另外人工筛选标准会随着阅读推进而变细,比如中途发现「会议摘要也要排除」,一次排序完全没法吸收这条新信息。
ASReview 把整个过程改成了在线学习循环。第 0 轮随机抽十几条记录(prior),由研究员标出相关或不相关;之后每轮用当前标注集训练分类器,用查询策略从未标注池里挑一批候选(默认每批 10 条),研究员继续标;标注并入训练集,进入下一轮。这样模型始终在用最新标准自查边界,人也只读模型挑出来的少数题录,未标注的池子始终不需要通读。
这段逻辑用一个极简循环就能示意,ASReview 0.13 内部跑的本质上就是这件事:
# 示意 ASReview 每一轮查询要做的三件事,真实实现还要处理去重、 # 优先级排序(prioritization)和随机种子,但骨架是下面这三步 model.fit(X[train_mask], y[train_mask]) # 1. 用已标注文献训练分类器 proba = model.predict_proba(X[pool_mask])[:, 1] # 2. 对未标注池预测相关概率 uncertainty = abs(proba - 0.5) # 3. 离决策边界越近越值得问 query = pool_mask[uncertainty.argsort()[:10]] # 4. 取最不确定的 10 条给人标轮数、批量大小和是否加入随机探索,是决定筛选成本的三组参数。批量太小模型更新频繁但人工来回切界面累,批量太大单轮信息量会稀释;0.13 里默认批量是 10,我跑内部项目一般调到 15 到 20,界面操作节奏更顺。pool_mask代表「还没被标注」的文献位,train_mask代表已标注部分,这两组掩码的维护就是状态文件最核心的职责。
2.2 asreview 0.13 里三个可替换的组件:分类器、特征与查询策略
2.2.1 分类器和文本特征
除了 scikit-learn 那套常规文本分类管线,ASReview 把模型抽象成model和feature_extraction两层。文本默认走 TF-IDF,输入是标题加摘要拼接后的字符串,特征落在一个高维稀疏矩阵上。TF-IDF 对文献筛选足够且可解释,0.13 里也保留了词频和嵌入特征的扩展口子。
内置分类器常见就四类:nb、logistic、svm、rf。它们都不是深度学习,理由很简单:每一轮都要重新训练,几万条题录规模的场景耗时按秒计,才能让研究员在标注间隔里等得住。
| 模型 | 特点 | 常见用法 |
|---|---|---|
| nb | 训练最快,特征独立性假设强 | 几千条以内、只求速度的基线 |
| logistic | 表现均衡,模型可解释性好 | 默认首选,多数文本筛选用它起步 |
| svm | 在高维稀疏文本特征上通常效果最好,需要调正则 | 跑批调优时重点比较的对象 |
| rf | 非线性,但更容易过拟合,特征维度高时明显变慢 | 小批量对照组用,不适合大池子 |
我一般固定-m logistic起步,把查询策略和 prior 数量调出个基线,再切 svm 对比。多数项目从 logistic 换到 svm 能涨几个点的召回率,代价是单轮耗时从秒级到十几秒,几十轮累计下来值得关心。max_features这类 TF-IDF 参数在 0.13 里可以通过配置段覆盖,默认值对大多数题录够用,我很少动它,先看模型差异再看特征差异。
2.2.2 查询策略决定「先问谁」
查询策略控制每轮从未标注池选出的那 10 条。0.13 里常用三种:max是纯粹的不确定性采样,只选abs(p - 0.5)最小的样本;random是随机抽,等价于传统人工通读,主要当对照组;mixed是 80% max 加 20% random,保留一点探索性,避免模型过分自信地绕过某些相关文献。
实际使用中纯max越到后期越吃亏,因为剩下的大多是难例,人工标注疲劳度会上来;而mixed可以靠 20% 的随机样本兜底,防止模型一直重复自己的偏见。若是只跑 simulate 做离线调参,我通常先全跑max,因为随机部分会让不同初始种子之间的方差变大,不方便归因。
2.3 状态文件:JSON 与 HDF5 两种轨迹,断点续跑的关键
ASReview 0.13 的每一次查询、每一条标注记录、模型参数与数据路径都会落进状态文件。默认状态格式是 JSON,可以用编辑器打开逐条审计;命令行模拟输出的结果则常写为 HDF5(h5 文件),便于按 key 快速读取批量结果。状态文件的存在有两个直接价值:一是项目标到一半想换台机器,把文件拷过去用asreview lab打开即可续标;二是系统综述的审稿要求可复现,状态文件本身就是筛选轨迹的审计证据。
状态文件还决定了「恢复」和「对比」的粒度。同一个数据集可以生成多份状态文件,每份对应一组模型与查询策略的组合,之后要出对比报告,只需把每份文件里的query_idx和真实标签放在一起算指标,不必重跑筛选过程。
3. 从源码包到跑通第一个筛选命令:安装 asreview 0.13 的完整路径
3.1 用 conda 隔离环境,再 pip 安装 tar.gz
先交代:0.13 依赖 scikit-learn、numpy、pandas、h5py 这一组数值计算生态,直接装进系统 Python 容易把版本关系搞乱。我一般建一个独立环境,这一步对应很多人搜的「python 环境安装」和「conda create 新环境」:
conda create -n asreview python=3.9 -y conda activate asreview pip install asreview-0.13.tar.gz # 或等价地直接从 PyPI 装指定版本 pip install asreview==0.13也可以把 tar.gz 放到某目录后执行pip install /path/to/asreview-0.13.tar.gz,pip 会先解包构建再安装到当前环境。装好后验证一下三件事:包版本、命令行入口、导入是否正常。
pip show asreview | grep -E "Version|Location" asreview --version python -c "import asreview"第一段里python=3.9是刻意选的。0.13 发布时期主流环境是 Python 3.8/3.9,后续小版本虽然能跑在更高版本上,但 numpy 等依赖的预编译轮子在太新的解释器上偶尔会缺失,源码编译又慢又容易报错。没有 conda 的用户,用 venv 也可以,但 conda 一条命令把解释器和依赖一起隔离,确实是常见做法里最省事的路径。
提示:如果
asreview --version报ModuleNotFoundError,先确认当前激活的是哪个环境,which python和which asreview指向是否一致。多数安装问题都出在「装进 A 环境,命令行却在 B 环境里找」。
3.2 用 asreview lab 起一个本地筛选项目
安装完成后最直观的入口是图形界面。0.13 里启动命令统一为asreview lab,运行后会自动在本地起一个 Web 服务并打开浏览器:
asreview lab默认监听本地 5000 端口,终端会打印访问地址。浏览器进入后创建项目、上传 RIS 或 CSV 题录文件、确认字段映射,然后进入标注界面。界面左侧显示当前候选文献的标题和摘要,中间是两个大按钮:Relevant 和 Irrelevant,右侧面板展示已筛条数与进度。标注够一批,模型就会训练并加载下一批。
如果你所在环境没有浏览器,或者是在服务器上跑,命令行照常能用。asreview --help会列出所有子命令,这里不把所有参数背出来,下面只给一个最小可跑的 simulate 示例,这也是整个库里最常被用到的子命令。
3.3 CLI 跑通一套最小 simulate:参数逐行说明
假设手里有一份dataset.ris,里面既包含题录信息,也包含人工标注的相关/不相关标签。下面的命令会让 ASReview 模拟一遍「从零开始筛选」的过程:
asreview simulate dataset.ris \ -m logistic \ -q mixed \ --prior-included 10 \ --prior-excluded 10 \ --n-queries 50 \ -s result.h5各参数做的事:-m logistic指定分类器;-q mixed指定查询策略;--prior-included 10 --prior-excluded 10设定第 0 轮随机抽取的已标注相关、不相关文献各 10 条;--n-queries 50是查询轮数,乘以默认批量 10,意味着模拟最多读完 50×10 = 500 条候选;-s result.h5把完整状态与结果写入 HDF5 文件。
跑完以后最直观的产物是result.h5和命令行打印的进度条。--n-queries设大并不会让人工等待,因为 simulate 全程由真实标签自动完成标注;它是离线调参最重要的旋钮。
下表是 0.13 里 simulate 最常用的参数速查,具体以asreview simulate --help输出为准:
| 参数 | 示例 | 作用 |
|---|---|---|
-m / --model | logistic、svm、nb、rf | 选择分类器 |
-q / --query-strategy | max、mixed、random | 选择查询策略 |
--prior-included | 10 | 初始相关标注条数 |
--prior-excluded | 10 | 初始不相关标注条数 |
--n-queries | 50 | 最多执行多少轮查询,每轮默认 10 条 |
-s / --state-file | result.h5 | 状态与结果输出路径 |
--seed | 42 | 随机种子,保证多次运行结果可重复 |
--seed值得单独说一句:主动学习本身带随机性,prior 的抽取、mixed 里那 20% 随机样本都依赖随机种子。做对比实验时不固定 seed,两组结果没法归因;我每次跑调参都写死--seed 42。注意在标注真实新项目时不设 seed 反而更自然,因为是拿来筛选不是拿来复现结果。
4. 用 Python API 做一次完整的 simulate:评估召回率与筛选效率
4.1 为什么要在「有标准答案」的数据上回测
simulate 本质上是一次回测:你已经有一份人工标好的数据,让 ASReview 从同一起点重新「模拟」筛选,唯一区别是人工标注被替换成了数据里的真实标签。这样你能回答两个问题:如果当时用 AI 辅助,读 20% 的题录能找回多少相关文献?需要在第几次查询之前把批量调大或换模型?
这两个问题直接决定一个真实项目愿不愿意上辅助筛选。我不建议在还没标完的项目里直接判断效果,因为没标完就没有相关文献总数,召回率无从计算。先把历史数据或者公开数据集的标签拿过来模拟,再迁移到真实项目。
4.2 数据准备:ASReviewData 读入三种常见格式
ASReview 的 Python 包对数据接入做了统一封装,核心类就是ASReviewData。它支持从 CSV、RIS、TSV 三种常见格式读入,并自动识别扩展名:
from asreview import ASReviewData data = ASReviewData.from_file("records.ris") data_csv = ASReviewData.from_file("records.csv") # included 列存在时,后续 simulate 会用这些标签当作真实答案 print(data.df.shape, data_csv.df.shape)读进来之后是 Pandas DataFrame,列名不区分大小写,但必须能对上 title、abstract、included 这三个关键角色。included是 0/1 标签,缺失时全库跑不了 simulate;abstract缺失的题录会被当作空字符串参与建模,不影响流程。
手动指定格式的兼容写法也存在:ASReviewData.from_csv(path)、ASReviewData.from_ris(path),适合扩展名和实际内容不一致的场景。遇到编码乱码时,先不折腾 API,把原始文件用工具转成 UTF-8 再导入,这一步最省时间。
4.3 把 simulate 跑起来并解析 HDF5 结果
数据准备好后,可以直接复用第 3 章的命令行形式,也可以把命令包在 shell 里批量跑。这里给一个包含解析脚本的完整流程,便于在 notebook 里直接看结果:
asreview simulate records.csv \ -m svm \ -q max \ --prior-included 15 \ --n-queries 60 \ --seed 42 \ -s sim.h5跑完后用 h5py 打开 h5 文件。ASReview 状态文件的大致结构是:data组存原始题录字段与标签,results组存每次查询被挑中的文献下标和对应标注结果。字段名在不同 0.x 小版本间略有差异,所以第一步先打印顶层 key:
import h5py with h5py.File("sim.h5", "r") as f: print(list(f.keys())) # 先看结构,找到 results 和 data q_idx = f["results"]["query_idx"][:] labels = f["data"]["labels"][:]query_idx是一维数组,长度等于查询轮数乘批量,顺序就是模型认为的「由易到难」的阅读顺序。把遍历顺序和真实标签结合,就能还原任何阅读截断点下的表现。需要注意labels可能以二维形式存储,后面计算时要用ravel()压成一维再索引。
4.4 从结果里读三个关键指标
第一是recall@k,读了前 k 条候选时找回的相关文献占全部相关文献的比例。第二是time to discovery,模型找到第 n 篇相关文献时已经读了多少篇,这个值越小说明相关文献越靠前。第三是终态敏感度与特异度,反映如果按模型建议标到最后,分类器本身对「相关」这个概念的拟合程度。
下面这段代码计算不同阅读量下的 recall@k:
import h5py import numpy as np with h5py.File("sim.h5", "r") as f: q_idx = f["results"]["query_idx"][:] labels = np.asarray(f["data"]["labels"][:]).ravel() def recall_at(read_count, order, labels): seen = np.asarray(order[:read_count], dtype=int) total = labels.sum() if total == 0: return 0.0 return labels[seen].sum() / total for k in [100, 200, 500, 1000]: print(f"recall@{k}: {recall_at(k, q_idx, labels):.3f}")参数说明:order是query_idx数组,代表推荐阅读顺序;read_count是从头截断的位置;labels是全量真实标签。这个函数的复杂度是 O(k),对单次截断没问题,但多个 k 反复算时最好把累积命中数组算一次,再用索引取任意截断点的值。实际报告中我一般输出一个两列表格:阅读条数、对应 recall,再画一条曲线,足以向团队说明辅助筛选的价值。
| 阅读条数 | recall | 说明 |
|---|---|---|
| 200 | 0.62 | 只读了全库 2%,找回超过六成相关文献 |
| 500 | 0.84 | 在读 5% 时接近九成 |
| 1000 | 0.93 | 继续读的边际收益明显下降 |
这个表格的数值只是示例量级,真实数字取决于数据集难度和 prior 质量。看到曲线变平的那个点,就是可以停止筛选的位置。
5. 进阶:多格式批量导入、断点恢复与跑批提速的实操技巧
5.1 编码与列名是导入环节最大的坑
RIS 文件来自老文献管理系统时,常见编码是 GBK/ANSI,直接读会得到一堆乱码或者空标题。常见做法是统一转码后再交给ASReviewData.from_file:
iconv -f GBK -t UTF-8 old.ris > clean.risCSV 导入则要盯两件事:列名必须是 title、abstract、included 或它们的兼容别名;布尔列不要用「是/否」,要写 1/0。如果导进去条目数是 0,优先检查是不是标题列名写成了Title_Abs而不是title,不要先怀疑模型。
5.2 断点续跑与多数据集批量跑批
真实项目筛到一半换电脑,或切模型对比,都要靠状态文件恢复。asreview lab打开原有状态文件即可继续标注,命令行 simulate 生成的 h5 也可以作为输入继续迭代。批量处理多个数据集时,最省事的写法是 shell 循环:
for f in data/*.csv; do name=$(basename "$f" .csv) asreview simulate "$f" -m logistic -q mixed \ --seed 42 -s "out/${name}_logistic_mixed.h5" done每跑一个数据集,模型和查询策略直接编码进文件名,后续对照不用翻日志。循环里加上--n-queries 与 --prior-included的组合参数,就能一次铺开多组实验。
5.3 把「清洗-模拟-解析」固化成一个小函数
到了要给十几个数据集反复调参的阶段,我会把这些步骤收进一个 Python 函数:入口传数据路径、模型名、查询策略,内部依次执行转码检查、调用 simulate(subprocess)、解析 h5、打印 recall@k 表,返回指标字典。之后每次实验只改参数组合,不改流程;参数组合保存在一个 dict 或 YAML 文件里,跑批就是两层循环。这也是整个 ASReview 项目里我获益最大的习惯:让主动学习管线本身也变成一个可重复的实验框架。
本文还有配套的精品资源,点击获取