sayed125 commited on
Commit
f0bfa3f
·
verified ·
1 Parent(s): d7ec002

Upload phase_p1_reliability.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. phase_p1_reliability.py +301 -0
phase_p1_reliability.py ADDED
@@ -0,0 +1,301 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ PHASE P1 — Reliability kit (موثوقية): calibration + abstention + gate v2.
3
+ قيس ثقته، وامتنع عن الإجابة عندما تكون ثقته منخفضة، وافصل الأنماط بدقة عند الترميز.
4
+
5
+ Anatomy mapping
6
+ - ACC (anterior cingulate cortex): monitoring + "I don't know" (abstention = error detection).
7
+ - ventromedial PFC / basal ganglia: VALUE CALIBRATION — mapping raw evidence to a
8
+ probability that can be trusted (patient S.M. / vmPFC damage: poor calibration).
9
+ - Hippocampus dentate gyrus: pattern separation v2 — subset-aware slot signatures +
10
+ content-collision check (the two documented Phase-S gate failure modes).
11
+ - Arachnoid granulations: ABSORPTION — never answer outside the trained distribution.
12
+
13
+ Everything is NumPy; no sklearn needed (Platt scaling by plain gradient descent).
14
+ """
15
+ import re
16
+ import numpy as np
17
+
18
+ # ---------------------------------------------------------------- Gate v2
19
+
20
+ def content_words(text):
21
+ """Content tokens = letters-only lowercase minus stopwords. Numbers are slots,
22
+ handled separately by the signature; function words carry no meaning here."""
23
+ stop = {"what", "is", "the", "a", "an", "of", "about", "fact", "number",
24
+ "for", "with", "and", "or", "to", "on", "in", "from", "give", "me",
25
+ "ما", "هي", "الحقيقة", "رقم", "حول", "عن", "في", "من", "إلى", "على",
26
+ "هو", "the", "value"}
27
+ toks = re.findall(r"[a-zA-Z\u0600-\u06FF]+", text.lower())
28
+ return set(t for t in toks if t not in stop and len(t) > 1)
29
+
30
+
31
+ def slot_sig(text):
32
+ """Numeric slot signature: the set of number tokens in the prompt."""
33
+ return frozenset(re.findall(r"\d+", text))
34
+
35
+
36
+ def build_canonical(syn_dict):
37
+ """Union-find over the augmentation thesaurus: 'waterways' → same class as
38
+ 'rivers', so synonymy paraphrases survive the content check."""
39
+ parent = {}
40
+
41
+ def find(x):
42
+ parent.setdefault(x, x)
43
+ while parent[x] != x:
44
+ parent[x] = parent[parent[x]]
45
+ x = parent[x]
46
+ return x
47
+
48
+ def union(a, b):
49
+ ra, rb = find(a), find(b)
50
+ if ra != rb:
51
+ parent[ra] = rb
52
+
53
+ for w, syns in syn_dict.items():
54
+ find(w)
55
+ for s in syns:
56
+ s0 = s.split()[0] # multi-word synonyms: head word
57
+ union(w, s0)
58
+ canon = {}
59
+ for w in list(parent):
60
+ root = find(w)
61
+ canon[w] = min(root, w) # deterministic representative
62
+ return canon
63
+
64
+
65
+ def _canon_set(text, canon):
66
+ return set(canon.get(w, w) for w in content_words(text))
67
+
68
+
69
+ def overlap_coef(A, B):
70
+ """|A∩B| / min(|A|,|B|) — tolerant to one-sided deletion (augmentation),
71
+ intolerant to pure content collision (rivers vs cooking). Empty side = neutral."""
72
+ if not A or not B:
73
+ return 1.0
74
+ return len(A & B) / min(len(A), len(B))
75
+
76
+
77
+ def sig_compatible(s1, s2):
78
+ """Extension of equality: {42} ⊆ {42,7} counts as the same slot lineage
79
+ (fixes the 'value 7' extension miss). Disjoint numbers stay separated."""
80
+ if not s1 or not s2:
81
+ return True
82
+ return s1 <= s2 or s2 <= s1
83
+
84
+
85
+ class ConsolidationGateV2:
86
+ """Hippocampal pattern separation v2 — merge ⇔ ALL of:
87
+ 1) semantic cosine ≥ θ (meaning is the same)
88
+ 2) slot signatures compatible (same fact slot lineage)
89
+ 3) content overlap ≥ τ_c (not a content collision)
90
+ Candidates are found via a per-digit posting list (subset-aware lookup), so
91
+ an extended signature {42,7} still finds the stored {42}.
92
+ Cost asymmetry: a missed merge leaves one redundant engram; a wrong merge
93
+ destroys a fact — so any failed factor means KEEP."""
94
+
95
+ def __init__(self, encoder, threshold=0.687, content_tau=0.5, canon=None):
96
+ self.enc = encoder
97
+ self.threshold = threshold
98
+ self.content_tau = content_tau
99
+ self.canon = canon or {}
100
+ self._index = {} # digit token -> [entry idx]
101
+ self._sigs = [] # entry idx -> signature
102
+ self._conts = [] # entry idx -> canonical content set
103
+ self._n = 0
104
+
105
+ def _sync(self, memory):
106
+ for j in range(self._n, len(memory.entries)):
107
+ text = memory.entries[j]["prompt"]
108
+ sig = slot_sig(text)
109
+ self._sigs.append(sig)
110
+ self._conts.append(_canon_set(text, self.canon))
111
+ for d in sig:
112
+ self._index.setdefault(d, []).append(j)
113
+ if not sig:
114
+ self._index.setdefault("", []).append(j)
115
+ self._n += 1
116
+
117
+ def check(self, memory, prompt):
118
+ self._sync(memory)
119
+ sig = slot_sig(prompt)
120
+ cand = set(self._index.get("", [])) # digit-less entries are always candidates
121
+ for d in sig:
122
+ cand.update(self._index.get(d, []))
123
+ if not cand:
124
+ return {"is_new": True, "score": 0.0, "content": 1.0,
125
+ "nearest": None, "reason": "no-slot-candidate"}
126
+ e = self.enc.encode(prompt)
127
+ cont = _canon_set(prompt, self.canon)
128
+ best, best_j = -2.0, None
129
+ for j in cand:
130
+ s = float(self.enc.encode(memory.entries[j]["prompt"]) @ e)
131
+ if s > best:
132
+ best, best_j = s, j
133
+ if best_j is None:
134
+ return {"is_new": True, "score": 0.0, "content": 1.0,
135
+ "nearest": None, "reason": "empty"}
136
+ s_sig = self._sigs[best_j]
137
+ c = overlap_coef(cont, self._conts[best_j])
138
+ ok_sig = sig_compatible(sig, s_sig)
139
+ ok_cont = c >= self.content_tau
140
+ is_dup = (best >= self.threshold) and ok_sig and ok_cont
141
+ if is_dup:
142
+ reason = "merge"
143
+ elif best >= self.threshold and not ok_sig:
144
+ reason = "slot-separation"
145
+ elif best >= self.threshold and not ok_cont:
146
+ reason = "content-collision"
147
+ else:
148
+ reason = "below-threshold"
149
+ return {"is_new": not is_dup, "score": best, "content": c,
150
+ "nearest": best_j, "reason": reason}
151
+
152
+
153
+ # ---------------------------------------------------------------- Calibration
154
+
155
+ class ConfidenceCalibrator:
156
+ """Multivariate Platt scaling: P(correct | x) = σ(x·w + b), fitted by NLL
157
+ gradient descent. Features: [top-1 cosine, ACC gap(top1,top2)] — the gap is
158
+ the brain's own conflict signal (anterior cingulate). This is the vmPFC
159
+ 'value calibration' step: raw evidence becomes a probability you can act on."""
160
+
161
+ def __init__(self):
162
+ self.w = None
163
+ self.b = 0.0
164
+
165
+ @staticmethod
166
+ def _sigmoid(z):
167
+ return 1.0 / (1.0 + np.exp(-np.clip(z, -30, 30)))
168
+
169
+ @staticmethod
170
+ def _design(scores):
171
+ X = np.asarray(scores, dtype=np.float64)
172
+ if X.ndim == 1:
173
+ X = X.reshape(-1, 1)
174
+ return X
175
+
176
+ def fit(self, scores, labels, lr=0.5, iters=1500, l2=1e-3):
177
+ X = self._design(scores)
178
+ y = np.asarray(labels, dtype=np.float64)
179
+ # standardize features for stable GD
180
+ self.mu = X.mean(axis=0)
181
+ self.sd = X.std(axis=0) + 1e-8
182
+ Xs = (X - self.mu) / self.sd
183
+ w = np.zeros(X.shape[1])
184
+ b = 0.0
185
+ n = len(Xs)
186
+ for _ in range(iters):
187
+ p = self._sigmoid(Xs @ w + b)
188
+ g = (p - y)
189
+ w -= lr * (Xs.T @ g / n + l2 * w)
190
+ b -= lr * float(g.mean())
191
+ self.w, self.b = w, float(b)
192
+ return self
193
+
194
+ def predict(self, scores):
195
+ X = self._design(scores)
196
+ Xs = (X - self.mu) / self.sd
197
+ return self._sigmoid(Xs @ self.w + self.b)
198
+
199
+
200
+ def ece(conf, correct, bins=10):
201
+ """Expected Calibration Error over equal-width bins."""
202
+ conf = np.asarray(conf, dtype=np.float64)
203
+ correct = np.asarray(correct, dtype=np.float64)
204
+ edges = np.linspace(0.0, 1.0, bins + 1)
205
+ total, e = len(conf), 0.0
206
+ for i in range(bins):
207
+ m = (conf >= edges[i]) & (conf < edges[i + 1] if i < bins - 1 else conf <= 1.0)
208
+ if m.sum() == 0:
209
+ continue
210
+ e += m.sum() / total * abs(correct[m].mean() - conf[m].mean())
211
+ return float(e)
212
+
213
+
214
+ def abstain_curve(scores, correct, is_ood, cal, taus):
215
+ """Selective prediction: answer iff calibrated confidence ≥ τ.
216
+ Returns per-τ: coverage, selective accuracy, OOD false-answer rate."""
217
+ conf = cal.predict(scores)
218
+ rows = []
219
+ for t in taus:
220
+ answered = conf >= t
221
+ cov = float(answered.mean()) if len(conf) else 0.0
222
+ sel_acc = float(correct[answered].mean()) if answered.any() else 1.0
223
+ ood = np.asarray(is_ood, dtype=bool)
224
+ ood_answer = float(answered[ood].mean()) if ood.any() else 0.0
225
+ rows.append({"tau": t, "coverage": round(cov, 3),
226
+ "selective_acc": round(sel_acc, 3),
227
+ "ood_false_answer": round(ood_answer, 3)})
228
+ return rows
229
+
230
+
231
+ # ---------------------------------------------------------------- Benchmark
232
+
233
+ OOD_TOPICS_EN = ["quantum knitting", "cheese sculpture", "volcanic pastry",
234
+ "underwater chess", "antique clocks", "cloud farming",
235
+ "neon gardening", "polar astronomy", "crystal logistics"]
236
+ OOD_TOPICS_AR = ["التطريز الكمي", "نحت الجبن", "الشطرنج تحت الماء",
237
+ "زراعة الغيوم", "علم الفلك القطبي"]
238
+
239
+ GATE_KINDS = ["dup_aug", "dup_extend", "sibling", "sigcoll", "fresh"]
240
+
241
+
242
+ def make_reliability_benchmark(corpus, n_aug=600, n_novel=400, n_ood=400, seed=7):
243
+ """≈1400 retrieval cases + gate cases come from make_gate_cases.
244
+ Returns list of dicts: {query, target_prompt|None, kind, ood:bool}."""
245
+ import random as _random
246
+ from phase_c_augment import augment
247
+ from phase_s_scale import TEMPLATES_NOVEL
248
+ rng = _random.Random(seed)
249
+ nrs = np.random.RandomState(seed)
250
+ idx = rng.sample(range(len(corpus)), n_aug + n_novel)
251
+ cases = []
252
+ for k, i in enumerate(idx[:n_aug]):
253
+ cases.append({"query": augment(corpus[i]["prompt"], nrs),
254
+ "target_prompt": corpus[i]["prompt"], "kind": "aug", "ood": False})
255
+ for k, i in enumerate(idx[n_aug:]):
256
+ p = corpus[i]["prompt"]
257
+ m = re.search(r"(\d+)", p)
258
+ num = m.group(1) if m else str(i)
259
+ m2 = (re.search(r"about (?:the )?(\w+)", p) or
260
+ re.search(r"حول (\w+)", p) or
261
+ re.search(r"the (\w+) value", p))
262
+ topic = m2.group(1) if m2 else "topic"
263
+ tpl_q, _ = TEMPLATES_NOVEL[k % len(TEMPLATES_NOVEL)]
264
+ cases.append({"query": tpl_q.format(i=num, t=topic),
265
+ "target_prompt": p, "kind": "novel", "ood": False})
266
+ for k in range(n_ood):
267
+ if k % 2 == 0:
268
+ t = OOD_TOPICS_EN[k % len(OOD_TOPICS_EN)]
269
+ q = f"What is fact number {900000 + k} about {t}?"
270
+ else:
271
+ t = OOD_TOPICS_AR[k % len(OOD_TOPICS_AR)]
272
+ q = f"ما هي الحقيقة رقم {900000 + k} حول {t}؟"
273
+ cases.append({"query": q, "target_prompt": None, "kind": "ood", "ood": True})
274
+ return cases
275
+
276
+
277
+ def make_gate_cases(n_each=120, seed=11):
278
+ """(stored, query, expected_merge) covering the two documented failure modes:
279
+ dup_extend (extra digit + extra words — was missed by equal-signature lookup)
280
+ and sigcoll (same digit, different meaning — was wrongly merged)."""
281
+ import random as _random
282
+ from phase_c_augment import augment
283
+ rng = _random.Random(seed)
284
+ nrs = np.random.RandomState(seed)
285
+ cases = []
286
+ for k in range(n_each):
287
+ i = 1000 + k
288
+ base = f"What is fact number {i} about rivers?"
289
+ # true duplicates: augmented view (synonyms / deletions / typos)
290
+ cases.append((base, augment(base, nrs), True, "dup_aug"))
291
+ # true duplicates: extended phrasing with an EXTRA number (demo failure 1)
292
+ ext = f"What is fact number {i} about rivers value {k % 50} with rivers detail"
293
+ cases.append((base, ext, True, "dup_extend"))
294
+ # template sibling: different number, same words → keep
295
+ cases.append((base, f"What is fact number {i + 5000} about rivers?", False, "sibling"))
296
+ # signature collision: same number, different meaning → keep (demo failure 2)
297
+ cases.append((base, f"What is fact number {i} about cooking?", False, "sigcoll"))
298
+ # fresh: different number + different meaning → keep
299
+ cases.append((base, f"Tell me something about quantum knitting {i + 7000}",
300
+ False, "fresh"))
301
+ return cases