Skip to content
Guides6 min read

Oltre la sovrapposizione totale: copertura adattiva degli annotatori per grandi dataset

Etichettare tutto in doppio costa caro; etichettare tutto una sola volta ti lascia cieco. Potato 2.6 ti consente di assegnare un annotatore alla maggior parte degli item e tre a un campione stratificato, con potenziamenti adattivi e instradamento automatico all'arbitrato.

Potato Team

In ogni progetto di annotazione di una certa dimensione c'è una tensione ricorrente. Se ogni item riceve due o tre annotatori, puoi misurare l'accordo e fidarti delle tue etichette, ma hai appena moltiplicato il budget per due o per tre. Se ogni item riceve un solo annotatore, etichetti tre volte più dati con la stessa spesa e non hai la minima idea di quanto ciascuno sia affidabile.

Il solito compromesso è ben noto a chiunque abbia condotto uno studio: annotare in singolo gran parte del corpus e annotare in doppio o in triplo un piccolo campione per tenere d'occhio la qualità. Il problema è sempre stato far sì che lo strumento lo facesse in modo pulito, e poi fare qualcosa della sovrapposizione una volta ottenuta. Potato 2.6 integra questo design alla radice, attraverso due blocchi di configurazione (num_annotators_per_item e per_annotator_quota) oltre ai potenziamenti adattivi e all'instradamento all'arbitrato.

Questo articolo ripercorre il design della copertura, dal caso semplice a quello adattivo. La documentazione sulla copertura eterogenea contiene il riferimento completo.

Copertura singola predefinita, un campione di sovrapposizione stratificato a tre annotatori, potenziamenti adattivi in caso di disaccordo e instradamento all'arbitratoCopertura adattiva degli annotatori in Potato

Limiti per item con un campione di sovrapposizione

num_annotators_per_item accetta un singolo intero come limite uniforme, oppure una mappatura strutturata quando vuoi coprire item diversi in modo diverso. La forma comune è un valore predefinito di uno, con un campione stratificato portato a tre:

yaml
num_annotators_per_item:
  default: 1
  overlap_sample:
    fraction: 0.1
    count: 3
    stratify_by: domain
    seed: 42
  min: 1

Il blocco overlap_sample alza il limite su un sottoinsieme deterministico di item. Il campionamento avviene una sola volta all'avvio, e gli item scelti vengono marcati internamente, così la logica di assegnazione li tratta da quel momento come ad alta copertura. I campi sono lineari: fraction è la proporzione campionata, count è il limite alzato (deve superare il valore predefinito) e seed rende la scelta riproducibile tra un riavvio e l'altro.

Il dettaglio su cui vale la pena soffermarsi è stratify_by. Puntalo su un campo dei tuoi dati (domain qui) e la proporzione si applica per strato anziché sull'intero pool. Ogni categoria contribuisce al campione di sovrapposizione in modo proporzionale, così non misuri l'accordo su un campione che per caso è al 90% di un solo dominio. Se il tuo corpus mescola notizie, social media e testi clinici, ciascuno compare nel campione di qualità in proporzione alla sua dimensione.

Potenziamento adattivo: spendere di più dove è difficile

Un campione di sovrapposizione fisso viene scelto alla cieca, prima che qualcuno abbia annotato alcunché. Ma gli item che più hanno bisogno di un secondo e un terzo sguardo sono quelli su cui gli annotatori sono effettivamente in disaccordo, e quali siano lo scopri solo dopo il primo passaggio. Il potenziamento adattivo si occupa esattamente di questo:

yaml
num_annotators_per_item:
  default: 1
  adaptive:
    enabled: true
    disagreement_threshold: 0.5
    boost_to: 3

Non appena un item ha almeno due annotazioni e il suo punteggio di disaccordo supera disagreement_threshold, il suo limite viene alzato a boost_to e l'item rientra nella coda di assegnazione per un altro passaggio. Il potenziamento avviene una sola volta per item, così un item controverso riceve una singola escalation invece di andare fuori controllo. È una copertura che segue la difficoltà dei dati anziché tirarla a indovinare in anticipo.

Quote per annotatore

I limiti di copertura controllano quanti annotatori riceve ciascun item. Un blocco separato controlla quanti item riceve ciascun annotatore, cosa che di solito si vuole far variare in base alla competenza o al contratto:

yaml
per_annotator_quota:
  default: 100
  by_user:
    alice: 30
  by_user_role:
    expert: 30
    novice: 200
 
user_roles:
  alice: expert
  carol: novice

La risoluzione procede dal più specifico: prima by_user[uid], poi by_user_role[user_roles[uid]], infine default. Così puoi limitare un esperto specifico a 30 item, ogni altro esperto a 30 in base al ruolo e i principianti a 200, senza che i due sistemi interferiscano con i limiti per item visti sopra.

Trasformare la sovrapposizione in una decisione

Raccogliere sovrapposizione è solo metà del lavoro; il punto è agire sui disaccordi. Con il blocco di arbitrato abilitato, gli item del campione di sovrapposizione che raggiungono il loro limite vengono valutati automaticamente e spinti in una coda di arbitrato quando l'accordo scende sotto la tua soglia:

yaml
adjudication:
  enabled: true
  adjudicator_users: [admin]
  min_annotations: 2
  agreement_threshold: 0.75

L'effetto è che gli item a basso accordo emergono nel momento stesso in cui il campione si satura, anziché aspettare che qualcuno si ricordi di ricostruire la coda a mano. Un arbitro apre la coda e vede gli item realmente contesi, già filtrati dalla massa su cui tutti erano d'accordo.

Leggere l'accordo

Una volta che gli item del campione di sovrapposizione si saturano, le statistiche di accordo sono disponibili su /admin/iaa. L'endpoint calcola la metrica adatta al tipo di ciascuno schema invece di imporre un unico numero a tutto: kappa di Cohen e di Fleiss per gli schemi nominali, weighted kappa per quelli ordinali e kappa a livello di token più span F1 per gli span. Questo conta, perché un κ calcolato come se le tue valutazioni Likert ordinali fossero categorie non ordinate sottostimerebbe l'accordo reale.

Per provarlo

Con la release viene fornita una dimostrazione eseguibile. Dalla radice del repository:

bash
python potato/flask_server.py start examples/advanced/heterogeneous-coverage/config.yaml -p 8000

Usa 20 item su due domini, campiona il 20% per una sovrapposizione a tre annotatori stratificata per dominio, abilita un potenziamento adattivo a soglia 0,5, definisce due livelli di competenza e instrada gli item a basso accordo verso l'arbitrato: l'intero design qui sopra, dall'inizio alla fine.

La forma di un buon piano di copertura

Nel complesso, il design ti permette di decidere dove va il tuo budget di annotazione invece di spalmarlo in modo uniforme. La maggior parte degli item riceve un passaggio. Una fetta stratificata ne riceve tre, così puoi riferire l'affidabilità sull'intero corpus e non su un solo suo angolo. Gli item che si rivelano davvero difficili vengono escalati automaticamente, e quelli contesi vengono instradati a un arbitro. Spendi di più sui dati più incerti e puoi difendere ogni decisione di copertura in una sezione metodi.

Quanti annotatori ti servano davvero per un dato compito è una questione a sé; l'articolo quanti annotatori ti servono passa in rassegna le regole pratiche. Questa release riguarda il rendere facile da esprimere qualunque risposta tu raggiunga. La copertura eterogenea è inclusa in Potato 2.6; consulta la documentazione sulla copertura eterogenea e il riferimento sull'assegnazione dei compiti per tutto ciò che i blocchi qui sopra possono fare.