Joblib
ynuozhang commited on
Commit
8cf0b21
Β·
1 Parent(s): adff1ee

update more test code for inference

Browse files
Files changed (3) hide show
  1. README.md +12 -4
  2. inference.py +163 -54
  3. requirements.txt +16 -10
README.md CHANGED
@@ -82,13 +82,13 @@ pip install -r requirements.txt
82
  ```
83
  ### Full Setup
84
  - Additional access to trained SVM and ElastNet models requires installation of `RAPIDS cuML`, with instructions available from their official [github page](https://github.com/rapidsai/cuml) (**CUDA-capable GPU required**).
85
- - Optional: pre-compiled Singularity/Apptainer environment (7.52G) is available at [Google drive](https://drive.google.com/file/d/1RJQ9HK0_gsPOhRo5H5ZmH_MYcpJqQD7e/view?usp=sharing) with everything you need (still need CUDA/GPU to load cuML models).
86
  ```
87
  # test
88
  apptainer exec peptiverse.sif python -c "import sys; print(sys.executable)"
89
 
90
  # run inference (see below)
91
- apptainer exec peptiverse.sif python inference.py
92
  ```
93
  ## Repository Structure 🌟
94
  This repo contains important large files for [PeptiVerse](https://huggingface.co/spaces/ChatterjeeLab/PeptiVerse), an interactive app for peptide property prediction. [Paper link.](https://www.biorxiv.org/content/10.64898/2025.12.31.697180v1)
@@ -112,6 +112,14 @@ PeptiVerse/
112
  ```
113
  For full data access, please download the corresponding `training_data_cleaned` and `training_classifiers` from [HuggingFace Dataset](https://huggingface.co/datasets/ChatterjeeLab/PeptiVerse_data) and [Zenodo](https://zenodo.org/records/19989009). The current Huggingface repo only hosts best model weights and meta data with splits labels.
114
 
 
 
 
 
 
 
 
 
115
  ## Training Data Collection 🌟
116
 
117
  <table>
@@ -241,7 +249,7 @@ python app.py
241
  Under the `training_data_cleaned`, we provided the generated embeddings in huggingface dataset format. The following scripts are the steps used to generate the data.
242
 
243
  ### Dataset integration
244
- - All processed training datasets are available at [ChatterjeeLab/PeptiVerse_data](https://huggingface.co/datasets/ChatterjeeLab/PeptiVerse_data).
245
  - Selective download the data you need with `huggingface-cli`
246
  ```bash
247
  huggingface-cli download ChatterjeeLab/PeptiVerse_data \
@@ -681,4 +689,4 @@ If you find this repository helpful for your publications, please consider citin
681
  journal = {bioRxiv}
682
  }
683
  ```
684
- To use this repository, you agree to abide by the MIT License.
 
82
  ```
83
  ### Full Setup
84
  - Additional access to trained SVM and ElastNet models requires installation of `RAPIDS cuML`, with instructions available from their official [github page](https://github.com/rapidsai/cuml) (**CUDA-capable GPU required**).
85
+ - Optional: pre-compiled Singularity/Apptainer environment (5.68G) is available at [Google drive](https://drive.google.com/file/d/1ybLJNTC3BITIqBd8IO09nOOm4PKwD4iS/view?usp=sharing) with everything you need (still need CUDA/GPU to load cuML models). The SHA256 for checking is `48619796ef0adc81bc420021821e5ee3d9b2176bf1f564104e06dc1ce56b3498`, check via `shasum -a 256 peptiverse.sif`.
86
  ```
87
  # test
88
  apptainer exec peptiverse.sif python -c "import sys; print(sys.executable)"
89
 
90
  # run inference (see below)
91
+ apptainer exec --nv peptiverse.sif python inference.py
92
  ```
93
  ## Repository Structure 🌟
94
  This repo contains important large files for [PeptiVerse](https://huggingface.co/spaces/ChatterjeeLab/PeptiVerse), an interactive app for peptide property prediction. [Paper link.](https://www.biorxiv.org/content/10.64898/2025.12.31.697180v1)
 
112
  ```
113
  For full data access, please download the corresponding `training_data_cleaned` and `training_classifiers` from [HuggingFace Dataset](https://huggingface.co/datasets/ChatterjeeLab/PeptiVerse_data) and [Zenodo](https://zenodo.org/records/19989009). The current Huggingface repo only hosts best model weights and meta data with splits labels.
114
 
115
+ ```
116
+ 1. Download and extract the Zenodo archive.
117
+ 2. Download or clone this repository.
118
+ 3. Copy/merge the repository `training_classifiers/` contents into the extracted Zenodo `training_classifiers/` directory, preserving the folder structure.
119
+ `rsync -av --ignore-existing training_classifiers/ /path/to/zenodo_extracted/training_classifiers/`
120
+ 4. Do not replace the entire Zenodo folder blindly; merge files so that large training outputs from Zenodo and updated best-model weights from this repository coexist.
121
+ ```
122
+
123
  ## Training Data Collection 🌟
124
 
125
  <table>
 
249
  Under the `training_data_cleaned`, we provided the generated embeddings in huggingface dataset format. The following scripts are the steps used to generate the data.
250
 
251
  ### Dataset integration
252
+ - All processed training datasets are available at [ChatterjeeLab/PeptiVerse\_data](https://huggingface.co/datasets/ChatterjeeLab/PeptiVerse_data).
253
  - Selective download the data you need with `huggingface-cli`
254
  ```bash
255
  huggingface-cli download ChatterjeeLab/PeptiVerse_data \
 
689
  journal = {bioRxiv}
690
  }
691
  ```
692
+ To use this repository, you agree to abide by the MIT License.
inference.py CHANGED
@@ -13,10 +13,6 @@ from tokenizer.my_tokenizers import SMILES_SPE_Tokenizer
13
  from lightning.pytorch import seed_everything
14
  seed_everything(1986)
15
 
16
- # -----------------------------
17
- # Manifest
18
- # -----------------------------
19
-
20
  EMB_TAG_TO_FOLDER_SUFFIX = {
21
  "wt": "wt",
22
  "peptideclm": "peptideclm",
@@ -372,17 +368,10 @@ def _mapie_uncertainty(mapie_bundle: dict, score: float,
372
  if "quantile" in mapie_bundle:
373
  q = float(mapie_bundle["quantile"])
374
  return float(score - q), float(score + q)
375
-
376
- X_dummy = np.zeros((1, 1))
377
- result = mapie.predict(X_dummy)
378
- if isinstance(result, tuple):
379
- intervals = np.asarray(result[1])
380
- if intervals.ndim == 3:
381
- return float(intervals[0, 0, 0]), float(intervals[0, 1, 0])
382
- return float(intervals[0, 0]), float(intervals[0, 1])
383
  raise RuntimeError(
384
- f"Cannot extract intervals: unknown MAPIE bundle format. "
385
- f"Bundle keys: {list(mapie_bundle.keys())}."
386
  )
387
 
388
  def affinity_to_class(y: float) -> int:
@@ -870,8 +859,11 @@ class PeptiVersePredictor:
870
  mapie_bundle = self.mapie.get((prop_key, col))
871
  if mapie_bundle:
872
  emb = get_pooled_emb() if mapie_bundle.get("adaptive") else None
873
- lo, hi = _mapie_uncertainty(mapie_bundle, score, emb)
874
- return (lo, hi), "conformal_prediction_interval"
 
 
 
875
  # Fall back to seed ensemble std
876
  ens = self.ensembles.get((prop_key, col))
877
  if ens:
@@ -892,8 +884,11 @@ class PeptiVersePredictor:
892
  mapie_bundle = self.mapie.get((prop_key, col))
893
  if mapie_bundle:
894
  emb = get_pooled_emb() if mapie_bundle.get("adaptive") else None
895
- lo, hi = _mapie_uncertainty(mapie_bundle, score, emb)
896
- return (lo, hi), "conformal_prediction_interval"
 
 
 
897
  return None, "unavailable (no MAPIE bundle for XGBoost regression)"
898
 
899
  # SVR / ElasticNet regression: MAPIE
@@ -901,8 +896,11 @@ class PeptiVersePredictor:
901
  mapie_bundle = self.mapie.get((prop_key, col))
902
  if mapie_bundle:
903
  emb = get_pooled_emb() if mapie_bundle.get("adaptive") else None
904
- lo, hi = _mapie_uncertainty(mapie_bundle, score, emb)
905
- return (lo, hi), "conformal_prediction_interval"
 
 
 
906
  return None, "unavailable (MAPIE bundle not found)"
907
 
908
  # joblib classifiers (SVM, ElasticNet used as classifier)
@@ -1035,9 +1033,13 @@ class PeptiVersePredictor:
1035
  emb = np.concatenate([t_vec, b_vec], axis=1)
1036
  else:
1037
  emb = None
1038
- lo, hi = _mapie_uncertainty(mapie_bundle, affinity, emb)
1039
- out["uncertainty"] = (lo, hi)
1040
- out["uncertainty_type"] = "conformal_prediction_interval"
 
 
 
 
1041
  else:
1042
  out["uncertainty"] = None
1043
  out["uncertainty_type"] = "unavailable (no MAPIE bundle found)"
@@ -1045,35 +1047,142 @@ class PeptiVersePredictor:
1045
  return out
1046
 
1047
  if __name__ == "__main__":
1048
- root = Path(__file__).resolve().parent # current script folder
1049
 
1050
- predictor = PeptiVersePredictor(
1051
- manifest_path=root / "basic_models.txt",
1052
- classifier_weight_root=root
1053
- )
1054
- print(predictor.training_root)
1055
- print("MAPIE keys:", list(predictor.mapie.keys()))
1056
- print("Ensemble keys:", list(predictor.ensembles.keys()))
1057
-
1058
- seq = "GIGAVLKVLTTGLPALISWIKRKRQQ"
1059
- smiles = "C(C)C[C@@H]1NC(=O)[C@@H]2CCCN2C(=O)[C@@H](CC(C)C)NC(=O)[C@@H](CC(C)C)N(C)C(=O)[C@H](C)NC(=O)[C@H](Cc2ccccc2)NC1=O"
1060
-
1061
- print(predictor.predict_property("hemolysis", "wt", seq))
1062
- print(predictor.predict_property("hemolysis", "smiles", smiles, uncertainty=False))
1063
- print(predictor.predict_property("nf", "wt", seq, uncertainty=False))
1064
- print(predictor.predict_property("nf", "smiles", smiles, uncertainty=False))
1065
- print(predictor.predict_binding_affinity("wt", target_seq=seq, binder_str="GIGAVLKVLT"))
1066
- print(predictor.predict_binding_affinity("wt", target_seq=seq, binder_str="GIGAVLKVLT", uncertainty=False))
1067
- seq1 = "GIGAVLKVLTTGLPALISWIKRKRQQ"
1068
- seq2 = "ACDEFGHIKLMNPQRSTVWY"
1069
-
1070
- r1 = predictor.predict_binding_affinity("wt", target_seq=seq2, binder_str="GIGAVLKVLT", uncertainty=False)
1071
- r2 = predictor.predict_property("nf", "wt", seq1, uncertainty=False)
1072
- r3 = predictor.predict_property("nf", "wt", seq2, uncertainty=False)
1073
- r4 = predictor.predict_binding_affinity("wt", target_seq=seq2, binder_str=smiles, uncertainty=False)
1074
- r5 = predictor.predict_property("halflife", "smiles", smiles)
1075
- print(r1)
1076
- print(r2)
1077
- print(r3)
1078
- print(r4)
1079
- print(r5)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  from lightning.pytorch import seed_everything
14
  seed_everything(1986)
15
 
 
 
 
 
16
  EMB_TAG_TO_FOLDER_SUFFIX = {
17
  "wt": "wt",
18
  "peptideclm": "peptideclm",
 
368
  if "quantile" in mapie_bundle:
369
  q = float(mapie_bundle["quantile"])
370
  return float(score - q), float(score + q)
371
+
 
 
 
 
 
 
 
372
  raise RuntimeError(
373
+ f"Unrecognised MAPIE bundle (keys={list(mapie_bundle.keys()) if isinstance(mapie_bundle, dict) else type(mapie_bundle).__name__}). "
374
+ f"Run repair_mapie_bundles.py to convert."
375
  )
376
 
377
  def affinity_to_class(y: float) -> int:
 
859
  mapie_bundle = self.mapie.get((prop_key, col))
860
  if mapie_bundle:
861
  emb = get_pooled_emb() if mapie_bundle.get("adaptive") else None
862
+ try:
863
+ lo, hi = _mapie_uncertainty(mapie_bundle, score, emb)
864
+ return (lo, hi), "conformal_prediction_interval"
865
+ except RuntimeError as _e:
866
+ return None, f"unavailable (legacy bundle: {_e})"
867
  # Fall back to seed ensemble std
868
  ens = self.ensembles.get((prop_key, col))
869
  if ens:
 
884
  mapie_bundle = self.mapie.get((prop_key, col))
885
  if mapie_bundle:
886
  emb = get_pooled_emb() if mapie_bundle.get("adaptive") else None
887
+ try:
888
+ lo, hi = _mapie_uncertainty(mapie_bundle, score, emb)
889
+ return (lo, hi), "conformal_prediction_interval"
890
+ except RuntimeError as _e:
891
+ return None, f"unavailable (legacy bundle: {_e})"
892
  return None, "unavailable (no MAPIE bundle for XGBoost regression)"
893
 
894
  # SVR / ElasticNet regression: MAPIE
 
896
  mapie_bundle = self.mapie.get((prop_key, col))
897
  if mapie_bundle:
898
  emb = get_pooled_emb() if mapie_bundle.get("adaptive") else None
899
+ try:
900
+ lo, hi = _mapie_uncertainty(mapie_bundle, score, emb)
901
+ return (lo, hi), "conformal_prediction_interval"
902
+ except RuntimeError as _e:
903
+ return None, f"unavailable (legacy bundle: {_e})"
904
  return None, "unavailable (MAPIE bundle not found)"
905
 
906
  # joblib classifiers (SVM, ElasticNet used as classifier)
 
1033
  emb = np.concatenate([t_vec, b_vec], axis=1)
1034
  else:
1035
  emb = None
1036
+ try:
1037
+ lo, hi = _mapie_uncertainty(mapie_bundle, affinity, emb)
1038
+ out["uncertainty"] = (lo, hi)
1039
+ out["uncertainty_type"] = "conformal_prediction_interval"
1040
+ except RuntimeError as _e:
1041
+ out["uncertainty"] = None
1042
+ out["uncertainty_type"] = f"unavailable (legacy bundle: {_e})"
1043
  else:
1044
  out["uncertainty"] = None
1045
  out["uncertainty_type"] = "unavailable (no MAPIE bundle found)"
 
1047
  return out
1048
 
1049
  if __name__ == "__main__":
1050
+ import traceback, time
1051
 
1052
+ root = Path(__file__).resolve().parent
1053
+
1054
+ SEQ = "GIGAVLKVLTTGLPALISWIKRKRQQ"
1055
+ SEQ2 = "ACDEFGHIKLMNPQRSTVWY"
1056
+ BINDER = "GIGAVLKVLT"
1057
+ SMILES = ("C(C)C[C@@H]1NC(=O)[C@@H]2CCCN2C(=O)[C@@H](CC(C)C)"
1058
+ "NC(=O)[C@@H](CC(C)C)N(C)C(=O)[C@H](C)NC(=O)"
1059
+ "[C@H](Cc2ccccc2)NC1=O")
1060
+
1061
+ COL_INPUT = {"wt": SEQ, "smiles": SMILES}
1062
+ BINDING_BINDER = {"wt": BINDER, "smiles": SMILES}
1063
+
1064
+ MANIFESTS = [
1065
+ ("best_models.txt", "best"),
1066
+ ("basic_models.txt", "basic"),
1067
+ ]
1068
+
1069
+ all_results: List[Tuple[str, str, str]] = [] # (label, status, detail)
1070
+
1071
+ for manifest_file, label in MANIFESTS:
1072
+ sep = "=" * 72
1073
+ print(f"\n{sep}")
1074
+ print(f" MANIFEST: {manifest_file} [{label}]")
1075
+ print(sep)
1076
+
1077
+ predictor = PeptiVersePredictor(
1078
+ manifest_path=root / manifest_file,
1079
+ classifier_weight_root=root,
1080
+ )
1081
+
1082
+ print(f"\n Models loaded : {len(predictor.models)}")
1083
+ print(f" MAPIE keys : {sorted(predictor.mapie.keys())}")
1084
+ print(f" Ensemble keys : {sorted(predictor.ensembles.keys())}")
1085
+ print()
1086
+
1087
+ # ── Regular properties ─────────────────────────────────────────────────
1088
+ for prop_key, row in predictor.manifest.items():
1089
+ if prop_key == "binding_affinity":
1090
+ continue
1091
+
1092
+ for col in ("wt", "smiles"):
1093
+ if (prop_key, col) not in predictor.models:
1094
+ continue
1095
+
1096
+ input_str = COL_INPUT[col]
1097
+ has_mapie = (prop_key, col) in predictor.mapie
1098
+
1099
+ for uncertainty in (False, True):
1100
+ tag = f"{label}/{prop_key}/{col}/unc={uncertainty}"
1101
+ try:
1102
+ t0 = time.time()
1103
+ r = predictor.predict_property(
1104
+ prop_key, col, input_str, uncertainty=uncertainty
1105
+ )
1106
+ dt = time.time() - t0
1107
+
1108
+ score_str = f"score={r['score']:.4f}"
1109
+ if uncertainty:
1110
+ u = r.get("uncertainty")
1111
+ u_type = r.get("uncertainty_type", "")
1112
+ u_str = (f" unc={u[0]:.3f}..{u[1]:.3f}" if isinstance(u, tuple)
1113
+ else f" unc={u:.4f}" if u is not None
1114
+ else f" unc=None")
1115
+ mapie_flag = " [MAPIE]" if has_mapie else ""
1116
+ print(f" [PASS] {tag:<60} {dt:5.2f}s {score_str}{u_str} "
1117
+ f"({u_type}){mapie_flag}")
1118
+ else:
1119
+ print(f" [PASS] {tag:<60} {dt:5.2f}s {score_str}")
1120
+
1121
+ all_results.append((tag, "PASS", ""))
1122
+ except Exception as e:
1123
+ print(f" [FAIL] {tag}")
1124
+ traceback.print_exc()
1125
+ all_results.append((tag, "FAIL", str(e)))
1126
+
1127
+ # ── Binding affinity ───────────────────────────────────────────────────
1128
+ prop_key = "binding_affinity"
1129
+ for col in ("wt", "smiles"):
1130
+ if (prop_key, col) not in predictor.models:
1131
+ continue
1132
+
1133
+ binder_str = BINDING_BINDER[col]
1134
+ has_mapie = (prop_key, col) in predictor.mapie
1135
+
1136
+ for uncertainty in (False, True):
1137
+ tag = f"{label}/{prop_key}/{col}/unc={uncertainty}"
1138
+ try:
1139
+ t0 = time.time()
1140
+ r = predictor.predict_binding_affinity(
1141
+ col, target_seq=SEQ2, binder_str=binder_str, uncertainty=uncertainty
1142
+ )
1143
+ dt = time.time() - t0
1144
+
1145
+ aff_str = f"affinity={r['affinity']:.3f}"
1146
+ if uncertainty:
1147
+ u = r.get("uncertainty")
1148
+ u_type = r.get("uncertainty_type", "")
1149
+ u_str = (f" unc={u[0]:.3f}..{u[1]:.3f}" if isinstance(u, tuple)
1150
+ else f" unc=None")
1151
+ mapie_flag = " [MAPIE]" if has_mapie else ""
1152
+ print(f" [PASS] {tag:<60} {dt:5.2f}s {aff_str}{u_str} "
1153
+ f"({u_type}){mapie_flag}")
1154
+ else:
1155
+ print(f" [PASS] {tag:<60} {dt:5.2f}s {aff_str} "
1156
+ f"cls={r['class_by_threshold']}")
1157
+
1158
+ all_results.append((tag, "PASS", ""))
1159
+ except Exception as e:
1160
+ print(f" [FAIL] {tag}")
1161
+ traceback.print_exc()
1162
+ all_results.append((tag, "FAIL", str(e)))
1163
+
1164
+ # ── MAPIE bundle inventory ─────────────────────────────────────────────
1165
+ print(f"\n --- MAPIE bundle inventory [{label}] ---")
1166
+ if not predictor.mapie:
1167
+ print(" (none)")
1168
+ for (pk, col), bundle in sorted(predictor.mapie.items()):
1169
+ fmt = "adaptive" if bundle.get("adaptive") else "plain_quantile"
1170
+ q = bundle.get("quantile", "?")
1171
+ sigma = "sigma_model=" + type(bundle["sigma_model"]).__name__ if "sigma_model" in bundle else ""
1172
+ print(f" {label}/{pk}/{col:<30} format={fmt} q={q} {sigma}")
1173
+
1174
+ # ── Summary ────────────────────────────────────────────────────────────────
1175
+ sep = "=" * 72
1176
+ print(f"\n{sep}")
1177
+ print(" SUMMARY")
1178
+ print(sep)
1179
+ passed = [r for r in all_results if r[1] == "PASS"]
1180
+ failed = [r for r in all_results if r[1] == "FAIL"]
1181
+ print(f" PASSED : {len(passed)} / {len(all_results)}")
1182
+ if failed:
1183
+ print(f" FAILED : {len(failed)}")
1184
+ for tag, _, err in failed:
1185
+ print(f" βœ— {tag}")
1186
+ print(f" {err}")
1187
+ else:
1188
+ print(" All tests passed.")
requirements.txt CHANGED
@@ -1,14 +1,20 @@
1
- gradio>=4.0.0
 
 
 
 
 
 
 
 
 
 
2
  pandas>=2.0.0
3
- numpy>=1.24.0
 
 
4
  plotly>=5.14.0
5
- torch>=2.0.0
6
- transformers==4.46.0
7
- scikit-learn>=1.3.0
8
- biopython>=1.81
9
- lightning==2.5.5
10
- rdkit>=2023.3.1
11
  seaborn
12
- SmilesPE
13
- xgboost
14
  ipython
 
1
+ # ── Core inference ────────────────────────────────
2
+ numpy>=2.0.0 # cupy 14.x (RAPIDS 25.x dep) requires numpy >= 2.0
3
+ torch>=2.1.0
4
+ transformers==4.46.0
5
+ scikit-learn>=1.3.0
6
+ lightning==2.5.5
7
+ joblib>=1.3.0
8
+ xgboost>=2.0.0
9
+ SmilesPE
10
+ mapie>=0.6.0 # conformal prediction (fit_mapie_adaptive.py)
11
+ tqdm
12
  pandas>=2.0.0
13
+
14
+ # ── App ────────────────────────────────────────────────────
15
+ gradio>=4.0.0
16
  plotly>=5.14.0
 
 
 
 
 
 
17
  seaborn
18
+ rdkit>=2023.3.1
19
+ biopython>=1.81
20
  ipython