Upload folder using huggingface_hub
Browse files- .gitattributes +22 -10
- README.md +245 -0
- benchmarks/__pycache__/_common.cpython-311.pyc +0 -0
- benchmarks/_common.py +102 -0
- benchmarks/casf2016_docking.py +142 -0
- benchmarks/casf2016_scoring_ranking.py +62 -0
- benchmarks/casf2016_screening.py +104 -0
- models/__init__.py +2 -0
- models/__pycache__/__init__.cpython-311.pyc +0 -0
- models/__pycache__/inference.cpython-311.pyc +0 -0
- models/__pycache__/train.cpython-311.pyc +0 -0
- models/inference.py +298 -0
- models/model/__init__.py +1 -0
- models/model/__pycache__/__init__.cpython-311.pyc +0 -0
- models/model/__pycache__/model.cpython-311.pyc +0 -0
- models/model/layer/__init__.py +1 -0
- models/model/layer/__pycache__/__init__.cpython-311.pyc +0 -0
- models/model/layer/__pycache__/gatedgcn_layer.cpython-311.pyc +0 -0
- models/model/layer/gatedgcn_layer.py +151 -0
- models/model/model.py +563 -0
- models/train.py +217 -0
- scripts/genscore.py +11 -0
- scripts/preprocess_pdbbind.py +117 -0
- scripts/run_genscore.sh +88 -0
- scripts/run_genscore_benchmarks.sh +151 -0
- scripts/train_genscore.py +11 -0
- scripts/train_genscore_full.sh +62 -0
- scripts/train_genscore_smoke.sh +31 -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
|
README.md
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
frameworks:
|
| 3 |
+
- ""
|
| 4 |
+
language:
|
| 5 |
+
- en
|
| 6 |
+
license: mit
|
| 7 |
+
tags:
|
| 8 |
+
- OneScience
|
| 9 |
+
- protein-ligand scoring
|
| 10 |
+
- graph neural network
|
| 11 |
+
---
|
| 12 |
+
<p align="center">
|
| 13 |
+
<strong>
|
| 14 |
+
<span style="font-size: 30px;">GenScore</span>
|
| 15 |
+
</strong>
|
| 16 |
+
</p>
|
| 17 |
+
|
| 18 |
+
# Model Overview
|
| 19 |
+
|
| 20 |
+
GenScore is a **protein-ligand scoring framework** that can predict protein-small molecule binding affinity and evaluate docking pose quality. It demonstrates balanced scoring, ranking, docking, and virtual screening capabilities across multiple datasets.
|
| 21 |
+
|
| 22 |
+
# Model Description
|
| 23 |
+
|
| 24 |
+
GenScore is based on graph neural networks and extends RTMScore.
|
| 25 |
+
|
| 26 |
+
Weights and datasets are not provided at the moment. They will be uploaded to Hugging Face soon, and command-line downloads will be supported later.
|
| 27 |
+
|
| 28 |
+
# Use Cases
|
| 29 |
+
|
| 30 |
+
| Use case | Description |
|
| 31 |
+
| :---: | :---: |
|
| 32 |
+
| Protein-ligand scoring | Predicts binding scores for a given protein, or an extracted binding pocket, and ligand conformation. |
|
| 33 |
+
| Automatic pocket generation | Automatically extracts the binding pocket from a full protein structure based on the reference ligand position. |
|
| 34 |
+
| Contribution analysis | Outputs atom-level and residue-level contributions to the final score for interpretability analysis. |
|
| 35 |
+
| Model training | Trains the GenScore scoring network using preprocessed PDBbind protein-ligand graph data. |
|
| 36 |
+
| CASF-2016 benchmark evaluation | Supports three standard tests: scoring/ranking, docking, and virtual screening. |
|
| 37 |
+
|
| 38 |
+
# Usage
|
| 39 |
+
|
| 40 |
+
## 1. Using OneCode
|
| 41 |
+
|
| 42 |
+
You can try intelligent one-click AI4S programming through the OneCode online environment:
|
| 43 |
+
|
| 44 |
+
[Try intelligent one-click AI4S programming](https://web-2069360198568017922-iaaj.ksai.scnet.cn:58043/home)
|
| 45 |
+
|
| 46 |
+
## 2. Manual Installation and Usage
|
| 47 |
+
|
| 48 |
+
**Hardware Requirements**
|
| 49 |
+
|
| 50 |
+
- Running on a GPU or DCU is recommended.
|
| 51 |
+
- CPU can be used for connectivity checks, but it is relatively slow.
|
| 52 |
+
- DCU users need to install DTK in advance. DTK 25.04.2 or later is recommended, or the OneScience-recommended version that matches the current cluster.
|
| 53 |
+
|
| 54 |
+
**Software Requirements**
|
| 55 |
+
|
| 56 |
+
DCU users who want to learn more about adaptation details can contact liubiao@sugon.com.
|
| 57 |
+
|
| 58 |
+
**Environment Checks**
|
| 59 |
+
|
| 60 |
+
- NVIDIA GPU:
|
| 61 |
+
|
| 62 |
+
```bash
|
| 63 |
+
nvidia-smi
|
| 64 |
+
```
|
| 65 |
+
|
| 66 |
+
- Hygon DCU:
|
| 67 |
+
|
| 68 |
+
```bash
|
| 69 |
+
hy-smi
|
| 70 |
+
```
|
| 71 |
+
|
| 72 |
+
## Quick Start
|
| 73 |
+
|
| 74 |
+
### 1. Install the Runtime Environment
|
| 75 |
+
|
| 76 |
+
```bash
|
| 77 |
+
conda create -n onescience311 python=3.11 -y
|
| 78 |
+
conda activate onescience311
|
| 79 |
+
pip install onescience[bio] -i http://mirrors.onescience.ai:3141/pypi/simple/ --trusted-host mirrors.onescience.ai
|
| 80 |
+
```
|
| 81 |
+
|
| 82 |
+
If the following code cannot find required libraries at runtime, activate CUDA as shown below.
|
| 83 |
+
|
| 84 |
+
```bash
|
| 85 |
+
source ${ROCM_PATH}/cuda/env.sh
|
| 86 |
+
export LD_LIBRARY_PATH="$CONDA_PREFIX/lib:$LD_LIBRARY_PATH"
|
| 87 |
+
export LD_LIBRARY_PATH="$CONDA_PREFIX/lib/python3.11/site-packages/fastpt/torch/lib:$LD_LIBRARY_PATH"
|
| 88 |
+
```
|
| 89 |
+
|
| 90 |
+
### 2. Download the Model Package
|
| 91 |
+
|
| 92 |
+
```bash
|
| 93 |
+
# By default, the package is downloaded to the GenScore folder under the current path. To change this, adjust the path after local_dir.
|
| 94 |
+
hf download --model OneScience-Sugon/GenScore --local-dir ./model
|
| 95 |
+
|
| 96 |
+
cd model
|
| 97 |
+
```
|
| 98 |
+
|
| 99 |
+
### Training Weights and Datasets
|
| 100 |
+
|
| 101 |
+
Weights and datasets are not provided at the moment. They will be uploaded to Hugging Face soon, and command-line downloads will be supported later.
|
| 102 |
+
|
| 103 |
+
### 3. Usage Modes
|
| 104 |
+
|
| 105 |
+
#### 1. Protein-Ligand Scoring Inference (`run_genscore.sh`)
|
| 106 |
+
|
| 107 |
+
```bash
|
| 108 |
+
cd ./scripts
|
| 109 |
+
bash run_genscore.sh
|
| 110 |
+
```
|
| 111 |
+
|
| 112 |
+
This script runs four inference examples in sequence, covering common use cases:
|
| 113 |
+
|
| 114 |
+
1. **GT model + automatic pocket generation**: inputs a full protein, a reference ligand, and a decoy ligand, then automatically generates the binding pocket and scores it.
|
| 115 |
+
2. **GatedGCN model + pre-extracted pocket**: inputs a pre-extracted pocket PDB and a decoy ligand for scoring.
|
| 116 |
+
3. **Atom contribution analysis**: uses the GatedGCN model to compute each atom's contribution to the score.
|
| 117 |
+
4. **Residue contribution analysis**: uses the GatedGCN model to compute each residue's contribution to the score.
|
| 118 |
+
|
| 119 |
+
#### 2. Model Training
|
| 120 |
+
|
| 121 |
+
##### 2.1 Small-Scale Smoke Test (`train_genscore_smoke.sh`)
|
| 122 |
+
|
| 123 |
+
```bash
|
| 124 |
+
cd ./scripts
|
| 125 |
+
bash train_genscore_smoke.sh
|
| 126 |
+
```
|
| 127 |
+
|
| 128 |
+
Default configuration:
|
| 129 |
+
|
| 130 |
+
| Parameter | Default value |
|
| 131 |
+
|------|--------|
|
| 132 |
+
| Number of training epochs | 100 |
|
| 133 |
+
| Batch size | 16 |
|
| 134 |
+
| Number of validation samples | 1500 |
|
| 135 |
+
| Encoder | `gatedgcn` |
|
| 136 |
+
| Output model | `genscore_smoke_bs16.pth` |
|
| 137 |
+
|
| 138 |
+
##### 2.2 Full Training (`train_genscore_full.sh`)
|
| 139 |
+
|
| 140 |
+
```bash
|
| 141 |
+
cd ./scripts
|
| 142 |
+
bash train_genscore_full.sh
|
| 143 |
+
```
|
| 144 |
+
|
| 145 |
+
Key parameters can be overridden through environment variables:
|
| 146 |
+
|
| 147 |
+
| Environment variable | Description | Default value |
|
| 148 |
+
|----------|------|--------|
|
| 149 |
+
| `GENSCORE_DATA_DIR` | Training data directory | `${ONESCIENCE_DATASETS_DIR}/GenScore/genscore_data/rtmscore_s` |
|
| 150 |
+
| `GENSCORE_DATA_PREFIX` | Data file prefix | `v2020_train` |
|
| 151 |
+
| `GENSCORE_ENCODER` | Graph encoder type | `gatedgcn` |
|
| 152 |
+
| `GENSCORE_MODEL_PATH` | Output model path | `examples/biosciences/genscore/genscore_${GENSCORE_ENCODER}_full_3000.pth` |
|
| 153 |
+
| `GENSCORE_NUM_EPOCHS` | Number of training epochs | `3000` |
|
| 154 |
+
| `GENSCORE_BATCH_SIZE` | Batch size | `64` |
|
| 155 |
+
| `GENSCORE_NUM_WORKERS` | Number of data loading workers | `8` |
|
| 156 |
+
| `GENSCORE_VALNUM` | Number of validation samples | `1500` |
|
| 157 |
+
| `GENSCORE_PATIENCE` | Early-stopping patience | `70` |
|
| 158 |
+
---
|
| 159 |
+
|
| 160 |
+
#### 3. CASF-2016 Benchmark Evaluation (`run_genscore_benchmarks.sh`)
|
| 161 |
+
|
| 162 |
+
```bash
|
| 163 |
+
cd ./scripts
|
| 164 |
+
bash run_genscore_benchmarks.sh all
|
| 165 |
+
```
|
| 166 |
+
|
| 167 |
+
You can also run a single evaluation task:
|
| 168 |
+
|
| 169 |
+
```bash
|
| 170 |
+
bash run_genscore_benchmarks.sh scoring
|
| 171 |
+
bash run_genscore_benchmarks.sh docking
|
| 172 |
+
bash run_genscore_benchmarks.sh screening
|
| 173 |
+
```
|
| 174 |
+
|
| 175 |
+
Evaluation task descriptions:
|
| 176 |
+
|
| 177 |
+
| Task | Description |
|
| 178 |
+
|------|------|
|
| 179 |
+
| `scoring` | Scoring capability evaluation (scoring/ranking) |
|
| 180 |
+
| `docking` | Docking capability evaluation (docking power) |
|
| 181 |
+
| `screening` | Virtual screening capability evaluation (screening power) |
|
| 182 |
+
|
| 183 |
+
### Data Preprocessing
|
| 184 |
+
|
| 185 |
+
Training requires preprocessed PDBbind graph data, including:
|
| 186 |
+
|
| 187 |
+
```text
|
| 188 |
+
<data_prefix>_ids.npy
|
| 189 |
+
<data_prefix>_lig.pt
|
| 190 |
+
<data_prefix>_prot.pt
|
| 191 |
+
```
|
| 192 |
+
|
| 193 |
+
You can preprocess the raw PDBbind data with the following command:
|
| 194 |
+
|
| 195 |
+
```bash
|
| 196 |
+
cd ./scripts
|
| 197 |
+
export PYTHONPATH=../../../src:$PYTHONPATH
|
| 198 |
+
python preprocess_pdbbind.py \
|
| 199 |
+
--dir /path/to/pdbbind \
|
| 200 |
+
--ref /path/to/pdbbind_2020_general.csv \
|
| 201 |
+
--cutoff 10.0 \
|
| 202 |
+
--outprefix /path/to/preprocessed/pdbbind/v2020_train
|
| 203 |
+
```
|
| 204 |
+
|
| 205 |
+
Main parameters:
|
| 206 |
+
|
| 207 |
+
| Parameter | Description |
|
| 208 |
+
|------|------|
|
| 209 |
+
| `--dir` | Raw PDBbind data directory |
|
| 210 |
+
| `--ref` | PDBbind index CSV file |
|
| 211 |
+
| `--cutoff` | Protein-ligand distance cutoff, default `10.0` Å |
|
| 212 |
+
| `--outprefix` | Output file prefix |
|
| 213 |
+
|
| 214 |
+
### Notes
|
| 215 |
+
|
| 216 |
+
- Make sure the `ONESCIENCE_DATASETS_DIR` environment variable is correctly set before running the scripts.
|
| 217 |
+
- The scripts automatically set ROCm/DCU-related `LD_LIBRARY_PATH` values and can run directly on Hygon DCU platforms.
|
| 218 |
+
- Automatic pocket generation depends on OpenBabel and ProDy. If OpenBabel requires explicit data paths, set `BABEL_LIBDIR` and `BABEL_DATADIR` in advance.
|
| 219 |
+
- Training and inference use a single device by default (`HIP_VISIBLE_DEVICES=0` or `CUDA_VISIBLE_DEVICES=0`). For multi-device training, adjust the parallel strategy in the training scripts.
|
| 220 |
+
- The training scripts check whether `<data_prefix>_ids.npy`, `<data_prefix>_lig.pt`, and `<data_prefix>_prot.pt` exist. If any are missing, the scripts will report an error and exit.
|
| 221 |
+
- The `docking` and `screening` tasks in CASF-2016 evaluation require complete CASF-2016 and PDBbind v2020 data.
|
| 222 |
+
|
| 223 |
+
# Official OneScience Information
|
| 224 |
+
|
| 225 |
+
| Platform | OneScience main repository | Skills repository |
|
| 226 |
+
| --- | --- | --- |
|
| 227 |
+
| Gitee | https://gitee.com/onescience-ai/onescience | https://gitee.com/onescience-ai/oneskills |
|
| 228 |
+
| GitHub | https://github.com/onescience-ai/OneScience | https://github.com/onescience-ai/oneskills |
|
| 229 |
+
|
| 230 |
+
# Citation and License
|
| 231 |
+
|
| 232 |
+
If you use GenScore in your research, please cite the original work:
|
| 233 |
+
|
| 234 |
+
```bibtex
|
| 235 |
+
@article{genScore,
|
| 236 |
+
title={GenScore: a generalized protein-ligand scoring framework},
|
| 237 |
+
author={Shen, Chao and Hu, Yafeng and Wang, Zhe and Zhang, Xujun and Li, Jianxin and Wang, Guisheng and Wang, Tingjun and Chen, Yen-Wei and Pan, Peichen and Hou, Tingjun},
|
| 238 |
+
journal={Journal of Chemical Information and Modeling},
|
| 239 |
+
year={2023}
|
| 240 |
+
}
|
| 241 |
+
```
|
| 242 |
+
|
| 243 |
+
For more information, see the official GenScore repository: https://github.com/sc8668/GenScore
|
| 244 |
+
|
| 245 |
+
For license information, refer to the description in the [official GenScore repository](https://github.com/sc8668/GenScore).
|
benchmarks/__pycache__/_common.cpython-311.pyc
ADDED
|
Binary file (5.17 kB). View file
|
|
|
benchmarks/_common.py
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sys
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
|
| 4 |
+
_DIR = Path(__file__).resolve().parent.parent
|
| 5 |
+
sys.path.insert(0, str(_DIR))
|
| 6 |
+
|
| 7 |
+
import argparse
|
| 8 |
+
|
| 9 |
+
import torch as th
|
| 10 |
+
import torch.multiprocessing
|
| 11 |
+
from torch_geometric.loader import DataLoader
|
| 12 |
+
|
| 13 |
+
from onescience.datapipes.genscore.data import PDBbindDataset
|
| 14 |
+
from onescience.metrics.genscore.utils import run_an_eval_epoch
|
| 15 |
+
from models.inference import _build_encoder, scoring
|
| 16 |
+
from models.model.model import GenScore
|
| 17 |
+
|
| 18 |
+
torch.multiprocessing.set_sharing_strategy("file_system")
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def add_model_args(parser):
|
| 22 |
+
parser.add_argument("--model-path", required=True, help="Path to a trained GenScore checkpoint.")
|
| 23 |
+
parser.add_argument("--encoder", choices=["gt", "gatedgcn"], default="gatedgcn")
|
| 24 |
+
parser.add_argument("--batch-size", type=int, default=128)
|
| 25 |
+
parser.add_argument("--num-workers", type=int, default=10)
|
| 26 |
+
parser.add_argument("--cutoff", type=float, default=10.0)
|
| 27 |
+
parser.add_argument("--outprefix", default="gatedgcn1x5")
|
| 28 |
+
parser.add_argument("--dist-threhold", type=float, default=5.0)
|
| 29 |
+
parser.add_argument("--hidden-dim0", type=int, default=128)
|
| 30 |
+
parser.add_argument("--hidden-dim", type=int, default=128)
|
| 31 |
+
parser.add_argument("--n-gaussians", type=int, default=10)
|
| 32 |
+
parser.add_argument("--dropout-rate", type=float, default=0.15)
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def runtime_kwargs(args):
|
| 36 |
+
return {
|
| 37 |
+
"batch_size": args.batch_size,
|
| 38 |
+
"dist_threhold": args.dist_threhold,
|
| 39 |
+
"device": "cuda" if th.cuda.is_available() else "cpu",
|
| 40 |
+
"num_workers": args.num_workers,
|
| 41 |
+
"num_node_featsp": 41,
|
| 42 |
+
"num_node_featsl": 41,
|
| 43 |
+
"num_edge_featsp": 5,
|
| 44 |
+
"num_edge_featsl": 10,
|
| 45 |
+
"hidden_dim0": args.hidden_dim0,
|
| 46 |
+
"hidden_dim": args.hidden_dim,
|
| 47 |
+
"n_gaussians": args.n_gaussians,
|
| 48 |
+
"dropout_rate": args.dropout_rate,
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def score_ligand_file(prot, lig, args, parallel=False):
|
| 53 |
+
return scoring(
|
| 54 |
+
prot=prot,
|
| 55 |
+
lig=lig,
|
| 56 |
+
modpath=args.model_path,
|
| 57 |
+
cut=args.cutoff,
|
| 58 |
+
gen_pocket=False,
|
| 59 |
+
reflig=None,
|
| 60 |
+
encoder=args.encoder,
|
| 61 |
+
explicit_H=False,
|
| 62 |
+
use_chirality=True,
|
| 63 |
+
parallel=parallel,
|
| 64 |
+
**runtime_kwargs(args),
|
| 65 |
+
)
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def score_preprocessed(ids, prots, ligs, args):
|
| 69 |
+
kwargs = runtime_kwargs(args)
|
| 70 |
+
data = PDBbindDataset(ids=ids, prots=prots, ligs=ligs)
|
| 71 |
+
loader = DataLoader(
|
| 72 |
+
dataset=data,
|
| 73 |
+
batch_size=kwargs["batch_size"],
|
| 74 |
+
shuffle=False,
|
| 75 |
+
num_workers=kwargs["num_workers"],
|
| 76 |
+
)
|
| 77 |
+
|
| 78 |
+
ligmodel, protmodel = _build_encoder(args.encoder, kwargs)
|
| 79 |
+
model = GenScore(
|
| 80 |
+
ligmodel,
|
| 81 |
+
protmodel,
|
| 82 |
+
in_channels=kwargs["hidden_dim0"],
|
| 83 |
+
hidden_dim=kwargs["hidden_dim"],
|
| 84 |
+
n_gaussians=kwargs["n_gaussians"],
|
| 85 |
+
dropout_rate=kwargs["dropout_rate"],
|
| 86 |
+
dist_threhold=kwargs["dist_threhold"],
|
| 87 |
+
).to(kwargs["device"])
|
| 88 |
+
|
| 89 |
+
checkpoint = th.load(args.model_path, map_location=th.device(kwargs["device"]))
|
| 90 |
+
model.load_state_dict(checkpoint["model_state_dict"])
|
| 91 |
+
preds = run_an_eval_epoch(
|
| 92 |
+
model,
|
| 93 |
+
loader,
|
| 94 |
+
pred=True,
|
| 95 |
+
dist_threhold=kwargs["dist_threhold"],
|
| 96 |
+
device=kwargs["device"],
|
| 97 |
+
)
|
| 98 |
+
return data.pdbids, preds
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
def formatter():
|
| 102 |
+
return argparse.ArgumentDefaultsHelpFormatter
|
benchmarks/casf2016_docking.py
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import argparse
|
| 2 |
+
import os
|
| 3 |
+
import pickle
|
| 4 |
+
|
| 5 |
+
import numpy as np
|
| 6 |
+
import pandas as pd
|
| 7 |
+
from joblib import Parallel, delayed
|
| 8 |
+
|
| 9 |
+
from _common import add_model_args, formatter, runtime_kwargs, score_ligand_file
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def parse_args():
|
| 13 |
+
parser = argparse.ArgumentParser(
|
| 14 |
+
description="Run GenScore CASF-2016 docking benchmark.",
|
| 15 |
+
formatter_class=formatter(),
|
| 16 |
+
)
|
| 17 |
+
add_model_args(parser)
|
| 18 |
+
parser.add_argument("--casf-dir", required=True, help="CASF-2016 root directory.")
|
| 19 |
+
parser.add_argument("--pdbbind-dir", required=True, help="PDBbind root directory.")
|
| 20 |
+
parser.add_argument(
|
| 21 |
+
"--native-ligand-dir",
|
| 22 |
+
default=None,
|
| 23 |
+
help="Directory containing <pdbid>_ligand.mol2 files.",
|
| 24 |
+
)
|
| 25 |
+
parser.add_argument("--outdir", required=True, help="Directory for CASF docking score .dat files.")
|
| 26 |
+
parser.add_argument("--decoys-subdir", default="decoys_docking")
|
| 27 |
+
parser.add_argument("--coreset-subdir", default="coreset")
|
| 28 |
+
parser.add_argument("--refined-subdir", default="v2020-refined")
|
| 29 |
+
parser.add_argument("--other-pl-subdir", default="v2020-other-PL")
|
| 30 |
+
parser.add_argument("--parallel", action="store_true", default=False)
|
| 31 |
+
return parser.parse_args()
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def _complex_ids(path):
|
| 35 |
+
return [name for name in os.listdir(path) if os.path.isdir(os.path.join(path, name))]
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def _pocket_path(pdbbind_dir, subset, pdbid, cutoff):
|
| 39 |
+
return os.path.join(
|
| 40 |
+
pdbbind_dir,
|
| 41 |
+
subset,
|
| 42 |
+
pdbid,
|
| 43 |
+
f"{pdbid}_prot",
|
| 44 |
+
f"{pdbid}_p_pocket_{cutoff}.pdb",
|
| 45 |
+
)
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def _native_ligand_path(pdbbind_dir, native_ligand_dir, subset, pdbid):
|
| 49 |
+
candidates = []
|
| 50 |
+
if native_ligand_dir:
|
| 51 |
+
candidates.append(
|
| 52 |
+
os.path.join(native_ligand_dir, f"{pdbid}_ligand.mol2")
|
| 53 |
+
)
|
| 54 |
+
candidates.extend([
|
| 55 |
+
os.path.join(pdbbind_dir, "mol2", f"{pdbid}_ligand.mol2"),
|
| 56 |
+
os.path.join(
|
| 57 |
+
pdbbind_dir, subset, pdbid, f"{pdbid}_prot",
|
| 58 |
+
f"{pdbid}_l.mol2",
|
| 59 |
+
),
|
| 60 |
+
os.path.join(
|
| 61 |
+
pdbbind_dir, subset, pdbid, f"{pdbid}_prot",
|
| 62 |
+
f"{pdbid}_l.sdf",
|
| 63 |
+
),
|
| 64 |
+
])
|
| 65 |
+
for candidate in candidates:
|
| 66 |
+
if os.path.isfile(candidate):
|
| 67 |
+
return candidate
|
| 68 |
+
raise FileNotFoundError(
|
| 69 |
+
f"No native ligand found for {pdbid}. Checked: "
|
| 70 |
+
+ ", ".join(candidates)
|
| 71 |
+
)
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
def _decoys_path(casf_dir, decoys_subdir, pdbid):
|
| 75 |
+
root = os.path.join(casf_dir, decoys_subdir)
|
| 76 |
+
return _ligand_path(root, f"{pdbid}_decoys")
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
def _ligand_path(root, stem):
|
| 80 |
+
candidates = [
|
| 81 |
+
os.path.join(root, f"{stem}.{extension}")
|
| 82 |
+
for extension in ("sdf", "mol2")
|
| 83 |
+
]
|
| 84 |
+
for candidate in candidates:
|
| 85 |
+
if os.path.isfile(candidate):
|
| 86 |
+
return candidate
|
| 87 |
+
raise FileNotFoundError(
|
| 88 |
+
f"No ligand file found for {stem}. Checked: {', '.join(candidates)}"
|
| 89 |
+
)
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
def score_compound(pdbid, subset, args):
|
| 93 |
+
prot = _pocket_path(args.pdbbind_dir, subset, pdbid, args.cutoff)
|
| 94 |
+
decoys = _decoys_path(args.casf_dir, args.decoys_subdir, pdbid)
|
| 95 |
+
ids_decoy, scores_decoy = score_ligand_file(prot, decoys, args, parallel=True)
|
| 96 |
+
|
| 97 |
+
native = _native_ligand_path(
|
| 98 |
+
args.pdbbind_dir,
|
| 99 |
+
args.native_ligand_dir,
|
| 100 |
+
subset,
|
| 101 |
+
pdbid,
|
| 102 |
+
)
|
| 103 |
+
ids_native, scores_native = score_ligand_file(prot, native, args, parallel=False)
|
| 104 |
+
ids_native = list(ids_native)
|
| 105 |
+
ids_native.pop(-1)
|
| 106 |
+
ids_native.append(f"{pdbid}_ligand")
|
| 107 |
+
return ids_decoy + ids_native, np.append(scores_decoy, scores_native)
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
def run_for_subset(pdbids, subset, args):
|
| 111 |
+
if args.parallel and runtime_kwargs(args)["device"] == "cpu":
|
| 112 |
+
return Parallel(n_jobs=-1, backend="threading")(
|
| 113 |
+
delayed(score_compound)(pdbid, subset, args) for pdbid in pdbids
|
| 114 |
+
)
|
| 115 |
+
return [score_compound(pdbid, subset, args) for pdbid in pdbids]
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
def main():
|
| 119 |
+
args = parse_args()
|
| 120 |
+
coreset = _complex_ids(os.path.join(args.casf_dir, args.coreset_subdir))
|
| 121 |
+
refined_ids = set(_complex_ids(os.path.join(args.pdbbind_dir, args.refined_subdir)))
|
| 122 |
+
other_ids = set(_complex_ids(os.path.join(args.pdbbind_dir, args.other_pl_subdir)))
|
| 123 |
+
|
| 124 |
+
ids_refined = [pdbid for pdbid in coreset if pdbid in refined_ids]
|
| 125 |
+
ids_other = [pdbid for pdbid in coreset if pdbid in other_ids]
|
| 126 |
+
|
| 127 |
+
results = run_for_subset(ids_refined, args.refined_subdir, args)
|
| 128 |
+
results += run_for_subset(ids_other, args.other_pl_subdir, args)
|
| 129 |
+
|
| 130 |
+
os.makedirs(args.outdir, exist_ok=True)
|
| 131 |
+
for ids, scores in results:
|
| 132 |
+
pdbid = ids[0].split("_")[0]
|
| 133 |
+
df = pd.DataFrame(zip(ids, scores), columns=["#code", "score"])
|
| 134 |
+
df["#code"] = df["#code"].str.split("-").apply(lambda item: item[0])
|
| 135 |
+
df.to_csv(os.path.join(args.outdir, f"{pdbid}_score.dat"), index=False, sep="\t")
|
| 136 |
+
|
| 137 |
+
with open(f"{args.outprefix}_docking.pkl", "wb") as handle:
|
| 138 |
+
pickle.dump(results, handle)
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
if __name__ == "__main__":
|
| 142 |
+
main()
|
benchmarks/casf2016_scoring_ranking.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import argparse
|
| 2 |
+
import os
|
| 3 |
+
|
| 4 |
+
import numpy as np
|
| 5 |
+
import pandas as pd
|
| 6 |
+
from scipy.stats import pearsonr
|
| 7 |
+
from sklearn import linear_model
|
| 8 |
+
from sklearn.metrics import mean_squared_error
|
| 9 |
+
|
| 10 |
+
from _common import add_model_args, formatter, score_preprocessed
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def parse_args():
|
| 14 |
+
parser = argparse.ArgumentParser(
|
| 15 |
+
description="Run GenScore CASF-2016 scoring/ranking benchmark.",
|
| 16 |
+
formatter_class=formatter(),
|
| 17 |
+
)
|
| 18 |
+
add_model_args(parser)
|
| 19 |
+
parser.add_argument("--data-dir", required=True, help="Directory containing preprocessed CASF tensors.")
|
| 20 |
+
parser.add_argument("--test-prefix", default="v2020_casf")
|
| 21 |
+
parser.add_argument("--coreset-file", required=True, help="Path to CASF-2016 CoreSet.dat.")
|
| 22 |
+
parser.add_argument("--outdir", required=True, help="Output directory for CASF ranking .dat file.")
|
| 23 |
+
return parser.parse_args()
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def obtain_metrics(df):
|
| 27 |
+
regr = linear_model.LinearRegression()
|
| 28 |
+
regr.fit(df.score.values.reshape(-1, 1), df.logKa.values.reshape(-1, 1))
|
| 29 |
+
preds = regr.predict(df.score.values.reshape(-1, 1))
|
| 30 |
+
rp = pearsonr(df.logKa, df.score)[0]
|
| 31 |
+
mse = mean_squared_error(df.logKa, preds)
|
| 32 |
+
num = df.shape[0]
|
| 33 |
+
sd = np.sqrt((mse * num) / (num - 1))
|
| 34 |
+
print("The regression equation: logKa = %.2f + %.2f * Score" % (float(regr.coef_), float(regr.intercept_)))
|
| 35 |
+
print("Number of favorable sample (N): %d" % num)
|
| 36 |
+
print("Pearson correlation coefficient (R): %.3f" % rp)
|
| 37 |
+
print("Standard deviation in fitting (SD): %.2f" % sd)
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def main():
|
| 41 |
+
args = parse_args()
|
| 42 |
+
prots = os.path.join(args.data_dir, f"{args.test_prefix}_prot.pt")
|
| 43 |
+
ligs = os.path.join(args.data_dir, f"{args.test_prefix}_lig.pt")
|
| 44 |
+
ids = os.path.join(args.data_dir, f"{args.test_prefix}_ids.npy")
|
| 45 |
+
|
| 46 |
+
_, preds = score_preprocessed(ids, prots, ligs, args)
|
| 47 |
+
|
| 48 |
+
core = pd.read_csv(args.coreset_file, sep=r"[,,\t, ]+", header=0, engine="python")
|
| 49 |
+
df_score = pd.DataFrame(zip(np.load(ids, allow_pickle=True)[0], preds), columns=["#code", "score"])
|
| 50 |
+
testdf = pd.merge(core, df_score, on="#code")
|
| 51 |
+
|
| 52 |
+
os.makedirs(args.outdir, exist_ok=True)
|
| 53 |
+
testdf[["#code", "score"]].to_csv(
|
| 54 |
+
os.path.join(args.outdir, f"{args.outprefix}.dat"),
|
| 55 |
+
index=False,
|
| 56 |
+
sep="\t",
|
| 57 |
+
)
|
| 58 |
+
obtain_metrics(testdf)
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
if __name__ == "__main__":
|
| 62 |
+
main()
|
benchmarks/casf2016_screening.py
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import argparse
|
| 2 |
+
import os
|
| 3 |
+
import pickle
|
| 4 |
+
|
| 5 |
+
import pandas as pd
|
| 6 |
+
from joblib import Parallel, delayed
|
| 7 |
+
|
| 8 |
+
from _common import add_model_args, formatter, runtime_kwargs, score_ligand_file
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def parse_args():
|
| 12 |
+
parser = argparse.ArgumentParser(
|
| 13 |
+
description="Run GenScore CASF-2016 screening benchmark.",
|
| 14 |
+
formatter_class=formatter(),
|
| 15 |
+
)
|
| 16 |
+
add_model_args(parser)
|
| 17 |
+
parser.add_argument("--casf-dir", required=True, help="CASF-2016 root directory.")
|
| 18 |
+
parser.add_argument("--pdbbind-dir", required=True, help="PDBbind root directory.")
|
| 19 |
+
parser.add_argument("--outdir", required=True, help="Directory for CASF screening score .dat files.")
|
| 20 |
+
parser.add_argument("--decoys-subdir", default="decoys_screening")
|
| 21 |
+
parser.add_argument("--coreset-subdir", default="coreset")
|
| 22 |
+
parser.add_argument("--refined-subdir", default="v2020-refined")
|
| 23 |
+
parser.add_argument("--other-pl-subdir", default="v2020-other-PL")
|
| 24 |
+
parser.add_argument("--parallel", action="store_true", default=False)
|
| 25 |
+
return parser.parse_args()
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def _complex_ids(path):
|
| 29 |
+
return [name for name in os.listdir(path) if os.path.isdir(os.path.join(path, name))]
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def _pocket_path(pdbbind_dir, subset, pdbid, cutoff):
|
| 33 |
+
return os.path.join(
|
| 34 |
+
pdbbind_dir,
|
| 35 |
+
subset,
|
| 36 |
+
pdbid,
|
| 37 |
+
f"{pdbid}_prot",
|
| 38 |
+
f"{pdbid}_p_pocket_{cutoff}.pdb",
|
| 39 |
+
)
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def _ligand_path(root, stem):
|
| 43 |
+
candidates = [os.path.join(root, f"{stem}.{extension}") for extension in ("mol2", "sdf")]
|
| 44 |
+
for path in candidates:
|
| 45 |
+
if os.path.isfile(path):
|
| 46 |
+
return path
|
| 47 |
+
raise FileNotFoundError(
|
| 48 |
+
f"No ligand file found for {stem}. Checked: {', '.join(candidates)}"
|
| 49 |
+
)
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def score_compound(pdbid, ligid, subset, args):
|
| 53 |
+
prot = _pocket_path(args.pdbbind_dir, subset, pdbid, args.cutoff)
|
| 54 |
+
root = os.path.join(args.casf_dir, args.decoys_subdir, pdbid)
|
| 55 |
+
lig = _ligand_path(root, f"{pdbid}_{ligid}")
|
| 56 |
+
return score_ligand_file(prot, lig, args, parallel=True)
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
def score_target(pdbid, subset, ligids, args):
|
| 60 |
+
print(f"{pdbid} started.....")
|
| 61 |
+
ids_list = []
|
| 62 |
+
scores_list = []
|
| 63 |
+
for ligid in ligids:
|
| 64 |
+
ids, scores = score_compound(pdbid, ligid, subset, args)
|
| 65 |
+
if ids is not None and scores is not None:
|
| 66 |
+
ids_list.extend(ids)
|
| 67 |
+
scores_list.extend(scores)
|
| 68 |
+
print(f"{pdbid} finished.....")
|
| 69 |
+
return pdbid, [ids_list, scores_list]
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def run_for_subset(pdbids, subset, ligids, args):
|
| 73 |
+
if args.parallel and runtime_kwargs(args)["device"] == "cpu":
|
| 74 |
+
return Parallel(n_jobs=-1, backend="threading")(
|
| 75 |
+
delayed(score_target)(pdbid, subset, ligids, args) for pdbid in pdbids
|
| 76 |
+
)
|
| 77 |
+
return [score_target(pdbid, subset, ligids, args) for pdbid in pdbids]
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
def main():
|
| 81 |
+
args = parse_args()
|
| 82 |
+
ligids = _complex_ids(os.path.join(args.casf_dir, args.coreset_subdir))
|
| 83 |
+
pdbids = _complex_ids(os.path.join(args.casf_dir, args.decoys_subdir))
|
| 84 |
+
refined_ids = set(_complex_ids(os.path.join(args.pdbbind_dir, args.refined_subdir)))
|
| 85 |
+
other_ids = set(_complex_ids(os.path.join(args.pdbbind_dir, args.other_pl_subdir)))
|
| 86 |
+
|
| 87 |
+
ids_refined = [pdbid for pdbid in pdbids if pdbid in refined_ids]
|
| 88 |
+
ids_other = [pdbid for pdbid in pdbids if pdbid in other_ids]
|
| 89 |
+
|
| 90 |
+
results = run_for_subset(ids_refined, args.refined_subdir, ligids, args)
|
| 91 |
+
results += run_for_subset(ids_other, args.other_pl_subdir, ligids, args)
|
| 92 |
+
|
| 93 |
+
os.makedirs(args.outdir, exist_ok=True)
|
| 94 |
+
for pdbid, payload in results:
|
| 95 |
+
df = pd.DataFrame(zip(*payload), columns=["#code_ligand_num", "score"])
|
| 96 |
+
df["#code_ligand_num"] = df["#code_ligand_num"].str.split("-").apply(lambda item: item[0])
|
| 97 |
+
df.to_csv(os.path.join(args.outdir, f"{pdbid}_score.dat"), index=False, sep="\t")
|
| 98 |
+
|
| 99 |
+
with open(f"{args.outprefix}_screening.pkl", "wb") as handle:
|
| 100 |
+
pickle.dump(results, handle)
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
if __name__ == "__main__":
|
| 104 |
+
main()
|
models/__init__.py
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""GenScore protein-ligand scoring model integration."""
|
| 2 |
+
|
models/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (232 Bytes). View file
|
|
|
models/__pycache__/inference.cpython-311.pyc
ADDED
|
Binary file (11.4 kB). View file
|
|
|
models/__pycache__/train.cpython-311.pyc
ADDED
|
Binary file (9.66 kB). View file
|
|
|
models/inference.py
ADDED
|
@@ -0,0 +1,298 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import argparse
|
| 2 |
+
import os
|
| 3 |
+
|
| 4 |
+
import MDAnalysis as mda
|
| 5 |
+
import numpy as np
|
| 6 |
+
import pandas as pd
|
| 7 |
+
import torch as th
|
| 8 |
+
import torch.multiprocessing
|
| 9 |
+
from torch_geometric.loader import DataLoader
|
| 10 |
+
|
| 11 |
+
from onescience.datapipes.genscore.data import VSDataset
|
| 12 |
+
from models.model.model import GatedGCN, GenScore, GraphTransformer
|
| 13 |
+
from onescience.metrics.genscore.utils import run_an_eval_epoch
|
| 14 |
+
|
| 15 |
+
torch.multiprocessing.set_sharing_strategy("file_system")
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def _default_model_path() -> str:
|
| 19 |
+
return os.path.join(
|
| 20 |
+
os.path.dirname(__file__),
|
| 21 |
+
"..",
|
| 22 |
+
"..",
|
| 23 |
+
"..",
|
| 24 |
+
"..",
|
| 25 |
+
"examples",
|
| 26 |
+
"biosciences",
|
| 27 |
+
"genscore",
|
| 28 |
+
"trained_models",
|
| 29 |
+
"GT_0.0_1.pth",
|
| 30 |
+
)
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def parse_args():
|
| 34 |
+
parser = argparse.ArgumentParser(description="Run GenScore protein-ligand scoring.")
|
| 35 |
+
parser.add_argument("-p", "--prot", required=True, help="Input protein or pocket file (.pdb).")
|
| 36 |
+
parser.add_argument("-l", "--lig", required=True, help="Input ligand file (.sdf/.mol2).")
|
| 37 |
+
parser.add_argument(
|
| 38 |
+
"-m",
|
| 39 |
+
"--model",
|
| 40 |
+
default=_default_model_path(),
|
| 41 |
+
help="Path to a trained GenScore checkpoint.",
|
| 42 |
+
)
|
| 43 |
+
parser.add_argument(
|
| 44 |
+
"-e",
|
| 45 |
+
"--encoder",
|
| 46 |
+
default="gt",
|
| 47 |
+
choices=["gt", "gatedgcn"],
|
| 48 |
+
help="Protein and ligand graph encoder.",
|
| 49 |
+
)
|
| 50 |
+
parser.add_argument("-o", "--outprefix", default="out", help="Output file prefix.")
|
| 51 |
+
parser.add_argument(
|
| 52 |
+
"-gen_pocket",
|
| 53 |
+
"--gen_pocket",
|
| 54 |
+
action="store_true",
|
| 55 |
+
default=False,
|
| 56 |
+
help="Generate a pocket from the input protein.",
|
| 57 |
+
)
|
| 58 |
+
parser.add_argument(
|
| 59 |
+
"-c",
|
| 60 |
+
"--cutoff",
|
| 61 |
+
default=10.0,
|
| 62 |
+
type=float,
|
| 63 |
+
help="Pocket and interaction cutoff distance.",
|
| 64 |
+
)
|
| 65 |
+
parser.add_argument("-rl", "--reflig", default=None, help="Reference ligand for pocket generation.")
|
| 66 |
+
parser.add_argument(
|
| 67 |
+
"-pl",
|
| 68 |
+
"--parallel",
|
| 69 |
+
default=False,
|
| 70 |
+
action="store_true",
|
| 71 |
+
help="Build ligand graphs in parallel.",
|
| 72 |
+
)
|
| 73 |
+
parser.add_argument(
|
| 74 |
+
"-ac",
|
| 75 |
+
"--atom_contribution",
|
| 76 |
+
default=False,
|
| 77 |
+
action="store_true",
|
| 78 |
+
help="Compute atom-level score contributions.",
|
| 79 |
+
)
|
| 80 |
+
parser.add_argument(
|
| 81 |
+
"-rc",
|
| 82 |
+
"--res_contribution",
|
| 83 |
+
default=False,
|
| 84 |
+
action="store_true",
|
| 85 |
+
help="Compute residue-level score contributions.",
|
| 86 |
+
)
|
| 87 |
+
parser.add_argument("--batch_size", type=int, default=128)
|
| 88 |
+
parser.add_argument("--num_workers", type=int, default=10)
|
| 89 |
+
args = parser.parse_args()
|
| 90 |
+
|
| 91 |
+
if args.gen_pocket and args.reflig is None:
|
| 92 |
+
raise ValueError("If pocket generation is enabled, --reflig must be provided.")
|
| 93 |
+
if args.atom_contribution and args.res_contribution:
|
| 94 |
+
raise ValueError("Only one of atom_contribution and res_contribution is supported.")
|
| 95 |
+
return args
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
def _build_encoder(encoder, kwargs):
|
| 99 |
+
if encoder == "gt":
|
| 100 |
+
ligmodel = GraphTransformer(
|
| 101 |
+
in_channels=kwargs["num_node_featsl"],
|
| 102 |
+
edge_features=kwargs["num_edge_featsl"],
|
| 103 |
+
num_hidden_channels=kwargs["hidden_dim0"],
|
| 104 |
+
activ_fn=th.nn.SiLU(),
|
| 105 |
+
transformer_residual=True,
|
| 106 |
+
num_attention_heads=4,
|
| 107 |
+
norm_to_apply="batch",
|
| 108 |
+
dropout_rate=0.15,
|
| 109 |
+
num_layers=6,
|
| 110 |
+
)
|
| 111 |
+
protmodel = GraphTransformer(
|
| 112 |
+
in_channels=kwargs["num_node_featsp"],
|
| 113 |
+
edge_features=kwargs["num_edge_featsp"],
|
| 114 |
+
num_hidden_channels=kwargs["hidden_dim0"],
|
| 115 |
+
activ_fn=th.nn.SiLU(),
|
| 116 |
+
transformer_residual=True,
|
| 117 |
+
num_attention_heads=4,
|
| 118 |
+
norm_to_apply="batch",
|
| 119 |
+
dropout_rate=0.15,
|
| 120 |
+
num_layers=6,
|
| 121 |
+
)
|
| 122 |
+
else:
|
| 123 |
+
ligmodel = GatedGCN(
|
| 124 |
+
in_channels=kwargs["num_node_featsl"],
|
| 125 |
+
edge_features=kwargs["num_edge_featsl"],
|
| 126 |
+
num_hidden_channels=kwargs["hidden_dim0"],
|
| 127 |
+
residual=True,
|
| 128 |
+
dropout_rate=0.15,
|
| 129 |
+
equivstable_pe=False,
|
| 130 |
+
num_layers=6,
|
| 131 |
+
)
|
| 132 |
+
protmodel = GatedGCN(
|
| 133 |
+
in_channels=kwargs["num_node_featsp"],
|
| 134 |
+
edge_features=kwargs["num_edge_featsp"],
|
| 135 |
+
num_hidden_channels=kwargs["hidden_dim0"],
|
| 136 |
+
residual=True,
|
| 137 |
+
dropout_rate=0.15,
|
| 138 |
+
equivstable_pe=False,
|
| 139 |
+
num_layers=6,
|
| 140 |
+
)
|
| 141 |
+
return ligmodel, protmodel
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
def scoring(
|
| 145 |
+
prot,
|
| 146 |
+
lig,
|
| 147 |
+
modpath,
|
| 148 |
+
cut=10.0,
|
| 149 |
+
gen_pocket=False,
|
| 150 |
+
reflig=None,
|
| 151 |
+
encoder="gt",
|
| 152 |
+
atom_contribution=False,
|
| 153 |
+
res_contribution=False,
|
| 154 |
+
explicit_H=False,
|
| 155 |
+
use_chirality=True,
|
| 156 |
+
parallel=False,
|
| 157 |
+
**kwargs,
|
| 158 |
+
):
|
| 159 |
+
data = VSDataset(
|
| 160 |
+
ligs=lig,
|
| 161 |
+
prot=prot,
|
| 162 |
+
cutoff=cut,
|
| 163 |
+
gen_pocket=gen_pocket,
|
| 164 |
+
reflig=reflig,
|
| 165 |
+
explicit_H=explicit_H,
|
| 166 |
+
use_chirality=use_chirality,
|
| 167 |
+
parallel=parallel,
|
| 168 |
+
)
|
| 169 |
+
test_loader = DataLoader(
|
| 170 |
+
dataset=data,
|
| 171 |
+
batch_size=kwargs["batch_size"],
|
| 172 |
+
shuffle=False,
|
| 173 |
+
num_workers=kwargs["num_workers"],
|
| 174 |
+
)
|
| 175 |
+
|
| 176 |
+
ligmodel, protmodel = _build_encoder(encoder, kwargs)
|
| 177 |
+
model = GenScore(
|
| 178 |
+
ligmodel,
|
| 179 |
+
protmodel,
|
| 180 |
+
in_channels=kwargs["hidden_dim0"],
|
| 181 |
+
hidden_dim=kwargs["hidden_dim"],
|
| 182 |
+
n_gaussians=kwargs["n_gaussians"],
|
| 183 |
+
dropout_rate=kwargs["dropout_rate"],
|
| 184 |
+
dist_threhold=kwargs["dist_threhold"],
|
| 185 |
+
).to(kwargs["device"])
|
| 186 |
+
|
| 187 |
+
checkpoint = th.load(modpath, map_location=th.device(kwargs["device"]))
|
| 188 |
+
model.load_state_dict(checkpoint["model_state_dict"])
|
| 189 |
+
|
| 190 |
+
if atom_contribution:
|
| 191 |
+
preds, at_contrs, _ = run_an_eval_epoch(
|
| 192 |
+
model,
|
| 193 |
+
test_loader,
|
| 194 |
+
pred=True,
|
| 195 |
+
atom_contribution=True,
|
| 196 |
+
res_contribution=False,
|
| 197 |
+
dist_threhold=kwargs["dist_threhold"],
|
| 198 |
+
device=kwargs["device"],
|
| 199 |
+
)
|
| 200 |
+
atids = [f"{a.GetSymbol()}{a.GetIdx()}" for a in data.ligs[0].GetAtoms()]
|
| 201 |
+
return data.ids, preds, atids, at_contrs
|
| 202 |
+
|
| 203 |
+
if res_contribution:
|
| 204 |
+
preds, _, res_contrs = run_an_eval_epoch(
|
| 205 |
+
model,
|
| 206 |
+
test_loader,
|
| 207 |
+
pred=True,
|
| 208 |
+
atom_contribution=False,
|
| 209 |
+
res_contribution=True,
|
| 210 |
+
dist_threhold=kwargs["dist_threhold"],
|
| 211 |
+
device=kwargs["device"],
|
| 212 |
+
)
|
| 213 |
+
universe = mda.Universe(data.prot)
|
| 214 |
+
resids = [
|
| 215 |
+
f"{chain_id}_{resname}{resid}"
|
| 216 |
+
for chain_id, resname, resid in zip(
|
| 217 |
+
universe.residues.chainIDs,
|
| 218 |
+
universe.residues.resnames,
|
| 219 |
+
universe.residues.resids,
|
| 220 |
+
)
|
| 221 |
+
]
|
| 222 |
+
return data.ids, preds, resids, res_contrs
|
| 223 |
+
|
| 224 |
+
preds = run_an_eval_epoch(
|
| 225 |
+
model,
|
| 226 |
+
test_loader,
|
| 227 |
+
pred=True,
|
| 228 |
+
dist_threhold=kwargs["dist_threhold"],
|
| 229 |
+
device=kwargs["device"],
|
| 230 |
+
)
|
| 231 |
+
return data.ids, preds
|
| 232 |
+
|
| 233 |
+
|
| 234 |
+
def _runtime_args(args):
|
| 235 |
+
return {
|
| 236 |
+
"batch_size": args.batch_size,
|
| 237 |
+
"dist_threhold": 5.0,
|
| 238 |
+
"device": "cuda" if th.cuda.is_available() else "cpu",
|
| 239 |
+
"num_workers": args.num_workers,
|
| 240 |
+
"num_node_featsp": 41,
|
| 241 |
+
"num_node_featsl": 41,
|
| 242 |
+
"num_edge_featsp": 5,
|
| 243 |
+
"num_edge_featsl": 10,
|
| 244 |
+
"hidden_dim0": 128,
|
| 245 |
+
"hidden_dim": 128,
|
| 246 |
+
"n_gaussians": 10,
|
| 247 |
+
"dropout_rate": 0.15,
|
| 248 |
+
}
|
| 249 |
+
|
| 250 |
+
|
| 251 |
+
def main():
|
| 252 |
+
inargs = parse_args()
|
| 253 |
+
runtime = _runtime_args(inargs)
|
| 254 |
+
common = {
|
| 255 |
+
"prot": inargs.prot,
|
| 256 |
+
"lig": inargs.lig,
|
| 257 |
+
"modpath": inargs.model,
|
| 258 |
+
"cut": inargs.cutoff,
|
| 259 |
+
"gen_pocket": inargs.gen_pocket,
|
| 260 |
+
"reflig": inargs.reflig,
|
| 261 |
+
"encoder": inargs.encoder,
|
| 262 |
+
"explicit_H": False,
|
| 263 |
+
"use_chirality": True,
|
| 264 |
+
"parallel": inargs.parallel,
|
| 265 |
+
**runtime,
|
| 266 |
+
}
|
| 267 |
+
|
| 268 |
+
if inargs.atom_contribution:
|
| 269 |
+
ids, scores, atids, at_contrs = scoring(atom_contribution=True, **common)
|
| 270 |
+
df = pd.DataFrame(at_contrs).T
|
| 271 |
+
df.columns = ids
|
| 272 |
+
df.index = atids
|
| 273 |
+
df = df[df.apply(np.sum, axis=1) != 0].T
|
| 274 |
+
dfx = pd.DataFrame(zip(*(ids, scores)), columns=["id", "score"])
|
| 275 |
+
dfx.index = dfx.id
|
| 276 |
+
df = pd.concat([dfx["score"], df], axis=1)
|
| 277 |
+
df.sort_values("score", ascending=False, inplace=True)
|
| 278 |
+
df.to_csv(f"{inargs.outprefix}_at.csv")
|
| 279 |
+
elif inargs.res_contribution:
|
| 280 |
+
ids, scores, resids, res_contrs = scoring(res_contribution=True, **common)
|
| 281 |
+
df = pd.DataFrame(res_contrs).T
|
| 282 |
+
df.columns = ids
|
| 283 |
+
df.index = resids
|
| 284 |
+
df = df[df.apply(np.sum, axis=1) != 0].T
|
| 285 |
+
dfx = pd.DataFrame(zip(*(ids, scores)), columns=["id", "score"])
|
| 286 |
+
dfx.index = dfx.id
|
| 287 |
+
df = pd.concat([dfx["score"], df], axis=1)
|
| 288 |
+
df.sort_values("score", ascending=False, inplace=True)
|
| 289 |
+
df.to_csv(f"{inargs.outprefix}_res.csv")
|
| 290 |
+
else:
|
| 291 |
+
ids, scores = scoring(**common)
|
| 292 |
+
df = pd.DataFrame(zip(*(ids, scores)), columns=["id", "score"])
|
| 293 |
+
df.sort_values("score", ascending=False, inplace=True)
|
| 294 |
+
df.to_csv(f"{inargs.outprefix}.csv", index=False)
|
| 295 |
+
|
| 296 |
+
|
| 297 |
+
if __name__ == "__main__":
|
| 298 |
+
main()
|
models/model/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
|
models/model/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (174 Bytes). View file
|
|
|
models/model/__pycache__/model.cpython-311.pyc
ADDED
|
Binary file (27.8 kB). View file
|
|
|
models/model/layer/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
|
models/model/layer/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (180 Bytes). View file
|
|
|
models/model/layer/__pycache__/gatedgcn_layer.cpython-311.pyc
ADDED
|
Binary file (6.99 kB). View file
|
|
|
models/model/layer/gatedgcn_layer.py
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import torch.nn as nn
|
| 3 |
+
import torch.nn.functional as F
|
| 4 |
+
import torch_geometric.nn as pyg_nn
|
| 5 |
+
from torch_geometric.graphgym.models.layer import LayerConfig
|
| 6 |
+
from torch_scatter import scatter
|
| 7 |
+
|
| 8 |
+
from torch_geometric.graphgym.config import cfg
|
| 9 |
+
from torch_geometric.graphgym.register import register_layer
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class GatedGCNLayer(pyg_nn.conv.MessagePassing):
|
| 13 |
+
"""
|
| 14 |
+
GatedGCN layer
|
| 15 |
+
Residual Gated Graph ConvNets
|
| 16 |
+
https://arxiv.org/pdf/1711.07553.pdf
|
| 17 |
+
"""
|
| 18 |
+
def __init__(self, in_dim, out_dim, dropout, residual,
|
| 19 |
+
equivstable_pe=False, **kwargs):
|
| 20 |
+
super().__init__(**kwargs)
|
| 21 |
+
self.A = pyg_nn.Linear(in_dim, out_dim, bias=True)
|
| 22 |
+
self.B = pyg_nn.Linear(in_dim, out_dim, bias=True)
|
| 23 |
+
self.C = pyg_nn.Linear(in_dim, out_dim, bias=True)
|
| 24 |
+
self.D = pyg_nn.Linear(in_dim, out_dim, bias=True)
|
| 25 |
+
self.E = pyg_nn.Linear(in_dim, out_dim, bias=True)
|
| 26 |
+
|
| 27 |
+
# Handling for Equivariant and Stable PE using LapPE
|
| 28 |
+
# ICLR 2022 https://openreview.net/pdf?id=e95i1IHcWj
|
| 29 |
+
self.EquivStablePE = equivstable_pe
|
| 30 |
+
if self.EquivStablePE:
|
| 31 |
+
self.mlp_r_ij = nn.Sequential(
|
| 32 |
+
nn.Linear(1, out_dim), nn.ReLU(),
|
| 33 |
+
nn.Linear(out_dim, 1),
|
| 34 |
+
nn.Sigmoid())
|
| 35 |
+
|
| 36 |
+
self.bn_node_x = nn.BatchNorm1d(out_dim)
|
| 37 |
+
self.bn_edge_e = nn.BatchNorm1d(out_dim)
|
| 38 |
+
self.dropout = dropout
|
| 39 |
+
self.residual = residual
|
| 40 |
+
self.e = None
|
| 41 |
+
|
| 42 |
+
def forward(self, batch):
|
| 43 |
+
x, e, edge_index = batch.x, batch.edge_attr, batch.edge_index
|
| 44 |
+
|
| 45 |
+
"""
|
| 46 |
+
x : [n_nodes, in_dim]
|
| 47 |
+
e : [n_edges, in_dim]
|
| 48 |
+
edge_index : [2, n_edges]
|
| 49 |
+
"""
|
| 50 |
+
if self.residual:
|
| 51 |
+
x_in = x
|
| 52 |
+
e_in = e
|
| 53 |
+
|
| 54 |
+
Ax = self.A(x)
|
| 55 |
+
Bx = self.B(x)
|
| 56 |
+
Ce = self.C(e)
|
| 57 |
+
Dx = self.D(x)
|
| 58 |
+
Ex = self.E(x)
|
| 59 |
+
|
| 60 |
+
# Handling for Equivariant and Stable PE using LapPE
|
| 61 |
+
# ICLR 2022 https://openreview.net/pdf?id=e95i1IHcWj
|
| 62 |
+
pe_LapPE = batch.pe_EquivStableLapPE if self.EquivStablePE else None
|
| 63 |
+
|
| 64 |
+
x, e = self.propagate(edge_index,
|
| 65 |
+
Bx=Bx, Dx=Dx, Ex=Ex, Ce=Ce,
|
| 66 |
+
e=e, Ax=Ax,
|
| 67 |
+
PE=pe_LapPE)
|
| 68 |
+
|
| 69 |
+
x = self.bn_node_x(x)
|
| 70 |
+
e = self.bn_edge_e(e)
|
| 71 |
+
|
| 72 |
+
x = F.relu(x)
|
| 73 |
+
e = F.relu(e)
|
| 74 |
+
|
| 75 |
+
x = F.dropout(x, self.dropout, training=self.training)
|
| 76 |
+
e = F.dropout(e, self.dropout, training=self.training)
|
| 77 |
+
|
| 78 |
+
if self.residual:
|
| 79 |
+
x = x_in + x
|
| 80 |
+
e = e_in + e
|
| 81 |
+
|
| 82 |
+
batch.x = x
|
| 83 |
+
batch.edge_attr = e
|
| 84 |
+
|
| 85 |
+
return batch
|
| 86 |
+
|
| 87 |
+
def message(self, Dx_i, Ex_j, PE_i, PE_j, Ce):
|
| 88 |
+
"""
|
| 89 |
+
{}x_i : [n_edges, out_dim]
|
| 90 |
+
{}x_j : [n_edges, out_dim]
|
| 91 |
+
{}e : [n_edges, out_dim]
|
| 92 |
+
"""
|
| 93 |
+
e_ij = Dx_i + Ex_j + Ce
|
| 94 |
+
sigma_ij = torch.sigmoid(e_ij)
|
| 95 |
+
|
| 96 |
+
# Handling for Equivariant and Stable PE using LapPE
|
| 97 |
+
# ICLR 2022 https://openreview.net/pdf?id=e95i1IHcWj
|
| 98 |
+
if self.EquivStablePE:
|
| 99 |
+
r_ij = ((PE_i - PE_j) ** 2).sum(dim=-1, keepdim=True)
|
| 100 |
+
r_ij = self.mlp_r_ij(r_ij) # the MLP is 1 dim --> hidden_dim --> 1 dim
|
| 101 |
+
sigma_ij = sigma_ij * r_ij
|
| 102 |
+
|
| 103 |
+
self.e = e_ij
|
| 104 |
+
return sigma_ij
|
| 105 |
+
|
| 106 |
+
def aggregate(self, sigma_ij, index, Bx_j, Bx):
|
| 107 |
+
"""
|
| 108 |
+
sigma_ij : [n_edges, out_dim] ; is the output from message() function
|
| 109 |
+
index : [n_edges]
|
| 110 |
+
{}x_j : [n_edges, out_dim]
|
| 111 |
+
"""
|
| 112 |
+
dim_size = Bx.shape[0] # or None ?? <--- Double check this
|
| 113 |
+
|
| 114 |
+
sum_sigma_x = sigma_ij * Bx_j
|
| 115 |
+
numerator_eta_xj = scatter(sum_sigma_x, index, 0, None, dim_size,
|
| 116 |
+
reduce='sum')
|
| 117 |
+
|
| 118 |
+
sum_sigma = sigma_ij
|
| 119 |
+
denominator_eta_xj = scatter(sum_sigma, index, 0, None, dim_size,
|
| 120 |
+
reduce='sum')
|
| 121 |
+
|
| 122 |
+
out = numerator_eta_xj / (denominator_eta_xj + 1e-6)
|
| 123 |
+
return out
|
| 124 |
+
|
| 125 |
+
def update(self, aggr_out, Ax):
|
| 126 |
+
"""
|
| 127 |
+
aggr_out : [n_nodes, out_dim] ; is the output from aggregate() function after the aggregation
|
| 128 |
+
{}x : [n_nodes, out_dim]
|
| 129 |
+
"""
|
| 130 |
+
x = Ax + aggr_out
|
| 131 |
+
e_out = self.e
|
| 132 |
+
del self.e
|
| 133 |
+
return x, e_out
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
@register_layer('gatedgcnconv')
|
| 137 |
+
class GatedGCNGraphGymLayer(nn.Module):
|
| 138 |
+
"""GatedGCN layer.
|
| 139 |
+
Residual Gated Graph ConvNets
|
| 140 |
+
https://arxiv.org/pdf/1711.07553.pdf
|
| 141 |
+
"""
|
| 142 |
+
def __init__(self, layer_config: LayerConfig, **kwargs):
|
| 143 |
+
super().__init__()
|
| 144 |
+
self.model = GatedGCNLayer(in_dim=layer_config.dim_in,
|
| 145 |
+
out_dim=layer_config.dim_out,
|
| 146 |
+
dropout=0., # Dropout is handled by GraphGym's `GeneralLayer` wrapper
|
| 147 |
+
residual=False, # Residual connections are handled by GraphGym's `GNNStackStage` wrapper
|
| 148 |
+
**kwargs)
|
| 149 |
+
|
| 150 |
+
def forward(self, batch):
|
| 151 |
+
return self.model(batch)
|
models/model/model.py
ADDED
|
@@ -0,0 +1,563 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch as th
|
| 2 |
+
import torch.nn.functional as F
|
| 3 |
+
import numpy as np
|
| 4 |
+
from torch_scatter import scatter_add
|
| 5 |
+
from torch_geometric.utils import to_dense_batch
|
| 6 |
+
from torch import nn
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def glorot_orthogonal(tensor, scale):
|
| 10 |
+
"""Initialize a tensor's values according to an orthogonal Glorot initialization scheme."""
|
| 11 |
+
if tensor is not None:
|
| 12 |
+
th.nn.init.orthogonal_(tensor.data)
|
| 13 |
+
scale /= ((tensor.size(-2) + tensor.size(-1)) * tensor.var())
|
| 14 |
+
tensor.data *= scale.sqrt()
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
class MultiHeadAttentionLayer(nn.Module):
|
| 18 |
+
"""Compute attention scores with a DGLGraph's node and edge (geometric) features."""
|
| 19 |
+
def __init__(self, num_input_feats, num_output_feats,
|
| 20 |
+
num_heads, using_bias=False, update_edge_feats=True):
|
| 21 |
+
super(MultiHeadAttentionLayer, self).__init__()
|
| 22 |
+
|
| 23 |
+
# Declare shared variables
|
| 24 |
+
self.num_output_feats = num_output_feats
|
| 25 |
+
self.num_heads = num_heads
|
| 26 |
+
self.using_bias = using_bias
|
| 27 |
+
self.update_edge_feats = update_edge_feats
|
| 28 |
+
|
| 29 |
+
# Define node features' query, key, and value tensors, and define edge features' projection tensors
|
| 30 |
+
self.Q = nn.Linear(num_input_feats, self.num_output_feats * self.num_heads, bias=using_bias)
|
| 31 |
+
self.K = nn.Linear(num_input_feats, self.num_output_feats * self.num_heads, bias=using_bias)
|
| 32 |
+
self.V = nn.Linear(num_input_feats, self.num_output_feats * self.num_heads, bias=using_bias)
|
| 33 |
+
self.edge_feats_projection = nn.Linear(num_input_feats, self.num_output_feats * self.num_heads, bias=using_bias)
|
| 34 |
+
|
| 35 |
+
self.reset_parameters()
|
| 36 |
+
|
| 37 |
+
def reset_parameters(self):
|
| 38 |
+
"""Reinitialize learnable parameters."""
|
| 39 |
+
scale = 2.0
|
| 40 |
+
if self.using_bias:
|
| 41 |
+
glorot_orthogonal(self.Q.weight, scale=scale)
|
| 42 |
+
self.Q.bias.data.fill_(0)
|
| 43 |
+
|
| 44 |
+
glorot_orthogonal(self.K.weight, scale=scale)
|
| 45 |
+
self.K.bias.data.fill_(0)
|
| 46 |
+
|
| 47 |
+
glorot_orthogonal(self.V.weight, scale=scale)
|
| 48 |
+
self.V.bias.data.fill_(0)
|
| 49 |
+
|
| 50 |
+
glorot_orthogonal(self.edge_feats_projection.weight, scale=scale)
|
| 51 |
+
self.edge_feats_projection.bias.data.fill_(0)
|
| 52 |
+
else:
|
| 53 |
+
glorot_orthogonal(self.Q.weight, scale=scale)
|
| 54 |
+
glorot_orthogonal(self.K.weight, scale=scale)
|
| 55 |
+
glorot_orthogonal(self.V.weight, scale=scale)
|
| 56 |
+
glorot_orthogonal(self.edge_feats_projection.weight, scale=scale)
|
| 57 |
+
|
| 58 |
+
def propagate_attention(self, edge_index, node_feats_q, node_feats_k, node_feats_v, edge_feats_projection):
|
| 59 |
+
row, col = edge_index
|
| 60 |
+
e_out = None
|
| 61 |
+
# Compute attention scores
|
| 62 |
+
alpha = node_feats_k[row] * node_feats_q[col]
|
| 63 |
+
# Scale and clip attention scores
|
| 64 |
+
alpha = (alpha / np.sqrt(self.num_output_feats)).clamp(-5.0,5.0)
|
| 65 |
+
# Use available edge features to modify the attention scores
|
| 66 |
+
alpha = alpha * edge_feats_projection
|
| 67 |
+
# Copy edge features as e_out to be passed to edge_feats_MLP
|
| 68 |
+
if self.update_edge_feats:
|
| 69 |
+
e_out = alpha
|
| 70 |
+
|
| 71 |
+
# Apply softmax to attention scores, followed by clipping
|
| 72 |
+
alphax = th.exp((alpha.sum(-1, keepdim=True)).clamp(-5.0,5.0))
|
| 73 |
+
# Send weighted values to target nodes
|
| 74 |
+
wV = scatter_add(node_feats_v[row]*alphax, col, dim=0, dim_size=node_feats_q.size(0))
|
| 75 |
+
z = scatter_add(alphax, col, dim=0, dim_size=node_feats_q.size(0))
|
| 76 |
+
return wV, z, e_out
|
| 77 |
+
|
| 78 |
+
def forward(self, x, edge_attr, edge_index):
|
| 79 |
+
node_feats_q = self.Q(x).view(-1, self.num_heads, self.num_output_feats)
|
| 80 |
+
node_feats_k = self.K(x).view(-1, self.num_heads, self.num_output_feats)
|
| 81 |
+
node_feats_v = self.V(x).view(-1, self.num_heads, self.num_output_feats)
|
| 82 |
+
edge_feats_projection = self.edge_feats_projection(edge_attr).view(-1, self.num_heads, self.num_output_feats)
|
| 83 |
+
wV, z, e_out = self.propagate_attention(edge_index, node_feats_q, node_feats_k, node_feats_v, edge_feats_projection)
|
| 84 |
+
|
| 85 |
+
h_out = wV / (z + th.full_like(z, 1e-6))
|
| 86 |
+
return h_out, e_out
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
class GraphTransformerModule(nn.Module):
|
| 90 |
+
"""A Graph Transformer module (equivalent to one layer of graph convolutions)."""
|
| 91 |
+
def __init__(
|
| 92 |
+
self,
|
| 93 |
+
num_hidden_channels,
|
| 94 |
+
activ_fn=nn.SiLU(),
|
| 95 |
+
residual=True,
|
| 96 |
+
num_attention_heads=4,
|
| 97 |
+
norm_to_apply='batch',
|
| 98 |
+
dropout_rate=0.1,
|
| 99 |
+
num_layers=4,
|
| 100 |
+
):
|
| 101 |
+
super(GraphTransformerModule, self).__init__()
|
| 102 |
+
|
| 103 |
+
# Record parameters given
|
| 104 |
+
self.activ_fn = activ_fn
|
| 105 |
+
self.residual = residual
|
| 106 |
+
self.num_attention_heads = num_attention_heads
|
| 107 |
+
self.norm_to_apply = norm_to_apply
|
| 108 |
+
self.dropout_rate = dropout_rate
|
| 109 |
+
self.num_layers = num_layers
|
| 110 |
+
|
| 111 |
+
# --------------------
|
| 112 |
+
# Transformer Module
|
| 113 |
+
# --------------------
|
| 114 |
+
# Define all modules related to a Geometric Transformer module
|
| 115 |
+
self.apply_layer_norm = 'layer' in self.norm_to_apply.lower()
|
| 116 |
+
|
| 117 |
+
self.num_hidden_channels, self.num_output_feats = num_hidden_channels, num_hidden_channels
|
| 118 |
+
if self.apply_layer_norm:
|
| 119 |
+
self.layer_norm1_node_feats = nn.LayerNorm(self.num_output_feats)
|
| 120 |
+
self.layer_norm1_edge_feats = nn.LayerNorm(self.num_output_feats)
|
| 121 |
+
else: # Otherwise, default to using batch normalization
|
| 122 |
+
self.batch_norm1_node_feats = nn.BatchNorm1d(self.num_output_feats)
|
| 123 |
+
self.batch_norm1_edge_feats = nn.BatchNorm1d(self.num_output_feats)
|
| 124 |
+
|
| 125 |
+
self.mha_module = MultiHeadAttentionLayer(
|
| 126 |
+
self.num_hidden_channels,
|
| 127 |
+
self.num_output_feats // self.num_attention_heads,
|
| 128 |
+
self.num_attention_heads,
|
| 129 |
+
self.num_hidden_channels != self.num_output_feats, # Only use bias if a Linear() has to change sizes
|
| 130 |
+
update_edge_feats=True
|
| 131 |
+
)
|
| 132 |
+
|
| 133 |
+
self.O_node_feats = nn.Linear(self.num_output_feats, self.num_output_feats)
|
| 134 |
+
self.O_edge_feats = nn.Linear(self.num_output_feats, self.num_output_feats)
|
| 135 |
+
|
| 136 |
+
# MLP for node features
|
| 137 |
+
dropout = nn.Dropout(p=self.dropout_rate) if self.dropout_rate > 0.0 else nn.Identity()
|
| 138 |
+
self.node_feats_MLP = nn.ModuleList([
|
| 139 |
+
nn.Linear(self.num_output_feats, self.num_output_feats * 2, bias=False),
|
| 140 |
+
self.activ_fn,
|
| 141 |
+
dropout,
|
| 142 |
+
nn.Linear(self.num_output_feats * 2, self.num_output_feats, bias=False)
|
| 143 |
+
])
|
| 144 |
+
|
| 145 |
+
if self.apply_layer_norm:
|
| 146 |
+
self.layer_norm2_node_feats = nn.LayerNorm(self.num_output_feats)
|
| 147 |
+
self.layer_norm2_edge_feats = nn.LayerNorm(self.num_output_feats)
|
| 148 |
+
else: # Otherwise, default to using batch normalization
|
| 149 |
+
self.batch_norm2_node_feats = nn.BatchNorm1d(self.num_output_feats)
|
| 150 |
+
self.batch_norm2_edge_feats = nn.BatchNorm1d(self.num_output_feats)
|
| 151 |
+
|
| 152 |
+
# MLP for edge features
|
| 153 |
+
self.edge_feats_MLP = nn.ModuleList([
|
| 154 |
+
nn.Linear(self.num_output_feats, self.num_output_feats * 2, bias=False),
|
| 155 |
+
self.activ_fn,
|
| 156 |
+
dropout,
|
| 157 |
+
nn.Linear(self.num_output_feats * 2, self.num_output_feats, bias=False)
|
| 158 |
+
])
|
| 159 |
+
|
| 160 |
+
self.reset_parameters()
|
| 161 |
+
|
| 162 |
+
def reset_parameters(self):
|
| 163 |
+
"""Reinitialize learnable parameters."""
|
| 164 |
+
scale = 2.0
|
| 165 |
+
glorot_orthogonal(self.O_node_feats.weight, scale=scale)
|
| 166 |
+
self.O_node_feats.bias.data.fill_(0)
|
| 167 |
+
glorot_orthogonal(self.O_edge_feats.weight, scale=scale)
|
| 168 |
+
self.O_edge_feats.bias.data.fill_(0)
|
| 169 |
+
|
| 170 |
+
for layer in self.node_feats_MLP:
|
| 171 |
+
if hasattr(layer, 'weight'): # Skip initialization for activation functions
|
| 172 |
+
glorot_orthogonal(layer.weight, scale=scale)
|
| 173 |
+
|
| 174 |
+
for layer in self.edge_feats_MLP:
|
| 175 |
+
if hasattr(layer, 'weight'):
|
| 176 |
+
glorot_orthogonal(layer.weight, scale=scale)
|
| 177 |
+
|
| 178 |
+
def run_gt_layer(self, data, node_feats, edge_feats):
|
| 179 |
+
"""Perform a forward pass of geometric attention using a multi-head attention (MHA) module."""
|
| 180 |
+
node_feats_in1 = node_feats # Cache node representations for first residual connection
|
| 181 |
+
edge_feats_in1 = edge_feats # Cache edge representations for first residual connection
|
| 182 |
+
|
| 183 |
+
# Apply first round of normalization before applying geometric attention, for performance enhancement
|
| 184 |
+
if self.apply_layer_norm:
|
| 185 |
+
node_feats = self.layer_norm1_node_feats(node_feats)
|
| 186 |
+
edge_feats = self.layer_norm1_edge_feats(edge_feats)
|
| 187 |
+
else: # Otherwise, default to using batch normalization
|
| 188 |
+
node_feats = self.batch_norm1_node_feats(node_feats)
|
| 189 |
+
edge_feats = self.batch_norm1_edge_feats(edge_feats)
|
| 190 |
+
|
| 191 |
+
# Get multi-head attention output using provided node and edge representations
|
| 192 |
+
node_attn_out, edge_attn_out = self.mha_module(node_feats, edge_feats, data.edge_index)
|
| 193 |
+
|
| 194 |
+
node_feats = node_attn_out.view(-1, self.num_output_feats)
|
| 195 |
+
edge_feats = edge_attn_out.view(-1, self.num_output_feats)
|
| 196 |
+
|
| 197 |
+
node_feats = F.dropout(node_feats, self.dropout_rate, training=self.training)
|
| 198 |
+
edge_feats = F.dropout(edge_feats, self.dropout_rate, training=self.training)
|
| 199 |
+
|
| 200 |
+
node_feats = self.O_node_feats(node_feats)
|
| 201 |
+
edge_feats = self.O_edge_feats(edge_feats)
|
| 202 |
+
|
| 203 |
+
# Make first residual connection
|
| 204 |
+
if self.residual:
|
| 205 |
+
node_feats = node_feats_in1 + node_feats # Make first node residual connection
|
| 206 |
+
edge_feats = edge_feats_in1 + edge_feats # Make first edge residual connection
|
| 207 |
+
|
| 208 |
+
node_feats_in2 = node_feats # Cache node representations for second residual connection
|
| 209 |
+
edge_feats_in2 = edge_feats # Cache edge representations for second residual connection
|
| 210 |
+
|
| 211 |
+
# Apply second round of normalization after first residual connection has been made
|
| 212 |
+
if self.apply_layer_norm:
|
| 213 |
+
node_feats = self.layer_norm2_node_feats(node_feats)
|
| 214 |
+
edge_feats = self.layer_norm2_edge_feats(edge_feats)
|
| 215 |
+
else: # Otherwise, default to using batch normalization
|
| 216 |
+
node_feats = self.batch_norm2_node_feats(node_feats)
|
| 217 |
+
edge_feats = self.batch_norm2_edge_feats(edge_feats)
|
| 218 |
+
|
| 219 |
+
# Apply MLPs for node and edge features
|
| 220 |
+
for layer in self.node_feats_MLP:
|
| 221 |
+
node_feats = layer(node_feats)
|
| 222 |
+
for layer in self.edge_feats_MLP:
|
| 223 |
+
edge_feats = layer(edge_feats)
|
| 224 |
+
|
| 225 |
+
# Make second residual connection
|
| 226 |
+
if self.residual:
|
| 227 |
+
node_feats = node_feats_in2 + node_feats # Make second node residual connection
|
| 228 |
+
edge_feats = edge_feats_in2 + edge_feats # Make second edge residual connection
|
| 229 |
+
|
| 230 |
+
# Return edge representations along with node representations (for tasks other than interface prediction)
|
| 231 |
+
return node_feats, edge_feats
|
| 232 |
+
|
| 233 |
+
def forward(self, data, node_feats, edge_feats):
|
| 234 |
+
"""Perform a forward pass of a Geometric Transformer to get intermediate node and edge representations."""
|
| 235 |
+
node_feats, edge_feats = self.run_gt_layer(data, node_feats, edge_feats)
|
| 236 |
+
return node_feats, edge_feats
|
| 237 |
+
|
| 238 |
+
|
| 239 |
+
class FinalGraphTransformerModule(nn.Module):
|
| 240 |
+
"""A (final layer) Graph Transformer module that combines node and edge representations using self-attention."""
|
| 241 |
+
def __init__(self,
|
| 242 |
+
num_hidden_channels,
|
| 243 |
+
activ_fn=nn.SiLU(),
|
| 244 |
+
residual=True,
|
| 245 |
+
num_attention_heads=4,
|
| 246 |
+
norm_to_apply='batch',
|
| 247 |
+
dropout_rate=0.1,
|
| 248 |
+
num_layers=4):
|
| 249 |
+
super(FinalGraphTransformerModule, self).__init__()
|
| 250 |
+
|
| 251 |
+
# Record parameters given
|
| 252 |
+
self.activ_fn = activ_fn
|
| 253 |
+
self.residual = residual
|
| 254 |
+
self.num_attention_heads = num_attention_heads
|
| 255 |
+
self.norm_to_apply = norm_to_apply
|
| 256 |
+
self.dropout_rate = dropout_rate
|
| 257 |
+
self.num_layers = num_layers
|
| 258 |
+
|
| 259 |
+
# --------------------
|
| 260 |
+
# Transformer Module
|
| 261 |
+
# --------------------
|
| 262 |
+
# Define all modules related to a Geometric Transformer module
|
| 263 |
+
self.apply_layer_norm = 'layer' in self.norm_to_apply.lower()
|
| 264 |
+
|
| 265 |
+
self.num_hidden_channels, self.num_output_feats = num_hidden_channels, num_hidden_channels
|
| 266 |
+
if self.apply_layer_norm:
|
| 267 |
+
self.layer_norm1_node_feats = nn.LayerNorm(self.num_output_feats)
|
| 268 |
+
self.layer_norm1_edge_feats = nn.LayerNorm(self.num_output_feats)
|
| 269 |
+
else: # Otherwise, default to using batch normalization
|
| 270 |
+
self.batch_norm1_node_feats = nn.BatchNorm1d(self.num_output_feats)
|
| 271 |
+
self.batch_norm1_edge_feats = nn.BatchNorm1d(self.num_output_feats)
|
| 272 |
+
|
| 273 |
+
self.mha_module = MultiHeadAttentionLayer(
|
| 274 |
+
self.num_hidden_channels,
|
| 275 |
+
self.num_output_feats // self.num_attention_heads,
|
| 276 |
+
self.num_attention_heads,
|
| 277 |
+
self.num_hidden_channels != self.num_output_feats, # Only use bias if a Linear() has to change sizes
|
| 278 |
+
update_edge_feats=False)
|
| 279 |
+
|
| 280 |
+
self.O_node_feats = nn.Linear(self.num_output_feats, self.num_output_feats)
|
| 281 |
+
|
| 282 |
+
# MLP for node features
|
| 283 |
+
dropout = nn.Dropout(p=self.dropout_rate) if self.dropout_rate > 0.0 else nn.Identity()
|
| 284 |
+
self.node_feats_MLP = nn.ModuleList([
|
| 285 |
+
nn.Linear(self.num_output_feats, self.num_output_feats * 2, bias=False),
|
| 286 |
+
self.activ_fn,
|
| 287 |
+
dropout,
|
| 288 |
+
nn.Linear(self.num_output_feats * 2, self.num_output_feats, bias=False)
|
| 289 |
+
])
|
| 290 |
+
|
| 291 |
+
if self.apply_layer_norm:
|
| 292 |
+
self.layer_norm2_node_feats = nn.LayerNorm(self.num_output_feats)
|
| 293 |
+
else: # Otherwise, default to using batch normalization
|
| 294 |
+
self.batch_norm2_node_feats = nn.BatchNorm1d(self.num_output_feats)
|
| 295 |
+
|
| 296 |
+
self.reset_parameters()
|
| 297 |
+
|
| 298 |
+
def reset_parameters(self):
|
| 299 |
+
"""Reinitialize learnable parameters."""
|
| 300 |
+
scale = 2.0
|
| 301 |
+
glorot_orthogonal(self.O_node_feats.weight, scale=scale)
|
| 302 |
+
self.O_node_feats.bias.data.fill_(0)
|
| 303 |
+
|
| 304 |
+
for layer in self.node_feats_MLP:
|
| 305 |
+
if hasattr(layer, 'weight'): # Skip initialization for activation functions
|
| 306 |
+
glorot_orthogonal(layer.weight, scale=scale)
|
| 307 |
+
|
| 308 |
+
#glorot_orthogonal(self.conformation_module.weight, scale=scale)
|
| 309 |
+
|
| 310 |
+
def run_gt_layer(self, data, node_feats, edge_feats):
|
| 311 |
+
"""Perform a forward pass of geometric attention using a multi-head attention (MHA) module."""
|
| 312 |
+
node_feats_in1 = node_feats # Cache node representations for first residual connection
|
| 313 |
+
#edge_feats = self.conformation_module(edge_feats)
|
| 314 |
+
|
| 315 |
+
# Apply first round of normalization before applying geometric attention, for performance enhancement
|
| 316 |
+
if self.apply_layer_norm:
|
| 317 |
+
node_feats = self.layer_norm1_node_feats(node_feats)
|
| 318 |
+
edge_feats = self.layer_norm1_edge_feats(edge_feats)
|
| 319 |
+
else: # Otherwise, default to using batch normalization
|
| 320 |
+
node_feats = self.batch_norm1_node_feats(node_feats)
|
| 321 |
+
edge_feats = self.batch_norm1_edge_feats(edge_feats)
|
| 322 |
+
|
| 323 |
+
# Get multi-head attention output using provided node and edge representations
|
| 324 |
+
node_attn_out, _ = self.mha_module(node_feats, edge_feats, data.edge_index)
|
| 325 |
+
node_feats = node_attn_out.view(-1, self.num_output_feats)
|
| 326 |
+
node_feats = F.dropout(node_feats, self.dropout_rate, training=self.training)
|
| 327 |
+
node_feats = self.O_node_feats(node_feats)
|
| 328 |
+
|
| 329 |
+
# Make first residual connection
|
| 330 |
+
if self.residual:
|
| 331 |
+
node_feats = node_feats_in1 + node_feats # Make first node residual connection
|
| 332 |
+
|
| 333 |
+
node_feats_in2 = node_feats # Cache node representations for second residual connection
|
| 334 |
+
|
| 335 |
+
# Apply second round of normalization after first residual connection has been made
|
| 336 |
+
if self.apply_layer_norm:
|
| 337 |
+
node_feats = self.layer_norm2_node_feats(node_feats)
|
| 338 |
+
else: # Otherwise, default to using batch normalization
|
| 339 |
+
node_feats = self.batch_norm2_node_feats(node_feats)
|
| 340 |
+
|
| 341 |
+
# Apply MLP for node features
|
| 342 |
+
for layer in self.node_feats_MLP:
|
| 343 |
+
node_feats = layer(node_feats)
|
| 344 |
+
|
| 345 |
+
# Make second residual connection
|
| 346 |
+
if self.residual:
|
| 347 |
+
node_feats = node_feats_in2 + node_feats # Make second node residual connection
|
| 348 |
+
|
| 349 |
+
# Return node representations
|
| 350 |
+
return node_feats
|
| 351 |
+
|
| 352 |
+
def forward(self, data, node_feats, edge_feats):
|
| 353 |
+
"""Perform a forward pass of a Geometric Transformer to get final node representations."""
|
| 354 |
+
node_feats = self.run_gt_layer(data, node_feats, edge_feats)
|
| 355 |
+
return node_feats
|
| 356 |
+
|
| 357 |
+
|
| 358 |
+
class GraphTransformer(nn.Module):
|
| 359 |
+
"""A graph transformer
|
| 360 |
+
"""
|
| 361 |
+
def __init__(
|
| 362 |
+
self,
|
| 363 |
+
in_channels,
|
| 364 |
+
edge_features=10,
|
| 365 |
+
num_hidden_channels=128,
|
| 366 |
+
activ_fn=nn.SiLU(),
|
| 367 |
+
transformer_residual=True,
|
| 368 |
+
num_attention_heads=4,
|
| 369 |
+
norm_to_apply='batch',
|
| 370 |
+
dropout_rate=0.1,
|
| 371 |
+
num_layers=4,
|
| 372 |
+
**kwargs
|
| 373 |
+
):
|
| 374 |
+
super(GraphTransformer, self).__init__()
|
| 375 |
+
|
| 376 |
+
# Initialize model parameters
|
| 377 |
+
self.activ_fn = activ_fn
|
| 378 |
+
self.transformer_residual = transformer_residual
|
| 379 |
+
self.num_attention_heads = num_attention_heads
|
| 380 |
+
self.norm_to_apply = norm_to_apply
|
| 381 |
+
self.dropout_rate = dropout_rate
|
| 382 |
+
self.num_layers = num_layers
|
| 383 |
+
|
| 384 |
+
# --------------------
|
| 385 |
+
# Initializer Modules
|
| 386 |
+
# --------------------
|
| 387 |
+
# Define all modules related to edge and node initialization
|
| 388 |
+
self.node_encoder = nn.Linear(in_channels, num_hidden_channels)
|
| 389 |
+
self.edge_encoder = nn.Linear(edge_features, num_hidden_channels)
|
| 390 |
+
# --------------------
|
| 391 |
+
# Transformer Module
|
| 392 |
+
# --------------------
|
| 393 |
+
# Define all modules related to a variable number of Geometric Transformer modules
|
| 394 |
+
num_intermediate_layers = max(0, num_layers - 1)
|
| 395 |
+
gt_block_modules = [GraphTransformerModule(
|
| 396 |
+
num_hidden_channels=num_hidden_channels,
|
| 397 |
+
activ_fn=activ_fn,
|
| 398 |
+
residual=transformer_residual,
|
| 399 |
+
num_attention_heads=num_attention_heads,
|
| 400 |
+
norm_to_apply=norm_to_apply,
|
| 401 |
+
dropout_rate=dropout_rate,
|
| 402 |
+
num_layers=num_layers) for _ in range(num_intermediate_layers)]
|
| 403 |
+
if num_layers > 0:
|
| 404 |
+
gt_block_modules.extend([
|
| 405 |
+
FinalGraphTransformerModule(
|
| 406 |
+
num_hidden_channels=num_hidden_channels,
|
| 407 |
+
activ_fn=activ_fn,
|
| 408 |
+
residual=transformer_residual,
|
| 409 |
+
num_attention_heads=num_attention_heads,
|
| 410 |
+
norm_to_apply=norm_to_apply,
|
| 411 |
+
dropout_rate=dropout_rate,
|
| 412 |
+
num_layers=num_layers)])
|
| 413 |
+
self.gt_block = nn.ModuleList(gt_block_modules)
|
| 414 |
+
|
| 415 |
+
def forward(self, data):
|
| 416 |
+
node_feats = self.node_encoder(data.x)
|
| 417 |
+
edge_feats = self.edge_encoder(data.edge_attr)
|
| 418 |
+
|
| 419 |
+
# Apply a given number of intermediate geometric attention layers to the node and edge features given
|
| 420 |
+
for gt_layer in self.gt_block[:-1]:
|
| 421 |
+
node_feats, edge_feats = gt_layer(data, node_feats, edge_feats)
|
| 422 |
+
|
| 423 |
+
# Apply final layer to update node representations by merging current node and edge representations
|
| 424 |
+
node_feats = self.gt_block[-1](data, node_feats, edge_feats)
|
| 425 |
+
data.x = node_feats
|
| 426 |
+
data.edge_attr = edge_feats
|
| 427 |
+
#return node_feats
|
| 428 |
+
return data
|
| 429 |
+
|
| 430 |
+
#==============================================
|
| 431 |
+
from .layer.gatedgcn_layer import GatedGCNLayer
|
| 432 |
+
|
| 433 |
+
|
| 434 |
+
class GatedGCN(nn.Module):
|
| 435 |
+
"""A graph transformer
|
| 436 |
+
"""
|
| 437 |
+
def __init__(
|
| 438 |
+
self,
|
| 439 |
+
in_channels,
|
| 440 |
+
edge_features=10,
|
| 441 |
+
num_hidden_channels=128,
|
| 442 |
+
dropout_rate=0.1,
|
| 443 |
+
num_layers=4,
|
| 444 |
+
residual=True,
|
| 445 |
+
equivstable_pe=False,
|
| 446 |
+
**kwargs
|
| 447 |
+
):
|
| 448 |
+
super(GatedGCN, self).__init__()
|
| 449 |
+
|
| 450 |
+
# Initialize model parameters
|
| 451 |
+
self.residual = residual
|
| 452 |
+
self.dropout_rate = dropout_rate
|
| 453 |
+
self.num_layers = num_layers
|
| 454 |
+
|
| 455 |
+
self.node_encoder = nn.Linear(in_channels, num_hidden_channels)
|
| 456 |
+
self.edge_encoder = nn.Linear(edge_features, num_hidden_channels)
|
| 457 |
+
|
| 458 |
+
gt_block_modules = [GatedGCNLayer(
|
| 459 |
+
num_hidden_channels,
|
| 460 |
+
num_hidden_channels,
|
| 461 |
+
dropout_rate,
|
| 462 |
+
residual,
|
| 463 |
+
equivstable_pe=equivstable_pe) for _ in range(num_layers)]
|
| 464 |
+
|
| 465 |
+
self.gt_block = nn.ModuleList(gt_block_modules)
|
| 466 |
+
|
| 467 |
+
def forward(self, data):
|
| 468 |
+
data.x = self.node_encoder(data.x)
|
| 469 |
+
data.edge_attr = self.edge_encoder(data.edge_attr)
|
| 470 |
+
|
| 471 |
+
# Apply a given number of intermediate geometric attention layers to the node and edge features given
|
| 472 |
+
for gt_layer in self.gt_block:
|
| 473 |
+
data = gt_layer(data)
|
| 474 |
+
|
| 475 |
+
# Apply final layer to update node representations by merging current node and edge representations
|
| 476 |
+
#data.x = node_feats
|
| 477 |
+
#data.edge_attr = edge_feats
|
| 478 |
+
#return node_feats
|
| 479 |
+
return data
|
| 480 |
+
|
| 481 |
+
#==============================================
|
| 482 |
+
class GenScore(nn.Module):
|
| 483 |
+
def __init__(self, ligand_model, target_model, in_channels, hidden_dim, n_gaussians, dropout_rate=0.15,
|
| 484 |
+
dist_threhold=1000):
|
| 485 |
+
super(GenScore, self).__init__()
|
| 486 |
+
|
| 487 |
+
self.ligand_model = ligand_model
|
| 488 |
+
self.target_model = target_model
|
| 489 |
+
self.MLP = nn.Sequential(nn.Linear(in_channels*2, hidden_dim),
|
| 490 |
+
nn.BatchNorm1d(hidden_dim),
|
| 491 |
+
nn.ELU(),
|
| 492 |
+
nn.Dropout(p=dropout_rate))
|
| 493 |
+
|
| 494 |
+
self.z_pi = nn.Linear(hidden_dim, n_gaussians)
|
| 495 |
+
self.z_sigma = nn.Linear(hidden_dim, n_gaussians)
|
| 496 |
+
self.z_mu = nn.Linear(hidden_dim, n_gaussians)
|
| 497 |
+
self.atom_types = nn.Linear(in_channels, 17)
|
| 498 |
+
self.bond_types = nn.Linear(in_channels*2, 4)
|
| 499 |
+
|
| 500 |
+
#self.device = 'cuda' if th.cuda.is_available() else 'cpu'
|
| 501 |
+
self.dist_threhold = dist_threhold
|
| 502 |
+
|
| 503 |
+
def forward(self, data_ligand, data_target):
|
| 504 |
+
|
| 505 |
+
h_l = self.ligand_model(data_ligand)
|
| 506 |
+
h_t = self.target_model(data_target)
|
| 507 |
+
|
| 508 |
+
h_l_x, l_mask = to_dense_batch(h_l.x, h_l.batch, fill_value=0)
|
| 509 |
+
h_t_x, t_mask = to_dense_batch(h_t.x, h_t.batch, fill_value=0)
|
| 510 |
+
h_l_pos, _ = to_dense_batch(h_l.pos, h_l.batch, fill_value=0)
|
| 511 |
+
h_t_pos, _ = to_dense_batch(h_t.pos, h_t.batch, fill_value=0)
|
| 512 |
+
|
| 513 |
+
#assert h_l_x.size(0) == h_t_x.size(0), 'Encountered unequal batch-sizes'
|
| 514 |
+
(B, N_l, C_out), N_t = h_l_x.size(), h_t_x.size(1)
|
| 515 |
+
self.B = B
|
| 516 |
+
self.N_l = N_l
|
| 517 |
+
self.N_t = N_t
|
| 518 |
+
|
| 519 |
+
# Combine and mask
|
| 520 |
+
h_l_x = h_l_x.unsqueeze(-2)
|
| 521 |
+
h_l_x = h_l_x.repeat(1, 1, N_t, 1) # [B, N_l, N_t, C_out]
|
| 522 |
+
|
| 523 |
+
h_t_x = h_t_x.unsqueeze(-3)
|
| 524 |
+
h_t_x = h_t_x.repeat(1, N_l, 1, 1) # [B, N_l, N_t, C_out]
|
| 525 |
+
|
| 526 |
+
C = th.cat((h_l_x, h_t_x), -1)
|
| 527 |
+
self.C_mask = C_mask = l_mask.view(B, N_l, 1) & t_mask.view(B, 1, N_t)
|
| 528 |
+
self.C = C = C[C_mask]
|
| 529 |
+
C = self.MLP(C)
|
| 530 |
+
|
| 531 |
+
# Get batch indexes for ligand-target combined features
|
| 532 |
+
C_batch = th.tensor(range(B)).unsqueeze(-1).unsqueeze(-1)
|
| 533 |
+
# LL 2026 UPDATE
|
| 534 |
+
C_mask = C_mask.to(C_batch.device)
|
| 535 |
+
C_batch = C_batch.repeat(1, N_l, N_t)[C_mask]
|
| 536 |
+
|
| 537 |
+
# Outputs
|
| 538 |
+
pi = F.softmax(self.z_pi(C), -1)
|
| 539 |
+
sigma = F.elu(self.z_sigma(C))+1.1
|
| 540 |
+
mu = F.elu(self.z_mu(C))+1
|
| 541 |
+
atom_types = self.atom_types(h_l.x)
|
| 542 |
+
bond_types = self.bond_types(th.cat([h_l.x[h_l.edge_index[0]], h_l.x[h_l.edge_index[1]]], axis=1))
|
| 543 |
+
|
| 544 |
+
|
| 545 |
+
dist = self.compute_euclidean_distances_matrix(h_l_pos, h_t_pos.view(B,-1,3))[C_mask]
|
| 546 |
+
return pi, sigma, mu, dist.unsqueeze(1).detach(), atom_types, bond_types, C_batch
|
| 547 |
+
|
| 548 |
+
def compute_euclidean_distances_matrix(self, X, Y):
|
| 549 |
+
# Based on: https://medium.com/@souravdey/l2-distance-matrix-vectorization-trick-26aa3247ac6c
|
| 550 |
+
# (X-Y)^2 = X^2 + Y^2 -2XY
|
| 551 |
+
X = X.double()
|
| 552 |
+
Y = Y.double()
|
| 553 |
+
|
| 554 |
+
dists = -2 * th.bmm(X, Y.permute(0, 2, 1)) + th.sum(Y**2, axis=-1).unsqueeze(1) + th.sum(X**2, axis=-1).unsqueeze(-1)
|
| 555 |
+
return th.nan_to_num((dists**0.5).view(self.B, self.N_l,-1,24),10000).min(axis=-1)[0]
|
| 556 |
+
|
| 557 |
+
|
| 558 |
+
|
| 559 |
+
|
| 560 |
+
|
| 561 |
+
|
| 562 |
+
|
| 563 |
+
|
models/train.py
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import argparse
|
| 2 |
+
|
| 3 |
+
import numpy as np
|
| 4 |
+
import torch as th
|
| 5 |
+
import torch.multiprocessing
|
| 6 |
+
from torch_geometric.loader import DataLoader
|
| 7 |
+
|
| 8 |
+
from onescience.datapipes.genscore.data import PDBbindDataset
|
| 9 |
+
from models.model.model import GatedGCN, GenScore, GraphTransformer
|
| 10 |
+
from onescience.metrics.genscore.utils import (
|
| 11 |
+
EarlyStopping,
|
| 12 |
+
run_a_train_epoch,
|
| 13 |
+
run_an_eval_epoch,
|
| 14 |
+
set_random_seed,
|
| 15 |
+
)
|
| 16 |
+
|
| 17 |
+
torch.multiprocessing.set_sharing_strategy("file_system")
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def parse_args():
|
| 21 |
+
parser = argparse.ArgumentParser(description="Train GenScore.")
|
| 22 |
+
parser.add_argument("--num_epochs", type=int, default=5000)
|
| 23 |
+
parser.add_argument("--batch_size", type=int, default=64)
|
| 24 |
+
parser.add_argument("--aux_weight", type=float, default=0.001)
|
| 25 |
+
parser.add_argument("--affi_weight", type=float, default=-0.5)
|
| 26 |
+
parser.add_argument("--patience", type=int, default=70)
|
| 27 |
+
parser.add_argument("--num_workers", type=int, default=8)
|
| 28 |
+
parser.add_argument("--model_path", type=str, default="genscore.pth")
|
| 29 |
+
parser.add_argument("--encoder", type=str, choices=["gt", "gatedgcn"], default="gt")
|
| 30 |
+
parser.add_argument("--mode", type=str, choices=["lower", "higher"], default="lower")
|
| 31 |
+
parser.add_argument("--finetune", action="store_true", default=False)
|
| 32 |
+
parser.add_argument("--original_model_path", type=str, default=None)
|
| 33 |
+
parser.add_argument("--lr", type=int, default=3)
|
| 34 |
+
parser.add_argument("--weight_decay", type=int, default=5)
|
| 35 |
+
parser.add_argument("--data_dir", type=str, required=True)
|
| 36 |
+
parser.add_argument("--data_prefix", type=str, default="v2020_train")
|
| 37 |
+
parser.add_argument("--valnum", type=int, default=1500)
|
| 38 |
+
parser.add_argument("--seeds", type=int, default=126)
|
| 39 |
+
parser.add_argument("--hidden_dim0", type=int, default=128)
|
| 40 |
+
parser.add_argument("--hidden_dim", type=int, default=128)
|
| 41 |
+
parser.add_argument("--n_gaussians", type=int, default=10)
|
| 42 |
+
parser.add_argument("--dropout_rate", type=float, default=0.15)
|
| 43 |
+
parser.add_argument("--dist_threhold", type=float, default=7.0)
|
| 44 |
+
parser.add_argument("--dist_threhold2", type=float, default=5.0)
|
| 45 |
+
return parser.parse_args()
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def _build_encoder(args):
|
| 49 |
+
if args.encoder == "gt":
|
| 50 |
+
ligmodel = GraphTransformer(
|
| 51 |
+
in_channels=41,
|
| 52 |
+
edge_features=10,
|
| 53 |
+
num_hidden_channels=args.hidden_dim0,
|
| 54 |
+
activ_fn=th.nn.SiLU(),
|
| 55 |
+
transformer_residual=True,
|
| 56 |
+
num_attention_heads=4,
|
| 57 |
+
norm_to_apply="batch",
|
| 58 |
+
dropout_rate=0.15,
|
| 59 |
+
num_layers=6,
|
| 60 |
+
)
|
| 61 |
+
protmodel = GraphTransformer(
|
| 62 |
+
in_channels=41,
|
| 63 |
+
edge_features=5,
|
| 64 |
+
num_hidden_channels=args.hidden_dim0,
|
| 65 |
+
activ_fn=th.nn.SiLU(),
|
| 66 |
+
transformer_residual=True,
|
| 67 |
+
num_attention_heads=4,
|
| 68 |
+
norm_to_apply="batch",
|
| 69 |
+
dropout_rate=0.15,
|
| 70 |
+
num_layers=6,
|
| 71 |
+
)
|
| 72 |
+
else:
|
| 73 |
+
ligmodel = GatedGCN(
|
| 74 |
+
in_channels=41,
|
| 75 |
+
edge_features=10,
|
| 76 |
+
num_hidden_channels=args.hidden_dim0,
|
| 77 |
+
residual=True,
|
| 78 |
+
dropout_rate=0.15,
|
| 79 |
+
equivstable_pe=False,
|
| 80 |
+
num_layers=6,
|
| 81 |
+
)
|
| 82 |
+
protmodel = GatedGCN(
|
| 83 |
+
in_channels=41,
|
| 84 |
+
edge_features=5,
|
| 85 |
+
num_hidden_channels=args.hidden_dim0,
|
| 86 |
+
residual=True,
|
| 87 |
+
dropout_rate=0.15,
|
| 88 |
+
equivstable_pe=False,
|
| 89 |
+
num_layers=6,
|
| 90 |
+
)
|
| 91 |
+
return ligmodel, protmodel
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
def main():
|
| 95 |
+
args = parse_args()
|
| 96 |
+
args.device = "cuda" if th.cuda.is_available() else "cpu"
|
| 97 |
+
|
| 98 |
+
data = PDBbindDataset(
|
| 99 |
+
ids=f"{args.data_dir}/{args.data_prefix}_ids.npy",
|
| 100 |
+
ligs=f"{args.data_dir}/{args.data_prefix}_lig.pt",
|
| 101 |
+
prots=f"{args.data_dir}/{args.data_prefix}_prot.pt",
|
| 102 |
+
)
|
| 103 |
+
train_inds, val_inds = data.train_and_test_split(valnum=args.valnum, seed=args.seeds)
|
| 104 |
+
train_data = PDBbindDataset(
|
| 105 |
+
ids=data.pdbids[train_inds],
|
| 106 |
+
ligs=data.gls[train_inds],
|
| 107 |
+
prots=data.gps[train_inds],
|
| 108 |
+
labels=data.labels[train_inds],
|
| 109 |
+
)
|
| 110 |
+
val_data = PDBbindDataset(
|
| 111 |
+
ids=data.pdbids[val_inds],
|
| 112 |
+
ligs=data.gls[val_inds],
|
| 113 |
+
prots=data.gps[val_inds],
|
| 114 |
+
labels=data.labels[val_inds],
|
| 115 |
+
)
|
| 116 |
+
|
| 117 |
+
ligmodel, protmodel = _build_encoder(args)
|
| 118 |
+
model = GenScore(
|
| 119 |
+
ligmodel,
|
| 120 |
+
protmodel,
|
| 121 |
+
in_channels=args.hidden_dim0,
|
| 122 |
+
hidden_dim=args.hidden_dim,
|
| 123 |
+
n_gaussians=args.n_gaussians,
|
| 124 |
+
dropout_rate=args.dropout_rate,
|
| 125 |
+
dist_threhold=args.dist_threhold,
|
| 126 |
+
).to(args.device)
|
| 127 |
+
|
| 128 |
+
if args.finetune:
|
| 129 |
+
if args.original_model_path is None:
|
| 130 |
+
raise ValueError('--original_model_path is required when --finetune is used.')
|
| 131 |
+
checkpoint = th.load(args.original_model_path, map_location=th.device(args.device))
|
| 132 |
+
model.load_state_dict(checkpoint["model_state_dict"])
|
| 133 |
+
|
| 134 |
+
optimizer = th.optim.Adam(
|
| 135 |
+
model.parameters(),
|
| 136 |
+
lr=10**-args.lr,
|
| 137 |
+
weight_decay=10**-args.weight_decay,
|
| 138 |
+
)
|
| 139 |
+
train_loader = DataLoader(
|
| 140 |
+
dataset=train_data,
|
| 141 |
+
batch_size=args.batch_size,
|
| 142 |
+
shuffle=True,
|
| 143 |
+
num_workers=args.num_workers,
|
| 144 |
+
)
|
| 145 |
+
val_loader = DataLoader(
|
| 146 |
+
dataset=val_data,
|
| 147 |
+
batch_size=args.batch_size,
|
| 148 |
+
shuffle=False,
|
| 149 |
+
num_workers=args.num_workers,
|
| 150 |
+
)
|
| 151 |
+
stopper = EarlyStopping(patience=args.patience, mode=args.mode, filename=args.model_path)
|
| 152 |
+
|
| 153 |
+
set_random_seed(args.seeds)
|
| 154 |
+
for epoch in range(args.num_epochs):
|
| 155 |
+
total_loss_train, mdn_loss_train, affi_loss_train, atom_loss_train, bond_loss_train = run_a_train_epoch(
|
| 156 |
+
epoch,
|
| 157 |
+
model,
|
| 158 |
+
train_loader,
|
| 159 |
+
optimizer,
|
| 160 |
+
affi_weight=args.affi_weight,
|
| 161 |
+
aux_weight=args.aux_weight,
|
| 162 |
+
dist_threhold=args.dist_threhold2,
|
| 163 |
+
device=args.device,
|
| 164 |
+
)
|
| 165 |
+
if np.isinf(mdn_loss_train) or np.isnan(mdn_loss_train):
|
| 166 |
+
print("Inf ERROR")
|
| 167 |
+
break
|
| 168 |
+
|
| 169 |
+
total_loss_val, mdn_loss_val, affi_loss_val, atom_loss_val, bond_loss_val = run_an_eval_epoch(
|
| 170 |
+
model,
|
| 171 |
+
val_loader,
|
| 172 |
+
dist_threhold=args.dist_threhold2,
|
| 173 |
+
affi_weight=args.affi_weight,
|
| 174 |
+
aux_weight=args.aux_weight,
|
| 175 |
+
device=args.device,
|
| 176 |
+
)
|
| 177 |
+
early_stop = stopper.step(total_loss_val, model)
|
| 178 |
+
print(
|
| 179 |
+
"epoch {:d}/{:d}, total_loss_val {:.4f}, mdn_loss_val {:.4f}, "
|
| 180 |
+
"affi_loss_val {:.4f}, atom_loss_val {:.4f}, bond_loss_val {:.4f}, "
|
| 181 |
+
"best validation {:.4f}".format(
|
| 182 |
+
epoch + 1,
|
| 183 |
+
args.num_epochs,
|
| 184 |
+
total_loss_val,
|
| 185 |
+
mdn_loss_val,
|
| 186 |
+
affi_loss_val,
|
| 187 |
+
atom_loss_val,
|
| 188 |
+
bond_loss_val,
|
| 189 |
+
stopper.best_score,
|
| 190 |
+
)
|
| 191 |
+
)
|
| 192 |
+
if early_stop:
|
| 193 |
+
break
|
| 194 |
+
|
| 195 |
+
stopper.load_checkpoint(model)
|
| 196 |
+
train_metrics = run_an_eval_epoch(
|
| 197 |
+
model,
|
| 198 |
+
train_loader,
|
| 199 |
+
dist_threhold=args.dist_threhold2,
|
| 200 |
+
affi_weight=args.affi_weight,
|
| 201 |
+
aux_weight=args.aux_weight,
|
| 202 |
+
device=args.device,
|
| 203 |
+
)
|
| 204 |
+
val_metrics = run_an_eval_epoch(
|
| 205 |
+
model,
|
| 206 |
+
val_loader,
|
| 207 |
+
dist_threhold=args.dist_threhold2,
|
| 208 |
+
affi_weight=args.affi_weight,
|
| 209 |
+
aux_weight=args.aux_weight,
|
| 210 |
+
device=args.device,
|
| 211 |
+
)
|
| 212 |
+
print("train metrics:", train_metrics)
|
| 213 |
+
print("validation metrics:", val_metrics)
|
| 214 |
+
|
| 215 |
+
|
| 216 |
+
if __name__ == "__main__":
|
| 217 |
+
main()
|
scripts/genscore.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sys
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
|
| 4 |
+
GENSCORE_DIR = Path(__file__).resolve().parent.parent
|
| 5 |
+
sys.path.insert(0, str(GENSCORE_DIR))
|
| 6 |
+
|
| 7 |
+
from models.inference import main
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
if __name__ == "__main__":
|
| 11 |
+
main()
|
scripts/preprocess_pdbbind.py
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import argparse
|
| 2 |
+
import os
|
| 3 |
+
|
| 4 |
+
import numpy as np
|
| 5 |
+
import pandas as pd
|
| 6 |
+
import torch as th
|
| 7 |
+
from joblib import Parallel, delayed
|
| 8 |
+
|
| 9 |
+
from onescience.datapipes.genscore.feats.mol2graph_rdmda_res import mol_to_graph2
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def parse_args():
|
| 13 |
+
parser = argparse.ArgumentParser(description="Preprocess PDBbind complexes for GenScore training.")
|
| 14 |
+
parser.add_argument(
|
| 15 |
+
"-d",
|
| 16 |
+
"--dir",
|
| 17 |
+
default=".",
|
| 18 |
+
help="Directory containing PDBbind-style protein-ligand complex folders.",
|
| 19 |
+
)
|
| 20 |
+
parser.add_argument(
|
| 21 |
+
"-c",
|
| 22 |
+
"--cutoff",
|
| 23 |
+
default=10.0,
|
| 24 |
+
type=float,
|
| 25 |
+
help="Pocket cutoff used in pocket file names and graph construction.",
|
| 26 |
+
)
|
| 27 |
+
parser.add_argument(
|
| 28 |
+
"-o",
|
| 29 |
+
"--outprefix",
|
| 30 |
+
default="out",
|
| 31 |
+
help="Output prefix for generated _ids.npy, _prot.pt, and _lig.pt files.",
|
| 32 |
+
)
|
| 33 |
+
parser.add_argument(
|
| 34 |
+
"-r",
|
| 35 |
+
"--ref",
|
| 36 |
+
default="pdbbind_2020_general.csv",
|
| 37 |
+
help="CSV containing labels indexed by PDB id with a labels column.",
|
| 38 |
+
)
|
| 39 |
+
parser.add_argument(
|
| 40 |
+
"-usH",
|
| 41 |
+
"--useH",
|
| 42 |
+
default=False,
|
| 43 |
+
action="store_true",
|
| 44 |
+
help="Use explicit hydrogen atoms.",
|
| 45 |
+
)
|
| 46 |
+
parser.add_argument(
|
| 47 |
+
"-uschi",
|
| 48 |
+
"--use_chirality",
|
| 49 |
+
default=False,
|
| 50 |
+
action="store_true",
|
| 51 |
+
help="Use chirality features.",
|
| 52 |
+
)
|
| 53 |
+
parser.add_argument(
|
| 54 |
+
"-p",
|
| 55 |
+
"--parallel",
|
| 56 |
+
default=False,
|
| 57 |
+
action="store_true",
|
| 58 |
+
help="Build graphs in parallel.",
|
| 59 |
+
)
|
| 60 |
+
return parser.parse_args()
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def _label_query(pdbid, labels):
|
| 64 |
+
return labels.loc[pdbid, "labels"]
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
def _pdbbind_paths(root_dir, pdbid, cutoff):
|
| 68 |
+
complex_dir = os.path.join(root_dir, pdbid, f"{pdbid}_prot")
|
| 69 |
+
prot_path = os.path.join(complex_dir, f"{pdbid}_p_pocket_{cutoff}.pdb")
|
| 70 |
+
lig_path = os.path.join(complex_dir, f"{pdbid}_l.sdf")
|
| 71 |
+
return prot_path, lig_path
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
def _pdbbind_handle(pdbid, args, labels):
|
| 75 |
+
prot_path, lig_path = _pdbbind_paths(args.dir, pdbid, args.cutoff)
|
| 76 |
+
try:
|
| 77 |
+
graph_prot, graph_lig = mol_to_graph2(
|
| 78 |
+
prot_path,
|
| 79 |
+
lig_path,
|
| 80 |
+
cutoff=args.cutoff,
|
| 81 |
+
explicit_H=args.useH,
|
| 82 |
+
use_chirality=args.use_chirality,
|
| 83 |
+
)
|
| 84 |
+
except Exception as exc:
|
| 85 |
+
print(f"{pdbid} failed to generate graph: {exc}")
|
| 86 |
+
return None
|
| 87 |
+
return pdbid, graph_prot, graph_lig, _label_query(pdbid, labels)
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
def main():
|
| 91 |
+
args = parse_args()
|
| 92 |
+
labels = pd.read_csv(args.ref, index_col=0, header=0)
|
| 93 |
+
pdbids = [
|
| 94 |
+
name
|
| 95 |
+
for name in os.listdir(args.dir)
|
| 96 |
+
if os.path.isdir(os.path.join(args.dir, name))
|
| 97 |
+
]
|
| 98 |
+
|
| 99 |
+
if args.parallel:
|
| 100 |
+
results = Parallel(n_jobs=-1)(
|
| 101 |
+
delayed(_pdbbind_handle)(pdbid, args, labels) for pdbid in pdbids
|
| 102 |
+
)
|
| 103 |
+
else:
|
| 104 |
+
results = [_pdbbind_handle(pdbid, args, labels) for pdbid in pdbids]
|
| 105 |
+
|
| 106 |
+
results = [item for item in results if item is not None]
|
| 107 |
+
if not results:
|
| 108 |
+
raise RuntimeError("No valid PDBbind complexes were converted.")
|
| 109 |
+
|
| 110 |
+
ids, graphs_p, graphs_l, label_values = list(zip(*results))
|
| 111 |
+
np.save(f"{args.outprefix}_ids", (ids, label_values))
|
| 112 |
+
th.save(graphs_p, f"{args.outprefix}_prot.pt")
|
| 113 |
+
th.save(graphs_l, f"{args.outprefix}_lig.pt")
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
if __name__ == "__main__":
|
| 117 |
+
main()
|
scripts/run_genscore.sh
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
set -euo pipefail
|
| 3 |
+
source ${ROCM_PATH}/cuda/env.sh
|
| 4 |
+
export LD_LIBRARY_PATH="$CONDA_PREFIX/lib:$LD_LIBRARY_PATH"
|
| 5 |
+
export LD_LIBRARY_PATH="$CONDA_PREFIX/lib/python3.11/site-packages/fastpt/torch/lib:$LD_LIBRARY_PATH"
|
| 6 |
+
export LD_LIBRARY_PATH=${ROCM_PATH}/opencl/lib:$LD_LIBRARY_PATH
|
| 7 |
+
|
| 8 |
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
| 9 |
+
OUTPUT_PREFIX="${SCRIPT_DIR}/out"
|
| 10 |
+
ROOT_DIR="$(cd "${SCRIPT_DIR}/../../.." && pwd)"
|
| 11 |
+
|
| 12 |
+
cd "${ROOT_DIR}"
|
| 13 |
+
source ${ROOT_DIR}/env.sh
|
| 14 |
+
|
| 15 |
+
export PYTHONPATH="${ROOT_DIR}/src:${PYTHONPATH:-}"
|
| 16 |
+
|
| 17 |
+
MODEL_DIR="${GENSCORE_MODEL_DIR:-${ONESCIENCE_DATASETS_DIR}/GenScore/trained_models}"
|
| 18 |
+
DATA_DIR="${GENSCORE_DATA_DIR:-${ONESCIENCE_DATASETS_DIR}/GenScore/genscore_data/inferdata}"
|
| 19 |
+
BATCH_SIZE="${GENSCORE_BATCH_SIZE:-8}"
|
| 20 |
+
NUM_WORKERS="${GENSCORE_NUM_WORKERS:-0}"
|
| 21 |
+
|
| 22 |
+
for input_file in 1qkt_decoys.sdf 1qkt_l.sdf 1qkt_p.pdb 1qkt_p_pocket_10.0.pdb; do
|
| 23 |
+
if [[ ! -f "${DATA_DIR}/${input_file}" ]]; then
|
| 24 |
+
echo "Missing required inference file: ${DATA_DIR}/${input_file}" >&2
|
| 25 |
+
exit 1
|
| 26 |
+
fi
|
| 27 |
+
done
|
| 28 |
+
|
| 29 |
+
run_step() {
|
| 30 |
+
local name="$1"
|
| 31 |
+
local output="$2"
|
| 32 |
+
shift 2
|
| 33 |
+
|
| 34 |
+
echo "[run] ${name}"
|
| 35 |
+
"$@"
|
| 36 |
+
echo "[done] ${name}: ${output}"
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
# input is protein (needs to be converted to pocket)
|
| 40 |
+
run_step "GT scoring with generated pocket" "${OUTPUT_PREFIX}_gt.csv" \
|
| 41 |
+
python "${SCRIPT_DIR}/genscore.py" \
|
| 42 |
+
-p "${DATA_DIR}/1qkt_p.pdb" \
|
| 43 |
+
-l "${DATA_DIR}/1qkt_decoys.sdf" \
|
| 44 |
+
-rl "${DATA_DIR}/1qkt_l.sdf" \
|
| 45 |
+
-gen_pocket \
|
| 46 |
+
-c 10.0 \
|
| 47 |
+
-e gt \
|
| 48 |
+
-m "${MODEL_DIR}/GT_0.0_1.pth" \
|
| 49 |
+
-o "${OUTPUT_PREFIX}" \
|
| 50 |
+
--batch_size "${BATCH_SIZE}" \
|
| 51 |
+
--num_workers "${NUM_WORKERS}"
|
| 52 |
+
|
| 53 |
+
# input is pocket
|
| 54 |
+
run_step "GatedGCN scoring with prepared pocket" "${OUTPUT_PREFIX}_out_gatedgcn.csv" \
|
| 55 |
+
python "${SCRIPT_DIR}/genscore.py" \
|
| 56 |
+
-p "${DATA_DIR}/1qkt_p_pocket_10.0.pdb" \
|
| 57 |
+
-l "${DATA_DIR}/1qkt_decoys.sdf" \
|
| 58 |
+
-e gatedgcn \
|
| 59 |
+
-m "${MODEL_DIR}/GatedGCN_0.5_1.pth" \
|
| 60 |
+
-o "${OUTPUT_PREFIX}" \
|
| 61 |
+
--batch_size "${BATCH_SIZE}" \
|
| 62 |
+
--num_workers "${NUM_WORKERS}"
|
| 63 |
+
|
| 64 |
+
# calculate the atom contributions of the score
|
| 65 |
+
run_step "GatedGCN atom contribution scoring" "${OUTPUT_PREFIX}_out_at.csv" \
|
| 66 |
+
python "${SCRIPT_DIR}/genscore.py" \
|
| 67 |
+
-p "${DATA_DIR}/1qkt_p_pocket_10.0.pdb" \
|
| 68 |
+
-l "${DATA_DIR}/1qkt_decoys.sdf" \
|
| 69 |
+
-e gatedgcn \
|
| 70 |
+
-ac \
|
| 71 |
+
-m "${MODEL_DIR}/GatedGCN_ft_1.0_1.pth" \
|
| 72 |
+
-o "${OUTPUT_PREFIX}" \
|
| 73 |
+
--batch_size "${BATCH_SIZE}" \
|
| 74 |
+
--num_workers "${NUM_WORKERS}"
|
| 75 |
+
|
| 76 |
+
# calculate the residue contributions of the score
|
| 77 |
+
run_step "GatedGCN residue contribution scoring" "${OUTPUT_PREFIX}_out_res.csv" \
|
| 78 |
+
python "${SCRIPT_DIR}/genscore.py" \
|
| 79 |
+
-p "${DATA_DIR}/1qkt_p_pocket_10.0.pdb" \
|
| 80 |
+
-l "${DATA_DIR}/1qkt_decoys.sdf" \
|
| 81 |
+
-e gatedgcn \
|
| 82 |
+
-rc \
|
| 83 |
+
-m "${MODEL_DIR}/GatedGCN_ft_1.0_1.pth" \
|
| 84 |
+
-o "${OUTPUT_PREFIX}" \
|
| 85 |
+
--batch_size "${BATCH_SIZE}" \
|
| 86 |
+
--num_workers "${NUM_WORKERS}"
|
| 87 |
+
|
| 88 |
+
echo "[done] All GenScore inference examples completed."
|
scripts/run_genscore_benchmarks.sh
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
set -euo pipefail
|
| 3 |
+
|
| 4 |
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
| 5 |
+
|
| 6 |
+
ROOT_DIR="$(cd "${SCRIPT_DIR}/../../.." && pwd)"
|
| 7 |
+
cd "${ROOT_DIR}"
|
| 8 |
+
|
| 9 |
+
if [[ -n "${ROCM_PATH:-}" && -f "${ROCM_PATH}/cuda/env.sh" ]]; then
|
| 10 |
+
source "${ROCM_PATH}/cuda/env.sh"
|
| 11 |
+
fi
|
| 12 |
+
|
| 13 |
+
source "${ROOT_DIR}/env.sh"
|
| 14 |
+
export PYTHONPATH="${ROOT_DIR}/src:${PYTHONPATH:-}"
|
| 15 |
+
|
| 16 |
+
if [[ -n "${CONDA_PREFIX:-}" ]]; then
|
| 17 |
+
export LD_LIBRARY_PATH="${CONDA_PREFIX}/lib:${CONDA_PREFIX}/lib/python3.11/site-packages/torch/lib:${CONDA_PREFIX}/lib/python3.11/site-packages/fastpt/torch/lib:${LD_LIBRARY_PATH:-}"
|
| 18 |
+
fi
|
| 19 |
+
if [[ -n "${ROCM_PATH:-}" && -d "${ROCM_PATH}/opencl/lib" ]]; then
|
| 20 |
+
export LD_LIBRARY_PATH="${ROCM_PATH}/opencl/lib:${LD_LIBRARY_PATH:-}"
|
| 21 |
+
fi
|
| 22 |
+
|
| 23 |
+
TASK="${1:-all}"
|
| 24 |
+
|
| 25 |
+
GENSCORE_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
| 26 |
+
BENCHMARK_DIR="${GENSCORE_DIR}/benchmarks"
|
| 27 |
+
MODEL_PATH="${MODEL_PATH:-${ONESCIENCE_DATASETS_DIR}/GenScore/trained_models/GatedGCN_ft_1.0_1.pth}"
|
| 28 |
+
OUT_ROOT="${OUT_ROOT:-${GENSCORE_DIR}/benchmark_outputs}"
|
| 29 |
+
|
| 30 |
+
GENSCORE_DATA_ROOT="${GENSCORE_DATA_ROOT:-${ONESCIENCE_DATASETS_DIR}/GenScore/genscore_data}"
|
| 31 |
+
CASF_DIR="${CASF_DIR:-${GENSCORE_DATA_ROOT}/CASF-2016}"
|
| 32 |
+
PDBBIND_DIR="${PDBBIND_DIR:-${GENSCORE_DATA_ROOT}/PDBbind_v2020}"
|
| 33 |
+
PDBBIND_NATIVE_LIGAND_DIR="${PDBBIND_NATIVE_LIGAND_DIR:-${PDBBIND_DIR}/mol2}"
|
| 34 |
+
PDBBIND_REFINED_SUBDIR="${PDBBIND_REFINED_SUBDIR:-refined-set}"
|
| 35 |
+
PDBBIND_OTHER_PL_SUBDIR="${PDBBIND_OTHER_PL_SUBDIR:-v2020-other-PL}"
|
| 36 |
+
CASF_PREPROCESSED_DIR="${CASF_PREPROCESSED_DIR:-${GENSCORE_DATA_ROOT}/rtmscore_s}"
|
| 37 |
+
CASF_TEST_PREFIX="${CASF_TEST_PREFIX:-v2020_casf}"
|
| 38 |
+
CASF_CORESET_FILE="${CASF_CORESET_FILE:-${CASF_PREPROCESSED_DIR}/v2020_casf_coreset.csv}"
|
| 39 |
+
|
| 40 |
+
ENCODER="${ENCODER:-gatedgcn}"
|
| 41 |
+
CUTOFF="${CUTOFF:-10.0}"
|
| 42 |
+
BATCH_SIZE="${BATCH_SIZE:-128}"
|
| 43 |
+
NUM_WORKERS="${NUM_WORKERS:-0}"
|
| 44 |
+
OUTPREFIX="${OUTPREFIX:-gatedgcn1x5}"
|
| 45 |
+
|
| 46 |
+
require_dir() {
|
| 47 |
+
local name="$1"
|
| 48 |
+
local path="$2"
|
| 49 |
+
if [[ ! -d "${path}" ]]; then
|
| 50 |
+
echo "Missing required directory ${name}: ${path}" >&2
|
| 51 |
+
exit 1
|
| 52 |
+
fi
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
require_file() {
|
| 56 |
+
local name="$1"
|
| 57 |
+
local path="$2"
|
| 58 |
+
if [[ ! -f "${path}" ]]; then
|
| 59 |
+
echo "Missing required file ${name}: ${path}" >&2
|
| 60 |
+
exit 1
|
| 61 |
+
fi
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
run_scoring_ranking() {
|
| 65 |
+
require_dir "CASF_PREPROCESSED_DIR" "${CASF_PREPROCESSED_DIR}"
|
| 66 |
+
require_file "CASF_CORESET_FILE" "${CASF_CORESET_FILE}"
|
| 67 |
+
require_file "MODEL_PATH" "${MODEL_PATH}"
|
| 68 |
+
|
| 69 |
+
echo "[run] CASF-2016 scoring/ranking"
|
| 70 |
+
python "${BENCHMARK_DIR}/casf2016_scoring_ranking.py" \
|
| 71 |
+
--data-dir "${CASF_PREPROCESSED_DIR}" \
|
| 72 |
+
--test-prefix "${CASF_TEST_PREFIX}" \
|
| 73 |
+
--coreset-file "${CASF_CORESET_FILE}" \
|
| 74 |
+
--model-path "${MODEL_PATH}" \
|
| 75 |
+
--encoder "${ENCODER}" \
|
| 76 |
+
--cutoff "${CUTOFF}" \
|
| 77 |
+
--batch-size "${BATCH_SIZE}" \
|
| 78 |
+
--num-workers "${NUM_WORKERS}" \
|
| 79 |
+
--outprefix "${OUTPREFIX}" \
|
| 80 |
+
--outdir "${OUT_ROOT}/power_ranking/examples"
|
| 81 |
+
echo "[done] CASF-2016 scoring/ranking"
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
run_docking() {
|
| 85 |
+
require_dir "CASF_DIR" "${CASF_DIR}"
|
| 86 |
+
require_dir "PDBBIND_DIR" "${PDBBIND_DIR}"
|
| 87 |
+
require_dir "PDBBIND_NATIVE_LIGAND_DIR" "${PDBBIND_NATIVE_LIGAND_DIR}"
|
| 88 |
+
require_dir "PDBbind refined subset" "${PDBBIND_DIR}/${PDBBIND_REFINED_SUBDIR}"
|
| 89 |
+
require_dir "PDBbind other-PL subset" "${PDBBIND_DIR}/${PDBBIND_OTHER_PL_SUBDIR}"
|
| 90 |
+
require_file "MODEL_PATH" "${MODEL_PATH}"
|
| 91 |
+
|
| 92 |
+
echo "[run] CASF-2016 docking"
|
| 93 |
+
python "${BENCHMARK_DIR}/casf2016_docking.py" \
|
| 94 |
+
--casf-dir "${CASF_DIR}" \
|
| 95 |
+
--pdbbind-dir "${PDBBIND_DIR}" \
|
| 96 |
+
--native-ligand-dir "${PDBBIND_NATIVE_LIGAND_DIR}" \
|
| 97 |
+
--refined-subdir "${PDBBIND_REFINED_SUBDIR}" \
|
| 98 |
+
--other-pl-subdir "${PDBBIND_OTHER_PL_SUBDIR}" \
|
| 99 |
+
--model-path "${MODEL_PATH}" \
|
| 100 |
+
--encoder "${ENCODER}" \
|
| 101 |
+
--cutoff "${CUTOFF}" \
|
| 102 |
+
--batch-size "${BATCH_SIZE}" \
|
| 103 |
+
--num-workers "${NUM_WORKERS}" \
|
| 104 |
+
--outprefix "${OUTPREFIX}" \
|
| 105 |
+
--outdir "${OUT_ROOT}/power_docking/examples/${OUTPREFIX}"
|
| 106 |
+
echo "[done] CASF-2016 docking"
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
run_screening() {
|
| 110 |
+
require_dir "CASF_DIR" "${CASF_DIR}"
|
| 111 |
+
require_dir "PDBBIND_DIR" "${PDBBIND_DIR}"
|
| 112 |
+
require_dir "PDBbind refined subset" "${PDBBIND_DIR}/${PDBBIND_REFINED_SUBDIR}"
|
| 113 |
+
require_dir "PDBbind other-PL subset" "${PDBBIND_DIR}/${PDBBIND_OTHER_PL_SUBDIR}"
|
| 114 |
+
require_file "MODEL_PATH" "${MODEL_PATH}"
|
| 115 |
+
|
| 116 |
+
echo "[run] CASF-2016 screening"
|
| 117 |
+
python "${BENCHMARK_DIR}/casf2016_screening.py" \
|
| 118 |
+
--casf-dir "${CASF_DIR}" \
|
| 119 |
+
--pdbbind-dir "${PDBBIND_DIR}" \
|
| 120 |
+
--refined-subdir "${PDBBIND_REFINED_SUBDIR}" \
|
| 121 |
+
--other-pl-subdir "${PDBBIND_OTHER_PL_SUBDIR}" \
|
| 122 |
+
--model-path "${MODEL_PATH}" \
|
| 123 |
+
--encoder "${ENCODER}" \
|
| 124 |
+
--cutoff "${CUTOFF}" \
|
| 125 |
+
--batch-size "${BATCH_SIZE}" \
|
| 126 |
+
--num-workers "${NUM_WORKERS}" \
|
| 127 |
+
--outprefix "${OUTPREFIX}" \
|
| 128 |
+
--outdir "${OUT_ROOT}/power_screening/examples/${OUTPREFIX}"
|
| 129 |
+
echo "[done] CASF-2016 screening"
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
case "${TASK}" in
|
| 133 |
+
scoring|scoring_ranking)
|
| 134 |
+
run_scoring_ranking
|
| 135 |
+
;;
|
| 136 |
+
docking)
|
| 137 |
+
run_docking
|
| 138 |
+
;;
|
| 139 |
+
screening)
|
| 140 |
+
run_screening
|
| 141 |
+
;;
|
| 142 |
+
all)
|
| 143 |
+
run_scoring_ranking
|
| 144 |
+
run_docking
|
| 145 |
+
run_screening
|
| 146 |
+
;;
|
| 147 |
+
*)
|
| 148 |
+
echo "Usage: $0 [scoring|docking|screening|all]" >&2
|
| 149 |
+
exit 1
|
| 150 |
+
;;
|
| 151 |
+
esac
|
scripts/train_genscore.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sys
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
|
| 4 |
+
GENSCORE_DIR = Path(__file__).resolve().parent.parent
|
| 5 |
+
sys.path.insert(0, str(GENSCORE_DIR))
|
| 6 |
+
|
| 7 |
+
from models.train import main
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
if __name__ == "__main__":
|
| 11 |
+
main()
|
scripts/train_genscore_full.sh
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
|
| 3 |
+
set -euo pipefail
|
| 4 |
+
source ${ROCM_PATH}/cuda/env.sh
|
| 5 |
+
export LD_LIBRARY_PATH=${ROCM_PATH}/opencl/lib:$LD_LIBRARY_PATH
|
| 6 |
+
|
| 7 |
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
| 8 |
+
|
| 9 |
+
ROOT_DIR="$(cd "${SCRIPT_DIR}/../../.." && pwd)"
|
| 10 |
+
echo ${ROOT_DIR}
|
| 11 |
+
source ${ROOT_DIR}/env.sh
|
| 12 |
+
|
| 13 |
+
cd "${ROOT_DIR}"
|
| 14 |
+
|
| 15 |
+
export PYTHONPATH="${ROOT_DIR}/src:${PYTHONPATH:-}"
|
| 16 |
+
|
| 17 |
+
if [[ -n "${CONDA_PREFIX:-}" ]]; then
|
| 18 |
+
export LD_LIBRARY_PATH="${CONDA_PREFIX}/lib:${CONDA_PREFIX}/lib/python3.11/site-packages/torch/lib:${CONDA_PREFIX}/lib/python3.11/site-packages/fastpt/torch/lib:/opt/dtk/.hyhal/rocm_smi/lib:${LD_LIBRARY_PATH:-}"
|
| 19 |
+
fi
|
| 20 |
+
|
| 21 |
+
GENSCORE_DATA_DIR="${GENSCORE_DATA_DIR:-${ONESCIENCE_DATASETS_DIR}/GenScore/genscore_data/rtmscore_s}"
|
| 22 |
+
GENSCORE_DATA_PREFIX="${GENSCORE_DATA_PREFIX:-v2020_train}"
|
| 23 |
+
GENSCORE_ENCODER="${GENSCORE_ENCODER:-gatedgcn}"
|
| 24 |
+
GENSCORE_MODEL_PATH="${GENSCORE_MODEL_PATH:-${SCRIPT_DIR}/genscore_${GENSCORE_ENCODER}_full_3000.pth}"
|
| 25 |
+
|
| 26 |
+
GENSCORE_NUM_EPOCHS="${GENSCORE_NUM_EPOCHS:-3000}"
|
| 27 |
+
GENSCORE_BATCH_SIZE="${GENSCORE_BATCH_SIZE:-64}"
|
| 28 |
+
GENSCORE_NUM_WORKERS="${GENSCORE_NUM_WORKERS:-8}"
|
| 29 |
+
GENSCORE_VALNUM="${GENSCORE_VALNUM:-1500}"
|
| 30 |
+
GENSCORE_PATIENCE="${GENSCORE_PATIENCE:-70}"
|
| 31 |
+
|
| 32 |
+
for suffix in ids.npy lig.pt prot.pt; do
|
| 33 |
+
path="${GENSCORE_DATA_DIR}/${GENSCORE_DATA_PREFIX}_${suffix}"
|
| 34 |
+
if [[ ! -f "${path}" ]]; then
|
| 35 |
+
echo "Missing required training file: ${path}" >&2
|
| 36 |
+
exit 1
|
| 37 |
+
fi
|
| 38 |
+
done
|
| 39 |
+
|
| 40 |
+
mkdir -p "$(dirname "${GENSCORE_MODEL_PATH}")"
|
| 41 |
+
|
| 42 |
+
echo "GenScore full training"
|
| 43 |
+
echo " data_dir: ${GENSCORE_DATA_DIR}"
|
| 44 |
+
echo " data_prefix: ${GENSCORE_DATA_PREFIX}"
|
| 45 |
+
echo " model_path: ${GENSCORE_MODEL_PATH}"
|
| 46 |
+
echo " encoder: ${GENSCORE_ENCODER}"
|
| 47 |
+
echo " num_epochs: ${GENSCORE_NUM_EPOCHS}"
|
| 48 |
+
echo " batch_size: ${GENSCORE_BATCH_SIZE}"
|
| 49 |
+
echo " num_workers: ${GENSCORE_NUM_WORKERS}"
|
| 50 |
+
echo " valnum: ${GENSCORE_VALNUM}"
|
| 51 |
+
echo " patience: ${GENSCORE_PATIENCE}"
|
| 52 |
+
|
| 53 |
+
python ${SCRIPT_DIR}/train_genscore.py \
|
| 54 |
+
--data_dir "${GENSCORE_DATA_DIR}" \
|
| 55 |
+
--data_prefix "${GENSCORE_DATA_PREFIX}" \
|
| 56 |
+
--model_path "${GENSCORE_MODEL_PATH}" \
|
| 57 |
+
--num_epochs "${GENSCORE_NUM_EPOCHS}" \
|
| 58 |
+
--batch_size "${GENSCORE_BATCH_SIZE}" \
|
| 59 |
+
--num_workers "${GENSCORE_NUM_WORKERS}" \
|
| 60 |
+
--valnum "${GENSCORE_VALNUM}" \
|
| 61 |
+
--patience "${GENSCORE_PATIENCE}" \
|
| 62 |
+
--encoder "${GENSCORE_ENCODER}"
|
scripts/train_genscore_smoke.sh
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
set -euo pipefail
|
| 3 |
+
source ${ROCM_PATH}/cuda/env.sh
|
| 4 |
+
export LD_LIBRARY_PATH=${ROCM_PATH}/opencl/lib:$LD_LIBRARY_PATH
|
| 5 |
+
|
| 6 |
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
| 7 |
+
|
| 8 |
+
ROOT_DIR="$(cd "${SCRIPT_DIR}/../../.." && pwd)"
|
| 9 |
+
echo ${ROOT_DIR}
|
| 10 |
+
source ${ROOT_DIR}/env.sh
|
| 11 |
+
cd "${ROOT_DIR}"
|
| 12 |
+
|
| 13 |
+
export PYTHONPATH="${ROOT_DIR}/src:${PYTHONPATH:-}"
|
| 14 |
+
|
| 15 |
+
if [[ -n "${CONDA_PREFIX:-}" ]]; then
|
| 16 |
+
export LD_LIBRARY_PATH="${CONDA_PREFIX}/lib:${CONDA_PREFIX}/lib/python3.11/site-packages/torch/lib:${CONDA_PREFIX}/lib/python3.11/site-packages/fastpt/torch/lib:/opt/dtk/.hyhal/rocm_smi/lib:${LD_LIBRARY_PATH:-}"
|
| 17 |
+
fi
|
| 18 |
+
|
| 19 |
+
GENSCORE_DATA_DIR="${GENSCORE_DATA_DIR:-${ONESCIENCE_DATASETS_DIR}/GenScore/genscore_data/rtmscore_s}"
|
| 20 |
+
GENSCORE_DATA_PREFIX="${GENSCORE_DATA_PREFIX:-v2020_train}"
|
| 21 |
+
GENSCORE_MODEL_PATH="${GENSCORE_MODEL_PATH:-${SCRIPT_DIR}/genscore_smoke_bs16.pth}"
|
| 22 |
+
|
| 23 |
+
python ${SCRIPT_DIR}/train_genscore.py \
|
| 24 |
+
--data_dir "${GENSCORE_DATA_DIR}" \
|
| 25 |
+
--data_prefix "${GENSCORE_DATA_PREFIX}" \
|
| 26 |
+
--model_path "${GENSCORE_MODEL_PATH}" \
|
| 27 |
+
--num_epochs 100 \
|
| 28 |
+
--batch_size 16 \
|
| 29 |
+
--num_workers 0 \
|
| 30 |
+
--valnum 1500 \
|
| 31 |
+
--encoder "${GENSCORE_ENCODER:-gatedgcn}"
|