Du graphe causal au do-calculus — le pont entre les quatre séries causales

Notebook-pont de la constellation causale du dépôt (règle F : dowhy installé et exécuté réellement, SOTA-OK). Il ne remplace pas les notebooks dédiés de chaque moteur : il donne l’armature formelle unifiée (échelle de Pearl + trois règles du do-calculus) et la fait tourner sur l’outil de référence dowhy, avant de renvoyer à chaque série pour l’instanciation par moteur.

La causalité est traitée à quatre endroits du dépôt, chacun avec son moteur et son angle :

Série Moteur Angle
Tweety-11 Tweety (.NET, logique) SCM, opérateur do, contrefactuels
Infer-5 Infer.NET (message passing) backdoor, front-door, Simpson, médiation
PyMC-5 PyMC (MCMC) backdoor, front-door, contrefactuel bayésien
ICT-5 / ICT-6 PyPhi (CE 2.0) émergence causale (information effective de Hoel)

Ce que ce pont ajoute, que les quatre notebooks ne font pas chacun séparément : (1) la théorie unifiée du do-calculus présentée une bonne fois ; (2) une exécution sur l’outil SOTA dowhy qui identifie l’estimande (backdoor/front-door/IV), estime puis réfute ; (3) la mise en regard des deux grands paradigmes — l’interventionnisme de Pearl et l’émergence causale de Hoel. (4) l’ouverture sur la cinquième modalité — le raisonnement causal d’un LLM à partir des seules métadonnées et connaissances (§9).

Objectifs d’apprentissage

À l’issue de ce notebook vous saurez :

  1. Situer chaque série causale du dépôt sur l’échelle de Pearl (observation / intervention / contrefactuel).
  2. Énoncer les trois règles du do-calculus et les lire comme des chirurgies du graphe causal.
  3. Reconnaître les critères backdoor et front-door, et les exécuter avec dowhy sur des données simulées à effet connu.
  4. Distinguer la causalité interventionniste (Pearl) de l’émergence causale (Hoel / information effective) — deux réponses différentes à la question « quelle échelle cause ? ».
  5. Classer les quatre tâches prototypiques du data-fusion (confondage observationnel, régiment expérimental, biais de sélection, transportabilité) et corriger sélection et transport par pondération inverse.
  6. Démontrer la cécité de la couche L1 : deux SCM gaussiens à loi jointe identique et conclusions interventionnelles opposées — le Causal Hierarchy Theorem rendu machine.
  7. Relier le do-calculus aux attributions de Shapley : choisir une baseline d’attribution, c’est choisir un estimand causal (voir/intervenir).

Prérequis : probabilités conditionnelles, graphes orientés acycliques (DAG), notions d’inférence bayésienne. Une lecture préalable de l’un des quatre notebooks ci-dessus rend le pont plus concret.

1. L’échelle de Pearl — trois niveaux de causalité

Judea Pearl structure la causalité en trois échelons croissants, chacun subsumant le précédent :

Niveau Question type Symbole Opération
1. Association Que vois-je ? \(P(y \mid x)\) observer
2. Intervention Que se passe-t-il si je fais \(x\) ? \(P(y \mid do(x))\) agir
3. Contrefactuel Que se serait-il passé si j’avais fait \(x'\) ? \(P(y_x \mid x', y')\) imaginer

Le saut du niveau 1 au niveau 2 est le cœur du do-calculus : \(P(y \mid x)\) (ce qu’on observe dans les données) diffère en général de \(P(y \mid do(x))\) (l’effet d’une intervention), précisément à cause des chemins confondants que le do-calculus permet de neutraliser. Les quatre notebooks de la constellation couvrent les trois niveaux depuis l’angle de leur moteur ; nous récapitulons ici le formalisme partagé.

2. Les trois règles du do-calculus

Soit \(G\) un graphe causal, \(G_{\overline{Z}}\) (resp. \(G_{\underline{Z}}\)) le graphe où l’on coupe les arêtes entrant dans (resp. sortant de) \(Z\). Les trois règles de Pearl permettent de transformer toute expression avec \(do(\cdot)\) en expression sans \(do(\cdot)\) lorsque c’est possible :

Règle 1 (insertion/suppression d’observations). \(P(y \mid do(x), z, w) = P(y \mid do(x), w)\) si \((Y \perp\!\!\!\perp Z \mid X, W)_{G_{\overline{X}}}\).

Règle 2 (échange action/observation). \(P(y \mid do(x), do(z), w) = P(y \mid do(x), z, w)\) si \((Y \perp\!\!\!\perp Z \mid X, W)_{G_{\overline{X}\underline{Z}}}\).

Règle 3 (insertion/suppression d’actions). \(P(y \mid do(x), do(z), w) = P(y \mid do(x), w)\) si \((Y \perp\!\!\!\perp Z \mid X, W)_{G_{\overline{X}\overline{Z(W)}}}\).

Intuition unifiée : chaque règle est une chirurgie du graphe suivie d’un test de \(d\)-séparation. Si deux variables sont indépendantes conditionnellement dans le graphe mutilé approprié, on peut réécrire la probabilité. Le critère backdoor (§4) et le critère front-door (§5) sont les cas spéciaux les plus utiles qui en découlent directement.

3. Configuration — l’outil de référence dowhy

Nous utilisons dowhy (écosystème PyWhy), la librairie de référence pour l’inférence causale. Elle poursuit le pipeline en quatre étapes qui structurent toute analyse causale sérieuse :

  1. Modèle — on fournit les données et le graphe causal (hypothèses expertes) ;
  2. Identification — dowhy lit le graphe et détermine quel estimande est calculable (backdoor, front-door, variable instrumentale) ;
  3. Estimation — on choisit un estimateur (régression, appariement, pondération inverse) ;
  4. Réfutation — on stress-teste l’estimation (placebo, sous-échantillon, proxy) pour vérifier sa robustesse.

C’est précisément la rigueur (identification avant estimation) qui distingue une analyse causale d’une simple régression corrélationnelle.

import warnings

# dowhy emet un advisory de modelisation causale ("N variables are assumed") qui fuit le chemin
# absolu du fichier source. C'est une information utile (identification causale) -> on la GARDE
# visible, mais on retire le prefixe de chemin (anti path-leak #3436).


def _warn_no_path(message, category, filename, lineno, file=None, line=None):
    """Affiche le warning SANS le chemin absolu du fichier source (anti path-leak #3436)."""
    return f"{category.__name__}: {message}\n"


warnings.formatwarning = _warn_no_path

import numpy as np
import pandas as pd
import networkx as nx
from dowhy import CausalModel
import dowhy

print("dowhy", dowhy.__version__, "| networkx", nx.__version__, "| pandas", pd.__version__)
dowhy 0.14 | networkx 3.6.1 | pandas 3.0.5

4. Le critère backdoor — neutraliser le confondeur observable

Énoncé (Pearl). Un ensemble \(Z\) satisfait le critère backdoor relativement à \((X, Y)\) si :

  1. aucun nœud de \(Z\) n’est un descendant de \(X\) ;
  2. \(Z\) bloque tous les chemins backdoor (chemins entrant dans \(X\) par une arête pointée vers \(X\)) entre \(X\) et \(Y\).

Quand un tel \(Z\) existe et est observable, alors

\[P(y \mid do(x)) = \sum_z P(y \mid x, z)\, P(z),\]

ce qui ramène l’intervention à un ajustement — règle 2 du do-calculus.

Exemple : aptitude → {études, salaire}

Le facteur d’aptitude \(U\) cause à la fois la décision de faire des études (\(T\)) et le salaire \(Y\). L’effet vrai de \(T\) sur \(Y\) est \(2{,}0\). La simple comparaison des salaires moyens est biaisée car \(U\) ouvre un chemin backdoor \(T \leftarrow U \rightarrow Y\).

np.random.seed(42)
N = 20_000
aptitude = np.random.normal(0, 1, N)
college  = (0.8 * aptitude + np.random.normal(0, 1, N) > 0).astype(int)
earnings = 2.0 * college + 1.5 * aptitude + np.random.normal(0, 1, N)
df_backdoor = pd.DataFrame({"aptitude": aptitude, "college": college, "earnings": earnings})

naive = df_backdoor.query("college == 1").earnings.mean() - df_backdoor.query("college == 0").earnings.mean()
print(f"Effet NAIF observé : {naive:.3f}   (biaisé — l'aptitude confond)")
print(f"Effet VRAI (connu) : 2.000")
Effet NAIF observé : 3.494   (biaisé — l'aptitude confond)
Effet VRAI (connu) : 2.000

Le biais est net : l’effet naïf (~3,49) surévalue largement l’effet vrai (2,0) — l’aptitude ouvre un chemin backdoor \(T \leftarrow U \rightarrow Y\). Pour corriger, il faut indiquer à dowhy la structure causale du problème. Le graphe ci-dessous déclare nos connaissances (\(U \to T\), \(T \to Y\), \(U \to Y\)) ; à partir de lui, dowhy va identifier lui-même l’ensemble des variables à ajuster. C’est l’étape identify, distincte de l’estimation : on ne dit pas comment ajuster, on dit ce que l’on sait du mécanisme, et le moteur en déduit la stratégie d’identification.

# Graphe causal : aptitude -> college, college -> earnings, aptitude -> earnings
gml_backdoor = '''
graph [
  directed 1
  node [ id 0 label "aptitude" ]
  node [ id 1 label "college" ]
  node [ id 2 label "earnings" ]
  edge [ source 0 target 1 ]
  edge [ source 1 target 2 ]
  edge [ source 0 target 2 ]
]
'''
model_bd = CausalModel(data=df_backdoor, treatment="college", outcome="earnings", graph=gml_backdoor)
estimand_bd = model_bd.identify_effect(proceed_when_unidentifiable=False)
print(estimand_bd)
Estimand type: EstimandType.NONPARAMETRIC_ATE

### Estimand : 1
Estimand name: backdoor
Estimand expression:
    d                           
──────────(E[earnings|aptitude])
d[college]                      
Estimand assumption 1, Unconfoundedness: If U→{college} and U→earnings then P(earnings|college,aptitude,U) = P(earnings|college,aptitude)

### Estimand : 2
Estimand name: iv
No such variable(s) found!

### Estimand : 3
Estimand name: frontdoor
No such variable(s) found!

### Estimand : 4
Estimand name: general_adjustment
Estimand expression:
    d                           
──────────(E[earnings|aptitude])
d[college]                      
Estimand assumption 1, Unconfoundedness: If U→{college} and U→earnings then P(earnings|college,aptitude,U) = P(earnings|college,aptitude)

dowhy a lu le graphe et produit l’estimande : la seule stratégie viable est backdoor en ajustant sur aptitude (les variables instrumentales et la voie front-door sont rejetées — il n’y en a pas dans ce graphe). La forme de l’estimande, \(\frac{d}{d[\text{college}]} E[\text{earnings} \mid \text{aptitude}]\), est la règle 2 du do-calculus matérialisée : l’intervention \(do(\text{college})\) se ramène à un ajustement conditionnel. On estime maintenant l’effet en appliquant cet ajustement (régression linéaire), puis on le soumet à deux réfutations : un placebo (le vrai effet doit disparaître) et un sous-échantillon (l’effet doit rester stable).

estimate_bd = model_bd.estimate_effect(estimand_bd, method_name="backdoor.linear_regression")
print(f"Effet estime (dowhy, ajustement backdoor) : {estimate_bd.value:.3f}")
print(f"Effet vrai                                : 2.000")

# Réfutation : l'effet résiste-t-il à un placebo et à un sous-échantillon ?
refute_placebo = model_bd.refute_estimate(estimand_bd, estimate_bd, method_name="placebo_treatment_refuter")
refute_subset  = model_bd.refute_estimate(estimand_bd, estimate_bd, method_name="data_subset_refuter")
print("\nPlacebo (effet attendu ~0) :", round(refute_placebo.new_effect, 3))
print("Sous-echantillon           :", round(refute_subset.new_effect, 3))
Effet estime (dowhy, ajustement backdoor) : 1.981
Effet vrai                                : 2.000

Placebo (effet attendu ~0) : -0.001
Sous-echantillon           : 1.981

Interprétation

  • L’effet naïf (~3,49) surévalue l’effet des études : les diplômés gagnent plus en partie parce qu’ils étaient plus aptes au départ (chemin backdoor).
  • Une fois ajusté sur l’aptitude (l’ensemble backdoor \(\{U\}\)), dowhy recouvre ~1,98 ≈ effet vrai 2,0 : la chirurgie graphique a neutralisé le confondeur.
  • Les réfutations confirment la robustesse : l’effet tombe vers 0 sous placebo (un faux traitement ne donne rien) et reste stable sur un sous-échantillon.

5. Le critère front-door — quand le confondeur est inobservable

Que faire si le confondeur est inobservable (génétique, préférences latentes) ? Si l’on dispose d’un médiateur \(M\) captant tout l’effet de \(X\) sur \(Y\), le critère front-door sauve la situation :

  1. \(M\) intercepte tous les chemins dirigés de \(X\) vers \(Y\) ;
  2. il n’existe pas de chemin backdoor de \(X\) vers \(M\) ;
  3. tous les chemins backdoor de \(M\) vers \(Y\) sont bloqués par \(X\).

Alors \(P(y \mid do(x)) = \sum_m P(m \mid x) \sum_{x'} P(y \mid x', m)\, P(x')\).

Exemple : génotype (latent) → tabagisme → goudron → cancer

Un génotype \(U\) (non observé) pousse à la fois au tabagisme \(X\) et au cancer \(Y\). Impossible d’ajuster sur \(U\) : on n’a pas la donnée. Mais le tabagisme n’affecte le cancer qu’à travers le goudron \(M\), qu’on mesure. dowhy doit alors échouer à trouver une backdoor et basculer sur la front-door via le médiateur.

np.random.seed(7)
N = 20_000
genotype = np.random.normal(0, 1, N)                      # CONFOUNDEUR LATENT (non observé)
smoking  = (1.0 * genotype + np.random.normal(0, 1, N) > 0).astype(int)
tar      = 0.9 * smoking + np.random.normal(0, 0.5, N)
cancer   = 1.0 * tar + 0.8 * genotype + np.random.normal(0, 1, N)
# On n'observe QUE smoking, tar, cancer (le génotype reste caché)
df_frontdoor = pd.DataFrame({"smoking": smoking, "tar": tar, "cancer": cancer})

Le génotype \(U\) est latent : il a généré les données mais n’apparaît pas dans le DataFrame. On déclare néanmoins le graphe à dowhy en incluant \(U\) comme nœud — c’est décisif. Sans aucune variable observable pour bloquer le chemin backdoor \(X \leftarrow U \rightarrow Y\), dowhy doit échouer à trouver une backdoor et basculer sur la voie front-door à travers le médiateur tar, que l’on mesure. C’est toute la situation qui motive le critère front-door : un confondeur inobservable, un médiateur mesurable.

# Graphe : U(latent) -> smoking, smoking -> tar, tar -> cancer, U(latent) -> cancer
gml_frontdoor = '''
graph [
  directed 1
  node [ id 0 label "U" ]
  node [ id 1 label "smoking" ]
  node [ id 2 label "tar" ]
  node [ id 3 label "cancer" ]
  edge [ source 0 target 1 ]
  edge [ source 1 target 2 ]
  edge [ source 2 target 3 ]
  edge [ source 0 target 3 ]
]
'''
model_fd = CausalModel(data=df_frontdoor, treatment="smoking", outcome="cancer", graph=gml_frontdoor)
estimand_fd = model_fd.identify_effect(proceed_when_unidentifiable=True)
print(estimand_fd)
Estimand type: EstimandType.NONPARAMETRIC_ATE

### Estimand : 1
Estimand name: backdoor
No such variable(s) found!

### Estimand : 2
Estimand name: iv
No such variable(s) found!

### Estimand : 3
Estimand name: frontdoor
Estimand expression:
 ⎡  d                d            ⎤
E⎢──────(cancer)⋅──────────([tar])⎥
 ⎣d[tar]         d[smoking]       ⎦
Estimand assumption 1, Full-mediation: tar intercepts (blocks) all directed paths from smoking to c,a,n,c,e,r.
Estimand assumption 2, First-stage-unconfoundedness: If U→{smoking} and U→{tar} then P(tar|smoking,U) = P(tar|smoking)
Estimand assumption 3, Second-stage-unconfoundedness: If U→{tar} and U→cancer then P(cancer|tar, smoking, U) = P(cancer|tar, smoking)

### Estimand : 4
Estimand name: general_adjustment
No such variable(s) found!

Comme prévu, dowhy rejette la backdoor (« No such variable(s) found! » — \(U\) est inobservable) et identifie la front-door via tar. Trois hypothèses sont posées : (1) tar médie entièrement l’effet smoking \(\to\) cancer ; (2) aucun chemin backdoor de smoking vers tar ; (3) tout chemin backdoor de tar vers cancer est bloqué par smoking. L’estimande devient alors \(E\!\left[\frac{\partial\,\text{cancer}}{\partial\,\text{tar}} \cdot \frac{\partial\,\text{tar}}{\partial\,\text{smoking}}\right]\). On l’estime par la régression en deux étapes — sans jamais avoir observé le génotype.

estimate_fd = model_fd.estimate_effect(estimand_fd, method_name="frontdoor.two_stage_regression")
print(f"Effet estime (front-door, deux etapes) : {estimate_fd.value:.3f}")
print(f"Effet vrai via le mediateur             : ~0.900  (0.9 * 1.0)")
Effet estime (front-door, deux etapes) : 0.934
Effet vrai via le mediateur             : ~0.900  (0.9 * 1.0)

Interprétation

dowhy confirme le raisonnement :

  • backdoor : « No such variable(s) found! » — \(U\) est latent, aucun ajustement direct possible ;
  • front-door : l’estimande identifiée passe par le médiateur tar (\(E[\frac{\partial\, cancer}{\partial\, tar} \cdot \frac{\partial\, tar}{\partial\, smoking}]\)) ;
  • l’estimation (~0,93) recouvre l’effet vrai (~0,9) sans jamais observer le génotype. C’est toute la puissance du front-door : neutraliser un confondeur latent via un médiateur mesurable.

6. Les quatre tâches prototypiques du data-fusion

Le do-calculus ne sert pas seulement à passer de l’observationnel à l’interventionnel sur un jeu de données : Bareinboim & Pearl (Causal inference and the data-fusion problem, PNAS 2016, Fig. 1) encodent chaque design de collecte comme un triplet — population, régime (observationnel vs expérimental), méthode d’échantillonnage — et demandent quelles requêtes causales chaque design permet de répondre. Quatre tâches archétypales :

# Design Information disponible Biais à dompter
1 Observationnel, même population \(P(v)\) confondage — neutralisé par backdoor (§4)
2 Expérimental sur \(Z\) (plus accessible que \(X\)) \(P(v \mid do(z))\) extrapolation d’intervention — le cas instrumental (§5 de CausalBridges-06)
3 Essai randomisé, échantillon non représentatif \(P(v \mid do(x), S{=}1)\) sélection — l’essai dit vrai… de son échantillon
4 Source expérimentale ≠ population cible \(P(v \mid do(x))\) + \(P(v \mid S{=}s)\) transportabilité — généraliser l’effet à la cible

Les tâches 1-2 sont couvertes par les critères des sections précédentes. Les tâches 3-4 partagent un même mécanisme profond — un tiers variable (\(S\) ou le site) filtre la distribution des modificateurs d’effet — et un même remède : repondérer par la distribution de la population visée.

# Tache 3 : essai randomise sur echantillon non representatif
# Monde : Y depend de X (effet heterogene) et de Z ; l'essai randomise X, mais
# l'inclusion dans l'essai (S=1) depend de Z -- les forts Z s'enrolent plus.
import numpy as np

rng6 = np.random.default_rng(7)
N6 = 400_000
sig = lambda z: 1.0 / (1.0 + np.exp(-z))

Z6 = rng6.normal(0, 1, N6)
X6 = (rng6.uniform(0, 1, N6) < 0.5).astype(float)            # RCT : X randomise
Y6 = (0.8 + 0.4 * Z6) * X6 + rng6.normal(0, 1, N6)           # TE(Z) = 0.8 + 0.4 Z
S6 = (rng6.uniform(0, 1, N6) < sig(1.8 * Z6)).astype(int)    # selection sur Z
obs6 = S6 == 1

te_population = 0.8                                          # = 0.8 + 0.4 * E[Z], E[Z] = 0
naif6 = Y6[obs6 & (X6 == 1)].mean() - Y6[obs6 & (X6 == 0)].mean()

# Correction IPW : reponderer l'echantillon selectionne par 1/P(S=1|Z)
w6 = 1.0 / sig(1.8 * Z6[obs6])
Xo, Yo = X6[obs6], Y6[obs6]
n1, d1 = (w6 * Yo * (Xo == 1)).sum(), (w6 * (Xo == 1)).sum()
n0, d0 = (w6 * Yo * (Xo == 0)).sum(), (w6 * (Xo == 0)).sum()
ipw6 = n1 / d1 - n0 / d0

print(f"TE population     = {te_population:.3f}")
print(f"Naif (essai, S=1) = {naif6:+.3f}   <- l'essai randomise est BIAS pour la population")
print(f"IPW (1/P(S|Z))    = {ipw6:+.3f}   <- selection corrigee")
print(f"E[Z | S=1] = {Z6[obs6].mean():+.3f} vs E[Z] = {Z6.mean():+.3f} : l'echantillon sur-represente les forts Z")
TE population     = 0.800
Naif (essai, S=1) = +1.029   <- l'essai randomise est BIAS pour la population
IPW (1/P(S|Z))    = +0.808   <- selection corrigee
E[Z | S=1] = +0.578 vs E[Z] = +0.001 : l'echantillon sur-represente les forts Z

Lecture — pourquoi un essai randomisé peut être biaisé

Le contraste \(E[Y \mid X{=}1, S{=}1] - E[Y \mid X{=}0, S{=}1]\) est un ATE non biaisé pour l’échantillon (X y est randomisé), mais l’échantillon n’est pas la population : il sur-représente les forts \(Z\) (\(E[Z \mid S{=}1] = +0.58\)), et comme l’effet varie avec Z (\(TE(Z) = 0.8 + 0.4Z\)), l’effet mesuré est l’effet des volontaires, pas celui de la population. C’est le résultat des essais cliniques à recrutement sélectif. La pondération inverse \(1/P(S{=}1 \mid Z)\) reconstitue la population — à condition de connaître (ou d’estimer) la probabilité d’inclusion : en pratique, un modèle de participation.

La sélection ne mord que parce qu’elle corrige un modificateur d’effet. Sans hétérogénéité (\(0.4 \to 0\)), le contraste RCT serait resté 0.8 malgré l’échantillon bancal — retenez la condition, pas seulement la recette.

# Tache 4 : transportabilite -- generaliser un effet source vers une population cible
def site(mu_z, seed, n=400_000):
    r = np.random.default_rng(seed)
    Zs = r.normal(mu_z, 1, n)
    Xs = (r.uniform(0, 1, n) < 0.5).astype(float)             # RCT local
    Ys = (0.4 + 0.3 * Zs) * Xs + r.normal(0, 1, n)            # TE(Z) = 0.4 + 0.3 Z
    return Zs, Xs, Ys

Zs, Xs, Ys = site(0.0, seed=1)      # site source : Z ~ N(0, 1)
Zt, Xt, Yt = site(1.2, seed=2)      # population cible : Z ~ N(1.2, 1) (verite, non observable en RCT)
te_cible_exact = 0.4 + 0.3 * 1.2    # = 0.76

te_source = Ys[Xs == 1].mean() - Ys[Xs == 0].mean()           # ce que le RCT source rapporte

# Transport : TE estime PAR STRATES de Z sur la source, repondere par la
# distribution de Z dans la cible (elle, observationnelle : un recensement suffit)
K = 20
bords = np.quantile(np.concatenate([Zs, Zt]), np.linspace(0, 1, K + 1))
str_s = np.clip(np.digitize(Zs, bords[1:-1]), 0, K - 1)
str_t = np.clip(np.digitize(Zt, bords[1:-1]), 0, K - 1)
tes_strates = np.array([Ys[(str_s == k) & (Xs == 1)].mean()
                        - Ys[(str_s == k) & (Xs == 0)].mean() for k in range(K)])
poids_cible = np.array([(str_t == k).mean() for k in range(K)])
te_transporte = float((tes_strates * poids_cible).sum())

print(f"TE source (RCT)   = {te_source:+.3f}   <- vrai pour la source, faux pour la cible")
print(f"TE cible (verite) = {te_cible_exact:.3f}")
print(f"Transport K=20    = {te_transporte:+.3f}   <- RCT source + distribution de Z dans la cible")
TE source (RCT)   = +0.396   <- vrai pour la source, faux pour la cible
TE cible (verite) = 0.760
Transport K=20    = +0.750   <- RCT source + distribution de Z dans la cible

Lecture — le RCT local n’est pas un effet universel

Transplanter naïvement le chiffre de la source (+0.396) vers la cible livre la moitié de l’effet réel (0.760) : l’effet dépend de Z et la cible a \(E[Z] = 1.2\). La formule de transport est une moyenne de l’effet stratifié, repondérée par la distribution du modificateur dans la cible — une donnée observationnelle bon marché (recensement, cohortes). Tâche 3 et tâche 4 sont la même pièce vue des deux côtés :

  • tâche 3 : l’échantillon n’est pas la population → repondérer vers la population ;
  • tâche 4 : l’essai n’est pas mené dans la population → repondérer vers la cible.

Dans les deux cas, le do-calculus certifie quand la repondération suffit (quels ensembles ajuster pour que l’identification tienne) — l’identification précède l’estimation, même en terrain expérimental.

7. Le Causal Hierarchy Theorem — pourquoi L1 ne devient pas L2 tout seul

L’échelle de Pearl (§1) n’est pas une convention pédagogique : elle est théoriquement étanche. Le Causal Hierarchy Theorem (Bareinboim et al., Causal Artificial Intelligence, 2026, Thm 2.3.1) formalise le fait que la hiérarchie « presque jamais ne s’effondre » : pour presque tout SCM \(\mathcal{M}^*\), il existe un SCM \(\mathcal{M}\) qui génère exactement les mêmes distributions L1 (et même L2) mais contredit \(\mathcal{M}^*\) sur des questions contrefactuelles — et symétriquement, des paires indistinguables en L1 qui divergent dès la première intervention.

La démonstration la plus épurée tient en deux lois gaussiennes. Construisez deux SCM sur \((X, Y)\) :

  • SCM-A : \(X \sim \mathcal{N}(0,1)\), puis \(Y = 0.8X + \varepsilon\), \(\varepsilon \sim \mathcal{N}(0, 0.36)\) ;
  • SCM-B : \(Y \sim \mathcal{N}(0,1)\), puis \(X = 0.8Y + \varepsilon'\), \(\varepsilon' \sim \mathcal{N}(0, 0.36)\).

Les deux produisent la même loi jointe gaussienne (marginales standard, covariance 0.8) — donc le même tableau de données, la même regression, le même \(R^2\) — mais la flèche causale est inversée.

# CHT rendu machine : deux SCM, meme L1, verdicts interventionnels opposes
rng7 = np.random.default_rng(42)
n7 = 400_000

XA = rng7.normal(0, 1, n7); YA = 0.8 * XA + rng7.normal(0, 0.6, n7)   # X cause Y
YB = rng7.normal(0, 1, n7); XB = 0.8 * YB + rng7.normal(0, 0.6, n7)   # Y cause X

print("Ce que voit un observateur L1 (meme tableau de données, au bruit Monte-Carlo pres) :")
print(f"  SCM-A : std(X) = {XA.std():.3f}, std(Y) = {YA.std():.3f}, cov(X,Y) = {np.cov(XA, YA)[0,1]:+.3f}")
print(f"  SCM-B : std(X) = {XB.std():.3f}, std(Y) = {YB.std():.3f}, cov(X,Y) = {np.cov(XB, YB)[0,1]:+.3f}")
print(f"  regresseur E[Y|X~1] : SCM-A {YA[(XA > 0.99) & (XA < 1.01)].mean():+.3f} vs SCM-B {YB[(XB > 0.99) & (XB < 1.01)].mean():+.3f}")

print("\nCe que dit do(X=1) (invisible en L1, oppose entre les deux mondes) :")
print(f"  SCM-A : E[Y | do(X=1)] = {0.8 * 1.0:+.3f}   (X cause Y : l'intervention deplace Y)")
print(f"  SCM-B : E[Y | do(X=1)] = {YB.mean():+.3f}   (X n'a aucun effet sur Y)")

# Verification par intervention SIMULEE (pas par theorie) : re-simuler sous do(X=1)
eps_do = rng7.normal(0, 1, n7)          # bruit de Y, independant de X dans les deux mondes
print(f"  re-simulation do(X=1) : SCM-A {np.mean(0.8 * 1.0 + rng7.normal(0, 0.6, n7)):+.3f}"
      f" | SCM-B {eps_do.mean():+.3f}")
Ce que voit un observateur L1 (meme tableau de données, au bruit Monte-Carlo pres) :
  SCM-A : std(X) = 1.001, std(Y) = 1.002, cov(X,Y) = +0.803
  SCM-B : std(X) = 0.999, std(Y) = 0.999, cov(X,Y) = +0.798
  regresseur E[Y|X~1] : SCM-A +0.819 vs SCM-B +0.786

Ce que dit do(X=1) (invisible en L1, oppose entre les deux mondes) :
  SCM-A : E[Y | do(X=1)] = +0.800   (X cause Y : l'intervention deplace Y)
  SCM-B : E[Y | do(X=1)] = +0.002   (X n'a aucun effet sur Y)
  re-simulation do(X=1) : SCM-A +0.800 | SCM-B +0.000

Lecture — l’information qui manque n’est pas dans les données

Même covariance, mêmes marginales : aucun algorithme entraîné sur le tableau L1 ne peut départager les deux mondes — l’information n’y est pas, ce n’est pas une question de taille d’échantillon ou de puissance de modèle. C’est la teneur du CHT : monter de L1 à L2 exige une hypothèse causale externe aux données (le graphe, l’ordre causal, une variable instrumentale, un essai). C’est pourquoi :

  • « laisser les données parler » ne peut pas, en principe, produire une conclusion interventionnelle ;
  • les régularités apprises en L1 (tout l’apprentissage statistique supervisé) restent aveugles aux régimes — elles prédisent \(P(y \mid x)\), pas \(P(y \mid do(x))\) ;
  • le do-calculus n’est pas un outillage optionnel au-dessus du machine learning : il est la seule porte entre ce que les données contiennent et ce qu’on demande à un système causal.

Le pont pratique avec le §3 : dowhy exige un graphe en entrée — ce n’est pas un défaut d’ergonomie, c’est le CHT payé d’avance.

8. La jonction XAI ↔︎ Pearl — choisir une baseline d’attribution, c’est choisir un estimand

Les valeurs de Shapley (SHAP) décomposent \(f(x) - \text{baseline}\) en sommant des contributions marginales. Pour un modèle à features corrélées, deux conventions s’affrontent, et le choix est exactement le choix voir vs intervenir :

  • attribution interventionnelle : la feature est forcée (\(do\)), indépendamment des autres — la baseline est une marginale ; l’attribution de \(A\) mesure son effet causal dans le modèle (c’est l’esprit « interventional SHAP » de Janzing et al. 2020 / Aas et al. 2021, et le mode par défaut de TreeSHAP) ;
  • attribution conditionnelle : la feature est observée à sa valeur, les autres suivent leur distribution conditionnelle — l’attribution de \(A\) absorbe aussi les corrélations de \(A\) avec le reste : elle mesure une association, pas un effet.

Et ce ne sont que les deux premiers étages de l’échelle d’attribution — la section 8bis monte au troisième (L3 SV contre-factuelles, Def/Thm 6.2.6 de Bareinboim 2026).

Sur un monde linéaire avec confondeur, l’écart entre les deux se calcule au cordeau — et c’est exactement l’écart TV/TE de l’analyse d’équité causale (notebook Causal-Fairness, Epic #16620) :

# La meme quantite sous les deux baselines : effondrement de la jonction sur un monde lineaire
# Monde d'embauche minimal : Z (SES) -> A (protege) -> W -> Y ; Z -> W ; Z -> Y
rng8 = np.random.default_rng(42)
n8 = 200_000
Z8 = rng8.normal(0, 1, n8)
A8 = (rng8.uniform(0, 1, n8) < 1.0 / (1.0 + np.exp(-0.9 * Z8))).astype(float)
W8 = 0.5 * A8 + 0.6 * Z8 + rng8.normal(0, 1, n8)
Y8 = 0.3 * A8 + 0.4 * W8 + 0.2 * Z8 + rng8.normal(0, 1, n8)

# Baseline interventionnelle : A force, bruit ET confondeur ne suivent pas -> effet causal
Y_do1 = 0.3 * 1.0 + 0.4 * (0.5 * 1.0 + 0.6 * Z8) + 0.2 * Z8 + rng8.normal(0, 1, n8)
Y_do0 = 0.3 * 0.0 + 0.4 * (0.5 * 0.0 + 0.6 * Z8) + 0.2 * Z8 + rng8.normal(0, 1, n8)
attr_do = float(Y_do1.mean() - Y_do0.mean())

# Baseline conditionnelle : A observe -> le contraste observationnel absorbe la selection
attr_see = float(Y8[A8 == 1].mean() - Y8[A8 == 0].mean())

print(f"Attribution interventionnelle (baseline do) : {attr_do:+.4f}   = effet causal total (TE)")
print(f"Attribution conditionnelle (baseline voir)  : {attr_see:+.4f}   = variation totale (TV)")
print(f"Ecart = {attr_see - attr_do:+.4f} : le chemin spurieux Z -> A, facture a la feature A")
print("\nL'attribution 'voir' accuse la feature de corriger le monde ; l'attribution 'do'")
print("lui fait porter seulement ce que le modele fait d'elle.")
Attribution interventionnelle (baseline do) : +0.5006   = effet causal total (TE)
Attribution conditionnelle (baseline voir)  : +0.8438   = variation totale (TV)
Ecart = +0.3432 : le chemin spurieux Z -> A, facture a la feature A

L'attribution 'voir' accuse la feature de corriger le monde ; l'attribution 'do'
lui fait porter seulement ce que le modele fait d'elle.

Lecture — le grain d’asymétrie d’attribution

L’écart mesuré (≈ +0.34 ici) n’est ni un bug ni un détail d’implémentation : c’est la part d’association que la baseline voir impute à la feature via ses corrélations — le chemin \(Z \to A\) que ni le modèle ni la feature ne « possèdent ». Choisir une convention d’attribution, c’est donc répondre à une question causale :

  • « combien la prédiction changerait-elle si on forçait cette feature ? » → baseline do (interventionnelle) ;
  • « combien la prédiction est-elle associée à cette feature, monde corrélé compris ? » → baseline voir (conditionnelle).

SHAP n’est pas la causalité — mais le choix de sa baseline est un estimand causal déguisé. C’est la jonction formelle entre la série XAI (attribution par instance : 2.14-Explicabilite, PR ouverte) et ce pont : les deux disciplines résolvent le même problème d’allocation de responsabilité (à des features ou à des chemins), avec les mêmes pièges de corrélations, et le do-calculus fournit le langage pour dire précisément quoi on alloue. L’Epic #16620 déplie cette jonction : décomposition d’équité (famille TV, notebook Causal-Fairness), attribution par instance (ML-4 Tree SHAP), et le présent pont.

8bis. L’attribution ne s’arrête pas à L2 — les valeurs de Shapley contre-factuelles (L3 SV)

La jonction XAI ↔︎ Pearl de la section 8 s’arrête au niveau interventionnel : la baseline do attribue à \(A\) son effet causal moyen (+0.50 ci-dessus) — le même chiffre pour chaque individu. Bareinboim pousse l’échelle un étage plus haut (Causal Artificial Intelligence, 2026, § 6.2) :

  • GDE (Def 6.2.5, p. 494) : la contribution marginale de \(X\) quand le sous-ensemble \(Z\) varie naturellement entre unités — \(GDE_Z(X,Y \mid v) = NTE(\{X\} \cup Z, Y \mid v) - NTE(Z, Y \mid v)\), où le NTE contre-factuel fixe l’unité \(u\) et laisse les variables de \(Z\) suivre leur marginale naturelle ;
  • Théorème 6.2.1 (p. 496) : pour tout ordre \(\pi\), le NTE se décompose exactement en \(\sum_i GDE_{\pi_{<i}}(X_i, Y \mid v)\) — le \(Z\)-set résout la dépendance à l’ordre exactement comme les coalitions de Shapley ;
  • L3 SV (Def 6.2.6, p. 496) : \(\phi^{L3}_X(v) = \mathbb{E}_{\pi}[GDE_{\pi_{<X}}(X, Y \mid v)]\) — la moyenne sur les ordres, propre à l’unité \(v\) (abduction–action–prédiction : le bruit de CETTE unité est conservé) ;
  • Théorème 6.2.6 (p. 502) : les L3 SV satisfont simultanément causal admissibility, causal explanatory power et causal normality — aucun des attributors concurrents (LIME, SHAP classique, effets totaux, actual causation) ne les satisfait tous (Table 6.3).

C’est la réponse à la question laissée ouverte en section 8 : l’attribution interventionnelle est un moyen, la L3 SV est une explication individuelle — elle dit ce que CETTE unité doit à \(A\), en gardant son contexte \(Z^*\) et ses bruits. On la calcule au cordeau sur le même monde linéaire (abduction triviale : contre-factuel = même bruit, équations modifiées).

# L3 Shapley values (Def 6.2.6) : GDE sur permutations, contre-factuel propre a l'unite
# Memes equations que la section 8 : Z -> A -> W -> Y ; Z -> W ; Z -> Y.
from itertools import permutations

rng8b = np.random.default_rng(7)

def draw_unit(rng):
    Z = rng.normal(0, 1)
    A = float(rng.uniform(0, 1) < 1.0 / (1.0 + np.exp(-0.9 * Z)))
    eW = rng.normal(0, 1)
    W = 0.5 * A + 0.6 * Z + eW
    eY = rng.normal(0, 1)
    Y = 0.3 * A + 0.4 * W + 0.2 * Z + eY
    return dict(Z=Z, A=A, W=W, Y=Y, eW=eW, eY=eY)

def Y_cf(u, do=None):
    # Action-prediction : variables de `do` forcees, exogenes de u conserves (abduction lineaire)
    Z, A = u["Z"], u["A"]
    if do and "Z" in do: Z = do["Z"]
    if do and "A" in do: A = do["A"]
    W = 0.5 * A + 0.6 * Z + u["eW"]
    if do and "W" in do: W = do["W"]
    return 0.3 * A + 0.4 * W + 0.2 * Z + u["eY"]

def NTE(u, S, rng, m=400_000):
    # NTE(S,Y|v) = Y(u) - E_{s~P(S)}[Y_s(u)] : s tire de la marginale naturelle JOINTE
    if not S:
        return 0.0
    Zs = rng.normal(0, 1, m)
    As = (rng.uniform(0, 1, m) < 1.0 / (1.0 + np.exp(-0.9 * Zs))).astype(float)
    Ws = 0.5 * As + 0.6 * Zs + rng.normal(0, 1, m)
    vals = {"Z": Zs, "A": As, "W": Ws}
    return u["Y"] - float(np.mean(Y_cf(u, {k: vals[k] for k in S})))

def GDE(u, X, Zset, rng):
    return NTE(u, set(Zset) | {X}, rng) - NTE(u, set(Zset), rng)

def l3_sv(u, feats=("Z", "A", "W"), rng=None, check=True):
    phis = dict.fromkeys(feats, 0.0)
    perms = list(permutations(feats))
    for pi in perms:
        for i, X in enumerate(pi):
            phis[X] += GDE(u, X, set(pi[:i]), rng)
    phis = {f: v / len(perms) for f, v in phis.items()}
    if check:  # Thm 6.2.1 : NTE(Z,Y|v) = somme des GDE pour TOUT ordre
        nte_full = NTE(u, set(feats), rng)
        assert all(abs(sum(GDE(u, X, set(pi[:pi.index(X)]), rng) for X in pi) - nte_full) < 5e-3
                   for pi in perms), "decomposition 6.2.1 rompue"
    return phis

rng_u = np.random.default_rng(11)
units = [draw_unit(rng_u) for _ in range(200)]
u_A1 = next(u for u in units if u["A"] == 1.0 and u["Z"] > 0.5)   # embauche, SES eleve
u_A0 = next(u for u in units if u["A"] == 0.0 and u["Z"] < -0.5)  # refusee, SES faible

for u in (u_A1, u_A0):
    phis = l3_sv(u, rng=np.random.default_rng(13))
    total = sum(phis.values())
    nte_full = NTE(u, {"Z", "A", "W"}, np.random.default_rng(17))
    print(f"Unite A={u['A']:.0f} Z*={u['Z']:+.3f} W*={u['W']:+.3f} Y*={u['Y']:+.3f}")
    for f, v in phis.items():
        print(f"  phi_L3({f}) = {v:+.4f}")
    print(f"  Somme = {total:+.4f} | NTE(Z,A,W | v) = {nte_full:+.4f}  (efficacite, Thm 6.2.1)")
    print(f"  phi_L3(A) seul = {phis['A']:+.4f} vs TE moyen (section 8, do) = +0.5006 constant")
    print()
Unite A=1 Z*=+0.747 W*=+2.515 Y*=+1.359
  phi_L3(Z) = +0.2388
  phi_L3(A) = +0.2000
  phi_L3(W) = +0.7665
  Somme = +1.2052 | NTE(Z,A,W | v) = +1.2043  (efficacite, Thm 6.2.1)
  phi_L3(A) seul = +0.2000 vs TE moyen (section 8, do) = +0.5006 constant

Unite A=0 Z*=-1.920 W*=-1.620 Y*=-2.225
  phi_L3(Z) = -0.6147
  phi_L3(A) = -0.2000
  phi_L3(W) = -0.4672
  Somme = -1.2820 | NTE(Z,A,W | v) = -1.2830  (efficacite, Thm 6.2.1)
  phi_L3(A) seul = -0.2000 vs TE moyen (section 8, do) = +0.5006 constant

Lecture — l’attribution L3 est propre à l’unité, l’attribution L2 est une moyenne

Les deux unités reçoivent le même effet interventionnel moyen (+0.50 : c’est une propriété du modèle, pas de l’individu) mais des L3 SV différentes : l’unité à SES élevé et l’unité à SES faible ne doivent pas la même chose à \(A\) — leur contexte \(Z^*\) et leurs bruits \(\varepsilon_W, \varepsilon_Y\), conservés par l’abduction, pèsent dans la décomposition. C’est exactement la hiérarchie de Pearl rendue visible dans l’attribution : L2 ne peut pas dire ça (le do moyen efface l’unité), L1 non plus (le conditionnement mélange association et effet — écart +0.34 de la section 8).

La vérification imprimée (somme des \(\phi^{L3}\) = \(NTE(Z,A,W \mid v)\) à la précision Monte-Carlo) est le Théorème 6.2.1 en action : la décomposition en GDE est exacte pour chaque ordre, la moyenne sur les ordres hérite de l’exactitude — la propriété d’efficacité de Shapley, obtenue causalement.

Limite honnête : l’abduction est triviale ici (SCM linéaire gaussien : bruit = résidu). Sur un modèle profond, l’étape d’abduction (inférer le \(u\) compatible avec l’observation) devient le coût dominant — c’est le prix de l’étage L3, et la raison pour laquelle les attributions SHAP déployées restent presque toutes L2. Le Théorème 6.2.6 reste la référence : admissibilité causale + pouvoir explicatif causal + normalité causale, les trois ensemble, uniquement au niveau 3.

9. Comment chaque moteur instancie le do-calculus

Les quatre séries implantent le même formalisme (§1-2) avec des outils différents. Partout, \(do(X=x)\) se traduit par une mutilation du graphe : on supprime les arêtes entrant dans \(X\) (déconnexion du mécanisme générant \(X\)) puis on fige \(X=x\).

Série Moteur do(X) opérationnellement Angle couvert
Infer-5 Infer.NET mutilation + message passing exact backdoor, front-door, Simpson, médiation
PyMC-5 PyMC mutilation + échantillonnage MCMC backdoor, front-door, contrefactuel bayésien
Tweety-11 Tweety (.NET) backend causal logique, opérateur do natif SCM, contrefactuels
Ce pont dowhy identification + estimation + réfutation formalisme unifié + outil SOTA

Le présent notebook est complémentaire : là où Infer-5 et PyMC-5 instrumentent le do à la main sur leur moteur, dowhy automatise l’identification (lit le graphe, choisit backdoor/front-door/IV) puis estime et réfute — le pipeline qu’on utiliserait en pratique pour une vraie étude causale.

10. Deux paradigmes : Pearl (intervention) vs Hoel (émergence causale)

Les notebooks ICT-5 et ICT-6 défendent une thèse différente et complémentaire : la causalité émergente d’Erik Hoel. Là où Pearl demande « quel est l’effet d’une intervention sur \(X\) ? » au sein d’un graphe fixé, Hoel demande « quelle échelle de description du système porte le plus de causalité ? ».

Le formalisme de Hoel repose sur l’information effective (EI), mesurée sur la matrice de transition d’un système :

\[\text{EI} = \underbrace{\text{déterminisme}}_{\text{le futur est-il prévisible ?}} \;-\; \underbrace{\text{dégénérescence}}_{\text{plusieurs passés} \to \text{même futur}}.\]

Un coarse-graining (regroupement de micro-états en macro-état) peut augmenter l’EI : la macro-échelle est alors plus causale que la micro. ICT-5 le démontre sur le réseau canonique de PyPhi (EI passe de ~0,11 à ~0,60).

Pearl (do-calculus) Hoel (émergence causale)
Question Effet d’une intervention ? Quelle échelle cause le plus ?
Objet un graphe causal fixé l’échelle de description optimale
Outil dowhy, Infer.NET, PyMC PyPhi (CE 2.0)
Causalité vue comme chirurgie du graphe (\(do\)) information effective (EI)

Pont conceptuel : les deux cadres répondent à « où est la causalité ? » — Pearl la localise dans les flèches qu’on coupe, Hoel dans l’échelle qui maximise l’information sur le futur. Un système peut admettre une description causale riche au sens de Hoel (forte EI macro) tout en se prêtant au do-calculus de Pearl au niveau micro : ce sont deux loupes différentes, non concurrentes.

Du simple coarse-graining au multiscale : l’émergence causale 2.0 (Jansma & Hoel, 2025)

La formulation originelle de Hoel (2017) compare deux échelles — le micro et un macro — et constate que le macro peut porter davantage d’information effective (EI). Jansma & Hoel (Engineering Emergence, 2025) généralisent le cadre à une hiérarchie complète d’échelles : c’est l’émergence causale 2.0, mise en œuvre numériquement dans ICT-5 et ICT-6.

Extension CE 2.0 Ce qu’elle change par rapport à Hoel 2017
Hiérarchie multiescale La causalité n’est plus comparée entre deux niveaux seulement, mais à travers toute la hiérarchie de description (ex. matériel → code machine → système d’exploitation). Le « bon » niveau n’est plus nécessairement le macro absolu : c’est celui qui maximise l’EI dans la hiérarchie.
Taxonomie top-heavy / bottom-heavy Selon l’endroit où se concentrent les contributions causales, un système est dit top-heavy (la causalité domine aux grandes échelles) ou bottom-heavy (elle domine au micro). Ce vocabulaire classifie les systèmes, au-delà du verdict binaire « émerge / n’émerge pas » de la version 1.0.
Scale-freeness causale Une mesure de complexité fondée sur une notion littérale de sans-échelle : la causation est répartie également à travers les échelles. Les auteurs la relient à la scale-freeness des réseaux (distribution de degré en loi de puissance).

Ce que cela change pour le pont. Le do-calculus de Pearl opère à une échelle fixée — on intervient sur un graphe donné, quelle que soit l’échelle de description. L’émergence causale 2.0 demande, elle, à quelle échelle se concentre la causalité, et admet qu’un même système soit top-heavy (description macro privilégiée) tout en restant entièrement soumis au do-calculus au niveau micro. Les deux cadres restent complémentaires : Pearl localise la causalité dans les flèches qu’on coupe ; Jansma–Hoel dans l’échelle qui maximise l’information sur le futur. La mise en œuvre chiffrée (recherche exhaustive de l’échelle émergente via pyphi.macro, démonstration que l’émergence n’est pas automatique) est dans ICT-5.

11. Causal primitives communes : Pearl, Hoel, et les mesures classiques se ramènent au même vocabulaire

Comolatti & Hoel (2022, Causal emergence is widespread across measures of causation, arXiv:2202.01854) montrent que toutes les mesures majeures de la causalité se décomposent dans un petit jeu de primitives causales communes — et que ces mêmes primitives sont déjà au cœur du couple Pearl/Hoel présenté en §7. C’est le pont formel manquant entre les deux paradigmes.

8.1 Les deux primitives (axes orthogonaux)

Pour une cause candidate \(c\) et un effet candidat \(e\) dans un ensemble de causes \(C\) et d’effets \(E\) :

Primitive Symbole Lecture
Sufficiency \(\mathrm{suff}(e; c) = P(e \mid c)\) Quand \(c\) se produit, \(e\) suit-il ?
Necessity \(\mathrm{nec}(e; c) = P(C_{\neg c} \mid e)\) Les autres causes peuvent-elles aussi produire \(e\) ?

Les deux sont orthogonales (Comolatti & Hoel §2.1) : \(c\) peut être suffisant et non-nécessaire (d’autres causes produisent aussi \(e\)) ; ou nécessaire et non-suffisant (à lui seul \(c\) ne déclenche pas \(e\) à coup sûr).

8.2 Extension information-théorique : déterminisme et dégénérescence

Hoel utilise les versions entropiques (qui moyennent sur l’ensemble des effets ou des causes) :

\[\det(c) = 1 - \frac{H(e \mid c)}{\log_2 n}, \qquad \deg(e) = 1 - \frac{H(e \mid C)}{\log_2 n}\]

  • Déterminisme = certitude des effets d’une cause \(c\) (faible entropie sur \(e \mid c\)). Différent de la simple sufficiency : 4 effets équiprobables depuis \(c\) donnent \(\mathrm{suff}=1/4\) chacune mais \(\det=0\).
  • Dégénérescence = généralisation information-théorique de la necessity : plusieurs causes convergent vers le même effet.

Information effective (EI) = det − deg : c’est exactement la formule Hoel de §7. Les deux paradigmes Pearl (intervention) et Hoel (émergence) ne sont donc pas des vocabulaires distincts : ce sont deux fenêtres différentes sur le même couple (sufficiency, necessity) — Pearl en intervention, Hoel en entropie.

8.3 Toutes les mesures classiques se décomposent en (suff, nec)

Mesure Auteur Formule Décomposition
Probability raising Eells (1991) \(P(e \mid c) - P(e \mid C_{\neg c})\) \(\mathrm{suff}(e; c) + \mathrm{nec}(e; c) - 1\)
Probability raising Suppes (1970) \(P(e \mid c) - P(e \mid C)\) \(\mathrm{suff}(e; c) - \mathrm{nec}(e; c)\)
Relative risk Lewis (1973) \(\frac{P(e \mid c)}{P(e \mid C_{\neg c})}\) \(\frac{\mathrm{suff} + \mathrm{nec} - 1}{\mathrm{suff}}\)
PN (prob. necessity) Pearl (1999) \(P(\neg e \mid \neg c; e)\) expression contrefactuelle en suff+nec
PS (prob. sufficiency) Pearl (1999) \(P(e \mid c; \neg e)\) expression contrefactuelle en suff+nec

Le résultat de consilience (Comolatti & Hoel §3) : des mesures développées indépendamment dans philo, statistique, psycho, génétique — toutes — sont fonctions du même couple de primitives. Ce n’est pas une coïncidence, c’est la signature d’un fait général sur les relations causales.

8.4 Pont avec le notebook

ICT-06-SortingToTPM-CausalEmergence-Python.ipynb calcule déjà \(\det\) et \(\deg\) sur la matrice de transition d’un système de votes (S4 de la constellation ICT). En passant au do-calculus :

  • Le set d’ajustement backdoor vise à rendre les conditionnements sur \(c\) aussi proches que possible de \(P(e \mid do(c))\) — c’est neutraliser la dégénérescence non-contrôlée (mêmes effets par d’autres causes latentes).
  • Le médiateur front-door découple le chemin \(c \to e\) en deux segments où la sufficiency reste valable localement.

En ce sens, les trois stratégies du do-calculus sont des différents contrôles sur le couple (suff, nec) : ajuster les causes dégénérées, garantir une médiation suffisante, séparer observation et intervention.

8.5 Limite honnête

Le cadre des primitives suppose qu’on peut définir une distribution \(P(C)\) sur l’ensemble des causes (ce qu’on appelle “intervention distribution” dans le papier). Sous distribution observationnelle pure, la sufficiency et la necessity ne sont pas identifiées séparément — elles se confondent en leur combinaison visible \(P(e \mid c)\). Les interventions max-ent de Hoel (et d’ICT-6) résolvent ce décalage, mais au prix d’une plausibilité limitée si le système réel n’a pas une distribution uniforme sur les causes.

C’est pourquoi le verdict “émergence causale” dépend du système et de l’intervention choisie : deux systèmes équivalents observationnellement peuvent diverger sous intervention. La leçon vaut autant pour Pearl (l’estimande identifié dépend des variables observables) que pour Hoel (l’EI dépend de la TPM impliquée).

12. Cinquième modalité : le raisonnement causal par connaissance (LLM)

Les quatre séries de la constellation — et dowhy dans ce pont — estiment toutes la causalité à partir des valeurs de données : covariances, distributions conditionnelles, matrices de transition. Kıcıman et al. (2023) explorent la modalité complémentaire : un LLM qui raisonne à partir des métadonnées et des connaissances — noms de variables, contexte de domaine, sémantique des relations — sans voir une seule valeur de donnée. Les erreurs de cette modalité diffèrent de celles des méthodes fondées sur la covariance, ce qui motive un usage hybride plutôt qu’un remplacement.

Source canonique archivée : G:\Mon Drive\MyIA\IA\Bibliographie IA\Probabilistic\2023 - Kiciman et al - Causal Reasoning and Large Language Models.pdf — E. Kıcıman, R. O. Ness, A. Sharma, C. Tan, Causal Reasoning and Large Language Models: Opening a New Frontier for Causality, arXiv:2305.00050v3 (2023). Chaque nombre ci-dessous est attribué à son modèle, dataset et protocole d’origine.

9.1 Trois résultats du papier, chaque nombre attribué

Tâche (dataset) Modèle et protocole Résultat mesuré Réf.
Découverte causale par paires — Tübingen cause-effect pairs (Mooij et al. 2016 : 108 paires issues de 37 jeux de données) gpt-4, prompt unique « step-by-step » ; précision pondérée (pondération recommandée par Mooij et al. contre le surcomptage de paires similaires) 97 % pondéré (96 % brut) contre 82 % pondéré (83 % brut) pour Mosaic (Wu & Fukumizu 2020), meilleure baseline covariance-based Table 2
Découverte du graphe complet — Neuropathic pain (221 variables) gpt-3.5-turbo, prompt unique ; F1 sur les arêtes orientées F1 = 0,68 (precision 0,66 / recall 0,71) ; le même modèle avec le prompt naïf de Tu et al. (2023) obtient F1 0,21, sous la baseline aléatoire (0,33) Table 6
Raisonnement contrefactuel — CRASS (Frohberg & Binder 2022) gpt-4 92,4 % contre 83,9 % pour text-davinci-003 (meilleur score rapporté par les auteurs du benchmark) ; annotateurs humains 98,2 % Table 9

Nuance obligatoire : sur la tâche la plus difficile (graphe complet), les auteurs concluent que les LLM atteignent une précision similaire aux méthodes récentes d’apprentissage profond — pas supérieure. L’avantage net du tableau n’existe qu’en régime métadonnées-seules ; dès qu’il s’agit d’estimer l’amplitude d’un effet ou de l’identifier depuis des données, le do-calculus (§2) et ses critères (§4-5) restent l’arbitre.

9.2 Deux contre-mesures d’honnêteté

  1. Sensibilité au prompt. Chaque performance ci-dessus est une propriété du couple (modèle, prompt), pas du modèle seul : sur le graphe de douleur, changer seulement le prompt fait passer F1 de 0,21 à 0,68 (même modèle) ; sur Tübingen, la persona « agent of causal reasoning » gagne environ 5 points à gpt-3.5-turbo (86,9 % pondéré). Un chiffre LLM cité sans son prompt n’est pas reproductible.
  2. Échecs imprévisibles. Les auteurs documentent des modes d’échec imprévisibles : un LLM énonce une arête fausse avec la même assurance qu’une arête correcte — sans borne de confiance ni diagnostic d’erreur. Une vérification anti-mémorisation (datasets publiés après la date de coupure d’entraînement des modèles) atteste néanmoins que la capacité ne se réduit pas à de la mémorisation : les scores tiennent sur des données nouvelles.

9.3 Conclusion pédagogique : l’hybride, pas le remplacement

Les erreurs des deux familles ne se recouvrent pas : le LLM se trompe là où la connaissance du domaine manque ou induit en erreur, l’algorithme là où les valeurs seules laissent la direction ambiguë (classe d’équivalence de Markov). La leçon pour la constellation : utiliser le LLM comme oracle de connaissances — proposer des arêtes candidates, écarter les directions absurdes du domaine — puis laisser l’estimation causale sur données (backdoor/front-door de §4-5, Infer.NET, PyMC) identifier et estimer ce que les données soutiennent. La cinquième modalité ne remplace pas les quatre : elle répond à « que dit notre connaissance de la structure plausible ? » là où les §1-5 demandent « qu’est-ce que les données identifient ? »

Un benchmark local (Qwen) de cette modalité sur nos propres paires appartient à un futur sous-grain de l’EPIC ; aucune mesure locale n’est présentée ici comme réplication des scores GPT-4.

13. Exercices

Les exercices suivent la convention du dépôt (stub à compléter). Aucun ne lève d’erreur : remplacez None par votre code.

Exercice 1 — Ajouter un second confondeur à la backdoor

Ajoutez un confondeur observable motivation (qui cause college et earnings) au modèle backdoor, puis demandez à dowhy d’identifier l’estimande. L’ensemble backdoor doit maintenant contenir {aptitude, motivation}.

Exercice 2 — Rompre le critère front-door

Modifiez le graphe front-door en ajoutant un chemin direct smoking -> cancer (contournant le médiateur tar). Vérifiez que le critère front-door échoue à identifier l’effet — l’hypothèse de médiation complète est rompue.

Exercice 3 — Comparer effet naïf et effet ajusté sur un nouveau jeu

Générez un nouveau jeu backdoor avec un effet vrai de 5,0 et un confondeur plus fort (coefficient 2,5 sur l’aptitude). Calculez l’effet naïf puis l’effet ajusté, et commentez l’écart relatif.

# Exercice 1 — ajoutez un 2e confondeur observable au modèle backdoor.
# Objectif : dowhy doit identifier l'ensemble backdoor {aptitude, motivation}.
# Indice : motivation -> college ET motivation -> earnings (mêmes flèches que aptitude).
# TODO etudiant : construisez df_ex1, le graphe gml_ex1, puis identifiez l'estimande.
df_ex1 = None        # TODO etudiant
gml_ex1 = None       # TODO etudiant
estimand_ex1 = None  # TODO etudiant
print("Exercice 1 a completer")
Exercice 1 a completer
# Exercice 2 — ajoutez un chemin direct smoking -> cancer (médiation rompue).
# Objectif : montrer que l'identification front-door échoue.
# Indice : ajoutez 'edge [ source 1 target 3 ]' au graphe gml_frontdoor.
# TODO etudiant : définissez gml_ex2 avec ce chemin direct, identifiez l'effet.
gml_ex2 = None  # TODO etudiant
print("Exercice 2 a completer")
Exercice 2 a completer
# Exercice 3 — nouvel effet vrai = 5.0, confondeur plus fort (2.5*aptitude).
# Objectif : comparer effet naïf vs ajusté, commenter l'écart relatif.
# TODO etudiant : générez les données, calculez naive puis estimate_bd, affichez l'écart.
print("Exercice 3 a completer")
Exercice 3 a completer

Exercice 4 — Le compromis biais-variance du transport stratifié

Reprenez le transport de la tâche 4 avec \(K \in \{3, 20, 100\}\) strates : comment l’estimé évolue-t-il ? Prédisez d’abord (strates grossières = biais résiduel de modification intra-strate ; strates fines = variance Monte-Carlo par strate), puis mesurez.

# Exercice 4 : transport stratifie a K = 3, 20, 100 strates
# Etapes :
#   1. Re-utiliser le monde de la tache 4 (site() ci-dessus, memes seeds)
#   2. Boucler sur K, recalculer te_transporte pour chaque valeur
#   3. Comparer a te_cible_exact = 0.76 : biais a petit K, variance a grand K ?
resultat_ex4 = None  # TODO etudiant : dict {K: te_transporte} + une phrase de lecture

14. Synthèse

Vous avez parcouru l’armature formelle partagée par les quatre séries causales du dépôt :

  • Échelle de Pearl (§1) : observation < intervention < contrefactuel — le do-calculus opère le saut \(P(y\mid x) \to P(y\mid do(x))\).
  • Trois règles (§2) : chacune est une chirurgie du graphe + un test de \(d\)-séparation.
  • Backdoor (§4) : ajustement sur les confondeurs observables — exécuté par dowhy, recouvre l’effet vrai.
  • Front-door (§5) : sauvetage par médiateur quand le confondeur est latent.
  • Data-fusion (§6) : les quatre designs archétypaux — sélection et transportabilité corrigées par repondération du modificateur d’effet.
  • CHT (§7) : deux SCM à loi jointe identique et interventions opposées — L1 ne devient pas L2 sans hypothèse causale externe.
  • Jonction XAI (§8) : baseline d’attribution do vs voir — l’écart mesure le chemin spurieux facturé à la feature.
  • Pearl vs Hoel (§10) : intervention sur un graphe fixé vs échelle de description optimale (information effective).
  • Connaissance/LLM (§9) : cinquième modalité — raisonnement causal sur les seules métadonnées (Kıcıman et al. 2023) ; prompt-sensible, échecs imprévisibles, à hybrider avec l’estimation sur données.

Constellation causale — où approfondir

Direction Notebook Ce qu’il apporte de plus
Logique + SCM + contrefactuels Tweety-11 backend causal .NET, opérateur do natif
Message passing exact Infer-5 Simpson, médiation, capstone contrefactuel
MCMC bayésien PyMC-5 incertitude postérieure sur l’effet
Émergence causale (Hoel) ICT-5, ICT-6 information effective, coarse-graining

Pont suivant (après merge de ce notebook) : câbler les quatre notebooks en aller-retour vers ce pont, pour que chaque série renvoie ici pour le formalisme unifié.

Retour au sommet