Полнотекстовый поиск для качественного кодирования
Найдите каждый фрагмент интервью или открытый ответ, где встречается слово, и дайте кодировщику забрать подходящие объекты в свою очередь.
При запуске сервера Potato строит полнотекстовый индекс по тексту каждого объекта, и администратор может искать по этому индексу в любом проекте, с QDA Mode или без него. Поиск по запросу copay возвращает каждый фрагмент интервью или открытый ответ из опроса, где есть слово, начинающееся с «copay». Результаты упорядочены по релевантности, а совпавшие слова подсвечены в коротком сниппете. Необязательная настройка search.annotator_claim добавляет на страницу кодирования панель Find. На панели Find кодировщик ищет по тому же индексу и забирает подходящие объекты в собственную очередь. Поиск и QDA Mode появились в Potato 2.6.0.
FTS5 — это расширение SQLite для полнотекстового поиска. SQLite — встроенная база данных, на которой работает хранилище проекта Potato. Potato хранит индекс как таблицу в <task_dir>/project.sqlite, и в том же файле лежат заметки и кодбук. Индекс содержит слова из объектов. Коды, заметки и разметка в индекс не входят, и поиск никогда не присваивает код найденным фрагментам.
В качественном проекте поиск решает две задачи:
- Поиск фрагментов для кодирования. Кодировщик, который проходит корпус по темам, ищет «bus» или «copay», забирает объекты, где встречается слово, и кодирует нужные фрагменты схемой выделения span.
- Проверка термина по всему корпусу. Аналитик или арбитр, который пишет критерии включения для кода, ищет термин, чтобы увидеть, в каких объектах термин встречается. Очередь ни одного кодировщика не меняется.
Настройка
Поиску не нужна настройка. Проект без блока search индексирует каждый объект при запуске и обслуживает поиск для администратора. Конфигурация ниже описывает полный проект в QDA Mode, в котором для кодировщика также включены поиск и взятие объектов:
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 Mode исходит из того, что весь корпус кодирует один кодировщик. При qda_mode.enabled: true Potato принимает annotator_claim при любой схеме назначения. Ключ admin_api_key позволяет вызывать поиск администратора из скрипта или командной строки.
Блок search принимает четыре ключа:
| Параметр | По умолчанию | Описание |
|---|---|---|
search.enabled | true | Строить индекс при запуске и обслуживать эндпоинты поиска. При false Potato не строит индекс, и поиск администратора отвечает 503. |
search.backend | fts5 | Движок индекса. В Potato 2.13.1 fts5 — единственный бэкенд. Любое другое значение отключает поиск и записывает предупреждение в лог. |
search.max_instances | 100000 | Наибольшее число индексируемых объектов. Potato индексирует объекты в порядке загрузки, пока не достигнет предела. Объекты сверх предела в индекс не попадают, и лог сервера сообщает о пределе. |
search.annotator_claim | false | Показывать кодировщикам панель 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 | Один объект на запрос |
Доступ администратора означает либо ключ API администратора в заголовке X-API-Key, либо вошедшего пользователя, чья роль даёт доступ к панели администратора. В проекте с включённым блоком adjudication поиском администратора может пользоваться и арбитр. Поиск администратора работает только на чтение и не меняет ни одной очереди. В Potato 2.13.1 на панели администратора нет поля поиска, и администраторы ищут через эндпоинт:
curl -H "X-API-Key: change-this-key" \
"http://localhost:8000/admin/api/search?q=copay&limit=20"Эндпоинт возвращает 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 — это ранг BM25 в FTS5. Чем ниже значение, тем лучше совпадение, и результаты идут от лучшего совпадения к худшему. count — число возвращённых результатов. limit ограничивает это число, и ответ не сообщает, сколько ещё объектов совпадает сверх ограничения.
Синтаксис запросов
Potato разбивает запрос на каждом символе, который не является буквой, цифрой или подчёркиванием, и возвращает объекты, содержащие каждое получившееся слово как префикс. Potato никогда не передаёт запрос в FTS5 как синтаксис запроса. Правила ниже следуют из этого одного шага:
- Каждое слово должно встретиться.
bus lineнаходит объект, где есть и «bus», и «line», в любом порядке и на любом расстоянии друг от друга. - Каждое слово сопоставляется как префикс.
copayнаходит «copay», «copays» и «copayment».careнаходит «career» и «careful», а также «care». - Регистр и диакритика не учитываются.
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 индексирует одну строку на объект и выбирает её в таком порядке:
- Поле, названное в
item_properties.text_key(по умолчаниюtext). - Список или объект под
text_key, например транскрипт, сохранённый как список реплик. Potato индексирует JSON-текст, который показывает страница кодирования, вместе с именами полей. - Для объекта без пригодного значения под
text_key— первое непустое поле изcaption,description,transcript,prompt,question,title,contentиmessage.
Объекты без этих полей в индекс не попадают, и лог сервера сообщает, сколько объектов пропущено. Чаще всего это изображение без подписи. ID объектов, поля кейсов вроде participant_id и любые другие поля объекта не индексируются, поэтому поиск по ID участника ничего не находит. Кейсы группируют фрагменты по участникам.
Potato перестраивает весь индекс при каждом запуске сервера. Для исследования, загруженного из data_directory, Potato перестраивает индекс ещё раз после загрузки каталога, а наблюдатель за каталогом переиндексирует данные, когда приходят новые файлы. Если в SQLite из сборки Python нет 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 больше 1 | Potato не может гарантировать, что объекты перекрытия дойдут до нескольких кодировщиков. |
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 не проходит проверку с таким сообщением:
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 Mode, который назначает выборку перекрытия для расчёта надёжности между кодировщиками, запускается с включённым взятием, и кодировщики могут брать объекты вне выборки, которую Potato назначил каждому из них. Пока команда кодирует выборку для оценки надёжности, оставляйте annotator_claim выключенным, а потом включите настройку, если самостоятельный выбор объектов кодировщиками подходит под дизайн исследования. Поиск администратора защита не затрагивает, потому что поиск администратора не меняет ни одной очереди.
Поиск во время кодирования в QDA Mode
Исследователь, который ищет термин в корпусе и затем кодирует каждый фрагмент, где термин встречается, выполняет в Potato такие шаги:
- Откройте Find и найдите термин, например
transport. По правилу префикса найдутся также «transportation» и «transported». - Прочитайте сниппеты и возьмите каждый объект, сниппет которого выглядит подходящим.
- Кодируйте каждый взятый объект, когда объект появится в вашей очереди. Выделите фрагмент и примените код из кодбука или нажмите
i, чтобы создать код in vivo из выделения. - Прикрепите заметку к объекту или выделению, чтобы записать, почему фрагмент подходит под код или почему почти подходящий фрагмент остаётся за пределами кода.
Поиск возвращает объекты, по одному сниппету на объект. Объект, где термин упоминается пять раз, появляется один раз, а целиком объект открывается только из очереди. В Potato 2.13.1 нет списка «ключевое слово в контексте» (keyword-in-context) со всеми вхождениями и нет шага, который применяет код ко всем совпадениям поиска. Каждое решение о кодировании остаётся за кодировщиком, который читает фрагмент.
Ограничения
- Только текст объектов. Коды, заметки, разметка, ID объектов и поля, кроме индексируемого текста, недоступны для поиска.
- Нет поиска по фразе, булевого, по близости или со стеммингом. Каждый запрос — набор префиксов, которые должны встретиться все.
- Один сниппет на объект. Сниппет содержит до 12 слов, и объект появляется один раз, сколько бы раз ни встречался термин.
- Ограниченные списки результатов. Поиск администратора возвращает не больше 500 объектов на запрос, поиск кодировщика не больше 200, а панель Find 50.
- Языки, в которых слова не разделяются пробелами. Текст на китайском или японском индексируется как последовательности символов между знаками препинания, и запрос совпадает только с началом последовательности.
- Медиа без текста. Объект с изображением, аудио или видео индексируется только через поле подписи или транскрипта, названное в
text_keyили найденное среди резервных полей.
Пример проекта из основного репозитория
В рабочей копии Potato запустите пример поиска из основного репозитория и обратитесь к эндпоинту администратора с ключом из примера:
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 в проекте с фрагментами интервью, запустите вместо него пример QDA Mode из examples/advanced/qda-mode-example/config.yaml.
Что почитать дальше
- QDA Mode — рабочая среда, частью которой является поиск
- Кодбук — коды, которые кодировщик применяет к найденным фрагментам
- Заметки — заметки к объектам и выделениям
- Назначение задач — стратегии назначения, которые проверяет защита
- Кодирование транскриптов интервью — весь путь от записей до закодированных фрагментов
Детали реализации см. в исходной документации.