anzhi2710gmailcom commited on
Commit
3b99abb
·
verified ·
1 Parent(s): 1e0210e

Upload folder using huggingface_hub

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +22 -10
  2. .idea/SaProt.iml +7 -0
  3. LICENSE +21 -0
  4. README.md +605 -0
  5. conf/ClinVar/esm2.yaml +36 -0
  6. conf/ClinVar/saprot.yaml +37 -0
  7. conf/Contact/esm2.yaml +67 -0
  8. conf/Contact/saprot.yaml +66 -0
  9. conf/DeepLoc/cls10/esm2.yaml +67 -0
  10. conf/DeepLoc/cls10/saprot.yaml +68 -0
  11. conf/DeepLoc/cls2/esm2.yaml +67 -0
  12. conf/DeepLoc/cls2/saprot.yaml +68 -0
  13. conf/EC/esm2.yaml +67 -0
  14. conf/EC/saprot.yaml +68 -0
  15. conf/GO/BP/esm2.yaml +67 -0
  16. conf/GO/BP/saprot.yaml +68 -0
  17. conf/GO/CC/esm2.yaml +67 -0
  18. conf/GO/CC/saprot.yaml +68 -0
  19. conf/GO/MF/esm2.yaml +67 -0
  20. conf/GO/MF/saprot.yaml +68 -0
  21. conf/HumanPPI/esm2.yaml +66 -0
  22. conf/HumanPPI/saprot.yaml +67 -0
  23. conf/MetalIonBinding/esm2.yaml +67 -0
  24. conf/MetalIonBinding/saprot.yaml +68 -0
  25. conf/ProteinGym/esm2.yaml +33 -0
  26. conf/ProteinGym/saprot.yaml +36 -0
  27. conf/Thermostability/esm2.yaml +69 -0
  28. conf/Thermostability/saprot.yaml +68 -0
  29. conf/pretrain/saprot.yaml +81 -0
  30. conf/scnet/ClinVar_saprot.yaml +37 -0
  31. conf/scnet/Thermostability_saprot_1gpu.yaml +68 -0
  32. config.json +102 -0
  33. model/README.md +9 -0
  34. model/abstract_model.py +282 -0
  35. model/model_interface.py +103 -0
  36. model/saprot/base.py +210 -0
  37. model/saprot/esm_mutation_model.py +288 -0
  38. model/saprot/saprot_annotation_model.py +89 -0
  39. model/saprot/saprot_classification_model.py +74 -0
  40. model/saprot/saprot_contact_model.py +145 -0
  41. model/saprot/saprot_foldseek_mutation_model.py +383 -0
  42. model/saprot/saprot_if_model.py +130 -0
  43. model/saprot/saprot_lm_model.py +74 -0
  44. model/saprot/saprot_ppi_model.py +78 -0
  45. model/saprot/saprot_regression_model.py +95 -0
  46. requirements.txt +16 -0
  47. scripts/LMDB/README.md +1 -0
  48. scripts/__init__.py +1 -0
  49. scripts/bin/README.md +1 -0
  50. scripts/compute_clinvar_auc.py +57 -0
.gitattributes CHANGED
@@ -1,35 +1,47 @@
1
  *.7z filter=lfs diff=lfs merge=lfs -text
2
  *.arrow filter=lfs diff=lfs merge=lfs -text
3
  *.bin filter=lfs diff=lfs merge=lfs -text
 
4
  *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
  *.ftz filter=lfs diff=lfs merge=lfs -text
7
  *.gz filter=lfs diff=lfs merge=lfs -text
8
  *.h5 filter=lfs diff=lfs merge=lfs -text
9
  *.joblib filter=lfs diff=lfs merge=lfs -text
10
  *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
  *.model filter=lfs diff=lfs merge=lfs -text
13
  *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
  *.onnx filter=lfs diff=lfs merge=lfs -text
17
  *.ot filter=lfs diff=lfs merge=lfs -text
18
  *.parquet filter=lfs diff=lfs merge=lfs -text
19
  *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
  *.pt filter=lfs diff=lfs merge=lfs -text
23
  *.pth filter=lfs diff=lfs merge=lfs -text
24
  *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
  saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
  *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
  *.tflite filter=lfs diff=lfs merge=lfs -text
30
  *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
  *.xz filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
1
  *.7z filter=lfs diff=lfs merge=lfs -text
2
  *.arrow filter=lfs diff=lfs merge=lfs -text
3
  *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bin.* filter=lfs diff=lfs merge=lfs -text
5
  *.bz2 filter=lfs diff=lfs merge=lfs -text
 
6
  *.ftz filter=lfs diff=lfs merge=lfs -text
7
  *.gz filter=lfs diff=lfs merge=lfs -text
8
  *.h5 filter=lfs diff=lfs merge=lfs -text
9
  *.joblib filter=lfs diff=lfs merge=lfs -text
10
  *.lfs.* filter=lfs diff=lfs merge=lfs -text
 
11
  *.model filter=lfs diff=lfs merge=lfs -text
12
  *.msgpack filter=lfs diff=lfs merge=lfs -text
 
 
13
  *.onnx filter=lfs diff=lfs merge=lfs -text
14
  *.ot filter=lfs diff=lfs merge=lfs -text
15
  *.parquet filter=lfs diff=lfs merge=lfs -text
16
  *.pb filter=lfs diff=lfs merge=lfs -text
 
 
17
  *.pt filter=lfs diff=lfs merge=lfs -text
18
  *.pth filter=lfs diff=lfs merge=lfs -text
19
  *.rar filter=lfs diff=lfs merge=lfs -text
 
20
  saved_model/**/* filter=lfs diff=lfs merge=lfs -text
21
  *.tar.* filter=lfs diff=lfs merge=lfs -text
 
22
  *.tflite filter=lfs diff=lfs merge=lfs -text
23
  *.tgz filter=lfs diff=lfs merge=lfs -text
 
24
  *.xz filter=lfs diff=lfs merge=lfs -text
25
  *.zip filter=lfs diff=lfs merge=lfs -text
26
+ *.zstandard filter=lfs diff=lfs merge=lfs -text
27
+ *.tfevents* filter=lfs diff=lfs merge=lfs -text
28
+ *.db* filter=lfs diff=lfs merge=lfs -text
29
+ *.ark* filter=lfs diff=lfs merge=lfs -text
30
+ **/*ckpt*data* filter=lfs diff=lfs merge=lfs -text
31
+ **/*ckpt*.meta filter=lfs diff=lfs merge=lfs -text
32
+ **/*ckpt*.index filter=lfs diff=lfs merge=lfs -text
33
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
34
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
35
+ *.gguf* filter=lfs diff=lfs merge=lfs -text
36
+ *.ggml filter=lfs diff=lfs merge=lfs -text
37
+ *.llamafile* filter=lfs diff=lfs merge=lfs -text
38
+ *.pt2 filter=lfs diff=lfs merge=lfs -text
39
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
40
+ *.npy filter=lfs diff=lfs merge=lfs -text
41
+ *.npz filter=lfs diff=lfs merge=lfs -text
42
+ *.pickle filter=lfs diff=lfs merge=lfs -text
43
+ *.pkl filter=lfs diff=lfs merge=lfs -text
44
+ *.tar filter=lfs diff=lfs merge=lfs -text
45
+ *.wasm filter=lfs diff=lfs merge=lfs -text
46
  *.zst filter=lfs diff=lfs merge=lfs -text
47
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
.idea/SaProt.iml ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <module version="4">
3
+ <component name="PyDocumentationSettings">
4
+ <option name="format" value="GOOGLE" />
5
+ <option name="myDocStringFormat" value="Google" />
6
+ </component>
7
+ </module>
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2023 westlake-repl
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
README.md ADDED
@@ -0,0 +1,605 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: mit
3
+ language:
4
+ - en
5
+ - zh
6
+ tags:
7
+ - OneScience
8
+ - life-science
9
+ - protein
10
+ - protein-language-model
11
+ - structure-aware
12
+ - mutation-effect
13
+ - embedding
14
+ - inverse-folding
15
+ - SaProt
16
+ frameworks: PyTorch
17
+ ---
18
+
19
+ <p align="center">
20
+ <strong>
21
+ <span style="font-size: 30px;">SaProt</span>
22
+ </strong>
23
+ </p>
24
+
25
+ # Model Introduction
26
+
27
+ SaProt (Protein Language Modeling with Structure-aware Vocabulary) is a protein language model that jointly models protein amino acid sequences and structural information. Its central idea is to combine amino acids (AA) with the 3Di structural alphabet generated by Foldseek into structure-aware tokens, allowing the model to learn representations from both protein sequences and structural context.
28
+
29
+ SaProt can be used for protein representation extraction, zero-shot mutation effect prediction, protein inverse folding, and downstream task fine-tuning.
30
+
31
+ Paper:
32
+
33
+ > **SaProt: Protein Language Modeling with Structure-aware Vocabulary**
34
+ > ICLR 2024 Spotlight
35
+ > Follow-up work was published in Nature Biotechnology (2025)
36
+
37
+ # Model Description
38
+
39
+ SaProt models proteins using a structure-aware vocabulary formed by combining amino acids (AA) with the Foldseek 3Di structural alphabet.
40
+
41
+ For example, a structure-aware sequence can be represented as:
42
+
43
+ ```text
44
+ M#EvVpQpL#VyQdYaKv
45
+ ```
46
+
47
+ Every two characters form a structure-aware token: the first character represents the amino acid, and the second represents the corresponding 3Di structural state. `#` can be used to mask low-confidence structural regions.
48
+
49
+ The official release provides pretrained models at multiple scales:
50
+
51
+ | Model | Parameter scale | Training data |
52
+ | --- | ---: | --- |
53
+ | `SaProt_35M_AF2` | 35M | 40M AF2 structures |
54
+ | `SaProt_650M_PDB` | 650M | 40M AF2 structures + 60K PDB structures |
55
+ | `SaProt_650M_AF2` | 650M | 40M AF2 structures |
56
+ | `SaProt_1.3B_AF2` | 1.3B | 40M AF2 structures |
57
+ | `SaProt_1.3B_AFDB_OMG_NCBI` | 1.3B | AFDB + OMG_prot50 + NCBI |
58
+
59
+ For the 35M and 650M SaProt models, the official recommendation is to use SA-token inputs containing structural information for the best results. The 1.3B version can handle both structure-aware sequences and amino-acid-only sequences relatively well.
60
+
61
+ # Use Cases
62
+
63
+ | Use case | Description |
64
+ | --- | --- |
65
+ | Protein representation extraction | Extract residue-level or protein-level embeddings |
66
+ | Zero-shot mutation effect prediction | Evaluate single or multiple mutations directly without task-specific fine-tuning |
67
+ | Structure-aware protein modeling | Jointly use amino acid and 3Di structural tokens |
68
+ | Protein inverse folding | Design sequences from structural information |
69
+ | Downstream task fine-tuning | Apply to tasks such as EC, GO, stability, PPI, Contact, and DeepLoc |
70
+
71
+ # Usage
72
+
73
+ ## 1. Using OneCode
74
+
75
+ Experience intelligent one-click AI4S programming in the OneCode online environment:
76
+
77
+ [Try intelligent one-click AI4S programming](https://web-2069360198568017922-iaaj.ksai.scnet.cn:58043/home)
78
+
79
+ ## 2. Manual Installation and Usage
80
+
81
+ **Hardware Requirements**
82
+
83
+ - SaProt supports inference on CPUs and GPUs/DCUs.
84
+ - The 35M model can be used for lightweight testing; GPUs/DCUs are recommended for the 650M and 1.3B models.
85
+ - Batch embedding, mutation scanning, pretraining, and fine-tuning substantially increase GPU memory and host memory requirements.
86
+
87
+ ### Set Up the Runtime Environment
88
+
89
+ #### DCU Environment
90
+
91
+ ```bash
92
+ # Activate DTK and CONDA first
93
+ conda create -n onescience311 python=3.11 -y
94
+ conda activate onescience311
95
+
96
+ pip install onescience[bio] \
97
+ -i http://mirrors.onescience.ai:3141/pypi/simple/ \
98
+ --trusted-host mirrors.onescience.ai
99
+ ```
100
+
101
+ #### Environment Notes
102
+
103
+ - If you encounter missing dependencies or version incompatibilities during execution, refer to the dependency versions specified in `requirements.txt` and install or adjust the relevant dependencies as needed.
104
+
105
+ ### Prepare Models and Data
106
+
107
+ #### 1) SaProt Model Weights
108
+
109
+ The official models are primarily released on Hugging Face:
110
+
111
+ ```text
112
+ SaProt_35M_AF2
113
+ https://huggingface.co/westlake-repl/SaProt_35M_AF2
114
+
115
+ SaProt_650M_PDB
116
+ https://huggingface.co/westlake-repl/SaProt_650M_PDB
117
+
118
+ SaProt_650M_AF2
119
+ https://huggingface.co/westlake-repl/SaProt_650M_AF2
120
+
121
+ SaProt_1.3B_AF2
122
+ https://huggingface.co/westlake-repl/SaProt_1.3B_AF2
123
+
124
+ SaProt_1.3B_AFDB_OMG_NCBI
125
+ https://huggingface.co/westlake-repl/SaProt_1.3B_AFDB_OMG_NCBI
126
+ ```
127
+
128
+ For example, download `SaProt_650M_AF2` in advance for offline use:
129
+
130
+ ```bash
131
+ huggingface-cli download \
132
+ westlake-repl/SaProt_650M_AF2 \
133
+ --local-dir ./weight/PLMs/SaProt_650M_AF2
134
+ ```
135
+
136
+ It is recommended to download all model weights under `weight/PLMs/`. The current SaProt configuration reads from:
137
+
138
+ ```text
139
+ weight/PLMs/SaProt_650M_AF2
140
+ ```
141
+
142
+ To run the ESM2 comparison experiment, also prepare:
143
+
144
+ ```bash
145
+ huggingface-cli download \
146
+ facebook/esm2_t33_650M_UR50D \
147
+ --local-dir ./weight/PLMs/esm2_t33_650M_UR50D
148
+ ```
149
+
150
+ The corresponding configuration reads from:
151
+
152
+ ```text
153
+ weight/PLMs/esm2_t33_650M_UR50D
154
+ ```
155
+
156
+ #### 2) Foldseek
157
+
158
+ SaProt structure-aware inputs require PDB/CIF structures to be encoded as Foldseek 3Di sequences first. The official README provides the following download link:
159
+
160
+ ```text
161
+ https://drive.google.com/file/d/1B_9t3n_nlj8Y3Kpc_mMjtMdY0OPYa7Re/view
162
+ ```
163
+
164
+ You can also use the official Foldseek Linux prebuilt package or a Foldseek installation already available on the system or platform.
165
+
166
+ For this adapted version, place Foldseek at:
167
+
168
+ ```text
169
+ SaProt/
170
+ └── scripts/
171
+ └── bin/
172
+ └── foldseek
173
+ ```
174
+
175
+ Then grant it execute permission:
176
+
177
+ ```bash
178
+ chmod +x scripts/bin/foldseek
179
+ ```
180
+
181
+ The Foldseek path used by the current configuration is:
182
+
183
+ ```text
184
+ scripts/bin/foldseek
185
+ ```
186
+
187
+ #### 3) Downstream Task Datasets
188
+
189
+ The official downstream task datasets are available at:
190
+
191
+ ```text
192
+ https://drive.google.com/drive/folders/11dNGqPYfLE3M-Mbh4U7IQpuHxJpuRr4g?usp=sharing
193
+ ```
194
+
195
+ For this adapted version, extract the downstream task data to:
196
+
197
+ ```text
198
+ scripts/LMDB/
199
+ ```
200
+
201
+ Typical paths used by the configuration include:
202
+
203
+ ```text
204
+ scripts/LMDB/Thermostability/foldseek/train
205
+ scripts/LMDB/Thermostability/foldseek/valid
206
+ scripts/LMDB/Thermostability/foldseek/test
207
+ scripts/LMDB/ProteinGym/substitutions
208
+ scripts/LMDB/ClinVar
209
+ ```
210
+
211
+ #### 4) Pretraining Dataset
212
+
213
+ To pretrain SaProt from scratch or continue pretraining, prepare the official pretraining data:
214
+
215
+ ```text
216
+ westlake-repl/AF2_UniRef50
217
+ https://huggingface.co/datasets/westlake-repl/AF2_UniRef50
218
+ ```
219
+
220
+ The official pretraining configuration uses LMDB data directories such as:
221
+
222
+ ```text
223
+ scripts/LMDB/AF2_Uniref50/foldseek/train
224
+ scripts/LMDB/AF2_Uniref50/foldseek/valid
225
+ ```
226
+
227
+ The pretraining dataset is large and is only needed when pretraining from scratch or continuing pretraining.
228
+
229
+ ## 3. Quick Start
230
+
231
+ ### Download the Model Package
232
+
233
+ ```bash
234
+ hf download OneScience-Group/SaProt \
235
+ --local-dir ./SaProt
236
+
237
+ cd SaProt
238
+ ```
239
+
240
+ ### Quick Verification
241
+
242
+ Check the dependencies:
243
+
244
+ ```bash
245
+ python - <<'PY'
246
+ import torch
247
+ import transformers
248
+ import esm
249
+ import pytorch_lightning as pl
250
+
251
+ print("torch:", torch.__version__)
252
+ print("transformers:", transformers.__version__)
253
+ print("pytorch_lightning:", pl.__version__)
254
+ print("SaProt dependencies OK")
255
+ PY
256
+ ```
257
+
258
+ Check Foldseek:
259
+
260
+ ```bash
261
+ ./scripts/bin/foldseek version
262
+ ```
263
+
264
+ Test model loading:
265
+
266
+ ```bash
267
+ python - <<'PY'
268
+ from transformers import EsmTokenizer, EsmForMaskedLM
269
+
270
+ model_path = "./weight/PLMs/SaProt_650M_AF2"
271
+
272
+ tokenizer = EsmTokenizer.from_pretrained(model_path)
273
+ model = EsmForMaskedLM.from_pretrained(model_path)
274
+
275
+ print("SaProt load OK")
276
+ PY
277
+ ```
278
+
279
+ # Example Data
280
+
281
+ The official repository provides:
282
+
283
+ ```text
284
+ scripts/example/8ac8.cif
285
+ ```
286
+
287
+ This can be used to demonstrate conversion from a protein structure to a structure-aware sequence.
288
+
289
+ Your own structure input can be:
290
+
291
+ ```text
292
+ *.pdb
293
+ *.cif
294
+ ```
295
+
296
+ If you already have a Foldseek-encoded structure-aware sequence, you can pass it directly to SaProt without processing the structure file again.
297
+
298
+ # Inference Examples
299
+
300
+ ## Load SaProt for Forward Inference
301
+
302
+ ```bash
303
+ python - <<'PY'
304
+ import torch
305
+ from transformers import EsmTokenizer, EsmForMaskedLM
306
+
307
+ model_path = "weight/PLMs/SaProt_650M_AF2"
308
+ device = "cuda" if torch.cuda.is_available() else "cpu"
309
+
310
+ tokenizer = EsmTokenizer.from_pretrained(model_path)
311
+ model = EsmForMaskedLM.from_pretrained(model_path)
312
+ model.to(device)
313
+ model.eval()
314
+
315
+ seq = "M#EvVpQpL#VyQdYaKv"
316
+ tokens = tokenizer.tokenize(seq)
317
+ print(tokens)
318
+
319
+ inputs = tokenizer(seq, return_tensors="pt")
320
+ inputs = {k: v.to(device) for k, v in inputs.items()}
321
+
322
+ with torch.no_grad():
323
+ outputs = model(**inputs)
324
+
325
+ print(outputs.logits.shape)
326
+ PY
327
+ ```
328
+
329
+ ## Load SaProt with the ESM Interface
330
+
331
+ If the model directory contains `SaProt_650M_AF2.pt`, you can use the ESM loading function provided by the project:
332
+
333
+ ```bash
334
+ python - <<'PY'
335
+ from scripts.utils.esm_loader import load_esm_saprot
336
+
337
+ model_path = "weight/PLMs/SaProt_650M_AF2/SaProt_650M_AF2.pt"
338
+ model, alphabet = load_esm_saprot(model_path)
339
+
340
+ print("ESM SaProt load OK")
341
+ PY
342
+ ```
343
+
344
+ ## Convert a Structure File to a Structure-Aware Sequence
345
+
346
+ ```bash
347
+ python - <<'PY'
348
+ from scripts.utils.foldseek_util import get_struc_seq
349
+
350
+ pdb_path = "scripts/example/8ac8.cif"
351
+
352
+ parsed_seqs = get_struc_seq("scripts/bin/foldseek", pdb_path, ["A"], plddt_mask=False)["A"]
353
+ seq, foldseek_seq, combined_seq = parsed_seqs
354
+
355
+ print(f"seq: {seq}")
356
+ print(f"foldseek_seq: {foldseek_seq}")
357
+ print(f"combined_seq: {combined_seq}")
358
+ PY
359
+ ```
360
+
361
+ The `A` chain selection extracts only chain A from the structure file. The `combined_seq` in the returned result is a structure-aware sequence that can be used directly by SaProt.
362
+
363
+ ## Mutation Effect Prediction
364
+
365
+ ```bash
366
+ python - <<'PY'
367
+ import torch
368
+ from model.saprot.saprot_foldseek_mutation_model import SaprotFoldseekMutationModel
369
+
370
+ config = {
371
+ "foldseek_path": None,
372
+ "config_path": "weight/PLMs/SaProt_650M_AF2",
373
+ "load_pretrained": True,
374
+ }
375
+ model = SaprotFoldseekMutationModel(**config)
376
+ tokenizer = model.tokenizer
377
+
378
+ device = "cuda" if torch.cuda.is_available() else "cpu"
379
+ model.eval()
380
+ model.to(device)
381
+
382
+ seq = "M#EvVpQpL#VyQdYaKv"
383
+
384
+ mut_info = "V3A"
385
+ mut_value = model.predict_mut(seq, mut_info)
386
+ print(mut_value)
387
+
388
+ mut_info = "V3A:Q4M"
389
+ mut_value = model.predict_mut(seq, mut_info)
390
+ print(mut_value)
391
+
392
+ mut_pos = 3
393
+ mut_dict = model.predict_pos_mut(seq, mut_pos)
394
+ print(mut_dict)
395
+
396
+ mut_pos = 3
397
+ mut_dict = model.predict_pos_prob(seq, mut_pos)
398
+ print(mut_dict)
399
+ PY
400
+ ```
401
+
402
+ ## Extract Protein Embeddings
403
+
404
+ ```bash
405
+ python - <<'PY'
406
+ import torch
407
+ from model.saprot.base import SaprotBaseModel
408
+ from transformers import EsmTokenizer
409
+
410
+ config = {
411
+ "task": "base",
412
+ "config_path": "weight/PLMs/SaProt_650M_AF2",
413
+ "load_pretrained": True,
414
+ }
415
+
416
+ model = SaprotBaseModel(**config)
417
+ tokenizer = EsmTokenizer.from_pretrained(config["config_path"])
418
+
419
+ device = "cuda" if torch.cuda.is_available() else "cpu"
420
+ model.to(device)
421
+ model.eval()
422
+
423
+ seq = "M#EvVpQpL#VyQdYaKv"
424
+ tokens = tokenizer.tokenize(seq)
425
+ print(tokens)
426
+
427
+ inputs = tokenizer(seq, return_tensors="pt")
428
+ inputs = {k: v.to(device) for k, v in inputs.items()}
429
+
430
+ with torch.no_grad():
431
+ embeddings = model.get_hidden_states(inputs, reduction="mean")
432
+
433
+ print(embeddings[0].shape)
434
+ PY
435
+ ```
436
+
437
+ ## Protein Inverse Folding
438
+
439
+ Inverse folding requires additional inverse folding model weights:
440
+
441
+ ```text
442
+ https://huggingface.co/westlake-repl/SaProt_650M_AF2_inverse_folding
443
+ ```
444
+
445
+ After downloading, place them at:
446
+
447
+ ```text
448
+ weight/PLMs/SaProt_650M_AF2_inverse_folding
449
+ ```
450
+
451
+ Example:
452
+
453
+ ```bash
454
+ python - <<'PY'
455
+ import torch
456
+ from model.saprot.saprot_if_model import SaProtIFModel
457
+
458
+ config = {
459
+ "config_path": "weight/PLMs/SaProt_650M_AF2_inverse_folding",
460
+ "load_pretrained": True,
461
+ }
462
+
463
+ device = "cuda" if torch.cuda.is_available() else "cpu"
464
+ model = SaProtIFModel(**config)
465
+ model = model.to(device)
466
+
467
+ aa_seq = "##########"
468
+ struc_seq = "dddddddddd"
469
+
470
+ pred_aa_seq = model.predict(aa_seq, struc_seq)
471
+ print(pred_aa_seq)
472
+ PY
473
+ ```
474
+
475
+ # Training
476
+
477
+ This repository uses a unified training entry point:
478
+
479
+ ```bash
480
+ python scripts/training.py -c <config_path>
481
+ ```
482
+
483
+ Configuration files are located in `conf/`, model code is located in `model/`, and data processing and utility code is located in `scripts/`. The current configuration uses the pretrained model weights in `weight/PLMs/SaProt_650M_AF2` by default.
484
+
485
+ ## Pretraining
486
+
487
+ To pretrain SaProt from scratch or continue pretraining, first prepare the pretraining LMDB dataset, then run:
488
+
489
+ ```bash
490
+ python scripts/training.py -c conf/pretrain/saprot.yaml
491
+ ```
492
+
493
+ This configuration reads from the following paths by default:
494
+
495
+ ```text
496
+ scripts/LMDB/AF2_Uniref50/foldseek/train
497
+ scripts/LMDB/AF2_Uniref50/foldseek/valid
498
+ ```
499
+
500
+ ## Downstream Fine-Tuning
501
+
502
+ Use the following commands to fine-tune SaProt on downstream tasks:
503
+
504
+ ```bash
505
+ # Thermostability
506
+ python scripts/training.py -c conf/Thermostability/saprot.yaml
507
+
508
+ # EC
509
+ python scripts/training.py -c conf/EC/saprot.yaml
510
+
511
+ # GO
512
+ python scripts/training.py -c conf/GO/MF/saprot.yaml
513
+ python scripts/training.py -c conf/GO/BP/saprot.yaml
514
+ python scripts/training.py -c conf/GO/CC/saprot.yaml
515
+
516
+ # Metal ion binding
517
+ python scripts/training.py -c conf/MetalIonBinding/saprot.yaml
518
+
519
+ # Human PPI
520
+ python scripts/training.py -c conf/HumanPPI/saprot.yaml
521
+
522
+ # Contact prediction
523
+ python scripts/training.py -c conf/Contact/saprot.yaml
524
+
525
+ # DeepLoc
526
+ python scripts/training.py -c conf/DeepLoc/cls2/saprot.yaml
527
+ python scripts/training.py -c conf/DeepLoc/cls10/saprot.yaml
528
+ ```
529
+
530
+ For single-GPU or limited-memory environments, use `conf/scnet/Thermostability_saprot_1gpu.yaml` as a starting point:
531
+
532
+ ```bash
533
+ python scripts/training.py -c conf/scnet/Thermostability_saprot_1gpu.yaml
534
+ ```
535
+
536
+ ## Zero-Shot Mutation Effect Evaluation
537
+
538
+ ProteinGym evaluation:
539
+
540
+ ```bash
541
+ python scripts/mutation_zeroshot.py -c conf/ProteinGym/saprot.yaml
542
+ ```
543
+
544
+ The output file is saved by default to:
545
+
546
+ ```text
547
+ output/ProteinGym/SaProt_650M_AF2.tsv
548
+ ```
549
+
550
+ ClinVar evaluation:
551
+
552
+ ```bash
553
+ python scripts/mutation_zeroshot.py -c conf/ClinVar/saprot.yaml
554
+ python scripts/compute_clinvar_auc.py -c conf/ClinVar/saprot.yaml
555
+ ```
556
+
557
+ ClinVar prediction results are saved by default to:
558
+
559
+ ```text
560
+ output/ClinVar/SaProt_650M_AF2
561
+ ```
562
+
563
+ Single-GPU environments can also use the adapted configuration:
564
+
565
+ ```bash
566
+ python scripts/mutation_zeroshot.py -c conf/scnet/ClinVar_saprot.yaml
567
+ python scripts/compute_clinvar_auc.py -c conf/scnet/ClinVar_saprot.yaml
568
+ ```
569
+
570
+ ## ESM2 Comparison Experiment
571
+
572
+ To run the ESM2 baseline, additionally prepare the weights in `weight/PLMs/esm2_t33_650M_UR50D` and the corresponding normal LMDB data. Example commands:
573
+
574
+ ```bash
575
+ python scripts/training.py -c conf/Thermostability/esm2.yaml
576
+ python scripts/mutation_zeroshot.py -c conf/ProteinGym/esm2.yaml
577
+ ```
578
+
579
+ # Output Description
580
+
581
+ | Task | Main output |
582
+ | --- | --- |
583
+ | Structure encoding | AA sequence, 3Di sequence, and structure-aware sequence |
584
+ | Model forward pass | Token-level logits |
585
+ | Protein representation | Residue-level/protein-level embeddings |
586
+ | Mutation effect prediction | Mutation score |
587
+ | Zero-shot evaluation | ProteinGym Spearman results or ClinVar AUC results |
588
+ | Inverse folding | Protein sequences generated or evaluated under structural conditions |
589
+ | Downstream fine-tuning | Prediction results and model checkpoint for the corresponding task |
590
+
591
+ # Official OneScience Information
592
+
593
+ | Platform | Main OneScience repository | Skills repository |
594
+ | --- | --- | --- |
595
+ | Gitee | https://gitee.com/onescience-ai/onescience | https://gitee.com/onescience-ai/oneskills |
596
+ | GitHub | https://github.com/onescience-ai/OneScience | https://github.com/onescience-ai/oneskills |
597
+
598
+ # Citation and License
599
+
600
+ - The official SaProt source repository is released under the **MIT License**, which permits use, modification, distribution, sublicensing, and commercial use. When copying or distributing it, retain the original copyright notice and the MIT License text.
601
+
602
+ - SaProt model weights are released independently through Hugging Face. For commercial use, redistribution, or other purposes, check and comply with the license on each corresponding model page. The relevant pretraining and downstream datasets are also subject to the licenses and terms of use on their respective dataset pages.
603
+
604
+ - This repository is a **DCU-adapted version** of SaProt. Use of the repository code, model weights, and related data remains subject to the licenses and terms of use of their respective original projects.
605
+
conf/ClinVar/esm2.yaml ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ setting:
2
+ seed: 20000812
3
+ os_environ:
4
+ CUDA_VISIBLE_DEVICES: 1,2,3,4,5,6,7
5
+ MASTER_ADDR: 127.0.0.1
6
+ MASTER_PORT: 12316
7
+ WORLD_SIZE: 1
8
+ NODE_RANK: 0
9
+
10
+ out_path: ~
11
+ dataset_dir: scripts/LMDB/ClinVar
12
+
13
+ model:
14
+ model_py_path: saprot/saprot_mutation_model
15
+ load_prev_scheduler: True
16
+ kwargs:
17
+ config_path: weight/PLMs/esm2_t33_650M_UR50D
18
+ load_pretrained: True
19
+ log_clinvar: True
20
+ log_dir: output/ClinVar/esm2_t33_650M_UR50D
21
+
22
+ dataset:
23
+ dataset_py_path: mutation_zeroshot_dataset
24
+ dataloader_kwargs:
25
+ batch_size: 16
26
+ num_workers: 8
27
+ kwargs: {}
28
+
29
+ Trainer:
30
+ log_every_n_steps: 1
31
+ devices: 1
32
+ accelerator: gpu
33
+ num_nodes: 1
34
+ strategy:
35
+ find_unused_parameters: False
36
+ logger: False
conf/ClinVar/saprot.yaml ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ setting:
2
+ seed: 20000812
3
+ os_environ:
4
+ CUDA_VISIBLE_DEVICES: 5
5
+ MASTER_ADDR: 127.0.0.1
6
+ MASTER_PORT: 12315
7
+ WORLD_SIZE: 1
8
+ NODE_RANK: 0
9
+
10
+ out_path: ~
11
+ dataset_dir: scripts/LMDB/ClinVar
12
+
13
+ model:
14
+ model_py_path: saprot/saprot_foldseek_mutation_model
15
+ kwargs:
16
+ foldseek_path: scripts/bin/foldseek
17
+ config_path: weight/PLMs/SaProt_650M_AF2
18
+ load_pretrained: True
19
+ log_clinvar: True
20
+ log_dir: output/ClinVar/SaProt_650M_AF2
21
+ plddt_threshold: 70.
22
+
23
+ dataset:
24
+ dataset_py_path: mutation_zeroshot_dataset
25
+ dataloader_kwargs:
26
+ batch_size: 16
27
+ num_workers: 8
28
+ kwargs: {}
29
+
30
+ Trainer:
31
+ log_every_n_steps: 1
32
+ devices: 1
33
+ accelerator: gpu
34
+ num_nodes: 1
35
+ strategy:
36
+ find_unused_parameters: False
37
+ logger: False
conf/Contact/esm2.yaml ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ setting:
2
+ seed: 20000812
3
+ use_wandb: true
4
+ os_environ:
5
+ WANDB_API_KEY: ~
6
+ WANDB_RUN_ID: ~
7
+ CUDA_VISIBLE_DEVICES: 0,1,2,3,4,5,6,7
8
+ MASTER_ADDR: 127.0.0.1
9
+ MASTER_PORT: 12316
10
+ WORLD_SIZE: 1
11
+ NODE_RANK: 0
12
+ wandb_config:
13
+ project: TAPE_Contact
14
+ name: esm2_t33_650M_UR50D
15
+
16
+ model:
17
+ # Which model to use
18
+ model_py_path: saprot/saprot_contact_model
19
+ kwargs:
20
+ # Arguments to initialize the specific class
21
+ config_path: weight/PLMs/esm2_t33_650M_UR50D
22
+ load_pretrained: True
23
+
24
+ # Arguments to initialize the basic class AbstractModel
25
+ lr_scheduler_kwargs:
26
+ last_epoch: -1
27
+ init_lr: 1.0e-2
28
+ # Weather to use this scheduler or not
29
+ on_use: false
30
+
31
+ optimizer_kwargs:
32
+ betas: [0.9, 0.98]
33
+ weight_decay: 0.01
34
+
35
+ save_path: weight/Contact/esm2_t33_650M_UR50D.pt
36
+ load_prev_scheduler: false
37
+ save_weights_only: true
38
+
39
+
40
+ dataset:
41
+ # Arguments to initialize the basic class LMDBDataset
42
+ dataset_py_path: saprot/saprot_contact_dataset
43
+ dataloader_kwargs:
44
+ batch_size: 2
45
+ num_workers: 8
46
+ train_lmdb: scripts/LMDB/Contact/normal/train
47
+ valid_lmdb: scripts/LMDB/Contact/normal/valid
48
+ test_lmdb: scripts/LMDB/Contact/normal/test
49
+ # Arguments to initialize the specific class
50
+ kwargs:
51
+ tokenizer: weight/PLMs/esm2_t33_650M_UR50D
52
+
53
+ # Arguments to initialize Pytorch Lightning Trainer
54
+ Trainer:
55
+ max_epochs: 30
56
+ log_every_n_steps: 1
57
+ strategy:
58
+ find_unused_parameters: False
59
+ logger: False
60
+ enable_checkpointing: false
61
+ val_check_interval: 0.1
62
+ accelerator: gpu
63
+ devices: 1
64
+ num_nodes: 1
65
+ accumulate_grad_batches: 1
66
+ precision: 16
67
+ num_sanity_val_steps: 0
conf/Contact/saprot.yaml ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ setting:
2
+ seed: 20000812
3
+ os_environ:
4
+ WANDB_API_KEY: ~
5
+ WANDB_RUN_ID: ~
6
+ CUDA_VISIBLE_DEVICES: 0,1,2,3,4,5,6,7
7
+ MASTER_ADDR: 127.0.0.1
8
+ MASTER_PORT: 12316
9
+ WORLD_SIZE: 1
10
+ NODE_RANK: 0
11
+ wandb_config:
12
+ project: TAPE_Contact
13
+ name: SaProt_650M_AF2
14
+
15
+ model:
16
+ # Which model to use
17
+ model_py_path: saprot/saprot_contact_model
18
+ kwargs:
19
+ # Arguments to initialize the specific class
20
+ config_path: weight/PLMs/SaProt_650M_AF2
21
+ load_pretrained: True
22
+
23
+ # Arguments to initialize the basic class AbstractModel
24
+ lr_scheduler_kwargs:
25
+ last_epoch: -1
26
+ init_lr: 1.0e-2
27
+ # Weather to use this scheduler or not
28
+ on_use: false
29
+
30
+ optimizer_kwargs:
31
+ betas: [0.9, 0.98]
32
+ weight_decay: 0.01
33
+
34
+ save_path: weight/Contact/SaProt_650M_AF2.pt
35
+ load_prev_scheduler: false
36
+ save_weights_only: true
37
+
38
+
39
+ dataset:
40
+ # Arguments to initialize the basic class LMDBDataset
41
+ dataset_py_path: saprot/saprot_contact_dataset
42
+ dataloader_kwargs:
43
+ batch_size: 2
44
+ num_workers: 8
45
+ train_lmdb: scripts/LMDB/Contact/foldseek/train
46
+ valid_lmdb: scripts/LMDB/Contact/foldseek/valid
47
+ test_lmdb: scripts/LMDB/Contact/foldseek/test
48
+ # Arguments to initialize the specific class
49
+ kwargs:
50
+ tokenizer: weight/PLMs/SaProt_650M_AF2
51
+
52
+ # Arguments to initialize Pytorch Lightning Trainer
53
+ Trainer:
54
+ max_epochs: 30
55
+ log_every_n_steps: 1
56
+ strategy:
57
+ find_unused_parameters: False
58
+ logger: False
59
+ enable_checkpointing: false
60
+ val_check_interval: 0.1
61
+ accelerator: gpu
62
+ devices: 1
63
+ num_nodes: 1
64
+ accumulate_grad_batches: 1
65
+ precision: 16
66
+ num_sanity_val_steps: 0
conf/DeepLoc/cls10/esm2.yaml ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ setting:
2
+ seed: 20000812
3
+ os_environ:
4
+ WANDB_API_KEY: ~
5
+ WANDB_RUN_ID: ~
6
+ CUDA_VISIBLE_DEVICES: 0,1,2,3,4,5,6,7
7
+ MASTER_ADDR: localhost
8
+ MASTER_PORT: 12315
9
+ WORLD_SIZE: 1
10
+ NODE_RANK: 0
11
+ wandb_config:
12
+ project: DeepLoc_cls10
13
+ name: esm2_t33_650M_UR50D
14
+
15
+ model:
16
+ # Which model to use
17
+ model_py_path: saprot/saprot_classification_model
18
+ kwargs:
19
+ # Arguments to initialize the specific class
20
+ config_path: weight/PLMs/esm2_t33_650M_UR50D
21
+ load_pretrained: True
22
+ num_labels: 10
23
+
24
+ # Arguments to initialize the basic class AbstractModel
25
+ lr_scheduler_kwargs:
26
+ last_epoch: -1
27
+ init_lr: 2.0e-5
28
+ # Weather to use this scheduler or not
29
+ on_use: false
30
+
31
+ optimizer_kwargs:
32
+ betas: [0.9, 0.98]
33
+ weight_decay: 0.01
34
+
35
+ save_path: weight/DeepLoc/cls10/esm2_t33_650M_UR50D.pt
36
+
37
+
38
+ dataset:
39
+ # Arguments to initialize the basic class LMDBDataset
40
+ dataset_py_path: saprot/saprot_classification_dataset
41
+ dataloader_kwargs:
42
+ batch_size: 8
43
+ num_workers: 8
44
+
45
+ train_lmdb: scripts/LMDB/DeepLoc/cls10/normal/train
46
+ valid_lmdb: scripts/LMDB/DeepLoc/cls10/normal/valid
47
+ test_lmdb: scripts/LMDB/DeepLoc/cls10/normal/test
48
+ # Arguments to initialize the specific class
49
+ kwargs:
50
+ tokenizer: weight/PLMs/esm2_t33_650M_UR50D
51
+
52
+
53
+ # Arguments to initialize Pytorch Lightning Trainer
54
+ Trainer:
55
+ max_epochs: 100
56
+ log_every_n_steps: 1
57
+ strategy:
58
+ find_unused_parameters: True
59
+ logger: True
60
+ enable_checkpointing: false
61
+ val_check_interval: 0.5
62
+ accelerator: gpu
63
+ devices: 8
64
+ num_nodes: 1
65
+ accumulate_grad_batches: 1
66
+ precision: 16
67
+ num_sanity_val_steps: 0
conf/DeepLoc/cls10/saprot.yaml ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ setting:
2
+ seed: 20000812
3
+ os_environ:
4
+ WANDB_API_KEY: ~
5
+ WANDB_RUN_ID: ~
6
+ CUDA_VISIBLE_DEVICES: 0,1,2,3,4,5,6,7
7
+ MASTER_ADDR: localhost
8
+ MASTER_PORT: 12315
9
+ WORLD_SIZE: 1
10
+ NODE_RANK: 0
11
+ wandb_config:
12
+ project: DeepLoc_cls10
13
+ name: SaProt_650M_AF2
14
+
15
+ model:
16
+ # Which model to use
17
+ model_py_path: saprot/saprot_classification_model
18
+ kwargs:
19
+ # Arguments to initialize the specific class
20
+ config_path: weight/PLMs/SaProt_650M_AF2
21
+ load_pretrained: True
22
+ num_labels: 10
23
+
24
+ # Arguments to initialize the basic class AbstractModel
25
+ lr_scheduler_kwargs:
26
+ last_epoch: -1
27
+ init_lr: 2.0e-5
28
+ # Weather to use this scheduler or not
29
+ on_use: false
30
+
31
+ optimizer_kwargs:
32
+ betas: [0.9, 0.98]
33
+ weight_decay: 0.01
34
+
35
+ save_path: weight/DeepLoc/cls10/SaProt_650M_AF2.pt
36
+
37
+
38
+ dataset:
39
+ # Arguments to initialize the basic class LMDBDataset
40
+ dataset_py_path: saprot/saprot_classification_dataset
41
+ dataloader_kwargs:
42
+ batch_size: 8
43
+ num_workers: 8
44
+
45
+ train_lmdb: scripts/LMDB/DeepLoc/cls10/foldseek/train
46
+ valid_lmdb: scripts/LMDB/DeepLoc/cls10/foldseek/valid
47
+ test_lmdb: scripts/LMDB/DeepLoc/cls10/foldseek/test
48
+ # Arguments to initialize the specific class
49
+ kwargs:
50
+ tokenizer: weight/PLMs/SaProt_650M_AF2
51
+ plddt_threshold: 70.
52
+
53
+
54
+ # Arguments to initialize Pytorch Lightning Trainer
55
+ Trainer:
56
+ max_epochs: 100
57
+ log_every_n_steps: 1
58
+ strategy:
59
+ find_unused_parameters: True
60
+ logger: True
61
+ enable_checkpointing: false
62
+ val_check_interval: 0.5
63
+ accelerator: gpu
64
+ devices: 8
65
+ num_nodes: 1
66
+ accumulate_grad_batches: 1
67
+ precision: 16
68
+ num_sanity_val_steps: 0
conf/DeepLoc/cls2/esm2.yaml ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ setting:
2
+ seed: 20000812
3
+ os_environ:
4
+ WANDB_API_KEY: ~
5
+ WANDB_RUN_ID: ~
6
+ CUDA_VISIBLE_DEVICES: 0,1,2,3,4,5,6,7
7
+ MASTER_ADDR: localhost
8
+ MASTER_PORT: 12315
9
+ WORLD_SIZE: 1
10
+ NODE_RANK: 0
11
+ wandb_config:
12
+ project: DeepLoc_cls2
13
+ name: esm2_t33_650M_UR50D
14
+
15
+ model:
16
+ # Which model to use
17
+ model_py_path: saprot/saprot_classification_model
18
+ kwargs:
19
+ # Arguments to initialize the specific class
20
+ config_path: weight/PLMs/esm2_t33_650M_UR50D
21
+ load_pretrained: True
22
+ num_labels: 2
23
+
24
+ # Arguments to initialize the basic class AbstractModel
25
+ lr_scheduler_kwargs:
26
+ last_epoch: -1
27
+ init_lr: 2.0e-5
28
+ # Weather to use this scheduler or not
29
+ on_use: false
30
+
31
+ optimizer_kwargs:
32
+ betas: [0.9, 0.98]
33
+ weight_decay: 0.01
34
+
35
+ save_path: weight/DeepLoc/cls2/esm2_t33_650M_UR50D.pt
36
+
37
+
38
+ dataset:
39
+ # Arguments to initialize the basic class LMDBDataset
40
+ dataset_py_path: saprot/saprot_classification_dataset
41
+ dataloader_kwargs:
42
+ batch_size: 8
43
+ num_workers: 8
44
+
45
+ train_lmdb: scripts/LMDB/DeepLoc/cls2/normal/train
46
+ valid_lmdb: scripts/LMDB/DeepLoc/cls2/normal/valid
47
+ test_lmdb: scripts/LMDB/DeepLoc/cls2/normal/test
48
+ # Arguments to initialize the specific class
49
+ kwargs:
50
+ tokenizer: weight/PLMs/esm2_t33_650M_UR50D
51
+
52
+
53
+ # Arguments to initialize Pytorch Lightning Trainer
54
+ Trainer:
55
+ max_epochs: 100
56
+ log_every_n_steps: 1
57
+ strategy:
58
+ find_unused_parameters: True
59
+ logger: True
60
+ enable_checkpointing: false
61
+ val_check_interval: 0.5
62
+ accelerator: gpu
63
+ devices: 8
64
+ num_nodes: 1
65
+ accumulate_grad_batches: 1
66
+ precision: 16
67
+ num_sanity_val_steps: 0
conf/DeepLoc/cls2/saprot.yaml ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ setting:
2
+ seed: 20000812
3
+ os_environ:
4
+ WANDB_API_KEY: ~
5
+ WANDB_RUN_ID: ~
6
+ CUDA_VISIBLE_DEVICES: 0,1,2,3,4,5,6,7
7
+ MASTER_ADDR: localhost
8
+ MASTER_PORT: 12315
9
+ WORLD_SIZE: 1
10
+ NODE_RANK: 0
11
+ wandb_config:
12
+ project: DeepLoc_cls2
13
+ name: SaProt_650M_AF2
14
+
15
+ model:
16
+ # Which model to use
17
+ model_py_path: saprot/saprot_classification_model
18
+ kwargs:
19
+ # Arguments to initialize the specific class
20
+ config_path: weight/PLMs/SaProt_650M_AF2
21
+ load_pretrained: True
22
+ num_labels: 2
23
+
24
+ # Arguments to initialize the basic class AbstractModel
25
+ lr_scheduler_kwargs:
26
+ last_epoch: -1
27
+ init_lr: 2.0e-5
28
+ # Weather to use this scheduler or not
29
+ on_use: false
30
+
31
+ optimizer_kwargs:
32
+ betas: [0.9, 0.98]
33
+ weight_decay: 0.01
34
+
35
+ save_path: weight/DeepLoc/cls2/SaProt_650M_AF2.pt
36
+
37
+
38
+ dataset:
39
+ # Arguments to initialize the basic class LMDBDataset
40
+ dataset_py_path: saprot/saprot_classification_dataset
41
+ dataloader_kwargs:
42
+ batch_size: 8
43
+ num_workers: 8
44
+
45
+ train_lmdb: scripts/LMDB/DeepLoc/cls2/foldseek/train
46
+ valid_lmdb: scripts/LMDB/DeepLoc/cls2/foldseek/valid
47
+ test_lmdb: scripts/LMDB/DeepLoc/cls2/foldseek/test
48
+ # Arguments to initialize the specific class
49
+ kwargs:
50
+ tokenizer: weight/PLMs/SaProt_650M_AF2
51
+ plddt_threshold: 70
52
+
53
+
54
+ # Arguments to initialize Pytorch Lightning Trainer
55
+ Trainer:
56
+ max_epochs: 100
57
+ log_every_n_steps: 1
58
+ strategy:
59
+ find_unused_parameters: True
60
+ logger: True
61
+ enable_checkpointing: false
62
+ val_check_interval: 0.5
63
+ accelerator: gpu
64
+ devices: 8
65
+ num_nodes: 1
66
+ accumulate_grad_batches: 1
67
+ precision: 16
68
+ num_sanity_val_steps: 0
conf/EC/esm2.yaml ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ setting:
2
+ seed: 20000812
3
+ os_environ:
4
+ WANDB_API_KEY: ~
5
+ WANDB_RUN_ID: ~
6
+ CUDA_VISIBLE_DEVICES: 0,1,2,3,4,5,6,7
7
+ MASTER_ADDR: localhost
8
+ MASTER_PORT: 12315
9
+ WORLD_SIZE: 1
10
+ NODE_RANK: 0
11
+ wandb_config:
12
+ project: EC
13
+ name: esm2_t33_650M_UR50D
14
+
15
+ model:
16
+ # Which model to use
17
+ model_py_path: saprot/saprot_annotation_model
18
+ kwargs:
19
+ # Arguments to initialize the specific class
20
+ config_path: weight/PLMs/esm2_t33_650M_UR50D
21
+ load_pretrained: True
22
+ anno_type: EC
23
+
24
+ # Arguments to initialize the basic class AbstractModel
25
+ lr_scheduler_kwargs:
26
+ last_epoch: -1
27
+ init_lr: 2.0e-5
28
+ # Weather to use this scheduler or not
29
+ on_use: false
30
+
31
+ optimizer_kwargs:
32
+ betas: [0.9, 0.98]
33
+ weight_decay: 0.01
34
+
35
+ save_path: weight/EC/esm2_t33_650M_UR50D.pt
36
+
37
+
38
+ dataset:
39
+ # Arguments to initialize the basic class LMDBDataset
40
+ dataset_py_path: saprot/saprot_annotation_dataset
41
+ dataloader_kwargs:
42
+ batch_size: 8
43
+ num_workers: 8
44
+
45
+ train_lmdb: scripts/LMDB/EC/AF2/normal/train
46
+ valid_lmdb: scripts/LMDB/EC/AF2/normal/valid
47
+ test_lmdb: scripts/LMDB/EC/AF2/normal/test
48
+ # Arguments to initialize the specific class
49
+ kwargs:
50
+ tokenizer: weight/PLMs/esm2_t33_650M_UR50D
51
+
52
+
53
+ # Arguments to initialize Pytorch Lightning Trainer
54
+ Trainer:
55
+ max_epochs: 100
56
+ log_every_n_steps: 1
57
+ strategy:
58
+ find_unused_parameters: True
59
+ logger: True
60
+ enable_checkpointing: false
61
+ val_check_interval: 0.1
62
+ accelerator: gpu
63
+ devices: 8
64
+ num_nodes: 1
65
+ accumulate_grad_batches: 1
66
+ precision: 16
67
+ num_sanity_val_steps: 0
conf/EC/saprot.yaml ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ setting:
2
+ seed: 20000812
3
+ os_environ:
4
+ WANDB_API_KEY: ~
5
+ WANDB_RUN_ID: ~
6
+ CUDA_VISIBLE_DEVICES: 0,1,2,3,4,5,6,7
7
+ MASTER_ADDR: localhost
8
+ MASTER_PORT: 12315
9
+ WORLD_SIZE: 1
10
+ NODE_RANK: 0
11
+ wandb_config:
12
+ project: EC
13
+ name: SaProt_650M_AF2
14
+
15
+ model:
16
+ # Which model to use
17
+ model_py_path: saprot/saprot_annotation_model
18
+ kwargs:
19
+ # Arguments to initialize the specific class
20
+ config_path: weight/PLMs/SaProt_650M_AF2
21
+ load_pretrained: True
22
+ anno_type: EC
23
+
24
+ # Arguments to initialize the basic class AbstractModel
25
+ lr_scheduler_kwargs:
26
+ last_epoch: -1
27
+ init_lr: 2.0e-5
28
+ # Weather to use this scheduler or not
29
+ on_use: false
30
+
31
+ optimizer_kwargs:
32
+ betas: [0.9, 0.98]
33
+ weight_decay: 0.01
34
+
35
+ save_path: weight/EC/SaProt_650M_AF2.pt
36
+
37
+
38
+ dataset:
39
+ # Arguments to initialize the basic class LMDBDataset
40
+ dataset_py_path: saprot/saprot_annotation_dataset
41
+ dataloader_kwargs:
42
+ batch_size: 8
43
+ num_workers: 8
44
+
45
+ train_lmdb: scripts/LMDB/EC/AF2/foldseek/train
46
+ valid_lmdb: scripts/LMDB/EC/AF2/foldseek/valid
47
+ test_lmdb: scripts/LMDB/EC/AF2/foldseek/test
48
+ # Arguments to initialize the specific class
49
+ kwargs:
50
+ tokenizer: weight/PLMs/SaProt_650M_AF2
51
+ plddt_threshold: 70
52
+
53
+
54
+ # Arguments to initialize Pytorch Lightning Trainer
55
+ Trainer:
56
+ max_epochs: 100
57
+ log_every_n_steps: 1
58
+ strategy:
59
+ find_unused_parameters: True
60
+ logger: True
61
+ enable_checkpointing: false
62
+ val_check_interval: 0.1
63
+ accelerator: gpu
64
+ devices: 8
65
+ num_nodes: 1
66
+ accumulate_grad_batches: 1
67
+ precision: 16
68
+ num_sanity_val_steps: 0
conf/GO/BP/esm2.yaml ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ setting:
2
+ seed: 20000812
3
+ os_environ:
4
+ WANDB_API_KEY: ~
5
+ WANDB_RUN_ID: ~
6
+ CUDA_VISIBLE_DEVICES: 0,1,2,3,4,5,6,7
7
+ MASTER_ADDR: localhost
8
+ MASTER_PORT: 12315
9
+ WORLD_SIZE: 1
10
+ NODE_RANK: 0
11
+ wandb_config:
12
+ project: GO_BP
13
+ name: esm2_t33_650M_UR50D
14
+
15
+ model:
16
+ # Which model to use
17
+ model_py_path: saprot/saprot_annotation_model
18
+ kwargs:
19
+ # Arguments to initialize the specific class
20
+ config_path: weight/PLMs/esm2_t33_650M_UR50D
21
+ load_pretrained: True
22
+ anno_type: GO_BP
23
+
24
+ # Arguments to initialize the basic class AbstractModel
25
+ lr_scheduler_kwargs:
26
+ last_epoch: -1
27
+ init_lr: 2.0e-5
28
+ # Weather to use this scheduler or not
29
+ on_use: false
30
+
31
+ optimizer_kwargs:
32
+ betas: [0.9, 0.98]
33
+ weight_decay: 0.01
34
+
35
+ save_path: weight/GO/BP/esm2_t33_650M_UR50D.pt
36
+
37
+
38
+ dataset:
39
+ # Arguments to initialize the basic class LMDBDataset
40
+ dataset_py_path: saprot/saprot_annotation_dataset
41
+ dataloader_kwargs:
42
+ batch_size: 4
43
+ num_workers: 8
44
+
45
+ train_lmdb: scripts/LMDB/GO/AF2/BP/normal/train
46
+ valid_lmdb: scripts/LMDB/GO/AF2/BP/normal/valid
47
+ test_lmdb: scripts/LMDB/GO/AF2/BP/normal/test
48
+ # Arguments to initialize the specific class
49
+ kwargs:
50
+ tokenizer: weight/PLMs/esm2_t33_650M_UR50D
51
+
52
+
53
+ # Arguments to initialize Pytorch Lightning Trainer
54
+ Trainer:
55
+ max_epochs: 100
56
+ log_every_n_steps: 1
57
+ strategy:
58
+ find_unused_parameters: True
59
+ logger: True
60
+ enable_checkpointing: false
61
+ val_check_interval: 0.5
62
+ accelerator: gpu
63
+ devices: 8
64
+ num_nodes: 1
65
+ accumulate_grad_batches: 2
66
+ precision: 16
67
+ num_sanity_val_steps: 0
conf/GO/BP/saprot.yaml ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ setting:
2
+ seed: 20000812
3
+ os_environ:
4
+ WANDB_API_KEY: ~
5
+ WANDB_RUN_ID: ~
6
+ CUDA_VISIBLE_DEVICES: 0,1,2,3,4,5,6,7
7
+ MASTER_ADDR: localhost
8
+ MASTER_PORT: 12315
9
+ WORLD_SIZE: 1
10
+ NODE_RANK: 0
11
+ wandb_config:
12
+ project: GO_BP
13
+ name: SaProt_650M_AF2
14
+
15
+ model:
16
+ # Which model to use
17
+ model_py_path: saprot/saprot_annotation_model
18
+ kwargs:
19
+ # Arguments to initialize the specific class
20
+ config_path: weight/PLMs/SaProt_650M_AF2
21
+ load_pretrained: True
22
+ anno_type: GO_BP
23
+
24
+ # Arguments to initialize the basic class AbstractModel
25
+ lr_scheduler_kwargs:
26
+ last_epoch: -1
27
+ init_lr: 2.0e-5
28
+ # Weather to use this scheduler or not
29
+ on_use: false
30
+
31
+ optimizer_kwargs:
32
+ betas: [0.9, 0.98]
33
+ weight_decay: 0.01
34
+
35
+ save_path: weight/GO/BP/SaProt_650M_AF2.pt
36
+
37
+
38
+ dataset:
39
+ # Arguments to initialize the basic class LMDBDataset
40
+ dataset_py_path: saprot/saprot_annotation_dataset
41
+ dataloader_kwargs:
42
+ batch_size: 4
43
+ num_workers: 8
44
+
45
+ train_lmdb: scripts/LMDB/GO/AF2/BP/foldseek/train
46
+ valid_lmdb: scripts/LMDB/GO/AF2/BP/foldseek/valid
47
+ test_lmdb: scripts/LMDB/GO/AF2/BP/foldseek/test
48
+ # Arguments to initialize the specific class
49
+ kwargs:
50
+ tokenizer: weight/PLMs/SaProt_650M_AF2
51
+ plddt_threshold: 70
52
+
53
+
54
+ # Arguments to initialize Pytorch Lightning Trainer
55
+ Trainer:
56
+ max_epochs: 100
57
+ log_every_n_steps: 1
58
+ strategy:
59
+ find_unused_parameters: True
60
+ logger: True
61
+ enable_checkpointing: false
62
+ val_check_interval: 0.5
63
+ accelerator: gpu
64
+ devices: 8
65
+ num_nodes: 1
66
+ accumulate_grad_batches: 2
67
+ precision: 16
68
+ num_sanity_val_steps: 0
conf/GO/CC/esm2.yaml ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ setting:
2
+ seed: 20000812
3
+ os_environ:
4
+ WANDB_API_KEY: ~
5
+ WANDB_RUN_ID: ~
6
+ CUDA_VISIBLE_DEVICES: 0,1,2,3,4,5,6,7
7
+ MASTER_ADDR: localhost
8
+ MASTER_PORT: 12315
9
+ WORLD_SIZE: 1
10
+ NODE_RANK: 0
11
+ wandb_config:
12
+ project: GO_CC
13
+ name: esm2_t33_650M_UR50D
14
+
15
+ model:
16
+ # Which model to use
17
+ model_py_path: saprot/saprot_annotation_model
18
+ kwargs:
19
+ # Arguments to initialize the specific class
20
+ config_path: weight/PLMs/esm2_t33_650M_UR50D
21
+ load_pretrained: True
22
+ anno_type: GO_CC
23
+
24
+ # Arguments to initialize the basic class AbstractModel
25
+ lr_scheduler_kwargs:
26
+ last_epoch: -1
27
+ init_lr: 2.0e-5
28
+ # Weather to use this scheduler or not
29
+ on_use: false
30
+
31
+ optimizer_kwargs:
32
+ betas: [0.9, 0.98]
33
+ weight_decay: 0.01
34
+
35
+ save_path: weight/GO/CC/esm2_t33_650M_UR50D.pt
36
+
37
+
38
+ dataset:
39
+ # Arguments to initialize the basic class LMDBDataset
40
+ dataset_py_path: saprot/saprot_annotation_dataset
41
+ dataloader_kwargs:
42
+ batch_size: 4
43
+ num_workers: 8
44
+
45
+ train_lmdb: scripts/LMDB/GO/AF2/CC/normal/train
46
+ valid_lmdb: scripts/LMDB/GO/AF2/CC/normal/valid
47
+ test_lmdb: scripts/LMDB/GO/AF2/CC/normal/test
48
+ # Arguments to initialize the specific class
49
+ kwargs:
50
+ tokenizer: weight/PLMs/esm2_t33_650M_UR50D
51
+
52
+
53
+ # Arguments to initialize Pytorch Lightning Trainer
54
+ Trainer:
55
+ max_epochs: 100
56
+ log_every_n_steps: 1
57
+ strategy:
58
+ find_unused_parameters: True
59
+ logger: True
60
+ enable_checkpointing: false
61
+ val_check_interval: 0.5
62
+ accelerator: gpu
63
+ devices: 8
64
+ num_nodes: 1
65
+ accumulate_grad_batches: 2
66
+ precision: 16
67
+ num_sanity_val_steps: 0
conf/GO/CC/saprot.yaml ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ setting:
2
+ seed: 20000812
3
+ os_environ:
4
+ WANDB_API_KEY: ~
5
+ WANDB_RUN_ID: ~
6
+ CUDA_VISIBLE_DEVICES: 0,1,2,3,4,5,6,7
7
+ MASTER_ADDR: localhost
8
+ MASTER_PORT: 12315
9
+ WORLD_SIZE: 1
10
+ NODE_RANK: 0
11
+ wandb_config:
12
+ project: GO_CC
13
+ name: SaProt_650M_AF2
14
+
15
+ model:
16
+ # Which model to use
17
+ model_py_path: saprot/saprot_annotation_model
18
+ kwargs:
19
+ # Arguments to initialize the specific class
20
+ config_path: weight/PLMs/SaProt_650M_AF2
21
+ load_pretrained: True
22
+ anno_type: GO_CC
23
+
24
+ # Arguments to initialize the basic class AbstractModel
25
+ lr_scheduler_kwargs:
26
+ last_epoch: -1
27
+ init_lr: 2.0e-5
28
+ # Weather to use this scheduler or not
29
+ on_use: false
30
+
31
+ optimizer_kwargs:
32
+ betas: [0.9, 0.98]
33
+ weight_decay: 0.01
34
+
35
+ save_path: weight/GO/CC/SaProt_650M_AF2.pt
36
+
37
+
38
+ dataset:
39
+ # Arguments to initialize the basic class LMDBDataset
40
+ dataset_py_path: saprot/saprot_annotation_dataset
41
+ dataloader_kwargs:
42
+ batch_size: 4
43
+ num_workers: 8
44
+
45
+ train_lmdb: scripts/LMDB/GO/AF2/CC/foldseek/train
46
+ valid_lmdb: scripts/LMDB/GO/AF2/CC/foldseek/valid
47
+ test_lmdb: scripts/LMDB/GO/AF2/CC/foldseek/test
48
+ # Arguments to initialize the specific class
49
+ kwargs:
50
+ tokenizer: weight/PLMs/SaProt_650M_AF2
51
+ plddt_threshold: 70
52
+
53
+
54
+ # Arguments to initialize Pytorch Lightning Trainer
55
+ Trainer:
56
+ max_epochs: 100
57
+ log_every_n_steps: 1
58
+ strategy:
59
+ find_unused_parameters: True
60
+ logger: True
61
+ enable_checkpointing: false
62
+ val_check_interval: 0.5
63
+ accelerator: gpu
64
+ devices: 8
65
+ num_nodes: 1
66
+ accumulate_grad_batches: 2
67
+ precision: 16
68
+ num_sanity_val_steps: 0
conf/GO/MF/esm2.yaml ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ setting:
2
+ seed: 20000812
3
+ os_environ:
4
+ WANDB_API_KEY: ~
5
+ WANDB_RUN_ID: ~
6
+ CUDA_VISIBLE_DEVICES: 0,1,2,3,4,5,6,7
7
+ MASTER_ADDR: localhost
8
+ MASTER_PORT: 12315
9
+ WORLD_SIZE: 1
10
+ NODE_RANK: 0
11
+ wandb_config:
12
+ project: GO_MF
13
+ name: esm2_t33_650M_UR50D
14
+
15
+ model:
16
+ # Which model to use
17
+ model_py_path: saprot/saprot_annotation_model
18
+ kwargs:
19
+ # Arguments to initialize the specific class
20
+ config_path: weight/PLMs/esm2_t33_650M_UR50D
21
+ load_pretrained: True
22
+ anno_type: GO_MF
23
+
24
+ # Arguments to initialize the basic class AbstractModel
25
+ lr_scheduler_kwargs:
26
+ last_epoch: -1
27
+ init_lr: 2.0e-5
28
+ # Weather to use this scheduler or not
29
+ on_use: false
30
+
31
+ optimizer_kwargs:
32
+ betas: [0.9, 0.98]
33
+ weight_decay: 0.01
34
+
35
+ save_path: weight/GO/MF/esm2_t33_650M_UR50D.pt
36
+
37
+
38
+ dataset:
39
+ # Arguments to initialize the basic class LMDBDataset
40
+ dataset_py_path: saprot/saprot_annotation_dataset
41
+ dataloader_kwargs:
42
+ batch_size: 4
43
+ num_workers: 8
44
+
45
+ train_lmdb: scripts/LMDB/GO/AF2/MF/normal/train
46
+ valid_lmdb: scripts/LMDB/GO/AF2/MF/normal/valid
47
+ test_lmdb: scripts/LMDB/GO/AF2/MF/normal/test
48
+ # Arguments to initialize the specific class
49
+ kwargs:
50
+ tokenizer: weight/PLMs/esm2_t33_650M_UR50D
51
+
52
+
53
+ # Arguments to initialize Pytorch Lightning Trainer
54
+ Trainer:
55
+ max_epochs: 100
56
+ log_every_n_steps: 1
57
+ strategy:
58
+ find_unused_parameters: True
59
+ logger: True
60
+ enable_checkpointing: false
61
+ val_check_interval: 0.5
62
+ accelerator: gpu
63
+ devices: 8
64
+ num_nodes: 1
65
+ accumulate_grad_batches: 2
66
+ precision: 16
67
+ num_sanity_val_steps: 0
conf/GO/MF/saprot.yaml ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ setting:
2
+ seed: 20000812
3
+ os_environ:
4
+ WANDB_API_KEY: ~
5
+ WANDB_RUN_ID: ~
6
+ CUDA_VISIBLE_DEVICES: 0,1,2,3,4,5,6,7
7
+ MASTER_ADDR: localhost
8
+ MASTER_PORT: 12315
9
+ WORLD_SIZE: 1
10
+ NODE_RANK: 0
11
+ wandb_config:
12
+ project: GO_MF
13
+ name: SaProt_650M_AF2
14
+
15
+ model:
16
+ # Which model to use
17
+ model_py_path: saprot/saprot_annotation_model
18
+ kwargs:
19
+ # Arguments to initialize the specific class
20
+ config_path: weight/PLMs/SaProt_650M_AF2
21
+ load_pretrained: True
22
+ anno_type: GO_MF
23
+
24
+ # Arguments to initialize the basic class AbstractModel
25
+ lr_scheduler_kwargs:
26
+ last_epoch: -1
27
+ init_lr: 2.0e-5
28
+ # Weather to use this scheduler or not
29
+ on_use: false
30
+
31
+ optimizer_kwargs:
32
+ betas: [0.9, 0.98]
33
+ weight_decay: 0.01
34
+
35
+ save_path: weight/GO/MF/SaProt_650M_AF2.pt
36
+
37
+
38
+ dataset:
39
+ # Arguments to initialize the basic class LMDBDataset
40
+ dataset_py_path: saprot/saprot_annotation_dataset
41
+ dataloader_kwargs:
42
+ batch_size: 4
43
+ num_workers: 8
44
+
45
+ train_lmdb: scripts/LMDB/GO/AF2/MF/foldseek/train
46
+ valid_lmdb: scripts/LMDB/GO/AF2/MF/foldseek/valid
47
+ test_lmdb: scripts/LMDB/GO/AF2/MF/foldseek/test
48
+ # Arguments to initialize the specific class
49
+ kwargs:
50
+ tokenizer: weight/PLMs/SaProt_650M_AF2
51
+ plddt_threshold: 70
52
+
53
+
54
+ # Arguments to initialize Pytorch Lightning Trainer
55
+ Trainer:
56
+ max_epochs: 100
57
+ log_every_n_steps: 1
58
+ strategy:
59
+ find_unused_parameters: True
60
+ logger: True
61
+ enable_checkpointing: false
62
+ val_check_interval: 0.5
63
+ accelerator: gpu
64
+ devices: 8
65
+ num_nodes: 1
66
+ accumulate_grad_batches: 2
67
+ precision: 16
68
+ num_sanity_val_steps: 0
conf/HumanPPI/esm2.yaml ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ setting:
2
+ seed: 20000812
3
+ os_environ:
4
+ WANDB_API_KEY: ~
5
+ WANDB_RUN_ID: ~
6
+ CUDA_VISIBLE_DEVICES: 0,1,2,3,4,5,6,7
7
+ MASTER_ADDR: localhost
8
+ MASTER_PORT: 12315
9
+ WORLD_SIZE: 1
10
+ NODE_RANK: 0
11
+ wandb_config:
12
+ project: HumanPPI
13
+ name: esm2_t33_650M_UR50D
14
+
15
+ model:
16
+ # Which model to use
17
+ model_py_path: saprot/saprot_ppi_model
18
+ kwargs:
19
+ # Arguments to initialize the specific class
20
+ config_path: weight/PLMs/esm2_t33_650M_UR50D
21
+ load_pretrained: True
22
+
23
+ # Arguments to initialize the basic class AbstractModel
24
+ lr_scheduler_kwargs:
25
+ last_epoch: -1
26
+ init_lr: 2.0e-5
27
+ # Weather to use this scheduler or not
28
+ on_use: false
29
+
30
+ optimizer_kwargs:
31
+ betas: [0.9, 0.98]
32
+ weight_decay: 0.01
33
+
34
+ save_path: weight/HumanPPI/esm2_t33_650M_UR50D.pt
35
+
36
+
37
+ dataset:
38
+ # Arguments to initialize the basic class LMDBDataset
39
+ dataset_py_path: saprot/saprot_ppi_dataset
40
+ dataloader_kwargs:
41
+ batch_size: 4
42
+ num_workers: 8
43
+
44
+ train_lmdb: scripts/LMDB/HumanPPI/normal/train
45
+ valid_lmdb: scripts/LMDB/HumanPPI/normal/valid
46
+ test_lmdb: scripts/LMDB/HumanPPI/normal/test
47
+ # Arguments to initialize the specific class
48
+ kwargs:
49
+ tokenizer: weight/PLMs/esm2_t33_650M_UR50D
50
+
51
+
52
+ # Arguments to initialize Pytorch Lightning Trainer
53
+ Trainer:
54
+ max_epochs: 20
55
+ log_every_n_steps: 1
56
+ strategy:
57
+ find_unused_parameters: True
58
+ logger: True
59
+ enable_checkpointing: false
60
+ val_check_interval: 0.1
61
+ accelerator: gpu
62
+ devices: 8
63
+ num_nodes: 1
64
+ accumulate_grad_batches: 2
65
+ precision: 16
66
+ num_sanity_val_steps: 0
conf/HumanPPI/saprot.yaml ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ setting:
2
+ seed: 20000812
3
+ os_environ:
4
+ WANDB_API_KEY: ~
5
+ WANDB_RUN_ID: ~
6
+ CUDA_VISIBLE_DEVICES: 0,1,2,3,4,5,6,7
7
+ MASTER_ADDR: localhost
8
+ MASTER_PORT: 12315
9
+ WORLD_SIZE: 1
10
+ NODE_RANK: 0
11
+ wandb_config:
12
+ project: HumanPPI
13
+ name: SaProt_650M_AF2
14
+
15
+ model:
16
+ # Which model to use
17
+ model_py_path: saprot/saprot_ppi_model
18
+ kwargs:
19
+ # Arguments to initialize the specific class
20
+ config_path: weight/PLMs/SaProt_650M_AF2
21
+ load_pretrained: True
22
+
23
+ # Arguments to initialize the basic class AbstractModel
24
+ lr_scheduler_kwargs:
25
+ last_epoch: -1
26
+ init_lr: 2.0e-5
27
+ # Weather to use this scheduler or not
28
+ on_use: false
29
+
30
+ optimizer_kwargs:
31
+ betas: [0.9, 0.98]
32
+ weight_decay: 0.01
33
+
34
+ save_path: weight/HumanPPI/SaProt_650M_AF2.pt
35
+
36
+
37
+ dataset:
38
+ # Arguments to initialize the basic class LMDBDataset
39
+ dataset_py_path: saprot/saprot_ppi_dataset
40
+ dataloader_kwargs:
41
+ batch_size: 4
42
+ num_workers: 8
43
+
44
+ train_lmdb: scripts/LMDB/HumanPPI/foldseek/train
45
+ valid_lmdb: scripts/LMDB/HumanPPI/foldseek/valid
46
+ test_lmdb: scripts/LMDB/HumanPPI/foldseek/test
47
+ # Arguments to initialize the specific class
48
+ kwargs:
49
+ tokenizer: weight/PLMs/SaProt_650M_AF2
50
+ plddt_threshold: 70
51
+
52
+
53
+ # Arguments to initialize Pytorch Lightning Trainer
54
+ Trainer:
55
+ max_epochs: 20
56
+ log_every_n_steps: 1
57
+ strategy:
58
+ find_unused_parameters: True
59
+ logger: True
60
+ enable_checkpointing: false
61
+ val_check_interval: 0.1
62
+ accelerator: gpu
63
+ devices: 8
64
+ num_nodes: 1
65
+ accumulate_grad_batches: 2
66
+ precision: 16
67
+ num_sanity_val_steps: 0
conf/MetalIonBinding/esm2.yaml ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ setting:
2
+ seed: 20000812
3
+ os_environ:
4
+ WANDB_API_KEY: ~
5
+ WANDB_RUN_ID: ~
6
+ CUDA_VISIBLE_DEVICES: 0,1,2,3,4,5,6,7
7
+ MASTER_ADDR: localhost
8
+ MASTER_PORT: 12315
9
+ WORLD_SIZE: 1
10
+ NODE_RANK: 0
11
+ wandb_config:
12
+ project: MetalIonBinding
13
+ name: esm2_t33_650M_UR50D
14
+
15
+ model:
16
+ # Which model to use
17
+ model_py_path: saprot/saprot_classification_model
18
+ kwargs:
19
+ # Arguments to initialize the specific class
20
+ config_path: weight/PLMs/esm2_t33_650M_UR50D
21
+ load_pretrained: True
22
+ num_labels: 2
23
+
24
+ # Arguments to initialize the basic class AbstractModel
25
+ lr_scheduler_kwargs:
26
+ last_epoch: -1
27
+ init_lr: 2.0e-5
28
+ # Weather to use this scheduler or not
29
+ on_use: false
30
+
31
+ optimizer_kwargs:
32
+ betas: [0.9, 0.98]
33
+ weight_decay: 0.01
34
+
35
+ save_path: weight/MetalIonBinding/esm2_t33_650M_UR50D.pt
36
+
37
+
38
+ dataset:
39
+ # Arguments to initialize the basic class LMDBDataset
40
+ dataset_py_path: saprot/saprot_classification_dataset
41
+ dataloader_kwargs:
42
+ batch_size: 8
43
+ num_workers: 8
44
+
45
+ train_lmdb: scripts/LMDB/MetalIonBinding/AF2/normal/train
46
+ valid_lmdb: scripts/LMDB/MetalIonBinding/AF2/normal/valid
47
+ test_lmdb: scripts/LMDB/MetalIonBinding/AF2/normal/test
48
+ # Arguments to initialize the specific class
49
+ kwargs:
50
+ tokenizer: weight/PLMs/esm2_t33_650M_UR50D
51
+
52
+
53
+ # Arguments to initialize Pytorch Lightning Trainer
54
+ Trainer:
55
+ max_epochs: 200
56
+ log_every_n_steps: 1
57
+ strategy:
58
+ find_unused_parameters: True
59
+ logger: True
60
+ enable_checkpointing: false
61
+ val_check_interval: 0.5
62
+ accelerator: gpu
63
+ devices: 4
64
+ num_nodes: 1
65
+ accumulate_grad_batches: 2
66
+ precision: 16
67
+ num_sanity_val_steps: 0
conf/MetalIonBinding/saprot.yaml ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ setting:
2
+ seed: 20000812
3
+ os_environ:
4
+ WANDB_API_KEY: ~
5
+ WANDB_RUN_ID: ~
6
+ CUDA_VISIBLE_DEVICES: 0,1,2,3,4,5,6,7
7
+ MASTER_ADDR: localhost
8
+ MASTER_PORT: 12315
9
+ WORLD_SIZE: 1
10
+ NODE_RANK: 0
11
+ wandb_config:
12
+ project: MetalIonBinding
13
+ name: SaProt_650M_AF2
14
+
15
+ model:
16
+ # Which model to use
17
+ model_py_path: saprot/saprot_classification_model
18
+ kwargs:
19
+ # Arguments to initialize the specific class
20
+ config_path: weight/PLMs/SaProt_650M_AF2
21
+ load_pretrained: True
22
+ num_labels: 2
23
+
24
+ # Arguments to initialize the basic class AbstractModel
25
+ lr_scheduler_kwargs:
26
+ last_epoch: -1
27
+ init_lr: 2.0e-5
28
+ # Weather to use this scheduler or not
29
+ on_use: false
30
+
31
+ optimizer_kwargs:
32
+ betas: [0.9, 0.98]
33
+ weight_decay: 0.01
34
+
35
+ save_path: weight/MetalIonBinding/SaProt_650M_AF2.pt
36
+
37
+
38
+ dataset:
39
+ # Arguments to initialize the basic class LMDBDataset
40
+ dataset_py_path: saprot/saprot_classification_dataset
41
+ dataloader_kwargs:
42
+ batch_size: 8
43
+ num_workers: 8
44
+
45
+ train_lmdb: scripts/LMDB/MetalIonBinding/AF2/foldseek/train
46
+ valid_lmdb: scripts/LMDB/MetalIonBinding/AF2/foldseek/valid
47
+ test_lmdb: scripts/LMDB/MetalIonBinding/AF2/foldseek/test
48
+ # Arguments to initialize the specific class
49
+ kwargs:
50
+ tokenizer: weight/PLMs/SaProt_650M_AF2
51
+ plddt_threshold: 70
52
+
53
+
54
+ # Arguments to initialize Pytorch Lightning Trainer
55
+ Trainer:
56
+ max_epochs: 200
57
+ log_every_n_steps: 1
58
+ strategy:
59
+ find_unused_parameters: True
60
+ logger: True
61
+ enable_checkpointing: false
62
+ val_check_interval: 0.5
63
+ accelerator: gpu
64
+ devices: 4
65
+ num_nodes: 1
66
+ accumulate_grad_batches: 2
67
+ precision: 16
68
+ num_sanity_val_steps: 0
conf/ProteinGym/esm2.yaml ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ setting:
2
+ seed: 20000812
3
+ os_environ:
4
+ CUDA_VISIBLE_DEVICES: 0,1,2,3,4,5,6,7
5
+ MASTER_ADDR: 127.0.0.1
6
+ MASTER_PORT: 12315
7
+ WORLD_SIZE: 1
8
+ NODE_RANK: 0
9
+
10
+ out_path: output/ProteinGym/esm2_t33_650M_UR50D.tsv
11
+ dataset_dir: scripts/LMDB/ProteinGym/substitutions
12
+
13
+ model:
14
+ model_py_path: saprot/esm_mutation_model
15
+ kwargs:
16
+ config_path: weight/PLMs/esm2_t33_650M_UR50D
17
+ load_pretrained: True
18
+
19
+ dataset:
20
+ dataset_py_path: mutation_zeroshot_dataset
21
+ dataloader_kwargs:
22
+ batch_size: 32
23
+ num_workers: 8
24
+ kwargs: {}
25
+
26
+ Trainer:
27
+ log_every_n_steps: 1
28
+ devices: 1
29
+ accelerator: gpu
30
+ num_nodes: 1
31
+ strategy:
32
+ find_unused_parameters: False
33
+ logger: False
conf/ProteinGym/saprot.yaml ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ setting:
2
+ seed: 20000812
3
+ os_environ:
4
+ #CUDA_VISIBLE_DEVICES: 0,1,2,3,4,5,6,7
5
+ CUDA_VISIBLE_DEVICES: 6
6
+ MASTER_ADDR: 127.0.0.1
7
+ MASTER_PORT: 12315
8
+ WORLD_SIZE: 1
9
+ NODE_RANK: 0
10
+
11
+ out_path: output/ProteinGym/SaProt_650M_AF2.tsv
12
+ dataset_dir: scripts/LMDB/ProteinGym/substitutions
13
+
14
+ model:
15
+ model_py_path: saprot/saprot_foldseek_mutation_model
16
+ kwargs:
17
+ foldseek_path: scripts/bin/foldseek
18
+ config_path: weight/PLMs/SaProt_650M_AF2
19
+ load_pretrained: True
20
+ plddt_threshold: 70
21
+
22
+ dataset:
23
+ dataset_py_path: mutation_zeroshot_dataset
24
+ dataloader_kwargs:
25
+ batch_size: 32
26
+ num_workers: 8
27
+ kwargs: {}
28
+
29
+ Trainer:
30
+ log_every_n_steps: 1
31
+ devices: 1
32
+ accelerator: gpu
33
+ num_nodes: 1
34
+ strategy:
35
+ find_unused_parameters: False
36
+ logger: False
conf/Thermostability/esm2.yaml ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ setting:
2
+ seed: 20000812
3
+ os_environ:
4
+ WANDB_API_KEY: ~
5
+ WANDB_RUN_ID: ~
6
+ CUDA_VISIBLE_DEVICES: 0,1,2,3,4,5,6,7
7
+ MASTER_ADDR: localhost
8
+ MASTER_PORT: 12315
9
+ WORLD_SIZE: 1
10
+ NODE_RANK: 0
11
+ wandb_config:
12
+ project: Thermostability
13
+ name: esm2_t33_650M_UR50D
14
+
15
+ model:
16
+ # Which model to use
17
+ model_py_path: saprot/saprot_regression_model
18
+ kwargs:
19
+ # Arguments to initialize the specific class
20
+ config_path: weight/PLMs/esm2_t33_650M_UR50D
21
+ load_pretrained: True
22
+
23
+ # Arguments to initialize the basic class AbstractModel
24
+ lr_scheduler_kwargs:
25
+ last_epoch: -1
26
+ init_lr: 2.0e-5
27
+ # Weather to use this scheduler or not
28
+ on_use: false
29
+
30
+ optimizer_kwargs:
31
+ betas: [0.9, 0.98]
32
+ weight_decay: 0.01
33
+
34
+ save_path: weight/Thermostability/esm2_t33_650M_UR50D.pt
35
+
36
+
37
+ dataset:
38
+ # Arguments to initialize the basic class LMDBDataset
39
+ dataset_py_path: saprot/saprot_regression_dataset
40
+ dataloader_kwargs:
41
+ batch_size: 8
42
+ num_workers: 8
43
+
44
+ train_lmdb: scripts/LMDB/Thermostability/normal/train
45
+ valid_lmdb: scripts/LMDB/Thermostability/normal/valid
46
+ test_lmdb: scripts/LMDB/Thermostability/normal/test
47
+ # Arguments to initialize the specific class
48
+ kwargs:
49
+ tokenizer: weight/PLMs/esm2_t33_650M_UR50D
50
+ mix_max_norm: [40, 67]
51
+
52
+
53
+ # Arguments to initialize Pytorch Lightning Trainer
54
+ Trainer:
55
+ max_epochs: 200
56
+ log_every_n_steps: 1
57
+ strategy:
58
+ find_unused_parameters: True
59
+ logger: True
60
+ enable_checkpointing: false
61
+ val_check_interval: 0.5
62
+ accelerator: gpu
63
+ devices: 8
64
+ num_nodes: 1
65
+ accumulate_grad_batches: 1
66
+ precision: 16
67
+ num_sanity_val_steps: 0
68
+
69
+
conf/Thermostability/saprot.yaml ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ setting:
2
+ seed: 20000812
3
+ os_environ:
4
+ WANDB_API_KEY: ~
5
+ WANDB_RUN_ID: ~
6
+ CUDA_VISIBLE_DEVICES: 0,1,2,3,4,5,6,7
7
+ MASTER_ADDR: localhost
8
+ MASTER_PORT: 12315
9
+ WORLD_SIZE: 1
10
+ NODE_RANK: 0
11
+ wandb_config:
12
+ project: Thermostability
13
+ name: SaProt_650M_AF2
14
+
15
+ model:
16
+ # Which model to use
17
+ model_py_path: saprot/saprot_regression_model
18
+ kwargs:
19
+ # Arguments to initialize the specific class
20
+ config_path: weight/PLMs/SaProt_650M_AF2
21
+ load_pretrained: True
22
+
23
+ # Arguments to initialize the basic class AbstractModel
24
+ lr_scheduler_kwargs:
25
+ last_epoch: -1
26
+ init_lr: 2.0e-5
27
+ # Weather to use this scheduler or not
28
+ on_use: false
29
+
30
+ optimizer_kwargs:
31
+ betas: [0.9, 0.98]
32
+ weight_decay: 0.01
33
+
34
+ save_path: weight/Thermostability/SaProt_650M_AF2.pt
35
+
36
+
37
+ dataset:
38
+ # Arguments to initialize the basic class LMDBDataset
39
+ dataset_py_path: saprot/saprot_regression_dataset
40
+ dataloader_kwargs:
41
+ batch_size: 8
42
+ num_workers: 8
43
+
44
+ train_lmdb: scripts/LMDB/Thermostability/foldseek/train
45
+ valid_lmdb: scripts/LMDB/Thermostability/foldseek/valid
46
+ test_lmdb: scripts/LMDB/Thermostability/foldseek/test
47
+ # Arguments to initialize the specific class
48
+ kwargs:
49
+ tokenizer: weight/PLMs/SaProt_650M_AF2
50
+ mix_max_norm: [40, 67]
51
+ plddt_threshold: 70
52
+
53
+
54
+ # Arguments to initialize Pytorch Lightning Trainer
55
+ Trainer:
56
+ max_epochs: 200
57
+ log_every_n_steps: 1
58
+ strategy:
59
+ find_unused_parameters: True
60
+ logger: True
61
+ enable_checkpointing: false
62
+ val_check_interval: 0.5
63
+ accelerator: gpu
64
+ devices: 4
65
+ num_nodes: 1
66
+ accumulate_grad_batches: 2
67
+ precision: 16
68
+ num_sanity_val_steps: 0
conf/pretrain/saprot.yaml ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ setting:
2
+ seed: 20000812
3
+ os_environ:
4
+ WANDB_API_KEY: ~
5
+ WANDB_RUN_ID: ~
6
+ CUDA_VISIBLE_DEVICES: 0,1,2,3,4,5,6,7
7
+ MASTER_ADDR: 127.0.0.1
8
+ MASTER_PORT: 12315
9
+ WORLD_SIZE: 1
10
+ NODE_RANK: 0
11
+ # WANDB_MODE: offline
12
+ wandb_config:
13
+ project: pretraining
14
+ name: SaProt_650M_AF2
15
+
16
+ model:
17
+ # Which model to use
18
+ model_py_path: saprot/saprot_lm_model
19
+ kwargs:
20
+ # Arguments to initialize the specific class
21
+ config_path: weight/PLMs/SaProt_650M_AF2
22
+ load_pretrained: True
23
+
24
+ # Arguments to initialize the basic class AbstractModel
25
+ lr_scheduler_kwargs:
26
+ last_epoch: -1
27
+ init_lr: 0
28
+ max_lr: 4.0e-4
29
+ final_lr: 4.0e-5
30
+ warmup_steps: 2000
31
+ start_decay_after_n_steps: 150000
32
+ end_decay_after_n_steps: 1500000
33
+ # Weather to use this scheduler or not
34
+ on_use: True
35
+
36
+ optimizer_kwargs:
37
+ betas: [0.9, 0.98]
38
+ weight_decay: 0.01
39
+
40
+ save_path: weight/Pretrain/SaProt_650M_AF2.pt
41
+ load_prev_scheduler: false
42
+ save_weights_only: false
43
+
44
+
45
+ dataset:
46
+ # Arguments to initialize the basic class LMDBDataset
47
+ dataset_py_path: saprot/saprot_foldseek_dataset
48
+ dataloader_kwargs:
49
+ batch_size: 8
50
+ num_workers: 8
51
+
52
+ train_lmdb: scripts/LMDB/AF2_Uniref50/foldseek/train
53
+ valid_lmdb: scripts/LMDB/AF2_Uniref50/foldseek/valid
54
+ test_lmdb: scripts/LMDB/AF2_Uniref50/foldseek/valid
55
+ # Arguments to initialize the specific class
56
+ kwargs:
57
+ tokenizer: weight/PLMs/SaProt_650M_AF2
58
+ max_length: 1024
59
+ mask_ratio: 0.15
60
+
61
+
62
+ # Arguments to initialize Pytorch Lightning Trainer
63
+ Trainer:
64
+ # max_epochs: 100
65
+ max_steps: 1000000
66
+ min_steps: 1000000
67
+ log_every_n_steps: 1
68
+ accelerator: gpu
69
+ devices: 1
70
+ accumulate_grad_batches: 1
71
+ num_nodes: 1
72
+ strategy:
73
+ find_unused_parameters: True
74
+ logger: False
75
+ enable_checkpointing: false
76
+ num_sanity_val_steps: 0
77
+ val_check_interval: 100
78
+ precision: 16
79
+ limit_val_batches: 1.0
80
+ limit_test_batches: 1.0
81
+
conf/scnet/ClinVar_saprot.yaml ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ setting:
2
+ seed: 20000812
3
+ os_environ:
4
+ CUDA_VISIBLE_DEVICES: 0
5
+ MASTER_ADDR: 127.0.0.1
6
+ MASTER_PORT: 12315
7
+ WORLD_SIZE: 1
8
+ NODE_RANK: 0
9
+
10
+ out_path: ~
11
+ dataset_dir: scripts/LMDB/ClinVar
12
+
13
+ model:
14
+ model_py_path: saprot/saprot_foldseek_mutation_model
15
+ kwargs:
16
+ foldseek_path: scripts/bin/foldseek
17
+ config_path: weight/PLMs/SaProt_650M_AF2
18
+ load_pretrained: True
19
+ log_clinvar: True
20
+ log_dir: output/ClinVar/SaProt_650M_AF2_scnet
21
+ plddt_threshold: 70.
22
+
23
+ dataset:
24
+ dataset_py_path: mutation_zeroshot_dataset
25
+ dataloader_kwargs:
26
+ batch_size: 8
27
+ num_workers: 4
28
+ kwargs: {}
29
+
30
+ Trainer:
31
+ log_every_n_steps: 1
32
+ devices: 1
33
+ accelerator: gpu
34
+ num_nodes: 1
35
+ strategy:
36
+ find_unused_parameters: False
37
+ logger: False
conf/scnet/Thermostability_saprot_1gpu.yaml ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ setting:
2
+ seed: 20000812
3
+ os_environ:
4
+ WANDB_API_KEY: ~
5
+ WANDB_RUN_ID: ~
6
+ CUDA_VISIBLE_DEVICES: 3
7
+ MASTER_ADDR: localhost
8
+ MASTER_PORT: 12315
9
+ WORLD_SIZE: 1
10
+ NODE_RANK: 0
11
+ wandb_config:
12
+ project: Thermostability
13
+ name: SaProt_650M_AF2_scnet_1gpu
14
+
15
+ model:
16
+ # Which model to use
17
+ model_py_path: saprot/saprot_regression_model
18
+ kwargs:
19
+ # Arguments to initialize the specific class
20
+ config_path: weight/PLMs/SaProt_650M_AF2
21
+ load_pretrained: True
22
+
23
+ # Arguments to initialize the basic class AbstractModel
24
+ lr_scheduler_kwargs:
25
+ last_epoch: -1
26
+ init_lr: 2.0e-5
27
+ # Weather to use this scheduler or not
28
+ on_use: false
29
+
30
+ optimizer_kwargs:
31
+ betas: [0.9, 0.98]
32
+ weight_decay: 0.01
33
+
34
+ save_path: weight/Thermostability/SaProt_650M_AF2_scnet_1gpu.pt
35
+
36
+
37
+ dataset:
38
+ # Arguments to initialize the basic class LMDBDataset
39
+ dataset_py_path: saprot/saprot_regression_dataset
40
+ dataloader_kwargs:
41
+ batch_size: 1
42
+ num_workers: 4
43
+
44
+ train_lmdb: scripts/LMDB/Thermostability/foldseek/train
45
+ valid_lmdb: scripts/LMDB/Thermostability/foldseek/valid
46
+ test_lmdb: scripts/LMDB/Thermostability/foldseek/test
47
+ # Arguments to initialize the specific class
48
+ kwargs:
49
+ tokenizer: weight/PLMs/SaProt_650M_AF2
50
+ mix_max_norm: [40, 67]
51
+ plddt_threshold: 70
52
+
53
+
54
+ # Arguments to initialize Pytorch Lightning Trainer
55
+ Trainer:
56
+ max_epochs: 200
57
+ log_every_n_steps: 1
58
+ strategy:
59
+ find_unused_parameters: True
60
+ logger: False
61
+ enable_checkpointing: false
62
+ val_check_interval: 0.5
63
+ accelerator: gpu
64
+ devices: 1
65
+ num_nodes: 1
66
+ accumulate_grad_batches: 8
67
+ precision: 16
68
+ num_sanity_val_steps: 0
config.json ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "framework": "PyTorch",
3
+ "task": "protein-language-modeling",
4
+ "model": "SaProt",
5
+ "description": "Structure-aware protein language modeling with amino-acid and Foldseek 3Di tokens for representation extraction, mutation-effect prediction, inverse folding, pretraining, downstream fine-tuning, and benchmark evaluation.",
6
+ "subtasks": [
7
+ "structure-aware-sequence-encoding",
8
+ "masked-language-model-inference",
9
+ "protein-embedding-extraction",
10
+ "zero-shot-mutation-effect-prediction",
11
+ "protein-inverse-folding",
12
+ "saprot-pretraining",
13
+ "downstream-finetuning",
14
+ "benchmark-evaluation",
15
+ "esm2-baseline-evaluation"
16
+ ],
17
+ "entrypoints": {
18
+ "train": "scripts/training.py",
19
+ "pretrain_config": "conf/pretrain/saprot.yaml",
20
+ "thermos_finetune_config": "conf/Thermostability/saprot.yaml",
21
+ "scnet_thermos_finetune_config": "conf/scnet/Thermostability_saprot_1gpu.yaml",
22
+ "mutation_zeroshot": "scripts/mutation_zeroshot.py",
23
+ "proteingym_config": "conf/ProteinGym/saprot.yaml",
24
+ "clinvar_config": "conf/ClinVar/saprot.yaml",
25
+ "scnet_clinvar_config": "conf/scnet/ClinVar_saprot.yaml",
26
+ "clinvar_auc": "scripts/compute_clinvar_auc.py",
27
+ "environment_setup": "scripts/environment.sh"
28
+ },
29
+ "packages": [
30
+ "model",
31
+ "scripts.dataset",
32
+ "scripts.utils"
33
+ ],
34
+ "configs": {
35
+ "root": "conf",
36
+ "saprot_tasks": [
37
+ "conf/Thermostability/saprot.yaml",
38
+ "conf/EC/saprot.yaml",
39
+ "conf/GO/MF/saprot.yaml",
40
+ "conf/GO/BP/saprot.yaml",
41
+ "conf/GO/CC/saprot.yaml",
42
+ "conf/MetalIonBinding/saprot.yaml",
43
+ "conf/HumanPPI/saprot.yaml",
44
+ "conf/Contact/saprot.yaml",
45
+ "conf/DeepLoc/cls2/saprot.yaml",
46
+ "conf/DeepLoc/cls10/saprot.yaml",
47
+ "conf/ProteinGym/saprot.yaml",
48
+ "conf/ClinVar/saprot.yaml"
49
+ ],
50
+ "esm2_baselines": [
51
+ "conf/Thermostability/esm2.yaml",
52
+ "conf/EC/esm2.yaml",
53
+ "conf/GO/MF/esm2.yaml",
54
+ "conf/GO/BP/esm2.yaml",
55
+ "conf/GO/CC/esm2.yaml",
56
+ "conf/MetalIonBinding/esm2.yaml",
57
+ "conf/HumanPPI/esm2.yaml",
58
+ "conf/Contact/esm2.yaml",
59
+ "conf/DeepLoc/cls2/esm2.yaml",
60
+ "conf/DeepLoc/cls10/esm2.yaml",
61
+ "conf/ProteinGym/esm2.yaml",
62
+ "conf/ClinVar/esm2.yaml"
63
+ ],
64
+ "scnet": [
65
+ "conf/scnet/Thermostability_saprot_1gpu.yaml",
66
+ "conf/scnet/ClinVar_saprot.yaml"
67
+ ]
68
+ },
69
+ "example_data": [
70
+ "scripts/example/8ac8.cif"
71
+ ],
72
+ "data_roots": {
73
+ "downstream_lmdb": "scripts/LMDB",
74
+ "pretrain_lmdb": "scripts/LMDB/AF2_Uniref50/foldseek",
75
+ "proteingym": "scripts/LMDB/ProteinGym/substitutions",
76
+ "clinvar": "scripts/LMDB/ClinVar",
77
+ "thermos": "scripts/LMDB/Thermostability/foldseek"
78
+ },
79
+ "model_cache": "weight/PLMs",
80
+ "default_model": "weight/PLMs/SaProt_650M_AF2",
81
+ "optional_models": {
82
+ "esm2_650m": "weight/PLMs/esm2_t33_650M_UR50D",
83
+ "saprot_inverse_folding": "weight/PLMs/SaProt_650M_AF2_inverse_folding"
84
+ },
85
+ "external_tools": {
86
+ "foldseek": "scripts/bin/foldseek"
87
+ },
88
+ "outputs": {
89
+ "finetuned_checkpoints": "weight/{task}/*.pt",
90
+ "proteingym_scores": "output/ProteinGym/SaProt_650M_AF2.tsv",
91
+ "clinvar_predictions": "output/ClinVar/SaProt_650M_AF2",
92
+ "scnet_clinvar_predictions": "output/ClinVar/SaProt_650M_AF2_scnet"
93
+ },
94
+ "quick_commands": {
95
+ "setup": "bash scripts/environment.sh",
96
+ "train_thermostability": "python scripts/training.py -c conf/Thermostability/saprot.yaml",
97
+ "train_thermostability_scnet_1gpu": "python scripts/training.py -c conf/scnet/Thermostability_saprot_1gpu.yaml",
98
+ "zeroshot_proteingym": "python scripts/mutation_zeroshot.py -c conf/ProteinGym/saprot.yaml",
99
+ "zeroshot_clinvar": "python scripts/mutation_zeroshot.py -c conf/ClinVar/saprot.yaml",
100
+ "compute_clinvar_auc": "python scripts/compute_clinvar_auc.py -c conf/ClinVar/saprot.yaml"
101
+ }
102
+ }
model/README.md ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ # Introduction
2
+ ## abstract_model.py
3
+ The `abstract_model.py` file contains the abstract class for all models.
4
+
5
+ ## model_interface.py
6
+ The `model_interface.py` file is used to dynamically load a model.
7
+ ## saprot
8
+ The `saprot` folder contains various implementations for using SaProt model on downstream tasks. Note that
9
+ SaProt shares the same architecture with ESM models, so we can use the same code to fine-tune both SaProt and ESM-2.
model/abstract_model.py ADDED
@@ -0,0 +1,282 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import abc
3
+ import os
4
+
5
+ import pytorch_lightning as pl
6
+ from scripts.utils.lr_scheduler import Esm2LRScheduler
7
+ from torch import distributed as dist
8
+
9
+
10
+ class AbstractModel(pl.LightningModule):
11
+ def __init__(self,
12
+ lr_scheduler_kwargs: dict = None,
13
+ optimizer_kwargs: dict = None,
14
+ save_path: str = None,
15
+ from_checkpoint: str = None,
16
+ load_prev_scheduler: bool = False,
17
+ save_weights_only: bool = True,):
18
+ """
19
+
20
+ Args:
21
+ lr_scheduler: Kwargs for lr_scheduler
22
+ optimizer_kwargs: Kwargs for optimizer_kwargs
23
+ save_path: Save trained model
24
+ from_checkpoint: Load model from checkpoint
25
+ load_prev_scheduler: Whether load previous scheduler from save_path
26
+ load_strict: Whether load model strictly
27
+ save_weights_only: Whether save only weights or also optimizer and lr_scheduler
28
+
29
+ """
30
+ super().__init__()
31
+ self.initialize_model()
32
+
33
+ self.metrics = {}
34
+ for stage in ["train", "valid", "test"]:
35
+ stage_metrics = self.initialize_metrics(stage)
36
+ # Rigister metrics as attributes
37
+ for metric_name, metric in stage_metrics.items():
38
+ setattr(self, metric_name, metric)
39
+
40
+ self.metrics[stage] = stage_metrics
41
+
42
+ self.lr_scheduler_kwargs = {"init_lr": 0} if lr_scheduler_kwargs is None else lr_scheduler_kwargs
43
+ self.optimizer_kwargs = {} if optimizer_kwargs is None else optimizer_kwargs
44
+ self.init_optimizers()
45
+
46
+ self.save_path = save_path
47
+ self.save_weights_only = save_weights_only
48
+
49
+ self.step = 0
50
+ self.epoch = 0
51
+
52
+ self.load_prev_scheduler = load_prev_scheduler
53
+ if from_checkpoint:
54
+ self.load_checkpoint(from_checkpoint, load_prev_scheduler)
55
+
56
+ @abc.abstractmethod
57
+ def initialize_model(self) -> None:
58
+ """
59
+ All model initialization should be done here
60
+ Note that the whole model must be named as "self.model" for model saving and loading
61
+ """
62
+ raise NotImplementedError
63
+
64
+ @abc.abstractmethod
65
+ def forward(self, *args, **kwargs):
66
+ """
67
+ Forward propagation
68
+ """
69
+ raise NotImplementedError
70
+
71
+ @abc.abstractmethod
72
+ def initialize_metrics(self, stage: str) -> dict:
73
+ """
74
+ Initialize metrics for each stage
75
+ Args:
76
+ stage: "train", "valid" or "test"
77
+
78
+ Returns:
79
+ A dictionary of metrics for the stage. Keys are metric names and values are metric objects
80
+ """
81
+ raise NotImplementedError
82
+
83
+ @abc.abstractmethod
84
+ def loss_func(self, stage: str, outputs, labels) -> torch.Tensor:
85
+ """
86
+
87
+ Args:
88
+ stage: "train", "valid" or "test"
89
+ outputs: model outputs for calculating loss
90
+ labels: labels for calculating loss
91
+
92
+ Returns:
93
+ loss
94
+
95
+ """
96
+ raise NotImplementedError
97
+
98
+ @staticmethod
99
+ def load_weights(model, weights):
100
+ model_dict = model.state_dict()
101
+
102
+ unused_params = []
103
+ missed_params = list(model_dict.keys())
104
+
105
+ for k, v in weights.items():
106
+ if k in model_dict.keys():
107
+ model_dict[k] = v
108
+ missed_params.remove(k)
109
+
110
+ else:
111
+ unused_params.append(k)
112
+
113
+ if len(missed_params) > 0:
114
+ print(f"\033[31mSome weights of {type(model).__name__} were not "
115
+ f"initialized from the model checkpoint: {missed_params}\033[0m")
116
+
117
+ if len(unused_params) > 0:
118
+ print(f"\033[31mSome weights of the model checkpoint were not used: {unused_params}\033[0m")
119
+
120
+ model.load_state_dict(model_dict)
121
+
122
+ # Add 1 to step after each optimizer step
123
+ def optimizer_step(
124
+ self,
125
+ epoch: int,
126
+ batch_idx: int,
127
+ optimizer,
128
+ optimizer_idx: int = 0,
129
+ optimizer_closure=None,
130
+ on_tpu: bool = False,
131
+ using_native_amp: bool = False,
132
+ using_lbfgs: bool = False,
133
+ ) -> None:
134
+ super().optimizer_step(
135
+ epoch, batch_idx, optimizer, optimizer_idx, optimizer_closure, on_tpu, using_native_amp, using_lbfgs
136
+ )
137
+ self.step += 1
138
+
139
+ def on_train_epoch_end(self):
140
+ self.epoch += 1
141
+
142
+ def training_step(self, batch, batch_idx):
143
+ inputs, labels = batch
144
+ outputs = self(**inputs)
145
+ loss = self.loss_func('train', outputs, labels)
146
+ return loss
147
+
148
+ def validation_step(self, batch, batch_idx):
149
+ inputs, labels = batch
150
+ outputs = self(**inputs)
151
+ return self.loss_func('valid', outputs, labels)
152
+
153
+ def test_step(self, batch, batch_idx):
154
+ inputs, labels = batch
155
+ outputs = self(**inputs)
156
+ return self.loss_func('test', outputs, labels)
157
+
158
+ def load_checkpoint(self, from_checkpoint, load_prev_scheduler):
159
+ state_dict = torch.load(from_checkpoint, map_location=self.device)
160
+ self.load_weights(self.model, state_dict["model"])
161
+
162
+ if load_prev_scheduler:
163
+ try:
164
+ self.step = state_dict["global_step"]
165
+ self.epoch = state_dict["epoch"]
166
+ self.best_value = state_dict["best_value"]
167
+ self.optimizer.load_state_dict(state_dict["optimizer"])
168
+ self.lr_scheduler.load_state_dict(state_dict["lr_scheduler"])
169
+ print(f"Previous training global step: {self.step}")
170
+ print(f"Previous training epoch: {self.epoch}")
171
+ print(f"Previous best value: {self.best_value}")
172
+ print(f"Previous lr_scheduler: {state_dict['lr_scheduler']}")
173
+
174
+ except Exception as e:
175
+ print(e)
176
+ raise KeyError("Wrong in loading previous scheduler, please set load_prev_scheduler=False")
177
+
178
+ def save_checkpoint(self, save_info: dict = None) -> None:
179
+ """
180
+ Save model to save_path
181
+ Args:
182
+ save_info: Other info to save
183
+ """
184
+ state_dict = {} if save_info is None else save_info
185
+ state_dict["model"] = self.model.state_dict()
186
+
187
+ if not self.save_weights_only:
188
+ state_dict["global_step"] = self.step
189
+ state_dict["epoch"] = self.epoch
190
+ state_dict["best_value"] = getattr(self, f"best_value", None)
191
+ state_dict["optimizer"] = self.optimizers().optimizer.state_dict()
192
+ state_dict["lr_scheduler"] = self.lr_schedulers().state_dict()
193
+
194
+ torch.save(state_dict, self.save_path)
195
+
196
+ def check_save_condition(self, now_value: float, mode: str, save_info: dict = None) -> None:
197
+ """
198
+ Check whether to save model. If save_path is not None and now_value is the best, save model.
199
+ Args:
200
+ now_value: Current metric value
201
+ mode: "min" or "max", meaning whether the lower the better or the higher the better
202
+ save_info: Other info to save
203
+ """
204
+
205
+ assert mode in ["min", "max"], "mode should be 'min' or 'max'"
206
+
207
+ if self.save_path is not None:
208
+ dir = os.path.dirname(self.save_path)
209
+ os.makedirs(dir, exist_ok=True)
210
+
211
+ if dist.get_rank() == 0:
212
+ # save the best checkpoint
213
+ best_value = getattr(self, f"best_value", None)
214
+ if best_value:
215
+ if mode == "min" and now_value < best_value or mode == "max" and now_value > best_value:
216
+ setattr(self, "best_value", now_value)
217
+ self.save_checkpoint(save_info)
218
+
219
+ else:
220
+ setattr(self, "best_value", now_value)
221
+ self.save_checkpoint(save_info)
222
+
223
+ def reset_metrics(self, stage) -> None:
224
+ """
225
+ Reset metrics for given stage
226
+ Args:
227
+ stage: "train", "valid" or "test"
228
+ """
229
+ for metric in self.metrics[stage].values():
230
+ metric.reset()
231
+
232
+ def get_log_dict(self, stage: str) -> dict:
233
+ """
234
+ Get log dict for the stage
235
+ Args:
236
+ stage: "train", "valid" or "test"
237
+
238
+ Returns:
239
+ A dictionary of metrics for the stage. Keys are metric names and values are metric values
240
+
241
+ """
242
+ return {name: metric.compute() for name, metric in self.metrics[stage].items()}
243
+
244
+ def log_info(self, info: dict) -> None:
245
+ """
246
+ Record metrics during training and testing
247
+ Args:
248
+ info: dict of metrics
249
+ """
250
+ if getattr(self, "logger", None) is not None:
251
+ info["learning_rate"] = self.lr_scheduler.get_last_lr()[0]
252
+ info["epoch"] = self.epoch
253
+ self.logger.log_metrics(info, step=self.step)
254
+
255
+ def init_optimizers(self):
256
+ # No decay for layer norm and bias
257
+ no_decay = ['LayerNorm.weight', 'bias']
258
+
259
+ if "weight_decay" in self.optimizer_kwargs:
260
+ weight_decay = self.optimizer_kwargs.pop("weight_decay")
261
+ else:
262
+ weight_decay = 0.01
263
+
264
+ optimizer_grouped_parameters = [
265
+ {'params': [p for n, p in self.model.named_parameters() if not any(nd in n for nd in no_decay)],
266
+ 'weight_decay': weight_decay},
267
+ {'params': [p for n, p in self.model.named_parameters() if any(nd in n for nd in no_decay)],
268
+ 'weight_decay': 0.0}
269
+ ]
270
+
271
+ self.optimizer = torch.optim.AdamW(optimizer_grouped_parameters,
272
+ lr=self.lr_scheduler_kwargs['init_lr'],
273
+ **self.optimizer_kwargs)
274
+
275
+ self.lr_scheduler = Esm2LRScheduler(self.optimizer, **self.lr_scheduler_kwargs)
276
+
277
+ def configure_optimizers(self):
278
+ return {"optimizer": self.optimizer,
279
+ "lr_scheduler": {"scheduler": self.lr_scheduler,
280
+ "interval": "step",
281
+ "frequency": 1}
282
+ }
model/model_interface.py ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import yaml
2
+ import glob
3
+
4
+
5
+ # register all available models through *_model.py files
6
+ # def construct_model():
7
+ # model_dir = os.path.dirname(__file__)
8
+ #
9
+ # # lists all model files
10
+ # model_list = []
11
+ # for root, _, names in os.walk(model_dir):
12
+ # for name in names:
13
+ # if name.endswith('_model.py'):
14
+ # sub_dirs = root.replace(model_dir, '').split(os.sep)
15
+ # model_list.append((sub_dirs, name[:-3]))
16
+ #
17
+ # # load model_config.yaml, controlling which models to be loaded
18
+ # model_config = yaml.safe_load(open(f"{model_dir}/model_config.yaml", "r"))
19
+ #
20
+ # if model_config["verbose"]:
21
+ # print("*" * 30 + f" Loading model " + "*" * 30)
22
+ #
23
+ # # register models
24
+ # for sub_dirs, name in model_list:
25
+ # if name in model_config["models"]:
26
+ # if len(sub_dirs) > 1:
27
+ # cmd = f"from {'.'.join(sub_dirs)} import {name}"
28
+ # else:
29
+ # cmd = f"from . import {name}"
30
+ #
31
+ # exec(cmd)
32
+ #
33
+ # if model_config["verbose"]:
34
+ # info = f"Loaded model: {name}"
35
+ # print(f"\033[32m{info}\033[0m")
36
+ # else:
37
+ # if model_config["verbose"]:
38
+ # info = f"Skipped model: {name}"
39
+ # print(f"\033[31m{info}\033[0m")
40
+ #
41
+ # if model_config["verbose"]:
42
+ # print("*" * 75)
43
+ #
44
+ #
45
+ # # register function as a wrapper for all models
46
+ # def register_model(cls):
47
+ # model_dict[cls.__name__] = cls
48
+ # return cls
49
+ #
50
+ #
51
+ # model_dict = {}
52
+ # construct_model()
53
+ #
54
+ #
55
+ # class ModelInterface:
56
+ # @classmethod
57
+ # def get_available_models(cls):
58
+ # return model_dict.keys()
59
+ #
60
+ # @classmethod
61
+ # def init_model(cls, model: str, **kwargs):
62
+ # """
63
+ #
64
+ # Args:
65
+ # model : Class name of model you want to use. Must be in model_dict.keys()
66
+ # **kwargs: Kwargs for model initialization
67
+ #
68
+ # Returns: Corresponding model
69
+ #
70
+ # """
71
+ # assert model in model_dict.keys(), f"class {model} doesn't exist!"
72
+ # return model_dict[model](**kwargs)
73
+
74
+
75
+ ########################################################################
76
+ # Version 2 #
77
+ ########################################################################
78
+ # register function as a wrapper for all models
79
+ def register_model(cls):
80
+ global now_cls
81
+ now_cls = cls
82
+ return cls
83
+
84
+
85
+ now_cls = None
86
+
87
+
88
+ class ModelInterface:
89
+ @classmethod
90
+ def init_model(cls, model_py_path: str, **kwargs):
91
+ """
92
+
93
+ Args:
94
+ model_py_path: Py file Path of model you want to use.
95
+ **kwargs: Kwargs for model initialization
96
+
97
+ Returns: Corresponding model
98
+ """
99
+ sub_dirs = model_py_path.replace("\\", "/").split("/")
100
+ cmd = f"from {'.' + '.'.join(sub_dirs[:-1])} import {sub_dirs[-1]}"
101
+ exec(cmd)
102
+
103
+ return now_cls(**kwargs)
model/saprot/base.py ADDED
@@ -0,0 +1,210 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+
3
+ from typing import List, Dict
4
+ # from data.pdb2feature import batch_coords2feature
5
+ from transformers import EsmConfig, EsmTokenizer, EsmForMaskedLM, EsmForSequenceClassification
6
+ # from module.esm.structure_module import (
7
+ # EsmForMaskedLMWithStructure as EsmForMaskedLM,
8
+ # EsmForSequenceClassificationWithStructure as EsmForSequenceClassification,
9
+ # )
10
+ from ..abstract_model import AbstractModel
11
+
12
+
13
+ class SaprotBaseModel(AbstractModel):
14
+ """
15
+ ESM base model. It cannot be used directly but provides model initialization for downstream tasks.
16
+ """
17
+
18
+ def __init__(self,
19
+ task: str,
20
+ config_path: str,
21
+ extra_config: dict = None,
22
+ load_pretrained: bool = False,
23
+ freeze_backbone: bool = False,
24
+ use_lora: bool = False,
25
+ lora_config_path: str = None,
26
+ **kwargs):
27
+ """
28
+ Args:
29
+ task: Task name. Must be one of ['classification', 'regression', 'lm', 'base']
30
+
31
+ config_path: Path to the config file of huggingface esm model
32
+
33
+ extra_config: Extra config for the model
34
+
35
+ load_pretrained: Whether to load pretrained weights of base model
36
+
37
+ freeze_backbone: Whether to freeze the backbone of the model
38
+
39
+ use_lora: Whether to use LoRA on downstream tasks
40
+
41
+ lora_config_path: Path to the config file of LoRA. If not None, LoRA model is for inference only.
42
+ Otherwise, LoRA model is for training.
43
+
44
+ **kwargs: Other arguments for AbstractModel
45
+ """
46
+ assert task in ['classification', 'regression', 'lm', 'base']
47
+ self.task = task
48
+ self.config_path = config_path
49
+ self.extra_config = extra_config
50
+ self.load_pretrained = load_pretrained
51
+ self.freeze_backbone = freeze_backbone
52
+ super().__init__(**kwargs)
53
+
54
+ # After all initialization done, lora technique is applied if needed
55
+ self.use_lora = use_lora
56
+ if use_lora:
57
+ self._init_lora(lora_config_path)
58
+
59
+ def _init_lora(self, lora_config_path):
60
+ from peft import (
61
+ PeftModelForSequenceClassification,
62
+ get_peft_model,
63
+ LoraConfig,
64
+ )
65
+
66
+ if lora_config_path:
67
+ # Note that the model is for inference only
68
+ self.model = PeftModelForSequenceClassification.from_pretrained(self.model, lora_config_path)
69
+ self.model.merge_and_unload()
70
+ print("LoRA model is initialized for inference.")
71
+
72
+ else:
73
+ lora_config = {
74
+ "task_type": "SEQ_CLS",
75
+ "target_modules": ["query", "key", "value", "intermediate.dense", "output.dense"],
76
+ "modules_to_save": ["classifier"],
77
+ "inference_mode": False,
78
+ "lora_dropout": 0.1,
79
+ "lora_alpha": 8,
80
+ }
81
+
82
+ peft_config = LoraConfig(**lora_config)
83
+ self.model = get_peft_model(self.model, peft_config)
84
+ # original_module is not needed for training
85
+ self.model.classifier.original_module = None
86
+
87
+ print("LoRA model is initialized for training.")
88
+ self.model.print_trainable_parameters()
89
+
90
+ # After LoRA model is initialized, add trainable parameters to optimizer
91
+ self.init_optimizers()
92
+
93
+ def initialize_model(self):
94
+ # Initialize tokenizer
95
+ self.tokenizer = EsmTokenizer.from_pretrained(self.config_path)
96
+
97
+ # Initialize different models according to task
98
+ config = EsmConfig.from_pretrained(self.config_path)
99
+
100
+ # Add extra config if needed
101
+ if self.extra_config is None:
102
+ self.extra_config = {}
103
+
104
+ for k, v in self.extra_config.items():
105
+ setattr(config, k, v)
106
+
107
+ if self.task == 'classification':
108
+ # Note that self.num_labels should be set in child classes
109
+ if self.load_pretrained:
110
+ self.model = EsmForSequenceClassification.from_pretrained(
111
+ self.config_path, num_labels=self.num_labels, **self.extra_config)
112
+
113
+ else:
114
+ config.num_labels = self.num_labels
115
+ self.model = EsmForSequenceClassification(config)
116
+
117
+ elif self.task == 'regression':
118
+ if self.load_pretrained:
119
+ self.model = EsmForSequenceClassification.from_pretrained(
120
+ self.config_path, num_labels=1, **self.extra_config)
121
+
122
+ else:
123
+ config.num_labels = 1
124
+ self.model = EsmForSequenceClassification(config)
125
+
126
+ elif self.task == 'lm':
127
+ if self.load_pretrained:
128
+ self.model = EsmForMaskedLM.from_pretrained(self.config_path, **self.extra_config)
129
+
130
+ else:
131
+ self.model = EsmForMaskedLM(config)
132
+
133
+ elif self.task == 'base':
134
+ if self.load_pretrained:
135
+ self.model = EsmForMaskedLM.from_pretrained(self.config_path, **self.extra_config)
136
+
137
+ else:
138
+ self.model = EsmForMaskedLM(config)
139
+
140
+ # Remove lm_head as it is not needed for PPI task
141
+ self.model.lm_head = None
142
+
143
+ # Freeze the backbone of the model
144
+ if self.freeze_backbone:
145
+ for param in self.model.esm.parameters():
146
+ param.requires_grad = False
147
+
148
+ def initialize_metrics(self, stage: str) -> dict:
149
+ return {}
150
+
151
+ def get_hidden_states(self, inputs, reduction: str = None) -> list:
152
+ """
153
+ Get hidden representations of the model.
154
+
155
+ Args:
156
+ inputs: A dictionary of inputs. It should contain keys ["input_ids", "attention_mask", "token_type_ids"].
157
+ reduction: Whether to reduce the hidden states. If None, the hidden states are not reduced. If "mean",
158
+ the hidden states are averaged over the sequence length.
159
+
160
+ Returns:
161
+ hidden_states: A list of tensors. Each tensor is of shape [L, D], where L is the sequence length and D is
162
+ the hidden dimension.
163
+ """
164
+ inputs["output_hidden_states"] = True
165
+ outputs = self.model.esm(**inputs)
166
+
167
+ # Get the index of the first <eos> token
168
+ input_ids = inputs["input_ids"]
169
+ eos_id = self.tokenizer.eos_token_id
170
+ ends = (input_ids == eos_id).int()
171
+ indices = ends.argmax(dim=-1)
172
+
173
+ repr_list = []
174
+ hidden_states = outputs["hidden_states"][-1]
175
+ for i, idx in enumerate(indices):
176
+ if reduction == "mean":
177
+ repr = hidden_states[i][1:idx].mean(dim=0)
178
+ else:
179
+ repr = hidden_states[i][1:idx]
180
+
181
+ repr_list.append(repr)
182
+
183
+ return repr_list
184
+
185
+ # def add_bias_feature(self, inputs, coords: List[Dict]) -> torch.Tensor:
186
+ # """
187
+ # Add structure information as biases to attention map. This function is used to add structure information
188
+ # to the model as Evoformer does.
189
+ #
190
+ # Args:
191
+ # inputs: A dictionary of inputs. It should contain keys ["input_ids", "attention_mask", "token_type_ids"].
192
+ # coords: Coordinates of backbone atoms. Each element is a dictionary with keys ["N", "CA", "C", "O"].
193
+ #
194
+ # Returns
195
+ # pair_feature: A tensor of shape [B, L, L, 407]. Here 407 is the RBF of distance(400) + angle(7).
196
+ # """
197
+ # inputs["pair_feature"] = batch_coords2feature(coords, self.model.device)
198
+ # return inputs
199
+
200
+ def save_checkpoint(self, save_info: dict = None) -> None:
201
+ """
202
+ Rewrite this function for saving LoRA parameters
203
+ """
204
+ if not self.use_lora:
205
+ return super().save_checkpoint(save_info)
206
+
207
+ else:
208
+ self.model.save_pretrained(self.save_path)
209
+
210
+
model/saprot/esm_mutation_model.py ADDED
@@ -0,0 +1,288 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import torch
3
+ import copy
4
+ import json
5
+ import torchmetrics
6
+ import torch.distributed as dist
7
+
8
+ from scripts.utils.constants import aa_set, aa_list
9
+ from ..model_interface import register_model
10
+ from .base import SaprotBaseModel
11
+
12
+
13
+ @register_model
14
+ class EsmMutationModel(SaprotBaseModel):
15
+ def __init__(self,
16
+ use_bias_feature: bool = False,
17
+ MSA_log_path: str = None,
18
+ log_clinvar: bool = False,
19
+ log_dir: str = None,
20
+ **kwargs):
21
+ """
22
+ Args:
23
+ use_bias_feature: Whether to use structure information as bias feature
24
+
25
+ MSA_log_path: If not None, the model will load MSA log from this path (following Tranception paper)
26
+
27
+ log_clinvar: If True, the model will log the predicted evolutionary indices for ClinVar variants
28
+
29
+ log_dir: If log_clinvar is True, the model will save the predicted evolutionary indices for ClinVar variants
30
+
31
+ **kwargs: other arguments for SaprotBaseModel
32
+ """
33
+ self.use_bias_feature = use_bias_feature
34
+ self.MSA_log_path = MSA_log_path
35
+ self.MSA_info_dict = {}
36
+ if MSA_log_path:
37
+ with open(MSA_log_path, "r") as r:
38
+ for line in r:
39
+ data = json.loads(line)
40
+ data["MSA_log_prior"] = torch.tensor(data["MSA_log_prior"])
41
+ self.MSA_info_dict[data["DMS_id"]] = data
42
+
43
+ self.log_clinvar = log_clinvar
44
+ self.log_dir = log_dir
45
+ if log_clinvar:
46
+ self.mut_info_list = []
47
+
48
+ super().__init__(task="lm", **kwargs)
49
+
50
+ def initialize_metrics(self, stage):
51
+ return {f"{stage}_spearman": torchmetrics.SpearmanCorrCoef()}
52
+
53
+ def forward(self, wild_type, seqs, mut_info, structure_content, structure_type, plddt, struc_seq):
54
+ if self.use_bias_feature and getattr(self, "coords", None) is None:
55
+ structure_type = "cif" if structure_type == "mmcif" else structure_type
56
+ tmp_path = f"EsmMutationModel_{self.global_rank}.{structure_type}"
57
+ with open(tmp_path, "w") as f:
58
+ f.write(structure_content)
59
+
60
+ self.coords = parse_structure(tmp_path, ["A"])["A"]["coords"]
61
+ os.remove(tmp_path)
62
+
63
+ ins_seqs = []
64
+ ori_seqs = []
65
+ mut_data = []
66
+
67
+ # The running bottleneck is two forward passes of the model to deal with insertion
68
+ # Therefore we only forward pass the model twice for sequences with insertion
69
+ ins_dict = {}
70
+
71
+ for i, (seq, info) in enumerate(zip(seqs, mut_info)):
72
+ # We adopt the same strategy for esm2 model as in esm2 inverse folding paper
73
+ ori_seq = [aa for aa in wild_type]
74
+ ins_seq = copy.deepcopy(ori_seq)
75
+ tmp_data = []
76
+ ins_num = 0
77
+
78
+ # To indicate whether there is insertion in the sequence
79
+ flag = False
80
+
81
+ for single in info.split(":"):
82
+ # Mask the amino acid where the mutation happens
83
+ # -1 is added because the index starts from 1 and we need to convert it to 0
84
+ if single[0] in aa_set:
85
+ ori_aa, pos, mut_aa = single[0], int(single[1:-1]), single[-1]
86
+ ori_seq[pos - ins_num - 1] = self.tokenizer.mask_token
87
+ ins_seq[pos - 1] = self.tokenizer.mask_token
88
+
89
+ tmp_data.append((ori_aa, pos - ins_num, mut_aa, pos))
90
+
91
+ # For insertion
92
+ else:
93
+ ins_dict[i] = len(ins_dict)
94
+ flag = True
95
+
96
+ ins_num += 1
97
+ ins_pos = int(single[:-1])
98
+ ins_seq = ins_seq[:ins_pos - 1] + [self.tokenizer.mask_token] + ins_seq[ins_pos - 1:]
99
+
100
+ if flag:
101
+ ins_seqs.append(" ".join(ins_seq))
102
+
103
+ ori_seqs.append(" ".join(ori_seq))
104
+ mut_data.append(tmp_data)
105
+
106
+ device = self.device
107
+
108
+ if len(ins_seqs) > 0:
109
+ ins_inputs = self.tokenizer.batch_encode_plus(ins_seqs, return_tensors="pt", padding=True)
110
+ ins_inputs = {k: v.to(device) for k, v in ins_inputs.items()}
111
+ if self.use_bias_feature:
112
+ coords = [copy.deepcopy(self.coords) for _ in range(len(seqs))]
113
+ self.add_bias_feature(ins_inputs, coords)
114
+
115
+ ins_outputs = self.model(**ins_inputs)
116
+ ins_probs = ins_outputs['logits'].softmax(dim=-1)
117
+
118
+ ori_inputs = self.tokenizer.batch_encode_plus(ori_seqs, return_tensors="pt", padding=True)
119
+ ori_inputs = {k: v.to(device) for k, v in ori_inputs.items()}
120
+ if self.use_bias_feature:
121
+ coords = [copy.deepcopy(self.coords) for _ in range(len(seqs))]
122
+ self.add_bias_feature(ori_inputs, coords)
123
+
124
+ ori_outputs = self.model(**ori_inputs)
125
+ ori_probs = ori_outputs['logits'].softmax(dim=-1)
126
+
127
+ if self.MSA_log_path is not None:
128
+ aa2id = {"A": 5, "C": 6, "D": 7, "E": 8, "F": 9, "G": 10, "H": 11, "I": 12, "K": 13, "L": 14, "M": 15,
129
+ "N": 16, "P": 17, "Q": 18, "R": 19, "S": 20, "T": 21, "V": 22, "W": 23, "Y": 24}
130
+ DMS_id = os.path.basename(self.trainer.datamodule.test_lmdb)
131
+ MSA_info = self.MSA_info_dict[DMS_id]
132
+ MSA_log_prior = MSA_info["MSA_log_prior"].to(device)
133
+ st, ed = MSA_info["MSA_start"], MSA_info["MSA_end"]
134
+
135
+ preds = []
136
+ for i, data_list in enumerate(mut_data):
137
+ pred = 0
138
+ for data in data_list:
139
+ ori_aa, ori_pos, mut_aa, ins_pos = data
140
+
141
+ ori_prob = ori_probs[i, ori_pos, self.tokenizer.convert_tokens_to_ids(ori_aa)]
142
+
143
+ if i in ins_dict:
144
+ mut_prob = ins_probs[ins_dict[i], ins_pos, self.tokenizer.convert_tokens_to_ids(mut_aa)]
145
+ else:
146
+ mut_prob = ori_probs[i, ins_pos, self.tokenizer.convert_tokens_to_ids(mut_aa)]
147
+
148
+ # Add MSA info if available
149
+ if self.MSA_log_path is not None and st <= ori_pos -1 < ed:
150
+ ori_msa_prob = MSA_log_prior[ori_pos - 1 - st, aa2id[ori_aa]]
151
+ mut_msa_prob = MSA_log_prior[ori_pos - 1 - st, aa2id[mut_aa]]
152
+ pred += 0.4 * torch.log(mut_prob / ori_prob) + 0.6 * (mut_msa_prob - ori_msa_prob)
153
+
154
+ else:
155
+ # compute zero-shot score
156
+ pred += torch.log(mut_prob / ori_prob)
157
+
158
+ preds.append(pred)
159
+
160
+ if self.log_clinvar:
161
+ self.mut_info_list.append((mut_info, -torch.tensor(preds)))
162
+
163
+ return torch.tensor(preds).to(ori_probs)
164
+
165
+ def loss_func(self, stage, outputs, labels):
166
+ fitness = labels['labels']
167
+ self.test_spearman(outputs, fitness)
168
+
169
+ def on_test_epoch_end(self):
170
+ spearman = self.test_spearman.compute()
171
+ self.reset_metrics("test")
172
+ self.log("spearman", spearman)
173
+ if self.use_bias_feature:
174
+ self.coords = None
175
+
176
+ if self.log_clinvar:
177
+ # Get dataset name
178
+ name = os.path.basename(self.trainer.datamodule.test_lmdb)
179
+ device_rank = dist.get_rank()
180
+ log_path = f"{self.log_dir}/{name}_{device_rank}.csv"
181
+ with open(log_path, "w") as w:
182
+ w.write("protein_name,mutations,evol_indices\n")
183
+
184
+ for mut_info, preds in self.mut_info_list:
185
+ for mut, pred in zip(mut_info, preds):
186
+ w.write(f"{name},{mut},{pred}\n")
187
+
188
+ self.mut_info_list = []
189
+
190
+ def predict_mut(self, seq: str, mut_info: str) -> float:
191
+ """
192
+ Predict the mutational effect of a given mutation
193
+ Args:
194
+ seq: The wild type sequence
195
+
196
+ mut_info: The mutation information in the format of "A123B", where A is the original amino acid, 123 is the
197
+ position and B is the mutated amino acid. If multiple mutations are provided, they should be
198
+ separated by colon, e.g. "A123B:C124D".
199
+
200
+ Returns:
201
+ The predicted mutational effect
202
+ """
203
+ tokens = self.tokenizer.tokenize(seq)
204
+ for single in mut_info.split(":"):
205
+ pos = int(single[1:-1])
206
+ tokens[pos - 1] = self.tokenizer.mask_token
207
+
208
+ mask_seq = " ".join(tokens)
209
+ inputs = self.tokenizer(mask_seq, return_tensors="pt")
210
+ inputs = {k: v.to(self.device) for k, v in inputs.items()}
211
+
212
+ with torch.no_grad():
213
+ outputs = self.model(**inputs)
214
+ logits = outputs.logits
215
+ probs = logits.softmax(dim=-1)
216
+
217
+ score = 0
218
+ for single in mut_info.split(":"):
219
+ ori_aa, pos, mut_aa = single[0], int(single[1:-1]), single[-1]
220
+ ori_prob = probs[0, pos, self.tokenizer.convert_tokens_to_ids(ori_aa)]
221
+ mut_prob = probs[0, pos, self.tokenizer.convert_tokens_to_ids(mut_aa)]
222
+
223
+ score += torch.log(mut_prob / ori_prob)
224
+
225
+ return score
226
+
227
+ def predict_pos_mut(self, seq: str, pos: int) -> dict:
228
+ """
229
+ Predict the mutational effect of mutations at a given position
230
+ Args:
231
+ seq: The wild type sequence
232
+
233
+ pos: The position of the mutation
234
+
235
+ Returns:
236
+ The predicted mutational effect
237
+ """
238
+ tokens = self.tokenizer.tokenize(seq)
239
+ ori_aa = tokens[pos - 1][0]
240
+ tokens[pos - 1] = self.tokenizer.mask_token
241
+
242
+ mask_seq = " ".join(tokens)
243
+ inputs = self.tokenizer(mask_seq, return_tensors="pt")
244
+ inputs = {k: v.to(self.device) for k, v in inputs.items()}
245
+
246
+ with torch.no_grad():
247
+ outputs = self.model(**inputs)
248
+ logits = outputs.logits
249
+ probs = logits.softmax(dim=-1)[0, pos]
250
+
251
+ scores = {}
252
+ ori_prob = probs[self.tokenizer.convert_tokens_to_ids(ori_aa)]
253
+ for mut_aa in aa_list:
254
+ mut_prob = probs[self.tokenizer.convert_tokens_to_ids(mut_aa)]
255
+ score = torch.log(mut_prob / ori_prob).item()
256
+ scores[f"{ori_aa}{pos}{mut_aa}"] = score
257
+
258
+ return scores
259
+
260
+ def predict_pos_prob(self, seq: str, pos: int) -> dict:
261
+ """
262
+ Predict the probability of all amino acids at a given position
263
+ Args:
264
+ seq: The wild type sequence
265
+
266
+ pos: The position of the mutation
267
+
268
+ Returns:
269
+ The predicted probability of all amino acids
270
+ """
271
+ tokens = self.tokenizer.tokenize(seq)
272
+ tokens[pos - 1] = self.tokenizer.mask_token
273
+
274
+ mask_seq = " ".join(tokens)
275
+ inputs = self.tokenizer(mask_seq, return_tensors="pt")
276
+ inputs = {k: v.to(self.device) for k, v in inputs.items()}
277
+
278
+ with torch.no_grad():
279
+ outputs = self.model(**inputs)
280
+ logits = outputs.logits
281
+ probs = logits.softmax(dim=-1)[0, pos]
282
+
283
+ scores = {}
284
+ for aa in aa_list:
285
+ prob = probs[self.tokenizer.convert_tokens_to_ids(aa)]
286
+ scores[aa] = prob.item()
287
+
288
+ return scores
model/saprot/saprot_annotation_model.py ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torchmetrics
2
+ import torch
3
+
4
+
5
+ from torch.nn.functional import binary_cross_entropy_with_logits
6
+ from scripts.utils.metrics import count_f1_max
7
+ from ..model_interface import register_model
8
+ from .base import SaprotBaseModel
9
+
10
+
11
+ @register_model
12
+ class SaprotAnnotationModel(SaprotBaseModel):
13
+ def __init__(self, anno_type: str, **kwargs):
14
+ """
15
+ Args:
16
+ anno_type: one of EC, GO, GO_MF, GO_CC
17
+ **kwargs: other parameters for SaprotBaseModel
18
+ """
19
+ label2num = {"EC": 585, "GO_BP": 1943, "GO_MF": 489, "GO_CC": 320}
20
+ self.num_labels = label2num[anno_type]
21
+ super().__init__(task="classification", **kwargs)
22
+
23
+ def initialize_metrics(self, stage):
24
+ return {f"{stage}_aupr": torchmetrics.AveragePrecision(pos_label=1, average='micro')}
25
+
26
+ def forward(self, inputs, coords=None):
27
+ if coords is not None:
28
+ inputs = self.add_bias_feature(inputs, coords)
29
+
30
+ # If backbone is frozen, the embedding will be the average of all residues
31
+ if self.freeze_backbone:
32
+ repr = torch.stack(self.get_hidden_states(inputs, reduction="mean"))
33
+ x = self.model.classifier.dropout(repr)
34
+ x = self.model.classifier.dense(x)
35
+ x = torch.tanh(x)
36
+ x = self.model.classifier.dropout(x)
37
+ logits = self.model.classifier.out_proj(x)
38
+
39
+ else:
40
+ logits = self.model(**inputs).logits
41
+
42
+ return logits
43
+
44
+ def loss_func(self, stage, logits, labels):
45
+ label = labels['labels'].to(logits)
46
+ # add weight to balance positive and negative samples
47
+ # num_pos = label.sum()
48
+ # pos_weight = (label.numel() - num_pos) / num_pos
49
+
50
+ loss = binary_cross_entropy_with_logits(logits, label.float())
51
+ aupr = getattr(self, f"{stage}_aupr")(logits.sigmoid().detach(), label)
52
+
53
+ if stage == "train":
54
+ log_dict = {"train_loss": loss,
55
+ # "train_aupr": aupr
56
+ }
57
+ self.log_info(log_dict)
58
+ self.reset_metrics("train")
59
+
60
+ return loss
61
+
62
+ def test_epoch_end(self, outputs):
63
+ preds = self.all_gather(torch.cat(self.test_aupr.preds, dim=-1)).view(-1, self.num_labels)
64
+ target = self.all_gather(torch.cat(self.test_aupr.target, dim=-1)).long().view(-1, self.num_labels)
65
+ fmax = count_f1_max(preds, target)
66
+
67
+ log_dict = {"test_f1_max": fmax,
68
+ "test_loss": torch.cat(self.all_gather(outputs), dim=-1).mean(),
69
+ # "test_aupr": self.test_aupr.compute()
70
+ }
71
+ self.log_info(log_dict)
72
+ print(log_dict)
73
+ self.reset_metrics("test")
74
+
75
+ def validation_epoch_end(self, outputs):
76
+ aupr = self.valid_aupr.compute()
77
+
78
+ preds = self.all_gather(torch.cat(self.valid_aupr.preds, dim=-1)).view(-1, self.num_labels)
79
+ target = self.all_gather(torch.cat(self.valid_aupr.target, dim=-1)).long().view(-1, self.num_labels)
80
+ f1_max = count_f1_max(preds, target)
81
+
82
+ log_dict = {"valid_f1_max": f1_max,
83
+ "valid_loss": torch.cat(self.all_gather(outputs), dim=-1).mean(),
84
+ # "valid_aupr": aupr
85
+ }
86
+
87
+ self.log_info(log_dict)
88
+ self.reset_metrics("valid")
89
+ self.check_save_condition(log_dict["valid_f1_max"], mode="max")
model/saprot/saprot_classification_model.py ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torchmetrics
2
+ import torch
3
+
4
+ from torch.nn.functional import cross_entropy
5
+ from ..model_interface import register_model
6
+ from .base import SaprotBaseModel
7
+
8
+
9
+ @register_model
10
+ class SaprotClassificationModel(SaprotBaseModel):
11
+ def __init__(self, num_labels: int, **kwargs):
12
+ """
13
+ Args:
14
+ num_labels: number of labels
15
+ **kwargs: other arguments for SaprotBaseModel
16
+ """
17
+ self.num_labels = num_labels
18
+ super().__init__(task="classification", **kwargs)
19
+
20
+ def initialize_metrics(self, stage):
21
+ return {f"{stage}_acc": torchmetrics.Accuracy()}
22
+
23
+ def forward(self, inputs, coords=None):
24
+ if coords is not None:
25
+ inputs = self.add_bias_feature(inputs, coords)
26
+
27
+ # If backbone is frozen, the embedding will be the average of all residues
28
+ if self.freeze_backbone:
29
+ repr = torch.stack(self.get_hidden_states(inputs, reduction="mean"))
30
+ x = self.model.classifier.dropout(repr)
31
+ x = self.model.classifier.dense(x)
32
+ x = torch.tanh(x)
33
+ x = self.model.classifier.dropout(x)
34
+ logits = self.model.classifier.out_proj(x)
35
+
36
+ else:
37
+ logits = self.model(**inputs).logits
38
+
39
+ return logits
40
+
41
+ def loss_func(self, stage, logits, labels):
42
+ label = labels['labels']
43
+ loss = cross_entropy(logits, label)
44
+
45
+ # Update metrics
46
+ for metric in self.metrics[stage].values():
47
+ metric.update(logits.detach(), label)
48
+
49
+ if stage == "train":
50
+ log_dict = self.get_log_dict("train")
51
+ log_dict["train_loss"] = loss
52
+ self.log_info(log_dict)
53
+
54
+ # Reset train metrics
55
+ self.reset_metrics("train")
56
+
57
+ return loss
58
+
59
+ def test_epoch_end(self, outputs):
60
+ log_dict = self.get_log_dict("test")
61
+ log_dict["test_loss"] = torch.cat(self.all_gather(outputs), dim=-1).mean()
62
+
63
+ print(log_dict)
64
+ self.log_info(log_dict)
65
+
66
+ self.reset_metrics("test")
67
+
68
+ def validation_epoch_end(self, outputs):
69
+ log_dict = self.get_log_dict("valid")
70
+ log_dict["valid_loss"] = torch.cat(self.all_gather(outputs), dim=-1).mean()
71
+
72
+ self.log_info(log_dict)
73
+ self.reset_metrics("valid")
74
+ self.check_save_condition(log_dict["valid_acc"], mode="max")
model/saprot/saprot_contact_model.py ADDED
@@ -0,0 +1,145 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torchmetrics
2
+ import torch
3
+ import numpy as np
4
+ import math
5
+
6
+ from torch.nn import Linear, ReLU
7
+ from torch.nn.functional import cross_entropy
8
+ from ..model_interface import register_model
9
+ from .base import SaprotBaseModel
10
+
11
+
12
+ @register_model
13
+ class SaprotContactModel(SaprotBaseModel):
14
+ def __init__(self, **kwargs):
15
+ """
16
+ Args:
17
+ num_labels: number of labels
18
+ **kwargs: other arguments for SaprotBaseModel
19
+ """
20
+ super().__init__(task="base", **kwargs)
21
+
22
+ def initialize_model(self):
23
+ super().initialize_model()
24
+
25
+ # hidden_size = self.model.config.hidden_size * 2
26
+ hidden_size = self.model.config.num_attention_heads
27
+ # hidden_size = self.model.config.num_hidden_layers * self.model.config.num_attention_heads
28
+
29
+ classifier = torch.nn.Sequential(
30
+ # Linear(hidden_size, hidden_size),
31
+ # ReLU(),
32
+ Linear(hidden_size, 2)
33
+ )
34
+
35
+ # Freeze all parameters except classifier
36
+ for param in self.model.parameters():
37
+ param.requires_grad = False
38
+
39
+ setattr(self.model, "classifier", classifier)
40
+
41
+ def initialize_metrics(self, stage):
42
+ metric_dict = {}
43
+ for length in ["P@L", "P@L/2", "P@L/5"]:
44
+ for range in ["short_range", "medium_range", "long_range"]:
45
+ metric_dict[f"{stage}_{range}_{length}"] = torchmetrics.Accuracy(ignore_index=-1)
46
+
47
+ return metric_dict
48
+
49
+ def forward(self, inputs):
50
+ # inputs["output_hidden_states"] = True
51
+ # outputs = self.model.esm(**inputs)
52
+ #
53
+ # hidden_states = outputs["hidden_states"][-1]
54
+ # prod = hidden_states[:, :, None, :] * hidden_states[:, None, :, :]
55
+ # diff = hidden_states[:, :, None, :] - hidden_states[:, None, :, :]
56
+ # pairwise_features = torch.cat((prod, diff), -1)
57
+ # pairwise_features = (pairwise_features + pairwise_features.transpose(1, 2)) / 2
58
+ #
59
+ # logits = self.model.classifier(pairwise_features)
60
+ # logits = logits[:, 1: -1, 1: -1].contiguous()
61
+ #
62
+ # return logits
63
+
64
+ inputs["output_attentions"] = True
65
+ outputs = self.model.esm(**inputs)
66
+
67
+ attention_maps = torch.cat(outputs["attentions"][-1:], 1).permute(0, 2, 3, 1)
68
+ # attention_maps = torch.cat(outputs["attentions"], 1).permute(0, 2, 3, 1)
69
+ attention_maps = (attention_maps + attention_maps.transpose(1, 2)) / 2
70
+ logits = self.model.classifier(attention_maps)
71
+ logits = logits[:, 1: -1, 1: -1].contiguous()
72
+
73
+ return logits
74
+
75
+ def loss_func(self, stage, logits, labels):
76
+ lengths = labels["lengths"]
77
+ targets = labels["targets"].to(logits.device)
78
+ loss = cross_entropy(logits.view(-1, logits.size(-1)), targets.flatten(), ignore_index=-1)
79
+
80
+ # Iterate through all proteins and count accuracy
81
+ length_dict = {"P@L": 1, "P@L/2": 2, "P@L/5": 5}
82
+ range_dict = ["short_range", "medium_range", "long_range"]
83
+ for pred_map, label_map, L in zip(logits.detach(), targets, lengths):
84
+ x_inds, y_inds = np.indices(label_map.shape)
85
+ for r in range_dict:
86
+ if r == "short_range":
87
+ mask = (np.abs(y_inds - x_inds) < 6) | (np.abs(y_inds - x_inds) > 11)
88
+
89
+ elif r == "medium_range":
90
+ mask = (np.abs(y_inds - x_inds) < 12) | (np.abs(y_inds - x_inds) > 23)
91
+
92
+ else:
93
+ mask = np.abs(y_inds - x_inds) < 24
94
+
95
+ mask = torch.from_numpy(mask)
96
+ copy_label_map = label_map.clone()
97
+ copy_label_map[mask] = -1
98
+
99
+ # Mask the lower triangle
100
+ mask = torch.triu(torch.ones_like(copy_label_map), diagonal=1)
101
+ copy_label_map[mask == 0] = -1
102
+
103
+ selector = copy_label_map != -1
104
+ preds = pred_map[selector].float()
105
+ labels = copy_label_map[selector]
106
+
107
+ probs = preds.softmax(dim=-1)[:, 1]
108
+ for k, v in length_dict.items():
109
+ l = min(math.ceil(L / v), (labels == 1).sum().item())
110
+
111
+ top_inds = torch.argsort(probs, descending=True)[:l]
112
+ top_labels = labels[top_inds]
113
+
114
+ if top_labels.numel() == 0:
115
+ continue
116
+
117
+ metric = f"{stage}_{r}_{k}"
118
+ self.metrics[stage][metric].update(top_labels, torch.ones_like(top_labels))
119
+
120
+ if stage == "train":
121
+ # log_dict = self.get_log_dict("train")
122
+ # log_dict["train_loss"] = loss
123
+ # self.log_info(log_dict)
124
+
125
+ # Reset train metrics
126
+ self.reset_metrics("train")
127
+
128
+ return loss
129
+
130
+ def test_epoch_end(self, outputs):
131
+ log_dict = self.get_log_dict("test")
132
+ log_dict["test_loss"] = torch.cat(self.all_gather(outputs), dim=-1).mean()
133
+
134
+ print(log_dict)
135
+ self.log_info(log_dict)
136
+
137
+ self.reset_metrics("test")
138
+
139
+ def validation_epoch_end(self, outputs):
140
+ log_dict = self.get_log_dict("valid")
141
+ log_dict["valid_loss"] = torch.cat(self.all_gather(outputs), dim=-1).mean()
142
+
143
+ self.log_info(log_dict)
144
+ self.reset_metrics("valid")
145
+ self.check_save_condition(log_dict["valid_medium_range_P@L/5"], mode="max")
model/saprot/saprot_foldseek_mutation_model.py ADDED
@@ -0,0 +1,383 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import copy
2
+ import os
3
+ import torch
4
+ import json
5
+ import torchmetrics
6
+ import random
7
+ import numpy as np
8
+
9
+ from scripts.utils.constants import aa_set, foldseek_struc_vocab, aa_list
10
+ from ..model_interface import register_model
11
+ from .base import SaprotBaseModel
12
+
13
+
14
+ @register_model
15
+ class SaprotFoldseekMutationModel(SaprotBaseModel):
16
+ def __init__(self,
17
+ foldseek_path: str,
18
+ plddt_threshold: float = 0.,
19
+ mask_rate: float = None,
20
+ substitute_rate: float = None,
21
+ MSA_log_path: str = None,
22
+ log_clinvar: bool = False,
23
+ log_dir: str = None,
24
+ **kwargs):
25
+ """
26
+ Args:
27
+ foldseek_path: Foldseek binary file path
28
+
29
+ plddt_threshold: The threshold for plddt to determine whether a structure token should be masked
30
+
31
+ mask_rate: If not None, the model will randomly mask structure tokens with this rate
32
+
33
+ substitute_rate: If not None, the model will randomly substitute structure tokens with this rate
34
+
35
+ MSA_log_path: If not None, the model will load MSA log from this path (follow Tranception paper)
36
+
37
+ log_clinvar: If True, the model will log the predicted evolutionary indices for ClinVar variants
38
+
39
+ log_dir: If log_clinvar is True, the model will save the predicted evolutionary indices for ClinVar variants
40
+
41
+ **kwargs: Other arguments for SaprotBaseModel
42
+ """
43
+ self.foldseek_path = foldseek_path
44
+ self.plddt_threshold = plddt_threshold
45
+ self.mask_rate = mask_rate
46
+ self.substitute_rate = substitute_rate
47
+
48
+ self.MSA_log_path = MSA_log_path
49
+ self.MSA_info_dict = {}
50
+ if MSA_log_path:
51
+ with open(MSA_log_path, "r") as r:
52
+ for line in r:
53
+ data = json.loads(line)
54
+ data["MSA_log_prior"] = torch.tensor(data["MSA_log_prior"])
55
+ self.MSA_info_dict[data["DMS_id"]] = data
56
+
57
+ self.log_clinvar = log_clinvar
58
+ self.log_dir = log_dir
59
+ if log_clinvar:
60
+ self.mut_info_list = []
61
+
62
+ super().__init__(task="lm", **kwargs)
63
+
64
+ def initialize_metrics(self, stage):
65
+ return {f"{stage}_spearman": torchmetrics.SpearmanCorrCoef()}
66
+
67
+ def get_struc_seq(self, structure_content, structure_type, plddt):
68
+ structure_type = "cif" if structure_type == "mmcif" else structure_type
69
+
70
+ # Sample a random rank to avoid file conflict
71
+ rank = random.randint(0, 1000000)
72
+
73
+ tmp_pdb_path = f"EsmFoldseekMutationModel_{self.global_rank}_{rank}.{structure_type}"
74
+ tmp_save_path = f"EsmFoldseekMutationModel_{self.global_rank}_{rank}.tsv"
75
+
76
+ # Save structure content to temporary file
77
+ with open(tmp_pdb_path, "w") as w:
78
+ w.write(structure_content)
79
+
80
+ # Get foldseek structural sequecne
81
+ cmd = f"{self.foldseek_path} structureto3didescriptor -v 0 --threads 1 {tmp_pdb_path} {tmp_save_path}"
82
+ os.system(cmd)
83
+
84
+ with open(tmp_save_path, "r") as r:
85
+ line = r.readline()
86
+ struc_seq = line.split("\t")[2]
87
+
88
+ if plddt is not None:
89
+ plddts = np.array(plddt)
90
+
91
+ # Mask regions with plddt < threshold
92
+ indices = np.where(plddts < self.plddt_threshold)[0]
93
+ np_seq = np.array(list(struc_seq))
94
+ np_seq[indices] = "#"
95
+ struc_seq = "".join(np_seq)
96
+
97
+ if self.mask_rate is not None:
98
+ # Mask random structure tokens
99
+ indices = np.random.choice(len(struc_seq), int(len(struc_seq) * self.mask_rate), replace=False)
100
+ np_seq = np.array(list(struc_seq))
101
+ np_seq[indices] = "#"
102
+ struc_seq = "".join(np_seq)
103
+
104
+ if self.substitute_rate is not None:
105
+ # Substitute random structure tokens
106
+ indices = np.random.choice(len(struc_seq), int(len(struc_seq) * self.substitute_rate), replace=False)
107
+ np_seq = np.array(list(struc_seq))
108
+ np_seq[indices] = np.random.choice(list(foldseek_struc_vocab), len(indices))
109
+ struc_seq = "".join(np_seq)
110
+
111
+ os.remove(tmp_pdb_path)
112
+ os.remove(tmp_save_path)
113
+ os.remove(tmp_save_path + ".dbtype")
114
+ return struc_seq
115
+
116
+ def forward(self, wild_type, seqs, mut_info, structure_content, structure_type, plddt):
117
+ device = self.device
118
+
119
+ if getattr(self, "struc_seq", None) is None:
120
+ self.struc_seq = self.get_struc_seq(structure_content, structure_type, plddt)
121
+
122
+ ins_seqs = []
123
+ ori_seqs = []
124
+ mut_data = []
125
+
126
+ # The running bottleneck is two forward passes of the model to deal with insertion
127
+ # Therefore we only forward pass the model twice for sequences with insertion
128
+ ins_dict = {}
129
+
130
+ for i, (seq, info) in enumerate(zip(seqs, mut_info)):
131
+ # We adopt the same strategy for esm2 model as in esm2 inverse folding paper
132
+ ori_seq = [a+b.lower() for a, b in zip(wild_type, self.struc_seq)]
133
+ ins_seq = copy.deepcopy(ori_seq)
134
+ tmp_data = []
135
+ ins_num = 0
136
+
137
+ # To indicate whether there is insertion in the sequence
138
+ flag = False
139
+
140
+ for single in info.split(":"):
141
+ # Mask the amino acid where the mutation happens
142
+ # -1 is added because the index starts from 1 and we need to convert it to 0
143
+ if single[0] in aa_set:
144
+ ori_aa, pos, mut_aa = single[0], int(single[1:-1]), single[-1]
145
+
146
+ ori_seq[pos-ins_num-1] = "#" + ori_seq[pos-ins_num-1][-1]
147
+ ins_seq[pos-1] = "#" + ins_seq[pos-1][-1]
148
+ # ori_seq[pos - ins_num - 1] = self.tokenizer.mask_token
149
+ # ins_seq[pos - 1] = self.tokenizer.mask_token
150
+
151
+ tmp_data.append((ori_aa, pos-ins_num, mut_aa, pos))
152
+
153
+ # For insertion
154
+ else:
155
+ ins_dict[i] = len(ins_dict)
156
+ flag = True
157
+
158
+ ins_num += 1
159
+ ins_pos = int(single[:-1])
160
+ ins_seq = ins_seq[:ins_pos-1] + ["##"] + ins_seq[ins_pos-1:]
161
+ # ins_seq = ins_seq[:ins_pos-1] + [self.tokenizer.mask_token] + ins_seq[ins_pos-1:]
162
+
163
+ if flag:
164
+ ins_seqs.append(" ".join(ins_seq))
165
+
166
+ ori_seqs.append(" ".join(ori_seq))
167
+ mut_data.append(tmp_data)
168
+
169
+ if len(ins_seqs) > 0:
170
+ ins_inputs = self.tokenizer.batch_encode_plus(ins_seqs, return_tensors="pt", padding=True)
171
+ ins_inputs = {k: v.to(device) for k, v in ins_inputs.items()}
172
+ ins_outputs = self.model(**ins_inputs)
173
+ ins_probs = ins_outputs['logits'].softmax(dim=-1)
174
+
175
+ ori_inputs = self.tokenizer.batch_encode_plus(ori_seqs, return_tensors="pt", padding=True)
176
+ ori_inputs = {k: v.to(device) for k, v in ori_inputs.items()}
177
+ ori_outputs = self.model(**ori_inputs)
178
+ ori_probs = ori_outputs['logits'].softmax(dim=-1)
179
+
180
+ if self.MSA_log_path is not None:
181
+ aa2id = {"A": 5, "C": 6, "D": 7, "E": 8, "F": 9, "G": 10, "H": 11, "I": 12, "K": 13, "L": 14, "M": 15,
182
+ "N": 16, "P": 17, "Q": 18, "R": 19, "S": 20, "T": 21, "V": 22, "W": 23, "Y": 24}
183
+ DMS_id = os.path.basename(self.trainer.datamodule.test_lmdb)
184
+ MSA_info = self.MSA_info_dict[DMS_id]
185
+ MSA_log_prior = MSA_info["MSA_log_prior"].to(device)
186
+ st, ed = MSA_info["MSA_start"], MSA_info["MSA_end"]
187
+
188
+ preds = []
189
+ for i, data_list in enumerate(mut_data):
190
+ pred = 0
191
+ for data in data_list:
192
+ ori_aa, ori_pos, mut_aa, ins_pos = data
193
+
194
+ ori_st = self.tokenizer.get_vocab()[ori_aa + foldseek_struc_vocab[0]]
195
+ mut_st = self.tokenizer.get_vocab()[mut_aa + foldseek_struc_vocab[0]]
196
+
197
+ ori_prob = ori_probs[i, ori_pos, ori_st: ori_st + len(foldseek_struc_vocab)].sum()
198
+ # ori_sturc_aa = ori_aa + self.struc_seq[ori_pos-1].lower()
199
+ # ori_struc_id = self.tokenizer.get_vocab()[ori_sturc_aa]
200
+ # ori_prob = ori_probs[i, ori_pos, ori_struc_id]
201
+
202
+ if i in ins_dict:
203
+ mut_prob = ins_probs[ins_dict[i], ins_pos, mut_st: mut_st + len(foldseek_struc_vocab)].sum()
204
+ # mut_prob = ins_probs[ins_dict[i], ins_pos, mut_st: mut_st + len(foldseek_struc_vocab)].max()
205
+ else:
206
+ mut_prob = ori_probs[i, ori_pos, mut_st: mut_st + len(foldseek_struc_vocab)].sum()
207
+ # mut_prob = ori_probs[i, ins_pos, mut_st: mut_st + len(foldseek_struc_vocab)].max()
208
+ # mut_struc_aa = mut_aa + self.struc_seq[ori_pos-1].lower()
209
+ # mut_struc_id = self.tokenizer.get_vocab()[mut_struc_aa]
210
+ # mut_prob = ori_probs[i, ori_pos, mut_struc_id]
211
+
212
+ # print(ori_prob, mut_prob)
213
+ # struc_logits = []
214
+ # ori_idx, mut_idx = None, None
215
+ # for aa in aa_list:
216
+ # struct_aa = aa + self.struc_seq[ori_pos - 1].lower()
217
+ # aa_logits = ori_outputs['logits'][i, ori_pos, self.tokenizer.get_vocab()[struct_aa]]
218
+ # struc_logits.append(aa_logits)
219
+ #
220
+ # if aa == ori_aa:
221
+ # ori_idx = len(struc_logits) - 1
222
+ #
223
+ # if aa == mut_aa:
224
+ # mut_idx = len(struc_logits) - 1
225
+ #
226
+ # struc_probs = torch.softmax(torch.tensor(struc_logits), dim=-1)
227
+ # ori_prob = struc_probs[ori_idx]
228
+ # mut_prob = struc_probs[mut_idx]
229
+
230
+ # Add MSA info if available
231
+ if self.MSA_log_path is not None and st <= ori_pos -1 < ed:
232
+ ori_msa_prob = MSA_log_prior[ori_pos - 1 - st, aa2id[ori_aa]]
233
+ mut_msa_prob = MSA_log_prior[ori_pos - 1 - st, aa2id[mut_aa]]
234
+ pred += 0.4 * torch.log(mut_prob / ori_prob) + 0.6 * (mut_msa_prob - ori_msa_prob)
235
+
236
+ # compute zero-shot score
237
+ else:
238
+ pred += torch.log(mut_prob / ori_prob)
239
+ # ori_prob = ori_probs[i, ori_pos, ori_st: ori_st + len(foldseek_struc_vocab)]
240
+ # mut_prob = ori_probs[i, ori_pos, mut_st: mut_st + len(foldseek_struc_vocab)]
241
+ # pred += torch.log(mut_prob / ori_prob).mean()
242
+
243
+ preds.append(pred)
244
+
245
+ if self.log_clinvar:
246
+ self.mut_info_list.append((mut_info, -torch.tensor(preds)))
247
+
248
+ return torch.tensor(preds).to(ori_probs)
249
+
250
+ def loss_func(self, stage, outputs, labels):
251
+ fitness = labels['labels']
252
+
253
+ # Update metrics
254
+ for metric in self.metrics[stage].values():
255
+ metric.update(outputs.detach().float(), fitness.float())
256
+
257
+ def test_epoch_end(self, outputs):
258
+ spearman = self.test_spearman.compute()
259
+ self.struc_seq = None
260
+ self.reset_metrics("test")
261
+ self.log("spearman", spearman)
262
+
263
+ if self.log_clinvar:
264
+ # Get dataset name
265
+ name = os.path.basename(self.trainer.datamodule.test_lmdb)
266
+ log_path = f"{self.log_dir}/{name}.csv"
267
+ with open(log_path, "w") as w:
268
+ w.write("protein_name,mutations,evol_indices\n")
269
+
270
+ for mut_info, preds in self.mut_info_list:
271
+ for mut, pred in zip(mut_info, preds):
272
+ w.write(f"{name},{mut},{pred}\n")
273
+
274
+ self.mut_info_list = []
275
+
276
+ def predict_mut(self, seq: str, mut_info: str) -> float:
277
+ """
278
+ Predict the mutational effect of a given mutation
279
+ Args:
280
+ seq: The wild type sequence
281
+
282
+ mut_info: The mutation information in the format of "A123B", where A is the original amino acid, 123 is the
283
+ position and B is the mutated amino acid. If multiple mutations are provided, they should be
284
+ separated by colon, e.g. "A123B:C124D".
285
+
286
+ Returns:
287
+ The predicted mutational effect
288
+ """
289
+ tokens = self.tokenizer.tokenize(seq)
290
+ for single in mut_info.split(":"):
291
+ pos = int(single[1:-1])
292
+ tokens[pos - 1] = "#" + tokens[pos - 1][-1]
293
+
294
+ mask_seq = " ".join(tokens)
295
+ inputs = self.tokenizer(mask_seq, return_tensors="pt")
296
+ inputs = {k: v.to(self.device) for k, v in inputs.items()}
297
+
298
+ with torch.no_grad():
299
+ outputs = self.model(**inputs)
300
+ logits = outputs.logits
301
+ probs = logits.softmax(dim=-1)
302
+
303
+ score = 0
304
+ for single in mut_info.split(":"):
305
+ ori_aa, pos, mut_aa = single[0], int(single[1:-1]), single[-1]
306
+ ori_st = self.tokenizer.get_vocab()[ori_aa + foldseek_struc_vocab[0]]
307
+ mut_st = self.tokenizer.get_vocab()[mut_aa + foldseek_struc_vocab[0]]
308
+
309
+ ori_prob = probs[0, pos, ori_st: ori_st + len(foldseek_struc_vocab)].sum()
310
+ mut_prob = probs[0, pos, mut_st: mut_st + len(foldseek_struc_vocab)].sum()
311
+
312
+ score += torch.log(mut_prob / ori_prob)
313
+
314
+ return score.item()
315
+
316
+ def predict_pos_mut(self, seq: str, pos: int) -> dict:
317
+ """
318
+ Predict the mutational effect of mutations at a given position
319
+ Args:
320
+ seq: The wild type sequence
321
+
322
+ pos: The position of the mutation
323
+
324
+ Returns:
325
+ The predicted mutational effect
326
+ """
327
+ tokens = self.tokenizer.tokenize(seq)
328
+ ori_aa = tokens[pos - 1][0]
329
+ tokens[pos - 1] = "#" + tokens[pos - 1][-1]
330
+
331
+ mask_seq = " ".join(tokens)
332
+ inputs = self.tokenizer(mask_seq, return_tensors="pt")
333
+ inputs = {k: v.to(self.device) for k, v in inputs.items()}
334
+
335
+ with torch.no_grad():
336
+ outputs = self.model(**inputs)
337
+ logits = outputs.logits
338
+ probs = logits.softmax(dim=-1)[0, pos]
339
+
340
+ scores = {}
341
+ ori_st = self.tokenizer.get_vocab()[ori_aa + foldseek_struc_vocab[0]]
342
+ for mut_aa in aa_list:
343
+ mut_st = self.tokenizer.get_vocab()[mut_aa + foldseek_struc_vocab[0]]
344
+
345
+ ori_prob = probs[ori_st: ori_st + len(foldseek_struc_vocab)].sum()
346
+ mut_prob = probs[mut_st: mut_st + len(foldseek_struc_vocab)].sum()
347
+
348
+ score = torch.log(mut_prob / ori_prob)
349
+ scores[f"{ori_aa}{pos}{mut_aa}"] = score.item()
350
+
351
+ return scores
352
+
353
+ def predict_pos_prob(self, seq: str, pos: int) -> dict:
354
+ """
355
+ Predict the probability of all amino acids at a given position
356
+ Args:
357
+ seq: The wild type sequence
358
+
359
+ pos: The position of the mutation
360
+
361
+ Returns:
362
+ The predicted probability of all amino acids
363
+ """
364
+ tokens = self.tokenizer.tokenize(seq)
365
+ tokens[pos - 1] = "#" + tokens[pos - 1][-1]
366
+
367
+ mask_seq = " ".join(tokens)
368
+ inputs = self.tokenizer(mask_seq, return_tensors="pt")
369
+ inputs = {k: v.to(self.device) for k, v in inputs.items()}
370
+
371
+ with torch.no_grad():
372
+ outputs = self.model(**inputs)
373
+ logits = outputs.logits
374
+ probs = logits.softmax(dim=-1)[0, pos]
375
+
376
+ scores = {}
377
+ for aa in aa_list:
378
+ st = self.tokenizer.get_vocab()[aa + foldseek_struc_vocab[0]]
379
+ prob = probs[st: st + len(foldseek_struc_vocab)].sum()
380
+
381
+ scores[aa] = prob.item()
382
+
383
+ return scores
model/saprot/saprot_if_model.py ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.distributed as dist
3
+ import torchmetrics
4
+
5
+ from torch.nn.functional import cross_entropy
6
+
7
+ from ..model_interface import register_model
8
+ from .base import SaprotBaseModel
9
+
10
+
11
+ @register_model
12
+ class SaProtIFModel(SaprotBaseModel):
13
+ """
14
+ SaProt inverse folding model.
15
+ """
16
+ def __init__(self, **kwargs):
17
+ super().__init__(task='lm', **kwargs)
18
+
19
+ def initialize_metrics(self, stage):
20
+ return {f"{stage}_acc": torchmetrics.Accuracy(ignore_index=-1)}
21
+
22
+ def forward(self, inputs, coords=None):
23
+ if coords is not None:
24
+ inputs = self.add_bias_feature(inputs, coords)
25
+
26
+ outputs = self.model(**inputs)
27
+ return outputs
28
+
29
+ def loss_func(self, stage, outputs, labels):
30
+ logits = outputs['logits']
31
+ # merge the first and second dimension of logits
32
+ logits = logits.view(-1, logits.size(-1))
33
+
34
+ # flatten labels
35
+ labels = labels['labels'].flatten().to(logits.device)
36
+
37
+ loss = cross_entropy(logits, labels, ignore_index=-1)
38
+ getattr(self, f"{stage}_acc").update(logits.detach(), labels)
39
+
40
+ if stage == 'train':
41
+ log_dict = self.get_log_dict("train")
42
+ log_dict["train_loss"] = loss
43
+ self.log_info(log_dict)
44
+ self.reset_metrics("train")
45
+
46
+ return loss
47
+
48
+ def on_train_epoch_end(self):
49
+ super().on_train_epoch_end()
50
+
51
+ # Re-sample the subset of the training data
52
+ if getattr(self.trainer.datamodule.train_dataset, "num_per_cluster", None) is not None:
53
+ self.trainer.datamodule.train_dataset.sample_subset()
54
+
55
+ def on_test_epoch_end(self):
56
+ log_dict = self.get_log_dict("test")
57
+ log_dict["test_loss"] = torch.cat(self.all_gather(self.test_outputs), dim=-1).mean()
58
+
59
+ if dist.get_rank() == 0:
60
+ print(log_dict)
61
+ self.log_info(log_dict)
62
+
63
+ self.reset_metrics("test")
64
+
65
+ def on_validation_epoch_end(self):
66
+ log_dict = self.get_log_dict("valid")
67
+ log_dict["valid_loss"] = torch.cat(self.all_gather(self.valid_outputs), dim=-1).mean()
68
+
69
+ if dist.get_rank() == 0:
70
+ print(log_dict)
71
+ self.log_info(log_dict)
72
+ self.reset_metrics("valid")
73
+
74
+ valid_acc = log_dict["valid_acc"]
75
+ self.check_save_condition(valid_acc, mode="max")
76
+
77
+ def predict(self, aa_seq: str, struc_seq: str, method: str = "argmax", num_samples: int = 1) -> str:
78
+ """
79
+ Predict all masked amino acids in the sequence.
80
+ Args:
81
+ aa_seq: Amino acid sequence (could be all masked or partially masked).
82
+
83
+ struc_seq: Foldseek sequence.
84
+
85
+ method: Prediction method. It could be either "argmax" or "multinomial". If "argmax", the most probable
86
+ amino acid will be selected. If "multinomial", the amino acid will be sampled from the multinomial
87
+ distribution.
88
+
89
+ num_samples: Number of predicted sequences. Only works when method is "multinomial".
90
+
91
+ Returns:
92
+ Predicted residue sequence.
93
+ """
94
+ assert len(aa_seq) == len(struc_seq), "The length of the amino acid sequence and the foldseek sequence must be the same."
95
+ assert method in ["argmax", "multinomial"], "The prediction method must be either 'argmax' or 'multinomial'."
96
+ if method == "argmax":
97
+ assert num_samples == 1, "The sample number must be 1 when the prediction method is 'argmax'."
98
+
99
+ sa_seq = "".join(f"{aa}{struc}" for aa, struc in zip(aa_seq, struc_seq))
100
+
101
+ # Record the index of masked amino acids
102
+ mask_indices = [i for i, aa in enumerate(aa_seq) if aa == '#']
103
+
104
+ with torch.no_grad():
105
+ inputs = self.tokenizer(sa_seq, return_tensors='pt')
106
+ inputs = {k: v.to(self.device) for k, v in inputs.items()}
107
+
108
+ outputs = self.model(**inputs)
109
+ logits = outputs.logits[0, 1: -1]
110
+ probs = torch.softmax(logits, dim=-1)
111
+
112
+ # Predict amino acids
113
+ if method == "argmax":
114
+ batch_preds = probs.argmax(dim=-1).unsqueeze(0)
115
+ else:
116
+ batch_preds = torch.multinomial(probs, num_samples, replacement=True).permute(1, 0)
117
+
118
+ pred_aa_seqs = []
119
+ for preds in batch_preds:
120
+ masked_preds = preds[mask_indices]
121
+ pred_tokens = self.tokenizer.convert_ids_to_tokens(masked_preds)
122
+
123
+ tokens = list(aa_seq)
124
+ for i, pred_token in zip(mask_indices, pred_tokens):
125
+ tokens[i] = pred_token[0]
126
+
127
+ pred_aa_seq = "".join(tokens)
128
+ pred_aa_seqs.append(pred_aa_seq)
129
+
130
+ return pred_aa_seqs
model/saprot/saprot_lm_model.py ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import os
3
+ import torchmetrics
4
+
5
+ from torch.nn.functional import cross_entropy
6
+ from ..model_interface import register_model
7
+ from .base import SaprotBaseModel
8
+
9
+
10
+ @register_model
11
+ class SaprotLMModel(SaprotBaseModel):
12
+ def __init__(self, **kwargs):
13
+ super().__init__(task='lm', **kwargs)
14
+
15
+ def initialize_metrics(self, stage):
16
+ return {f"{stage}_acc": torchmetrics.Accuracy(ignore_index=-1)}
17
+
18
+ def forward(self, inputs, coords=None):
19
+ if coords is not None:
20
+ inputs = self.add_bias_feature(inputs, coords)
21
+
22
+ outputs = self.model(**inputs)
23
+
24
+ # Get hidden representations
25
+ if "output_hidden_states" in inputs and inputs["output_hidden_states"]:
26
+ input_ids = inputs["input_ids"]
27
+ ends = (input_ids == 2).int()
28
+ indices = ends.argmax(dim=-1)
29
+ repr_list = []
30
+ hidden_states = outputs["hidden_states"][-1]
31
+ for i, idx in enumerate(indices):
32
+ repr = hidden_states[i][1:idx].mean(dim=0)
33
+ repr_list.append(repr)
34
+
35
+ reprs = torch.stack(repr_list, dim=0)
36
+ outputs["hidden_states"] = reprs
37
+
38
+ return outputs
39
+
40
+ def loss_func(self, stage, outputs, labels):
41
+ logits = outputs['logits']
42
+ # merge the first and second dimension of logits
43
+ logits = logits.view(-1, logits.size(-1))
44
+
45
+ # flatten labels
46
+ labels = labels['labels'].flatten().to(logits.device)
47
+
48
+ loss = cross_entropy(logits, labels, ignore_index=-1)
49
+ getattr(self, f"{stage}_acc").update(logits.detach(), labels)
50
+
51
+ if stage == 'train':
52
+ log_dict = self.get_log_dict("train")
53
+ log_dict["train_loss"] = loss
54
+ self.log_info(log_dict)
55
+ self.reset_metrics("train")
56
+
57
+ return loss
58
+
59
+ def test_epoch_end(self, outputs):
60
+ log_dict = self.get_log_dict("test")
61
+ log_dict["test_loss"] = torch.cat(self.all_gather(outputs), dim=-1).mean()
62
+
63
+ print(log_dict)
64
+ self.log_info(log_dict)
65
+
66
+ self.reset_metrics("test")
67
+
68
+ def validation_epoch_end(self, outputs):
69
+ log_dict = self.get_log_dict("valid")
70
+ log_dict["valid_loss"] = torch.cat(self.all_gather(outputs), dim=-1).mean()
71
+
72
+ self.log_info(log_dict)
73
+ self.reset_metrics("valid")
74
+ self.check_save_condition(log_dict["valid_loss"], mode="min")
model/saprot/saprot_ppi_model.py ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torchmetrics
2
+ import torch
3
+
4
+ from torch.nn import Linear, ReLU
5
+ from torch.nn.functional import cross_entropy
6
+ from ..model_interface import register_model
7
+ from .base import SaprotBaseModel
8
+
9
+
10
+ @register_model
11
+ class SaprotPPIModel(SaprotBaseModel):
12
+ def __init__(self, **kwargs):
13
+ """
14
+ Args:
15
+ **kwargs: other arguments for SaprotBaseModel
16
+ """
17
+ super().__init__(task="base", **kwargs)
18
+
19
+ def initialize_model(self):
20
+ super().initialize_model()
21
+
22
+ hidden_size = self.model.config.hidden_size * 2
23
+ classifier = torch.nn.Sequential(
24
+ Linear(hidden_size, hidden_size),
25
+ ReLU(),
26
+ Linear(hidden_size, 2)
27
+ )
28
+
29
+ setattr(self.model, "classifier", classifier)
30
+
31
+ def initialize_metrics(self, stage):
32
+ return {f"{stage}_acc": torchmetrics.Accuracy()}
33
+
34
+ def forward(self, inputs_1, inputs_2):
35
+ if self.freeze_backbone:
36
+ hidden_1 = torch.stack(self.get_hidden_states(inputs_1, reduction="mean"))
37
+ hidden_2 = torch.stack(self.get_hidden_states(inputs_2, reduction="mean"))
38
+ else:
39
+ hidden_1 = self.model.esm(**inputs_1)[0][:, 0, :]
40
+ hidden_2 = self.model.esm(**inputs_2)[0][:, 0, :]
41
+
42
+ hidden_concat = torch.cat([hidden_1, hidden_2], dim=-1)
43
+ return self.model.classifier(hidden_concat)
44
+
45
+ def loss_func(self, stage, logits, labels):
46
+ label = labels['labels']
47
+ loss = cross_entropy(logits, label)
48
+
49
+ # Update metrics
50
+ for metric in self.metrics[stage].values():
51
+ metric.update(logits.detach(), label)
52
+
53
+ if stage == "train":
54
+ log_dict = self.get_log_dict("train")
55
+ log_dict["train_loss"] = loss
56
+ self.log_info(log_dict)
57
+
58
+ # Reset train metrics
59
+ self.reset_metrics("train")
60
+
61
+ return loss
62
+
63
+ def test_epoch_end(self, outputs):
64
+ log_dict = self.get_log_dict("test")
65
+ log_dict["test_loss"] = torch.cat(self.all_gather(outputs), dim=-1).mean()
66
+
67
+ print(log_dict)
68
+ self.log_info(log_dict)
69
+
70
+ self.reset_metrics("test")
71
+
72
+ def validation_epoch_end(self, outputs):
73
+ log_dict = self.get_log_dict("valid")
74
+ log_dict["valid_loss"] = torch.cat(self.all_gather(outputs), dim=-1).mean()
75
+
76
+ self.log_info(log_dict)
77
+ self.reset_metrics("valid")
78
+ self.check_save_condition(log_dict["valid_acc"], mode="max")
model/saprot/saprot_regression_model.py ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch.distributed as dist
2
+ import torchmetrics
3
+ import torch
4
+
5
+ from ..model_interface import register_model
6
+ from .base import SaprotBaseModel
7
+
8
+
9
+ @register_model
10
+ class SaprotRegressionModel(SaprotBaseModel):
11
+ def __init__(self, test_result_path: str = None, **kwargs):
12
+ """
13
+ Args:
14
+ test_result_path: path to save test result
15
+ **kwargs: other arguments for SaprotBaseModel
16
+ """
17
+ self.test_result_path = test_result_path
18
+ super().__init__(task="regression", **kwargs)
19
+
20
+ def initialize_metrics(self, stage):
21
+ return {f"{stage}_loss": torchmetrics.MeanSquaredError(),
22
+ f"{stage}_spearman": torchmetrics.SpearmanCorrCoef(),
23
+ f"{stage}_R2": torchmetrics.R2Score(),
24
+ f"{stage}_pearson": torchmetrics.PearsonCorrCoef()}
25
+
26
+ def forward(self, inputs, structure_info=None):
27
+ if structure_info:
28
+ # To be implemented
29
+ raise NotImplementedError
30
+
31
+ # If backbone is frozen, the embedding will be the average of all residues
32
+ if self.freeze_backbone:
33
+ repr = torch.stack(self.get_hidden_states(inputs, reduction="mean"))
34
+ x = self.model.classifier.dropout(repr)
35
+ x = self.model.classifier.dense(x)
36
+ x = torch.tanh(x)
37
+ x = self.model.classifier.dropout(x)
38
+ logits = self.model.classifier.out_proj(x).squeeze(dim=-1)
39
+
40
+ else:
41
+ logits = self.model(**inputs).logits.squeeze(dim=-1)
42
+
43
+ return logits
44
+
45
+ def loss_func(self, stage, outputs, labels):
46
+ fitness = labels['labels'].to(outputs)
47
+ loss = torch.nn.functional.mse_loss(outputs, fitness)
48
+
49
+ # Update metrics
50
+ for metric in self.metrics[stage].values():
51
+ # Training is on half precision, but metrics expect float to compute correctly.
52
+ metric.update(outputs.detach().float(), fitness.float())
53
+
54
+ if stage == "train":
55
+ # Skip calculating metrics if the batch size is 1
56
+ if fitness.shape[0] > 1:
57
+ log_dict = self.get_log_dict("train")
58
+ self.log_info(log_dict)
59
+
60
+ # Reset train metrics
61
+ self.reset_metrics("train")
62
+
63
+ return loss
64
+
65
+ def test_epoch_end(self, outputs):
66
+ if self.test_result_path is not None:
67
+ from torchmetrics.utilities.distributed import gather_all_tensors
68
+
69
+ preds = self.test_spearman.preds
70
+ preds[-1] = preds[-1].unsqueeze(dim=0) if preds[-1].shape == () else preds[-1]
71
+ preds = torch.cat(gather_all_tensors(torch.cat(preds, dim=0)))
72
+
73
+ targets = self.test_spearman.target
74
+ targets[-1] = targets[-1].unsqueeze(dim=0) if targets[-1].shape == () else targets[-1]
75
+ targets = torch.cat(gather_all_tensors(torch.cat(targets, dim=0)))
76
+
77
+ if dist.get_rank() == 0:
78
+ with open(self.test_result_path, 'w') as w:
79
+ w.write("pred\ttarget\n")
80
+ for pred, target in zip(preds, targets):
81
+ w.write(f"{pred.item()}\t{target.item()}\n")
82
+
83
+ log_dict = self.get_log_dict("test")
84
+
85
+ print(log_dict)
86
+ self.log_info(log_dict)
87
+ self.reset_metrics("test")
88
+
89
+ def validation_epoch_end(self, outputs):
90
+ log_dict = self.get_log_dict("valid")
91
+
92
+ self.log_info(log_dict)
93
+ self.reset_metrics("valid")
94
+ self.check_save_condition(log_dict["valid_loss"], mode="min")
95
+
requirements.txt ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ torch==1.13.1
2
+ torchvision==0.14.1
3
+ torchaudio==0.13.1
4
+ pytorch-lightning==1.8.3
5
+ wandb==0.12.10
6
+ transformers==4.28.0
7
+ easydict==1.10
8
+ biopython==1.81
9
+ scipy==1.14.1
10
+ peft==0.10.0
11
+ lmdb==1.4.1
12
+ torchmetrics==0.9.3
13
+ pandas==2.1.1
14
+ fair-esm==2.0.0
15
+ numpy==1.25.2
16
+ protobuf<4,>=3.20.0
scripts/LMDB/README.md ADDED
@@ -0,0 +1 @@
 
 
1
+ # Place the datasets here
scripts/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+
scripts/bin/README.md ADDED
@@ -0,0 +1 @@
 
 
1
+ # Place the foldseek binary file here
scripts/compute_clinvar_auc.py ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import argparse
3
+ import pandas as pd
4
+ import yaml
5
+
6
+ from glob import glob
7
+ from sklearn import metrics
8
+ from easydict import EasyDict
9
+
10
+
11
+ def get_args():
12
+ parser = argparse.ArgumentParser()
13
+ parser.add_argument('-c', '--config', help="running configurations", type=str, required=True)
14
+ return parser.parse_args()
15
+
16
+
17
+ def main(args):
18
+ with open(args.config, 'r', encoding='utf-8') as r:
19
+ config = EasyDict(yaml.safe_load(r))
20
+
21
+ output_dir = "output/ClinVar"
22
+ # file_name = "esm2_t33_650M_UR50D_foldseek_plddt70_iter2900448_mask70"
23
+
24
+ label_name = "ClinVar_labels.csv"
25
+ label_path = os.path.join(output_dir, label_name)
26
+
27
+ list_variables_to_keep = ["protein_name", "mutations", "evol_indices"]
28
+ all_evol_indices = pd.concat(
29
+ [
30
+ pd.read_csv(path, low_memory=False)[list_variables_to_keep]
31
+ for path in glob(f"{config.model.kwargs.log_dir}/*.csv")
32
+ ],
33
+ ignore_index=True,
34
+ )
35
+ all_evol_indices = all_evol_indices.drop_duplicates()
36
+
37
+ labels_dataset = pd.read_csv(label_path, low_memory=False)
38
+ all_evol_indices_with_labels = pd.merge(
39
+ all_evol_indices,
40
+ labels_dataset[["protein_name", "mutations", "ClinVar_labels"]],
41
+ how="right",
42
+ on=["protein_name", "mutations"],
43
+ )
44
+
45
+ all_evol_indices_with_labels = all_evol_indices_with_labels[
46
+ all_evol_indices_with_labels.ClinVar_labels != 0.5
47
+ ]
48
+
49
+ fpr, tpr, threshold = metrics.roc_curve(
50
+ all_evol_indices_with_labels["ClinVar_labels"], all_evol_indices_with_labels["evol_indices"]
51
+ )
52
+ roc_auc = metrics.auc(fpr, tpr)
53
+ print(roc_auc)
54
+
55
+
56
+ if __name__ == '__main__':
57
+ main(get_args())