File size: 7,184 Bytes
177308a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 | """
Finite-sample counterparts of the graphical results, using the same causal-learn
implementations the paper used.
(A) Lemma 1 in data: a selection-blind skeleton search on evolutionary data
produces adjacencies that the static selection model says cannot be there;
the same search on data WITHOUT selection (pa(S) = {}) must not.
(B) Theorem 2 in data: PC on evolutionary data, oriented vs all adjacencies.
(C) Theorem 4 in data: CDNOD on K = 4 heterogeneous domains (selection
mechanism changed) vs PC on a single domain.
Run: python3 finite_sample.py
"""
import json, os, warnings, itertools, time
import numpy as np
warnings.filterwarnings('ignore')
os.environ.setdefault('OMP_NUM_THREADS', '1')
OUT = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'outputs')
os.makedirs(OUT, exist_ok=True)
N = 5000
ALPHA = 0.05
def skeleton(X, alpha=ALPHA):
from causallearn.search.ConstraintBased.PC import pc
from exp6_synthetic import _edges_from_cl
cg = pc(X, alpha=alpha, indep_test='fisherz', show_progress=False)
dd, uu = _edges_from_cl(cg.G, X.shape[1])
return dd, uu, set(frozenset(e) for e in dd) | uu
def task_A(seed):
"""Lemma 1 in data."""
from evosel import (random_static_dag, simulate_evolution, clique_augmented,
SEL, DG)
d = 8
rng = np.random.default_rng(seed)
G = random_static_dag(d, rng, avg_deg=2.0, n_sel_parents=3)
true = set(frozenset((i, j)) for j in range(d) for i in G.pa[j] if i != SEL)
gp = clique_augmented(G, d)
gpe = set(frozenset(e) for e in gp.edges())
B, w, var = None, None, None
Xe, p = simulate_evolution(G, d, 3, N, rng) # with selection
Xn, _ = simulate_evolution(G, d, 3, N, rng, selection=False,
B=p[0], w=p[1], var=p[2]) # reproduction at random
_, _, ske = skeleton(Xe)
_, _, skn = skeleton(Xn)
return {'seed': seed, 'd': d,
'n_true_causal_edges': len(true),
'evolution_adjacencies': len(ske),
'evolution_spurious_wrt_true_causal': len(ske - true),
'evolution_spurious_explained_by_Gplus': len((ske - true) & gpe),
'no_selection_adjacencies': len(skn),
'no_selection_spurious_wrt_true_causal': len(skn - true)}
def task_B(seed):
"""Theorem 2 in data (d = 10, the smallest size the paper reports)."""
from evosel import random_static_dag, simulate_evolution, clique_augmented, cpdag, SEL
from exp6_synthetic import _score
d = 10
rng = np.random.default_rng(1000 + seed)
G = random_static_dag(d, rng, avg_deg=2.0)
true = set((i, j) for j in range(d) for i in G.pa[j] if i != SEL)
anS = G.ancestors([SEL]) - {SEL}
X, _ = simulate_evolution(G, d, 3, N, rng)
dd, uu, _ = skeleton(X)
s = _score(dd, uu, true, anS)
s.update({'seed': seed, 'd': d, 'n_anS': len(anS)})
return s
def task_C(seed):
"""Theorem 4 in data: CDNOD over 4 domains where the selection mechanism
changes, versus PC on domain 1 alone."""
from causallearn.search.ConstraintBased.CDNOD import cdnod
from evosel import (random_static_dag, simulate_evolution, sem_params,
clique_augmented, cpdag, multidomain_augmented, SEL)
from exp6_synthetic import _edges_from_cl, _score
d, K = 8, 4
rng = np.random.default_rng(2000 + seed)
G = random_static_dag(d, rng, avg_deg=2.0, n_sel_parents=2)
true = set((i, j) for j in range(d) for i in G.pa[j] if i != SEL)
anS = G.ancestors([SEL]) - {SEL}
B, w, var = sem_params(G, d, rng)
Xs = []
for k in range(K): # only the selection weights change
wk = w * rng.uniform(0.3, 2.0, size=d) if k else w
Xk, _ = simulate_evolution(G, d, 3, N, rng, B=B, w=wk, var=var)
Xs.append(Xk)
X = np.vstack(Xs)
cidx = np.repeat(np.arange(K), N).reshape(-1, 1)
cg = cdnod(X, cidx, alpha=ALPHA, indep_test='fisherz', show_progress=False)
dd, uu = _edges_from_cl(cg.G, d + 1)
dd = set((u, v) for (u, v) in dd if u < d and v < d)
uu = set(e for e in uu if all(x < d for x in e))
multi = _score(dd, uu, true, anS)
d1, u1, _ = skeleton(Xs[0])
single = _score(d1, u1, true, anS)
return {'seed': seed, 'd': d, 'K': K, 'n_anS': len(anS),
'single_domain': single, 'multi_domain': multi,
'single_correct_oriented': single['oriented_direction_correct'],
'multi_correct_oriented': multi['oriented_direction_correct'],
'single_n_oriented': single['n_oriented'],
'multi_n_oriented': multi['n_oriented']}
def mean(rows, path):
v = []
for r in rows:
x = r
for p in path:
x = x[p]
v.append(x)
a = np.array(v, dtype=float)
a = a[~np.isnan(a)]
return float(a.mean())
if __name__ == '__main__':
import multiprocessing as mp
t0 = time.time()
with mp.Pool(processes=min(mp.cpu_count(), 12)) as pool:
A = pool.map(task_A, range(20))
B = pool.map(task_B, range(20))
C = pool.map(task_C, range(20))
res = {
'A_lemma1_in_data': {
'runs': len(A), 'd': 8, 'N': N, 'T': 3,
'mean_adjacencies_with_evolution': mean(A, ['evolution_adjacencies']),
'total_spurious_with_evolution': sum(r['evolution_spurious_wrt_true_causal'] for r in A),
'total_spurious_explained_by_Gplus': sum(r['evolution_spurious_explained_by_Gplus'] for r in A),
'total_spurious_without_selection': sum(r['no_selection_spurious_wrt_true_causal'] for r in A),
'mean_spurious_with_evolution': mean(A, ['evolution_spurious_wrt_true_causal']),
'mean_spurious_without_selection': mean(A, ['no_selection_spurious_wrt_true_causal']),
'detail': A},
'B_theorem2_in_data': {
'runs': len(B), 'd': 10, 'N': N, 'T': 3,
'precision_standard': mean(B, ['precision_standard']),
'precision_ours': mean(B, ['precision_ours']),
'theorem2_soundness_rate': (
sum(r['oriented_satisfying_theorem2'] for r in B) /
max(sum(r['n_oriented'] for r in B), 1)),
'runs_where_ours_ge_standard': sum(
1 for r in B if r['precision_ours'] >= r['precision_standard']),
'detail': B},
'C_theorem4_in_data': {
'runs': len(C), 'd': 8, 'K': 4, 'N_per_domain': N,
'mean_correct_oriented_single': mean(C, ['single_correct_oriented']),
'mean_correct_oriented_multi': mean(C, ['multi_correct_oriented']),
'mean_n_oriented_single': mean(C, ['single_n_oriented']),
'mean_n_oriented_multi': mean(C, ['multi_n_oriented']),
'runs_multi_ge_single': sum(
1 for r in C if r['multi_correct_oriented'] >= r['single_correct_oriented']),
'detail': C},
'runtime_sec': time.time() - t0}
json.dump(res, open(os.path.join(OUT, 'finite_sample.json'), 'w'), indent=1)
for k in ('A_lemma1_in_data', 'B_theorem2_in_data', 'C_theorem4_in_data'):
print(k, {a: b for a, b in res[k].items() if a != 'detail'})
|