← Retour au blog

Gouvernance des modeles model cards et system cards

Lucian BLETAN

Une model card décrit objectifs, périmètre, données, métrriques, limites et usages prévus d’un modèle. Une system card étend la vue à l’application: RAG, prompts, filtres, mesures, monitoring, coûts, responsables. L’enjeu est la confiance opérationnelle, pas la paperasse. Ce guide propose des gabarits concrets, un flux de validation en CI et des exemples prêts à adapter.

prérequis

  • un dépôt Git pour stocker cartes, schémas et exemples.
  • une convention de nommage et de version (semver) partagée.
  • un validateur (JSON Schema ou script) exécuté en CI.
  • un emplacement de publication interne (docs, catalogue data/ML).

aperçu rapide

  • écrire court et utile: 1 page + un schéma, pas un roman.
  • lier cartes et artefacts réels: données, code, jeux d’évaluation.
  • valider automatiquement la présence des champs clés en CI.
  • tenir à jour à chaque version majeure et lors de changements de données.
  • exposer métriques et limites par segments pour éviter les angles morts.
  • rattacher les cartes aux journaux d’usage et aux incidents.

où commence et finit chaque carte

cartes

model card

objectif

donnees

methodes

metrics

limites

fairness

version

system card

architecture

sources

prompts

filtres

monitoring

risques

owners

modèle de model card (gabarit YAML)

model_card:
  id: "ticket_classifier_fr_v1"
  title: "Classification de tickets FR (priorite et theme)"
  owner: "@ml-support"
  version: "1.3.0"
  objective: "Aider au routage initial des tickets en français"
  scope:
    inputs: ["titre", "description"]
    outputs: ["priorite in {basse, moyenne, haute}", "theme in {facturation, technique, compte, autre}"]
    exclusions: ["dossiers juridiques", "tickets non FR"]
  data:
    training_sources: ["tickets_2022_2023_fr_clean.parquet"]
    eval_set: "eval_fr_q1_2024.parquet"
    pii_handling: "emails et tels masques en amont"
    drift_signals: ["lang_detect", "distribution_themes"]
  method:
    family: "transformer distille"
    compression: ["distillation", "quantization int8"]
    seed: 42
  metrics:
    global:
      accuracy: 0.89
      macro_f1: 0.86
    by_segment:
      "priorite:haute": {f1: 0.91}
      "theme:facturation": {f1: 0.88}
  limitations:
    - "performances plus faibles sur tickets mixtes FR/EN"
    - "erreurs sur textes très courts (< 5 mots)"
  fairness_notes:
    segments_compares: ["region", "canal"]
    ecarts_acceptables: "delta f1 <= 3 points"
  risks:
    - "faux negatif sur priorite:haute"
    - "biais canal email vs chat"
  monitoring:
    metrics: ["latence_p95_ms", "macro_f1_sem", "delta_segment_f1"]
    thresholds: {"latence_p95_ms": 120, "delta_segment_f1": 3}
  changelog:
    - "1.3.0: ajout quantization int8, latence -35% p95; f1 stable"
    - "1.2.0: nouveau jeu d'evaluation Q1 2024"

modèle de system card (gabarit YAML)

system_card:
  id: "assistant_support_fr"
  title: "Assistant de reponse support FR"
  owners: ["@support", "@ml-platform"]
  architecture:
    diagram: "ui -> orchestrateur -> modele + rag -> filtres -> ui"
    components:
      - "retriever dense + bm25"
      - "index faq_support_fr_v2"
      - "modele: ticket_classifier_fr_v1"
  context:
    inputs: ["question_agent", "contexte_faq"]
    outputs: ["suggestion_reponse", "tags"]
    languages: ["fr"]
  sources:
    allowlist: ["faq_support_fr_v2", "wiki_support_fr"]
    blocklist: ["dossiers_sensibles"]
  prompts:
    style: "formel, concis"
    guardrails: ["ne pas inventer", "referencer source"]
  filters:
    inbound: ["normalisation unicode", "suppression html", "limite longueur"]
    outbound: ["filtre pii", "liste interdits", "seuil confiance"]
  quality:
    kpi: ["exactitude@top1 sur 200 questions", "taux d'acceptation agent"]
    target: {"exactitude_top1": 0.78, "acceptation": 0.7}
  security:
    policies: ["masquage pii", "quotas", "logs"]
  operations:
    latency_p95_ms_target: 150
    cost_per_1000: "0.04 eur inference locale"
    rollout: {"canary": 10, "rollback_on_error": true}
  risks:
    - "reponses trop assures sur contenus obsoletes"
    - "prompt injection via piece jointe"
  evidence:
    eval_report_uri: "s3://ml-reports/assistant_support_fr/2024Q2.html"

tutoriel pas-à-pas

étape 1: créer les gabarits

Placez des gabarits YAML et un schéma de validation dans le dépôt.

# cards/schema/model_card.schema.yaml (extrait)
type: object
required: ["id","title","owner","version","objective","data","metrics","limitations","monitoring"]
properties:
  id: {type: "string", minLength: 3}
  version: {type: "string", pattern: "^[0-9]+\\.[0-9]+\\.[0-9]+$"}
  metrics:
    type: object
    properties:
      global:
        type: object
        properties:
          accuracy: {type: "number", minimum: 0, maximum: 1}
          macro_f1: {type: "number", minimum: 0, maximum: 1}

étape 2: lier artefacts et jeux d’évaluation

Ajoutez des URI vérifiables vers jeux d’évaluation, notebooks et rapports.

evidence:
  eval_set_uri: "s3://ml-eval/eval_fr_q1_2024.parquet"
  notebook_uri: "s3://ml-eval/notebooks/ticket_cls_eval.ipynb"
  report_uri: "s3://ml-reports/ticket_cls/1.3.0.html"

étape 3: valider en CI

Utilisez un validateur simple (Python) pour bloquer les PR si des champs manquent.

# tools/validate_cards.py
import sys, json, yaml, jsonschema, glob
from jsonschema import validate

def load_schema(path):
    with open(path) as f:
        return yaml.safe_load(f)

def check_card(card_path, schema):
    with open(card_path) as f:
        card = yaml.safe_load(f)
    try:
        validate(instance=card.get("model_card") or card.get("system_card"), schema=schema)
        return True, None
    except jsonschema.exceptions.ValidationError as e:
        return False, f"{card_path}: {e.message}"

def main():
    ok = True
    schema = load_schema(sys.argv[1])
    for p in glob.glob("cards/**/*.yaml", recursive=True):
        valid, err = check_card(p, schema)
        if not valid:
            ok = False
            print(err)
    sys.exit(0 if ok else 1)

if __name__ == "__main__":
    main()

étape 4: publier et relier

Exposez les cartes dans le catalogue interne et reliez-les aux pages produit.

# pseudo-ci
python tools/validate_cards.py cards/schema/model_card.schema.yaml
python tools/validate_cards.py cards/schema/system_card.schema.yaml
# si OK, publier
rsync -av cards/ docs/cards/

étape 5: boucler avec la production

Connectez cartes et métriques réelles (latence, coûts, qualité) pour rester crédibles.

-- table de suivi des KPI par version
CREATE TABLE IF NOT EXISTS ml.cards_kpi (
  day DATE,
  card_id TEXT,
  version TEXT,
  metric TEXT,
  value DOUBLE PRECISION
);

diagrammes utiles

flux de revue et publication

ok

ko

proposer carte

validation schema ci

revue pairs

ajout preuves

publication catalogue

monitoring relie

séquence de mise à jour contrôlée

CatalogueRelecteurCIAuteurCatalogueRelecteurCIAuteurpush carte v1.3.0schema okdemande validationremarques limites fairnesspush correctionpublier nouvelle version

exemples complets

exemple 1: model card concrète

model_card:
  id: "sentiment_fr_v2"
  title: "Analyse de sentiment FR (avis clients)"
  owner: "@marketing-ml"
  version: "2.0.0"
  objective: "Classer avis en {negatif, neutre, positif} pour prioriser la moderation"
  scope:
    inputs: ["avis_texte"]
    outputs: ["sentiment"]
  data:
    training_sources: ["avis_2019_2023_fr.parquet"]
    eval_set: "eval_fr_retail_q1_2024.parquet"
    pii_handling: "masquage email/tel"
  method:
    family: "cnn text + embeddings fr"
    compression: ["pruning light"]
  metrics:
    global: {accuracy: 0.92, macro_f1: 0.90}
    by_segment:
      "canal:web": {f1: 0.91}
      "canal:magasin": {f1: 0.88}
  limitations:
    - "ironies détectées partiellement"
    - "mauvaise qualité sur messages très courts"
  fairness_notes:
    segments_compares: ["genre_estime", "region"]
    ecarts_acceptables: "delta f1 <= 3 points"
  risks:
    - "mauvaise priorisation des cas urgents"
  monitoring:
    metrics: ["latence_p95_ms","macro_f1_sem","drift_kl"]
    thresholds: {"latence_p95_ms": 80}
  changelog:
    - "2.0.0: nouveau backbone, f1 +2 pts; latence +5 ms"

exemple 2: system card concrète

system_card:
  id: "assistant_support_fr"
  title: "Assistant de reponse support FR"
  owners: ["@support", "@ml-platform"]
  architecture:
    diagram: "ui -> orchestrateur -> modele + rag -> filtres -> ui"
    components:
      - "retriever hybrid"
      - "index faq_support_fr_v2"
      - "modele: sentiment_fr_v2 (tonalité)"
  sources:
    allowlist: ["faq_support_fr_v2", "wiki_support_fr"]
  prompts:
    style: "clair et sourcé"
    guardrails: ["ne pas inventer", "indiquer sources"]
  filters:
    inbound: ["normalisation", "limite longueur"]
    outbound: ["filtre pii", "liste interdits"]
  quality:
    kpi: ["exactitude@top1 sur 200 questions", "taux d'acceptation agent"]
  operations:
    latency_p95_ms_target: 150
    cost_per_1000: "0.03 eur"
  risks:
    - "reponses hors périmètre"
    - "documents obsoletes"
  evidence:
    eval_report_uri: "s3://ml-reports/assistant_support_fr/2024Q2.html"

rattacher cartes et mesures

Reliez chaque card à des métriques vivantes pour éviter l’écart entre promesse et réalité.

-- joindre kpi de production à la carte
SELECT c.card_id, c.version, k.day, k.metric, k.value
FROM ml.cards_kpi k
JOIN LATERAL (
  VALUES ('sentiment_fr_v2','2.0.0'),
         ('ticket_classifier_fr_v1','1.3.0')
) AS c(card_id,version) ON true
WHERE k.card_id = c.card_id AND k.version = c.version
ORDER BY k.day DESC;

pratiques recommandées

  • stocker cartes avec le code: même MR, même revue, même historique.
  • valider en CI la présence des champs et la cohérence des valeurs.
  • mettre à jour à chaque version majeure et quand les données changent.
  • raccrocher aux tests et aux journaux: preuves et diagnostics à portée de main.
  • écrire pour les lecteurs: 1 page, un schéma, des liens vers les détails.

erreurs courantes et solutions

  • roman illisible -> personne ne lit -> viser 1 page utile + schéma clair.
  • cartes isolées -> dérive dans le temps -> lier aux métriques et aux incidents.
  • champs manquants -> ambiguïtés -> valider en CI avec schémas stricts.
  • métriques flatteuses -> surprises en prod -> publier par segments et suivre en continu.
  • version floue -> confusion -> semver, changelog, liens vers artefacts.

faq

  • A quoi sert une model card au-delà du marketing ? A cadrer l’usage, exposer les limites et fournir des références vérifiables sur données et métriques. C’est un contrat technique lisible.

  • Quand faut-il créer ou mettre à jour une system card ? A l’introduction d’un nouveau composant, à chaque changement de source, d’index ou de politique de filtrage, et à chaque version majeure.

  • Faut-il tout standardiser avec un schéma unique ? Oui pour les champs essentiels (objectif, données, métriques, limites, owners). Laissez des sections libres pour les spécificités.

  • Comment éviter que les cartes deviennent obsolètes ? Connectez-les au monitoring, exigez un lien vers le dernier rapport d’évaluation, et rendez le check obligatoire en CI.

  • Où les publier pour qu’elles soient réellement utiles ? Dans le catalogue interne au côté des jeux de données et des APIs, avec des permaliens et des extraits lisibles.