Skip to content
Tutoriels11 min de lecture

Tâches de comparaison et de préférence d'images

Construisez dans Potato une tâche de comparaison d'images avec deux images côte à côte, un schéma de préférence, un ordre de présentation aléatoire et une configuration de classement pour les ensembles plus grands.

Potato Team

Ce tutoriel construit dans Potato une tâche de comparaison d'images avec deux images générées côte à côte, une échelle de préférence à cinq points, des questions de suivi par critère, ainsi que les variantes de classement et de Best-Worst Scaling pour les ensembles de plus de deux images. La même construction couvre les données de préférence pour le RLHF, les comparaisons de compression et d'amélioration, et les tests A/B de design.

Une comparaison demande à l'annotateur de choisir entre deux images plutôt que de placer une image sur une échelle, ce qui dispense tous les annotateurs de partager un même étalonnage de ce que signifie un 4 sur 5. La contrepartie est qu'un ensemble de n images demande beaucoup plus de jugements pour produire un ordre complet, et c'est ce que traitent les sections sur le classement et le Best-Worst Scaling à la fin.

Le fichier de données

Chaque ligne contient un identifiant, les chemins des deux images et le contexte dont l'annotateur a besoin pour les juger. Potato lit les noms des champs depuis item_properties, donc c'est vous qui choisissez ces noms :

json
{"id": "pair_001", "prompt": "A sunset over mountains", "image_a": "/media/model_a_001.png", "image_b": "/media/model_b_001.png"}
{"id": "pair_002", "prompt": "A cat on a windowsill", "image_a": "/media/model_a_002.png", "image_b": "/media/model_b_002.png"}

Afficher deux images côte à côte

Dans Potato, le contenu et les questions se configurent séparément. Le bloc instance_display indique ce que l'annotateur regarde, et annotation_schemes ce qu'on lui demande. Deux champs image avec une disposition horizontale placent les images l'une à côté de l'autre, et zoomable permet à l'annotateur d'inspecter des artefacts invisibles à la taille d'affichage :

yaml
instance_display:
  layout:
    direction: horizontal
    gap: 24px
  fields:
    - key: image_a
      type: image
      label: "Image A"
      display_options:
        max_height: 512
        zoomable: true
        alt_text: "First generated image"
    - key: image_b
      type: image
      label: "Image B"
      display_options:
        max_height: 512
        zoomable: true
        alt_text: "Second generated image"

Voici ce que voit l'annotateur une fois l'affichage et le schéma configurés :

Deux images côte à côte avec une échelle de préférence en dessousUne tâche de comparaison montrant les deux candidats avec la question de préférence en dessous

La question de préférence

Une échelle à cinq points distingue une préférence forte d'une préférence faible. Cela compte quand vous entraînez un modèle sur le résultat, car une paire sur laquelle les annotateurs se partagent à parts égales porte un signal d'entraînement différent de celui d'une paire qu'ils ont tous tranchée nettement. sequential_key_binding associe aux cinq options les touches 1 à 5, et c'est ce qui sépare une comparaison qui prend quatre secondes d'une comparaison qui en prend douze.

yaml
annotation_schemes:
  - annotation_type: radio
    name: preference
    description: "Which image better matches the prompt?"
    labels:
      - name: "A is clearly better"
      - name: "A is slightly better"
      - name: "About equal"
      - name: "B is slightly better"
      - name: "B is clearly better"
    sequential_key_binding: true
    label_requirement:
      required: true

Ajoutez ce bloc à la configuration ci-dessus. Faites en sorte que l'option centrale dise "About equal" plutôt que "No preference", car les deux décrivent des jugements différents et les annotateurs se servent d'une option « sans préférence » comme d'un bouton pour passer.

Biais de position

Les annotateurs choisissent l'option de gauche plus souvent qu'ils ne le devraient, donc le côté où tombe une image doit varier. Rendez-le aléatoire en construisant le fichier de données : échangez image_a et image_b pour une moitié aléatoire des lignes, notez le sens de chaque ligne, puis remettez les jugements enregistrés dans le bon sens avant de les analyser. Les champs d'affichage de Potato s'affichent dans l'ordre où vous les listez, donc la configuration ne peut pas le faire à votre place.

Les comparaisons de texte fonctionnent autrement. Le schéma pairwise tire ses deux candidats d'un seul champ des données de l'item, donc randomize_order peut les permuter, et Potato enregistre l'ordre que chaque annotateur a réellement vu. La graine est dérivée à la fois de l'annotateur, de l'item et du schéma, et c'est ce qui fait que la préférence d'une personne pour la première position tombe sur des candidats différents d'un item à l'autre, au lieu d'être corrélée à un même côté pendant toute l'étude. Consultez la documentation de la comparaison par paires quand les éléments comparés sont des textes.

Poser des questions sur des critères précis

Une préférence globale vous dit quelle image l'a emporté, et rien sur la raison. Ajouter en dessous deux ou trois questions ciblées rend les désaccords lisibles, car des annotateurs divisés sur le jugement global s'accordent souvent sur la fidélité au prompt et divergent sur l'esthétique.

yaml
  - annotation_type: radio
    name: prompt_adherence
    description: "Which image better matches the prompt?"
    labels:
      - name: A
      - name: Tie
      - name: B
 
  - annotation_type: multiselect
    name: issues_a
    description: "Problems in Image A (select all that apply)"
    labels:
      - name: "Distorted faces or hands"
      - name: "Text rendering errors"
      - name: "Unnatural lighting"
      - name: "Missing elements from the prompt"
      - name: "Blurry or low quality"
      - name: "None"

Ajoutez ces schémas au bloc ci-dessus. Gardez une liste de critères courte. Chaque question supplémentaire se multiplie sur toute l'étude, et un formulaire à cinq critères sur 2 000 paires représente 10 000 jugements qui demandent chacun leur propre définition dans les instructions.

Comparaison avant/après

Les tâches d'amélioration, de restauration et d'édition utilisent la même construction à deux images, avec des côtés nommés plutôt que tirés au hasard, car savoir quel côté est l'original fait partie de la question :

yaml
instance_display:
  layout:
    direction: horizontal
    gap: 24px
  fields:
    - key: original
      type: image
      label: "Original"
      display_options:
        max_height: 512
        zoomable: true
    - key: enhanced
      type: image
      label: "Enhanced"
      display_options:
        max_height: 512
        zoomable: true
 
annotation_schemes:
  - annotation_type: radio
    name: enhancement_quality
    description: "How did the edit change the image?"
    labels:
      - name: "Clearly improved"
      - name: "Slightly improved"
      - name: "No noticeable change"
      - name: "Made worse"
    label_requirement:
      required: true
 
  - annotation_type: multiselect
    name: problems_introduced
    description: "Problems the edit introduced (select all that apply)"
    labels:
      - name: "Over-sharpening or halos"
      - name: "Color shift"
      - name: "Loss of detail"
      - name: "New artifacts"
      - name: "None"

Posez la question des problèmes introduits séparément de celle de la qualité globale. Une retouche qui accentue la netteté du sujet et ajoute des halos autour se lit comme une amélioration sur une échelle unique, alors que ce sont les halos qu'il vous faut connaître.

Plus de deux images

Pour trois à huit candidats par item, affichez-les dans un champ gallery et recueillez un ordre complet avec le schéma ranking. La galerie prend une liste de chemins d'images depuis un seul champ, donc le fichier de données contient un tableau plutôt que deux clés séparées :

yaml
item_properties:
  id_key: id
  text_key: prompt
 
instance_display:
  fields:
    - key: prompt
      type: text
      label: "Prompt"
    - key: images
      type: gallery
      display_options:
        layout: grid
        columns: 2
        thumbnail_size: 320
        zoomable: true
 
annotation_schemes:
  - annotation_type: ranking
    name: preference_rank
    description: "Drag the image numbers to rank them from best at the top to worst at the bottom."
    labels:
      - "Image 1"
      - "Image 2"
      - "Image 3"
      - "Image 4"
    allow_ties: false
    label_requirement:
      required: true

Les labels nomment les positions dans la galerie, donc la liste doit comporter une entrée par image et chaque item doit avoir le même nombre de candidats. Le classement convient aux petits ensembles, car l'annotateur compare chaque candidat à tous les autres en même temps. Au-delà d'environ huit candidats, l'effort par item croît plus vite que l'information que vous en retirez.

Ensembles plus grands

Le Best-Worst Scaling est l'option à retenir quand l'ensemble est trop grand pour être classé sur un seul écran. Les annotateurs voient un tuple d'éléments, en général quatre, et ne choisissent que le meilleur et le pire, puis les scores sont calculés sur de nombreux tuples qui se chevauchent :

yaml
  - annotation_type: bws
    name: quality
    description: "Select the BEST and the WORST image in this set."
    tuple_size: 4
    best_description: "Best"
    worst_description: "Worst"

Ajoutez ce schéma à la configuration ci-dessus. Le calcul des scores se fait après la collecte, depuis l'onglet BWS du tableau de bord d'administration ou en ligne de commande, et écrit bws_scores.tsv dans le répertoire de sortie :

bash
python -m potato.bws_scoring --config config.yaml --method bradley_terry

La méthode counting ne demande aucune dépendance supplémentaire et sert de référence transparente. bradley_terry ajuste un modèle sur les comparaisons par paires implicites et nécessite pip install choix. plackett_luce modélise directement chaque choix du meilleur et du pire, et ne demande rien d'autre que Potato lui-même. La documentation du Best-Worst Scaling couvre la génération des tuples, qui doit avoir lieu avant tout cela.

Instructions et contrôles d'attention

Précisez dans les instructions ce que « meilleur » veut dire pour cette tâche, faute de quoi les annotateurs apportent leur propre définition et vous le découvrez au moment de mesurer l'accord. annotation_instructions place le texte sur chaque page d'annotation :

yaml
annotation_instructions: |
  Judge each pair on how well it matches the prompt first, then on visual
  quality, then on how appealing it looks. Zoom in before deciding: hands,
  text, and fine texture are where generation failures show up. Mark "About
  equal" only when you have looked at both and cannot separate them.

Pour une tâche de comparaison, les contrôles d'attention sont des paires dont le gagnant est évident, mêlées à la file depuis leur propre fichier afin de rester hors de votre jeu de données :

yaml
attention_checks:
  enabled: true
  items_file: "data/attention_checks.json"
  frequency: 20
  min_response_time: 3.0
  failure_handling:
    warn_threshold: 2
    block_threshold: 5

Ajoutez les deux blocs à la configuration ci-dessus. min_response_time repère le mode d'échec auquel une tâche de comparaison est la plus exposée : un annotateur qui clique à un rythme qui ne lui laisse pas le temps d'avoir regardé la seconde image.

La configuration complète

Cette configuration exécute une collecte de préférences pour le RLHF avec deux sorties de modèle par item, une préférence à cinq points, une question de critère et une note de confiance. Trois annotateurs voient chaque paire, ce qui suffit pour rapporter l'accord et retenir une étiquette majoritaire.

yaml
annotation_task_name: "Generative Model Comparison"
task_description: "Compare two generated images against the prompt that produced them."
 
data_files:
  - data/model_outputs.json
 
item_properties:
  id_key: id
  text_key: prompt
 
instance_display:
  layout:
    direction: horizontal
    gap: 24px
  fields:
    - key: image_a
      type: image
      label: "Image A"
      display_options:
        max_height: 512
        zoomable: true
        alt_text: "First generated image"
    - key: image_b
      type: image
      label: "Image B"
      display_options:
        max_height: 512
        zoomable: true
        alt_text: "Second generated image"
 
annotation_instructions: |
  Judge each pair on prompt match first, then visual quality, then appeal.
  Zoom in before deciding. Mark "About equal" only when you cannot separate them.
 
annotation_schemes:
  - annotation_type: radio
    name: preference
    description: "Which image better represents the prompt?"
    labels:
      - name: "A is clearly better"
      - name: "A is slightly better"
      - name: "About equal"
      - name: "B is slightly better"
      - name: "B is clearly better"
    sequential_key_binding: true
    label_requirement:
      required: true
 
  - annotation_type: radio
    name: prompt_adherence
    description: "Which image matches the prompt more closely?"
    labels:
      - name: A
      - name: Tie
      - name: B
 
  - annotation_type: likert
    name: confidence
    description: "How confident are you in this judgment?"
    size: 5
    min_label: "Guessing"
    max_label: "Certain"
 
attention_checks:
  enabled: true
  items_file: "data/attention_checks.json"
  frequency: 20
  min_response_time: 3.0
  failure_handling:
    warn_threshold: 2
    block_threshold: 5
 
num_annotators_per_item: 3
output_annotation_dir: annotation_output/image-comparison/
export_annotation_format: jsonl

Lancez-la avec potato start config.yaml -p 8000, ouvrez le port dans un navigateur et connectez-vous pour vérifier que la paire s'affiche comme prévu avant d'envoyer le lien à qui que ce soit. Annotez aussi quelques items vous-même, car un schéma qui paraît clair dans la configuration se révèle souvent ambigu dès la troisième vraie paire.

Ce que contient la sortie

Potato écrit des fichiers par annotateur sous output_annotation_dir, et export_annotation_format ajoute un export périodique sous <output_annotation_dir>/exports/<format>/. Chaque enregistrement exporté indique l'instance, l'annotateur et l'étiquette choisie pour chaque schéma :

json
{"instance_id": "pair_001", "user_id": "rater_01", "labels": {"preference": {"A is slightly better": "A is slightly better"}, "confidence": {"4": "4"}}, "spans": {}, "links": {}}

Rejoignez-le à votre fichier de données sur instance_id pour retrouver quel modèle a produit quel côté, et annulez l'échange gauche-droite appliqué lors de la construction du fichier avant d'agréger quoi que ce soit. Faire cette étape en dernier, plutôt que dans la configuration d'annotation, est ce qui préserve l'enregistrement brut de ce que chaque annotateur a réellement vu.

Prochaines étapes