# Scripts

Un script d'entraînement, de validation, d'inférence et de nettoyage des points de contrôle, inclus dans le dossier racine de GitHub. Les scripts ne sont actuellement pas inclus dans la publication pip.

Les scripts d’entraînement et de validation ont évolué à partir des premières versions des [PyTorch Imagenet Examples](https://github.com/pytorch/examples). J’y ai ajouté des fonctionnalités importantes au fil du temps, notamment des améliorations de performances spécifiques à CUDA basées sur les [NVIDIA's APEX Examples](https://github.com/NVIDIA/apex/tree/master/examples).

## Script d'entraînement

La variété des arguments d’entraînement est importante et toutes les combinaisons d’options (ou même certaines options) n’ont pas été entièrement testées. Pour le dossier du jeu de données d’entraînement, spécifiez le dossier de base contenant un dossier `train` et un dossier `validation`.

Pour une exécution sur un seul nœud, utilisez `torchrun` avec un processus par GPU. Par exemple, après avoir enregistré la [configuration MobileNetV4](hparams#mobilenetv4-conv-medium) sous le nom `mobilenetv4.yaml` :

```bash
torchrun --standalone --nproc_per_node=2 train.py \
  --config mobilenetv4.yaml --data-dir /data/imagenet --experiment mobilenetv4-cm
```

`./distributed_train.sh 2 ...` est un wrapper pratique autour de `torchrun`. Pour une seule GPU, utilisez `python train.py ...` et ajustez la taille du lot et le taux d’apprentissage comme décrit dans [HParams](hparams#adapting-a-recorded-config).

    `--amp` utilise la précision mixte native de PyTorch, avec float16 par défaut. Utilisez `--amp-dtype bfloat16` sur le matériel pris en charge. Les exemples ci-dessous utilisent l’AMP et le DDP natifs de PyTorch ; Apex n’est pas requis.

### Fichiers de configuration

Tout argument d’entraînement peut être fourni dans un fichier YAML via `--config`, en utilisant le nom de l’argument avec des tirets bas comme clé (`--weight-decay` devient `weight_decay`). Les arguments de la ligne de commande prennent le pas sur le fichier. Chaque exécution écrit l’ensemble des arguments effectifs dans `args.yaml` dans son dossier de sortie, et ce fichier est lui-même un `--config` valide, de sorte qu’une exécution peut être reproduite avec `python train.py --config output/train/my-run/args.yaml --experiment my-run-2`. Le fichier enregistre également `output`, `experiment` et `resume`, de sorte qu’en l’absence d’un nouveau nom d’expérience, la commande écrit dans le dossier d’origine, et avec `--resume auto` elle reprend l’exécution d’origine plutôt que de la répéter. Les clés de configuration sont validées par rapport aux arguments du script : les clés inconnues déclenchent un avertissement, tandis que les choix invalides ou les valeurs non numériques pour des arguments numériques provoquent une erreur.

### Reprise et points de contrôle des jalons

Chaque exécution écrit `last.pth.tar` (toujours l'époque la plus récente, avec l'état du modèle, de l'optimiseur, de l'EMA et de l'échelleur AMP), `model_best.pth.tar`, et un historique classé de fichiers `checkpoint-N.pth.tar` limité par `--checkpoint-hist`.

* `--resume PATH` restaure cet état et reprend le planning à partir de l'époque du point de contrôle. C'est la méthode habituelle pour démarrer un nouveau dossier d'expérience à partir d'un point de contrôle existant.
* `--resume auto` reprend sur place : avec `--output` et `--experiment` définis, il continue depuis `last.pth.tar` dans ce dossier d’expérience s’il existe, et démarre sinon une nouvelle exécution. La même commande peut être relancée après une interruption. L’historique des points de contrôle, le suivi de la meilleure métrique et `summary.csv` se poursuivent de manière cohérente, de sorte que l’éviction de `--checkpoint-hist` et `model_best` restent corrects après le redémarrage.
* `--stop-epoch N` arrête l’entraînement après `N` époques complétées sans modifier le plan défini par `--epochs`. La dernière époque est toujours évaluée et sauvegardée, ce qui permet d’entraîner un modèle par segments.
* `--save-epochs N [N ...]` conserve des instantanés immuables `milestone-N.pth.tar` (N étant le nombre d’époques terminées) en dehors de l’historique classé. Ils sont liés de manière dure à `last.pth.tar` lorsque le système de fichiers le permet, ce qui ne génère aucun espace supplémentaire, et une évaluation est forcée à ces époques.

Ensemble, ces éléments facilitent la réalisation manuelle d’expériences de type warmup-stable-decay (WSD) : entraînez une fois un tronc stable, puis dérivez des cooldowns indépendants à partir de ses jalons, en conservant l’état complet de l’optimiseur.

```bash
# stable trunk through 30 completed epochs, keeping milestones at 18, 24 and 30. The 1 epoch decay is never reached.
python train.py --config configs/vit.yaml --sched wsd --sched-on-updates --epochs 31 --decay-epochs 1 \
    --stop-epoch 30 --save-epochs 18 24 30 --output output/wsd --experiment stable --resume auto

# a 6 epoch 1-sqrt cooldown branched from epoch 24, i.e. a 30 epoch run that shares the first 24 epochs with the trunk
python train.py --config configs/vit.yaml --sched wsd --sched-on-updates --epochs 30 --decay-epochs 6 --lr-decay-fn 1-sqrt \
    --resume output/wsd/stable/milestone-24.pth.tar --output output/wsd --experiment decay-p24-d6
```

La décroissance occupe les `--decay-epochs` derniers de l’horaire, donc un refroidissement de `D` époques à partir de `P` époques complétées correspond à `--epochs P+D --decay-epochs D` (soustraire `--warmup-epochs` de `--epochs` lorsque `--warmup-prefix` est utilisé). `sweep.py` ci-dessous automatise cette opération.

## Balayages d'hyperparamètres

`sweep.py` (PyTorch 2.5 ou version ultérieure) planifie et exécute des balayages sur n’importe quels arguments de `train.py`, éventuellement avec un branchement WSD. Il génère un fichier `train.py --config` complet par tâche, ainsi qu’un `plan.json` contenant le graphe de dépendances des tâches, puis exécute les tâches prêtes sur un pool de GPU locaux. Le plan reste fixe pendant l’exécution des workers. Chaque tâche est une invocation ordinaire de `train.py` avec son propre dossier d’expérience, et chaque `run.sh` généré peut être exécuté manuellement ou confié à un autre planificateur.

Un balayage rapide ne nécessite pas de fichier de spécification. Les axes de recherche proviennent de `--sweep`, la configuration de base des arguments de `train.py` qui suivent `--` :

```bash
python sweep.py run --name lr-wd --sweep lr=1e-4,3e-4,1e-3 --sweep weight_decay=0.05,0.1 --gpus 0,1,2,3 \
    -- --model vit_small_patch16_224 --data-dir /data/imagenet --sched cosine --epochs 100 --amp
```

`--gpus` liste les GPU sur lesquels exécuter la tâche, en tant qu’indices dans `CUDA_VISIBLE_DEVICES` lorsque celui-ci est défini (par exemple, dans une allocation Slurm).

Un balayage WSD partage un tronc stable par combinaison d'hyperparamètres sur de nombreux cooldowns. Pour chaque taux d'apprentissage ci-dessous, le tronc s'exécute jusqu'à 30 époques avec des jalons à 18, 24 et 30, et un cooldown de 6 époques est dérivé de chaque jalon, soit 4 troncs et 12 cooldowns au total au lieu de 12 exécutions complètes. Les cooldowns démarrent dès que leur jalon existe, tandis que le tronc continue. Les tâches nécessitant plus d'une GPU sont lancées avec `torchrun` :

```bash
python sweep.py run --name vit-wsd --sweep lr=1e-3,5e-4,2e-4,1e-4 \
    --wsd-points 18 24 30 --wsd-decay-epochs 6 --wsd-decay-fn 1-sqrt --gpus-per-job 2 --gpus 0,1 \
    -- --model vit_small_patch16_224 --data-dir /data/imagenet --warmup-epochs 3 --warmup-prefix --amp
```

Le planificateur possède les drapeaux suivants, ils ne peuvent donc pas être fournis sur la ligne de commande ni balayés.

À chaque balayage :

* `--output`
* `--experiment`
* `--resume`
* `--stop-epoch`
* `--save-epochs`

De plus, dans un balayage WSD :

* `--sched` (peut être fourni sous la forme `wsd`, ce que le planificateur définit de toute façon)
* `--epochs`
* `--decay-epochs`
* `--lr-decay-fn`

Une spécification YAML ajoute la recherche aléatoire avec des distributions, des axes appariés (zippés), des répétitions sur des graines et un fichier de configuration de base, voir la docstring en haut de `sweep.py` pour le schéma. `python sweep.py plan spec.yaml` écrit les configurations et `graph.dot` sans rien exécuter.

Le balayage est enregistré dans `output/sweeps/<name>/`, par rapport au fichier de spécification (ou au répertoire de travail pour un balayage en ligne de commande), avec un dossier par essai, nommé d’après les valeurs balayées (`trial-000-lr0.001`), chacun contenant les dossiers d’expériences du job. Chaque point de contrôle conservé est une copie complète incluant l’état de l’optimiseur, de sorte que les jobs de balayage conservent un point de contrôle classé en plus de `last.pth.tar` et `model_best.pth.tar` (`--checkpoint-hist 1`), et les balayages WSD font avancer le planning à chaque mise à jour (`--sched-on-updates`). Définir l’un ou l’autre dans la configuration de base l’annule. Pendant son exécution, ou par la suite :

```bash
python sweep.py status output/sweeps/vit-wsd    # job states and progress
python sweep.py results output/sweeps/vit-wsd   # ranked endpoint metrics, written to results.csv
```

Les résultats comparent les essais à budgets égaux. Un balayage WSD affiche une ligne par essai et une colonne par cooldown (`p24-d6` correspond à un cooldown de 6 époques à partir de 24 époques) avec la métrique finale, moyennée sur les répétitions, et le meilleur de chaque colonne est marqué. Un balayage simple est classé selon sa métrique finale au sein de chaque budget d'époques. `results.csv` contient tous les jobs, segments de tronc inclus, avec la meilleure époque de chacun. En l'absence d'un ensemble de validation, le classement repose sur la perte d'entraînement, et les points de contrôle de fin peuvent être évalués avec `validate.py`.

L’exécution de `sweep.py run` à nouveau sur un balayage interrompu le reprend : les tâches terminées sont ignorées et une tâche dont l’exécution est partielle reprend à partir de son propre `last.pth.tar`. Les travailleurs sur plusieurs machines partageant le dossier de balayage peuvent chacun l’exécuter. Ils se coordonnent via des verrous `flock` sous `state/locks`, que chaque processus d’entraînement d’une tâche détient également, de sorte qu’une tâche dont le travailleur est mort n’est ni relancée ni replanifiée tant que son dernier processus n’a pas quitté. Un balayage partagé par plusieurs machines nécessite un système de fichiers dont le `flock` fonctionne entre toutes, tel que NFSv4 ou Lustre monté avec `-o flock`. Les montages avec un verrouillage uniquement local ne se coordonnent pas entre les machines, il convient donc de vérifier le verrouillage depuis chaque client. L’arrêt d’un travailleur (Ctrl-C, ou SIGTERM de `scancel`) arrête ses tâches, et l’exécution manuelle du `run.sh` d’une tâche contourne la coordination.

Les fichiers de verrou sous `state/locks` sont permanents : ne les supprimez ni ne les remplacez jamais pour effacer un balayage occupé. Arrêtez ou attendez plutôt que ses workers et processus d’entraînement se terminent. Un statut `running (gone)` signifie que le verrou d’entraînement est libre mais que la tentative n’a pas été réconciliée ; exécutez un worker pour le résoudre et reprendre le travail inachevé. `status` et `results` ne réparent pas l’état du job.

Un travailleur lit le plan une seule fois au démarrage. Pour modifier un balayage, arrêtez tous ses travailleurs et processus d’entraînement, planifiez à nouveau avec `--force`, puis relancez-les. Avant le démarrage de tout travail, tout élément du plan peut être modifié. Par la suite, ses travaux d’entraînement ne peuvent être qu’étendus : plus d’essais, de périodes de refroidissement ou de points WSD après les existants, chaque tronc reprenant là où il s’était arrêté. Les modifications des travaux d’entraînement existants nécessitent un nouveau balayage. Les chemins générés sont figés lors de l’écriture du plan ; planifiez donc à nouveau sous un nouvel emplacement plutôt que de déplacer le dossier du balayage.

Si la planification est interrompue, les workers détectent les configurations ou les launchers issus d’une écriture de plan incomplète et refusent de démarrer. Relancez la commande de planification d’origine avec `--force` pour terminer ou réparer le plan, puis redémarrez les workers. Chaque écriture de plan réussie publie une génération correspondante dans `plan.json`, ainsi que les configurations et les launchers ; un plan inchangé et complet n’écrit rien.

Par exemple, pour étendre le balayage WSD de l’interface en ligne de commande ci-dessus à 36 époques stables, arrêtez ses travailleurs et ses tâches, puis répétez les arguments de planification d’origine avec le point ultérieur ajouté :

```bash
python sweep.py plan --force --name vit-wsd --sweep lr=1e-3,5e-4,2e-4,1e-4 \
    --wsd-points 18 24 30 36 --wsd-decay-epochs 6 --wsd-decay-fn 1-sqrt --gpus-per-job 2 \
    -- --model vit_small_patch16_224 --data-dir /data/imagenet --warmup-epochs 3 --warmup-prefix --amp
python sweep.py run output/sweeps/vit-wsd --gpus 0,1
```

Pour un balayage YAML, ajoutez le point à la spécification d’origine et exécutez `python sweep.py plan spec.yaml --force` avant de redémarrer. Gardez `wsd.decay_epochs` explicite et inchangé (`[6]` dans cet exemple) : sa valeur par défaut est de 20 % du point final, de sorte que l’extension de l’horizon peut modifier les périodes de refroidissement existantes. Conservez les identifiants des essais existants ; réorganiser ou étendre les axes de la grille peut renuméroter les essais. Avec `lr_noise`, le calendrier du tronc dépend de son horizon, de sorte que l’ajout d’un point ultérieur à un balayage déjà démarré nécessite un nouveau balayage.

Les paramètres d’entraînement appartiennent au plan : utilisez le bloc `train` de la spécification, ou les arguments après `--` lors de la création d’un balayage CLI. Il n’existe pas de substitutions d’entraînement à l’exécution. Les paramètres de rapport, tels que la métrique de classement, et les paramètres de sortie (`checkpoint_hist`, `log_wandb`, `wandb_project`, `wandb_tags`) peuvent être modifiés hors ligne avec `--force` sans réinitialiser l’état du travail ni modifier l’identité de l’entraînement. Les paramètres de sortie s’appliquent aux tentatives futures ; les travaux terminés ne sont pas relancés.

`sweep.py` nécessite `flock` Unix : testé sur Linux, censé fonctionner mais non testé sur macOS. Windows natif n'est pas pris en charge ; utilisez WSL2. Les verrous ne survivent pas à une coupure réseau qui dépasse la durée d'un bail NFS : un processus déconnecté peut continuer à entraîner après avoir perdu ses verrous, ce qui permet à une autre machine de lancer une tentative chevauchante.

## Scripts de validation / inférence

Les scripts de validation et d'inférence sont similaires dans leur utilisation. L'un affiche des métriques sur un ensemble de validation, tandis que l'autre affiche les identifiants des classes topk dans un fichier csv. Spécifiez le dossier contenant les images de validation, et non le dossier de base comme dans le script d'entraînement.

Pour valider avec les poids pré-entraînés du modèle (s'ils existent) :

```bash
python validate.py --data-dir /imagenet/validation/ --model seresnext26_32x4d --pretrained
```

Pour exécuter une inférence à partir d'un point de contrôle :

```bash
python inference.py --data-dir /imagenet/validation/ --model mobilenetv3_large_100 --checkpoint ./output/train/model_best.pth.tar
```

## Exemples d'entraînement

### Recettes d'entraînement ImageNet

Ces commandes correspondent aux [recettes HParams sélectionnées](hparams#selected-recipes), où vous trouverez des configurations YAML copiables, des gists source et des résultats de points de contrôle publiés. Elles supposent des données ImageFolder sous `/data/imagenet/train/` et `/data/imagenet/validation/`. `--batch-size` est **par GPU**. Les taux d’apprentissage explicites dans les exemples MobileNetV4 et ConvNeXt s’appliquent aux tailles de lot globales indiquées ; ils ne sont pas mis à l’échelle automatiquement si le nombre de GPU change.

#### MobileNetV4 Conv Medium, ImageNet-1k

Depuis [mnv4_cm_r256_e500_gpu2.yaml](https://gist.github.com/rwightman/f6705cb65c03daeebca8aa129b1b94ad#file-mnv4_cm_r256_e500_gpu2-yaml) : 2 GPU, lot global de 1024, entraînement à 256 px, et 20 époques de montée en charge + 500 époques cosinus. Les poids correspondants sont [mobilenetv4_conv_medium.e500_r256_in1k](https://huggingface.co/timm/mobilenetv4_conv_medium.e500_r256_in1k).

```bash
torchrun --standalone --nproc_per_node=2 train.py --data-dir /data/imagenet \
  --model mobilenetv4_conv_medium.e500_r256_in1k --img-size 256 --crop-pct 0.95 --batch-size 512 \
  --opt adamw --lr 0.002 --weight-decay 0.1 --clip-grad 5.0 \
  --sched cosine --sched-on-updates --epochs 500 --warmup-epochs 20 --warmup-prefix \
  --warmup-lr 0 --min-lr 0 --cooldown-epochs 0 \
  --aa rand-m8-inc1-mstd1.0 --mixup 0.2 --mixup-prob 0.8 --smoothing 0.1 \
  --reprob 0.25 --remode pixel --drop 0.2 --drop-path 0.1 \
  --model-ema --model-ema-decay 0.9998 --model-ema-warmup --dist-bn reduce \
  --amp --amp-dtype bfloat16 --torchcompile inductor --experiment mobilenetv4-cm
```

Utilisez `--amp-dtype float16` si votre GPU ne prend pas en charge bfloat16 ; `--torchcompile inductor` est facultatif.

#### ViT Medium avec registres, ImageNet-1k

Depuis [train-vit_medium_reg4-4gpu-in1k.yaml](https://gist.github.com/rwightman/fb37c339efd2334177ff99a8083ebbc4#file-train-vit_medium_reg4-4gpu-in1k-yaml) : 4 GPU, lot global de 1024 et 600 époques incluant le réchauffement + 10 époques de refroidissement. Les poids correspondants sont [vit_medium_patch16_reg4_gap_256.sbb_in1k](https://huggingface.co/timm/vit_medium_patch16_reg4_gap_256.sbb_in1k).

```bash
torchrun --standalone --nproc_per_node=4 train.py --data-dir /data/imagenet \
  --model vit_medium_patch16_reg4_gap_256.sbb_in1k --model-kwargs fix_init=True --head-init-scale 0 \
  --img-size 256 --crop-pct 0.95 --batch-size 256 \
  --opt adamw --opt-eps 1e-8 --lr-base 0.0008 --lr-base-size 512 --lr-base-scale sqrt \
  --weight-decay 0.07 --clip-grad 5.0 --sched cosine --sched-on-updates \
  --epochs 600 --warmup-epochs 20 --warmup-lr 5e-7 --min-lr 5e-7 --cooldown-epochs 10 \
  --aa rand-m7-inc1-mstd1.0-n3 --aug-repeats 3 --color-jitter 0 \
  --mixup 0.8 --cutmix 1.0 --smoothing 0.1 --reprob 0.2 --remode pixel --drop-path 0.2 \
  --seed 0 --amp --amp-dtype float16 --torchcompile inductor --experiment vit-medium-sbb
```

Cette exécution utilise AdamW sans EMA. Son LR maximal est d'environ 0.001131 au lot global 1024.

#### ConvNeXt Tiny, fine-tuning d’ImageNet-12k à ImageNet-1k

Depuis [hparams_84_00-convnext_tiny.in12k-args.yaml](https://gist.github.com/rwightman/ee0b02c1e99a0761264d1d1319e95e5b#file-hparams_84_00-convnext_tiny-in12k-args-yaml) : 8 GPU, lot global de 4096 et 75 époques incluant le warmup + 10 époques de cooldown. Chargez les poids **ImageNet-12k** et définissez `--num-classes 1000` pour remplacer le classifieur en vue du fine-tuning. La famille de points de contrôle résultante est [convnext_tiny.in12k_ft_in1k](https://huggingface.co/timm/convnext_tiny.in12k_ft_in1k).

```bash
torchrun --standalone --nproc_per_node=8 train.py --data-dir /data/imagenet \
  --model convnext_tiny.in12k --pretrained --num-classes 1000 \
  --img-size 224 --crop-pct 1.0 --batch-size 512 --grad-checkpointing \
  --opt adamw --lr 1e-4 --weight-decay 0.05 --layer-decay 0.75 --clip-grad 3.0 \
  --sched cosine --epochs 75 --warmup-epochs 10 --warmup-lr 1e-6 --min-lr 5e-7 \
  --cooldown-epochs 10 --lr-noise 0.1 1.0 \
  --aa rand-m8-inc1-mstd101 --mixup 0.8 --cutmix 1.0 --smoothing 0.1 \
  --reprob 0.3 --remode pixel --drop-path 0.1 --model-ema --model-ema-decay 0.9998 \
  --amp --amp-dtype float16 --experiment convnext-tiny-ft
```

### Classification multi-étiquettes

Utilisez `--task multilabel` pour les images avec plusieurs étiquettes indépendantes.

* **`--num-classes`** définit la taille du vocabulaire.
* **`--target-format indices`** (par défaut) lit la colonne cible comme une liste d’identifiants de classes positifs, à base zéro. Les étiquettes sont converties en cibles multi-hot flottantes avant le groupement par lots, et les listes vides représentent des images sans étiquettes positives.
* **`--target-format multihot`** lit un vecteur multi-hot dense de longueur `--num-classes` (valeurs bool, int ou float dans `[0, 1]`). Le vecteur est utilisé tel quel, de sorte que les valeurs douces entraînent directement avec BCE et sont binarisées à 0,5 pour les métriques d’évaluation. Les cartes de classes pour les cibles multi-hot doivent être indexées par l’indice de classe source.

Sans ce drapeau, chaque liste est lue comme des indices de classes, car un vecteur multi-hot d'entiers est indistinguable d'une liste d'indices.

Le [jeu de données de pathologie végétale](https://huggingface.co/datasets/timm/plant-pathology-2021) fournit ce format : une colonne `image` et une colonne `labels` contenant une séquence de valeurs `ClassLabel`. Ses six classes sont `complex`, `frog_eye_leaf_spot`, `healthy`, `powdery_mildew`, `rust` et `scab`. Par exemple, `[5, 1, 0]` devient `[1, 1, 0, 0, 0, 1]`.

```bash
python train.py --dataset hfds/timm/plant-pathology-2021 \
  --task multilabel --target-key labels --num-classes 6 \
  --model resnet50 --pretrained --batch-size 32 --epochs 30 \
  --opt adamw --lr 1e-4 --weight-decay 0.01 --amp

python validate.py --dataset hfds/timm/plant-pathology-2021 \
  --task multilabel --target-key labels --num-classes 6 \
  --model resnet50 --checkpoint ./output/train/<experiment>/model_best.pth.tar
```

`hfids/timm/plant-pathology-2021` prend en charge le streaming avec le même format cible. Prise en charge des lecteurs :

* **Hugging Face `hfds` et `hfids`** prennent en charge les cibles multi-étiquettes.
* **WebDataset (`wds`)** prend en charge les listes d’identifiants de classes stockées dans le fichier annexe `.json` de chaque échantillon, sous le champ nommé par `--target-key`. En l’absence de `--target-key`, le fichier `.cls` fournit un seul index de classe.
* **Lecteurs de dossiers et de fichiers tar** renvoient des indices de classe uniques.

Les jeux de données qui stockent un champ binaire par étiquette, comme [Wake Vision](https://www.tensorflow.org/datasets/catalog/wake_vision), utilisent ces paramètres :

* **`--target-key person,depiction,body_part`** sélectionne une liste de champs séparés par des virgules. Chaque champ devient une classe dans l’ordre indiqué, et les valeurs supérieures à zéro sont comptées comme positives. Cela fonctionne avec les lecteurs `hfds`, `hfids` et `tfds`, ainsi qu’avec WebDataset lorsque les champs sont dans le fichier annexe `.json`.
* **`--num-classes`** doit être égal au nombre de champs.
* **`--target-format`** reste à sa valeur par défaut, car les clés multi-champs définissent leur propre format.
* **Les chemins d’accès aux champs imbriqués** utilisent `/`, par exemple `--input-key input --target-key output/pollen_output,output/wasps_output` pour le jeu de données TFDS `bee_dataset`. Les clés cibles uniques acceptent les mêmes chemins. Un nom de champ littéral contenant `/` (tel que `image/filename` dans TFDS) a la priorité sur un chemin.

Les chargeurs Standard et NaFlex prennent en charge les cibles multi-étiquettes, y compris mixup et cutmix ; l'entraînement NaFlex nécessite actuellement un jeu de données de type map tel que `hfds`.

La tâche s’entraîne avec l’entropie croisée binaire avec logits. Le lissage est par défaut nul pour l’entraînement multi-étiquettes ; définir `--smoothing s` déplace chaque cible binaire vers `target * (1 - s) + 0.5 * s`, appliqué par Mixup/CutMix lorsqu’ils sont actifs et par la fonction de perte dans le cas contraire. Les options existantes `--bce-sum` et `--bce-target-thresh` s’appliquent également. La distillation et la perte JSD ne sont pas prises en charge par cette tâche.

`--loss` sélectionne d’autres pertes (`bce` est la valeur par défaut pour les étiquettes multiples) :

* `asl` : [Asymmetric Loss](https://arxiv.org/abs/2009.14119). Elle suit le même comportement de lissage et de Mixup/CutMix que BCE et est calculée en float32, ce qui la rend sûre avec `--amp`.

  * **`--asl-gamma-neg`** définit le paramètre de focalisation pour les cibles négatives (valeur par défaut : 4).
  * **`--asl-gamma-pos`** définit le paramètre de focalisation pour les cibles positives (valeur par défaut : 1).
  * **`--asl-clip`** définit la marge de probabilité pour les négatifs (valeur par défaut 0,05).
  * **`--asl-reduction`** a par défaut la valeur `batchmean`, qui somme la perte sur les classes et fait la moyenne sur le lot. `sum` restaure la réduction ASL d'origine, qui somme également sur le lot, de sorte que le taux d'apprentissage effectif est proportionnel à la taille du lot.

* `twoway` : [Two-way Multi-Label Loss](https://staff.aist.go.jp/takumi.kobayashi/publication/2023/CVPR2023.pdf), qui contraste les logits positifs et négatifs entre les classes de chaque échantillon et les échantillons de chaque classe. Ses logits sont des scores de classement, la métrique mAP est donc la plus pertinente ; le F1 à un seuil sigmoïde n'est pas calibré.

  * **`--twoway-tp`** définit la température des logits positifs (valeur par défaut : 4).
  * **`--twoway-tn`** définit la température du logit négatif (valeur par défaut 1).

* `zlpr` : [ZLPR](https://arxiv.org/abs/2208.02955), une extension multi-étiquettes de l'entropie croisée softmax, sans hyperparamètre, avec 0 comme seuil de logit intégré.
* `db` : [Distribution-Balanced Loss](https://arxiv.org/abs/2007.09654) pour les données multi-étiquettes à longue traîne. BCE avec des poids de rééquilibrage par exemple qui tiennent compte de la co-occurrence des étiquettes, une régularisation tolérante aux négatifs des logits, et un terme focal. Il prend en charge Mixup/CutMix et le lissage, avec des valeurs par défaut issues de la configuration de référence DB :

  * **`--db-neg-scale`** définit l'échelle des logits négatifs (2 par défaut).
  * **`--db-init-bias`** définit le facteur de décalage des logits a priori de la classe (valeur par défaut 0.05).
  * **`--db-focal-gamma`** définit le focal focusing (valeur par défaut : 2).
  * **`--class-stats`** fournit les nombres de labels d’entraînement requis sous forme de fichier de statistiques de classes (ci-dessous).

`twoway` et `zlpr` nécessitent des cibles binaires, ils ne prennent donc pas en charge Mixup/CutMix ni `--smoothing`.

Pour les distributions de labels déséquilibrées, `bce` et `asl` prennent en charge deux drapeaux de pondération :

* **`--loss-pos-weight`** pondère le terme positif de chaque classe, équilibrant les positifs par rapport aux négatifs au sein d'une classe. Cela déplace également les prédictions vers le positif (rappel plus élevé à un seuil fixe, probabilités moins calibrées).
* **`--loss-class-weight`** pondère la perte globale de chaque classe, équilibrant ainsi les classes entre elles tout en préservant la calibration.

Chaque drapeau accepte des poids explicites (une valeur unique, une liste séparée par des virgules avec une valeur par classe, ou un fichier) ou le nom d'une méthode qui dérive les poids à partir des compteurs d'étiquettes d'entraînement fournis avec `--class-stats` :

* `--loss-pos-weight neg_pos` : négatifs / positifs par classe.
* `--loss-class-weight inv_freq` : fréquence inverse de classe, normalisée pour une moyenne de 1.
* `--loss-class-weight effective_num` : pondérations [class-balanced](https://arxiv.org/abs/1901.05555) issues du nombre effectif d'échantillons, normalisées pour une moyenne de 1.

Options de poids dérivés :

* **`--loss-weight-power`** définit l'exposant pour `neg_pos` et `inv_freq` (valeur par défaut 1), avec 0,5 pour un ré-pondération plus douce par racine carrée.
* **`--loss-weight-beta`** définit beta pour les poids de `effective_num` (valeur par défaut 0.999).
* **`--loss-weight-max`** limite les poids dérivés.

Les deux drapeaux de pondération peuvent utiliser les mêmes statistiques. `class_weights.py` compte les étiquettes d’un split, avec les mêmes arguments de jeu de données que `train.py`, et écrit le fichier de statistiques des classes (`--method` prévisualise les poids d’une méthode) :

```bash
python class_weights.py --dataset hfds/timm/plant-pathology-2021 --split train \
  --task multilabel --target-key labels --num-classes 6 --output stats.json

python train.py --dataset hfds/timm/plant-pathology-2021 \
  --task multilabel --target-key labels --num-classes 6 \
  --class-stats stats.json --loss-pos-weight neg_pos --loss-weight-power 0.5 ...
```

Drapeaux obsolètes :

| Ancien drapeau | Remplacement |
|---|---|
| `--bce-loss` | `--loss bce` |
| `--jsd-loss` | `--loss jsd` |
| `--bce-pos-weight` | `--loss-pos-weight` |

Les rapports de validation affichent la perte BCE, `map` (précision moyenne moyenne), `micro_f1`, `macro_f1` et `sample_f1`. Les métriques sont exprimées en pourcentage et utilisent l’ensemble de validation complet, y compris les prédictions collectées sur les rangs distribués. Le bourrage du sampler distribué ordonné est exclu. Le mAP exact conserve les prédictions sur le CPU jusqu’à la fin de l’évaluation, si bien que son utilisation mémoire augmente avec le nombre d’exemples et de classes. Les classes sans exemples positifs contribuent à un AP nul ; les valeurs F1 avec un dénominateur nul sont nulles.

`--eval-metric map` est la valeur par défaut pour la sélection des points de contrôle et la planification. F1 utilise un seuil de probabilité sigmoïde de 0,5, configurable avec `--multilabel-threshold` ; mAP est indépendant de ce seuil. Les sorties du modèle restent des logits, il convient donc d’appliquer une sigmoïde pour obtenir des probabilités de classes indépendantes lors de l’utilisation du modèle entraîné.

### Recettes ImageNet historiques

Ces exécutions antérieures restent des références utiles pour l’entraînement avec RMSProp, SGD et AugMix/JSD. Le matériel et les métriques rapportées décrivent les exécutions d’origine. Pour les recettes AdamW plus récentes, commencez par les exemples ci-dessus ou les [collections de gists](hparams#config-file-gists).

#### EfficientNet-B2 avec RandAugment - 80,4 top-1, 95,1 top-5

L'exécution d'origine utilisait des cartes Titan RTX doubles avec NVIDIA Apex ; l'option `--amp` actuelle utilise l'AMP natif de PyTorch :

```bash
./distributed_train.sh 2 --data-dir /imagenet/ --model efficientnet_b2 -b 128 --sched step --epochs 450 --decay-epochs 2.4 --decay-rate .97 --opt rmsproptf --opt-eps .001 -j 8 --warmup-lr 1e-6 --weight-decay 1e-5 --drop 0.3 --drop-path 0.2 --model-ema --model-ema-decay 0.9999 --aa rand-m9-mstd0.5 --remode pixel --reprob 0.2 --amp --lr .016
```

#### MixNet-XL avec RandAugment - 80,5 top-1, 94,9 top-5

L'exécution d'origine utilisait des cartes Titan RTX doubles avec NVIDIA Apex ; l'option `--amp` actuelle utilise l'AMP natif de PyTorch :

```bash
./distributed_train.sh 2 --data-dir /imagenet/ --model mixnet_xl -b 128 --sched step --epochs 450 --decay-epochs 2.4 --decay-rate .969 --opt rmsproptf --opt-eps .001 -j 8 --warmup-lr 1e-6 --weight-decay 1e-5 --drop 0.3 --drop-path 0.2 --model-ema --model-ema-decay 0.9999 --aa rand-m9-mstd0.5 --remode pixel --reprob 0.3 --amp --lr .016 --dist-bn reduce
```

#### SE-ResNeXt-26-D et SE-ResNeXt-26-T

Ces hparams (ou similaires) fonctionnent bien pour une large gamme d’architectures ResNet. Il est généralement judicieux d’augmenter le nombre d’époques à mesure que la taille du modèle augmente, soit environ 180-200 pour ResNe(X)t50, et 220+ pour les modèles plus grands. Augmentez la taille du lot et le LR de manière proportionnelle pour de meilleures GPU ou avec AMP activé. Ces paramètres étaient destinés à 2 cartes 1080Ti :

```bash
./distributed_train.sh 2 --data-dir /imagenet/ --model seresnext26t_32x4d --lr 0.1 --warmup-epochs 5 --epochs 160 --weight-decay 1e-4 --sched cosine --reprob 0.4 --remode pixel -b 112
```
#### EfficientNet-B3 avec RandAugment - 81,5 top-1, 95,7 top-5

L'entraînement de ce modèle a démarré avec la même ligne de commande que EfficientNet-B2 w/ RA ci-dessus. Après près de trois semaines d'entraînement, le processus a planté. Les résultats n'étaient pas exceptionnels, j'ai donc repris l'entraînement à plusieurs reprises en ajustant quelques paramètres (augmentation de la probabilité RE, diminution de rand-aug, augmentation de ema-decay). Rien ne semblait concluant. J'ai fini par faire la moyenne des meilleurs checkpoints de toutes les reprises. Le résultat est médiocre avec la résolution/crop par défaut, mais surprenamment bien meilleur avec un crop de test sur l'image complète de 1.0.

#### EfficientNet-B0 avec RandAugment - 77,7 top-1, 95,3 top-5

[Michael Klachko](https://github.com/michaelklachko) a obtenu ces résultats avec la ligne de commande pour B2 adaptée à une taille de lot plus grande, avec le taux de dropout B0 recommandé de 0,2.

```bash
./distributed_train.sh 2 --data-dir /imagenet/ --model efficientnet_b0 -b 384 --sched step --epochs 450 --decay-epochs 2.4 --decay-rate .97 --opt rmsproptf --opt-eps .001 -j 8 --warmup-lr 1e-6 --weight-decay 1e-5 --drop 0.2 --drop-path 0.2 --model-ema --model-ema-decay 0.9999 --aa rand-m9-mstd0.5 --remode pixel --reprob 0.2 --amp --lr .048
```
#### ResNet50 avec la perte JSD et RandAugment (propre + 2x RA augs) - 79,04 top-1, 94,39 top-5

Entraîné sur deux cartes 1080Ti plus anciennes, ce processus a pris un certain temps. Le résultat de validation sur ImageNet n'est que légèrement, et non statistiquement, meilleur que mon premier bon entraînement AugMix à 78,99. Cependant, ces poids sont plus robustes lors des tests sur ImageNetV2, ImageNet-Sketch, etc. Contrairement à mes premières exécutions AugMix, j'ai activé SplitBatchNorm, désactivé l'effacement aléatoire sur le split propre et augmenté la probabilité d'effacement aléatoire sur les 2 chemins augmentés.

```bash
./distributed_train.sh 2 --data-dir /imagenet -b 64 --model resnet50 --sched cosine --epochs 200 --lr 0.05 --amp --remode pixel --reprob 0.6 --aug-splits 3 --aa rand-m9-mstd0.5-inc1 --resplit --split-bn --loss jsd --dist-bn reduce
```
#### EfficientNet-ES (EdgeTPU-Small) avec RandAugment - 78,066 top-1, 93,926 top-5

Entraîné par [Andrew Lavin](https://github.com/andravin) avec 8 cartes V100. L’EMA du modèle n’a pas été utilisé ; le point de contrôle final est la moyenne des 8 meilleurs points de contrôle obtenus pendant l’entraînement.

```bash
./distributed_train.sh 8 --data-dir /imagenet --model efficientnet_es -b 128 --sched step --epochs 450 --decay-epochs 2.4 --decay-rate .97 --opt rmsproptf --opt-eps .001 -j 8 --warmup-lr 1e-6 --weight-decay 1e-5 --drop 0.2 --drop-path 0.2  --aa rand-m9-mstd0.5 --remode pixel --reprob 0.2 --amp --lr .064
```
#### MobileNetV3-Large-100 - 75,766 top-1, 92,542 top-5

```bash
./distributed_train.sh 2 --data-dir /imagenet/ --model mobilenetv3_large_100 -b 512 --sched step --epochs 600 --decay-epochs 2.4 --decay-rate .973 --opt rmsproptf --opt-eps .001 -j 7 --warmup-lr 1e-6 --weight-decay 1e-5 --drop 0.2 --drop-path 0.2 --model-ema --model-ema-decay 0.9999 --aa rand-m9-mstd0.5 --remode pixel --reprob 0.2 --amp --lr .064 --lr-noise 0.42 0.9
```

#### ResNeXt-50 32x4d avec RandAugment - 79.762 top-1, 94.60 top-5
Ces paramètres fonctionneront également bien avec SE-ResNeXt-50 et SK-ResNeXt-50, et probablement 101. Je les ai utilisés pour le SK-ResNeXt-50 32x4d que j'ai entraîné avec 2 GPU en utilisant un LR légèrement plus élevé par taille de lot effective (lr=0.18, b=192 par GPU). La ligne de commande ci-dessous est ajustée pour un entraînement sur 8 GPU.

```bash
./distributed_train.sh 8 --data-dir /imagenet --model resnext50_32x4d --lr 0.6 --warmup-epochs 5 --epochs 240 --weight-decay 1e-4 --sched cosine --reprob 0.4 --recount 3 --remode pixel --aa rand-m7-mstd0.5-inc1 -b 192 -j 6 --amp --dist-bn reduce
```

