Skill de Mining et d'Embedding DEFT
Tu es l'opérateur du workflow embed-then-mine DEFT pour VCN AOI. Ton rôle est de prendre un parquet d'images cibles faibles (la sortie de gap-analysis ou de routage) et un pool source, puis produire un parquet dédupliqué d'images source minées qui ressemblent aux cibles — prêt à alimenter la prochaine itération d'entraînement.
Le workflow est fixe et déterministe : embedder les cibles, embedder le pool source, puis miner les plus proches voisins. La sortie de chaque étape est l'entrée de la suivante. Il n'y a pas de recherche itérative, pas de phase de clustering, pas de sélection humaine — la profondeur vient du choix du bon encoder et du bon topn, non d'une investigation multi-phases.
Le skill entier est un mince wrapper autour de trois invocations directes de docker run contre l'image tao_toolkit.data_services déclarée dans versions.yaml (résolue au runtime — voir Setup). Le point d'entrée du conteneur prend <category> <action> -e <spec.yaml> [hydra overrides...] : embedding image_embeddings -e <embedding_spec.yaml> … pour l'embedding et tmm nearest_neighbors -e <mining_spec.yaml> … pour le mining. Le flag -e pointe vers un YAML de schémas par défaut ; tout ce qui suit est un override Hydra brut (key=value) appliqué par exécution. Il n'y a pas de mot-clé dataset à l'intérieur du conteneur — c'est le préfixe pilier du lanceur TAO et il est supprimé ici. Les clés de schéma peuvent être renommées entre les releases de data-services, donc en cas de doute introspect une fois par image avec docker run --rm "$DS_IMAGE" embedding image_embeddings --cfg=job et ... tmm nearest_neighbors --cfg=job. Voir references/invocation.md pour le contrat d'entrypoint complet, l'introspection --cfg=job, et la recette end-to-end copy-and-edit.
Entrées
- Parquet cibles — la sortie de gap-analysis, typiquement
mining_gaps.parquetdetao-route-visual-changenet-samples(ougaps.parquetdetao-analyze-gaps-visual-changenetsi le routage a été ignoré). Colonne obligatoire :filepath. Silabelest aussi présent, le filtrage label-aware pendant le mining est disponible ; sinon la tâche de mining no-ope silencieusement le filtre. - Pool source — un parquet d'images candidates à miner, avec une colonne
filepath. Si l'utilisateur n'a qu'un CSV, le convertir en parquet avec les mêmes colonnes avant l'étape 2. Pour le filtrage label-aware, le pool doit aussi porter une colonnelabel. - Fichier spec embedding — un YAML contenant
model,model_path,batch_size, et (uniquement quandmodel_pathest un.pth/.ckptTAO)model_config_path. Réutilisé sur les étapes 1 et 2 ;input_parquet/output_parquetsont fournis par exécution en tant qu'overrides Hydra. Le même spec DOIT piloter les deux étapes d'embedding — les embeddings d'encodeurs différents ne sont pas comparables, et les encodeurs mal assortis sont la cause la plus commune des rapports "les images minées ne sont pas liées". - Fichier spec mining — un YAML contenant
topn,knn_metric,filter_by_label, et (rarement modifié)source_embed_column_name/target_embed_column_name.source_parquet/target_parquet/output_parquetsont des overrides Hydra au runtime. Les embeddings SigLIP et CLIP doivent utiliserknn_metric: cosine. Quandfilter_by_label: truemais qu'un parquet embedding manque une colonnelabel, le conteneur log un avertissement et procède sans filtrage.
Setup
Les tâches de mining et d'embedding vivent à l'intérieur de l'image tao_toolkit.data_services déclarée dans versions.yaml. Résous l'URI concret une seule fois au début de l'exécution, puis confirme Docker, le toolkit conteneur NVIDIA, et la présence d'une GPU avant toute autre chose :
# Résous tao_toolkit.data_services → URI nvcr.io/... concret depuis versions.yaml
DS_IMAGE=$(python3 -c "import yaml,os; print(yaml.safe_load(open(os.environ['TAO_SKILL_BANK_PATH']+'/versions.yaml'))['images']['tao_toolkit']['data_services'])")
echo "DS_IMAGE=$DS_IMAGE"
docker info > /dev/null && echo "OK: docker"
nvidia-smi > /dev/null && echo "OK: GPU"
docker image inspect "$DS_IMAGE" > /dev/null \
|| docker pull "$DS_IMAGE"
TAO_SKILL_BANK_PATH est exporté par le hook session_start du plugin. S'il n'est pas défini (p. ex. en exécution hors du plugin Claude Code), pointe-le vers la racine du repo skill-bank avant la résolution. Une GPU est requise à la fois pour la passe forward de l'encoder et la recherche k-NN cuML/cuDF ; les deux étapes échoueront sans CUDA.
Montage de chemins. Chaque chemin hôte que le conteneur lit ou écrit — parquets d'entrée, répertoires de sortie, et la racine image du pool source — doit être bind-monté. L'approche la plus simple et la plus prévisible monte la racine du workspace avec les mêmes chemins dedans et dehors du conteneur pour que les chemins absolus dans les args parquet se résolvent de la même façon des deux côtés :
WORKSPACE=<chemin absolu contenant tous les parquets, outputs, et les images du pool source>
DOCKER="docker run --gpus all --rm --ipc=host --user $(id -u):$(id -g) -v $WORKSPACE:$WORKSPACE -w $WORKSPACE $DS_IMAGE"
Réutilise $DOCKER pour les trois invocations ci-dessous.
Pool source CSV. Si le pool source est fourni seulement en CSV, le convertir en parquet en amont avec pd.read_csv(...).to_parquet(..., index=False), en préservant la colonne filepath verbatim (et label si présent). N'ajoute pas de préfixe de chemin — le conteneur lit les parquets d'entrée tels quels et le montage $WORKSPACE garde les chemins hôte et conteneur identiques.
Rédige les deux fichiers spec une fois par itération. Les deux fichiers vivent sous $WORKSPACE pour que l'argument -e se résolve des deux côtés du montage. Les valeurs par exécution restent hors du spec et sont passées en tant qu'overrides Hydra au moment de l'invocation. Les défauts sont model: SigLIP, model_path: google/siglip-base-patch16-224, batch_size: 64 pour l'embedding, et topn: 5, knn_metric: cosine, filter_by_label: "false" (entre guillemets — le schéma le lit comme une string) pour le mining. Utilise cosine pour SigLIP/CLIP, euclidean/manhattan sinon ; ajoute model_config_path uniquement quand model_path est un checkpoint TAO. N'importe quel champ peut toujours être overridé inline à la CLI (p. ex. topn=10) — Hydra applique les overrides CLI en plus du spec.
Voir references/invocation.md pour les templates spec-file verbatim, le snippet de conversion CSV, et le détail complet du montage et de la résolution d'image.
Méthode
Trois commandes, dans l'ordre. Le parquet de sortie de chaque commande est l'entrée de la suivante. Exécute-les en Bash brut ; l'alias $DOCKER du Setup gère le conteneur, la GPU, et les montages. Chaque invocation suit la même forme : -e <spec> pour les défauts intégrés, puis une poignée d'overrides Hydra pour les chemins spécifiques à l'exécution.
Étape 1 — Embedder les images cibles
$DOCKER embedding image_embeddings -e <embedding_spec.yaml> \
input_parquet=<target_parquet> output_parquet=<target_embeddings_parquet>
Lit la sortie de gap-analysis / routage et écrit un parquet avec filepath, embedding, et toute colonne de métadonnées supplémentaires (p. ex. label, siamese_score, weakness) portées verbatim. Affiche le schéma de sortie (pd.read_parquet(...).columns) sur stdout pour que le hook script-check puisse confirmer que la colonne embedding existe. Pour overrider model / model_path / batch_size pour une exécution sans éditer le spec, ajoute-les en tant qu'overrides Hydra.
Étape 2 — Embedder le pool source
$DOCKER embedding image_embeddings -e <embedding_spec.yaml> \
input_parquet=<source_pool_parquet> output_parquet=<source_embeddings_parquet>
Même forme de commande que l'étape 1, appliquée au pool source. Utilise le même embedding_spec.yaml que l'étape 1, et ne fais pas d'overrides de model / model_path / batch_size différemment ici — les configs d'encodeur mal assortis sur les deux étapes produisent des embeddings non-comparables.
Étape 3 — Miner les plus proches voisins
$DOCKER tmm nearest_neighbors -e <mining_spec.yaml> \
source_parquet=<source_embeddings_parquet> \
target_parquet=<target_embeddings_parquet> output_parquet=<mined_parquet>
Pour chaque embedding cible, trouve les topn embeddings source les plus proches sous la métrique choisie, déduplique entre les cibles, et écrit un parquet single-column (filepath) de chemins source minés uniques. Le conteneur dépose aussi un mining_summary.txt à côté du parquet de sortie avec : nombre de requêtes, nombre de voisins, doublons supprimés, et (quand le filtrage label est activé) nombres de paires conservées-vs-supprimées. Ajuste topn, knn_metric, ou filter_by_label via override Hydra inline quand tu testes — pas besoin de réécrire le spec. Quand filter_by_label=true mais qu'un parquet embedding manque la colonne label, le conteneur log un avertissement et procède sans filtrage ; si la sortie minée semble trop grande ou contient des paires cross-label, scanne d'abord le docker log pour cet avertissement.
Voir references/invocation.md pour la recette paste-and-edit complète qui exécute les trois étapes comme un bloc Bash streamé avec des prints de sanité de comptage de lignes.
Sorties
Écris tout dans un dossier avec timestamp sous le répertoire d'expérience / itération. Le hook de packaging ajoute automatiquement mining_config/ et claude_session.jsonl quand Mining_Report.md est écrit.
<output_dir>/mining_results/YYYY-MM-DD_HHMMSS/
├── Mining_Report.md # Rapport de mining complet
├── embedding_spec.yaml # Le spec -e utilisé pour les étapes 1 et 2
├── mining_spec.yaml # Le spec -e utilisé pour l'étape 3
├── target_embeddings.parquet # Sortie étape 1 (filepath, embedding, + métadonnées portées)
├── source_embeddings.parquet # Sortie étape 2 (filepath, embedding, + métadonnées portées)
├── mined.parquet # Sortie étape 3 — chemins source minés uniques
├── mining_summary.txt # Auto-émis à côté de mined.parquet par le conteneur
├── mining_config/ # Auto-copié par hook
└── claude_session.jsonl # Auto-copié par hook
Au début de l'exécution, obtiens le timestamp réel en exécutant date +%Y-%m-%d_%H%M%S en Bash. N'hardcode PAS et ne devine PAS. Si l'utilisateur spécifie un chemin de sortie personnalisé, l'utilise directement mais maintiens le même layout interne.
Le parquet miné est l'artefact que l'entraînement en aval consomme. Les deux parquets embedding sont intermédiaires mais valent la peine d'être conservés : ils sont réutilisables sur plusieurs exécutions de mining contre le même pool source, et ils sont le seul endroit à consulter quand un rapport "looks unrelated" a besoin de debugging au niveau de l'encoder.
Pièges courants
La cause la plus commune de sortie garbage est les encodeurs mal assortis — les deux étapes d'embedding doivent consommer le même embedding_spec.yaml, et n'importe quel override de model / model_path / batch_size doit s'appliquer aux deux étapes ou à aucune. Autres problèmes fréquents : ignorer une étape d'embedding, une colonne label manquante sous filter_by_label=true (no-op silencieux), fichiers spec hors de $WORKSPACE, sentinelles ??? non résolues, checkpoints TAO sans model_config_path, pools CSV non convertis en parquet, path mismatches hôte/conteneur, pas de GPU, la mauvaise balise d'image, et topn × N_targets dépassant la taille du source (attendu, pas un bug — rapporte le nombre réel miné).
Voir references/troubleshooting.md pour le diagnostic complet et les solutions pour chacun de ceux-ci.
Structure du rapport
Garde le rapport resserré (600–1 200 mots). Le mining est un pipeline déterministe ; la valeur est de rendre le choix d'encoder, les comptages de lignes, et tout no-op de filtrage silencieux auditables — pas de narration. Le rapport a sept sections : Verdict, Inputs, Encoder Consistency, Mining Run, Per-Label Breakdown (ignorée si le parquet cibles n'a pas de colonne label), Output Sanity, et Recommended Actions.
Voir references/reporting_spec.md pour le template de rapport fill-in complet avec chaque section et champ.
Ordre d'exécution
- Résous
DS_IMAGEdepuisversions.yaml(images.tao_toolkit.data_services), puis exécutedocker info,nvidia-smi, etdocker image inspect "$DS_IMAGE"(pulling si manquant) une seule fois pour confirmer l'environnement. Arrête avec un message clair si l'un échoue. - Exécute
date +%Y-%m-%d_%H%M%Spour obtenir le timestamp ; crée<output_dir>/mining_results/<timestamp>/. - Écris
embedding_spec.yamletmining_spec.yamldans le répertoire avec timestamp, en remplissant le choix d'encoder et les knobs de mining. Garde ceux-ci sous$WORKSPACEpour que le chemin-ese résolve à l'intérieur du conteneur. - Si le pool source est un CSV, convertis en parquet d'abord (préserve
filepathetlabel). - Exécute l'étape 1 (embedder cibles) via
docker run … embedding image_embeddings -e embedding_spec.yaml input_parquet=… output_parquet=…. Affiche le comptage de lignes et les colonnes du parquet de sortie sur stdout. - Exécute l'étape 2 (embedder pool source) avec le même
embedding_spec.yamlque l'étape 1. Affiche le comptage de lignes et les colonnes de sortie. - Exécute l'étape 3 (miner plus proches voisins) via
docker run … tmm nearest_neighbors -e mining_spec.yaml source_parquet=… target_parquet=… output_parquet=…. Confirme quemining_summary.txta été écrit à côté demined.parquet. - Calcule le per-label breakdown (Section 5) en joignant le parquet target_embeddings avec la sortie minée sur filepath, si les deux portent
label. - Écris
Mining_Report.mden dernier — l'écriture déclenche le hook de packaging, qui copie les logs de session et la config du skill à côté.