跳转到内容

质性编码的全文搜索

找出提到某个词的每一条访谈摘录或开放式回答,并让编码者把匹配的条目拉入自己的队列。

Potato 在服务器启动时为每个条目的文本建立全文索引,管理员可以在任何项目中搜索这个索引,无论项目是否启用 QDA 模式。搜索 copay 会返回每一条含有以 “copay” 开头的词的访谈摘录或开放式问卷回答。结果按相关度排序,匹配的词在一段简短的摘要片段中高亮显示。可选设置 search.annotator_claim 会在编码页面上添加一个 Find 面板。编码者在 Find 面板中搜索同一个索引,并把匹配的条目认领到自己的队列中。搜索和 QDA 模式都是在 Potato 2.6.0 中加入的。

FTS5 是 SQLite 的全文搜索扩展。SQLite 是 Potato 项目存储背后的嵌入式数据库。Potato 把索引作为一张表保存在 <task_dir>/project.sqlite 中,备忘录和码本也保存在同一个文件里。索引只包含条目中的词。代码、备忘录和标注都不在索引之内,搜索也从不给搜索结果中的段落应用代码。

在质性项目中,搜索有两种用途:

  • 查找要编码的段落。 按主题通读语料库的编码者搜索 “bus” 或 “copay”,认领提到该词的条目,再用跨度方案为相关段落编码。
  • 在整个语料库中核查一个词。 撰写某个代码纳入标准的分析者或裁定者搜索一个词,查看哪些条目用到了这个词。任何编码者的队列都不会改变。

配置

搜索无需任何配置。没有 search 块的项目会在启动时为每个条目建立索引,并提供管理员搜索。下面的配置是一个完整的 QDA 模式项目,同时为编码者开启了搜索并认领:

yaml
annotation_task_name: Clinic access interviews
task_dir: .
output_annotation_dir: annotation_output/
data_files:
- data/interviews.json
item_properties:
  id_key: id
  text_key: text
admin_api_key: change-this-key
 
qda_mode:
  enabled: true
 
search:
  annotator_claim: true
 
annotation_schemes:
- annotation_type: span
  name: codes
  description: Highlight a passage and apply a code
  codebook: true
  labels: [access barriers, cost concerns, provider trust]

QDA 模式假定由一位编码者处理整个语料库。在 qda_mode.enabled: true 下,Potato 在任何分配设计中都接受 annotator_claim。有了 admin_api_key,你可以从脚本或命令行调用管理员搜索。

search 块接受四个键:

选项默认值说明
search.enabledtrue在启动时建立索引并提供搜索端点。设为 false 时,Potato 不建立索引,管理员搜索返回 503。
search.backendfts5索引引擎。fts5 是 Potato 2.13.1 中唯一的后端。任何其他值都会关闭搜索并在日志中记录一条警告。
search.max_instances100000建立索引的条目数上限。Potato 按载入顺序为条目建立索引,直到达到上限。超过上限的条目不进入索引,服务器日志会报告这个上限。
search.annotator_claimfalse向编码者显示 Find 面板,并提供编码者搜索和认领端点。当该设置与分配设计冲突时,Potato 拒绝启动(参见分配保护)。

谁可以搜索

管理员搜索和编码者搜索通过不同的端点读取同一个索引:

谁端点何时可用每次请求的结果数
管理员和裁定者GET /admin/api/search?q=<query>&limit=<n>search.enabled 为 true默认 50,最多 500
任何已登录的编码者GET /api/search?q=<query>&limit=<n>,以及 Find 面板search.annotator_claim: true默认 50,最多 200;Find 面板请求 50
任何已登录的编码者POST /api/search/claim,请求体为 {"instance_id": "<id>"}search.annotator_claim: true每次请求一个条目

管理员权限指在 X-API-Key 请求头中提供管理员 API 密钥,或者以角色拥有管理后台权限的用户身份登录。在启用了 adjudication 块的项目中,裁定者也可以使用管理员搜索。管理员搜索是只读的,不改变任何队列。Potato 2.13.1 的管理后台没有搜索框,管理员通过端点进行搜索:

bash
curl -H "X-API-Key: change-this-key" \
  "http://localhost:8000/admin/api/search?q=copay&limit=20"

端点返回如下结构的 JSON:

json
{
  "count": 1,
  "query": "copay",
  "results": [
    {
      "id": "P01-03",
      "instance_id": "P01-03",
      "score": -0.525,
      "snippet": "My \u0002copay\u0003 went up again this year, so I skipped the follow…"
    }
  ]
}

摘要片段最多包含条目中最匹配部分周围的 12 个词,并用控制字符 U+0002 和 U+0003 标出每个匹配的词。Find 面板把这些标记转换为高亮。score 是 FTS5 的 BM25 排名。分数越低表示匹配越好,结果按最佳匹配在前排列。count 是返回的结果数。limit 限制结果数,响应不会报告超出上限之外还有多少条目匹配。

查询语法

Potato 在每个不是字母、数字或下划线的字符处拆分查询,并返回把拆分出的每个词都作为前缀包含在内的条目。Potato 从不把查询作为查询语法传给 FTS5。下面的规则都来自这一步:

  • 每个词都必须出现。 bus line 匹配同时包含 “bus” 和 “line” 的条目,两个词顺序不限、间隔不限。
  • 每个词都按前缀匹配。 copay 匹配 “copay”、“copays” 和 “copayment”。care 除了匹配 “care”,还匹配 “career” 和 “careful”。
  • 忽略大小写和重音符号。 Copay 和 copay 返回相同的条目,cafe 也能匹配 “café”。
  • 标点会拆分单词。 self-care 搜索的是 “self” 和 “care”。cost-of-care 返回文本中任意位置含有 “cost”、“of” 和 “care” 的任何条目。
  • 撇号会拆分单词。 couldn't 搜索的是 “couldn” 和 “t”。含有 “couldn't” 的条目能被找到,同时摘要片段也会高亮其他以 “t” 开头的词。
  • 运算符只是普通的词。 引号和 * 会被丢弃,OR、NOT 和 NEAR 则变成条目必须包含的词。像 cost OR care 这样的查询通常什么也不返回。
  • 不合并词形变化。 run 通过前缀规则能匹配 “running”,而 ran 永远匹配不到 “run”。请为参与者可能使用的每种词形分别搜索。

索引的文本与索引重建

Potato 为每个条目索引一个字符串,按以下顺序选取:

  1. 由 item_properties.text_key 指定的字段(默认 text)。
  2. text_key 下的列表或对象,例如以发言轮次列表形式保存的转写稿。Potato 索引编码页面所显示的 JSON 文本,包括字段名。
  3. 对于 text_key 下没有可用值的条目,取 caption、description、transcript、prompt、question、title、content 和 message 中第一个非空字段。

不含上述任何字段的条目不进入索引,服务器日志会报告跳过了多少条目。没有说明文字的图像是最常见的情况。条目 ID、participant_id 这样的案例字段以及其他任何条目字段都不会被索引,因此搜索参与者 ID 什么也找不到。案例按参与者对摘录分组。

Potato 每次启动服务器时都会重建整个索引。对于从 data_directory 载入的研究,Potato 会在目录载入后再次重建索引,目录监视器也会在有新文件到达时重新建立索引。当 Python 构建所带的 SQLite 缺少 FTS5 时,Potato 会关闭搜索,搜索端点返回 503,Potato 的其余部分照常运行。

搜索并认领

Find 面板

在 search.annotator_claim: true 下,编码页面会显示一个 Find 按钮。Find 面板接受一条查询并列出最多 50 个匹配项,每项带有摘要片段、条目 ID 和一个 Claim 按钮。Claim 会把条目添加到编码者队列的末尾,并把按钮替换为 “In your queue”。认领一个已在编码者队列中的条目不会改变任何东西,认领端点会报告 "already_assigned": true。面板不会打开条目,编码者要在队列中向前推进才能到达已认领的条目。

认领端点接受项目中的任何条目 ID。另一位编码者已持有的条目同样可以被认领。未知 ID 返回 404,没有会话的请求返回 401,未启用 annotator_claim 的项目返回 403。

分配保护

认领条目的编码者是在自行选择样本,而许多研究设计依赖于由 Potato 来做这个选择。在 search.annotator_claim: true 下,如果配置还设置了以下任何一项,Potato 会拒绝启动:

设置Potato 拒绝的原因
assignment_strategy 设为 random、diversity_clustering、max_diversity、active_learning、llm_confidence、least_annotated、category_based、batch、psychometric、model_review 或 engine该策略的样本或顺序依赖于由 Potato 选择每个条目。
num_annotators_per_item 大于 1、overlap_sample.count 大于 1,或旧键 max_annotations_per_item 和 min_annotators_per_instance 大于 1Potato 无法保证重叠条目送达多位编码者。
attention_checks.enabled 或 gold_standards.enabled编码者可能找到并跳过检查条目。
icl_labeling.enabled面向模型的盲验证条目会变得可被搜到。
adjudication.enabled裁定队列由 Potato 编排。
crowdsourcing.provider 设为 expert 以外的任何值,或存在旧版 prolific 或 mturk 块或登录类型付费任务是分配的单位,认领会破坏报酬结算和覆盖范围。

保护机制允许的分配策略是 fixed_order 和 priority,其中 fixed_order 是 Potato 的默认值。同时设置 assignment_strategy: random 和 num_annotators_per_item: 2 的配置无法通过校验,并给出如下消息:

text
ERRORS:
  - search.annotator_claim: true is incompatible with this configuration:
  - assignment_strategy: random (self-selection breaks sampling/ordering)
  - num_annotators_per_item: 2 (inter-annotator overlap cannot be guaranteed under self-selection)

在 qda_mode.enabled: true 或 solo_mode.enabled: true 下,Potato 会跳过上表中的所有检查。因此,一个为编码者间信度分配重叠样本的 QDA 模式团队项目会在开启认领的状态下启动,编码者随后就能认领 Potato 分配给各自的样本之外的条目。在团队为信度样本编码期间请关闭 annotator_claim,之后如果让编码者自选条目符合研究设计,再开启该设置。管理员搜索不受保护机制约束,因为管理员搜索不改变任何队列。

在 QDA 模式中边编码边搜索

研究者若要在语料库中搜索一个词,再为该词出现的每个段落编码,在 Potato 中按以下步骤操作:

  1. 打开 Find 并搜索这个词,例如 transport。前缀规则也会返回 “transportation” 和 “transported”。
  2. 阅读摘要片段,认领每一个摘要片段看起来相关的条目。
  3. 当已认领的条目在你的队列中出现时,为其编码。高亮段落并应用码本中的代码,或按 i 从选中文本生成一个原位代码。
  4. 为条目或选区附加一条备忘录,记录该段落为何符合这个代码,或某个近似段落为何落在代码之外。

搜索返回的是条目,每个条目一段摘要片段。一个提到该词五次的条目只出现一次,完整条目只能从队列中打开。Potato 2.13.1 没有列出每次出现位置的上下文关键词(keyword-in-context)列表,也没有把代码一次性应用到所有搜索匹配上的步骤。每一个编码决定都由阅读该段落的编码者做出。

限制

  • 只搜索条目文本。 代码、备忘录、标注、条目 ID 以及被索引文本以外的字段都无法搜索。
  • 不支持短语、布尔、邻近或词干搜索。 每个查询都是一组必须全部出现的前缀。
  • 每个条目一段摘要片段。 摘要片段最多 12 个词,无论该词出现多少次,条目都只出现一次。
  • 结果列表有上限。 管理员搜索每次请求最多返回 500 个条目,编码者搜索最多 200 个,Find 面板 50 个。
  • 不用空格分词的语言。 中文或日文文本按标点之间的连续字符段建立索引,查询只能在字符段的开头匹配。
  • 没有文本的媒体。 图像、音频或视频条目只能通过由 text_key 指定或在后备字段中找到的说明文字或转写稿字段被索引。

上游示例项目

在 Potato 的源码目录中,启动上游的搜索示例,并用示例的密钥查询管理员端点:

bash
python potato/flask_server.py start examples/advanced/search-example/config.yaml -p 8000
curl -H "X-API-Key: search-example-key" \
  "http://localhost:8000/admin/api/search?q=rare"

该示例载入六份简短的状态报告,其中三份提到了 “rare”。示例在 fixed_order 分配下开启搜索并认领,并设置 max_annotations_per_user: 2。若要在一个访谈摘录项目中查看 Find 面板,请改为启动位于 examples/advanced/qda-mode-example/config.yaml 的 QDA 模式示例。

延伸阅读

有关实现细节,请参见源文档。