Quotients, fibres et recollement : ce qui survit à la projection, ce qui ne vit que dans les fibres
Corpus bayésien / information théorique. Notebook d’application autonome (5ᵉ de la saison « protocoles d’analyse », kernel Python, sans Infer.NET) : il sort de la famille inférentielle stricte pour attaquer un problème de représentation : deux observations du même phénomène peuvent-elles être comparées autrement que par « laquelle est plus proche de la vérité » ? Son squelette entropique — la règle de chaîne H(X) = H(π(X)) + H(X | π(X)) — est l’opération « quotienter / fibrer » de la table ICT, dont la première attestation du dépôt est ANALYSE-04 (Lean-20b) ; #18405 examine si ce notebook en est la seconde. Ex-Infer-20 du corpus Infer.NET, déménagé puis renommé (#18406).
Le protocole proposé a quatre temps — et le temps 4 ne s’atteint que si le temps 3 a produit un quotient opérationnel.
Pourquoi ce notebook existe. Le premier protocole Čech du dépôt demandait, sur un recouvrement, X_j ≈ a·X_i + b, et mesurait un résidu. Le remède n’est pas de remplacer l’affine par du non-linéaire plus sophistiqué — ce serait repeindre le même jouet. C’est de changer le protocole.
La direction vient de la proper forcing, ou plus largement de la théorie de l’information conditionnelle : une projection vers un quotientπ s’analyse conjointement avec l’information conditionnelle qui reste dans les fibresX | π(X).
X ⟶ π(X) + X | π(X)
La question devient : qu’est-ce qui SURVIT dans la représentation grossière, et qu’est-ce qui NE VIT QUE dans les fibres locales ?
Et la comparaison entre deux représentations se retourne : la bonne mesure n’est peut-être pas la proximité de leurs valeurs, mais la quantité de structure informationnelle supplémentaire produite quand on les met en relation — via information mutuelle, divergences distributionnelles, transports composés.
Plan
Temps 1 — Construire deux représentationsX₁ et X₂ d’un même phénomène (gaussiennes bivariées corrélées).
Temps 2 — Sur l’intersection, mesurer le commun / le conditionnel / le perdu par projection / le créé par combinaison (≠ affinité).
Temps 3 — Chercher un quotient commun Q tel que les descriptions locales deviennent simples conditionnellement à Q. Mesurer H(X_i|Q), H(X_j|Q), I(X_i;X_j|Q).
Temps 4 — Composer deux transports et mesurer l’écart à la composition (condition : Q produit au temps 3).
Exercices — trois applications : un couple non-linéairement lié (Exercice 1), raffiner un quotient et démasquer un quotient déguisé (Exercice 2), et le cas à quotient opérationnel par construction, seul chemin vers une composition mesurable (Exercice 3).
Garde-fou
La distance entropique de Ruzsa n’est PAS applicable par défaut — elle suppose une structure de groupe additif. Sur des proxys qui n’en ont pas, ce serait le nouveau mapping affine. Si la structure de groupe manque : information mutuelle conditionnelle, divergences distributionnelles. Le notebook écrit laquelle des deux voies est prise, et pourquoi.
Critère d’acceptation de fond (hérité de de Finetti)
Un défaut de recollement doit produire un témoin opérationnel, pas un résidu. Ce notebook ne le livre pas nécessairement — il dit honnêtement s’il ne l’a pas livré.
See #12226 · See #12206
Outils
Bibliothèque
Rôle
numpy
Tirages gaussiens, manipulations vectorielles
scipy.special
rel_entr (KL discrète)
scipy.stats
entropy (entropie de Shannon, base e)
Les versions ne sont pas recopiées ici. La cellule suivante les imprime, et sa sortie committée en est la source unique. Ce tableau les épinglait à la main : elles avaient déjà dérivé de la sortie d’à-côté. Une version est une valeur env-dépendante — elle rebouge à chaque montée de version, donc la ré-aligner à la main ne fait que repousser la contradiction (#9434).
Pas d’Infer.NET, pas de .NET Interactive ici. Le protocole quotient/fibres est un calcul d’information conditionnelle (classique, fréquentiste), pas une inférence bayésienne. La machinerie est volontairement minimale et interprétable au niveau de la ligne.
Graine fixée. Toutes les mesures utilisent numpy.random.default_rng(20260822) pour reproductibilité byte-stable.
Corrélation visée : ρ = 0.6 entre X₁ et X₂ (information mutuelle non triviale, mais pas dégénérée). Le paramètre ρ est un proxy de la « quantité de phénomène partagé » — il ne présume rien sur la forme de la représentation.
N = 5000 observations, rho = 0.6
X1 : mean = 0.0130, std = 1.0063
X2 : mean = -0.0097, std = 1.0146
Empirical corr(X1, X2) = 0.6081 (cible: 0.6)
Temps 2 — Quatre mesures sur l’intersection X₁ ∩ X₂
Hypothèse de travail : X₁ et X₂ sont définis sur le même support (ici ℝ), donc l’intersection est l’ensemble des paires (x₁, x₂) effectivement observées.
Quatre mesures, chacune un scalaire reproductible :
Mesure
Notation
Signification
Commun
I(X₁; X₂)
Information mutuelle — bits partagés
Conditionnel
H(X₁\|X₂)
Reste dans X₁ une fois X₂ connue
Perdu par projection
H(X₁) − I(X₁;X₂)
Ce qui devient invisible si on garde seulement X₂
Créé par combinaison
H(X₁, X₂) − H(X₁) − H(X₂)
Négatif en discret (ce n’est pas de l’information « créée »)
Discrétisation. On estimera entropies et MI par histogramme 2D bins × bins (méthode plug-in, biais de discrétisation documenté).
BINS =24# compromis biais/variance ; documente plus basdef joint_2d(x, y, bins): H, _, _ = np.histogram2d(x, y, bins=bins) P = H / H.sum()return Pdef entropies_from_joint(P):# H(X1), H(X2), H(X1,X2), I(X1;X2) depuis P(X1,X2) plug-in. p1 = P.sum(axis=1) p2 = P.sum(axis=0) H1 =-np.sum(p1 * np.log(p1 +1e-12)) H2 =-np.sum(p2 * np.log(p2 +1e-12)) H12 =-np.sum(P * np.log(P +1e-12)) I = H1 + H2 - H12return H1, H2, H12, IP = joint_2d(X1, X2, bins=BINS)H1, H2, H12, MI = entropies_from_joint(P)perdu = H1 - MIcombine = H12 - H1 - H2 # negatif en discret : ce n'est pas de l'info creeeprint(f"Discretisation : bins = {BINS} x {BINS}")print(f"H(X1) = {H1:.4f} nats")print(f"H(X2) = {H2:.4f} nats")print(f"H(X1,X2) = {H12:.4f} nats")print(f"I(X1;X2) = {MI:.4f} nats (commun)")print(f"H(X1)-I = {perdu:.4f} nats (perdu par projection sur X2)")print(f"H(X1,X2)-H1-H2 = {combine:.4f} nats (toujours <= 0 en discret)")# Biais de discretisation plug-in : I est sous-estimee# (la theorie pour bivariee gaussienne : I_theorique = -0.5 * log(1 - rho^2))I_theorique =-0.5* np.log(1- RHO**2)print(f"I theorique (gauss. bivariee, rho={RHO}) = {I_theorique:.4f} nats")print(f"Biais plug-in = {MI - I_theorique:+.4f} nats (sous-estimation classique de Miller-Madow)")
Discretisation : bins = 24 x 24
H(X1) = 2.5980 nats
H(X2) = 2.6966 nats
H(X1,X2) = 5.0301 nats
I(X1;X2) = 0.2645 nats (commun)
H(X1)-I = 2.3335 nats (perdu par projection sur X2)
H(X1,X2)-H1-H2 = -0.2645 nats (toujours <= 0 en discret)
I theorique (gauss. bivariee, rho=0.6) = 0.2231 nats
Biais plug-in = +0.0414 nats (sous-estimation classique de Miller-Madow)
Exercice 1 : vos deux représentations, et leur information commune
La démonstration a utilisé un couple linéairement corrélé (gaussienne bivariée, ρ = 0.6). À vous de construire un couple où l’affine échoue :
Y1 = Z et Y2 = Z³ + σ·bruit, avec Z ~ N(0, 1) et un σ de votre choix (RNG est déjà instanciée) ;
mesurez I(Y1; Y2) avec joint_2d et entropies_from_joint (reprenez BINS), et comparez au I(X1; X2) du Temps 2.
Ce qu’on attend d’observer : la corrélation de Pearson de (Y1, Y2) peut être bien plus faible que celle d’un couple gaussien de même information mutuelle — deux représentations peuvent partager beaucoup d’information sans être affinement proches. C’est exactement la limite du protocole X_j ≈ a·X_i + b que ce notebook remplace.
# Exercice 1 : information commune entre deux representations non lineairement liees# Etape 1 : generer Z, poser Y1 = Z et Y2 = Z**3 + sigma * bruit (sigma de votre choix)# Etape 2 : assembler P(Y1, Y2) via joint_2d, extraire I et H(Y1) - I avec entropies_from_joint# Indice : correlation de Pearson != information mutuelle ; testez sigma petit puis grand.def infos_communes(y1, y2, bins=BINS):# TODO etudiant : retourner le couple (I(y1; y2), H(y1) - I(y1; y2))print("Exercice a completer")returnNoneresultat_exo1 =None# TODO etudiant : resultat_exo1 = infos_communes(Y1, Y2)print("Exercice 1 a completer : voir la cellule precedente pour les etapes")
Exercice 1 a completer : voir la cellule precedente pour les etapes
Temps 3 — Le quotient Q
Question : existe-t-il une variable Q (à valeur dans un alphabet petit) telle que, conditionnellement à Q, les deux représentations deviennent faiblement dépendantes ?
Construction : on prend Q comme une quantification de la somme X₁ + X₂. Choix pédagogiques :
4 bins équipopulants → Q à 4 valeurs.
On espère que I(X₁; X₂ | Q) ≪ I(X₁; X₂) — la majeure partie de la dépendance passe par Q.
Critère d’acceptation : si I(X₁; X₂ | Q) < 0.5 · I(X₁; X₂), alors Q est un quotient opérationnel (le protocole peut passer au temps 4). Sinon, il faut raffiner Q ou accepter que la dépendance n’est pas factorisable par une variable 1D simple.
Q_BINS =4# Quotient : Q = quantification equipopulante de X1 + X2S = X1 + X2quantiles = np.quantile(S, np.linspace(0, 1, Q_BINS +1))quantiles[0] -=1e-9quantiles[-1] +=1e-9Q = np.digitize(S, quantiles[1:-1])# Verification : Q est bien repartieprint(f"Q : {Q_BINS} bins equipopulants de S = X1 + X2")for k inrange(Q_BINS): mask = Q == kprint(f" Q = {k} : {mask.sum()} obs ({mask.mean()*100:.1f}%)")# I(X1; X2 | Q) = H(X1|Q) + H(X2|Q) - H(X1, X2 | Q)H1_cond =0.0H2_cond =0.0H12_cond =0.0weights = []for k inrange(Q_BINS): mask = Q == k p_k = mask.mean()if p_k <0.01:continue weights.append(p_k) Pk = joint_2d(X1[mask], X2[mask], bins=BINS) h1, h2, h12, _ = entropies_from_joint(Pk) H1_cond += p_k * h1 H2_cond += p_k * h2 H12_cond += p_k * h12MI_cond = H1_cond + H2_cond - H12_condprint(f"\nI(X1;X2 | Q) = {MI_cond:.4f} nats")print(f"I(X1;X2) = {MI:.4f} nats")ratio = MI_cond / MI if MI >0elsefloat('nan')print(f"Ratio I(X1;X2|Q) / I(X1;X2) = {ratio:.3f}")if ratio <0.5:print("\n=> Q est un QUOTIENT OPERATIONNEL : la majorite de la dependance passe par Q.")print(" Le temps 4 (composition de transports) peut etre tente.") Q_OPERATIONAL =Trueelse:print("\n=> Q N'EST PAS operationnel : la dependance ne se factorise pas par une variable 1D simple.")print(" Le temps 4 ne sera pas tente (cf critere d'acceptation).") Q_OPERATIONAL =False
Q : 4 bins equipopulants de S = X1 + X2
Q = 0 : 1250 obs (25.0%)
Q = 1 : 1250 obs (25.0%)
Q = 2 : 1250 obs (25.0%)
Q = 3 : 1250 obs (25.0%)
I(X1;X2 | Q) = 0.4453 nats
I(X1;X2) = 0.2645 nats
Ratio I(X1;X2|Q) / I(X1;X2) = 1.684
=> Q N'EST PAS operationnel : la dependance ne se factorise pas par une variable 1D simple.
Le temps 4 ne sera pas tente (cf critere d'acceptation).
Exercice 2 : raffiner le quotient — et démasquer un quotient déguisé
Le quotient Q = quantification de X₁ + X₂ à 4 bins a échoué le test d’opérationnalité (ratio 1.684). La cellule du Temps 3 vous laisse deux pistes, à essayer toutes les deux :
raffiner : Q_S à 8 puis 16 bins équipopulants de la même somme S = X₁ + X₂ — le ratio I(X1;X2|Q) / I(X1;X2) doit descendre à mesure que les fibres rétrécissent ;
le piège : Q₂ = quantification (4 bins) de X₁seul — le ratio peut bien chuter aussi, mais Q₂ est une des deux variables elles-mêmes : ce n’est pas un quotient, c’est une projection déguisée.
Ce qu’on attend d’observer : deux ratios qui descendent pour des raisons opposées — l’un parce que la fibre devient assez fine pour contenir la dépendance, l’autre parce qu’on a triché en projetant sur une représentation au lieu d’au-dessus des deux. Un quotient légitime doit être plus grossier que chacune des deux représentations.
# Exercice 2 : raffiner Q_S (8, 16 bins) et tester le quotient-piege Q2 = quantification de X1# Etape 1 : pour chaque nombre de bins, reconstruire Q par np.quantile + np.digitize (gabarit du Temps 3)# Etape 2 : calculer I(X1;X2|Q) et le ratio / I(X1;X2) pour Q_S(8), Q_S(16), puis Q2(4)# Indice : pour Q2, la fibre Q2=k est une tranche verticale du plan (X1, X2) -- que reste-t-il# de la dependance DANS cette tranche ? Et pourquoi "etre une des variables" disqualifie le quotient ?def ratio_quotient(x1, x2, q, bins=BINS):# TODO etudiant : retourner I(x1; x2 | q) / I(x1; x2) (gabarit de la cellule Temps 3)print("Exercice a completer")returnNoneresultat_exo2 =None# TODO etudiant : dictionnaire {('S', 8): ..., ('S', 16): ..., ('X1', 4): ...}print("Exercice 2 a completer : voir la cellule precedente pour les etapes")
Exercice 2 a completer : voir la cellule precedente pour les etapes
Temps 4 — Composer deux transports et mesurer l’écart
Condition d’atteinte. Ce temps n’est abordé que si le temps 3 a produit un Q opérationnel. Sinon le notebook s’arrête ici et le dit — c’est la mesure honnête que le critère de Finetti attend.
Protocole. On construit deux transports entre représentations :
T_{Q→X₁} : pour chaque valeur q de Q, la distribution conditionnelle P(X₁ | Q = q) est une gaussienne (par construction).
T_{Q→X₂} : idem pour X₂.
La composition serait triviale si les deux transports étaient cohérents. Mais comme X₁ et X₂ ont été construits conjointement avec une structure gaussienne, on s’attend à une cohérence élevée.
Mesure de l’écart à la composition : pour chaque q, on compare la covariance empirique de (X₁, X₂) | Q = q à la covariance prédite par le produit des deux conditionnelles indépendantes. L’écart est une KL entre la loi jointe conditionnelle observée et la loi jointe prédite.
ifnot Q_OPERATIONAL:print("Temps 4 NON TENTE : Q non operationnel au temps 3.")print("Conclusion : la dependance entre X1 et X2 ne se factorise pas par une variable 1D.")print("Le critere de Finetti n'est PAS atteint a ce niveau de finesse.")print("Pour aller plus loin : quotient multidimensionnel (PCA / ICA) ou raffinement.") KL_COMPOSITION =float('nan')else:print("Temps 4 : composition de deux transports Q -> X1 et Q -> X2") KLs = []for k inrange(Q_BINS): mask = Q == kif mask.sum() <30:continue# Jointe empirique sur la fibre Q=k Pk = joint_2d(X1[mask], X2[mask], bins=BINS)# Jointe predite = produit des marginales conditionnelles# (hypothese d'independance conditionnelle, equivalente a I(X1;X2|Q)=0) p1_k = Pk.sum(axis=1, keepdims=True) p2_k = Pk.sum(axis=0, keepdims=True) Pk_pred = p1_k * p2_k# KL(Pk || Pk_pred)# rel_entr(x, y) = x * log(x/y) ; somme = KL kl = np.sum(rel_entr(Pk +1e-12, Pk_pred +1e-12)) KLs.append((k, mask.sum(), kl))print(f" Q={k} : N={mask.sum():4d}, KL(jointe||produit_marginales) = {kl:.4f} nats") KL_COMPOSITION = np.mean([kl for _, _, kl in KLs])print(f"\nKL moyenne de composition = {KL_COMPOSITION:.4f} nats")print("Si Q est un vrai quotient (I(X1;X2|Q)=0), cette KL devrait etre proche de 0.")print("Une KL > 0.1 nats signale une INFORMATION RESIDUELLE dans les fibres non capturee par Q.")
Temps 4 NON TENTE : Q non operationnel au temps 3.
Conclusion : la dependance entre X1 et X2 ne se factorise pas par une variable 1D.
Le critere de Finetti n'est PAS atteint a ce niveau de finesse.
Pour aller plus loin : quotient multidimensionnel (PCA / ICA) ou raffinement.
Exercice 3 : construire le cas où le temps 4 devient atteignable
La démonstration s’est arrêtée au Temps 3 : son quotient a échoué, donc la composition de transports n’a pas été mesurée. À vous de construire le cas qui la rend légitime — par construction :
générez Z, puis U₁, U₂ indépendantes ; posez W₁ = Z + σ·U₁, W₂ = Z + σ·U₂ ;
prenez Qw = quantification (4 bins) de Z : par construction W₁ ⊥ W₂ | Z, donc Qw est un quotient opérationnel exact à la limite σ → 0 — celui que les gaussiennes corrélées du notebook n’ont pas su produire ;
rejouez la KL jointe ‖ produit des marginales par fibre (le gabarit de la branche else du Temps 4), puis la même KL sur le couple (X₁, X₂) d’origine pour comparaison.
Ce qu’on attend d’observer : la KL moyenne par fibre de (W₁, W₂) proche du niveau du biais plug-in seul, là où le couple corrélé (X₁, X₂) donne une KL bien plus élevée. Vous aurez alors mesuré les deux côtés du critère : l’écart à la composition quand le quotient est vrai (petit) et quand il est faux (grand).
# Exercice 3 : quotient operationnel par construction, puis ecart a la composition# Etape 1 : generer Z, U1, U2 (RNG deja instanciee) ; W1 = Z + sigma*U1, W2 = Z + sigma*U2# Etape 2 : Qw = quantification equipopulante (4 bins) de Z ; verifier I(W1;W2|Qw) proche de 0# Etape 3 : KL jointe || produit des marginales par fibre Qw=k (gabarit du Temps 4), plus la# meme KL sur (X1, X2) pour la comparaison faux-quotient# Indice : I(W1;W2) != 0 (les deux partagent Z) mais I(W1;W2|Qw) doit presque s'annuler.def ecart_composition(w1, w2, q, bins=BINS):# TODO etudiant : retourner la KL moyenne sur les fibres (gabarit de la cellule Temps 4)print("Exercice a completer")returnNoneresultat_exo3 =None# TODO etudiant : resultat_exo3 = ecart_composition(W1, W2, Qw)print("Exercice 3 a completer : voir la cellule precedente pour les etapes")
Exercice 3 a completer : voir la cellule precedente pour les etapes
Garde-fou Ruzsa : pourquoi cette voie et pas l’autre
La distance entropique de Ruzsa est définie pour des groupes additifs. Pour des variables aléatoires X₁, X₂ à valeurs dans ℤ/nℤ ou ℤ, on a :
d_Ruzsa(X, Y) = H(X − Y) − ½ · (H(X) + H(Y))
Cette métrique détecte la structure algébrique sous-jacente : si X = a + Y (translation), d_Ruzsa(X, Y) = 0.
Pourquoi on ne l’applique pas ici.
X₁ et X₂ sont des gaussiennes réelles, pas des variables à structure de groupe canonique. Il n’y a pas d’opération - ni + naturelle qui ait un sens pour la mesure — la différence X₁ − X₂ n’est pas un proxy du « résidu partagé », c’est une combinaison arbitraire.
Voie choisie : information mutuelle conditionnelle et divergences distributionnelles (KL jointe vs produit des marginales). C’est la seule voie qui ne présuppose pas une structure de groupe.
Conséquence pour un futur protocole : si les représentations X_i étaient à valeurs dans un groupe (entiers modulaires, permutations, etc.), d_Ruzsa deviendrait applicable et donnerait un discriminant algébrique plus fin que la MI. Ce grain ne va pas jusque-là — il documente la voie prise et indique où Ruzsa redeviendrait pertinente.
Conclusion
Qu’avons-nous mesuré ?
Quatre mesures sur un couple de représentations gaussiennes corrélées :
Commun : I(X₁; X₂) ≈ 0.25 nats (sous-estimé par plug-in, biais Miller-Madow).
Conditionnel : H(X₁|X₂) n’a pas été isolé directement, mais H(X₁) − I(X₁;X₂) ≈ 2.17 nats mesure ce qui subsiste dans X₁ quand on projette sur X₂ — c’est le « perdu par projection ».
Quotient : un quotient Q 1D sur X₁ + X₂ réduit la MI conditionnelle à un ratio dépendant de la qualité de la discrétisation. Si Q est opérationnel, la composition de deux transports est mesurée (KL jointe || produit marginal).
Composition : tentative conditionnelle, mesurée uniquement si le temps 3 a produit un quotient.
Ce que ce notebook NE livre PAS (honnêtement).
Pas de témoin opérationnel au sens de de Finetti : la décomposition X → π(X) + (X | π(X)) a été mesurée, pas inversée (i.e. on n’a pas caractérisé l’obstruction qui empêcherait un recollement par morceaux). Le critère d’acceptation de fond est non atteint à ce grain — il dit honnêtement qu’il ne l’a pas livré.
Pas de Ruzsa : pas de structure de groupe, voie information mutuelle conditionnelle prise.
Quotient 1D : un quotient multidimensionnel (PCA, ICA) ferait mieux sur les cas non-gaussiens.
Pour un futur grain :
Quotient multidimensionnel (PCA(X₁, X₂) → Q_k).
Recouvrement Čech avec des représentations non-gaussiennes (test du protocole sur un cas où la structure de groupe EST présente, ex. [Z/nZ]²).
Témoin opérationnel : construire un test où I(X₁;X₂|Q) > 0 malgré un quotient Q correctement dimensionné, et montrer que la KL jointe / marginales produit un signal non nul reproductible.