← Retour au blog

Données synthétiques et protection de la vie privée

Lucian BLETAN

Les données synthétiques permettent de partager, tester et entraîner sans exposer directement des personnes. Elles ne sont pas une cape d’invisibilité: il faut des vérifications systématiques, des limites claires et des preuves d’audit. Ce guide présente des usages pertinents, les risques, des garde-fous concrets et un mode opératoire reproductible, avec des diagrammes lisibles verticalement.

prérequis

  • un échantillon source déjà conforme aux lois et consentements applicables
  • une séparation stricte entre apprentissage et évaluation
  • un environnement d’exécution isolé pour la génération
  • un espace de preuves avec journaux, métriques et fiches de jeu

aperçu rapide

  • limiter la portée: variables, horizon temporel, granularité
  • choisir une méthode adaptée: simulation, statistique, modèle génératif
  • générer par lots et tracer chaque lot
  • évaluer utilité et confidentialité avant toute diffusion
  • documenter ce que le jeu peut et ne peut pas faire
  • publier sous un contrat d’usage clair et auditable

de l’idée au jeu partagé

données sources

définir variables et finalité

entraîner le générateur

génération par lots

tests d utilité

tests de confidentialité

rapport d utilité

rapport de confidentialité

validation interne

publication sous contrat

itérations si hors bornes

usages pertinents

  • jeux d’exemple publics pour documentation et didactique
  • tests de charge et de performance sans toucher la production
  • préentraînement de modèles sur signaux généraux
  • data augmentation quand les classes sont déséquilibrées
  • partage externe sous finalité limitée et contrôlée

usages

documentation et démonstration

tests de charge et performance

préentraînement de modèles

data augmentation

partage externe encadré

risques

  • mémorisation d’exemples rares et fuites indirectes
  • génération mal calibrée qui réintroduit des identifiants
  • biais existants reproduits ou amplifiés
  • faux sentiment d’anonymat et détournements de finalité

risques

mémorisation

réidentification

biais reproduits

détournement de finalité

garde-fous

  • évaluation de similarité avec les originaux et bornes strictes
  • bruit contrôlé et régularisation pour casser la mémorisation
  • documentation explicite des limites et du domaine de validité
  • revue par un sponsor métier et un responsable privacy

garde fous

mesures de similarité

bruit contrôlé

documentation des limites

revue privacy et métier

choisir une méthode de génération

  • règles et simulateurs: rapide, explicable, idéal pour données structurées simples
  • modèles probabilistes tabulaires: copules, arbres, GLM mixtes
  • modèles génératifs profonds: VAE, GAN, diffusion pour images et signaux
  • approche mixte simulation plus ajustement statistique pour respecter la physique du système

choisir une méthode

simulateur à règles

modèle probabiliste

génératif profond

mixte simulation plus stats

pipeline d’évaluation utilité et confidentialité

jeu synthétique candidat

tests d utilité

tests de confidentialité

score global et verdict

métriques d’utilité

  • préservation des distributions marginales et des corrélations
  • performance d’un modèle simple entraîné sur synthétique puis testé sur réel
  • métriques de tâches cibles: précision, rappel, AUC, RMSE selon le cas
# évaluer l'utilité: entraîner sur synthétique, tester sur réel
import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_auc_score

def utilite_auc(X_syn, y_syn, X_real, y_real):
    clf = LogisticRegression(max_iter=1000)
    clf.fit(X_syn, y_syn)
    proba = clf.predict_proba(X_real)[:, 1]
    return float(roc_auc_score(y_real, proba))

mesures de similarité et bornes

  • distance de Wasserstein sur variables numériques
  • divergence de Jensen Shannon sur histogrammes
  • taux de collisions exactes entre lignes réelles et synthétiques
# mesure de similarité cosinus pour vecteurs embarqués
import numpy as np

def cosinus(a, b):
    num = float(np.dot(a, b))
    den = float(np.linalg.norm(a) * np.linalg.norm(b) + 1e-9)
    return num / den

tests de mémorisation et de fuite

  • nearest neighbour distance ratio: comparer distance minimale dans réel vs synthétique
  • audit de collisions: hash de lignes canonisées
  • membership inference basique: classifieur qui décide si un point était dans l’entraînement
# test rapide de mémorisation via distances
import numpy as np
from sklearn.neighbors import NearestNeighbors

def ratio_nn(real, synth):
    nbr_r = NearestNeighbors(n_neighbors=1).fit(real)
    d_rs, _ = nbr_r.kneighbors(synth, return_distance=True)
    nbr_s = NearestNeighbors(n_neighbors=1).fit(synth)
    d_rr, _ = nbr_s.kneighbors(real, return_distance=True)
    return float(np.median(d_rr) / (np.median(d_rs) + 1e-9))

tutoriel pas à pas

étape 1: définir la finalité et le périmètre

  • variable cible, fenêtre temporelle, grain
  • champs sensibles exclus ou masqués en amont
  • définir les quasi identifiants pour les tests
scoping:
  target_task: "classification churn"
  time_window: "2023-01-01..2023-12-31"
  grain: "client_id mois"
  exclude: ["email", "téléphone", "adresse"]
  quasi_identifiers: ["âge", "code_postal", "nb_enfants"]

étape 2: préparer et partitionner

  • séparer la source en train et holdout réel
  • normaliser et encoder de façon déterministe
  • journaliser le hash des partitions pour vérifiabilité
import hashlib, json

def digest(obj):
    s = json.dumps(obj, sort_keys=True, ensure_ascii=False)
    return hashlib.sha256(s.encode()).hexdigest()[:12]

étape 3: entraîner un générateur

  • commencer simple: copules ou régressions avec bruit
  • tracer hyperparamètres, seed et version du code
train:
  method: "copula_gaussian"
  seed: 42
  params:
    regularization: 1e-2
    max_components: 8

étape 4: générer par lots et tracer

  • générer N lignes par lot
  • attribuer un id de lot et une seed
  • stocker des statistiques rapides de chaque lot
import numpy as np, uuid, time

def gen_batch(n, seed):
    rng = np.random.default_rng(seed)
    rows = int(n)
    meta = {"batch_id": str(uuid.uuid4()), "rows": rows, "generated_at": int(time.time()), "seed": int(seed)}
    return meta

étape 5: évaluer utilité et confidentialité

  • comparer distributions et performances
  • effectuer des tests de mémorisation
  • documenter les résultats et les écarts acceptés
evaluation:
  utility:
    auc_on_real: 0.79
    drift_ok: true
  privacy:
    nn_ratio: 1.35
    collisions_exactes: 0
  decision: "publiable avec limites"

étape 6: publier avec un contrat d’usage

  • finalités autorisées et interdites
  • conditions de licence et attribution
  • fenêtre de validité et point de contact
contract:
  dataset: "synthetic_customers_v1"
  purpose_allow: ["tests", "documentation", "benchmarks"]
  purpose_deny: ["ciblage marketing", "réidentification"]
  license: "cc by 4.0"
  retention_days: 365
  pii_status: "aucune donnée personnelle"
  contact: "data@exemple.com"

diagrammes de référence

séquence de validation interne

relecteurmoteur privacyanalysterelecteurmoteur privacyanalystealt[métriques dans les bornes][métriques hors bornes]soumet un lot synthétiquerenvoie métriques utilité et confidentialitédemande validation publicationapprobation avec numéro de versionrecommandations d ajustement

grille de décision publication

ok

ok

complet

incomplet

métriques

utilité suffisante

confidentialité suffisante

rejeter

contrat prêt

publier sous contrat

compléter contrat

exemples complets

cas 1: jeu tabulaire pour tests de charge

import numpy as np
import pandas as pd

def make_synth_load(n=1_000_000, seed=123):
    rng = np.random.default_rng(seed)
    df = pd.DataFrame({
        "client_id": rng.integers(1_000_000, 9_999_999, size=n),
        "âge": rng.integers(18, 90, size=n),
        "revenu_mensuel": rng.gamma(5.0, 600.0, size=n),
        "segment": rng.choice(["A","B","C"], size=n, p=[0.2,0.5,0.3]),
        "actif": rng.choice([0,1], size=n, p=[0.3,0.7])
    })
    return df

explications: variables plausibles, distributions maîtrisées et absence de lignes identiques à des personnes réelles.

cas 2: audit de collisions exactes

import hashlib

def row_hash(row):
    s = "|".join(str(v) for v in row)
    return hashlib.sha256(s.encode()).hexdigest()

def collision_rate(real_rows, synth_rows, sample=10000):
    real_set = set(row_hash(r) for r in real_rows[:sample])
    synth_set = set(row_hash(r) for r in synth_rows[:sample])
    inter = real_set.intersection(synth_set)
    return len(inter) / max(1, min(len(real_set), len(synth_set)))

explications: détecte toute ligne identique entre deux échantillons, à surveiller surtout quand le générateur est surentraîné.

gouvernance et traçabilité

  • journaliser seeds, versions et paramètres
  • attacher les rapports d’utilité et de confidentialité à chaque lot
  • exposer une fiche dataset machine lisible
{
  "dataset": "synthetic_customers_v1",
  "version": "1.0.0",
  "generated_at": "2024-06-09T10:00:00Z",
  "method": "copula_gaussian",
  "seed": 42,
  "batches": 5,
  "utility": {"auc_on_real": 0.79},
  "privacy": {"nn_ratio": 1.35, "collisions": 0},
  "contact": "data@exemple.com"
}

contrat d’usage

  • interdiction de réidentifier ou de recouper pour cibler des personnes
  • publication sous licence claire et compatible avec la finalité
  • audits échantillonnés et retrait possible en cas d’abus
usage_terms:
  reidentification: "strictement interdite"
  redistribution: "permise sous la même licence"
  attribution: "requise"
  audit: "possible sur échantillons"
  takedown: "processus établi avec délai"

erreurs courantes et solutions

  • croire à l’anonymat parfait -> mettre des tests et des bornes
  • jeux orphelins sans suivi -> ranger et dater dans un catalogue
  • absence de documentation -> fournir une fiche courte et un changelog
  • surajustement au réel -> ajouter du bruit et de la régularisation
  • utilité non vérifiée -> toujours tester sur tâches cibles

faq

  • Les données synthétiques suffisent-elles pour la production ? Non. Elles servent à tester, documenter et préentraîner. La production exige des données réelles conformes.

  • Puis-je partager un jeu synthétique en externe sans contrat d’usage ? Non. Un contrat d’usage clair et traçable est indispensable.

  • Comment savoir si le jeu est trop proche des originaux ? Combinez collisions, distances et tests de membership inference simples. Si un seuil est dépassé, ajustez ou abandonnez.

  • Faut-il des grands modèles pour générer un jeu utile ? Non. Commencez avec des approches simples et explicables, validez, puis complexifiez si besoin.

  • Que documenter au minimum pour un partage responsable ? Méthode, version, seed, métriques d’utilité et de confidentialité, limites explicites et contact.