knoxel commited on
Commit
8fff6b5
·
verified ·
1 Parent(s): 0d809c3

Upload run_all_extensions.py

Browse files
Files changed (1) hide show
  1. run_all_extensions.py +406 -0
run_all_extensions.py ADDED
@@ -0,0 +1,406 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ ConformalESM-Complete: All 10 extensions in one fast pipeline.
3
+ Optimized for CPU with cached predictions.
4
+ See paper_final.md for full documentation.
5
+ """
6
+ import os, time, json
7
+ import numpy as np
8
+ from collections import defaultdict
9
+ from datasets import load_dataset
10
+ from transformers import AutoTokenizer, AutoModelForTokenClassification
11
+ import torch
12
+
13
+ MODEL_ID = "AmelieSchreiber/esm2_t6_8M_UR50D-finetuned-secondary-structure"
14
+ DATASET_NAME = "lamm-mit/protein_secondary_structure_from_PDB"
15
+ MAX_LEN = 1022
16
+ SEED = 42
17
+ N_CAL = 400
18
+ N_TEST = 400
19
+
20
+ LABEL2ID = {"C": 0, "H": 1, "E": 2}
21
+ ID2LABEL = {0: "C", 1: "H", 2: "E"}
22
+ VALID_AA = set("ACDEFGHIKLMNPQRSTVWY")
23
+ CACHE_DIR = "/app/prediction_cache"
24
+
25
+ def dssp_to_q3(c):
26
+ if c in "HGI": return "H"
27
+ elif c in "EB": return "E"
28
+ else: return "C"
29
+
30
+ def get_predictions_cached(model, tokenizer, dataset, batch_size=8):
31
+ os.makedirs(CACHE_DIR, exist_ok=True)
32
+ cache_file = os.path.join(CACHE_DIR, f"preds_{len(dataset)}.npz")
33
+
34
+ if os.path.exists(cache_file):
35
+ print(f"Loading cached predictions from {cache_file}")
36
+ data = np.load(cache_file, allow_pickle=True)
37
+ results = []
38
+ for i in range(len(data["true"])):
39
+ results.append({
40
+ "true": data["true"][i],
41
+ "probs": data["probs"][i],
42
+ "preds": data["preds"][i],
43
+ "seq_len": int(data["seq_len"][i]),
44
+ "pdb_id": str(data["pdb_id"][i]),
45
+ })
46
+ return results
47
+
48
+ model.eval()
49
+ results = []
50
+ all_true, all_probs, all_preds, all_lens, all_ids = [], [], [], [], []
51
+
52
+ with torch.no_grad():
53
+ for i in range(0, len(dataset), batch_size):
54
+ batch = dataset[i:i + batch_size]
55
+ for j in range(len(batch["Sequence_spaced"])):
56
+ seq = batch["Sequence_spaced"][j].split()
57
+ ss = batch["Secondary_structure"][j][:len(seq)]
58
+ true = np.array([LABEL2ID[dssp_to_q3(c)] for c in ss])
59
+
60
+ spaced = " ".join(seq[:MAX_LEN - 2])
61
+ inputs = tokenizer(spaced, return_tensors="pt", truncation=True, max_length=MAX_LEN)
62
+ logits = model(**inputs).logits.squeeze(0)
63
+ probs = torch.softmax(logits, dim=-1).numpy()
64
+
65
+ input_ids = inputs["input_ids"].squeeze(0).tolist()
66
+ aligned_probs = []
67
+ residue_idx = 0
68
+ for tid in input_ids:
69
+ if tid in [tokenizer.cls_token_id, tokenizer.eos_token_id, tokenizer.pad_token_id]:
70
+ continue
71
+ if residue_idx < len(true):
72
+ aligned_probs.append(probs[residue_idx + 1])
73
+ residue_idx += 1
74
+
75
+ aligned_probs = np.array(aligned_probs)
76
+ min_len = min(len(true), len(aligned_probs))
77
+ preds = np.argmax(aligned_probs[:min_len], axis=-1)
78
+
79
+ result = {
80
+ "true": true[:min_len],
81
+ "probs": aligned_probs[:min_len],
82
+ "preds": preds,
83
+ "seq_len": min_len,
84
+ "pdb_id": batch["PDB_ID"][j] if "PDB_ID" in batch else f"prot_{i+j}",
85
+ }
86
+ results.append(result)
87
+ all_true.append(result["true"])
88
+ all_probs.append(result["probs"])
89
+ all_preds.append(result["preds"])
90
+ all_lens.append(result["seq_len"])
91
+ all_ids.append(result["pdb_id"])
92
+
93
+ if (i // batch_size) % 10 == 0:
94
+ print(f" Processed {i}/{len(dataset)} sequences")
95
+
96
+ np.savez(cache_file, true=np.array(all_true, dtype=object),
97
+ probs=np.array(all_probs, dtype=object),
98
+ preds=np.array(all_preds, dtype=object),
99
+ seq_len=np.array(all_lens),
100
+ pdb_id=np.array(all_ids, dtype=object))
101
+ print(f"Cached predictions to {cache_file}")
102
+ return results
103
+
104
+
105
+ def load_data():
106
+ ds = load_dataset(DATASET_NAME, split="train")
107
+ ds = ds.filter(lambda x: x["Sequence_length"] <= MAX_LEN - 2)
108
+ ds = ds.shuffle(seed=SEED)
109
+ cal = ds.select(range(N_CAL))
110
+ test = ds.select(range(N_CAL, N_CAL + N_TEST))
111
+ return cal, test
112
+
113
+
114
+ def accuracy(results):
115
+ correct = sum(np.sum(r["preds"] == r["true"]) for r in results)
116
+ total = sum(len(r["true"]) for r in results)
117
+ return correct / total
118
+
119
+
120
+ def ece(results, n_bins=10):
121
+ all_conf, all_correct = [], []
122
+ for r in results:
123
+ conf = np.max(r["probs"], axis=-1)
124
+ correct = (r["preds"] == r["true"]).astype(float)
125
+ all_conf.extend(conf)
126
+ all_correct.extend(correct)
127
+ all_conf = np.array(all_conf)
128
+ all_correct = np.array(all_correct)
129
+ ece_val = 0.0
130
+ for i in range(n_bins):
131
+ lo, hi = i / n_bins, (i + 1) / n_bins
132
+ mask = (all_conf > lo) & (all_conf <= hi)
133
+ if mask.sum() == 0: continue
134
+ ece_val += mask.sum() * abs(all_conf[mask].mean() - all_correct[mask].mean())
135
+ return ece_val / len(all_conf)
136
+
137
+
138
+ def find_temperature(cal_results):
139
+ all_logits, all_labels = [], []
140
+ for r in cal_results:
141
+ probs = np.clip(r["probs"], 1e-10, 1.0)
142
+ all_logits.append(np.log(probs))
143
+ all_labels.append(r["true"])
144
+ all_logits = np.concatenate(all_logits)
145
+ all_labels = np.concatenate(all_labels)
146
+ best_temp, best_nll = 1.0, float("inf")
147
+ for temp in np.linspace(0.3, 5.0, 50):
148
+ scaled = all_logits / temp
149
+ max_log = np.max(scaled, axis=-1, keepdims=True)
150
+ lp = scaled - max_log - np.log(np.sum(np.exp(scaled - max_log), axis=-1, keepdims=True))
151
+ nll = -np.mean(lp[np.arange(len(all_labels)), all_labels])
152
+ if nll < best_nll:
153
+ best_nll = nll
154
+ best_temp = temp
155
+ return best_temp
156
+
157
+
158
+ def apply_temperature(results, temp):
159
+ scaled = []
160
+ for r in results:
161
+ probs = np.clip(r["probs"], 1e-10, 1.0)
162
+ logits = np.log(probs) / temp
163
+ max_log = np.max(logits, axis=-1, keepdims=True)
164
+ new_probs = np.exp(logits - max_log) / np.sum(np.exp(logits - max_log), axis=-1, keepdims=True)
165
+ scaled.append({
166
+ "true": r["true"], "probs": new_probs, "preds": np.argmax(new_probs, axis=-1),
167
+ "seq_len": r["seq_len"], "pdb_id": r["pdb_id"],
168
+ })
169
+ return scaled
170
+
171
+
172
+ def extension_1_standard_conformal(cal_results, test_results):
173
+ print("\n--- EXT 1: Standard Conformal Prediction ---")
174
+ results = {}
175
+ for alpha in [0.05, 0.10, 0.20]:
176
+ scores = []
177
+ for r in cal_results:
178
+ for j, label in enumerate(r["true"]):
179
+ scores.append(1.0 - r["probs"][j, label])
180
+ scores = np.array(scores)
181
+ q = np.quantile(scores, np.ceil((len(scores) + 1) * (1 - alpha)) / len(scores), method="higher")
182
+
183
+ coverage, total, sizes = 0, 0, []
184
+ for r in test_results:
185
+ for j, label in enumerate(r["true"]):
186
+ total += 1
187
+ pred_set = [y for y in range(3) if (1.0 - r["probs"][j, y]) <= q]
188
+ if len(pred_set) == 0:
189
+ pred_set = [np.argmax(r["probs"][j])]
190
+ sizes.append(len(pred_set))
191
+ if label in pred_set:
192
+ coverage += 1
193
+ results[f"alpha_{alpha}"] = {
194
+ "coverage": coverage / total,
195
+ "avg_size": np.mean(sizes),
196
+ "target": 1 - alpha,
197
+ }
198
+ print(f" alpha={alpha:.2f}: coverage={coverage/total:.4f} (target={1-alpha:.2f}), size={np.mean(sizes):.2f}")
199
+ return results
200
+
201
+
202
+ def extension_2_class_conditional(cal_results, test_results):
203
+ print("\n--- EXT 2: Class-Conditional Conformal ---")
204
+ results = {}
205
+ for alpha in [0.10]:
206
+ class_scores = defaultdict(list)
207
+ for r in cal_results:
208
+ for j, label in enumerate(r["true"]):
209
+ class_scores[label].append(1.0 - r["probs"][j, label])
210
+ thresholds = {}
211
+ for label, scores in class_scores.items():
212
+ scores = np.array(scores)
213
+ q = np.quantile(scores, np.ceil((len(scores) + 1) * (1 - alpha)) / len(scores), method="higher")
214
+ thresholds[label] = q
215
+
216
+ coverage, total, sizes = 0, 0, []
217
+ class_cov, class_total, class_size = defaultdict(int), defaultdict(int), defaultdict(list)
218
+
219
+ for r in test_results:
220
+ for j, label in enumerate(r["true"]):
221
+ total += 1
222
+ q = thresholds[label]
223
+ pred_set = [y for y in range(3) if (1.0 - r["probs"][j, y]) <= q]
224
+ if len(pred_set) == 0:
225
+ pred_set = [np.argmax(r["probs"][j])]
226
+ sizes.append(len(pred_set))
227
+ if label in pred_set:
228
+ coverage += 1
229
+ class_cov[label] += 1
230
+ class_total[label] += 1
231
+ class_size[label].append(len(pred_set))
232
+
233
+ print(f" alpha={alpha:.2f}: coverage={coverage/total:.4f}, size={np.mean(sizes):.2f}")
234
+ for cls in sorted(class_total.keys()):
235
+ print(f" {ID2LABEL[cls]}: coverage={class_cov[cls]/class_total[cls]:.3f}, size={np.mean(class_size[cls]):.2f}")
236
+
237
+ results[f"alpha_{alpha}"] = {
238
+ "coverage": coverage / total,
239
+ "avg_size": np.mean(sizes),
240
+ "per_class": {ID2LABEL[k]: {"coverage": class_cov[k]/class_total[k], "size": np.mean(class_size[k])}
241
+ for k in class_total},
242
+ }
243
+ return results
244
+
245
+
246
+ def extension_9_mondrian(cal_results, test_results):
247
+ print("\n--- EXT 9: Mondrian Conformal ---")
248
+ results = {}
249
+ for alpha in [0.10]:
250
+ class_scores = defaultdict(list)
251
+ for r in cal_results:
252
+ for j, label in enumerate(r["true"]):
253
+ class_scores[label].append(1.0 - r["probs"][j, label])
254
+ thresholds = {}
255
+ for label, scores in class_scores.items():
256
+ scores = np.array(scores)
257
+ q = np.quantile(scores, np.ceil((len(scores) + 1) * (1 - alpha)) / len(scores), method="higher")
258
+ thresholds[label] = q
259
+
260
+ class_cov, class_total = defaultdict(int), defaultdict(int)
261
+ for r in test_results:
262
+ for j, label in enumerate(r["true"]):
263
+ q = thresholds[label]
264
+ pred_set = [y for y in range(3) if (1.0 - r["probs"][j, y]) <= q]
265
+ if len(pred_set) == 0:
266
+ pred_set = [np.argmax(r["probs"][j])]
267
+ class_total[label] += 1
268
+ if label in pred_set:
269
+ class_cov[label] += 1
270
+
271
+ print(f" alpha={alpha:.2f}:")
272
+ for label in sorted(class_total.keys()):
273
+ print(f" {ID2LABEL[label]}: coverage={class_cov[label]/class_total[label]:.4f} (n={class_total[label]})")
274
+ results[f"alpha_{alpha}"] = {ID2LABEL[k]: class_cov[k]/class_total[k] for k in class_total}
275
+ return results
276
+
277
+
278
+ def extension_6_size_stratified(test_results, q):
279
+ print("\n--- EXT 6: Size-Stratified Coverage ---")
280
+ size_stats = defaultdict(lambda: {"correct": 0, "total": 0})
281
+ for r in test_results:
282
+ for j, label in enumerate(r["true"]):
283
+ pred_set = [y for y in range(3) if (1.0 - r["probs"][j, y]) <= q]
284
+ if len(pred_set) == 0:
285
+ pred_set = [np.argmax(r["probs"][j])]
286
+ sz = len(pred_set)
287
+ size_stats[sz]["total"] += 1
288
+ if label in pred_set:
289
+ size_stats[sz]["correct"] += 1
290
+
291
+ for sz in sorted(size_stats.keys()):
292
+ cov = size_stats[sz]["correct"] / size_stats[sz]["total"]
293
+ print(f" Set size={sz}: coverage={cov:.4f} (n={size_stats[sz]['total']})")
294
+ return {sz: size_stats[sz]["correct"] / size_stats[sz]["total"] for sz in size_stats}
295
+
296
+
297
+ def extension_7_protein_uncertainty(test_results):
298
+ print("\n--- EXT 7: Protein-Level Uncertainty ---")
299
+ protein_scores = []
300
+ for r in test_results:
301
+ entropies = -np.sum(r["probs"] * np.log(r["probs"] + 1e-10), axis=-1)
302
+ confidences = np.max(r["probs"], axis=-1)
303
+ metrics = {
304
+ "pdb_id": r["pdb_id"],
305
+ "seq_len": r["seq_len"],
306
+ "mean_entropy": float(np.mean(entropies)),
307
+ "mean_confidence": float(np.mean(confidences)),
308
+ "low_conf_frac": float(np.mean(confidences < 0.5)),
309
+ "accuracy": float(np.mean(r["preds"] == r["true"])),
310
+ "uncertainty": float(1.0 - np.mean(confidences)),
311
+ }
312
+ protein_scores.append(metrics)
313
+
314
+ protein_scores.sort(key=lambda x: -x["uncertainty"])
315
+ for p in protein_scores[:5]:
316
+ print(f" {p['pdb_id']}: uncertainty={p['uncertainty']:.3f}, acc={p['accuracy']:.3f}, len={p['seq_len']}")
317
+ return protein_scores
318
+
319
+
320
+ def extension_10_calibration(test_results):
321
+ print("\n--- EXT 10: Calibration Diagnostic ---")
322
+ all_conf, all_correct = [], []
323
+ for r in test_results:
324
+ all_conf.extend(np.max(r["probs"], axis=-1))
325
+ all_correct.extend((r["preds"] == r["true"]).astype(float))
326
+ all_conf = np.array(all_conf)
327
+ all_correct = np.array(all_correct)
328
+
329
+ bins = np.linspace(0, 1, 11)
330
+ print(" Reliability diagram:")
331
+ for i in range(len(bins) - 1):
332
+ mask = (all_conf > bins[i]) & (all_conf <= bins[i + 1])
333
+ if mask.sum() > 0:
334
+ print(f" ({bins[i]:.1f}, {bins[i+1]:.1f}]: accuracy={all_correct[mask].mean():.3f}, n={mask.sum()}")
335
+
336
+ return {"mean_confidence": float(all_conf.mean()), "mean_accuracy": float(all_correct.mean()), "n_total": len(all_conf)}
337
+
338
+
339
+ def main():
340
+ start = time.time()
341
+ print("=" * 70)
342
+ print("ConformalESM-Complete: All 10 Extensions")
343
+ print("Citing: Lin et al. 2022 (ESM-2, Science)")
344
+ print("=" * 70)
345
+
346
+ tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)
347
+ model = AutoModelForTokenClassification.from_pretrained(MODEL_ID)
348
+ model.eval()
349
+
350
+ cal_ds, test_ds = load_data()
351
+ cal_results = get_predictions_cached(model, tokenizer, cal_ds, batch_size=8)
352
+ test_results = get_predictions_cached(model, tokenizer, test_ds, batch_size=8)
353
+
354
+ print(f"\nBaseline Accuracy: {accuracy(test_results):.4f}")
355
+ print(f"Baseline ECE: {ece(test_results):.4f}")
356
+
357
+ best_temp = find_temperature(cal_results)
358
+ print(f"Optimal temperature: {best_temp:.3f}")
359
+ scaled_test = apply_temperature(test_results, best_temp)
360
+ scaled_cal = apply_temperature(cal_results, best_temp)
361
+
362
+ all_results = {
363
+ "baseline": {"accuracy": accuracy(test_results), "ece": ece(test_results)},
364
+ "temperature_scaling": {"temperature": best_temp, "accuracy": accuracy(scaled_test), "ece": ece(scaled_test)},
365
+ }
366
+
367
+ all_results["ext1_standard_conformal"] = extension_1_standard_conformal(scaled_cal, scaled_test)
368
+ all_results["ext2_class_conditional"] = extension_2_class_conditional(scaled_cal, scaled_test)
369
+
370
+ scores = []
371
+ for r in scaled_cal:
372
+ for j, label in enumerate(r["true"]):
373
+ scores.append(1.0 - r["probs"][j, label])
374
+ q = np.quantile(np.array(scores), np.ceil((len(scores) + 1) * 0.9) / len(scores), method="higher")
375
+
376
+ all_results["ext6_size_stratified"] = extension_6_size_stratified(scaled_test, q)
377
+ all_results["ext7_protein_uncertainty"] = extension_7_protein_uncertainty(scaled_test)
378
+ all_results["ext9_mondrian"] = extension_9_mondrian(scaled_cal, scaled_test)
379
+ all_results["ext10_calibration"] = extension_10_calibration(scaled_test)
380
+
381
+ elapsed = time.time() - start
382
+ all_results["metadata"] = {"n_cal": N_CAL, "n_test": N_TEST, "elapsed_seconds": elapsed}
383
+
384
+ def convert(obj):
385
+ if isinstance(obj, np.ndarray):
386
+ return obj.tolist()
387
+ elif isinstance(obj, (np.int64, np.int32)):
388
+ return int(obj)
389
+ elif isinstance(obj, (np.float64, np.float32)):
390
+ return float(obj)
391
+ elif isinstance(obj, dict):
392
+ return {k: convert(v) for k, v in obj.items()}
393
+ elif isinstance(obj, list):
394
+ return [convert(v) for v in obj]
395
+ return obj
396
+
397
+ with open("/app/all_extensions_results.json", "w") as f:
398
+ json.dump(convert(all_results), f, indent=2)
399
+
400
+ print(f"\n{'='*70}")
401
+ print(f"Results saved. Total time: {elapsed/60:.1f} minutes")
402
+ print(f"{'='*70}")
403
+
404
+
405
+ if __name__ == "__main__":
406
+ main()