Дрейф согласованности и раунды перекалибровки
Potato считает согласованность по окнам завершённых объектов на /admin/iaa, отмечает падение ниже базового уровня проекта и возвращает кодировщиков к обучению.
Potato считает согласованность между кодировщиками в последовательных окнах завершённых объектов, поэтому ухудшение недавнего кодирования видно в отчёте о согласованности как низкое окно, а не растворяется в одной общей цифре по проекту. Коэффициент по всему проекту смешивает кодирование первой недели с кодированием последней, и команда, чья недавняя работа ухудшилась, всё равно может отчитаться о приемлемой α. Раздел Agreement over time на странице /admin/iaa показывает коэффициент каждой схемы разметки окно за окном и выводит подсказку о перекалибровке, когда последнее окно опускается ниже базового уровня проекта на заданную долю. Затем раунд перекалибровки снова проводит кодировщиков через обучающие объекты и оценивает ответы по заранее известным правильным ответам. Временная шкала и раунды есть в Potato начиная с v2.9.0, и временная шкала включена по умолчанию.
Дрейф здесь означает постепенное изменение того, как кодировщики применяют кодбук по ходу исследования. Дрейф накапливается, по мере того как появляются новые пограничные случаи и каждый кодировщик приходит к собственному прочтению какого-либо определения. Одна проверка надёжности между кодировщиками перед началом кодирования не может обнаружить дрейф. В отрывке, который цитируют Hennessy, Bleakley и Ellithorpe (2022), Lacy и соавторы (2015) пишут: "the intercoder reliability check should occur with study content as it is coded" («проверка надёжности между кодировщиками должна проводиться на материале исследования по мере его кодирования»). То же относится и к команде разметки для ML, чьи инструкции меняются по мере роста набора меток. Эта страница посвящена согласованности по ходу исследования, а страница о согласованности временных сегментов посвящена разметчикам, которые отмечают моменты начала и конца в аудио и видео.
Границы окон и временные метки объектов
Согласованность относится к объекту в целом, по всем кодировщикам, а не к ответу одного кодировщика. Поэтому Potato помещает каждый объект в то окно, в которое попадает момент, когда этот объект закончил последний кодировщик. В этот момент у объекта впервые появляется полный набор ответов для сравнения. На временную шкалу попадают только объекты, достигшие лимита разметчиков, и этот лимит должен быть не меньше двух (например, num_annotators_per_item: 2). Общая для проекта таблица согласованности строится по тем же объектам. Поэтому каждое окно содержит часть объектов, на которых построен базовый уровень.
Момент, когда кодировщик закончил объект, — это последнее зафиксированное изменение ответа по данной схеме. Если у объекта нет зафиксированных изменений, Potato берёт время, когда кодировщик покинул объект, а если нет и его, то время, когда кодировщик открыл объект. Время открытия используется в последнюю очередь, потому что кодировщик, который сначала просматривает очередь и отвечает позже, открыл каждый объект задолго до ответа. Объекты без какой-либо временной метки не попадают на временную шкалу, и раздел сообщает, сколько их. Чаще всего это разметка, импортированная из другого инструмента.
Объекты сортируются по этому моменту и делятся на окна одним из двух способов:
- Равное число объектов (
window_by: count, по умолчанию). В каждом окне одинаковое число объектов. Число окон не превышает числа объектов. Поэтому пилот из пяти объектов не даёт пустых окон. - Равная длительность (
window_by: time). Каждое окно охватывает одинаковый отрезок времени. Когда кодирование идёт рывками, например в два выходных с разницей в месяц, окна между рывками пустые. У пустого окна вообще нет значения согласованности. В таблице на странице согласованности пустое окно выглядело бы как обвал согласованности, хотя в это время никто не кодировал, и поэтому по умолчанию используется равное число объектов.
Какой коэффициент показывает каждое окно
Potato заново выполняет общий для проекта расчёт согласованности на объектах каждого окна. Поэтому для данной схемы каждое окно и базовый уровень используют один и тот же коэффициент. Potato выбирает по одному основному коэффициенту на схему и предпочитает меры с поправкой на случайность. Процент совпадений растёт уже из-за одного лишь дисбаланса классов и может расти даже тогда, когда кодировщики согласны друг с другом меньше, чем предсказывает случайность. Коэффициент зависит от типа схемы:
| Тип схемы | Коэффициент на временной шкале |
|---|---|
radio, select (один код на фрагмент) | альфа Криппендорфа, номинальная |
likert | альфа Криппендорфа, порядковая |
slider, number | альфа Криппендорфа, интервальная |
multiselect, hierarchical_multiselect (несколько кодов на фрагмент) | альфа Криппендорфа с расстоянием MASI |
span (закодированные отрывки) | позиционная α, а когда позиционная α не определена, κ на уровне токенов |
multirate и другие табличные схемы | α, объединённая по всем строкам |
| Схемы без α | первая доступная из мер каппа Флейса, каппа Коэна, взвешенная κ, корреляция или ICC, F1 по сегментам и процент совпадений |
Альфа Криппендорфа равна 1 при полном согласии и 0 при согласии на уровне случайности. Схемы со свободным текстом, такие как textbox, не имеют меры согласованности и не получают строки на временной шкале. Когда данные окна не позволяют вычислить коэффициент, который использует базовый уровень, ячейка остаётся без значения, а не показывает другую меру. Падение между двумя разными мерами ничего бы не значило.
Ячейки временной шкалы и пометки в ячейках
Каждой схеме отводится одна строка. Строка начинается с коэффициента по всему проекту как базового уровня, за которым идёт по одной ячейке на окно (W1, W2 и так далее) с коэффициентом окна и числом объектов. Ячейка без числа показывает вместо него короткую пометку, и Potato объясняет каждую такую пометку под таблицей временной шкалы. Окно со слишком малым числом объектов показывает значение и добавляет отметку. Пометки и отметка означают следующее:
| Пометка | Значение |
|---|---|
total agreement | Все кодировщики выбрали один и тот же код для каждого объекта в окне. Коэффициенту с поправкой на случайность нечего корректировать, и он не определён. Пометка обозначает наилучший для согласованности случай. |
undefined | Коэффициент не определён для данных окна, чаще всего потому, что на один код приходятся все суждения. |
no overlap | На объектах окна пересеклись менее двух кодировщиков. |
no items | В окно не попало ни одного объекта (только для окон равной длительности). |
too few to judge | В окне меньше пяти объектов. Значение показывается, но подсказка о перекалибровке это окно не учитывает. |
Если в проекте используется кодбук QDA Mode, под таблицами есть строка со списком редакций кодбука (r1 · r2 · r3). Potato увеличивает номер редакции при каждом изменении кодбука. Создание, переименование, смена цвета, перемещение или удаление кода считается изменением. Редакции, записанные начиная с v2.9.0, имеют точное время. Более старые редакции датируются по самой ранней аннотации с номером этой редакции. Такая дата является нижней границей, и редакция помечается как approximate. HTML-страница перечисляет редакции по номерам, а описанный ниже отчёт в JSON указывает время каждой редакции. Скрипт может сопоставить каждую правку кодбука с границами окон и показать, изменила ли согласованность правка инструкций.
Настройка
Временной шкале не нужна никакая настройка. Любой проект, в котором на каждый объект приходится два или больше кодировщиков и включён отчёт о согласованности, получает этот раздел на /admin/iaa. Админ-панель ссылается на эту страницу как на полный отчёт о согласованности. Чтобы изменить значения по умолчанию, добавьте блок calibration в существующую конфигурацию:
calibration:
enabled: true
windows: 6
window_by: count
drop_threshold: 0.15calibration принимает ровно эти четыре ключа:
| Ключ | Тип | По умолчанию | Что делает ключ |
|---|---|---|---|
enabled | boolean | true | Вычисляет временную шкалу при каждой загрузке /admin/iaa. Раздел отключает только false. |
windows | integer | 6 | Число окон. Шесть окон показывают тенденцию и при этом оставляют в каждом окне достаточно объектов для коэффициента с поправкой на случайность. В режиме равного числа объектов число окон не превышает числа объектов. |
window_by | string | count | count для окон равного размера, time для окон равной длительности. Любое другое значение обрабатывается как count. |
drop_threshold | number | 0.15 | Относительное падение ниже базового уровня, при котором выводится подсказка о перекалибровке. 0.15 означает на 15% ниже коэффициента по всему проекту. |
Временная шкала добавляет по одному расчёту согласованности на каждое окно при каждой загрузке /admin/iaa. В большом проекте настройка enabled: false убирает эти затраты. Тогда временная шкала исчезает и с /admin/iaa, и из JSON-отчёта /admin/iaa/drift.
Раунд перекалибровки заново запускает фазу обучения. Поэтому проекту, который будет проводить раунды, нужен включённый training и запись training в phases. Ниже приведена полная конфигурация исследования с двумя кодировщиками, которые кодируют открытые ответы на опрос, по одному коду на ответ:
annotation_task_name: Open-ended responses, two coders
task_dir: .
output_annotation_dir: annotation_output/
data_files: [data/responses.json]
item_properties: {id_key: id, text_key: text}
annotation_schemes:
- annotation_type: radio
name: barrier
description: Which barrier to care does the respondent describe?
labels: [Cost, Distance, Wait time, Trust, None]
num_annotators_per_item: 2
training:
enabled: true
data_file: data/training.json
allow_retry: true
failure_action: repeat_training
phases:
order: [training, annotation]
training: {type: training}
annotation: {type: annotation}
agreement_metrics: {enabled: true}
calibration:
windows: 6
window_by: count
drop_threshold: 0.15num_annotators_per_item: 2 показывает каждый фрагмент обоим кодировщикам. Два кодировщика на фрагмент дают каждому окну значение согласованности. failure_action: repeat_training заново запускает обучающие объекты для кодировщика, не прошедшего раунд, вместо того чтобы завершить участие этого кодировщика. Файл обучения содержит фрагменты с согласованным кодом и объяснением в формате, описанном на странице фазы обучения.
Подсказка о перекалибровке
Potato сравнивает последнее окно каждой схемы с базовым уровнем и выводит подсказку, когда относительное падение достигает drop_threshold. Относительное падение равно (базовый уровень − последнее окно) / базовый уровень. Базовым уровнем служит коэффициент по всему проекту, а не коэффициент первого окна, потому что у только что обученной команды согласованность менее всего устойчива в первом окне. Иначе необычно удачное первое окно задало бы планку для всего исследования. При базовой α 0.720 и последнем окне 0.580 падение составляет 19%, и раздел показывает:
Re-calibration suggested — barrier 19% below the project baseline (alpha_nominal 0.580 vs 0.720).Подсказка — это совет руководителю команды. Подсказка не уведомляет кодировщиков и не запускает раунд. Potato не выводит подсказку в трёх случаях:
- Более раннее падение. Potato оценивает только последнее окно, в котором пять или больше объектов. Подсказка о падении, от которого команда уже оправилась, приучила бы команду игнорировать подсказку.
- Неопределённое последнее окно. Когда последнее окно показывает
total agreementилиundefined, Potato не переходит к более раннему окну. - Базовый уровень на нуле или ниже. Согласованность на уровне случайности уже является проблемой, о которой сообщает таблица по всему проекту, а процентное падение от нуля не имеет смысла.
Запуск раунда калибровки через API администратора
Раунд сбрасывает прогресс обучения каждого названного кодировщика и возвращает кодировщика в фазу обучения. Кодировщик снова проходит те же обучающие объекты. Potato оценивает ответы по тем же правильным ответам и, как и при первом прохождении, показывает объяснение к каждому неверному ответу. После успешного прохождения кодировщик переходит к фазе, которая следует за обучением в phases.order, и продолжает с того места, где остановился. Раунд не меняет ни назначения, ни сохранённую разметку. Potato не показывает кодировщикам никакого сообщения о начале раунда. Предупредите команду перед запуском раунда.
Раунд запускается через API администратора с ключом API администратора. На страницах администратора нет кнопки для запуска раунда. На странице админ-панели объясняется, откуда берётся ключ. Проверьте, кто может участвовать и как прошёл последний раунд:
curl -H "X-API-Key: $KEY" http://localhost:8000/admin/calibrationОтвет перечисляет кодировщиков, которых раунд может вернуть к обучению, прошедшие раунды и прогресс каждого возвращённого кодировщика в последнем раунде. Прогресс принимает одно из значений passed, failed, in_progress или unknown вместе с числом отвеченных, общим числом и числом правильных ответов. Участвовать могут кодировщики, находящиеся в фазе разметки, в фазе после исследования или в завершающей фазе. Запустите раунд для всех, кто может участвовать:
curl -X POST -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
-d '{"reason": "barrier alpha fell 19%"}' \
http://localhost:8000/admin/calibration/startВызов возвращает ID раунда, возвращённых к обучению кодировщиков и пропущенных кодировщиков с причиной. Передайте "usernames": ["coder1", "coder2"], чтобы вернуть только названных кодировщиков. Если usernames не указан, возвращаются все, кто может участвовать. Дрейф в применении инструкций затрагивает всю команду. Поэтому вернуть всех обычно правильно.
Кодировщик, который всё ещё находится на странице согласия или инструкций, указывается в ответе в списке skipped. Когда training не включён, вызов возвращает HTTP 400 и никого не возвращает к обучению. Без обучающих объектов возвращённые кодировщики сразу прошли бы через пустую фазу обучения.
Продумайте настройки обучения до первого раунда по двум причинам. Во-первых, с кодировщиком, не прошедшим раунд, поступают согласно training.failure_action. При значении по умолчанию move_to_done проваленный раунд завершает участие этого кодировщика в исследовании. Провал первого прохождения приводит к тому же. Задайте failure_action: repeat_training, чтобы вместо этого заново запустить обучающие объекты. Во-вторых, раунд выбирает обучающие объекты по ID и показывает каждому кодировщику те объекты, которые этот кодировщик видел в первый раз. Обучающие объекты, добавленные в файл после первого прохождения кодировщика, не попадут к этому кодировщику в раунде. Поместите сложные случаи в файл обучения до начала кодирования.
Где команда обсуждает расхождения
Раунд повторяет обучающие объекты, потому что у этих объектов есть известные ответы, по которым можно оценивать. У объектов самого исследования, по которым есть расхождения, правильного ответа нет. Поэтому Potato не включает такие объекты в раунд. Обсуждение как часть перекалибровки происходит в другом месте Potato. Комната для короткого совещания (huddle) в Multiplayer Rooms проводит команду по объектам, по которым команда сейчас расходится, и показывает исходный код каждого кодировщика. В очереди арбитража арбитр разрешает спорные объекты по одному. Качественная исследовательская команда обычно завершает такое обсуждение пересмотром определений кодов в кодбуке, и следующая редакция затем появляется на временной шкале рядом с последующими окнами.
Отчёт о дрейфе и журнал раундов
Отчёт о дрейфе и журнал раундов существуют отдельно от экспорта разметки. Ни один формат python -m potato.export не включает ни отчёт о дрейфе, ни журнал раундов. Получите временную шкалу в формате JSON:
curl -H "X-API-Key: $KEY" http://localhost:8000/admin/iaa/driftJSON-отчёт содержит следующие поля:
windowsперечисляет для каждого окна индекс, время начала и конца, число объектов, флагsparse, ID объектов и полный отчёт о согласованности, ограниченный этим окном.schemasуказывает для каждой схемы основную метрику, базовый уровень и по одной точке на окно со значением окна, пометкой и числом объектов.triggersперечисляет все схемы, вызвавшие подсказку, с базовым уровнем, последним значением и относительным падением.markersперечисляет редакции кодбука со временем каждой редакции и флагомinferredдля приблизительных дат.n_itemsиn_untimed_itemsсчитают объекты на временной шкале и вне её, аreasonобъясняет пустой отчёт.
Время указывается как метки времени Unix в секундах. Отчёт пуст, когда сравнивать нечего, например когда ни один объект не достиг лимита разметчиков или когда все объекты с временными метками попадают в одно окно. Поле reason тогда объясняет причину.
Potato записывает каждый раунд в таблицу calibration_rounds файла <task_dir>/project.sqlite: время начала раунда, администратор, запустивший раунд (admin при вызове с ключом API), текст причины и возвращённые к обучению кодировщики. Журнал раундов и коэффициенты по окнам дают разделу о методах даты каждой перекалибровки и согласованность до и после каждого раунда. Для аудиторского следа качественного исследования нужны обе записи. Ответы кодировщика на обучающие объекты хранятся в user_state.json, и каждый раунд сбрасывает эти ответы. Чтобы сохранить результаты первого прохождения обучения, сохраните копию каталога с результатами разметки перед раундом.
Дополнительное чтение
- Фаза обучения: файл обучения, критерии прохождения и
failure_action - Multiplayer Rooms: сессии выравнивания и короткие совещания по спорным объектам
- Надёжность между кодировщиками в качественных исследованиях: какой коэффициент сообщать и когда
- Согласованность разметчиков: κ и α для разных типов разметки
- Согласованность временных сегментов: сегменты аудио и видео
Подробности реализации см. в исходной документации.
Литература
- Hennessy, M., Bleakley, A., & Ellithorpe, M. E. (2022). Evaluating and tracking qualitative content coder performance using item response theory. Quality & Quantity, 57(2), 1231-1245. https://doi.org/10.1007/s11135-022-01397-7
- Lacy, S., Watson, B. R., Riffe, D., & Lovejoy, J. (2015). Issues and best practices in content analysis. Journalism & Mass Communication Quarterly, 92(4), 791-811. https://doi.org/10.1177/1077699015607338 (цитируется по Hennessy et al., 2022)