Перейти к содержимому

Дрейф согласованности и раунды перекалибровки

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 в существующую конфигурацию:

yaml
calibration:
  enabled: true
  windows: 6
  window_by: count
  drop_threshold: 0.15

calibration принимает ровно эти четыре ключа:

КлючТипПо умолчаниюЧто делает ключ
enabledbooleantrueВычисляет временную шкалу при каждой загрузке /admin/iaa. Раздел отключает только false.
windowsinteger6Число окон. Шесть окон показывают тенденцию и при этом оставляют в каждом окне достаточно объектов для коэффициента с поправкой на случайность. В режиме равного числа объектов число окон не превышает числа объектов.
window_bystringcountcount для окон равного размера, time для окон равной длительности. Любое другое значение обрабатывается как count.
drop_thresholdnumber0.15Относительное падение ниже базового уровня, при котором выводится подсказка о перекалибровке. 0.15 означает на 15% ниже коэффициента по всему проекту.

Временная шкала добавляет по одному расчёту согласованности на каждое окно при каждой загрузке /admin/iaa. В большом проекте настройка enabled: false убирает эти затраты. Тогда временная шкала исчезает и с /admin/iaa, и из JSON-отчёта /admin/iaa/drift.

Раунд перекалибровки заново запускает фазу обучения. Поэтому проекту, который будет проводить раунды, нужен включённый training и запись training в phases. Ниже приведена полная конфигурация исследования с двумя кодировщиками, которые кодируют открытые ответы на опрос, по одному коду на ответ:

yaml
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.15

num_annotators_per_item: 2 показывает каждый фрагмент обоим кодировщикам. Два кодировщика на фрагмент дают каждому окну значение согласованности. failure_action: repeat_training заново запускает обучающие объекты для кодировщика, не прошедшего раунд, вместо того чтобы завершить участие этого кодировщика. Файл обучения содержит фрагменты с согласованным кодом и объяснением в формате, описанном на странице фазы обучения.

Подсказка о перекалибровке

Potato сравнивает последнее окно каждой схемы с базовым уровнем и выводит подсказку, когда относительное падение достигает drop_threshold. Относительное падение равно (базовый уровень − последнее окно) / базовый уровень. Базовым уровнем служит коэффициент по всему проекту, а не коэффициент первого окна, потому что у только что обученной команды согласованность менее всего устойчива в первом окне. Иначе необычно удачное первое окно задало бы планку для всего исследования. При базовой α 0.720 и последнем окне 0.580 падение составляет 19%, и раздел показывает:

text
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 администратора. На страницах администратора нет кнопки для запуска раунда. На странице админ-панели объясняется, откуда берётся ключ. Проверьте, кто может участвовать и как прошёл последний раунд:

bash
curl -H "X-API-Key: $KEY" http://localhost:8000/admin/calibration

Ответ перечисляет кодировщиков, которых раунд может вернуть к обучению, прошедшие раунды и прогресс каждого возвращённого кодировщика в последнем раунде. Прогресс принимает одно из значений passed, failed, in_progress или unknown вместе с числом отвеченных, общим числом и числом правильных ответов. Участвовать могут кодировщики, находящиеся в фазе разметки, в фазе после исследования или в завершающей фазе. Запустите раунд для всех, кто может участвовать:

bash
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:

bash
curl -H "X-API-Key: $KEY" http://localhost:8000/admin/iaa/drift

JSON-отчёт содержит следующие поля:

  • 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, и каждый раунд сбрасывает эти ответы. Чтобы сохранить результаты первого прохождения обучения, сохраните копию каталога с результатами разметки перед раундом.

Дополнительное чтение

Подробности реализации см. в исходной документации.

Литература

  • 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)