Fusion tardive avec observations partielles

Le pipeline public by_source apprend un modèle par source, puis un modèle de fusion sur les prédictions OOF organisées par DAG-ML. Les sources peuvent conserver leurs rangs d’origine : chaque branche contient son encodeur, par exemple TensorPCA pour une image ou une série de taille fixe.

Deux politiques restent distinctes :

  • missing_source_policy="error" exige toutes les sources pour chaque ligne ; "zero_with_indicator" ne prédit une branche que sur ses lignes présentes et ajoute un indicateur de présence à ses prédictions.

  • target_policy="complete" exige des cibles complètes ; "per_target" apprend chaque cible de régression uniquement sur ses valeurs observées, avec un encodeur refitté dans cette même intersection. Les cibles ne sont pas imputées.

from sklearn.linear_model import Ridge
from sklearn.model_selection import GroupKFold
from sklearn.preprocessing import StandardScaler

import nirs4all
from nirs4all.operators.models.multimodal import TensorPCA

# cohort est un MultimodalDataset de nirs4all-io : les sources portent leurs
# sample_ids et presence_mask ; les cibles portent target_names et target_mask.
pipeline = [
    GroupKFold(3),
    {"branch": {
        "by_source": True,
        "missing_source_policy": "zero_with_indicator",
        "target_policy": "per_target",
        "steps": {
            "nir": [StandardScaler(), Ridge(alpha=0.1)],
            "image": [TensorPCA(n_components=2), Ridge(alpha=0.2)],
        },
    }},
    {"merge": "predictions"},
    Ridge(alpha=0.5),
]
result = nirs4all.run(pipeline, cohort, engine="dag-ml", refit=True,
                     save_artifacts=True, random_state=19)
meta = result.runs[-1]
archive = meta.export("late_partial.n4a")
prediction = nirs4all.predict(archive, prediction_cohort, engine="dag-ml")
result.close()

Sélectionner le run du modèle final pour exporter le stack complet. Le meilleur score global peut appartenir à une seule branche ; son export() .n4a est refusé avant écriture, car cette composante ne porte pas le replay autonome des sources absentes. L’export ne réentraîne pas et ne remplace pas ce modèle par un autre implicitement.

Pour classifier, utiliser une cible unique complète et des classifieurs possédant predict_proba dans toutes les branches et dans le modèle final. La fusion reçoit les K colonnes du vocabulaire commun, dans le même ordre, et un indicateur par source. Les lignes absentes ne produisent aucune distribution de classe ; les zéros du tableau de fusion sont accompagnés de masques distincts. Toutes les classes doivent être observées dans chaque véritable scope d’apprentissage.

L’archive Python conserve les ancres des composants réellement refittés : poids, schéma IO complet, ordre des sources, politiques, vocabulaire et lignes utilisées par cible. Son chargement vérifie séparément les octets joblib et ces ancres. Le replay exécute uniquement la phase native PREDICT, sans FIT, CV ni HPO. Il s’agit d’une archive Python de confiance, avec ses dépendances, et non d’un modèle portable Core pour R/WASM.

La cohorte de prédiction doit déclarer les mêmes sources, dans le même ordre et avec les mêmes schémas. Une source complètement absente se déclare avec son presence_mask entièrement faux et des buffers de la forme déclarée ; sa branche n’est pas appelée. Aucun padding, remappage implicite de vocabulaire ou changement silencieux d’ordre n’est effectué. Les groupes et les masques de cibles déterminent les vrais scopes d’entraînement ; les données de Test restent une cohorte native séparée.

Ce profil partiel travaille dans l’espace des cibles d’origine. Une étape y_transform séparée est refusée avant apprentissage ; un TransformedTargetRegressor interne à un modèle reste inclus dans l’état appris capturé. La classification nécessite ses labels complets ; per_target concerne la régression.

Les petites données utilisées par les tests de cette fonctionnalité sont des fixtures déterministes. Aucun générateur multimodal de produit n’est ajouté.