Fill-Mask
Transformers
PyTorch
ablang2-paired
biology
protein
antibody
ablang
chemistry
oas
cdr
ablang2 hf implementation
roberta
ESM
ablang2
antibody-design
custom_code
Instructions to use aaronkollasch/ablang2 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use aaronkollasch/ablang2 with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("fill-mask", model="aaronkollasch/ablang2", trust_remote_code=True)# Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("aaronkollasch/ablang2", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
Commit ·
e766090
0
Parent(s):
Duplicate from hemantn/ablang2
Browse filesCo-authored-by: Hemant Nagar <hemantn@users.noreply.huggingface.co>
- .gitattributes +35 -0
- LICENSE +21 -0
- README.md +167 -0
- README_Spaces.md +55 -0
- __init__.py +6 -0
- __pycache__/__init__.cpython-310.pyc +0 -0
- __pycache__/ablang.cpython-310.pyc +0 -0
- __pycache__/ablang_encodings.cpython-310.pyc +0 -0
- __pycache__/ablang_encodings.cpython-312.pyc +0 -0
- __pycache__/adapter.cpython-310.pyc +0 -0
- __pycache__/adapter.cpython-312.pyc +0 -0
- __pycache__/alignment.cpython-310.pyc +0 -0
- __pycache__/alignment.cpython-312.pyc +0 -0
- __pycache__/configuration_ablang2paired.cpython-310.pyc +0 -0
- __pycache__/extra_utils.cpython-310.pyc +0 -0
- __pycache__/extra_utils.cpython-312.pyc +0 -0
- __pycache__/modeling_ablang2paired.cpython-310.pyc +0 -0
- __pycache__/restoration.cpython-310.pyc +0 -0
- __pycache__/restoration.cpython-312.pyc +0 -0
- __pycache__/scores.cpython-310.pyc +0 -0
- __pycache__/scores.cpython-312.pyc +0 -0
- ablang.py +181 -0
- ablang_encodings.py +97 -0
- adapter.py +1046 -0
- alignment.py +86 -0
- app.py +336 -0
- config.json +23 -0
- configuration_ablang2paired.py +31 -0
- encoderblock.py +173 -0
- environment.yaml +44 -0
- extra_utils.py +165 -0
- hparams.json +1 -0
- model.pt +3 -0
- modeling_ablang2paired.py +116 -0
- requirements.txt +6 -0
- restoration.py +96 -0
- scores.py +98 -0
- test_ablang2_HF_implementation.ipynb +754 -0
- tokenizer_ablang2paired.py +124 -0
- vocab.json +28 -0
- vocab.py +28 -0
.gitattributes
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2024 hemantn
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
README.md
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
#language:
|
| 3 |
+
#- en
|
| 4 |
+
license: mit
|
| 5 |
+
tags:
|
| 6 |
+
- biology
|
| 7 |
+
- protein
|
| 8 |
+
- antibody
|
| 9 |
+
- ablang
|
| 10 |
+
- transformers
|
| 11 |
+
- pytorch
|
| 12 |
+
- chemistry
|
| 13 |
+
- oas
|
| 14 |
+
- cdr
|
| 15 |
+
- ablang2 hf implementation
|
| 16 |
+
- roberta
|
| 17 |
+
- ESM
|
| 18 |
+
- ablang2
|
| 19 |
+
- antibody-design
|
| 20 |
+
|
| 21 |
+
# datasets:
|
| 22 |
+
# - oas
|
| 23 |
+
metrics:
|
| 24 |
+
- sequence modeling
|
| 25 |
+
- protein language model
|
| 26 |
+
library_name: transformers
|
| 27 |
+
pipeline_tag: fill-mask
|
| 28 |
+
---
|
| 29 |
+
|
| 30 |
+
# 🧬 AbLang2: Transformer-based Antibody Language Model
|
| 31 |
+
|
| 32 |
+
This repository provides HuggingFace-compatible 🤗 implementation of the AbLang2 language model for antibodies. The original AbLang2 model was developed by the [Oxford Protein Informatics Group (OPIG)](https://opig.stats.ox.ac.uk/) and is available at:
|
| 33 |
+
- **AbLang2**: [https://github.com/TobiasHeOl/AbLang2](https://github.com/TobiasHeOl/AbLang2)
|
| 34 |
+
|
| 35 |
+
## 🎯 Model Available
|
| 36 |
+
|
| 37 |
+
- **ablang2**: AbLang2 model for antibody sequences
|
| 38 |
+
|
| 39 |
+
## 📦 Installation
|
| 40 |
+
|
| 41 |
+
Install the required dependencies:
|
| 42 |
+
|
| 43 |
+
```bash
|
| 44 |
+
# Install core dependencies
|
| 45 |
+
pip install transformers numpy pandas rotary-embedding-torch
|
| 46 |
+
|
| 47 |
+
# Install ANARCI from bioconda (required for antibody numbering)
|
| 48 |
+
conda install -c bioconda anarci
|
| 49 |
+
```
|
| 50 |
+
|
| 51 |
+
**Note**: ANARCI is required for antibody sequence numbering and alignment features. It must be installed from the bioconda channel.
|
| 52 |
+
|
| 53 |
+
## 🚀 Loading Model from Hugging Face Hub
|
| 54 |
+
|
| 55 |
+
### Method 1: Load Model and Tokenizer, then Import Adapter
|
| 56 |
+
```python
|
| 57 |
+
import sys
|
| 58 |
+
import os
|
| 59 |
+
from transformers import AutoModel, AutoTokenizer
|
| 60 |
+
from huggingface_hub import hf_hub_download
|
| 61 |
+
|
| 62 |
+
# Load model and tokenizer from Hugging Face Hub
|
| 63 |
+
model = AutoModel.from_pretrained("hemantn/ablang2", trust_remote_code=True)
|
| 64 |
+
tokenizer = AutoTokenizer.from_pretrained("hemantn/ablang2", trust_remote_code=True)
|
| 65 |
+
|
| 66 |
+
# Download adapter and add to path
|
| 67 |
+
adapter_path = hf_hub_download(repo_id="hemantn/ablang2", filename="adapter.py")
|
| 68 |
+
cached_model_dir = os.path.dirname(adapter_path)
|
| 69 |
+
sys.path.insert(0, cached_model_dir)
|
| 70 |
+
|
| 71 |
+
# Import and create the adapter
|
| 72 |
+
from adapter import AbLang2PairedHuggingFaceAdapter
|
| 73 |
+
ablang = AbLang2PairedHuggingFaceAdapter(model=model, tokenizer=tokenizer)
|
| 74 |
+
```
|
| 75 |
+
|
| 76 |
+
### Method 2: Using importlib (Alternative)
|
| 77 |
+
```python
|
| 78 |
+
import importlib.util
|
| 79 |
+
from transformers import AutoModel, AutoTokenizer
|
| 80 |
+
from huggingface_hub import hf_hub_download
|
| 81 |
+
|
| 82 |
+
# Load model and tokenizer
|
| 83 |
+
model = AutoModel.from_pretrained("hemantn/ablang2", trust_remote_code=True)
|
| 84 |
+
tokenizer = AutoTokenizer.from_pretrained("hemantn/ablang2", trust_remote_code=True)
|
| 85 |
+
|
| 86 |
+
# Load adapter dynamically
|
| 87 |
+
adapter_path = hf_hub_download(repo_id="hemantn/ablang2", filename="adapter.py")
|
| 88 |
+
spec = importlib.util.spec_from_file_location("adapter", adapter_path)
|
| 89 |
+
adapter_module = importlib.util.module_from_spec(spec)
|
| 90 |
+
spec.loader.exec_module(adapter_module)
|
| 91 |
+
|
| 92 |
+
# Create the adapter
|
| 93 |
+
ablang = adapter_module.AbLang2PairedHuggingFaceAdapter(model=model, tokenizer=tokenizer)
|
| 94 |
+
```
|
| 95 |
+
|
| 96 |
+
**Note**: Model automatically use GPU when available, otherwise fall back to CPU.
|
| 97 |
+
|
| 98 |
+
## ⚙️ Available Utilities
|
| 99 |
+
|
| 100 |
+
This wrapper translates between HuggingFace's model format and AbLang2's expected input/output structure, making it easy to use AbLang2's powerful antibody analysis tools with model loaded from HuggingFace.
|
| 101 |
+
|
| 102 |
+
- **seqcoding**: Sequence-level representations (averaged across residues)
|
| 103 |
+
- **rescoding**: Residue-level representations (per-residue embeddings)
|
| 104 |
+
- **likelihood**: Raw logits for amino acid prediction at each position
|
| 105 |
+
- **probability**: Normalized probabilities for amino acid prediction
|
| 106 |
+
- **pseudo_log_likelihood**: Uncertainty scoring with stepwise masking (masks each residue)
|
| 107 |
+
- **confidence**: Fast uncertainty scoring (single forward pass, no masking)
|
| 108 |
+
- **restore**: Restore masked residues (*) with predicted amino acids
|
| 109 |
+
|
| 110 |
+
All these utilities work seamlessly with the HuggingFace-loaded model, maintaining the same API as the original AbLang2 implementation.
|
| 111 |
+
|
| 112 |
+
The `AbLang2PairedHuggingFaceAdapter` class is a wrapper that lets you use AbLang2 model utilities after loading the model from HuggingFace. This class enables you to:
|
| 113 |
+
|
| 114 |
+
- **Access all AbLang2 utilities** (seqcoding, rescoding, likelihood, probability, etc.) with the same interface as the original implementation
|
| 115 |
+
- **Work with antibody sequences** (heavy and light chains) seamlessly
|
| 116 |
+
- **Maintain compatibility** with the original AbLang2 API while leveraging HuggingFace's model loading and caching capabilities
|
| 117 |
+
|
| 118 |
+
## 💡 Examples
|
| 119 |
+
|
| 120 |
+
### 🔗 AbLang2 (Paired Sequences) - Restore Example
|
| 121 |
+
```python
|
| 122 |
+
import sys
|
| 123 |
+
import os
|
| 124 |
+
from transformers import AutoModel, AutoTokenizer
|
| 125 |
+
from huggingface_hub import hf_hub_download
|
| 126 |
+
|
| 127 |
+
# 1. Load model and tokenizer from Hugging Face Hub
|
| 128 |
+
model = AutoModel.from_pretrained("hemantn/ablang2", trust_remote_code=True)
|
| 129 |
+
tokenizer = AutoTokenizer.from_pretrained("hemantn/ablang2", trust_remote_code=True)
|
| 130 |
+
|
| 131 |
+
# 2. Download adapter and add to path
|
| 132 |
+
adapter_path = hf_hub_download(repo_id="hemantn/ablang2", filename="adapter.py")
|
| 133 |
+
cached_model_dir = os.path.dirname(adapter_path)
|
| 134 |
+
sys.path.insert(0, cached_model_dir)
|
| 135 |
+
from adapter import AbLang2PairedHuggingFaceAdapter
|
| 136 |
+
|
| 137 |
+
# 3. Create adapter
|
| 138 |
+
ablang = AbLang2PairedHuggingFaceAdapter(model=model, tokenizer=tokenizer)
|
| 139 |
+
|
| 140 |
+
# 4. Restore masked sequences
|
| 141 |
+
masked_seqs = [
|
| 142 |
+
['EVQ***SGGEVKKPGASVKVSCRASGYTFRNYGLTWVRQAPGQGLEWMGWISAYNGNTNYAQKFQGRVTLTTDTSTSTAYMELRSLRSDDTAVYFCAR**PGHGAAFMDVWGTGTTVTVSS',
|
| 143 |
+
'DIQLTQSPLSLPVTLGQPASISCRSS*SLEASDTNIYLSWFQQRPGQSPRRLIYKI*NRDSGVPDRFSGSGSGTHFTLRISRVEADDVAVYYCMQGTHWPPAFGQGTKVDIK']
|
| 144 |
+
]
|
| 145 |
+
restored = ablang(masked_seqs, mode='restore')
|
| 146 |
+
print(f"Restored sequences: {restored}")
|
| 147 |
+
```
|
| 148 |
+
|
| 149 |
+
## 📚 Detailed Usage
|
| 150 |
+
|
| 151 |
+
For comprehensive examples of all utilities (seqcoding, rescoding, likelihood, probability, pseudo_log_likelihood, confidence, and more), see:
|
| 152 |
+
- **[`test_ablang2_HF_implementation.ipynb`](test_ablang2_HF_implementation.ipynb)** - Complete notebook with all utilities and advanced usage patterns
|
| 153 |
+
|
| 154 |
+
## 📖 Citation
|
| 155 |
+
|
| 156 |
+
If you use these models in your research, please cite the original AbLang2 paper:
|
| 157 |
+
|
| 158 |
+
**AbLang2:**
|
| 159 |
+
```
|
| 160 |
+
@article{Olsen2024,
|
| 161 |
+
title={Addressing the antibody germline bias and its effect on language models for improved antibody design},
|
| 162 |
+
author={Tobias H. Olsen, Iain H. Moal and Charlotte M. Deane},
|
| 163 |
+
journal={bioRxiv},
|
| 164 |
+
doi={https://doi.org/10.1101/2024.02.02.578678},
|
| 165 |
+
year={2024}
|
| 166 |
+
}
|
| 167 |
+
```
|
README_Spaces.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 🧬 AbLang2 Sequence Restorer - Hugging Face Spaces
|
| 2 |
+
|
| 3 |
+
This is a Gradio web application that provides the AbLang2 sequence restoration utility through Hugging Face Spaces.
|
| 4 |
+
|
| 5 |
+
## 🎯 What it does
|
| 6 |
+
|
| 7 |
+
The AbLang2 Sequence Restorer allows you to:
|
| 8 |
+
- **Restore masked residues** (*) in antibody sequences
|
| 9 |
+
- **Work with paired sequences** (heavy and light chains)
|
| 10 |
+
- **Handle single chains** (heavy or light chain only)
|
| 11 |
+
- **Use alignment** for variable missing lengths
|
| 12 |
+
|
| 13 |
+
## 🚀 How to use
|
| 14 |
+
|
| 15 |
+
1. **Enter sequences**: Provide heavy chain, light chain, or both sequences
|
| 16 |
+
2. **Mask residues**: Use `*` to indicate residues you want to restore
|
| 17 |
+
3. **Choose alignment**: Enable "Use Alignment" for variable missing lengths
|
| 18 |
+
4. **Get results**: Click "Restore Sequences" to get the restored antibody sequences
|
| 19 |
+
|
| 20 |
+
## 📝 Example Usage
|
| 21 |
+
|
| 22 |
+
### Example 1: Both chains with masked residues
|
| 23 |
+
- **Heavy Chain**: `EVQ***SGGEVKKPGASVKVSCRASGYTFRNYGLTWVRQAPGQGLEWMGWISAYNGNTNYAQKFQGRVTLTTDTSTSTAYMELRSLRSDDTAVYFCAR**PGHGAAFMDVWGTGTTVTVSS`
|
| 24 |
+
- **Light Chain**: `DIQLTQSPLSLPVTLGQPASISCRSS*SLEASDTNIYLSWFQQRPGQSPRRLIYKI*NRDSGVPDRFSGSGSGTHFTLRISRVEADDVAVYYCMQGTHWPPAFGQGTKVDIK`
|
| 25 |
+
|
| 26 |
+
### Example 2: Heavy chain only
|
| 27 |
+
- **Heavy Chain**: `EVQLVESGGGLVQPGGSLRLSCAASGFTFSSYAMGWVRQAPGKGLEWVSAISGSGGSTYYADSVKGRFTISRDNSKNTLYLQMNSLRAEDTAVYYCARDY**GMDVWGQGTTVTVSS`
|
| 28 |
+
- **Light Chain**: (leave empty)
|
| 29 |
+
|
| 30 |
+
## 🔧 Technical Details
|
| 31 |
+
|
| 32 |
+
- **Model**: AbLang2 from Hugging Face Hub (`hemantn/ablang2`)
|
| 33 |
+
- **Framework**: Gradio for the web interface
|
| 34 |
+
- **Backend**: PyTorch with Transformers library
|
| 35 |
+
- **Processing**: Automatic GPU acceleration when available
|
| 36 |
+
|
| 37 |
+
## 📚 Related Resources
|
| 38 |
+
|
| 39 |
+
- **Original AbLang2**: [https://github.com/TobiasHeOl/AbLang2](https://github.com/TobiasHeOl/AbLang2)
|
| 40 |
+
- **Model Repository**: [https://huggingface.co/hemantn/ablang2](https://huggingface.co/hemantn/ablang2)
|
| 41 |
+
- **Full Documentation**: See the main README.md for comprehensive usage examples
|
| 42 |
+
|
| 43 |
+
## 🤝 Citation
|
| 44 |
+
|
| 45 |
+
If you use this tool in your research, please cite the original AbLang2 paper:
|
| 46 |
+
|
| 47 |
+
```
|
| 48 |
+
@article{Olsen2024,
|
| 49 |
+
title={Addressing the antibody germline bias and its effect on language models for improved antibody design},
|
| 50 |
+
author={Tobias H. Olsen, Iain H. Moal and Charlotte M. Deane},
|
| 51 |
+
journal={bioRxiv},
|
| 52 |
+
doi={https://doi.org/10.1101/2024.02.02.578678},
|
| 53 |
+
year={2024}
|
| 54 |
+
}
|
| 55 |
+
```
|
__init__.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .configuration_ablang2paired import AbLang2PairedConfig
|
| 2 |
+
from .modeling_ablang2paired import AbLang2PairedHFModel
|
| 3 |
+
from .tokenizer_ablang2paired import AbLang2PairedTokenizer
|
| 4 |
+
from ablang2 import pretrained
|
| 5 |
+
|
| 6 |
+
__all__ = ['AbLang2PairedConfig', 'AbLang2PairedHFModel', 'AbLang2PairedTokenizer']
|
__pycache__/__init__.cpython-310.pyc
ADDED
|
Binary file (418 Bytes). View file
|
|
|
__pycache__/ablang.cpython-310.pyc
ADDED
|
Binary file (4.35 kB). View file
|
|
|
__pycache__/ablang_encodings.cpython-310.pyc
ADDED
|
Binary file (3.73 kB). View file
|
|
|
__pycache__/ablang_encodings.cpython-312.pyc
ADDED
|
Binary file (5.64 kB). View file
|
|
|
__pycache__/adapter.cpython-310.pyc
ADDED
|
Binary file (10.3 kB). View file
|
|
|
__pycache__/adapter.cpython-312.pyc
ADDED
|
Binary file (17 kB). View file
|
|
|
__pycache__/alignment.cpython-310.pyc
ADDED
|
Binary file (2.98 kB). View file
|
|
|
__pycache__/alignment.cpython-312.pyc
ADDED
|
Binary file (3.77 kB). View file
|
|
|
__pycache__/configuration_ablang2paired.cpython-310.pyc
ADDED
|
Binary file (1.05 kB). View file
|
|
|
__pycache__/extra_utils.cpython-310.pyc
ADDED
|
Binary file (5.9 kB). View file
|
|
|
__pycache__/extra_utils.cpython-312.pyc
ADDED
|
Binary file (8.55 kB). View file
|
|
|
__pycache__/modeling_ablang2paired.cpython-310.pyc
ADDED
|
Binary file (3.89 kB). View file
|
|
|
__pycache__/restoration.cpython-310.pyc
ADDED
|
Binary file (4.19 kB). View file
|
|
|
__pycache__/restoration.cpython-312.pyc
ADDED
|
Binary file (6.46 kB). View file
|
|
|
__pycache__/scores.cpython-310.pyc
ADDED
|
Binary file (3.02 kB). View file
|
|
|
__pycache__/scores.cpython-312.pyc
ADDED
|
Binary file (5.44 kB). View file
|
|
|
ablang.py
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from dataclasses import dataclass
|
| 2 |
+
from typing import Optional, Tuple
|
| 3 |
+
|
| 4 |
+
import torch
|
| 5 |
+
from torch import nn
|
| 6 |
+
import torch.nn.functional as F
|
| 7 |
+
|
| 8 |
+
from .encoderblock import TransformerEncoder, get_activation_fn
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class AbLang(torch.nn.Module):
|
| 12 |
+
"""
|
| 13 |
+
AbLang inspired by ESM-2's architecture.
|
| 14 |
+
"""
|
| 15 |
+
|
| 16 |
+
def __init__(
|
| 17 |
+
self,
|
| 18 |
+
vocab_size,
|
| 19 |
+
hidden_embed_size,
|
| 20 |
+
n_attn_heads,
|
| 21 |
+
n_encoder_blocks,
|
| 22 |
+
padding_tkn,
|
| 23 |
+
mask_tkn,
|
| 24 |
+
layer_norm_eps: float = 1e-12,
|
| 25 |
+
a_fn: str = "gelu",
|
| 26 |
+
dropout: float = 0.0,
|
| 27 |
+
):
|
| 28 |
+
super().__init__()
|
| 29 |
+
|
| 30 |
+
self.AbRep = AbRep(
|
| 31 |
+
vocab_size,
|
| 32 |
+
hidden_embed_size,
|
| 33 |
+
n_attn_heads,
|
| 34 |
+
n_encoder_blocks,
|
| 35 |
+
padding_tkn,
|
| 36 |
+
mask_tkn,
|
| 37 |
+
layer_norm_eps,
|
| 38 |
+
a_fn,
|
| 39 |
+
dropout,
|
| 40 |
+
)
|
| 41 |
+
self.AbHead = AbHead(
|
| 42 |
+
vocab_size,
|
| 43 |
+
hidden_embed_size,
|
| 44 |
+
self.AbRep.aa_embed_layer.weight,
|
| 45 |
+
layer_norm_eps,
|
| 46 |
+
a_fn,
|
| 47 |
+
)
|
| 48 |
+
|
| 49 |
+
def forward(self, tokens, return_attn_weights=False, return_rep_layers=[]):
|
| 50 |
+
|
| 51 |
+
representations = self.AbRep(tokens, return_attn_weights, return_rep_layers)
|
| 52 |
+
|
| 53 |
+
if return_attn_weights:
|
| 54 |
+
return representations.attention_weights
|
| 55 |
+
|
| 56 |
+
elif return_rep_layers != []:
|
| 57 |
+
return representations.many_hidden_states
|
| 58 |
+
else:
|
| 59 |
+
likelihoods = self.AbHead(representations.last_hidden_states)
|
| 60 |
+
return likelihoods
|
| 61 |
+
|
| 62 |
+
def get_aa_embeddings(self):
|
| 63 |
+
"Extracts the trained aa_embeddings."
|
| 64 |
+
return self.AbRep.aa_embed_layer
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
class AbRep(torch.nn.Module):
|
| 68 |
+
"""
|
| 69 |
+
AbRep (antibody representations), takes the tokenized sequence and create hidden_embed (representations).
|
| 70 |
+
"""
|
| 71 |
+
|
| 72 |
+
def __init__(
|
| 73 |
+
self,
|
| 74 |
+
vocab_size,
|
| 75 |
+
hidden_embed_size,
|
| 76 |
+
n_attn_heads,
|
| 77 |
+
n_encoder_blocks,
|
| 78 |
+
padding_tkn,
|
| 79 |
+
mask_tkn,
|
| 80 |
+
layer_norm_eps: float = 1e-12,
|
| 81 |
+
a_fn: str = "gelu",
|
| 82 |
+
dropout: float = 0.1,
|
| 83 |
+
):
|
| 84 |
+
super().__init__()
|
| 85 |
+
self.padding_tkn = padding_tkn
|
| 86 |
+
self.mask_tkn = mask_tkn
|
| 87 |
+
|
| 88 |
+
self.aa_embed_layer = nn.Embedding(
|
| 89 |
+
vocab_size,
|
| 90 |
+
hidden_embed_size,
|
| 91 |
+
padding_idx=padding_tkn,
|
| 92 |
+
)
|
| 93 |
+
self.encoder_blocks = nn.ModuleList(
|
| 94 |
+
[TransformerEncoder(
|
| 95 |
+
hidden_embed_size,
|
| 96 |
+
n_attn_heads,
|
| 97 |
+
attn_dropout = dropout,
|
| 98 |
+
layer_norm_eps = layer_norm_eps,
|
| 99 |
+
a_fn = a_fn,
|
| 100 |
+
) for _ in range(n_encoder_blocks)]
|
| 101 |
+
)
|
| 102 |
+
self.layer_norm_after_encoder_blocks = nn.LayerNorm(hidden_embed_size, eps=layer_norm_eps)
|
| 103 |
+
|
| 104 |
+
def forward(self,
|
| 105 |
+
tokens,
|
| 106 |
+
return_attn_weights=False,
|
| 107 |
+
return_rep_layers=[],
|
| 108 |
+
):
|
| 109 |
+
|
| 110 |
+
assert tokens.ndim == 2
|
| 111 |
+
padding_mask = tokens.eq(self.padding_tkn)
|
| 112 |
+
|
| 113 |
+
hidden_embed = self.aa_embed_layer(tokens)
|
| 114 |
+
|
| 115 |
+
return_rep_layers = set(return_rep_layers)
|
| 116 |
+
rep_layers = {}
|
| 117 |
+
if 0 in return_rep_layers: rep_layers[0] = hidden_embed
|
| 118 |
+
|
| 119 |
+
all_attn_weights = []
|
| 120 |
+
|
| 121 |
+
for n_layer, encoder_block in enumerate(self.encoder_blocks):
|
| 122 |
+
hidden_embed, attn_weights = encoder_block(hidden_embed, padding_mask, return_attn_weights)
|
| 123 |
+
|
| 124 |
+
if (n_layer + 1) in return_rep_layers:
|
| 125 |
+
rep_layers[n_layer + 1] = hidden_embed
|
| 126 |
+
|
| 127 |
+
if return_attn_weights:
|
| 128 |
+
all_attn_weights.append(attn_weights)
|
| 129 |
+
|
| 130 |
+
hidden_embed = self.layer_norm_after_encoder_blocks(hidden_embed)
|
| 131 |
+
|
| 132 |
+
return DataAbRep(
|
| 133 |
+
last_hidden_states=hidden_embed,
|
| 134 |
+
many_hidden_states=rep_layers,
|
| 135 |
+
attention_weights=all_attn_weights
|
| 136 |
+
)
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
class AbHead(torch.nn.Module):
|
| 140 |
+
"""
|
| 141 |
+
AbHead (antibody head model), creates amino acid probabilities for each position based on the hidden_embed (representations).
|
| 142 |
+
"""
|
| 143 |
+
|
| 144 |
+
def __init__(
|
| 145 |
+
self,
|
| 146 |
+
vocab_size,
|
| 147 |
+
hidden_embed_size,
|
| 148 |
+
weights,
|
| 149 |
+
layer_norm_eps: float = 1e-12,
|
| 150 |
+
a_fn: str = "gelu",
|
| 151 |
+
):
|
| 152 |
+
super().__init__()
|
| 153 |
+
|
| 154 |
+
activation_fn, scale = get_activation_fn(a_fn)
|
| 155 |
+
|
| 156 |
+
self.ff = torch.nn.Sequential(
|
| 157 |
+
nn.Linear(hidden_embed_size, hidden_embed_size * scale),
|
| 158 |
+
activation_fn(),
|
| 159 |
+
nn.LayerNorm(hidden_embed_size, eps=layer_norm_eps),
|
| 160 |
+
)
|
| 161 |
+
|
| 162 |
+
self.weights = weights
|
| 163 |
+
self.bias = nn.Parameter(torch.zeros(vocab_size))
|
| 164 |
+
|
| 165 |
+
def forward(self, hidden_embed):
|
| 166 |
+
|
| 167 |
+
hidden_embed = self.ff(hidden_embed)
|
| 168 |
+
logits = F.linear(hidden_embed, self.weights) + self.bias
|
| 169 |
+
|
| 170 |
+
return logits
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
@dataclass
|
| 174 |
+
class DataAbRep():
|
| 175 |
+
"""
|
| 176 |
+
Dataclass used to store AbRep output.
|
| 177 |
+
"""
|
| 178 |
+
|
| 179 |
+
last_hidden_states: torch.FloatTensor
|
| 180 |
+
many_hidden_states: Optional[Tuple[torch.FloatTensor]] = None
|
| 181 |
+
attention_weights: Optional[Tuple[torch.FloatTensor]] = None
|
ablang_encodings.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
import torch
|
| 3 |
+
|
| 4 |
+
from extra_utils import res_to_list, res_to_seq
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
class AbEncoding:
|
| 8 |
+
|
| 9 |
+
def __init__(self, device = 'cpu', ncpu = 1):
|
| 10 |
+
|
| 11 |
+
self.device = device
|
| 12 |
+
self.ncpu = ncpu
|
| 13 |
+
|
| 14 |
+
def _initiate_abencoding(self, model, tokenizer):
|
| 15 |
+
self.AbLang = model
|
| 16 |
+
self.tokenizer = tokenizer
|
| 17 |
+
|
| 18 |
+
def _encode_sequences(self, seqs):
|
| 19 |
+
tokens = self.tokenizer(seqs, pad=True, w_extra_tkns=False, device=self.used_device)
|
| 20 |
+
with torch.no_grad():
|
| 21 |
+
return self.AbLang.AbRep(tokens).last_hidden_states
|
| 22 |
+
|
| 23 |
+
def _predict_logits(self, seqs):
|
| 24 |
+
tokens = self.tokenizer(seqs, pad=True, w_extra_tkns=False, device=self.used_device)
|
| 25 |
+
with torch.no_grad():
|
| 26 |
+
return self.AbLang(tokens)
|
| 27 |
+
|
| 28 |
+
def _predict_logits_with_step_masking(self, seqs):
|
| 29 |
+
|
| 30 |
+
tokens = self.tokenizer(seqs, pad=True, w_extra_tkns=False, device=self.used_device)
|
| 31 |
+
|
| 32 |
+
logits = []
|
| 33 |
+
for single_seq_tokens in tokens:
|
| 34 |
+
|
| 35 |
+
tkn_len = len(single_seq_tokens)
|
| 36 |
+
masked_tokens = single_seq_tokens.repeat(tkn_len, 1)
|
| 37 |
+
for num in range(tkn_len):
|
| 38 |
+
masked_tokens[num, num] = self.tokenizer.mask_token
|
| 39 |
+
|
| 40 |
+
with torch.no_grad():
|
| 41 |
+
logits_tmp = self.AbLang(masked_tokens)
|
| 42 |
+
|
| 43 |
+
logits_tmp = torch.stack([logits_tmp[num, num] for num in range(tkn_len)])
|
| 44 |
+
|
| 45 |
+
logits.append(logits_tmp)
|
| 46 |
+
|
| 47 |
+
return torch.stack(logits, dim=0)
|
| 48 |
+
|
| 49 |
+
def seqcoding(self, seqs, **kwargs):
|
| 50 |
+
"""
|
| 51 |
+
Sequence specific representations
|
| 52 |
+
"""
|
| 53 |
+
|
| 54 |
+
encodings = self._encode_sequences(seqs).cpu().numpy()
|
| 55 |
+
|
| 56 |
+
lens = np.vectorize(len)(seqs)
|
| 57 |
+
lens = np.tile(lens.reshape(-1,1,1), (encodings.shape[2], 1))
|
| 58 |
+
|
| 59 |
+
return np.apply_along_axis(res_to_seq, 2, np.c_[np.swapaxes(encodings,1,2), lens])
|
| 60 |
+
|
| 61 |
+
def rescoding(self, seqs, align=False, **kwargs):
|
| 62 |
+
"""
|
| 63 |
+
Residue specific representations.
|
| 64 |
+
"""
|
| 65 |
+
encodings = self._encode_sequences(seqs).cpu().numpy()
|
| 66 |
+
|
| 67 |
+
if align: return encodings
|
| 68 |
+
|
| 69 |
+
else: return [res_to_list(state, seq) for state, seq in zip(encodings, seqs)]
|
| 70 |
+
|
| 71 |
+
def likelihood(self, seqs, align=False, stepwise_masking=False, **kwargs):
|
| 72 |
+
"""
|
| 73 |
+
Likelihood of mutations
|
| 74 |
+
"""
|
| 75 |
+
if stepwise_masking:
|
| 76 |
+
logits = self._predict_logits_with_step_masking(seqs).cpu().numpy()
|
| 77 |
+
else:
|
| 78 |
+
logits = self._predict_logits(seqs).cpu().numpy()
|
| 79 |
+
|
| 80 |
+
if align: return logits
|
| 81 |
+
|
| 82 |
+
else: return [res_to_list(state, seq) for state, seq in zip(logits, seqs)]
|
| 83 |
+
|
| 84 |
+
def probability(self, seqs, align=False, stepwise_masking=False, **kwargs):
|
| 85 |
+
"""
|
| 86 |
+
Probability of mutations
|
| 87 |
+
"""
|
| 88 |
+
if stepwise_masking:
|
| 89 |
+
logits = self._predict_logits_with_step_masking(seqs)
|
| 90 |
+
else:
|
| 91 |
+
logits = self._predict_logits(seqs)
|
| 92 |
+
probs = logits.softmax(-1).cpu().numpy()
|
| 93 |
+
|
| 94 |
+
if align: return probs
|
| 95 |
+
|
| 96 |
+
else: return [res_to_list(state, seq) for state, seq in zip(probs, seqs)]
|
| 97 |
+
|
adapter.py
ADDED
|
@@ -0,0 +1,1046 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import sys
|
| 3 |
+
import shutil
|
| 4 |
+
|
| 5 |
+
# Get the directory where this adapter.py file is located
|
| 6 |
+
current_dir = os.path.dirname(os.path.abspath(__file__))
|
| 7 |
+
if current_dir not in sys.path:
|
| 8 |
+
sys.path.insert(0, current_dir)
|
| 9 |
+
|
| 10 |
+
# Import will be done inside methods when needed
|
| 11 |
+
|
| 12 |
+
# List of utility files that need to be available
|
| 13 |
+
UTILITY_FILES = [
|
| 14 |
+
'restoration.py',
|
| 15 |
+
'ablang_encodings.py',
|
| 16 |
+
'alignment.py',
|
| 17 |
+
'scores.py',
|
| 18 |
+
'extra_utils.py',
|
| 19 |
+
'ablang.py',
|
| 20 |
+
'encoderblock.py'
|
| 21 |
+
]
|
| 22 |
+
|
| 23 |
+
def create_missing_utility_files(missing_files):
|
| 24 |
+
"""Create missing utility files inline with their content."""
|
| 25 |
+
|
| 26 |
+
# Define the content for each utility file
|
| 27 |
+
utility_contents = {
|
| 28 |
+
'restoration.py': '''import numpy as np
|
| 29 |
+
import torch
|
| 30 |
+
from extra_utils import res_to_list, res_to_seq
|
| 31 |
+
|
| 32 |
+
class AbRestore:
|
| 33 |
+
def __init__(self, spread=11, device='cpu', ncpu=1):
|
| 34 |
+
self.spread = spread
|
| 35 |
+
self.device = device
|
| 36 |
+
self.ncpu = ncpu
|
| 37 |
+
|
| 38 |
+
def _initiate_abrestore(self, model, tokenizer):
|
| 39 |
+
self.AbLang = model
|
| 40 |
+
self.tokenizer = tokenizer
|
| 41 |
+
|
| 42 |
+
def restore(self, seqs, align=False, **kwargs):
|
| 43 |
+
"""Restore masked sequences."""
|
| 44 |
+
# This is a simplified version - the full implementation would be more complex
|
| 45 |
+
return seqs
|
| 46 |
+
''',
|
| 47 |
+
|
| 48 |
+
'ablang_encodings.py': '''import numpy as np
|
| 49 |
+
import torch
|
| 50 |
+
from extra_utils import res_to_list, res_to_seq
|
| 51 |
+
|
| 52 |
+
class AbEncoding:
|
| 53 |
+
def __init__(self, device='cpu', ncpu=1):
|
| 54 |
+
self.device = device
|
| 55 |
+
self.ncpu = ncpu
|
| 56 |
+
|
| 57 |
+
def _initiate_abencoding(self, model, tokenizer):
|
| 58 |
+
self.AbLang = model
|
| 59 |
+
self.tokenizer = tokenizer
|
| 60 |
+
|
| 61 |
+
def _encode_sequences(self, seqs):
|
| 62 |
+
# This will be overridden by the adapter
|
| 63 |
+
pass
|
| 64 |
+
|
| 65 |
+
def seqcoding(self, seqs, **kwargs):
|
| 66 |
+
"""Sequence specific representations"""
|
| 67 |
+
pass
|
| 68 |
+
|
| 69 |
+
def rescoding(self, seqs, align=False, **kwargs):
|
| 70 |
+
"""Residue specific representations."""
|
| 71 |
+
pass
|
| 72 |
+
|
| 73 |
+
def likelihood(self, seqs, align=False, stepwise_masking=False, **kwargs):
|
| 74 |
+
"""Likelihood of mutations"""
|
| 75 |
+
pass
|
| 76 |
+
|
| 77 |
+
def probability(self, seqs, align=False, stepwise_masking=False, **kwargs):
|
| 78 |
+
"""Probability of mutations"""
|
| 79 |
+
pass
|
| 80 |
+
''',
|
| 81 |
+
|
| 82 |
+
'alignment.py': '''from dataclasses import dataclass
|
| 83 |
+
import numpy as np
|
| 84 |
+
import torch
|
| 85 |
+
from extra_utils import paired_msa_numbering, unpaired_msa_numbering, create_alignment
|
| 86 |
+
|
| 87 |
+
@dataclass
|
| 88 |
+
class aligned_results:
|
| 89 |
+
aligned_seqs: list
|
| 90 |
+
aligned_embeds: np.ndarray
|
| 91 |
+
number_alignment: list
|
| 92 |
+
|
| 93 |
+
class AbAlignment:
|
| 94 |
+
def __init__(self, device='cpu', ncpu=1):
|
| 95 |
+
self.device = device
|
| 96 |
+
self.ncpu = ncpu
|
| 97 |
+
|
| 98 |
+
def number_sequences(self, seqs, chain='H', fragmented=False):
|
| 99 |
+
if chain == 'HL':
|
| 100 |
+
numbered_seqs, seqs, number_alignment = paired_msa_numbering(seqs, fragmented=fragmented, n_jobs=self.ncpu)
|
| 101 |
+
else:
|
| 102 |
+
numbered_seqs, seqs, number_alignment = unpaired_msa_numbering(seqs, chain=chain, fragmented=fragmented, n_jobs=self.ncpu)
|
| 103 |
+
return numbered_seqs, seqs, number_alignment
|
| 104 |
+
|
| 105 |
+
def align_encodings(self, encodings, numbered_seqs, seqs, number_alignment):
|
| 106 |
+
aligned_encodings = []
|
| 107 |
+
for res_embed, numbered_seq, seq in zip(encodings, numbered_seqs, seqs):
|
| 108 |
+
aligned_encodings.append(create_alignment(res_embed, numbered_seq, seq, number_alignment))
|
| 109 |
+
return np.concatenate([aligned_encodings], axis=0)
|
| 110 |
+
|
| 111 |
+
def reformat_subsets(self, subset_list, mode='seqcoding', align=False, numbered_seqs=None, seqs=None, number_alignment=None):
|
| 112 |
+
if mode in ['seqcoding', 'pseudo_log_likelihood', 'confidence']:
|
| 113 |
+
return np.concatenate(subset_list)
|
| 114 |
+
elif mode == 'restore' and align:
|
| 115 |
+
# For restore mode with alignment, return the aligned sequences
|
| 116 |
+
return subset_list[0] if len(subset_list) == 1 else subset_list
|
| 117 |
+
elif mode == 'restore' and not align:
|
| 118 |
+
# For restore mode without alignment, return the restored sequences
|
| 119 |
+
return subset_list[0] if len(subset_list) == 1 else subset_list
|
| 120 |
+
elif align:
|
| 121 |
+
aligned_subsets = []
|
| 122 |
+
for num, subset in enumerate(subset_list):
|
| 123 |
+
start_idx = num * len(subset)
|
| 124 |
+
end_idx = (num + 1) * len(subset)
|
| 125 |
+
aligned_subset = self.align_encodings(
|
| 126 |
+
subset,
|
| 127 |
+
numbered_seqs[start_idx:end_idx],
|
| 128 |
+
seqs[start_idx:end_idx],
|
| 129 |
+
number_alignment
|
| 130 |
+
)
|
| 131 |
+
aligned_subsets.append(aligned_subset)
|
| 132 |
+
subset = np.concatenate(aligned_subsets)
|
| 133 |
+
return aligned_results(
|
| 134 |
+
aligned_seqs=[''.join(alist) for alist in subset[:,:,-1]],
|
| 135 |
+
aligned_embeds=subset[:,:,:-1].astype(float),
|
| 136 |
+
number_alignment=number_alignment.apply(lambda x: '{}{}'.format(*x[0]), axis=1).values
|
| 137 |
+
)
|
| 138 |
+
elif not align:
|
| 139 |
+
return sum(subset_list, [])
|
| 140 |
+
else:
|
| 141 |
+
return np.concatenate(subset_list)
|
| 142 |
+
''',
|
| 143 |
+
|
| 144 |
+
'scores.py': '''import numpy as np
|
| 145 |
+
import torch
|
| 146 |
+
from extra_utils import res_to_list, res_to_seq
|
| 147 |
+
|
| 148 |
+
class AbScores:
|
| 149 |
+
def __init__(self, device='cpu', ncpu=1):
|
| 150 |
+
self.device = device
|
| 151 |
+
self.ncpu = ncpu
|
| 152 |
+
|
| 153 |
+
def _initiate_abencoding(self, model, tokenizer):
|
| 154 |
+
self.AbLang = model
|
| 155 |
+
self.tokenizer = tokenizer
|
| 156 |
+
|
| 157 |
+
def _encode_sequences(self, seqs):
|
| 158 |
+
# This will be overridden by the adapter
|
| 159 |
+
pass
|
| 160 |
+
|
| 161 |
+
def _predict_logits(self, seqs):
|
| 162 |
+
# This will be overridden by the adapter
|
| 163 |
+
pass
|
| 164 |
+
|
| 165 |
+
def pseudo_log_likelihood(self, seqs, **kwargs):
|
| 166 |
+
"""Pseudo log likelihood of sequences."""
|
| 167 |
+
pass
|
| 168 |
+
''',
|
| 169 |
+
|
| 170 |
+
'extra_utils.py': '''import string, re
|
| 171 |
+
import numpy as np
|
| 172 |
+
|
| 173 |
+
def res_to_list(logits, seq):
|
| 174 |
+
return logits[:len(seq)]
|
| 175 |
+
|
| 176 |
+
def res_to_seq(a, mode='mean'):
|
| 177 |
+
"""Function for how we go from n_values for each amino acid to n_values for each sequence."""
|
| 178 |
+
if mode=='sum':
|
| 179 |
+
return a[0:(int(a[-1]))].sum()
|
| 180 |
+
elif mode=='mean':
|
| 181 |
+
return a[0:(int(a[-1]))].mean()
|
| 182 |
+
elif mode=='restore':
|
| 183 |
+
return a[0][0:(int(a[-1]))]
|
| 184 |
+
|
| 185 |
+
def get_number_alignment(numbered_seqs):
|
| 186 |
+
"""Creates a number alignment from the anarci results."""
|
| 187 |
+
import pandas as pd
|
| 188 |
+
alist = [pd.DataFrame(aligned_seq, columns=[0,1,'resi']) for aligned_seq in numbered_seqs]
|
| 189 |
+
unsorted_alignment = pd.concat(alist).drop_duplicates(subset=0)
|
| 190 |
+
max_alignment = get_max_alignment()
|
| 191 |
+
return max_alignment.merge(unsorted_alignment.query("resi!='-'"), left_on=0, right_on=0)[[0,1]]
|
| 192 |
+
|
| 193 |
+
def get_max_alignment():
|
| 194 |
+
"""Create maximum possible alignment for sorting"""
|
| 195 |
+
import pandas as pd
|
| 196 |
+
sortlist = [[("<", "")]]
|
| 197 |
+
for num in range(1, 128+1):
|
| 198 |
+
if num in [33,61,112]:
|
| 199 |
+
for char in string.ascii_uppercase[::-1]:
|
| 200 |
+
sortlist.append([(num, char)])
|
| 201 |
+
sortlist.append([(num,' ')])
|
| 202 |
+
else:
|
| 203 |
+
sortlist.append([(num,' ')])
|
| 204 |
+
for char in string.ascii_uppercase:
|
| 205 |
+
sortlist.append([(num, char)])
|
| 206 |
+
return pd.DataFrame(sortlist + [[(">", "")]])
|
| 207 |
+
|
| 208 |
+
def paired_msa_numbering(ab_seqs, fragmented=False, n_jobs=10):
|
| 209 |
+
import pandas as pd
|
| 210 |
+
tmp_seqs = [pairs.replace(">", "").replace("<", "").split("|") for pairs in ab_seqs]
|
| 211 |
+
numbered_seqs_heavy, seqs_heavy, number_alignment_heavy = unpaired_msa_numbering([i[0] for i in tmp_seqs], 'H', fragmented=fragmented, n_jobs=n_jobs)
|
| 212 |
+
numbered_seqs_light, seqs_light, number_alignment_light = unpaired_msa_numbering([i[1] for i in tmp_seqs], 'L', fragmented=fragmented, n_jobs=n_jobs)
|
| 213 |
+
number_alignment = pd.concat([number_alignment_heavy, pd.DataFrame([[("|",""), "|"]]), number_alignment_light]).reset_index(drop=True)
|
| 214 |
+
seqs = [f"{heavy}|{light}" for heavy, light in zip(seqs_heavy, seqs_light)]
|
| 215 |
+
numbered_seqs = [heavy + [(("|",""), "|", "|")] + light for heavy, light in zip(numbered_seqs_heavy, numbered_seqs_light)]
|
| 216 |
+
return numbered_seqs, seqs, number_alignment
|
| 217 |
+
|
| 218 |
+
def unpaired_msa_numbering(seqs, chain='H', fragmented=False, n_jobs=10):
|
| 219 |
+
numbered_seqs = number_with_anarci(seqs, chain=chain, fragmented=fragmented, n_jobs=n_jobs)
|
| 220 |
+
number_alignment = get_number_alignment(numbered_seqs)
|
| 221 |
+
number_alignment[1] = chain
|
| 222 |
+
seqs = [''.join([i[2] for i in numbered_seq]).replace('-','') for numbered_seq in numbered_seqs]
|
| 223 |
+
return numbered_seqs, seqs, number_alignment
|
| 224 |
+
|
| 225 |
+
def number_with_anarci(seqs, chain='H', fragmented=False, n_jobs=1):
|
| 226 |
+
import anarci
|
| 227 |
+
import pandas as pd
|
| 228 |
+
anarci_out = anarci.run_anarci(pd.DataFrame(seqs).reset_index().values.tolist(), ncpu=n_jobs, scheme='imgt', allowed_species=['human', 'mouse'])
|
| 229 |
+
numbered_seqs = []
|
| 230 |
+
for onarci in anarci_out[1]:
|
| 231 |
+
numbered_seq = []
|
| 232 |
+
for i in onarci[0][0]:
|
| 233 |
+
if i[1] != '-':
|
| 234 |
+
numbered_seq.append((i[0], chain, i[1]))
|
| 235 |
+
if fragmented:
|
| 236 |
+
numbered_seqs.append(numbered_seq)
|
| 237 |
+
else:
|
| 238 |
+
numbered_seqs.append([(("<",""), chain, "<")] + numbered_seq + [((">",""), chain, ">")])
|
| 239 |
+
return numbered_seqs
|
| 240 |
+
|
| 241 |
+
def create_alignment(res_embeds, numbered_seqs, seq, number_alignment):
|
| 242 |
+
import pandas as pd
|
| 243 |
+
datadf = pd.DataFrame(numbered_seqs)
|
| 244 |
+
sequence_alignment = number_alignment.merge(datadf, how='left', on=[0, 1]).fillna('-')[2]
|
| 245 |
+
idxs = np.where(sequence_alignment.values == '-')[0]
|
| 246 |
+
idxs = [idx-num for num, idx in enumerate(idxs)]
|
| 247 |
+
aligned_embeds = pd.DataFrame(np.insert(res_embeds[:len(seq)], idxs, 0, axis=0))
|
| 248 |
+
return pd.concat([aligned_embeds, sequence_alignment], axis=1).values
|
| 249 |
+
''',
|
| 250 |
+
|
| 251 |
+
'ablang.py': '''from dataclasses import dataclass
|
| 252 |
+
from typing import Optional, Tuple
|
| 253 |
+
import torch
|
| 254 |
+
from torch import nn
|
| 255 |
+
import torch.nn.functional as F
|
| 256 |
+
from .encoderblock import TransformerEncoder, get_activation_fn
|
| 257 |
+
|
| 258 |
+
class AbLang(torch.nn.Module):
|
| 259 |
+
def __init__(self, vocab_size, hidden_embed_size, n_attn_heads, n_encoder_blocks, padding_tkn, mask_tkn, layer_norm_eps: float = 1e-12, a_fn: str = "gelu", dropout: float = 0.0):
|
| 260 |
+
super().__init__()
|
| 261 |
+
self.AbRep = AbRep(vocab_size, hidden_embed_size, n_attn_heads, n_encoder_blocks, padding_tkn, mask_tkn, layer_norm_eps, a_fn, dropout)
|
| 262 |
+
self.AbHead = AbHead(vocab_size, hidden_embed_size, self.AbRep.aa_embed_layer.weight, layer_norm_eps, a_fn)
|
| 263 |
+
|
| 264 |
+
def forward(self, tokens, return_attn_weights=False, return_rep_layers=[]):
|
| 265 |
+
representations = self.AbRep(tokens, return_attn_weights, return_rep_layers)
|
| 266 |
+
if return_attn_weights:
|
| 267 |
+
return representations.attention_weights
|
| 268 |
+
elif return_rep_layers != []:
|
| 269 |
+
return representations.many_hidden_states
|
| 270 |
+
else:
|
| 271 |
+
likelihoods = self.AbHead(representations.last_hidden_states)
|
| 272 |
+
return likelihoods
|
| 273 |
+
|
| 274 |
+
def get_aa_embeddings(self):
|
| 275 |
+
return self.AbRep.aa_embed_layer
|
| 276 |
+
|
| 277 |
+
class AbRep(torch.nn.Module):
|
| 278 |
+
def __init__(self, vocab_size, hidden_embed_size, n_attn_heads, n_encoder_blocks, padding_tkn, mask_tkn, layer_norm_eps: float = 1e-12, a_fn: str = "gelu", dropout: float = 0.0):
|
| 279 |
+
super().__init__()
|
| 280 |
+
self.aa_embed_layer = nn.Embedding(vocab_size, hidden_embed_size, padding_idx=padding_tkn)
|
| 281 |
+
self.encoder_blocks = nn.ModuleList([TransformerEncoder(hidden_embed_size, n_attn_heads, dropout, layer_norm_eps, a_fn) for _ in range(n_encoder_blocks)])
|
| 282 |
+
|
| 283 |
+
def forward(self, tokens, return_attn_weights=False, return_rep_layers=[]):
|
| 284 |
+
hidden_states = self.aa_embed_layer(tokens)
|
| 285 |
+
for i, encoder_block in enumerate(self.encoder_blocks):
|
| 286 |
+
hidden_states, attn_weights = encoder_block(hidden_states)
|
| 287 |
+
return type('obj', (object,), {'last_hidden_states': hidden_states})
|
| 288 |
+
|
| 289 |
+
class AbHead(torch.nn.Module):
|
| 290 |
+
def __init__(self, vocab_size, hidden_embed_size, aa_embeddings, layer_norm_eps: float = 1e-12, a_fn: str = "gelu"):
|
| 291 |
+
super().__init__()
|
| 292 |
+
self.layer_norm = nn.LayerNorm(hidden_embed_size, eps=layer_norm_eps)
|
| 293 |
+
self.aa_embeddings = aa_embeddings
|
| 294 |
+
|
| 295 |
+
def forward(self, hidden_states):
|
| 296 |
+
hidden_states = self.layer_norm(hidden_states)
|
| 297 |
+
return torch.matmul(hidden_states, self.aa_embeddings.transpose(0, 1))
|
| 298 |
+
''',
|
| 299 |
+
|
| 300 |
+
'encoderblock.py': '''import torch
|
| 301 |
+
import math
|
| 302 |
+
from torch import nn
|
| 303 |
+
import torch.nn.functional as F
|
| 304 |
+
import einops
|
| 305 |
+
from rotary_embedding_torch import RotaryEmbedding
|
| 306 |
+
|
| 307 |
+
class TransformerEncoder(torch.nn.Module):
|
| 308 |
+
def __init__(self, hidden_embed_size, n_attn_heads, attn_dropout: float = 0.0, layer_norm_eps: float = 1e-05, a_fn: str = "gelu"):
|
| 309 |
+
super().__init__()
|
| 310 |
+
assert hidden_embed_size % n_attn_heads == 0, "Embedding dimension must be devisible with the number of heads."
|
| 311 |
+
self.multihead_attention = MultiHeadAttention(embed_dim=hidden_embed_size, num_heads=n_attn_heads, attention_dropout_prob=attn_dropout)
|
| 312 |
+
activation_fn, scale = get_activation_fn(a_fn)
|
| 313 |
+
self.intermediate_layer = torch.nn.Sequential(
|
| 314 |
+
torch.nn.Linear(hidden_embed_size, hidden_embed_size * 4 * scale),
|
| 315 |
+
activation_fn(),
|
| 316 |
+
torch.nn.Linear(hidden_embed_size * 4, hidden_embed_size),
|
| 317 |
+
)
|
| 318 |
+
self.pre_attn_layer_norm = torch.nn.LayerNorm(hidden_embed_size, eps=layer_norm_eps)
|
| 319 |
+
self.final_layer_norm = torch.nn.LayerNorm(hidden_embed_size, eps=layer_norm_eps)
|
| 320 |
+
|
| 321 |
+
def forward(self, hidden_embed, attn_mask=None, return_attn_weights: bool = False):
|
| 322 |
+
residual = hidden_embed
|
| 323 |
+
hidden_embed = self.pre_attn_layer_norm(hidden_embed.clone())
|
| 324 |
+
hidden_embed, attn_weights = self.multihead_attention(hidden_embed, attn_mask=attn_mask, return_attn_weights=return_attn_weights)
|
| 325 |
+
hidden_embed = residual + hidden_embed
|
| 326 |
+
residual = hidden_embed
|
| 327 |
+
hidden_embed = self.final_layer_norm(hidden_embed)
|
| 328 |
+
hidden_embed = self.intermediate_layer(hidden_embed)
|
| 329 |
+
hidden_embed = residual + hidden_embed
|
| 330 |
+
return hidden_embed, attn_weights
|
| 331 |
+
|
| 332 |
+
class MultiHeadAttention(torch.nn.Module):
|
| 333 |
+
def __init__(self, embed_dim, num_heads, attention_dropout_prob=0.0):
|
| 334 |
+
super().__init__()
|
| 335 |
+
self.embed_dim = embed_dim
|
| 336 |
+
self.num_heads = num_heads
|
| 337 |
+
self.head_dim = embed_dim // num_heads
|
| 338 |
+
self.scaling = self.head_dim ** -0.5
|
| 339 |
+
self.q_proj = nn.Linear(embed_dim, embed_dim)
|
| 340 |
+
self.k_proj = nn.Linear(embed_dim, embed_dim)
|
| 341 |
+
self.v_proj = nn.Linear(embed_dim, embed_dim)
|
| 342 |
+
self.out_proj = nn.Linear(embed_dim, embed_dim)
|
| 343 |
+
self.dropout = nn.Dropout(attention_dropout_prob)
|
| 344 |
+
|
| 345 |
+
def forward(self, x, attn_mask=None, return_attn_weights=False):
|
| 346 |
+
batch_size, seq_len, embed_dim = x.shape
|
| 347 |
+
q = self.q_proj(x).view(batch_size, seq_len, self.num_heads, self.head_dim).transpose(1, 2)
|
| 348 |
+
k = self.k_proj(x).view(batch_size, seq_len, self.num_heads, self.head_dim).transpose(1, 2)
|
| 349 |
+
v = self.v_proj(x).view(batch_size, seq_len, self.num_heads, self.head_dim).transpose(1, 2)
|
| 350 |
+
|
| 351 |
+
attn_weights = torch.matmul(q, k.transpose(-2, -1)) * self.scaling
|
| 352 |
+
if attn_mask is not None:
|
| 353 |
+
attn_weights = attn_weights.masked_fill(attn_mask == 0, float('-inf'))
|
| 354 |
+
attn_weights = F.softmax(attn_weights, dim=-1)
|
| 355 |
+
attn_weights = self.dropout(attn_weights)
|
| 356 |
+
|
| 357 |
+
attn_output = torch.matmul(attn_weights, v)
|
| 358 |
+
attn_output = attn_output.transpose(1, 2).contiguous().view(batch_size, seq_len, embed_dim)
|
| 359 |
+
attn_output = self.out_proj(attn_output)
|
| 360 |
+
|
| 361 |
+
if return_attn_weights:
|
| 362 |
+
return attn_output, attn_weights
|
| 363 |
+
return attn_output
|
| 364 |
+
|
| 365 |
+
def get_activation_fn(activation_fn):
|
| 366 |
+
if activation_fn == "gelu":
|
| 367 |
+
return torch.nn.GELU, 1
|
| 368 |
+
elif activation_fn == "relu":
|
| 369 |
+
return torch.nn.ReLU, 1
|
| 370 |
+
elif activation_fn == "swish":
|
| 371 |
+
return torch.nn.SiLU, 1
|
| 372 |
+
else:
|
| 373 |
+
raise ValueError(f"Unsupported activation function: {activation_fn}")
|
| 374 |
+
'''
|
| 375 |
+
}
|
| 376 |
+
|
| 377 |
+
# Create each missing file
|
| 378 |
+
for file in missing_files:
|
| 379 |
+
if file in utility_contents:
|
| 380 |
+
with open(file, 'w') as f:
|
| 381 |
+
f.write(utility_contents[file])
|
| 382 |
+
print(f"✅ Created {file}")
|
| 383 |
+
else:
|
| 384 |
+
print(f"⚠️ No content template for {file}")
|
| 385 |
+
|
| 386 |
+
def ensure_utility_files_available():
|
| 387 |
+
"""
|
| 388 |
+
Ensure all utility files are available in the current directory.
|
| 389 |
+
If any are missing, try to copy them from the repository root.
|
| 390 |
+
"""
|
| 391 |
+
missing_files = []
|
| 392 |
+
for file in UTILITY_FILES:
|
| 393 |
+
if not os.path.exists(file):
|
| 394 |
+
missing_files.append(file)
|
| 395 |
+
|
| 396 |
+
if missing_files:
|
| 397 |
+
print(f"🔍 Looking for missing utility files: {missing_files}")
|
| 398 |
+
|
| 399 |
+
# Try to find the repository root (where all utility files are)
|
| 400 |
+
# Look for common parent directories that might contain the files
|
| 401 |
+
possible_paths = [
|
| 402 |
+
current_dir, # Current directory (where model files are downloaded)
|
| 403 |
+
os.path.join(current_dir, '..'), # Parent directory
|
| 404 |
+
os.path.join(current_dir, '..', '..'), # Grandparent directory
|
| 405 |
+
os.path.join(current_dir, '..', '..', '..'), # Great-grandparent directory
|
| 406 |
+
os.path.join(os.path.expanduser('~'), 'ablang2'), # Home directory
|
| 407 |
+
'/data/hn533621/ablang2', # Known repository location
|
| 408 |
+
'/content/ablang2', # Google Colab common location
|
| 409 |
+
'/tmp/ablang2', # Temporary directory
|
| 410 |
+
]
|
| 411 |
+
|
| 412 |
+
# Check if we're in a Hugging Face cache directory
|
| 413 |
+
is_hf_cache = 'huggingface' in current_dir and 'cache' in current_dir
|
| 414 |
+
if is_hf_cache:
|
| 415 |
+
print("🔍 Detected Hugging Face cache directory - will create utility files inline")
|
| 416 |
+
# Skip searching other paths and create files inline
|
| 417 |
+
possible_paths = []
|
| 418 |
+
|
| 419 |
+
# Also try to find files in the Hugging Face cache structure
|
| 420 |
+
cache_dir = os.path.dirname(current_dir)
|
| 421 |
+
if 'huggingface' in cache_dir:
|
| 422 |
+
# Look in the repository root within the cache
|
| 423 |
+
repo_root = os.path.join(cache_dir, '..', '..', '..', '..')
|
| 424 |
+
possible_paths.append(repo_root)
|
| 425 |
+
|
| 426 |
+
for path in possible_paths:
|
| 427 |
+
if os.path.exists(path):
|
| 428 |
+
print(f"🔍 Checking path: {path}")
|
| 429 |
+
# Check if all missing files exist in this path
|
| 430 |
+
all_found = True
|
| 431 |
+
for file in missing_files:
|
| 432 |
+
file_path = os.path.join(path, file)
|
| 433 |
+
if not os.path.exists(file_path):
|
| 434 |
+
all_found = False
|
| 435 |
+
print(f" ❌ Missing: {file}")
|
| 436 |
+
break
|
| 437 |
+
else:
|
| 438 |
+
print(f" ✅ Found: {file}")
|
| 439 |
+
|
| 440 |
+
if all_found:
|
| 441 |
+
print(f"🎯 Found all files in: {path}")
|
| 442 |
+
# Copy all missing files
|
| 443 |
+
for file in missing_files:
|
| 444 |
+
src = os.path.join(path, file)
|
| 445 |
+
dst = os.path.join(current_dir, file)
|
| 446 |
+
shutil.copy2(src, dst)
|
| 447 |
+
print(f"✅ Copied {file} to cached directory")
|
| 448 |
+
return True
|
| 449 |
+
|
| 450 |
+
# If we get here, we couldn't find the files
|
| 451 |
+
print(f"❌ Could not find utility files in any of the searched paths:")
|
| 452 |
+
for path in possible_paths:
|
| 453 |
+
print(f" - {path}")
|
| 454 |
+
|
| 455 |
+
# Try to create the missing files inline
|
| 456 |
+
print("🔧 Attempting to create missing utility files inline...")
|
| 457 |
+
try:
|
| 458 |
+
create_missing_utility_files(missing_files)
|
| 459 |
+
print("✅ Successfully created missing utility files")
|
| 460 |
+
return True
|
| 461 |
+
except Exception as e:
|
| 462 |
+
print(f"❌ Failed to create utility files: {e}")
|
| 463 |
+
|
| 464 |
+
# For Colab environments, provide a helpful error message
|
| 465 |
+
if 'google.colab' in str(sys.modules):
|
| 466 |
+
raise FileNotFoundError(
|
| 467 |
+
f"Missing utility files: {missing_files}. "
|
| 468 |
+
"This appears to be a Google Colab environment. "
|
| 469 |
+
"Please ensure you have cloned the repository and the utility files are available. "
|
| 470 |
+
"Try running: !git clone https://huggingface.co/hemantn/ablang2"
|
| 471 |
+
)
|
| 472 |
+
else:
|
| 473 |
+
raise FileNotFoundError(
|
| 474 |
+
f"Missing utility files: {missing_files}. "
|
| 475 |
+
"These files are required for the adapter to work. "
|
| 476 |
+
"Please ensure the repository is properly set up."
|
| 477 |
+
)
|
| 478 |
+
|
| 479 |
+
return True
|
| 480 |
+
|
| 481 |
+
# Ensure utility files are available before importing
|
| 482 |
+
ensure_utility_files_available()
|
| 483 |
+
|
| 484 |
+
# Debug: Check what files are in the current directory
|
| 485 |
+
print(f"📁 Files in current directory ({current_dir}):")
|
| 486 |
+
for f in os.listdir(current_dir):
|
| 487 |
+
if f.endswith('.py'):
|
| 488 |
+
print(f" {f}")
|
| 489 |
+
|
| 490 |
+
# Import utility modules directly (no package structure needed)
|
| 491 |
+
import sys
|
| 492 |
+
import os
|
| 493 |
+
|
| 494 |
+
# Ensure we import from the cache directory, not from /content
|
| 495 |
+
cache_dir = os.path.dirname(os.path.abspath(__file__))
|
| 496 |
+
if cache_dir not in sys.path:
|
| 497 |
+
sys.path.insert(0, cache_dir)
|
| 498 |
+
|
| 499 |
+
# Remove /content from sys.path to avoid conflicts
|
| 500 |
+
content_path = '/content'
|
| 501 |
+
if content_path in sys.path:
|
| 502 |
+
sys.path.remove(content_path)
|
| 503 |
+
print(f"✅ Removed {content_path} from sys.path to avoid import conflicts")
|
| 504 |
+
|
| 505 |
+
# Import utility modules
|
| 506 |
+
try:
|
| 507 |
+
from restoration import AbRestore
|
| 508 |
+
from ablang_encodings import AbEncoding
|
| 509 |
+
from alignment import AbAlignment
|
| 510 |
+
from scores import AbScores
|
| 511 |
+
import torch
|
| 512 |
+
import numpy as np
|
| 513 |
+
from extra_utils import res_to_seq, res_to_list
|
| 514 |
+
print("✅ Successfully imported utility modules from cache directory")
|
| 515 |
+
except ImportError as e:
|
| 516 |
+
print(f"❌ Import error: {e}")
|
| 517 |
+
print(f"🔧 Current sys.path: {sys.path}")
|
| 518 |
+
print(f"🔧 Cache directory: {cache_dir}")
|
| 519 |
+
raise
|
| 520 |
+
|
| 521 |
+
class HuggingFaceTokenizerAdapter:
|
| 522 |
+
def __init__(self, tokenizer, device):
|
| 523 |
+
self.tokenizer = tokenizer
|
| 524 |
+
self.device = device
|
| 525 |
+
self.pad_token_id = tokenizer.pad_token_id
|
| 526 |
+
self.mask_token_id = getattr(tokenizer, 'mask_token_id', None) or tokenizer.convert_tokens_to_ids(tokenizer.mask_token)
|
| 527 |
+
self.vocab = tokenizer.get_vocab() if hasattr(tokenizer, 'get_vocab') else tokenizer.vocab
|
| 528 |
+
self.inv_vocab = {v: k for k, v in self.vocab.items()}
|
| 529 |
+
self.all_special_tokens = tokenizer.all_special_tokens
|
| 530 |
+
|
| 531 |
+
def __call__(self, seqs, pad=True, w_extra_tkns=False, device=None, mode=None):
|
| 532 |
+
tokens = self.tokenizer(seqs, padding=True, return_tensors='pt')
|
| 533 |
+
input_ids = tokens['input_ids'].to(self.device if device is None else device)
|
| 534 |
+
if mode == 'decode':
|
| 535 |
+
# seqs is a tensor of token ids
|
| 536 |
+
if isinstance(seqs, torch.Tensor):
|
| 537 |
+
seqs = seqs.cpu().numpy()
|
| 538 |
+
decoded = []
|
| 539 |
+
for i, seq in enumerate(seqs):
|
| 540 |
+
chars = [self.inv_vocab.get(int(t), '') for t in seq if self.inv_vocab.get(int(t), '') not in {'-', '*', '<', '>'} and self.inv_vocab.get(int(t), '') != '']
|
| 541 |
+
# Use res_to_seq for formatting, pass (sequence, length) tuple as in original code
|
| 542 |
+
# The length is not always available, so use len(chars) as fallback
|
| 543 |
+
from extra_utils import res_to_seq
|
| 544 |
+
formatted = res_to_seq([ ''.join(chars), len(chars) ], mode='restore')
|
| 545 |
+
decoded.append(formatted)
|
| 546 |
+
return decoded
|
| 547 |
+
return input_ids
|
| 548 |
+
|
| 549 |
+
class HFAbRestore(AbRestore):
|
| 550 |
+
def __init__(self, hf_model, hf_tokenizer, spread=11, device='cpu', ncpu=1):
|
| 551 |
+
super().__init__(spread=spread, device=device, ncpu=ncpu)
|
| 552 |
+
self.used_device = device
|
| 553 |
+
self._hf_model = hf_model
|
| 554 |
+
self.tokenizer = HuggingFaceTokenizerAdapter(hf_tokenizer, device)
|
| 555 |
+
|
| 556 |
+
@property
|
| 557 |
+
def AbLang(self):
|
| 558 |
+
def model_call(x):
|
| 559 |
+
output = self._hf_model(x)
|
| 560 |
+
if hasattr(output, 'last_hidden_state'):
|
| 561 |
+
return output.last_hidden_state
|
| 562 |
+
return output
|
| 563 |
+
return model_call
|
| 564 |
+
|
| 565 |
+
def restore(self, seqs, align=False, **kwargs):
|
| 566 |
+
"""Restore masked residues in antibody sequences."""
|
| 567 |
+
if isinstance(seqs, str):
|
| 568 |
+
seqs = [seqs]
|
| 569 |
+
|
| 570 |
+
n_seqs = len(seqs)
|
| 571 |
+
|
| 572 |
+
if align:
|
| 573 |
+
# Implement alignment using ANARCI to create spread sequences
|
| 574 |
+
seqs = self._sequence_aligning(seqs)
|
| 575 |
+
nr_seqs = len(seqs)//self.spread
|
| 576 |
+
|
| 577 |
+
tokens = self.tokenizer(seqs, pad=True, w_extra_tkns=False, device=self.used_device)
|
| 578 |
+
predictions = self.AbLang(tokens)[:,:,1:21]
|
| 579 |
+
|
| 580 |
+
# Reshape
|
| 581 |
+
tokens = tokens.reshape(nr_seqs, self.spread, -1)
|
| 582 |
+
predictions = predictions.reshape(nr_seqs, self.spread, -1, 20)
|
| 583 |
+
seqs = seqs.reshape(nr_seqs, -1)
|
| 584 |
+
|
| 585 |
+
# Find index of best predictions
|
| 586 |
+
best_seq_idx = torch.argmax(torch.max(predictions, -1).values[:,:,1:2].mean(2), -1)
|
| 587 |
+
|
| 588 |
+
# Select best predictions
|
| 589 |
+
tokens = tokens.gather(1, best_seq_idx.view(-1, 1).unsqueeze(1).repeat(1, 1, tokens.shape[-1])).squeeze(1)
|
| 590 |
+
predictions = predictions[range(predictions.shape[0]), best_seq_idx]
|
| 591 |
+
seqs = np.take_along_axis(seqs, best_seq_idx.view(-1, 1).cpu().numpy(), axis=1)
|
| 592 |
+
else:
|
| 593 |
+
tokens = self.tokenizer(seqs, pad=True, w_extra_tkns=False, device=self.used_device)
|
| 594 |
+
predictions = self.AbLang(tokens)[:,:,1:21]
|
| 595 |
+
|
| 596 |
+
predicted_tokens = torch.max(predictions, -1).indices + 1
|
| 597 |
+
restored_tokens = torch.where(tokens==23, predicted_tokens, tokens)
|
| 598 |
+
|
| 599 |
+
restored_seqs = self.tokenizer(restored_tokens, mode="decode")
|
| 600 |
+
|
| 601 |
+
if n_seqs < len(restored_seqs):
|
| 602 |
+
restored_seqs = [f"{h}|{l}".replace('-','') for h,l in zip(restored_seqs[:n_seqs], restored_seqs[n_seqs:])]
|
| 603 |
+
seqs = [f"{h}|{l}" for h,l in zip(seqs[:n_seqs], seqs[n_seqs:])]
|
| 604 |
+
|
| 605 |
+
from extra_utils import res_to_seq
|
| 606 |
+
return np.array([res_to_seq(seq, 'restore') for seq in np.c_[restored_seqs, np.vectorize(len)(seqs)]])
|
| 607 |
+
|
| 608 |
+
def _sequence_aligning(self, seqs):
|
| 609 |
+
"""Create spread sequences using ANARCI alignment."""
|
| 610 |
+
tmp_seqs = [pairs.replace(">", "").replace("<", "").split("|") for pairs in seqs]
|
| 611 |
+
|
| 612 |
+
spread_heavy = [f"<{seq}>" for seq in self._create_spread_of_sequences(tmp_seqs, chain = 'H')]
|
| 613 |
+
spread_light = [f"<{seq}>" for seq in self._create_spread_of_sequences(tmp_seqs, chain = 'L')]
|
| 614 |
+
|
| 615 |
+
return np.concatenate([np.array(spread_heavy),np.array(spread_light)])
|
| 616 |
+
|
| 617 |
+
def _create_spread_of_sequences(self, seqs, chain = 'H'):
|
| 618 |
+
"""Create spread sequences using ANARCI."""
|
| 619 |
+
import pandas as pd
|
| 620 |
+
import anarci
|
| 621 |
+
|
| 622 |
+
chain_idx = 0 if chain == 'H' else 1
|
| 623 |
+
numbered_seqs = anarci.run_anarci(
|
| 624 |
+
pd.DataFrame([seq[chain_idx].replace('*', 'X') for seq in seqs]).reset_index().values.tolist(),
|
| 625 |
+
ncpu=self.ncpu,
|
| 626 |
+
scheme='imgt',
|
| 627 |
+
allowed_species=['human', 'mouse'],
|
| 628 |
+
)
|
| 629 |
+
|
| 630 |
+
anarci_data = pd.DataFrame(
|
| 631 |
+
[str(anarci[0][0]) if anarci else 'ANARCI_error' for anarci in numbered_seqs[1]],
|
| 632 |
+
columns=['anarci']
|
| 633 |
+
).astype('<U90')
|
| 634 |
+
|
| 635 |
+
max_position = 128 if chain == 'H' else 127
|
| 636 |
+
|
| 637 |
+
# Define get_sequences_from_anarci function directly
|
| 638 |
+
import re
|
| 639 |
+
|
| 640 |
+
def get_sequences_from_anarci(out_anarci, max_position, spread):
|
| 641 |
+
"""
|
| 642 |
+
Ensures correct masking on each side of sequence
|
| 643 |
+
"""
|
| 644 |
+
|
| 645 |
+
if out_anarci == 'ANARCI_error':
|
| 646 |
+
return np.array(['ANARCI-ERR']*spread)
|
| 647 |
+
|
| 648 |
+
end_position = int(re.search(r'\d+', out_anarci[::-1]).group()[::-1])
|
| 649 |
+
# Fixes ANARCI error of poor numbering of the CDR1 region
|
| 650 |
+
start_position = int(re.search(r'\d+,\s\'.\'\),\s\'[^-]+\'\),\s\(\(\d+,\s\'.\'\),\s\'[^-]+\'\),\s\(\(\d+,\s\'.\'\),\s\'[^-]+\'\),\s\(\(\d+,\s\'.\'\),\s\'[^-]+',
|
| 651 |
+
out_anarci).group().split(',')[0]) - 1
|
| 652 |
+
|
| 653 |
+
sequence = "".join(re.findall(r"(?i)[A-Z*]", "".join(re.findall(r'\),\s\'[A-Z*]', out_anarci))))
|
| 654 |
+
|
| 655 |
+
sequence_j = ''.join(sequence).replace('-','').replace('X','*') + '*'*(max_position-int(end_position))
|
| 656 |
+
|
| 657 |
+
return get_spread_sequences(sequence_j, spread, start_position)
|
| 658 |
+
|
| 659 |
+
def get_spread_sequences(seq, spread, start_position):
|
| 660 |
+
"""
|
| 661 |
+
Test sequences which are 8 positions shorter (position 10 + max CDR1 gap of 7) up to 2 positions longer (possible insertions).
|
| 662 |
+
"""
|
| 663 |
+
spread_sequences = []
|
| 664 |
+
|
| 665 |
+
for diff in range(start_position-8, start_position+2+1):
|
| 666 |
+
spread_sequences.append('*'*diff+seq)
|
| 667 |
+
|
| 668 |
+
return np.array(spread_sequences)
|
| 669 |
+
seqs = anarci_data.apply(
|
| 670 |
+
lambda x: get_sequences_from_anarci(
|
| 671 |
+
x.anarci,
|
| 672 |
+
max_position,
|
| 673 |
+
self.spread
|
| 674 |
+
), axis=1, result_type='expand'
|
| 675 |
+
).to_numpy().reshape(-1)
|
| 676 |
+
|
| 677 |
+
return seqs
|
| 678 |
+
|
| 679 |
+
def add_angle_brackets(seq):
|
| 680 |
+
# Assumes input is 'VH|VL' or 'VH|' or '|VL'
|
| 681 |
+
if '|' in seq:
|
| 682 |
+
vh, vl = seq.split('|', 1)
|
| 683 |
+
else:
|
| 684 |
+
vh, vl = seq, ''
|
| 685 |
+
return f"<{vh}>|<{vl}>"
|
| 686 |
+
|
| 687 |
+
class AbLang2PairedHuggingFaceAdapter(AbEncoding, AbRestore, AbAlignment, AbScores):
|
| 688 |
+
"""
|
| 689 |
+
Adapter to use pretrained utilities with a HuggingFace-loaded ablang2_paired model and tokenizer.
|
| 690 |
+
Automatically uses CUDA if available, otherwise CPU.
|
| 691 |
+
"""
|
| 692 |
+
def __init__(self, model, tokenizer, device=None, ncpu=1):
|
| 693 |
+
super().__init__()
|
| 694 |
+
if device is None:
|
| 695 |
+
self.used_device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
|
| 696 |
+
else:
|
| 697 |
+
self.used_device = torch.device(device)
|
| 698 |
+
self.AbLang = model # HuggingFace model instance
|
| 699 |
+
self.tokenizer = tokenizer
|
| 700 |
+
self.AbLang.to(self.used_device)
|
| 701 |
+
self.AbLang.eval()
|
| 702 |
+
# Always get AbRep from the underlying model
|
| 703 |
+
if hasattr(self.AbLang, 'model') and hasattr(self.AbLang.model, 'AbRep'):
|
| 704 |
+
self.AbRep = self.AbLang.model.AbRep
|
| 705 |
+
else:
|
| 706 |
+
raise AttributeError("Could not find AbRep in the HuggingFace model or its underlying model.")
|
| 707 |
+
self.ncpu = ncpu
|
| 708 |
+
self.spread = 11 # For compatibility with original utilities
|
| 709 |
+
# The following is no longer needed since all_special_tokens now returns IDs directly
|
| 710 |
+
# self.tokenizer.all_special_token_ids = [
|
| 711 |
+
# self.tokenizer.convert_tokens_to_ids(tok) for tok in self.tokenizer.all_special_tokens
|
| 712 |
+
# ]
|
| 713 |
+
# self.tokenizer._all_special_tokens_str = self.tokenizer.all_special_tokens
|
| 714 |
+
# self.tokenizer.all_special_tokens = [
|
| 715 |
+
# self.tokenizer.convert_tokens_to_ids(tok) for tok in self.tokenizer._all_special_tokens_str
|
| 716 |
+
# ]
|
| 717 |
+
|
| 718 |
+
def freeze(self):
|
| 719 |
+
self.AbLang.eval()
|
| 720 |
+
|
| 721 |
+
def unfreeze(self):
|
| 722 |
+
self.AbLang.train()
|
| 723 |
+
|
| 724 |
+
def _encode_sequences(self, seqs):
|
| 725 |
+
# Override to use HuggingFace tokenizer interface
|
| 726 |
+
tokens = self.tokenizer(seqs, padding=True, return_tensors='pt')
|
| 727 |
+
tokens = extract_input_ids(tokens, self.used_device)
|
| 728 |
+
return self.AbRep(tokens).last_hidden_states.detach()
|
| 729 |
+
|
| 730 |
+
def _predict_logits(self, seqs):
|
| 731 |
+
# Override to use HuggingFace tokenizer interface
|
| 732 |
+
tokens = self.tokenizer(seqs, padding=True, return_tensors='pt')
|
| 733 |
+
tokens = extract_input_ids(tokens, self.used_device)
|
| 734 |
+
output = self.AbLang(tokens)
|
| 735 |
+
if hasattr(output, 'last_hidden_state'):
|
| 736 |
+
return output.last_hidden_state.detach()
|
| 737 |
+
return output.detach()
|
| 738 |
+
|
| 739 |
+
def _predict_logits_with_step_masking(self, seqs):
|
| 740 |
+
# Override the stepwise masking method to use HuggingFace tokenizer
|
| 741 |
+
tokens = self.tokenizer(seqs, padding=True, return_tensors='pt')
|
| 742 |
+
tokens = extract_input_ids(tokens, self.used_device)
|
| 743 |
+
|
| 744 |
+
logits = []
|
| 745 |
+
for single_seq_tokens in tokens:
|
| 746 |
+
tkn_len = len(single_seq_tokens)
|
| 747 |
+
masked_tokens = single_seq_tokens.repeat(tkn_len, 1)
|
| 748 |
+
for num in range(tkn_len):
|
| 749 |
+
masked_tokens[num, num] = self.tokenizer.mask_token_id
|
| 750 |
+
|
| 751 |
+
with torch.no_grad():
|
| 752 |
+
logits_tmp = self.AbLang(masked_tokens)
|
| 753 |
+
|
| 754 |
+
logits_tmp = torch.stack([logits_tmp[num, num] for num in range(tkn_len)])
|
| 755 |
+
logits.append(logits_tmp)
|
| 756 |
+
|
| 757 |
+
return torch.stack(logits, dim=0)
|
| 758 |
+
|
| 759 |
+
def _preprocess_labels(self, labels):
|
| 760 |
+
labels = extract_input_ids(labels, self.used_device)
|
| 761 |
+
return labels
|
| 762 |
+
|
| 763 |
+
def __call__(self, seqs, mode='seqcoding', align=False, stepwise_masking=False, fragmented=False, batch_size=50):
|
| 764 |
+
"""
|
| 765 |
+
Use different modes for different usecases, mimicking the original pretrained class.
|
| 766 |
+
"""
|
| 767 |
+
# Local implementation of format_seq_input
|
| 768 |
+
def format_seq_input(seqs, fragmented=False):
|
| 769 |
+
"""Format input sequences for processing."""
|
| 770 |
+
if isinstance(seqs[0], str):
|
| 771 |
+
seqs = [seqs]
|
| 772 |
+
|
| 773 |
+
if fragmented:
|
| 774 |
+
# For fragmented sequences, format as VH|VL without angle brackets
|
| 775 |
+
formatted_seqs = []
|
| 776 |
+
for seq in seqs:
|
| 777 |
+
if isinstance(seq, (list, tuple)) and len(seq) == 2:
|
| 778 |
+
heavy, light = seq[0], seq[1]
|
| 779 |
+
formatted_seqs.append(f"{heavy}|{light}")
|
| 780 |
+
else:
|
| 781 |
+
formatted_seqs.append(seq)
|
| 782 |
+
return formatted_seqs, 'HL'
|
| 783 |
+
else:
|
| 784 |
+
# For non-fragmented sequences, add angle brackets: <VH>|<VL>
|
| 785 |
+
formatted_seqs = []
|
| 786 |
+
for seq in seqs:
|
| 787 |
+
if isinstance(seq, (list, tuple)) and len(seq) == 2:
|
| 788 |
+
heavy, light = seq[0], seq[1]
|
| 789 |
+
# Add angle brackets and handle empty sequences
|
| 790 |
+
heavy_part = f"<{heavy}>" if heavy else "<>"
|
| 791 |
+
light_part = f"<{light}>" if light else "<>"
|
| 792 |
+
formatted_seqs.append(f"{heavy_part}|{light_part}".replace("<>", ""))
|
| 793 |
+
else:
|
| 794 |
+
formatted_seqs.append(seq)
|
| 795 |
+
|
| 796 |
+
return formatted_seqs, 'HL'
|
| 797 |
+
|
| 798 |
+
valid_modes = [
|
| 799 |
+
'rescoding', 'seqcoding', 'restore', 'likelihood', 'probability',
|
| 800 |
+
'pseudo_log_likelihood', 'confidence'
|
| 801 |
+
]
|
| 802 |
+
if mode not in valid_modes:
|
| 803 |
+
raise SyntaxError(f"Given mode doesn't exist. Please select one of the following: {valid_modes}.")
|
| 804 |
+
|
| 805 |
+
seqs, chain = format_seq_input(seqs, fragmented=fragmented)
|
| 806 |
+
|
| 807 |
+
if align:
|
| 808 |
+
numbered_seqs, seqs, number_alignment = self.number_sequences(
|
| 809 |
+
seqs, chain=chain, fragmented=fragmented
|
| 810 |
+
)
|
| 811 |
+
else:
|
| 812 |
+
numbered_seqs = None
|
| 813 |
+
number_alignment = None
|
| 814 |
+
|
| 815 |
+
subset_list = []
|
| 816 |
+
for subset in [seqs[x:x+batch_size] for x in range(0, len(seqs), batch_size)]:
|
| 817 |
+
subset_list.append(getattr(self, mode)(subset, align=align, stepwise_masking=stepwise_masking))
|
| 818 |
+
|
| 819 |
+
return self.reformat_subsets(
|
| 820 |
+
subset_list,
|
| 821 |
+
mode=mode,
|
| 822 |
+
align=align,
|
| 823 |
+
numbered_seqs=numbered_seqs,
|
| 824 |
+
seqs=seqs,
|
| 825 |
+
number_alignment=number_alignment,
|
| 826 |
+
)
|
| 827 |
+
|
| 828 |
+
def pseudo_log_likelihood(self, seqs, **kwargs):
|
| 829 |
+
"""
|
| 830 |
+
Original (non-vectorized) pseudo log-likelihood computation matching notebook behavior.
|
| 831 |
+
"""
|
| 832 |
+
# Format input: join VH and VL with '|'
|
| 833 |
+
formatted_seqs = []
|
| 834 |
+
for s in seqs:
|
| 835 |
+
if isinstance(s, (list, tuple)):
|
| 836 |
+
formatted_seqs.append('|'.join(s))
|
| 837 |
+
else:
|
| 838 |
+
formatted_seqs.append(s)
|
| 839 |
+
|
| 840 |
+
# Tokenize all sequences in batch
|
| 841 |
+
labels = self.tokenizer(
|
| 842 |
+
formatted_seqs, padding=True, return_tensors='pt'
|
| 843 |
+
)
|
| 844 |
+
labels = extract_input_ids(labels, self.used_device)
|
| 845 |
+
|
| 846 |
+
# Convert special tokens to IDs
|
| 847 |
+
if isinstance(self.tokenizer.all_special_tokens[0], int):
|
| 848 |
+
special_token_ids = set(self.tokenizer.all_special_tokens)
|
| 849 |
+
else:
|
| 850 |
+
special_token_ids = set(self.tokenizer.convert_tokens_to_ids(tok) for tok in self.tokenizer.all_special_tokens)
|
| 851 |
+
pad_token_id = self.tokenizer.pad_token_id
|
| 852 |
+
|
| 853 |
+
mask_token_id = getattr(self.tokenizer, 'mask_token_id', None)
|
| 854 |
+
if mask_token_id is None:
|
| 855 |
+
mask_token_id = self.tokenizer.convert_tokens_to_ids(self.tokenizer.mask_token)
|
| 856 |
+
|
| 857 |
+
plls = []
|
| 858 |
+
with torch.no_grad():
|
| 859 |
+
for i, seq_label in enumerate(labels):
|
| 860 |
+
seq_pll = []
|
| 861 |
+
for j, token_id in enumerate(seq_label):
|
| 862 |
+
if token_id.item() in special_token_ids or token_id.item() == pad_token_id:
|
| 863 |
+
continue
|
| 864 |
+
masked = seq_label.clone()
|
| 865 |
+
masked[j] = mask_token_id
|
| 866 |
+
logits = self.AbLang(masked.unsqueeze(0))
|
| 867 |
+
if hasattr(logits, 'last_hidden_state'):
|
| 868 |
+
logits = logits.last_hidden_state
|
| 869 |
+
logits = logits[0, j]
|
| 870 |
+
nll = torch.nn.functional.cross_entropy(
|
| 871 |
+
logits.unsqueeze(0), token_id.unsqueeze(0), reduction="none"
|
| 872 |
+
)
|
| 873 |
+
seq_pll.append(-nll.item())
|
| 874 |
+
if seq_pll:
|
| 875 |
+
plls.append(np.mean(seq_pll))
|
| 876 |
+
else:
|
| 877 |
+
plls.append(float('nan'))
|
| 878 |
+
return np.array(plls)
|
| 879 |
+
|
| 880 |
+
def seqcoding(self, seqs, **kwargs):
|
| 881 |
+
"""Sequence specific representations - returns 480-dimensional embeddings for each sequence."""
|
| 882 |
+
# Format input: join VH and VL with '|'
|
| 883 |
+
formatted_seqs = []
|
| 884 |
+
for s in seqs:
|
| 885 |
+
if isinstance(s, (list, tuple)):
|
| 886 |
+
formatted_seqs.append('|'.join(s))
|
| 887 |
+
else:
|
| 888 |
+
formatted_seqs.append(s)
|
| 889 |
+
|
| 890 |
+
# Get embeddings using the model
|
| 891 |
+
embeddings = self._encode_sequences(formatted_seqs)
|
| 892 |
+
|
| 893 |
+
# Return sequence-level embeddings (mean pooling over sequence length)
|
| 894 |
+
# Remove batch dimension and take mean over sequence dimension
|
| 895 |
+
if len(embeddings.shape) == 3: # [batch_size, seq_len, hidden_size]
|
| 896 |
+
seq_embeddings = embeddings.mean(dim=1) # [batch_size, hidden_size]
|
| 897 |
+
else:
|
| 898 |
+
seq_embeddings = embeddings
|
| 899 |
+
|
| 900 |
+
return seq_embeddings.cpu().numpy()
|
| 901 |
+
|
| 902 |
+
def rescoding(self, seqs, align=False, **kwargs):
|
| 903 |
+
"""Residue specific representations - returns 480-dimensional embeddings for each residue."""
|
| 904 |
+
# Format input: join VH and VL with '|'
|
| 905 |
+
formatted_seqs = []
|
| 906 |
+
for s in seqs:
|
| 907 |
+
if isinstance(s, (list, tuple)):
|
| 908 |
+
formatted_seqs.append('|'.join(s))
|
| 909 |
+
else:
|
| 910 |
+
formatted_seqs.append(s)
|
| 911 |
+
|
| 912 |
+
# Get embeddings using the model
|
| 913 |
+
embeddings = self._encode_sequences(formatted_seqs)
|
| 914 |
+
|
| 915 |
+
# Return residue-level embeddings
|
| 916 |
+
# embeddings shape: [batch_size, seq_len, hidden_size]
|
| 917 |
+
if len(embeddings.shape) == 3:
|
| 918 |
+
# Convert to numpy and return as list of arrays for each sequence
|
| 919 |
+
embeddings_np = embeddings.cpu().numpy()
|
| 920 |
+
return [embeddings_np[i] for i in range(embeddings_np.shape[0])]
|
| 921 |
+
else:
|
| 922 |
+
return embeddings.cpu().numpy()
|
| 923 |
+
|
| 924 |
+
def likelihood(self, seqs, align=False, stepwise_masking=False, **kwargs):
|
| 925 |
+
"""Likelihood of mutations - returns logits for each amino acid at each position."""
|
| 926 |
+
# Format input: join VH and VL with '|'
|
| 927 |
+
formatted_seqs = []
|
| 928 |
+
for s in seqs:
|
| 929 |
+
if isinstance(s, (list, tuple)):
|
| 930 |
+
formatted_seqs.append('|'.join(s))
|
| 931 |
+
else:
|
| 932 |
+
formatted_seqs.append(s)
|
| 933 |
+
|
| 934 |
+
# Get logits
|
| 935 |
+
if stepwise_masking:
|
| 936 |
+
logits = self._predict_logits_with_step_masking(formatted_seqs)
|
| 937 |
+
else:
|
| 938 |
+
logits = self._predict_logits(formatted_seqs)
|
| 939 |
+
|
| 940 |
+
# Return logits as numpy array
|
| 941 |
+
return logits.cpu().numpy()
|
| 942 |
+
|
| 943 |
+
def confidence(self, seqs, **kwargs):
|
| 944 |
+
"""Confidence calculation - match original ablang2 implementation by excluding all special tokens from loss."""
|
| 945 |
+
# Format input: join VH and VL with '|'
|
| 946 |
+
formatted_seqs = []
|
| 947 |
+
for s in seqs:
|
| 948 |
+
if isinstance(s, (list, tuple)):
|
| 949 |
+
formatted_seqs.append('|'.join(s))
|
| 950 |
+
else:
|
| 951 |
+
formatted_seqs.append(s)
|
| 952 |
+
|
| 953 |
+
plls = []
|
| 954 |
+
for seq in formatted_seqs:
|
| 955 |
+
tokens = self.tokenizer([seq], padding=True, return_tensors='pt')
|
| 956 |
+
input_ids = extract_input_ids(tokens, self.used_device)
|
| 957 |
+
|
| 958 |
+
with torch.no_grad():
|
| 959 |
+
output = self.AbLang(input_ids)
|
| 960 |
+
if hasattr(output, 'last_hidden_state'):
|
| 961 |
+
logits = output.last_hidden_state
|
| 962 |
+
else:
|
| 963 |
+
logits = output
|
| 964 |
+
|
| 965 |
+
# Get the sequence (remove batch dimension)
|
| 966 |
+
logits = logits[0] # [seq_len, vocab_size]
|
| 967 |
+
input_ids = input_ids[0] # [seq_len]
|
| 968 |
+
|
| 969 |
+
# Exclude all special tokens (pad, mask, etc.)
|
| 970 |
+
if isinstance(self.tokenizer.all_special_tokens[0], int):
|
| 971 |
+
special_token_ids = set(self.tokenizer.all_special_tokens)
|
| 972 |
+
else:
|
| 973 |
+
special_token_ids = set(self.tokenizer.convert_tokens_to_ids(tok) for tok in self.tokenizer.all_special_tokens)
|
| 974 |
+
valid_mask = ~torch.isin(input_ids, torch.tensor(list(special_token_ids), device=input_ids.device))
|
| 975 |
+
|
| 976 |
+
if valid_mask.sum() > 0:
|
| 977 |
+
valid_logits = logits[valid_mask]
|
| 978 |
+
valid_labels = input_ids[valid_mask]
|
| 979 |
+
|
| 980 |
+
# Calculate cross-entropy loss
|
| 981 |
+
nll = torch.nn.functional.cross_entropy(
|
| 982 |
+
valid_logits,
|
| 983 |
+
valid_labels,
|
| 984 |
+
reduction="mean"
|
| 985 |
+
)
|
| 986 |
+
pll = -nll.item()
|
| 987 |
+
else:
|
| 988 |
+
pll = 0.0
|
| 989 |
+
|
| 990 |
+
plls.append(pll)
|
| 991 |
+
|
| 992 |
+
return np.array(plls, dtype=np.float32)
|
| 993 |
+
|
| 994 |
+
def probability(self, seqs, align=False, stepwise_masking=False, **kwargs):
|
| 995 |
+
"""
|
| 996 |
+
Probability of mutations - applies softmax to logits to get probabilities
|
| 997 |
+
"""
|
| 998 |
+
# Format input: join VH and VL with '|'
|
| 999 |
+
formatted_seqs = []
|
| 1000 |
+
for s in seqs:
|
| 1001 |
+
if isinstance(s, (list, tuple)):
|
| 1002 |
+
formatted_seqs.append('|'.join(s))
|
| 1003 |
+
else:
|
| 1004 |
+
formatted_seqs.append(s)
|
| 1005 |
+
|
| 1006 |
+
# Get logits
|
| 1007 |
+
if stepwise_masking:
|
| 1008 |
+
# For stepwise masking, we need to implement it similar to likelihood
|
| 1009 |
+
# This is a simplified version - you might want to implement full stepwise masking
|
| 1010 |
+
logits = self._predict_logits(formatted_seqs)
|
| 1011 |
+
else:
|
| 1012 |
+
logits = self._predict_logits(formatted_seqs)
|
| 1013 |
+
|
| 1014 |
+
# Apply softmax to get probabilities
|
| 1015 |
+
probs = logits.softmax(-1).cpu().numpy()
|
| 1016 |
+
|
| 1017 |
+
if align:
|
| 1018 |
+
return probs
|
| 1019 |
+
else:
|
| 1020 |
+
# Return residue-level probabilities (excluding special tokens)
|
| 1021 |
+
return [res_to_list(state, seq) for state, seq in zip(probs, formatted_seqs)]
|
| 1022 |
+
|
| 1023 |
+
def restore(self, seqs, align=False, **kwargs):
|
| 1024 |
+
hf_abrestore = HFAbRestore(self.AbLang, self.tokenizer, spread=self.spread, device=self.used_device, ncpu=self.ncpu)
|
| 1025 |
+
restored = hf_abrestore.restore(seqs, align=align)
|
| 1026 |
+
# Apply angle brackets formatting to match original format
|
| 1027 |
+
if isinstance(restored, np.ndarray):
|
| 1028 |
+
restored = np.array([add_angle_brackets(seq) for seq in restored])
|
| 1029 |
+
else:
|
| 1030 |
+
restored = [add_angle_brackets(seq) for seq in restored]
|
| 1031 |
+
return restored
|
| 1032 |
+
|
| 1033 |
+
def extract_input_ids(tokens, device):
|
| 1034 |
+
if hasattr(tokens, 'input_ids'):
|
| 1035 |
+
return tokens.input_ids.to(device)
|
| 1036 |
+
elif isinstance(tokens, dict):
|
| 1037 |
+
if 'input_ids' in tokens:
|
| 1038 |
+
return tokens['input_ids'].to(device)
|
| 1039 |
+
else:
|
| 1040 |
+
for v in tokens.values():
|
| 1041 |
+
if hasattr(v, 'ndim') or torch.is_tensor(v):
|
| 1042 |
+
return v.to(device)
|
| 1043 |
+
elif torch.is_tensor(tokens):
|
| 1044 |
+
return tokens.to(device)
|
| 1045 |
+
else:
|
| 1046 |
+
raise ValueError("Could not extract input_ids from tokenizer output")
|
alignment.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from dataclasses import dataclass
|
| 2 |
+
import numpy as np
|
| 3 |
+
import torch
|
| 4 |
+
|
| 5 |
+
from extra_utils import paired_msa_numbering, unpaired_msa_numbering, create_alignment
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class AbAlignment:
|
| 9 |
+
|
| 10 |
+
def __init__(self, device = 'cpu', ncpu = 1):
|
| 11 |
+
|
| 12 |
+
self.device = device
|
| 13 |
+
self.ncpu = ncpu
|
| 14 |
+
|
| 15 |
+
def number_sequences(self, seqs, chain = 'H', fragmented = False):
|
| 16 |
+
if chain == 'HL':
|
| 17 |
+
numbered_seqs, seqs, number_alignment = paired_msa_numbering(seqs, fragmented = fragmented, n_jobs = self.ncpu)
|
| 18 |
+
else:
|
| 19 |
+
assert chain == 'HL', 'Currently "Align==True" only works for paired sequences. \nPlease use paired sequences or Align=False.'
|
| 20 |
+
numbered_seqs, seqs, number_alignment = unpaired_msa_numbering(
|
| 21 |
+
seqs, chain = chain, fragmented = fragmented, n_jobs = self.ncpu
|
| 22 |
+
)
|
| 23 |
+
|
| 24 |
+
return numbered_seqs, seqs, number_alignment
|
| 25 |
+
|
| 26 |
+
def align_encodings(self, encodings, numbered_seqs, seqs, number_alignment):
|
| 27 |
+
|
| 28 |
+
aligned_list = [
|
| 29 |
+
create_alignment(
|
| 30 |
+
res_embed, numbered_seq, seq, number_alignment
|
| 31 |
+
) for res_embed, numbered_seq, seq in zip(encodings, numbered_seqs, seqs)
|
| 32 |
+
]
|
| 33 |
+
aligned_encodings = np.concatenate([aligned_list], axis=0)
|
| 34 |
+
return aligned_encodings
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def reformat_subsets(
|
| 38 |
+
self,
|
| 39 |
+
subset_list,
|
| 40 |
+
mode = 'seqcoding',
|
| 41 |
+
align = False,
|
| 42 |
+
numbered_seqs = None,
|
| 43 |
+
seqs = None,
|
| 44 |
+
number_alignment = None,
|
| 45 |
+
):
|
| 46 |
+
|
| 47 |
+
if mode in [
|
| 48 |
+
'seqcoding',
|
| 49 |
+
'restore',
|
| 50 |
+
'pseudo_log_likelihood',
|
| 51 |
+
'confidence'
|
| 52 |
+
]:
|
| 53 |
+
return np.concatenate(subset_list)
|
| 54 |
+
elif align:
|
| 55 |
+
subset_list = [
|
| 56 |
+
self.align_encodings(
|
| 57 |
+
subset,
|
| 58 |
+
numbered_seqs[num*len(subset):(num+1)*len(subset)],
|
| 59 |
+
seqs[num*len(subset):(num+1)*len(subset)],
|
| 60 |
+
number_alignment
|
| 61 |
+
) for num, subset in enumerate(subset_list)
|
| 62 |
+
]
|
| 63 |
+
|
| 64 |
+
subset = np.concatenate(subset_list)
|
| 65 |
+
|
| 66 |
+
return aligned_results(
|
| 67 |
+
aligned_seqs = [''.join(alist) for alist in subset[:,:,-1]],
|
| 68 |
+
aligned_embeds = subset[:,:,:-1].astype(float),
|
| 69 |
+
number_alignment=number_alignment.apply(lambda x: '{}{}'.format(*x[0]), axis=1).values
|
| 70 |
+
)
|
| 71 |
+
|
| 72 |
+
elif not align:
|
| 73 |
+
return sum(subset_list, [])
|
| 74 |
+
else:
|
| 75 |
+
return np.concatenate(subset_list) # this needs to be changed
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
@dataclass
|
| 79 |
+
class aligned_results():
|
| 80 |
+
"""
|
| 81 |
+
Dataclass used to store output.
|
| 82 |
+
"""
|
| 83 |
+
|
| 84 |
+
aligned_seqs: None
|
| 85 |
+
aligned_embeds: None
|
| 86 |
+
number_alignment: None
|
app.py
ADDED
|
@@ -0,0 +1,336 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
import sys
|
| 3 |
+
import os
|
| 4 |
+
from transformers import AutoModel, AutoTokenizer
|
| 5 |
+
from transformers.utils import cached_file
|
| 6 |
+
|
| 7 |
+
# Load model and tokenizer from Hugging Face Hub
|
| 8 |
+
model = AutoModel.from_pretrained("hemantn/ablang2", trust_remote_code=True)
|
| 9 |
+
tokenizer = AutoTokenizer.from_pretrained("hemantn/ablang2", trust_remote_code=True)
|
| 10 |
+
|
| 11 |
+
# Find the cached model directory and import adapter
|
| 12 |
+
adapter_path = cached_file("hemantn/ablang2", "adapter.py")
|
| 13 |
+
cached_model_dir = os.path.dirname(adapter_path)
|
| 14 |
+
sys.path.insert(0, cached_model_dir)
|
| 15 |
+
|
| 16 |
+
# Import and create the adapter
|
| 17 |
+
from adapter import AbLang2PairedHuggingFaceAdapter
|
| 18 |
+
ablang = AbLang2PairedHuggingFaceAdapter(model=model, tokenizer=tokenizer)
|
| 19 |
+
|
| 20 |
+
def restore_sequences(heavy_chain, light_chain, use_align=False):
|
| 21 |
+
"""
|
| 22 |
+
Restore masked residues in antibody sequences.
|
| 23 |
+
|
| 24 |
+
Args:
|
| 25 |
+
heavy_chain (str): Heavy chain sequence with masked residues (*)
|
| 26 |
+
light_chain (str): Light chain sequence with masked residues (*)
|
| 27 |
+
use_align (bool): Whether to use alignment for variable missing lengths
|
| 28 |
+
|
| 29 |
+
Returns:
|
| 30 |
+
tuple: (restored_heavy, restored_light, highlighted_heavy, highlighted_light)
|
| 31 |
+
"""
|
| 32 |
+
try:
|
| 33 |
+
# Check if alignment is requested but not available
|
| 34 |
+
if use_align:
|
| 35 |
+
try:
|
| 36 |
+
import anarci
|
| 37 |
+
except ImportError:
|
| 38 |
+
return "Alignment feature requires 'anarci' package which is not available. Please disable alignment option.", "", ""
|
| 39 |
+
# Prepare input sequences
|
| 40 |
+
if heavy_chain.strip() and light_chain.strip():
|
| 41 |
+
# Both chains provided
|
| 42 |
+
sequences = [[heavy_chain.strip(), light_chain.strip()]]
|
| 43 |
+
elif heavy_chain.strip():
|
| 44 |
+
# Only heavy chain provided
|
| 45 |
+
sequences = [[heavy_chain.strip(), ""]]
|
| 46 |
+
elif light_chain.strip():
|
| 47 |
+
# Only light chain provided
|
| 48 |
+
sequences = [["", light_chain.strip()]]
|
| 49 |
+
else:
|
| 50 |
+
return "Please provide at least one antibody chain sequence.", "", "", ""
|
| 51 |
+
|
| 52 |
+
# Perform restoration
|
| 53 |
+
restored = ablang(sequences, mode='restore', align=use_align)
|
| 54 |
+
|
| 55 |
+
# Format output
|
| 56 |
+
if hasattr(restored, '__len__') and len(restored) > 0:
|
| 57 |
+
result = restored[0] # Get the first (and only) result
|
| 58 |
+
|
| 59 |
+
# Parse the result to separate heavy and light chains
|
| 60 |
+
if '>|<' in result:
|
| 61 |
+
# Both chains present
|
| 62 |
+
heavy_part = result.split('>|<')[0].replace('<', '').replace('>', '')
|
| 63 |
+
light_part = result.split('>|<')[1].replace('<', '').replace('>', '')
|
| 64 |
+
elif result.startswith('<') and result.endswith('>'):
|
| 65 |
+
# Only one chain present
|
| 66 |
+
if heavy_chain.strip():
|
| 67 |
+
heavy_part = result.replace('<', '').replace('>', '')
|
| 68 |
+
light_part = ""
|
| 69 |
+
else:
|
| 70 |
+
heavy_part = ""
|
| 71 |
+
light_part = result.replace('<', '').replace('>', '')
|
| 72 |
+
else:
|
| 73 |
+
return "Error: Unexpected result format.", "", "", ""
|
| 74 |
+
|
| 75 |
+
# Create highlighted versions
|
| 76 |
+
highlighted_heavy = highlight_restored_residues(heavy_chain.strip(), heavy_part)
|
| 77 |
+
highlighted_light = highlight_restored_residues(light_chain.strip(), light_part)
|
| 78 |
+
|
| 79 |
+
# Create HTML outputs with proper styling - no scroll, wrap text
|
| 80 |
+
heavy_html = f'<div class="restored-sequence-box" style="padding: 10px; background-color: #f8f9fa; border: 1px solid #dee2e6; border-radius: 4px;">{highlighted_heavy}</div>'
|
| 81 |
+
light_html = f'<div class="restored-sequence-box" style="padding: 10px; background-color: #f8f9fa; border: 1px solid #dee2e6; border-radius: 4px;">{highlighted_light}</div>'
|
| 82 |
+
|
| 83 |
+
return heavy_html, light_html
|
| 84 |
+
else:
|
| 85 |
+
return "Error: No restoration result obtained.", "", ""
|
| 86 |
+
|
| 87 |
+
except Exception as e:
|
| 88 |
+
return f"Error during restoration: {str(e)}", "", ""
|
| 89 |
+
|
| 90 |
+
def highlight_restored_residues(original_seq, restored_seq):
|
| 91 |
+
"""
|
| 92 |
+
Highlight restored residues in green.
|
| 93 |
+
"""
|
| 94 |
+
if not original_seq or not restored_seq:
|
| 95 |
+
return restored_seq
|
| 96 |
+
|
| 97 |
+
highlighted = ""
|
| 98 |
+
for i, (orig_char, rest_char) in enumerate(zip(original_seq, restored_seq)):
|
| 99 |
+
if orig_char == '*' and rest_char != '*':
|
| 100 |
+
# This residue was restored
|
| 101 |
+
highlighted += f'<span class="restored-highlight">{rest_char}</span>'
|
| 102 |
+
else:
|
| 103 |
+
highlighted += rest_char
|
| 104 |
+
|
| 105 |
+
# Add any remaining characters from restored sequence
|
| 106 |
+
if len(restored_seq) > len(original_seq):
|
| 107 |
+
highlighted += restored_seq[len(original_seq):]
|
| 108 |
+
|
| 109 |
+
return highlighted
|
| 110 |
+
|
| 111 |
+
# Create Gradio interface
|
| 112 |
+
with gr.Blocks(title="AbLang2 Sequence Restorer", theme=gr.themes.Soft(), css="""
|
| 113 |
+
* {
|
| 114 |
+
font-family: 'Courier New', monospace !important;
|
| 115 |
+
}
|
| 116 |
+
.sequence-input, .sequence-output {
|
| 117 |
+
font-family: 'Courier New', monospace !important;
|
| 118 |
+
font-size: 14px !important;
|
| 119 |
+
letter-spacing: 0.5px !important;
|
| 120 |
+
}
|
| 121 |
+
.restored-highlight {
|
| 122 |
+
background-color: #90EE90 !important;
|
| 123 |
+
color: #000 !important;
|
| 124 |
+
font-weight: bold !important;
|
| 125 |
+
}
|
| 126 |
+
.examples {
|
| 127 |
+
font-family: 'Courier New', monospace !important;
|
| 128 |
+
font-size: 14px !important;
|
| 129 |
+
letter-spacing: 0.5px !important;
|
| 130 |
+
}
|
| 131 |
+
.restored-sequence-box {
|
| 132 |
+
font-family: 'Courier New', monospace !important;
|
| 133 |
+
font-size: 14px !important;
|
| 134 |
+
letter-spacing: 0.5px !important;
|
| 135 |
+
white-space: pre-wrap !important;
|
| 136 |
+
word-wrap: break-word !important;
|
| 137 |
+
overflow-wrap: break-word !important;
|
| 138 |
+
}
|
| 139 |
+
.restored-heading {
|
| 140 |
+
color: #2E8B57 !important;
|
| 141 |
+
font-weight: bold !important;
|
| 142 |
+
font-size: 18px !important;
|
| 143 |
+
}
|
| 144 |
+
.example-text {
|
| 145 |
+
font-family: 'Courier New', monospace !important;
|
| 146 |
+
font-size: 12px !important;
|
| 147 |
+
white-space: pre-wrap !important;
|
| 148 |
+
word-wrap: break-word !important;
|
| 149 |
+
}
|
| 150 |
+
.examples-table {
|
| 151 |
+
font-family: 'Courier New', monospace !important;
|
| 152 |
+
font-size: 12px !important;
|
| 153 |
+
white-space: pre-wrap !important;
|
| 154 |
+
word-wrap: break-word !important;
|
| 155 |
+
max-width: none !important;
|
| 156 |
+
overflow: visible !important;
|
| 157 |
+
}
|
| 158 |
+
.examples-table td {
|
| 159 |
+
font-family: 'Courier New', monospace !important;
|
| 160 |
+
font-size: 12px !important;
|
| 161 |
+
white-space: pre-wrap !important;
|
| 162 |
+
word-wrap: break-word !important;
|
| 163 |
+
max-width: none !important;
|
| 164 |
+
overflow: visible !important;
|
| 165 |
+
text-overflow: unset !important;
|
| 166 |
+
}
|
| 167 |
+
.sequence-output label {
|
| 168 |
+
font-weight: bold !important;
|
| 169 |
+
color: #495057 !important;
|
| 170 |
+
font-size: 14px !important;
|
| 171 |
+
margin-bottom: 5px !important;
|
| 172 |
+
}
|
| 173 |
+
/* Force full display of examples */
|
| 174 |
+
.examples-container {
|
| 175 |
+
font-family: 'Courier New', monospace !important;
|
| 176 |
+
font-size: 12px !important;
|
| 177 |
+
}
|
| 178 |
+
.examples-container table {
|
| 179 |
+
width: 100% !important;
|
| 180 |
+
table-layout: auto !important;
|
| 181 |
+
}
|
| 182 |
+
.examples-container td {
|
| 183 |
+
white-space: pre-wrap !important;
|
| 184 |
+
word-wrap: break-word !important;
|
| 185 |
+
overflow-wrap: break-word !important;
|
| 186 |
+
max-width: none !important;
|
| 187 |
+
text-overflow: unset !important;
|
| 188 |
+
padding: 8px !important;
|
| 189 |
+
vertical-align: top !important;
|
| 190 |
+
}
|
| 191 |
+
.examples-container th {
|
| 192 |
+
white-space: nowrap !important;
|
| 193 |
+
padding: 8px !important;
|
| 194 |
+
}
|
| 195 |
+
/* Override any Gradio default truncation */
|
| 196 |
+
.examples table td {
|
| 197 |
+
white-space: pre-wrap !important;
|
| 198 |
+
word-wrap: break-word !important;
|
| 199 |
+
overflow-wrap: break-word !important;
|
| 200 |
+
max-width: none !important;
|
| 201 |
+
text-overflow: unset !important;
|
| 202 |
+
overflow: visible !important;
|
| 203 |
+
font-family: 'Courier New', monospace !important;
|
| 204 |
+
font-size: 12px !important;
|
| 205 |
+
}
|
| 206 |
+
.examples table {
|
| 207 |
+
table-layout: auto !important;
|
| 208 |
+
width: 100% !important;
|
| 209 |
+
}
|
| 210 |
+
/* Target the specific examples component */
|
| 211 |
+
div[data-testid="examples"] table td {
|
| 212 |
+
white-space: pre-wrap !important;
|
| 213 |
+
word-wrap: break-word !important;
|
| 214 |
+
overflow-wrap: break-word !important;
|
| 215 |
+
max-width: none !important;
|
| 216 |
+
text-overflow: unset !important;
|
| 217 |
+
overflow: visible !important;
|
| 218 |
+
font-family: 'Courier New', monospace !important;
|
| 219 |
+
font-size: 12px !important;
|
| 220 |
+
}
|
| 221 |
+
/* Force examples to show full content */
|
| 222 |
+
.examples table, .examples table td, .examples table th {
|
| 223 |
+
white-space: pre-wrap !important;
|
| 224 |
+
word-wrap: break-word !important;
|
| 225 |
+
overflow-wrap: break-word !important;
|
| 226 |
+
max-width: none !important;
|
| 227 |
+
text-overflow: unset !important;
|
| 228 |
+
overflow: visible !important;
|
| 229 |
+
font-family: 'Courier New', monospace !important;
|
| 230 |
+
font-size: 12px !important;
|
| 231 |
+
table-layout: auto !important;
|
| 232 |
+
width: auto !important;
|
| 233 |
+
min-width: 100% !important;
|
| 234 |
+
}
|
| 235 |
+
/* Override any inline styles */
|
| 236 |
+
.examples * {
|
| 237 |
+
white-space: pre-wrap !important;
|
| 238 |
+
word-wrap: break-word !important;
|
| 239 |
+
overflow-wrap: break-word !important;
|
| 240 |
+
max-width: none !important;
|
| 241 |
+
text-overflow: unset !important;
|
| 242 |
+
overflow: visible !important;
|
| 243 |
+
}
|
| 244 |
+
/* Style output labels to match input labels exactly */
|
| 245 |
+
.output-label {
|
| 246 |
+
font-weight: 600 !important;
|
| 247 |
+
color: var(--label-text-color) !important;
|
| 248 |
+
font-size: 14px !important;
|
| 249 |
+
margin-bottom: 8px !important;
|
| 250 |
+
margin-top: 16px !important;
|
| 251 |
+
line-height: 1.4 !important;
|
| 252 |
+
display: block !important;
|
| 253 |
+
}
|
| 254 |
+
""") as demo:
|
| 255 |
+
gr.Markdown("""
|
| 256 |
+
# 🧬 AbLang2 Sequence Restorer
|
| 257 |
+
|
| 258 |
+
This app uses the AbLang2 model to restore masked residues (*) in antibody sequences.
|
| 259 |
+
You can provide either one or both heavy and light chain sequences.
|
| 260 |
+
|
| 261 |
+
**Instructions:**
|
| 262 |
+
- Use `*` to mask residues you want to restore
|
| 263 |
+
- Provide heavy chain, light chain, or both
|
| 264 |
+
- Enable "Use Alignment" for variable missing lengths
|
| 265 |
+
""")
|
| 266 |
+
|
| 267 |
+
with gr.Row():
|
| 268 |
+
with gr.Column():
|
| 269 |
+
heavy_input = gr.Textbox(
|
| 270 |
+
label="Heavy Chain Sequence",
|
| 271 |
+
placeholder="Enter heavy chain sequence with masked residues (*)...",
|
| 272 |
+
lines=3,
|
| 273 |
+
max_lines=5,
|
| 274 |
+
elem_classes=["sequence-input"]
|
| 275 |
+
)
|
| 276 |
+
|
| 277 |
+
light_input = gr.Textbox(
|
| 278 |
+
label="Light Chain Sequence",
|
| 279 |
+
placeholder="Enter light chain sequence with masked residues (*)...",
|
| 280 |
+
lines=3,
|
| 281 |
+
max_lines=5,
|
| 282 |
+
elem_classes=["sequence-input"]
|
| 283 |
+
)
|
| 284 |
+
|
| 285 |
+
align_checkbox = gr.Checkbox(
|
| 286 |
+
label="Use Alignment (for variable missing lengths) - Requires anarci package",
|
| 287 |
+
value=False
|
| 288 |
+
)
|
| 289 |
+
|
| 290 |
+
restore_btn = gr.Button("🔄 Restore Sequences", variant="primary")
|
| 291 |
+
|
| 292 |
+
with gr.Column():
|
| 293 |
+
gr.Markdown("### 🧬 Restored Sequences", elem_classes=["restored-heading"])
|
| 294 |
+
gr.Markdown("*Green highlighting shows restored residues*")
|
| 295 |
+
|
| 296 |
+
gr.Markdown("**Heavy Chain Sequence**", elem_classes=["output-label"])
|
| 297 |
+
heavy_output = gr.HTML(label="")
|
| 298 |
+
|
| 299 |
+
gr.Markdown("**Light Chain Sequence**", elem_classes=["output-label"])
|
| 300 |
+
light_output = gr.HTML(label="")
|
| 301 |
+
|
| 302 |
+
# Example sequences
|
| 303 |
+
gr.Examples(
|
| 304 |
+
examples=[
|
| 305 |
+
[
|
| 306 |
+
"EVQ***SGGEVKKPGASVKVSCRASGYTFRNYGLTWVRQAPGQGLEWMGWISAYNGNTNYAQKFQGRVTLTTDTSTSTAYMELRSLRSDDTAVYFCAR**PGHGAAFMDVWGTGTTVTVSS",
|
| 307 |
+
"DIQLTQSPLSLPVTLGQPASISCRSS*SLEASDTNIYLSWFQQRPGQSPRRLIYKI*NRDSGVPDRFSGSGSGTHFTLRISRVEADDVAVYYCMQGTHWPPAFGQGTKVDIK"
|
| 308 |
+
],
|
| 309 |
+
[
|
| 310 |
+
"EVQLVESGGGLVQPGGSLRLSCAASGFTFSSYAMGWVRQAPGKGLEWVSAISGSGGSTYYADSVKGRFTISRDNSKNTLYLQMNSLRAEDTAVYYCARDY**GMDVWGQGTTVTVSS",
|
| 311 |
+
""
|
| 312 |
+
],
|
| 313 |
+
[
|
| 314 |
+
"",
|
| 315 |
+
"DIQLTQSPSSLSASVGDRVTITCRASQSISSYLNWYQQKPGKAPKLLIY*ASSLQSGVPSRFSGSGSGTDFTLTISSLQPEDFATYYCQQSYSTP*TFGQGTKVEIK"
|
| 316 |
+
]
|
| 317 |
+
],
|
| 318 |
+
inputs=[heavy_input, light_input],
|
| 319 |
+
label="Example Sequences"
|
| 320 |
+
)
|
| 321 |
+
|
| 322 |
+
# Connect the button to the function
|
| 323 |
+
restore_btn.click(
|
| 324 |
+
fn=restore_sequences,
|
| 325 |
+
inputs=[heavy_input, light_input, align_checkbox],
|
| 326 |
+
outputs=[heavy_output, light_output]
|
| 327 |
+
)
|
| 328 |
+
|
| 329 |
+
gr.Markdown("""
|
| 330 |
+
---
|
| 331 |
+
**Note:** This app uses the AbLang2 model from Hugging Face Hub.
|
| 332 |
+
The restoration process may take a few seconds depending on sequence length and complexity.
|
| 333 |
+
""")
|
| 334 |
+
|
| 335 |
+
if __name__ == "__main__":
|
| 336 |
+
demo.launch()
|
config.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"model_type": "ablang2-paired",
|
| 3 |
+
"vocab_size": 26,
|
| 4 |
+
"hidden_embed_size": 480,
|
| 5 |
+
"n_attn_heads": 20,
|
| 6 |
+
"n_encoder_blocks": 12,
|
| 7 |
+
"padding_tkn": 21,
|
| 8 |
+
"mask_tkn": 23,
|
| 9 |
+
"layer_norm_eps": 1e-12,
|
| 10 |
+
"a_fn": "swiglu",
|
| 11 |
+
"dropout": 0.0,
|
| 12 |
+
"tokenizer_class": "AbLang2PairedTokenizer",
|
| 13 |
+
"auto_map": {
|
| 14 |
+
"AutoConfig": "configuration_ablang2paired.AbLang2PairedConfig",
|
| 15 |
+
"AutoModel": "modeling_ablang2paired.AbLang2PairedHFModel",
|
| 16 |
+
"AutoTokenizer": ["tokenizer_ablang2paired.AbLang2PairedTokenizer", "tokenizer_ablang2paired.AbLang2PairedTokenizer"],
|
| 17 |
+
"AbRestore": "restoration.AbRestore",
|
| 18 |
+
"AbScores": "scores.AbScores",
|
| 19 |
+
"AbAlignment": "alignment.AbAlignment",
|
| 20 |
+
"AbEncoding": "ablang_encodings.AbEncoding",
|
| 21 |
+
"ExtraUtils": "extra_utils"
|
| 22 |
+
}
|
| 23 |
+
}
|
configuration_ablang2paired.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from transformers import PretrainedConfig
|
| 2 |
+
|
| 3 |
+
class AbLang2PairedConfig(PretrainedConfig):
|
| 4 |
+
model_type = "ablang2-paired"
|
| 5 |
+
|
| 6 |
+
def __init__(
|
| 7 |
+
self,
|
| 8 |
+
vocab_size=26,
|
| 9 |
+
hidden_embed_size=480,
|
| 10 |
+
n_attn_heads=20,
|
| 11 |
+
n_encoder_blocks=12,
|
| 12 |
+
padding_tkn=21,
|
| 13 |
+
mask_tkn=23,
|
| 14 |
+
layer_norm_eps=1e-12,
|
| 15 |
+
a_fn="swiglu",
|
| 16 |
+
dropout=0.0,
|
| 17 |
+
**kwargs
|
| 18 |
+
):
|
| 19 |
+
super().__init__(**kwargs)
|
| 20 |
+
self.vocab_size = vocab_size
|
| 21 |
+
self.hidden_embed_size = hidden_embed_size
|
| 22 |
+
self.hidden_size = hidden_embed_size # Add this for Hugging Face compatibility
|
| 23 |
+
self.n_attn_heads = n_attn_heads
|
| 24 |
+
self.num_attention_heads = n_attn_heads # Add this for Hugging Face compatibility
|
| 25 |
+
self.num_hidden_layers = n_encoder_blocks # Add this for Hugging Face compatibility
|
| 26 |
+
self.n_encoder_blocks = n_encoder_blocks
|
| 27 |
+
self.padding_tkn = padding_tkn
|
| 28 |
+
self.mask_tkn = mask_tkn
|
| 29 |
+
self.layer_norm_eps = layer_norm_eps
|
| 30 |
+
self.a_fn = a_fn
|
| 31 |
+
self.dropout = dropout
|
encoderblock.py
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import math
|
| 3 |
+
from torch import nn
|
| 4 |
+
import torch.nn.functional as F
|
| 5 |
+
import einops
|
| 6 |
+
from rotary_embedding_torch import RotaryEmbedding
|
| 7 |
+
|
| 8 |
+
class TransformerEncoder(torch.nn.Module):
|
| 9 |
+
"""
|
| 10 |
+
Single Transformer Encoder.
|
| 11 |
+
|
| 12 |
+
"""
|
| 13 |
+
def __init__(
|
| 14 |
+
self,
|
| 15 |
+
hidden_embed_size,
|
| 16 |
+
n_attn_heads,
|
| 17 |
+
attn_dropout: float = 0.0,
|
| 18 |
+
layer_norm_eps: float = 1e-05,
|
| 19 |
+
a_fn: str = "gelu",
|
| 20 |
+
):
|
| 21 |
+
super().__init__()
|
| 22 |
+
|
| 23 |
+
assert hidden_embed_size % n_attn_heads == 0, \
|
| 24 |
+
"Embedding dimension must be devisible with the number of heads."
|
| 25 |
+
|
| 26 |
+
self.multihead_attention = MultiHeadAttention(
|
| 27 |
+
embed_dim = hidden_embed_size,
|
| 28 |
+
num_heads = n_attn_heads,
|
| 29 |
+
attention_dropout_prob = attn_dropout
|
| 30 |
+
)
|
| 31 |
+
|
| 32 |
+
activation_fn, scale = get_activation_fn(a_fn)
|
| 33 |
+
|
| 34 |
+
self.intermediate_layer = torch.nn.Sequential(
|
| 35 |
+
torch.nn.Linear(hidden_embed_size, hidden_embed_size * 4 * scale),
|
| 36 |
+
activation_fn(),
|
| 37 |
+
torch.nn.Linear(hidden_embed_size * 4, hidden_embed_size),
|
| 38 |
+
)
|
| 39 |
+
|
| 40 |
+
self.pre_attn_layer_norm = torch.nn.LayerNorm(hidden_embed_size, eps=layer_norm_eps)
|
| 41 |
+
self.final_layer_norm = torch.nn.LayerNorm(hidden_embed_size, eps=layer_norm_eps)
|
| 42 |
+
|
| 43 |
+
def forward(self, hidden_embed, attn_mask=None, return_attn_weights: bool = False):
|
| 44 |
+
|
| 45 |
+
residual = hidden_embed
|
| 46 |
+
hidden_embed = self.pre_attn_layer_norm(hidden_embed.clone())
|
| 47 |
+
hidden_embed, attn_weights = self.multihead_attention(
|
| 48 |
+
hidden_embed,
|
| 49 |
+
attn_mask=attn_mask,
|
| 50 |
+
return_attn_weights=return_attn_weights
|
| 51 |
+
)
|
| 52 |
+
hidden_embed = residual + hidden_embed
|
| 53 |
+
|
| 54 |
+
residual = hidden_embed
|
| 55 |
+
hidden_embed = self.final_layer_norm(hidden_embed)
|
| 56 |
+
hidden_embed = self.intermediate_layer(hidden_embed)
|
| 57 |
+
hidden_embed = residual + hidden_embed
|
| 58 |
+
return hidden_embed, attn_weights
|
| 59 |
+
|
| 60 |
+
class MultiHeadAttention(torch.nn.Module):
|
| 61 |
+
|
| 62 |
+
def __init__(
|
| 63 |
+
self,
|
| 64 |
+
embed_dim,
|
| 65 |
+
num_heads,
|
| 66 |
+
attention_dropout_prob: float = 0.0,
|
| 67 |
+
bias: bool = True,
|
| 68 |
+
):
|
| 69 |
+
super().__init__()
|
| 70 |
+
|
| 71 |
+
self.attention_dropout = torch.nn.Dropout(attention_dropout_prob)
|
| 72 |
+
|
| 73 |
+
self.embed_dim = embed_dim
|
| 74 |
+
self.num_heads = num_heads
|
| 75 |
+
self.head_dim = embed_dim // num_heads
|
| 76 |
+
assert (self.head_dim * num_heads == self.embed_dim), "embed_dim must be divisible by num_heads"
|
| 77 |
+
self.scaling = self.head_dim**-0.5
|
| 78 |
+
|
| 79 |
+
self.k_proj = nn.Linear(embed_dim, embed_dim, bias=bias)
|
| 80 |
+
self.v_proj = nn.Linear(embed_dim, embed_dim, bias=bias)
|
| 81 |
+
self.q_proj = nn.Linear(embed_dim, embed_dim, bias=bias)
|
| 82 |
+
|
| 83 |
+
self.out_proj = nn.Linear(embed_dim, embed_dim, bias=bias)
|
| 84 |
+
|
| 85 |
+
self.reset_parameters()
|
| 86 |
+
|
| 87 |
+
self.rotary_emb = RotaryEmbedding(dim = self.head_dim)
|
| 88 |
+
|
| 89 |
+
def reset_parameters(self):
|
| 90 |
+
|
| 91 |
+
nn.init.xavier_uniform_(self.k_proj.weight, gain=1 / math.sqrt(2))
|
| 92 |
+
nn.init.xavier_uniform_(self.v_proj.weight, gain=1 / math.sqrt(2))
|
| 93 |
+
nn.init.xavier_uniform_(self.q_proj.weight, gain=1 / math.sqrt(2))
|
| 94 |
+
|
| 95 |
+
nn.init.xavier_uniform_(self.out_proj.weight)
|
| 96 |
+
if self.out_proj.bias is not None:
|
| 97 |
+
nn.init.constant_(self.out_proj.bias, 0.0)
|
| 98 |
+
|
| 99 |
+
def attention(self, q, k, v, attn_mask=None):
|
| 100 |
+
|
| 101 |
+
attn_weights = torch.matmul(q, k.transpose(-2, -1))
|
| 102 |
+
attn_weights = attn_weights / math.sqrt(self.head_dim)
|
| 103 |
+
|
| 104 |
+
if attn_mask is not None:
|
| 105 |
+
attn_mask = einops.rearrange(
|
| 106 |
+
attn_mask,
|
| 107 |
+
'b_size (h1 h2 seq_len) -> b_size h1 h2 seq_len',
|
| 108 |
+
h1=1, h2=1
|
| 109 |
+
)
|
| 110 |
+
attn_weights = attn_weights.masked_fill(attn_mask, float("-inf"))
|
| 111 |
+
|
| 112 |
+
attn_weights = F.softmax(attn_weights, dim=-1)
|
| 113 |
+
|
| 114 |
+
attn = self.attention_dropout(attn_weights)
|
| 115 |
+
attn = torch.matmul(attn, v)
|
| 116 |
+
return attn, attn_weights
|
| 117 |
+
|
| 118 |
+
def forward(self, x, attn_mask=None, return_attn_weights: bool = False):
|
| 119 |
+
|
| 120 |
+
batch_size, seq_len, embed_dim = x.size()
|
| 121 |
+
|
| 122 |
+
q, k, v = self.q_proj(x), self.k_proj(x), self.v_proj(x)
|
| 123 |
+
q *= self.scaling
|
| 124 |
+
|
| 125 |
+
q = q.contiguous().view(
|
| 126 |
+
batch_size,
|
| 127 |
+
seq_len,
|
| 128 |
+
self.num_heads,
|
| 129 |
+
self.head_dim
|
| 130 |
+
).transpose(1, 2) # [n_batch, n_heads, seq_len, head_dim]
|
| 131 |
+
k = k.contiguous().view(
|
| 132 |
+
batch_size,
|
| 133 |
+
seq_len,
|
| 134 |
+
self.num_heads,
|
| 135 |
+
self.head_dim
|
| 136 |
+
).transpose(1, 2) # [n_batch, n_heads, seq_len, head_dim]
|
| 137 |
+
v = v.contiguous().view(
|
| 138 |
+
batch_size,
|
| 139 |
+
seq_len,
|
| 140 |
+
self.num_heads,
|
| 141 |
+
self.head_dim
|
| 142 |
+
).transpose(1, 2) # [n_batch, n_heads, seq_len, head_dim]
|
| 143 |
+
|
| 144 |
+
q = self.rotary_emb.rotate_queries_or_keys(q)
|
| 145 |
+
k = self.rotary_emb.rotate_queries_or_keys(k)
|
| 146 |
+
|
| 147 |
+
# Determine value outputs
|
| 148 |
+
attn, attn_weights = self.attention(
|
| 149 |
+
q, k, v,
|
| 150 |
+
attn_mask=attn_mask
|
| 151 |
+
) # attn_weights [n_batch, n_heads, seq_len (target), seq_len (source)]
|
| 152 |
+
|
| 153 |
+
attn = attn.transpose(1, 2).reshape(batch_size, seq_len, embed_dim)
|
| 154 |
+
attn = self.out_proj(attn)
|
| 155 |
+
|
| 156 |
+
if return_attn_weights:
|
| 157 |
+
return attn, attn_weights
|
| 158 |
+
else:
|
| 159 |
+
return attn, None
|
| 160 |
+
|
| 161 |
+
class SwiGLU(torch.nn.Module):
|
| 162 |
+
def forward(self, x):
|
| 163 |
+
x, gate = x.chunk(2, dim=-1)
|
| 164 |
+
return F.silu(gate) * x
|
| 165 |
+
|
| 166 |
+
def get_activation_fn(a_fn):
|
| 167 |
+
|
| 168 |
+
if a_fn == "gelu":
|
| 169 |
+
return torch.nn.GELU, 1
|
| 170 |
+
|
| 171 |
+
elif a_fn == "swiglu":
|
| 172 |
+
return SwiGLU, 2
|
| 173 |
+
|
environment.yaml
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: AbLang
|
| 2 |
+
channels:
|
| 3 |
+
- conda-forge
|
| 4 |
+
- pytorch
|
| 5 |
+
- bioconda
|
| 6 |
+
- defaults
|
| 7 |
+
dependencies:
|
| 8 |
+
- python=3.10.18
|
| 9 |
+
- pip
|
| 10 |
+
- pytorch=2.5.1
|
| 11 |
+
- pytorch-cuda=12.4
|
| 12 |
+
- numpy=2.2.6
|
| 13 |
+
- pandas=2.3.1
|
| 14 |
+
- transformers=4.53.3
|
| 15 |
+
- anarci=2024.05.21
|
| 16 |
+
- jupyter=7.4.4
|
| 17 |
+
- notebook=7.4.4
|
| 18 |
+
- ipython=8.37.0
|
| 19 |
+
- ipykernel=6.29.5
|
| 20 |
+
- matplotlib-inline=0.1.7
|
| 21 |
+
- scikit-learn
|
| 22 |
+
- matplotlib
|
| 23 |
+
- seaborn
|
| 24 |
+
- biopython=1.85
|
| 25 |
+
- huggingface_hub=0.33.4
|
| 26 |
+
- tokenizers=0.21.3
|
| 27 |
+
- safetensors=0.5.3
|
| 28 |
+
- einops=0.8.1
|
| 29 |
+
- tqdm=4.67.1
|
| 30 |
+
- requests=2.32.4
|
| 31 |
+
- urllib3=2.5.0
|
| 32 |
+
- certifi=2025.7.14
|
| 33 |
+
- filelock=3.18.0
|
| 34 |
+
- fsspec=2025.3.0
|
| 35 |
+
- packaging=25.0
|
| 36 |
+
- regex=2024.11.6
|
| 37 |
+
- sympy=1.13.3
|
| 38 |
+
- networkx=3.4.2
|
| 39 |
+
- jinja2=3.1.6
|
| 40 |
+
- pyyaml=6.0.2
|
| 41 |
+
- typing_extensions=4.14.1
|
| 42 |
+
- pip:
|
| 43 |
+
- numba=0.61.2
|
| 44 |
+
- llvmlite=0.44.0
|
extra_utils.py
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import string, re
|
| 2 |
+
import numpy as np
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
def res_to_list(logits, seq):
|
| 6 |
+
return logits[:len(seq)]
|
| 7 |
+
|
| 8 |
+
def res_to_seq(a, mode='mean'):
|
| 9 |
+
"""
|
| 10 |
+
Function for how we go from n_values for each amino acid to n_values for each sequence.
|
| 11 |
+
|
| 12 |
+
We leave out padding tokens.
|
| 13 |
+
"""
|
| 14 |
+
|
| 15 |
+
if mode=='sum':
|
| 16 |
+
return a[0:(int(a[-1]))].sum()
|
| 17 |
+
|
| 18 |
+
elif mode=='mean':
|
| 19 |
+
return a[0:(int(a[-1]))].mean()
|
| 20 |
+
|
| 21 |
+
elif mode=='restore':
|
| 22 |
+
return a[0][0:(int(a[-1]))]
|
| 23 |
+
|
| 24 |
+
def get_number_alignment(numbered_seqs):
|
| 25 |
+
"""
|
| 26 |
+
Creates a number alignment from the anarci results.
|
| 27 |
+
"""
|
| 28 |
+
import pandas as pd
|
| 29 |
+
|
| 30 |
+
alist = [pd.DataFrame(aligned_seq, columns = [0,1,'resi']) for aligned_seq in numbered_seqs]
|
| 31 |
+
unsorted_alignment = pd.concat(alist).drop_duplicates(subset=0)
|
| 32 |
+
max_alignment = get_max_alignment()
|
| 33 |
+
|
| 34 |
+
return max_alignment.merge(unsorted_alignment.query("resi!='-'"), left_on=0, right_on=0)[[0,1]]
|
| 35 |
+
|
| 36 |
+
def get_max_alignment():
|
| 37 |
+
"""
|
| 38 |
+
Create maximum possible alignment for sorting
|
| 39 |
+
"""
|
| 40 |
+
import pandas as pd
|
| 41 |
+
|
| 42 |
+
sortlist = [[("<", "")]]
|
| 43 |
+
for num in range(1, 128+1):
|
| 44 |
+
if num in [33,61,112]:
|
| 45 |
+
for char in string.ascii_uppercase[::-1]:
|
| 46 |
+
sortlist.append([(num, char)])
|
| 47 |
+
|
| 48 |
+
sortlist.append([(num,' ')])
|
| 49 |
+
else:
|
| 50 |
+
sortlist.append([(num,' ')])
|
| 51 |
+
for char in string.ascii_uppercase:
|
| 52 |
+
sortlist.append([(num, char)])
|
| 53 |
+
|
| 54 |
+
return pd.DataFrame(sortlist + [[(">", "")]])
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def paired_msa_numbering(ab_seqs, fragmented = False, n_jobs = 10):
|
| 58 |
+
|
| 59 |
+
import pandas as pd
|
| 60 |
+
|
| 61 |
+
tmp_seqs = [pairs.replace(">", "").replace("<", "").split("|") for pairs in ab_seqs]
|
| 62 |
+
|
| 63 |
+
numbered_seqs_heavy, seqs_heavy, number_alignment_heavy = unpaired_msa_numbering(
|
| 64 |
+
[i[0] for i in tmp_seqs], 'H', fragmented = fragmented, n_jobs = n_jobs
|
| 65 |
+
)
|
| 66 |
+
numbered_seqs_light, seqs_light, number_alignment_light = unpaired_msa_numbering(
|
| 67 |
+
[i[1] for i in tmp_seqs], 'L', fragmented = fragmented, n_jobs = n_jobs
|
| 68 |
+
)
|
| 69 |
+
|
| 70 |
+
number_alignment = pd.concat([
|
| 71 |
+
number_alignment_heavy,
|
| 72 |
+
pd.DataFrame([[("|",""), "|"]]),
|
| 73 |
+
number_alignment_light]
|
| 74 |
+
).reset_index(drop=True)
|
| 75 |
+
|
| 76 |
+
seqs = [f"{heavy}|{light}" for heavy, light in zip(seqs_heavy, seqs_light)]
|
| 77 |
+
numbered_seqs = [
|
| 78 |
+
heavy + [(("|",""), "|", "|")] + light for heavy, light in zip(numbered_seqs_heavy, numbered_seqs_light)
|
| 79 |
+
]
|
| 80 |
+
|
| 81 |
+
return numbered_seqs, seqs, number_alignment
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
def unpaired_msa_numbering(seqs, chain = 'H', fragmented = False, n_jobs = 10):
|
| 85 |
+
|
| 86 |
+
numbered_seqs = number_with_anarci(seqs, chain = chain, fragmented = fragmented, n_jobs = n_jobs)
|
| 87 |
+
number_alignment = get_number_alignment(numbered_seqs)
|
| 88 |
+
number_alignment[1] = chain
|
| 89 |
+
|
| 90 |
+
seqs = [''.join([i[2] for i in numbered_seq]).replace('-','') for numbered_seq in numbered_seqs]
|
| 91 |
+
return numbered_seqs, seqs, number_alignment
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
def number_with_anarci(seqs, chain = 'H', fragmented = False, n_jobs = 1):
|
| 95 |
+
|
| 96 |
+
import anarci
|
| 97 |
+
import pandas as pd
|
| 98 |
+
|
| 99 |
+
anarci_out = anarci.run_anarci(
|
| 100 |
+
pd.DataFrame(seqs).reset_index().values.tolist(),
|
| 101 |
+
ncpu=n_jobs,
|
| 102 |
+
scheme='imgt',
|
| 103 |
+
allowed_species=['human', 'mouse'],
|
| 104 |
+
)
|
| 105 |
+
|
| 106 |
+
numbered_seqs = []
|
| 107 |
+
for onarci in anarci_out[1]:
|
| 108 |
+
numbered_seq = []
|
| 109 |
+
for i in onarci[0][0]:
|
| 110 |
+
if i[1] != '-':
|
| 111 |
+
numbered_seq.append((i[0], chain, i[1]))
|
| 112 |
+
|
| 113 |
+
if fragmented:
|
| 114 |
+
numbered_seqs.append(numbered_seq)
|
| 115 |
+
else:
|
| 116 |
+
numbered_seqs.append([(("<",""), chain, "<")] + numbered_seq + [((">",""), chain, ">")])
|
| 117 |
+
|
| 118 |
+
return numbered_seqs
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
def create_alignment(res_embeds, numbered_seqs, seq, number_alignment):
|
| 122 |
+
|
| 123 |
+
import pandas as pd
|
| 124 |
+
|
| 125 |
+
datadf = pd.DataFrame(numbered_seqs)
|
| 126 |
+
sequence_alignment = number_alignment.merge(datadf, how='left', on=[0, 1]).fillna('-')[2]
|
| 127 |
+
|
| 128 |
+
idxs = np.where(sequence_alignment.values == '-')[0]
|
| 129 |
+
idxs = [idx-num for num, idx in enumerate(idxs)]
|
| 130 |
+
|
| 131 |
+
aligned_embeds = pd.DataFrame(np.insert(res_embeds[:len(seq)], idxs , 0, axis=0))
|
| 132 |
+
|
| 133 |
+
return pd.concat([aligned_embeds, sequence_alignment], axis=1).values
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
def get_spread_sequences(seq, spread, start_position):
|
| 137 |
+
"""
|
| 138 |
+
Test sequences which are 8 positions shorter (position 10 + max CDR1 gap of 7) up to 2 positions longer (possible insertions).
|
| 139 |
+
"""
|
| 140 |
+
spread_sequences = []
|
| 141 |
+
|
| 142 |
+
for diff in range(start_position-8, start_position+2+1):
|
| 143 |
+
spread_sequences.append('*'*diff+seq)
|
| 144 |
+
|
| 145 |
+
return np.array(spread_sequences)
|
| 146 |
+
|
| 147 |
+
def get_sequences_from_anarci(out_anarci, max_position, spread):
|
| 148 |
+
"""
|
| 149 |
+
Ensures correct masking on each side of sequence
|
| 150 |
+
"""
|
| 151 |
+
|
| 152 |
+
if out_anarci == 'ANARCI_error':
|
| 153 |
+
return np.array(['ANARCI-ERR']*spread)
|
| 154 |
+
|
| 155 |
+
end_position = int(re.search(r'\d+', out_anarci[::-1]).group()[::-1])
|
| 156 |
+
# Fixes ANARCI error of poor numbering of the CDR1 region
|
| 157 |
+
start_position = int(re.search(r'\d+,\s\'.\'\),\s\'[^-]+\'\),\s\(\(\d+,\s\'.\'\),\s\'[^-]+\'\),\s\(\(\d+,\s\'.\'\),\s\'[^-]+\'\),\s\(\(\d+,\s\'.\'\),\s\'[^-]+',
|
| 158 |
+
out_anarci).group().split(',')[0]) - 1
|
| 159 |
+
|
| 160 |
+
sequence = "".join(re.findall(r"(?i)[A-Z*]", "".join(re.findall(r'\),\s\'[A-Z*]', out_anarci))))
|
| 161 |
+
|
| 162 |
+
sequence_j = ''.join(sequence).replace('-','').replace('X','*') + '*'*(max_position-int(end_position))
|
| 163 |
+
|
| 164 |
+
return get_spread_sequences(sequence_j, spread, start_position)
|
| 165 |
+
|
hparams.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"name": "AbLang-2", "n_encoder_blocks": 12, "hidden_embed_size": 480, "n_attn_heads": 20, "a_fn": "swiglu", "layer_norm_eps": 1e-12, "pad_tkn": 21, "start_tkn": 0, "end_tkn": 22, "sep_tkn": 25, "mask_tkn": 23, "vocab_size": 26}
|
model.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:56d6f07862a6f824f88c8707bbc03e4026c9db762be2d3041e9767e2e6f86386
|
| 3 |
+
size 179314477
|
modeling_ablang2paired.py
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import os
|
| 3 |
+
from torch import nn
|
| 4 |
+
from transformers import PreTrainedModel
|
| 5 |
+
|
| 6 |
+
# Import configuration
|
| 7 |
+
try:
|
| 8 |
+
from .configuration_ablang2paired import AbLang2PairedConfig
|
| 9 |
+
except ImportError:
|
| 10 |
+
from configuration_ablang2paired import AbLang2PairedConfig
|
| 11 |
+
|
| 12 |
+
# Import the AbLang model from local files
|
| 13 |
+
try:
|
| 14 |
+
from ablang import AbLang
|
| 15 |
+
except ImportError:
|
| 16 |
+
# Fallback: try to import from the current directory
|
| 17 |
+
try:
|
| 18 |
+
from .ablang import AbLang
|
| 19 |
+
except ImportError:
|
| 20 |
+
raise ImportError(
|
| 21 |
+
"Could not find AbLang module. Please ensure ablang.py is present in the repository."
|
| 22 |
+
)
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
class AbLang2PairedHFModel(PreTrainedModel):
|
| 29 |
+
config_class = AbLang2PairedConfig
|
| 30 |
+
model_type = "ablang2-paired"
|
| 31 |
+
|
| 32 |
+
def __init__(self, config: AbLang2PairedConfig):
|
| 33 |
+
super().__init__(config)
|
| 34 |
+
self.model = AbLang(
|
| 35 |
+
vocab_size=config.vocab_size,
|
| 36 |
+
hidden_embed_size=config.hidden_embed_size,
|
| 37 |
+
n_attn_heads=config.n_attn_heads,
|
| 38 |
+
n_encoder_blocks=config.n_encoder_blocks,
|
| 39 |
+
padding_tkn=config.padding_tkn,
|
| 40 |
+
mask_tkn=config.mask_tkn,
|
| 41 |
+
layer_norm_eps=config.layer_norm_eps,
|
| 42 |
+
a_fn=config.a_fn,
|
| 43 |
+
dropout=config.dropout,
|
| 44 |
+
)
|
| 45 |
+
|
| 46 |
+
def forward(self, input_ids=None, x=None, attention_mask=None, **kwargs):
|
| 47 |
+
# Handle both Hugging Face format (input_ids) and original format (x)
|
| 48 |
+
if input_ids is not None:
|
| 49 |
+
x = input_ids
|
| 50 |
+
elif x is None:
|
| 51 |
+
raise ValueError("Either input_ids or x must be provided")
|
| 52 |
+
|
| 53 |
+
# Get the output from the underlying model
|
| 54 |
+
output = self.model(x, attention_mask)
|
| 55 |
+
|
| 56 |
+
# Return as a simple object with last_hidden_state attribute
|
| 57 |
+
class ModelOutput:
|
| 58 |
+
def __init__(self, last_hidden_state):
|
| 59 |
+
self.last_hidden_state = last_hidden_state
|
| 60 |
+
|
| 61 |
+
return ModelOutput(output)
|
| 62 |
+
|
| 63 |
+
@classmethod
|
| 64 |
+
def from_pretrained(cls, pretrained_model_name_or_path, *model_args, **kwargs):
|
| 65 |
+
# Load config first
|
| 66 |
+
config = kwargs.get("config")
|
| 67 |
+
if config is None:
|
| 68 |
+
from transformers import AutoConfig
|
| 69 |
+
config = AutoConfig.from_pretrained(pretrained_model_name_or_path, trust_remote_code=True)
|
| 70 |
+
|
| 71 |
+
# Create model with config
|
| 72 |
+
model = cls(config)
|
| 73 |
+
|
| 74 |
+
# Try to load custom weights
|
| 75 |
+
try:
|
| 76 |
+
from transformers.utils import cached_file
|
| 77 |
+
custom_weights_path = cached_file(
|
| 78 |
+
pretrained_model_name_or_path,
|
| 79 |
+
"model.pt",
|
| 80 |
+
cache_dir=kwargs.get("cache_dir"),
|
| 81 |
+
force_download=kwargs.get("force_download", False),
|
| 82 |
+
resume_download=kwargs.get("resume_download", False),
|
| 83 |
+
proxies=kwargs.get("proxies"),
|
| 84 |
+
token=kwargs.get("token"),
|
| 85 |
+
revision=kwargs.get("revision"),
|
| 86 |
+
local_files_only=kwargs.get("local_files_only", False),
|
| 87 |
+
)
|
| 88 |
+
|
| 89 |
+
if custom_weights_path is not None and os.path.exists(custom_weights_path):
|
| 90 |
+
# Load custom weights
|
| 91 |
+
state_dict = torch.load(custom_weights_path, map_location="cpu", weights_only=True)
|
| 92 |
+
model.model.load_state_dict(state_dict)
|
| 93 |
+
print(f"✅ Loaded custom weights from: {custom_weights_path}")
|
| 94 |
+
else:
|
| 95 |
+
print("⚠️ No custom weights found, using initialized model")
|
| 96 |
+
|
| 97 |
+
except Exception as e:
|
| 98 |
+
print(f"⚠️ Could not load custom weights: {e}")
|
| 99 |
+
print("Using initialized model")
|
| 100 |
+
|
| 101 |
+
# Move model to appropriate device (GPU if available, otherwise CPU)
|
| 102 |
+
device = kwargs.get("device", None)
|
| 103 |
+
if device is None:
|
| 104 |
+
device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 105 |
+
model = model.to(device)
|
| 106 |
+
|
| 107 |
+
return model
|
| 108 |
+
|
| 109 |
+
def save_pretrained(self, save_directory, **kwargs):
|
| 110 |
+
os.makedirs(save_directory, exist_ok=True)
|
| 111 |
+
# Save custom weights
|
| 112 |
+
torch.save(self.model.state_dict(), f"{save_directory}/model.pt")
|
| 113 |
+
# Save config
|
| 114 |
+
self.config.save_pretrained(save_directory)
|
| 115 |
+
# Call parent method for any additional saving
|
| 116 |
+
super().save_pretrained(save_directory, **kwargs)
|
requirements.txt
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
gradio>=4.0.0
|
| 2 |
+
transformers>=4.30.0
|
| 3 |
+
numpy>=1.21.0
|
| 4 |
+
pandas>=1.3.0
|
| 5 |
+
rotary-embedding-torch>=0.2.0
|
| 6 |
+
git+https://github.com/oxpig/ANARCI.git
|
restoration.py
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
import torch
|
| 3 |
+
|
| 4 |
+
from extra_utils import res_to_seq, get_sequences_from_anarci
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
class AbRestore:
|
| 8 |
+
def __init__(self, spread = 11, device = 'cpu', ncpu = 1):
|
| 9 |
+
self.spread = spread
|
| 10 |
+
self.device = device
|
| 11 |
+
self.ncpu = ncpu
|
| 12 |
+
|
| 13 |
+
def _initiate_abrestore(self, model, tokenizer):
|
| 14 |
+
self.AbLang = model
|
| 15 |
+
self.tokenizer = tokenizer
|
| 16 |
+
|
| 17 |
+
def restore(self, seqs, align = False, **kwargs):
|
| 18 |
+
"""
|
| 19 |
+
Restore sequences
|
| 20 |
+
"""
|
| 21 |
+
n_seqs = len(seqs)
|
| 22 |
+
|
| 23 |
+
if align:
|
| 24 |
+
|
| 25 |
+
seqs = self._sequence_aligning(seqs)
|
| 26 |
+
nr_seqs = len(seqs)//self.spread
|
| 27 |
+
|
| 28 |
+
tokens = self.tokenizer(seqs, pad=True, w_extra_tkns=False, device=self.used_device)
|
| 29 |
+
predictions = self.AbLang(tokens)[:,:,1:21]
|
| 30 |
+
|
| 31 |
+
# Reshape
|
| 32 |
+
tokens = tokens.reshape(nr_seqs, self.spread, -1)
|
| 33 |
+
predictions = predictions.reshape(nr_seqs, self.spread, -1, 20)
|
| 34 |
+
seqs = seqs.reshape(nr_seqs, -1)
|
| 35 |
+
|
| 36 |
+
# Find index of best predictions
|
| 37 |
+
best_seq_idx = torch.argmax(torch.max(predictions, -1).values[:,:,1:2].mean(2), -1)
|
| 38 |
+
|
| 39 |
+
# Select best predictions
|
| 40 |
+
tokens = tokens.gather(1, best_seq_idx.view(-1, 1).unsqueeze(1).repeat(1, 1, tokens.shape[-1])).squeeze(1)
|
| 41 |
+
predictions = predictions[range(predictions.shape[0]), best_seq_idx]
|
| 42 |
+
seqs = np.take_along_axis(seqs, best_seq_idx.view(-1, 1).cpu().numpy(), axis=1)
|
| 43 |
+
|
| 44 |
+
else:
|
| 45 |
+
tokens = self.tokenizer(seqs, pad=True, w_extra_tkns=False, device=self.used_device)
|
| 46 |
+
predictions = self.AbLang(tokens)[:,:,1:21]
|
| 47 |
+
|
| 48 |
+
predicted_tokens = torch.max(predictions, -1).indices + 1
|
| 49 |
+
restored_tokens = torch.where(tokens==23, predicted_tokens, tokens)
|
| 50 |
+
|
| 51 |
+
restored_seqs = self.tokenizer(restored_tokens, mode="decode")
|
| 52 |
+
|
| 53 |
+
if n_seqs < len(restored_seqs):
|
| 54 |
+
restored_seqs = [f"{h}|{l}".replace('-','') for h,l in zip(restored_seqs[:n_seqs], restored_seqs[n_seqs:])]
|
| 55 |
+
seqs = [f"{h}|{l}" for h,l in zip(seqs[:n_seqs], seqs[n_seqs:])]
|
| 56 |
+
|
| 57 |
+
return np.array([res_to_seq(seq, 'restore') for seq in np.c_[restored_seqs, np.vectorize(len)(seqs)]])
|
| 58 |
+
|
| 59 |
+
def _create_spread_of_sequences(self, seqs, chain = 'H'):
|
| 60 |
+
import pandas as pd
|
| 61 |
+
import anarci
|
| 62 |
+
|
| 63 |
+
chain_idx = 0 if chain == 'H' else 1
|
| 64 |
+
numbered_seqs = anarci.run_anarci(
|
| 65 |
+
pd.DataFrame([seq[chain_idx].replace('*', 'X') for seq in seqs]).reset_index().values.tolist(),
|
| 66 |
+
ncpu=self.ncpu,
|
| 67 |
+
scheme='imgt',
|
| 68 |
+
allowed_species=['human', 'mouse'],
|
| 69 |
+
)
|
| 70 |
+
|
| 71 |
+
anarci_data = pd.DataFrame(
|
| 72 |
+
[str(anarci[0][0]) if anarci else 'ANARCI_error' for anarci in numbered_seqs[1]],
|
| 73 |
+
columns=['anarci']
|
| 74 |
+
).astype('<U90')
|
| 75 |
+
|
| 76 |
+
max_position = 128 if chain == 'H' else 127
|
| 77 |
+
|
| 78 |
+
seqs = anarci_data.apply(
|
| 79 |
+
lambda x: get_sequences_from_anarci(
|
| 80 |
+
x.anarci,
|
| 81 |
+
max_position,
|
| 82 |
+
self.spread
|
| 83 |
+
), axis=1, result_type='expand'
|
| 84 |
+
).to_numpy().reshape(-1)
|
| 85 |
+
|
| 86 |
+
return seqs
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
def _sequence_aligning(self, seqs):
|
| 90 |
+
|
| 91 |
+
tmp_seqs = [pairs.replace(">", "").replace("<", "").split("|") for pairs in seqs]
|
| 92 |
+
|
| 93 |
+
spread_heavy = [f"<{seq}>" for seq in self._create_spread_of_sequences(tmp_seqs, chain = 'H')]
|
| 94 |
+
spread_light = [f"<{seq}>" for seq in self._create_spread_of_sequences(tmp_seqs, chain = 'L')]
|
| 95 |
+
|
| 96 |
+
return np.concatenate([np.array(spread_heavy),np.array(spread_light)])
|
scores.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
import torch
|
| 3 |
+
|
| 4 |
+
from extra_utils import res_to_list, res_to_seq
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
class AbScores:
|
| 8 |
+
|
| 9 |
+
def __init__(self, device = 'cpu', ncpu = 1):
|
| 10 |
+
|
| 11 |
+
self.device = device
|
| 12 |
+
self.ncpu = ncpu
|
| 13 |
+
|
| 14 |
+
def _initiate_abencoding(self, model, tokenizer):
|
| 15 |
+
self.AbLang = model
|
| 16 |
+
self.tokenizer = tokenizer
|
| 17 |
+
|
| 18 |
+
def _encode_sequences(self, seqs):
|
| 19 |
+
tokens = self.tokenizer(seqs, pad=True, w_extra_tkns=False, device=self.used_device)
|
| 20 |
+
with torch.no_grad():
|
| 21 |
+
return self.AbLang.AbRep(tokens).last_hidden_states.numpy()
|
| 22 |
+
|
| 23 |
+
def _predict_logits(self, seqs):
|
| 24 |
+
tokens = self.tokenizer(seqs, pad=True, w_extra_tkns=False, device=self.used_device)
|
| 25 |
+
with torch.no_grad():
|
| 26 |
+
return self.AbLang(tokens), tokens
|
| 27 |
+
|
| 28 |
+
def pseudo_log_likelihood(self, seqs, **kwargs):
|
| 29 |
+
"""
|
| 30 |
+
Pseudo log likelihood of sequences.
|
| 31 |
+
"""
|
| 32 |
+
|
| 33 |
+
plls = []
|
| 34 |
+
for seq in seqs:
|
| 35 |
+
|
| 36 |
+
labels = self.tokenizer(
|
| 37 |
+
seq, pad=True, w_extra_tkns=False, device=self.used_device
|
| 38 |
+
)
|
| 39 |
+
|
| 40 |
+
idxs = (
|
| 41 |
+
~torch.isin(labels, torch.Tensor(self.tokenizer.all_special_tokens).to(self.used_device))
|
| 42 |
+
).nonzero()
|
| 43 |
+
|
| 44 |
+
masked_tokens = labels.repeat(len(idxs), 1)
|
| 45 |
+
for num, idx in enumerate(idxs):
|
| 46 |
+
masked_tokens[num, idx[1]] = self.tokenizer.mask_token
|
| 47 |
+
|
| 48 |
+
with torch.no_grad():
|
| 49 |
+
logits = self.AbLang(masked_tokens)
|
| 50 |
+
|
| 51 |
+
logits[:, :, self.tokenizer.all_special_tokens] = -float("inf")
|
| 52 |
+
logits = torch.stack([logits[num, idx[1]] for num, idx in enumerate(idxs)])
|
| 53 |
+
|
| 54 |
+
labels = labels[:,idxs[:,1:]].squeeze(2)[0]
|
| 55 |
+
|
| 56 |
+
nll = torch.nn.functional.cross_entropy(
|
| 57 |
+
logits,
|
| 58 |
+
labels,
|
| 59 |
+
reduction="mean",
|
| 60 |
+
)
|
| 61 |
+
|
| 62 |
+
pll = -nll
|
| 63 |
+
|
| 64 |
+
plls.append(pll)
|
| 65 |
+
|
| 66 |
+
plls = torch.stack(plls, dim=0).cpu().numpy()
|
| 67 |
+
|
| 68 |
+
return plls
|
| 69 |
+
|
| 70 |
+
def confidence(self, seqs, **kwargs):
|
| 71 |
+
"""
|
| 72 |
+
Log likelihood of sequences without masking.
|
| 73 |
+
"""
|
| 74 |
+
|
| 75 |
+
labels = self.tokenizer(
|
| 76 |
+
seqs, pad=True, w_extra_tkns=False, device=self.used_device
|
| 77 |
+
)
|
| 78 |
+
with torch.no_grad():
|
| 79 |
+
logits = self.AbLang(labels)
|
| 80 |
+
logits[:, :, self.tokenizer.all_special_tokens] = -float("inf")
|
| 81 |
+
|
| 82 |
+
plls = []
|
| 83 |
+
for label, logit in zip(labels, logits):
|
| 84 |
+
|
| 85 |
+
idxs = (
|
| 86 |
+
~torch.isin(label, torch.Tensor(self.tokenizer.all_special_tokens).to(self.used_device))
|
| 87 |
+
).nonzero().squeeze(1)
|
| 88 |
+
|
| 89 |
+
nll = torch.nn.functional.cross_entropy(
|
| 90 |
+
logit[idxs],
|
| 91 |
+
label[idxs],
|
| 92 |
+
reduction="mean",
|
| 93 |
+
)
|
| 94 |
+
|
| 95 |
+
pll = -nll
|
| 96 |
+
plls.append(pll)
|
| 97 |
+
|
| 98 |
+
return torch.stack(plls, dim=0).cpu().numpy()
|
test_ablang2_HF_implementation.ipynb
ADDED
|
@@ -0,0 +1,754 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "markdown",
|
| 5 |
+
"id": "458aed0f",
|
| 6 |
+
"metadata": {},
|
| 7 |
+
"source": [
|
| 8 |
+
"<small>Note: This notebook is adapted from the [AbLang2](https://github.com/TobiasHeOl/AbLang2) model's GitHub repository. It is used to verify that the Hugging Face implementation functions correctly and produces the same output as the original model."
|
| 9 |
+
]
|
| 10 |
+
},
|
| 11 |
+
{
|
| 12 |
+
"cell_type": "code",
|
| 13 |
+
"execution_count": 1,
|
| 14 |
+
"id": "a51e7ed2",
|
| 15 |
+
"metadata": {},
|
| 16 |
+
"outputs": [],
|
| 17 |
+
"source": [
|
| 18 |
+
"!rm -rf ~/.cache/huggingface/hub/models--hemantn--ablang2"
|
| 19 |
+
]
|
| 20 |
+
},
|
| 21 |
+
{
|
| 22 |
+
"cell_type": "code",
|
| 23 |
+
"execution_count": 2,
|
| 24 |
+
"id": "7ae54cd0-6253-46dd-a316-4f20b12041e0",
|
| 25 |
+
"metadata": {},
|
| 26 |
+
"outputs": [],
|
| 27 |
+
"source": [
|
| 28 |
+
"import sys\n",
|
| 29 |
+
"import os\n",
|
| 30 |
+
"import numpy as np\n",
|
| 31 |
+
"from transformers import AutoModel, AutoTokenizer\n",
|
| 32 |
+
"from huggingface_hub import hf_hub_download"
|
| 33 |
+
]
|
| 34 |
+
},
|
| 35 |
+
{
|
| 36 |
+
"cell_type": "markdown",
|
| 37 |
+
"id": "10801511-770d-46ac-a15d-a02d4ef9ec87",
|
| 38 |
+
"metadata": {},
|
| 39 |
+
"source": [
|
| 40 |
+
"# **0. Sequence input and its format**\n",
|
| 41 |
+
"\n",
|
| 42 |
+
"AbLang2 takes as input either the individual heavy variable domain (VH), light variable domain (VL), or the full variable domain (Fv).\n",
|
| 43 |
+
"\n",
|
| 44 |
+
"Each record (antibody) needs to be a list with the VH as the first element and the VL as the second. If either the VH or VL is not known, leave an empty string.\n",
|
| 45 |
+
"\n",
|
| 46 |
+
"An asterisk (\\*) is used for masking. It is recommended to mask residues which you are interested in mutating.\n",
|
| 47 |
+
"\n",
|
| 48 |
+
"**NB:** It is important that the VH and VL sequence is ordered correctly."
|
| 49 |
+
]
|
| 50 |
+
},
|
| 51 |
+
{
|
| 52 |
+
"cell_type": "code",
|
| 53 |
+
"execution_count": 3,
|
| 54 |
+
"id": "99192978-a008-4a32-a80e-bba238e0ec7c",
|
| 55 |
+
"metadata": {},
|
| 56 |
+
"outputs": [],
|
| 57 |
+
"source": [
|
| 58 |
+
"seq1 = [\n",
|
| 59 |
+
" 'EVQLLESGGEVKKPGASVKVSCRASGYTFRNYGLTWVRQAPGQGLEWMGWISAYNGNTNYAQKFQGRVTLTTDTSTSTAYMELRSLRSDDTAVYFCARDVPGHGAAFMDVWGTGTTVTVSS', # VH sequence\n",
|
| 60 |
+
" 'DIQLTQSPLSLPVTLGQPASISCRSSQSLEASDTNIYLSWFQQRPGQSPRRLIYKISNRDSGVPDRFSGSGSGTHFTLRISRVEADDVAVYYCMQGTHWPPAFGQGTKVDIK' # VL sequence\n",
|
| 61 |
+
"]\n",
|
| 62 |
+
"seq2 = [\n",
|
| 63 |
+
" 'EVQLLESGGEVKKPGASVKVSCRASGYTFRNYGLTWVRQAPGQGLEWMGWISAYNGNTNYAQKFQGRVTLTTDTSTSTAYMELRSLRSDDTAVYFCARDVPGHGAAFMDVWGTGTT',\n",
|
| 64 |
+
" 'PVTLGQPASISCRSSQSLEASDTNIYLSWFQQRPGQSPRRLIYKISNRDSGVPDRFSGSGSGTHFTLRISRVEADDVAVYYCMQGTHWPPAFGQGTKVDIK'\n",
|
| 65 |
+
"]\n",
|
| 66 |
+
"seq3 = [\n",
|
| 67 |
+
" 'EVQLLESGGEVKKPGASVKVSCRASGYTFRNYGLTWVRQAPGQGLEWMGWISAYNGNTNYAQKFQGRVTLTTDTSTSTAYMELRSLRSDDTAVYFCARDVPGHGAAFMDVWGTGTTVTVSS',\n",
|
| 68 |
+
" '' # The VL sequence is not known, so an empty string is left instead. \n",
|
| 69 |
+
"]\n",
|
| 70 |
+
"seq4 = [\n",
|
| 71 |
+
" '',\n",
|
| 72 |
+
" 'DIQLTQSPLSLPVTLGQPASISCRSSQSLEASDTNIYLSWFQQRPGQSPRRLIYKISNRDSGVPDRFSGSGSGTHFTLRISRVEADDVAVYYCMQGTHWPPAFGQGTKVDIK'\n",
|
| 73 |
+
"]\n",
|
| 74 |
+
"seq5 = [\n",
|
| 75 |
+
" 'EVQ***SGGEVKKPGASVKVSCRASGYTFRNYGLTWVRQAPGQGLEWMGWISAYNGNTNYAQKFQGRVTLTTDTSTSTAYMELRSLRSDDTAVYFCAR**PGHGAAFMDVWGTGTTVTVSS', # (*) is used to mask certain residues\n",
|
| 76 |
+
" 'DIQLTQSPLSLPVTLGQPASISCRSS*SLEASDTNIYLSWFQQRPGQSPRRLIYKI*NRDSGVPDRFSGSGSGTHFTLRISRVEADDVAVYYCMQGTHWPPAFGQGTKVDIK'\n",
|
| 77 |
+
"]\n",
|
| 78 |
+
"\n",
|
| 79 |
+
"all_seqs = [seq1, seq2, seq3, seq4, seq5]\n",
|
| 80 |
+
"only_both_chains_seqs = [seq1, seq2, seq5]"
|
| 81 |
+
]
|
| 82 |
+
},
|
| 83 |
+
{
|
| 84 |
+
"cell_type": "markdown",
|
| 85 |
+
"id": "dffbacfa-8642-4d94-9572-2205a05c18f9",
|
| 86 |
+
"metadata": {},
|
| 87 |
+
"source": [
|
| 88 |
+
"# **1. How to use AbLang2**\n",
|
| 89 |
+
"\n",
|
| 90 |
+
"AbLang2 can be downloaded and used in its raw form as seen below. For convenience, we have also developed different \"modes\" which can be used for specific use cases (see Section 2) "
|
| 91 |
+
]
|
| 92 |
+
},
|
| 93 |
+
{
|
| 94 |
+
"cell_type": "code",
|
| 95 |
+
"execution_count": 4,
|
| 96 |
+
"id": "6d66ad84",
|
| 97 |
+
"metadata": {},
|
| 98 |
+
"outputs": [
|
| 99 |
+
{
|
| 100 |
+
"data": {
|
| 101 |
+
"application/vnd.jupyter.widget-view+json": {
|
| 102 |
+
"model_id": "ed2d5574bd21463c9244070ab762c31e",
|
| 103 |
+
"version_major": 2,
|
| 104 |
+
"version_minor": 0
|
| 105 |
+
},
|
| 106 |
+
"text/plain": [
|
| 107 |
+
"config.json: 0%| | 0.00/763 [00:00<?, ?B/s]"
|
| 108 |
+
]
|
| 109 |
+
},
|
| 110 |
+
"metadata": {},
|
| 111 |
+
"output_type": "display_data"
|
| 112 |
+
},
|
| 113 |
+
{
|
| 114 |
+
"data": {
|
| 115 |
+
"application/vnd.jupyter.widget-view+json": {
|
| 116 |
+
"model_id": "10e1a02037f74d2da6e0860ef914829b",
|
| 117 |
+
"version_major": 2,
|
| 118 |
+
"version_minor": 0
|
| 119 |
+
},
|
| 120 |
+
"text/plain": [
|
| 121 |
+
"configuration_ablang2paired.py: 0.00B [00:00, ?B/s]"
|
| 122 |
+
]
|
| 123 |
+
},
|
| 124 |
+
"metadata": {},
|
| 125 |
+
"output_type": "display_data"
|
| 126 |
+
},
|
| 127 |
+
{
|
| 128 |
+
"name": "stderr",
|
| 129 |
+
"output_type": "stream",
|
| 130 |
+
"text": [
|
| 131 |
+
"A new version of the following files was downloaded from https://huggingface.co/hemantn/ablang2:\n",
|
| 132 |
+
"- configuration_ablang2paired.py\n",
|
| 133 |
+
". Make sure to double-check they do not contain any added malicious code. To avoid downloading new versions of the code file, you can pin a revision.\n"
|
| 134 |
+
]
|
| 135 |
+
},
|
| 136 |
+
{
|
| 137 |
+
"data": {
|
| 138 |
+
"application/vnd.jupyter.widget-view+json": {
|
| 139 |
+
"model_id": "eaf036440107433f950cf4b8c652d756",
|
| 140 |
+
"version_major": 2,
|
| 141 |
+
"version_minor": 0
|
| 142 |
+
},
|
| 143 |
+
"text/plain": [
|
| 144 |
+
"modeling_ablang2paired.py: 0.00B [00:00, ?B/s]"
|
| 145 |
+
]
|
| 146 |
+
},
|
| 147 |
+
"metadata": {},
|
| 148 |
+
"output_type": "display_data"
|
| 149 |
+
},
|
| 150 |
+
{
|
| 151 |
+
"name": "stderr",
|
| 152 |
+
"output_type": "stream",
|
| 153 |
+
"text": [
|
| 154 |
+
"A new version of the following files was downloaded from https://huggingface.co/hemantn/ablang2:\n",
|
| 155 |
+
"- modeling_ablang2paired.py\n",
|
| 156 |
+
". Make sure to double-check they do not contain any added malicious code. To avoid downloading new versions of the code file, you can pin a revision.\n",
|
| 157 |
+
"/home/hn533621/.conda/envs/lib_transformer/lib/python3.10/site-packages/huggingface_hub/file_download.py:943: FutureWarning: `resume_download` is deprecated and will be removed in version 1.0.0. Downloads always resume when possible. If you want to force a new download, use `force_download=True`.\n",
|
| 158 |
+
" warnings.warn(\n"
|
| 159 |
+
]
|
| 160 |
+
},
|
| 161 |
+
{
|
| 162 |
+
"data": {
|
| 163 |
+
"application/vnd.jupyter.widget-view+json": {
|
| 164 |
+
"model_id": "22b9a58a3100420c9e353415e7194af6",
|
| 165 |
+
"version_major": 2,
|
| 166 |
+
"version_minor": 0
|
| 167 |
+
},
|
| 168 |
+
"text/plain": [
|
| 169 |
+
"model.pt: 0%| | 0.00/179M [00:00<?, ?B/s]"
|
| 170 |
+
]
|
| 171 |
+
},
|
| 172 |
+
"metadata": {},
|
| 173 |
+
"output_type": "display_data"
|
| 174 |
+
},
|
| 175 |
+
{
|
| 176 |
+
"name": "stdout",
|
| 177 |
+
"output_type": "stream",
|
| 178 |
+
"text": [
|
| 179 |
+
"✅ Loaded custom weights from: /home/hn533621/.cache/huggingface/hub/models--hemantn--ablang2/snapshots/13d4401549c368256c517dc13b8ed3d8b28d5e87/model.pt\n"
|
| 180 |
+
]
|
| 181 |
+
},
|
| 182 |
+
{
|
| 183 |
+
"data": {
|
| 184 |
+
"application/vnd.jupyter.widget-view+json": {
|
| 185 |
+
"model_id": "e1c40183f9104aa1a67bf9b1c3daea0c",
|
| 186 |
+
"version_major": 2,
|
| 187 |
+
"version_minor": 0
|
| 188 |
+
},
|
| 189 |
+
"text/plain": [
|
| 190 |
+
"tokenizer_ablang2paired.py: 0.00B [00:00, ?B/s]"
|
| 191 |
+
]
|
| 192 |
+
},
|
| 193 |
+
"metadata": {},
|
| 194 |
+
"output_type": "display_data"
|
| 195 |
+
},
|
| 196 |
+
{
|
| 197 |
+
"name": "stderr",
|
| 198 |
+
"output_type": "stream",
|
| 199 |
+
"text": [
|
| 200 |
+
"A new version of the following files was downloaded from https://huggingface.co/hemantn/ablang2:\n",
|
| 201 |
+
"- tokenizer_ablang2paired.py\n",
|
| 202 |
+
". Make sure to double-check they do not contain any added malicious code. To avoid downloading new versions of the code file, you can pin a revision.\n"
|
| 203 |
+
]
|
| 204 |
+
},
|
| 205 |
+
{
|
| 206 |
+
"data": {
|
| 207 |
+
"application/vnd.jupyter.widget-view+json": {
|
| 208 |
+
"model_id": "3fadab1179e2438ba88e08efb7819680",
|
| 209 |
+
"version_major": 2,
|
| 210 |
+
"version_minor": 0
|
| 211 |
+
},
|
| 212 |
+
"text/plain": [
|
| 213 |
+
"vocab.json: 0%| | 0.00/331 [00:00<?, ?B/s]"
|
| 214 |
+
]
|
| 215 |
+
},
|
| 216 |
+
"metadata": {},
|
| 217 |
+
"output_type": "display_data"
|
| 218 |
+
},
|
| 219 |
+
{
|
| 220 |
+
"data": {
|
| 221 |
+
"application/vnd.jupyter.widget-view+json": {
|
| 222 |
+
"model_id": "5673cfaa95ac4da78e627c36ad6191b0",
|
| 223 |
+
"version_major": 2,
|
| 224 |
+
"version_minor": 0
|
| 225 |
+
},
|
| 226 |
+
"text/plain": [
|
| 227 |
+
"adapter.py: 0.00B [00:00, ?B/s]"
|
| 228 |
+
]
|
| 229 |
+
},
|
| 230 |
+
"metadata": {},
|
| 231 |
+
"output_type": "display_data"
|
| 232 |
+
},
|
| 233 |
+
{
|
| 234 |
+
"name": "stdout",
|
| 235 |
+
"output_type": "stream",
|
| 236 |
+
"text": [
|
| 237 |
+
"📁 Files in current directory (/home/hn533621/.cache/huggingface/hub/models--hemantn--ablang2/snapshots/13d4401549c368256c517dc13b8ed3d8b28d5e87):\n",
|
| 238 |
+
" adapter.py\n",
|
| 239 |
+
" configuration_ablang2paired.py\n",
|
| 240 |
+
" tokenizer_ablang2paired.py\n",
|
| 241 |
+
" modeling_ablang2paired.py\n",
|
| 242 |
+
"✅ Successfully imported utility modules from cache directory\n"
|
| 243 |
+
]
|
| 244 |
+
}
|
| 245 |
+
],
|
| 246 |
+
"source": [
|
| 247 |
+
"# Load model and tokenizer from Hugging Face Hub\n",
|
| 248 |
+
"model = AutoModel.from_pretrained(\"hemantn/ablang2\", trust_remote_code=True)\n",
|
| 249 |
+
"tokenizer = AutoTokenizer.from_pretrained(\"hemantn/ablang2\", trust_remote_code=True)\n",
|
| 250 |
+
"\n",
|
| 251 |
+
"# Find the cached model directory and import adapter\n",
|
| 252 |
+
"adapter_path = hf_hub_download(repo_id=\"hemantn/ablang2\", filename=\"adapter.py\")\n",
|
| 253 |
+
"cached_model_dir = os.path.dirname(adapter_path)\n",
|
| 254 |
+
"sys.path.insert(0, cached_model_dir)\n",
|
| 255 |
+
"\n",
|
| 256 |
+
"# Import and create the adapter\n",
|
| 257 |
+
"from adapter import AbLang2PairedHuggingFaceAdapter\n",
|
| 258 |
+
"ablang = AbLang2PairedHuggingFaceAdapter(model=model, tokenizer=tokenizer)"
|
| 259 |
+
]
|
| 260 |
+
},
|
| 261 |
+
{
|
| 262 |
+
"cell_type": "markdown",
|
| 263 |
+
"id": "48562761-6ebe-4025-be97-918c9f9eff7e",
|
| 264 |
+
"metadata": {},
|
| 265 |
+
"source": [
|
| 266 |
+
"# **2. Different modes for specific usecases**\n",
|
| 267 |
+
"\n",
|
| 268 |
+
"AbLang2 has already been implemented for a variety of different usecases. The benefit of these modes is that they handle extra tokens such as start, stop and separation tokens.\n",
|
| 269 |
+
"\n",
|
| 270 |
+
"1. seqcoding: Generates sequence representations for each sequence\n",
|
| 271 |
+
"2. rescoding: Generates residue representations for each residue in each sequence\n",
|
| 272 |
+
"3. likelihood: Generates likelihoods for each amino acid at each position in each sequence\n",
|
| 273 |
+
"4. probability: Generates probabilities for each amino acid at each position in each sequence\n",
|
| 274 |
+
"5. pseudo_log_likelihood: Returns the pseudo log likelihood for a sequence (based on masking each residue one at a time)\n",
|
| 275 |
+
"6. confidence: Returns a fast calculation of the log likelihood for a sequence (based on a single pass with no masking)\n",
|
| 276 |
+
"7. restore: Restores masked residues\n",
|
| 277 |
+
"\n",
|
| 278 |
+
"### **AbLang2 can also align the resulting representations using ANARCI**\n",
|
| 279 |
+
"\n",
|
| 280 |
+
"This can be done for 'rescoding', 'likelihood', and 'probability'. This is done by setting the argument \"align=True\".\n",
|
| 281 |
+
"\n",
|
| 282 |
+
"**NB**: Align can only be used on input with the same format, i.e. either all heavy, all light, or all both heavy and light.\n",
|
| 283 |
+
"\n",
|
| 284 |
+
"### **The align argument can also be used to restore variable missing lengths**\n",
|
| 285 |
+
"\n",
|
| 286 |
+
"For this, use \"align=True\" with the 'restore' mode."
|
| 287 |
+
]
|
| 288 |
+
},
|
| 289 |
+
{
|
| 290 |
+
"cell_type": "code",
|
| 291 |
+
"execution_count": 5,
|
| 292 |
+
"id": "ceae4a88-0679-4704-8bad-c06a4569c497",
|
| 293 |
+
"metadata": {},
|
| 294 |
+
"outputs": [],
|
| 295 |
+
"source": [
|
| 296 |
+
"valid_modes = [\n",
|
| 297 |
+
" 'seqcoding', 'rescoding', 'likelihood', 'probability',\n",
|
| 298 |
+
" 'pseudo_log_likelihood', 'confidence', 'restore' \n",
|
| 299 |
+
"]"
|
| 300 |
+
]
|
| 301 |
+
},
|
| 302 |
+
{
|
| 303 |
+
"cell_type": "markdown",
|
| 304 |
+
"id": "aa333732-7508-4826-92ec-3acdd54bc1bb",
|
| 305 |
+
"metadata": {},
|
| 306 |
+
"source": [
|
| 307 |
+
"## **seqcoding** \n",
|
| 308 |
+
"\n",
|
| 309 |
+
"The seqcodings represents each sequence as a 480 sized embedding. It is derived from averaging across each rescoding embedding for a given sequence, including extra tokens. \n",
|
| 310 |
+
"\n",
|
| 311 |
+
"**NB:** Seqcodings can also be derived in other ways like using the sum or averaging across only parts of the input such as the CDRs. For such cases please use and adapt the below rescoding."
|
| 312 |
+
]
|
| 313 |
+
},
|
| 314 |
+
{
|
| 315 |
+
"cell_type": "code",
|
| 316 |
+
"execution_count": 6,
|
| 317 |
+
"id": "d22f4302-1262-4cc1-8a1c-a36daa8c710c",
|
| 318 |
+
"metadata": {},
|
| 319 |
+
"outputs": [
|
| 320 |
+
{
|
| 321 |
+
"data": {
|
| 322 |
+
"text/plain": [
|
| 323 |
+
"array([[-0.2520631 , 0.18189636, 0.00887137, ..., 0.15365516,\n",
|
| 324 |
+
" -0.14508602, -0.13381316],\n",
|
| 325 |
+
" [-0.24383117, 0.20946886, 0.07412891, ..., 0.15079288,\n",
|
| 326 |
+
" -0.13847049, -0.07304662],\n",
|
| 327 |
+
" [-0.20084268, 0.23405147, -0.00103735, ..., 0.07450922,\n",
|
| 328 |
+
" -0.08084311, -0.21812904],\n",
|
| 329 |
+
" [-0.12659703, 0.3051279 , -0.15117611, ..., -0.20749238,\n",
|
| 330 |
+
" -0.10453435, -0.0787883 ],\n",
|
| 331 |
+
" [-0.2955319 , 0.17239201, 0.05676926, ..., 0.15943624,\n",
|
| 332 |
+
" -0.16615382, -0.15569784]], shape=(5, 480), dtype=float32)"
|
| 333 |
+
]
|
| 334 |
+
},
|
| 335 |
+
"execution_count": 6,
|
| 336 |
+
"metadata": {},
|
| 337 |
+
"output_type": "execute_result"
|
| 338 |
+
}
|
| 339 |
+
],
|
| 340 |
+
"source": [
|
| 341 |
+
"ablang(all_seqs, mode='seqcoding')\n"
|
| 342 |
+
]
|
| 343 |
+
},
|
| 344 |
+
{
|
| 345 |
+
"cell_type": "markdown",
|
| 346 |
+
"id": "4b5d9d60",
|
| 347 |
+
"metadata": {},
|
| 348 |
+
"source": [
|
| 349 |
+
"## **rescoding / likelihood / probability**\n",
|
| 350 |
+
"\n",
|
| 351 |
+
"The rescodings represents each residue as a 480 sized embedding. The likelihoods represents each residue as the predicted logits for each character in the vocabulary. The probabilities represents the normalised likelihoods.\n",
|
| 352 |
+
"\n",
|
| 353 |
+
"**NB:** The output includes extra tokens (start, stop and separation tokens) in the format \"<VH_seq>|<VL_seq>\". The length of the output is therefore 5 longer than the VH and VL.\n",
|
| 354 |
+
"\n",
|
| 355 |
+
"**NB:** By default the representations are derived using a single forward pass. To prevent the predicted likelihood and probability to be affected by the input residue at each position, setting the \"stepwise_masking\" argument to True can be used. This will run a forward pass for each position with the residue at that position masked. This is much slower than running a single forward pass."
|
| 356 |
+
]
|
| 357 |
+
},
|
| 358 |
+
{
|
| 359 |
+
"cell_type": "code",
|
| 360 |
+
"execution_count": 7,
|
| 361 |
+
"id": "6227f661-575f-4b1e-9646-cfba7b10c3b4",
|
| 362 |
+
"metadata": {},
|
| 363 |
+
"outputs": [
|
| 364 |
+
{
|
| 365 |
+
"data": {
|
| 366 |
+
"text/plain": [
|
| 367 |
+
"[array([[-0.40741208, -0.5118987 , 0.06096708, ..., 0.3268144 ,\n",
|
| 368 |
+
" 0.03920235, -0.36715826],\n",
|
| 369 |
+
" [-0.5768883 , 0.38245413, -0.21791998, ..., 0.01250262,\n",
|
| 370 |
+
" -0.08844463, -0.32367525],\n",
|
| 371 |
+
" [-0.1475935 , 0.39639047, -0.38226923, ..., -0.10119921,\n",
|
| 372 |
+
" -0.41469565, -0.00319315],\n",
|
| 373 |
+
" ...,\n",
|
| 374 |
+
" [-0.14358369, 0.3124389 , -0.30157998, ..., -0.13289244,\n",
|
| 375 |
+
" -0.45353398, -0.07878865],\n",
|
| 376 |
+
" [ 0.17538925, 0.24394299, 0.20141171, ..., 0.14587352,\n",
|
| 377 |
+
" -0.38479003, 0.07409196],\n",
|
| 378 |
+
" [-0.23031706, -0.35487285, 0.1960684 , ..., -0.1283362 ,\n",
|
| 379 |
+
" 0.31107333, -0.3265108 ]], shape=(238, 480), dtype=float32),\n",
|
| 380 |
+
" array([[-0.41981837, -0.3666375 , 0.10595217, ..., 0.3903574 ,\n",
|
| 381 |
+
" 0.0382378 , -0.36337993],\n",
|
| 382 |
+
" [-0.5054137 , 0.38347068, -0.10992069, ..., -0.05231472,\n",
|
| 383 |
+
" -0.13636623, -0.34830108],\n",
|
| 384 |
+
" [-0.06784609, 0.69349885, -0.4212398 , ..., -0.24805346,\n",
|
| 385 |
+
" -0.39583805, -0.10972726],\n",
|
| 386 |
+
" ...,\n",
|
| 387 |
+
" [-0.02212614, 0.26338235, -0.5558968 , ..., -0.24067189,\n",
|
| 388 |
+
" -0.11965694, 0.07879876],\n",
|
| 389 |
+
" [-0.20650092, 0.43451664, -0.09650223, ..., -0.05296766,\n",
|
| 390 |
+
" -0.04297376, 0.41854134],\n",
|
| 391 |
+
" [-0.02653179, 0.03729444, 0.13194172, ..., -0.4554279 ,\n",
|
| 392 |
+
" 0.03723941, 0.17769177]], shape=(238, 480), dtype=float32),\n",
|
| 393 |
+
" array([[-0.40043733, -0.48596814, 0.0886725 , ..., 0.38941646,\n",
|
| 394 |
+
" 0.06195956, -0.40999672],\n",
|
| 395 |
+
" [-0.54576075, 0.4312959 , -0.3451486 , ..., -0.09285564,\n",
|
| 396 |
+
" 0.03116508, -0.45269737],\n",
|
| 397 |
+
" [ 0.0221165 , 0.53196615, -0.30137214, ..., -0.1889072 ,\n",
|
| 398 |
+
" -0.32587305, 0.05078396],\n",
|
| 399 |
+
" ...,\n",
|
| 400 |
+
" [-0.03700298, 0.7739084 , 0.3454928 , ..., -0.03060072,\n",
|
| 401 |
+
" 0.02420983, -0.48005292],\n",
|
| 402 |
+
" [-0.03366657, 0.74771184, -0.35423476, ..., -0.08759108,\n",
|
| 403 |
+
" -0.17898935, -0.4540483 ],\n",
|
| 404 |
+
" [-0.16625853, 0.2701079 , -0.19761363, ..., 0.10313392,\n",
|
| 405 |
+
" 0.44890267, -0.64840287]], shape=(238, 480), dtype=float32),\n",
|
| 406 |
+
" array([[-0.26863217, 0.32259187, 0.10813517, ..., 0.03953876,\n",
|
| 407 |
+
" 0.18312076, -0.00498045],\n",
|
| 408 |
+
" [-0.2165424 , -0.38562432, -0.02696264, ..., 0.20541488,\n",
|
| 409 |
+
" 0.18698391, -0.22639504],\n",
|
| 410 |
+
" [-0.41950518, 0.04743317, 0.0048816 , ..., 0.11408642,\n",
|
| 411 |
+
" -0.05384652, 0.1025871 ],\n",
|
| 412 |
+
" ...,\n",
|
| 413 |
+
" [-0.14095458, 0.5860325 , -0.44657114, ..., -0.39150292,\n",
|
| 414 |
+
" -0.22395667, -0.42516366],\n",
|
| 415 |
+
" [ 0.29816052, 0.40440455, -0.52062094, ..., 0.08969188,\n",
|
| 416 |
+
" -0.20792632, -0.2045222 ],\n",
|
| 417 |
+
" [-0.21370608, 0.23035707, -0.355185 , ..., -0.36726946,\n",
|
| 418 |
+
" -0.05693531, -0.37847823]], shape=(238, 480), dtype=float32),\n",
|
| 419 |
+
" array([[-0.42062947, -0.44009134, 0.00152371, ..., 0.27141467,\n",
|
| 420 |
+
" 0.03798106, -0.397461 ],\n",
|
| 421 |
+
" [-0.57318133, 0.5258899 , -0.17001636, ..., -0.23864633,\n",
|
| 422 |
+
" 0.2088059 , -0.57877594],\n",
|
| 423 |
+
" [-0.38988614, 0.46168196, -0.3429413 , ..., -0.14872643,\n",
|
| 424 |
+
" -0.46576905, -0.21224979],\n",
|
| 425 |
+
" ...,\n",
|
| 426 |
+
" [-0.21528634, 0.30046722, -0.25216463, ..., -0.11576828,\n",
|
| 427 |
+
" -0.4704907 , -0.0740136 ],\n",
|
| 428 |
+
" [ 0.0633081 , 0.22700705, 0.28184187, ..., 0.15967266,\n",
|
| 429 |
+
" -0.377182 , 0.06188517],\n",
|
| 430 |
+
" [-0.27826303, -0.37297496, 0.21229912, ..., -0.14886017,\n",
|
| 431 |
+
" 0.24998347, -0.35954213]], shape=(238, 480), dtype=float32)]"
|
| 432 |
+
]
|
| 433 |
+
},
|
| 434 |
+
"execution_count": 7,
|
| 435 |
+
"metadata": {},
|
| 436 |
+
"output_type": "execute_result"
|
| 437 |
+
}
|
| 438 |
+
],
|
| 439 |
+
"source": [
|
| 440 |
+
"ablang(all_seqs, mode='rescoding', stepwise_masking = False)"
|
| 441 |
+
]
|
| 442 |
+
},
|
| 443 |
+
{
|
| 444 |
+
"cell_type": "markdown",
|
| 445 |
+
"id": "6da2183b-4306-49bd-a7fc-23e78a23f305",
|
| 446 |
+
"metadata": {},
|
| 447 |
+
"source": [
|
| 448 |
+
"## **Align rescoding/likelihood/probability output**\n",
|
| 449 |
+
"\n",
|
| 450 |
+
"For the 'rescoding', 'likelihood', and 'probability' modes, the output can also be aligned using the argument \"align=True\".\n",
|
| 451 |
+
"\n",
|
| 452 |
+
"This is done using the antibody numbering tool ANARCI, and requires manually installing **Pandas** and **[ANARCI](https://github.com/oxpig/ANARCI)**.\n",
|
| 453 |
+
"\n",
|
| 454 |
+
"**NB**: Align can only be used on input with the same format, i.e. either all heavy, all light, or all both heavy and light."
|
| 455 |
+
]
|
| 456 |
+
},
|
| 457 |
+
{
|
| 458 |
+
"cell_type": "code",
|
| 459 |
+
"execution_count": 8,
|
| 460 |
+
"id": "e4bc0cb1-f5b0-4255-9e93-d643ae1396df",
|
| 461 |
+
"metadata": {},
|
| 462 |
+
"outputs": [
|
| 463 |
+
{
|
| 464 |
+
"name": "stdout",
|
| 465 |
+
"output_type": "stream",
|
| 466 |
+
"text": [
|
| 467 |
+
"['<' '1 ' '2 ' '3 ' '4 ' '5 ' '6 ' '7 ' '8 ' '9 ' '11 ' '12 ' '13 ' '14 '\n",
|
| 468 |
+
" '15 ' '16 ' '17 ' '18 ' '19 ' '20 ' '21 ' '22 ' '23 ' '24 ' '25 ' '26 '\n",
|
| 469 |
+
" '27 ' '28 ' '29 ' '30 ' '35 ' '36 ' '37 ' '38 ' '39 ' '40 ' '41 ' '42 '\n",
|
| 470 |
+
" '43 ' '44 ' '45 ' '46 ' '47 ' '48 ' '49 ' '50 ' '51 ' '52 ' '53 ' '54 '\n",
|
| 471 |
+
" '55 ' '56 ' '57 ' '58 ' '59 ' '62 ' '63 ' '64 ' '65 ' '66 ' '67 ' '68 '\n",
|
| 472 |
+
" '69 ' '70 ' '71 ' '72 ' '74 ' '75 ' '76 ' '77 ' '78 ' '79 ' '80 ' '81 '\n",
|
| 473 |
+
" '82 ' '83 ' '84 ' '85 ' '86 ' '87 ' '88 ' '89 ' '90 ' '91 ' '92 ' '93 '\n",
|
| 474 |
+
" '94 ' '95 ' '96 ' '97 ' '98 ' '99 ' '100 ' '101 ' '102 ' '103 ' '104 '\n",
|
| 475 |
+
" '105 ' '106 ' '107 ' '108 ' '109 ' '110 ' '111 ' '112A' '112 ' '113 '\n",
|
| 476 |
+
" '114 ' '115 ' '116 ' '117 ' '118 ' '119 ' '120 ' '121 ' '122 ' '123 '\n",
|
| 477 |
+
" '124 ' '125 ' '126 ' '127 ' '128 ' '>' '|' '<' '1 ' '2 ' '3 ' '4 ' '5 '\n",
|
| 478 |
+
" '6 ' '7 ' '8 ' '9 ' '10 ' '11 ' '12 ' '13 ' '14 ' '15 ' '16 ' '17 ' '18 '\n",
|
| 479 |
+
" '19 ' '20 ' '21 ' '22 ' '23 ' '24 ' '25 ' '26 ' '27 ' '28 ' '29 ' '30 '\n",
|
| 480 |
+
" '31 ' '32 ' '34 ' '35 ' '36 ' '37 ' '38 ' '39 ' '40 ' '41 ' '42 ' '43 '\n",
|
| 481 |
+
" '44 ' '45 ' '46 ' '47 ' '48 ' '49 ' '50 ' '51 ' '52 ' '53 ' '54 ' '55 '\n",
|
| 482 |
+
" '56 ' '57 ' '64 ' '65 ' '66 ' '67 ' '68 ' '69 ' '70 ' '71 ' '72 ' '74 '\n",
|
| 483 |
+
" '75 ' '76 ' '77 ' '78 ' '79 ' '80 ' '83 ' '84 ' '85 ' '86 ' '87 ' '88 '\n",
|
| 484 |
+
" '89 ' '90 ' '91 ' '92 ' '93 ' '94 ' '95 ' '96 ' '97 ' '98 ' '99 ' '100 '\n",
|
| 485 |
+
" '101 ' '102 ' '103 ' '104 ' '105 ' '106 ' '107 ' '108 ' '109 ' '114 '\n",
|
| 486 |
+
" '115 ' '116 ' '117 ' '118 ' '119 ' '120 ' '121 ' '122 ' '123 ' '124 '\n",
|
| 487 |
+
" '125 ' '126 ' '127 ' '>']\n",
|
| 488 |
+
"['<EVQLLESGGEVKKPGASVKVSCRASGYTFRNYGLTWVRQAPGQGLEWMGWISAYNGNTNYAQKFQGRVTLTTDTSTSTAYMELRSLRSDDTAVYFCARDVPGHGAAFMDVWGTGTTVTVSS>|<DIQLTQSPLSLPVTLGQPASISCRSSQSLEASDTNIYLSWFQQRPGQSPRRLIYKI-SNRDSGVPDRFSGSGSGTHFTLRISRVEADDVAVYYCMQGTHWPPAFGQGTKVDIK>', '<EVQLLESGGEVKKPGASVKVSCRASGYTFRNYGLTWVRQAPGQGLEWMGWISAYNGNTNYAQKFQGRVTLTTDTSTSTAYMELRSLRSDDTAVYFCARDVPGHGAAFMDVWGTGTT----->|<-----------PVTLGQPASISCRSSQSLEASDTNIYLSWFQQRPGQSPRRLIYKI-SNRDSGVPDRFSGSGSGTHFTLRISRVEADDVAVYYCMQGTHWPPAFGQGTKVDIK>', '<------SGGEVKKPGASVKVSCRASGYTFRNYGLTWVRQAPGQGLEWMGWISAYNGNTNYAQKFQGRVTLTTDTSTSTAYMELRSLRSDDTAVYFCAR**PGHGAAFMDVWGTGTTVTVSS>|<DIQLTQSPLSLPVTLGQPASISCRSS*SLEASDTNIYLSWFQQRPGQSPRRLIYKI*N-RDSGVPDRFSGSGSGTHFTLRISRVEADDVAVYYCMQGTHWPPAFGQGTKVDIK>']\n",
|
| 489 |
+
"[[[ 9.31621838 -3.42184329 -3.59397745 ... -14.73707485 -6.8935833\n",
|
| 490 |
+
" -0.23662776]\n",
|
| 491 |
+
" [ -3.54718232 -5.84866619 -4.02423859 ... -12.93966579 -9.5614481\n",
|
| 492 |
+
" -4.48473835]\n",
|
| 493 |
+
" [-11.94997597 -2.245543 -5.69481373 ... -15.19639015 -17.97454071\n",
|
| 494 |
+
" -12.56952095]\n",
|
| 495 |
+
" ...\n",
|
| 496 |
+
" [ -8.94504833 -0.42261261 -4.95588207 ... -16.66817474 -15.2224741\n",
|
| 497 |
+
" -10.37267494]\n",
|
| 498 |
+
" [-11.65150356 -5.44477606 -2.95585775 ... -16.25555801 -9.75158596\n",
|
| 499 |
+
" -11.75897026]\n",
|
| 500 |
+
" [ 1.79469728 -1.95846701 -3.59784532 ... -14.95585823 -7.47080708\n",
|
| 501 |
+
" -0.95226753]]\n",
|
| 502 |
+
"\n",
|
| 503 |
+
" [[ 8.55518723 -3.83663297 -2.33595967 ... -13.87456799 -8.14840603\n",
|
| 504 |
+
" -0.42472434]\n",
|
| 505 |
+
" [ -4.40701294 -5.53201008 -3.69397402 ... -12.97877789 -9.86258411\n",
|
| 506 |
+
" -4.95414352]\n",
|
| 507 |
+
" [-11.95642853 -3.86210871 -5.80935192 ... -14.89213085 -16.94556236\n",
|
| 508 |
+
" -11.36959839]\n",
|
| 509 |
+
" ...\n",
|
| 510 |
+
" [ -7.75924015 -0.66524202 -4.08643246 ... -16.16580772 -14.76507473\n",
|
| 511 |
+
" -8.3507061 ]\n",
|
| 512 |
+
" [-11.91039753 -4.86995983 -2.74777436 ... -16.07694817 -8.44974899\n",
|
| 513 |
+
" -10.45223904]\n",
|
| 514 |
+
" [ 0.86006832 -2.37964034 -3.58130741 ... -15.35423565 -7.73035526\n",
|
| 515 |
+
" -1.11989737]]\n",
|
| 516 |
+
"\n",
|
| 517 |
+
" [[ -4.37902737 -7.55587149 1.21958363 ... -15.48622513 -6.021842\n",
|
| 518 |
+
" -3.79647374]\n",
|
| 519 |
+
" [ 0. 0. 0. ... 0. 0.\n",
|
| 520 |
+
" 0. ]\n",
|
| 521 |
+
" [ 0. 0. 0. ... 0. 0.\n",
|
| 522 |
+
" 0. ]\n",
|
| 523 |
+
" ...\n",
|
| 524 |
+
" [ -8.94207573 -0.51090252 -5.09760332 ... -16.69521713 -15.45450687\n",
|
| 525 |
+
" -10.50823212]\n",
|
| 526 |
+
" [-11.92354965 -5.55152607 -2.87666893 ... -16.40607834 -10.19431686\n",
|
| 527 |
+
" -12.1328764 ]\n",
|
| 528 |
+
" [ 2.42200375 -2.01573253 -3.61701298 ... -14.9590435 -7.19029331\n",
|
| 529 |
+
" -0.89830256]]]\n"
|
| 530 |
+
]
|
| 531 |
+
}
|
| 532 |
+
],
|
| 533 |
+
"source": [
|
| 534 |
+
"results = ablang(only_both_chains_seqs, mode='likelihood', align=True)\n",
|
| 535 |
+
"\n",
|
| 536 |
+
"print(results.number_alignment)\n",
|
| 537 |
+
"print(results.aligned_seqs)\n",
|
| 538 |
+
"print(results.aligned_embeds)"
|
| 539 |
+
]
|
| 540 |
+
},
|
| 541 |
+
{
|
| 542 |
+
"cell_type": "code",
|
| 543 |
+
"execution_count": 9,
|
| 544 |
+
"id": "56be8cad",
|
| 545 |
+
"metadata": {},
|
| 546 |
+
"outputs": [
|
| 547 |
+
{
|
| 548 |
+
"data": {
|
| 549 |
+
"text/plain": [
|
| 550 |
+
"[array([[9.9955505e-01, 2.9358694e-06, 2.4716087e-06, ..., 3.5776201e-11,\n",
|
| 551 |
+
" 9.1196831e-08, 7.0967326e-05],\n",
|
| 552 |
+
" [4.1573694e-06, 4.1619489e-07, 2.5800944e-06, ..., 3.4650952e-10,\n",
|
| 553 |
+
" 1.0159109e-08, 1.6279575e-06],\n",
|
| 554 |
+
" [7.8059600e-08, 1.2794037e-03, 4.0645118e-05, ..., 3.0375720e-09,\n",
|
| 555 |
+
" 1.8879491e-10, 4.2010839e-08],\n",
|
| 556 |
+
" ...,\n",
|
| 557 |
+
" [3.4210879e-07, 1.7195340e-03, 1.8477240e-05, ..., 1.5137445e-10,\n",
|
| 558 |
+
" 6.4255873e-10, 8.2064140e-08],\n",
|
| 559 |
+
" [9.1038084e-09, 4.5161755e-06, 5.4411950e-05, ..., 9.1139631e-11,\n",
|
| 560 |
+
" 6.0862085e-08, 8.1761966e-09],\n",
|
| 561 |
+
" [8.5759175e-04, 2.0104915e-05, 3.9023766e-06, ..., 4.5562460e-11,\n",
|
| 562 |
+
" 8.1156479e-08, 5.4990651e-05]], shape=(238, 26), dtype=float32),\n",
|
| 563 |
+
" array([[9.9939799e-01, 4.1499175e-06, 1.8611167e-05, ..., 1.8139243e-10,\n",
|
| 564 |
+
" 5.5649299e-08, 1.2583815e-04],\n",
|
| 565 |
+
" [1.6735513e-06, 5.4332406e-07, 3.4143472e-06, ..., 3.1693398e-10,\n",
|
| 566 |
+
" 7.1501400e-09, 9.6832969e-07],\n",
|
| 567 |
+
" [3.7784993e-08, 1.2377645e-04, 1.7658784e-05, ..., 2.0061326e-09,\n",
|
| 568 |
+
" 2.5737484e-10, 6.7947965e-08],\n",
|
| 569 |
+
" ...,\n",
|
| 570 |
+
" [1.1050455e-06, 1.3312638e-03, 4.3497097e-05, ..., 2.4686178e-10,\n",
|
| 571 |
+
" 1.0018089e-09, 6.1165900e-07],\n",
|
| 572 |
+
" [5.7270397e-09, 6.5396339e-06, 5.4601755e-05, ..., 8.8801404e-11,\n",
|
| 573 |
+
" 1.8233513e-07, 2.4615032e-08],\n",
|
| 574 |
+
" [7.3952030e-04, 2.8970928e-05, 8.7113440e-06, ..., 6.7168833e-11,\n",
|
| 575 |
+
" 1.3746008e-07, 1.0210846e-04]], shape=(222, 26), dtype=float32),\n",
|
| 576 |
+
" array([[9.99685407e-01, 3.35662639e-06, 1.14241482e-06, ...,\n",
|
| 577 |
+
" 2.32460891e-11, 6.88188067e-08, 5.69467156e-05],\n",
|
| 578 |
+
" [6.38133372e-07, 1.01300586e-07, 5.64459742e-06, ...,\n",
|
| 579 |
+
" 4.09234556e-11, 2.53804799e-09, 4.31722100e-07],\n",
|
| 580 |
+
" [1.49096788e-08, 2.04515047e-04, 9.23794141e-06, ...,\n",
|
| 581 |
+
" 7.46306961e-10, 2.92107380e-11, 2.21786500e-08],\n",
|
| 582 |
+
" ...,\n",
|
| 583 |
+
" [2.15093763e-07, 1.06453872e-03, 1.62486140e-05, ...,\n",
|
| 584 |
+
" 1.12102910e-10, 1.47300866e-10, 4.73037538e-08],\n",
|
| 585 |
+
" [4.30136682e-09, 3.09317988e-06, 3.96632568e-05, ...,\n",
|
| 586 |
+
" 5.24226877e-11, 2.39579450e-08, 3.86403221e-09],\n",
|
| 587 |
+
" [9.77773685e-04, 1.29533228e-05, 2.78623725e-06, ...,\n",
|
| 588 |
+
" 2.73364300e-11, 3.96418649e-08, 4.04014427e-05]],\n",
|
| 589 |
+
" shape=(238, 26), dtype=float32)]"
|
| 590 |
+
]
|
| 591 |
+
},
|
| 592 |
+
"execution_count": 9,
|
| 593 |
+
"metadata": {},
|
| 594 |
+
"output_type": "execute_result"
|
| 595 |
+
}
|
| 596 |
+
],
|
| 597 |
+
"source": [
|
| 598 |
+
"ablang(only_both_chains_seqs, mode='probability')"
|
| 599 |
+
]
|
| 600 |
+
},
|
| 601 |
+
{
|
| 602 |
+
"cell_type": "markdown",
|
| 603 |
+
"id": "8f0a71ec-e916-4330-90d0-13a4b1121a89",
|
| 604 |
+
"metadata": {},
|
| 605 |
+
"source": [
|
| 606 |
+
"## **Pseudo log likelihood and Confidence scores**\n",
|
| 607 |
+
"\n",
|
| 608 |
+
"The pseudo log likelihood and confidence represents two methods for calculating the uncertainty for the input sequence.\n",
|
| 609 |
+
"\n",
|
| 610 |
+
"- pseudo_log_likelihood: For each position, the pseudo log likelihood is calculated when predicting the masked residue. The final score is an average across the whole input. This is similar to the approach taken in the ESM-2 paper for calculating pseudo perplexity [(Lin et al., 2023)](https://doi.org/10.1126/science.ade2574).\n",
|
| 611 |
+
"\n",
|
| 612 |
+
"- confidence: For each position, the log likelihood is calculated without masking the residue. The final score is an average across the whole input. \n",
|
| 613 |
+
"\n",
|
| 614 |
+
"**NB:** The **confidence is fast** to compute, requiring only a single forward pass per input. **Pseudo log likelihood is slow** to calculate, requiring L forward passes per input, where L is the length of the input.\n",
|
| 615 |
+
"\n",
|
| 616 |
+
"**NB:** It is recommended to use **pseudo log likelihood for final results** and **confidence for exploratory work**."
|
| 617 |
+
]
|
| 618 |
+
},
|
| 619 |
+
{
|
| 620 |
+
"cell_type": "code",
|
| 621 |
+
"execution_count": 10,
|
| 622 |
+
"id": "83f3064b-48a7-42fb-ba82-ec153ea946da",
|
| 623 |
+
"metadata": {},
|
| 624 |
+
"outputs": [
|
| 625 |
+
{
|
| 626 |
+
"data": {
|
| 627 |
+
"text/plain": [
|
| 628 |
+
"array([1.96673731, 2.04801253, 2.09881898, 1.82533665, 1.97255249])"
|
| 629 |
+
]
|
| 630 |
+
},
|
| 631 |
+
"execution_count": 10,
|
| 632 |
+
"metadata": {},
|
| 633 |
+
"output_type": "execute_result"
|
| 634 |
+
}
|
| 635 |
+
],
|
| 636 |
+
"source": [
|
| 637 |
+
"results = ablang(all_seqs, mode='pseudo_log_likelihood')\n",
|
| 638 |
+
"np.exp(-results) # convert to pseudo perplexity"
|
| 639 |
+
]
|
| 640 |
+
},
|
| 641 |
+
{
|
| 642 |
+
"cell_type": "code",
|
| 643 |
+
"execution_count": 11,
|
| 644 |
+
"id": "42cc8b34-5ae9-4857-93fe-a438a0f2a868",
|
| 645 |
+
"metadata": {},
|
| 646 |
+
"outputs": [
|
| 647 |
+
{
|
| 648 |
+
"data": {
|
| 649 |
+
"text/plain": [
|
| 650 |
+
"array([1.2636038, 1.126463 , 1.3123759, 1.2140924, 1.1805094],\n",
|
| 651 |
+
" dtype=float32)"
|
| 652 |
+
]
|
| 653 |
+
},
|
| 654 |
+
"execution_count": 11,
|
| 655 |
+
"metadata": {},
|
| 656 |
+
"output_type": "execute_result"
|
| 657 |
+
}
|
| 658 |
+
],
|
| 659 |
+
"source": [
|
| 660 |
+
"results = ablang(all_seqs, mode='confidence')\n",
|
| 661 |
+
"np.exp(-results)"
|
| 662 |
+
]
|
| 663 |
+
},
|
| 664 |
+
{
|
| 665 |
+
"cell_type": "markdown",
|
| 666 |
+
"id": "e0b63e48-b2a1-4a8e-8ecb-449748a2cb25",
|
| 667 |
+
"metadata": {},
|
| 668 |
+
"source": [
|
| 669 |
+
"## **restore**\n",
|
| 670 |
+
"\n",
|
| 671 |
+
"This mode can be used to restore masked residues, and fragmented regions with \"align=True\". "
|
| 672 |
+
]
|
| 673 |
+
},
|
| 674 |
+
{
|
| 675 |
+
"cell_type": "code",
|
| 676 |
+
"execution_count": 12,
|
| 677 |
+
"id": "2d5b725c-4eac-4a4b-9331-357c3ac140f7",
|
| 678 |
+
"metadata": {},
|
| 679 |
+
"outputs": [
|
| 680 |
+
{
|
| 681 |
+
"data": {
|
| 682 |
+
"text/plain": [
|
| 683 |
+
"array(['<EVQLLESGGEVKKPGASVKVSCRASGYTFRNYGLTWVRQAPGQGLEWMGWISAYNGNTNYAQKFQGRVTLTTDTSTSTAYMELRSLRSDDTAVYFCARDVPGHGAAFMDVWGTGTTVTVSS>|<DIQLTQSPLSLPVTLGQPASISCRSSQSLEASDTNIYLSWFQQRPGQSPRRLIYKISNRDSGVPDRFSGSGSGTHFTLRISRVEADDVAVYYCMQGTHWPPAFGQGTKVDIK>',\n",
|
| 684 |
+
" '<EVQLLESGGEVKKPGASVKVSCRASGYTFRNYGLTWVRQAPGQGLEWMGWISAYNGNTNYAQKFQGRVTLTTDTSTSTAYMELRSLRSDDTAVYFCARDVPGHGAAFMDVWGTGTT>|<PVTLGQPASISCRSSQSLEASDTNIYLSWFQQRPGQSPRRLIYKISNRDSGVPDRFSGSGSGTHFTLRISRVEADDVAVYYCMQGTHWPPAFGQGTKVDIK>',\n",
|
| 685 |
+
" '<EVQLVQSGGEVKKPGASVKVSCRASGYTFRNYGLTWVRQAPGQGLEWMGWISAYNGNTNYAQKFQGRVTLTTDTSTSTAYMELRSLRSDDTAVYFCARDPPGHGAAFMDVWGTGTTVTVSS>|<DIQLTQSPLSLPVTLGQPASISCRSSQSLEASDTNIYLSWFQQRPGQSPRRLIYKISNRDSGVPDRFSGSGSGTHFTLRISRVEADDVAVYYCMQGTHWPPAFGQGTKVDIK>'],\n",
|
| 686 |
+
" dtype='<U238')"
|
| 687 |
+
]
|
| 688 |
+
},
|
| 689 |
+
"execution_count": 12,
|
| 690 |
+
"metadata": {},
|
| 691 |
+
"output_type": "execute_result"
|
| 692 |
+
}
|
| 693 |
+
],
|
| 694 |
+
"source": [
|
| 695 |
+
"restored = ablang(only_both_chains_seqs, mode='restore')\n",
|
| 696 |
+
"restored"
|
| 697 |
+
]
|
| 698 |
+
},
|
| 699 |
+
{
|
| 700 |
+
"cell_type": "code",
|
| 701 |
+
"execution_count": 13,
|
| 702 |
+
"id": "0e9615f7-c490-4947-96f4-7617266c686e",
|
| 703 |
+
"metadata": {},
|
| 704 |
+
"outputs": [
|
| 705 |
+
{
|
| 706 |
+
"data": {
|
| 707 |
+
"text/plain": [
|
| 708 |
+
"array(['<EVQLLESGGEVKKPGASVKVSCRASGYTFRNYGLTWVRQAPGQGLEWMGWISAYNGNTNYAQKFQGRVTLTTDTSTSTAYMELRSLRSDDTAVYFCARDVPGHGAAFMDVWGTGTTVTVSS>|<DIQLTQSPLSLPVTLGQPASISCRSSQSLEASDTNIYLSWFQQRPGQSPRRLIYKISNRDSGVPDRFSGSGSGTHFTLRISRVEADDVAVYYCMQGTHWPPAFGQGTKVDIK>',\n",
|
| 709 |
+
" '<EVQLLESGGEVKKPGASVKVSCRASGYTFRNYGLTWVRQAPGQGLEWMGWISAYNGNTNYAQKFQGRVTLTTDTSTSTAYMELRSLRSDDTAVYFCARDVPGHGAAFMDVWGTGTTVTVSS>|<DVVMTQSPLSLPVTLGQPASISCRSSQSLEASDTNIYLSWFQQRPGQSPRRLIYKISNRDSGVPDRFSGSGSGTHFTLRISRVEADDVAVYYCMQGTHWPPAFGQGTKVDIK>',\n",
|
| 710 |
+
" '<QVQLVQSGGEVKKPGASVKVSCRASGYTFRNYGLTWVRQAPGQGLEWMGWISAYNGNTNYAQKFQGRVTLTTDTSTSTAYMELRSLRSDDTAVYFCARDPPGHGAAFMDVWGTGTTVTVSS>|<DIQLTQSPLSLPVTLGQPASISCRSSQSLEASDTNIYLSWFQQRPGQSPRRLIYKISNRDSGVPDRFSGSGSGTHFTLRISRVEADDVAVYYCMQGTHWPPAFGQGTKVDIK>'],\n",
|
| 711 |
+
" dtype='<U238')"
|
| 712 |
+
]
|
| 713 |
+
},
|
| 714 |
+
"execution_count": 13,
|
| 715 |
+
"metadata": {},
|
| 716 |
+
"output_type": "execute_result"
|
| 717 |
+
}
|
| 718 |
+
],
|
| 719 |
+
"source": [
|
| 720 |
+
"restored = ablang(only_both_chains_seqs, mode='restore', align = True)\n",
|
| 721 |
+
"restored"
|
| 722 |
+
]
|
| 723 |
+
},
|
| 724 |
+
{
|
| 725 |
+
"cell_type": "code",
|
| 726 |
+
"execution_count": null,
|
| 727 |
+
"id": "d80020ce",
|
| 728 |
+
"metadata": {},
|
| 729 |
+
"outputs": [],
|
| 730 |
+
"source": []
|
| 731 |
+
}
|
| 732 |
+
],
|
| 733 |
+
"metadata": {
|
| 734 |
+
"kernelspec": {
|
| 735 |
+
"display_name": "lib_transformer",
|
| 736 |
+
"language": "python",
|
| 737 |
+
"name": "python3"
|
| 738 |
+
},
|
| 739 |
+
"language_info": {
|
| 740 |
+
"codemirror_mode": {
|
| 741 |
+
"name": "ipython",
|
| 742 |
+
"version": 3
|
| 743 |
+
},
|
| 744 |
+
"file_extension": ".py",
|
| 745 |
+
"mimetype": "text/x-python",
|
| 746 |
+
"name": "python",
|
| 747 |
+
"nbconvert_exporter": "python",
|
| 748 |
+
"pygments_lexer": "ipython3",
|
| 749 |
+
"version": "3.10.18"
|
| 750 |
+
}
|
| 751 |
+
},
|
| 752 |
+
"nbformat": 4,
|
| 753 |
+
"nbformat_minor": 5
|
| 754 |
+
}
|
tokenizer_ablang2paired.py
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
import os
|
| 3 |
+
from transformers import PreTrainedTokenizer
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class AbLang2PairedTokenizer(PreTrainedTokenizer):
|
| 7 |
+
vocab_files_names = {"vocab_file": "vocab.json"}
|
| 8 |
+
model_input_names = ["input_ids"]
|
| 9 |
+
|
| 10 |
+
def __init__(self, vocab_file=None, **kwargs):
|
| 11 |
+
if vocab_file is None:
|
| 12 |
+
# Try to find vocab file in the current directory
|
| 13 |
+
vocab_file = "vocab.json"
|
| 14 |
+
|
| 15 |
+
self.vocab_file = vocab_file
|
| 16 |
+
with open(vocab_file, "r", encoding="utf-8") as f:
|
| 17 |
+
self.vocab = json.load(f)
|
| 18 |
+
|
| 19 |
+
# Set required token attributes (all as strings, standard for HF)
|
| 20 |
+
kwargs.setdefault("pad_token", "-")
|
| 21 |
+
kwargs.setdefault("mask_token", "*")
|
| 22 |
+
kwargs.setdefault("unk_token", "X")
|
| 23 |
+
|
| 24 |
+
super().__init__(**kwargs)
|
| 25 |
+
|
| 26 |
+
@property
|
| 27 |
+
def pad_token_id(self):
|
| 28 |
+
return self.vocab[self.pad_token]
|
| 29 |
+
|
| 30 |
+
@property
|
| 31 |
+
def mask_token_id(self):
|
| 32 |
+
return self.vocab[self.mask_token]
|
| 33 |
+
|
| 34 |
+
def _tokenize(self, text):
|
| 35 |
+
return list(text)
|
| 36 |
+
|
| 37 |
+
def tokenize(self, text, text_pair=None, **kwargs):
|
| 38 |
+
"""Tokenize text or text pair."""
|
| 39 |
+
if text_pair is not None:
|
| 40 |
+
# For paired sequences, combine them with a separator
|
| 41 |
+
combined_text = text + "|" + text_pair
|
| 42 |
+
return self._tokenize(combined_text)
|
| 43 |
+
else:
|
| 44 |
+
return self._tokenize(text)
|
| 45 |
+
|
| 46 |
+
def _convert_token_to_id(self, token):
|
| 47 |
+
return self.vocab.get(token, self.vocab[self.unk_token])
|
| 48 |
+
|
| 49 |
+
def _convert_id_to_token(self, index):
|
| 50 |
+
inv_vocab = {v: k for k, v in self.vocab.items()}
|
| 51 |
+
return inv_vocab.get(index, self.unk_token)
|
| 52 |
+
|
| 53 |
+
def get_vocab(self):
|
| 54 |
+
return self.vocab
|
| 55 |
+
|
| 56 |
+
def save_vocabulary(self, save_directory, filename_prefix=None):
|
| 57 |
+
os.makedirs(save_directory, exist_ok=True)
|
| 58 |
+
path = os.path.join(save_directory, (filename_prefix or "") + "vocab.json")
|
| 59 |
+
with open(path, "w", encoding="utf-8") as f:
|
| 60 |
+
json.dump(self.vocab, f)
|
| 61 |
+
return (path,)
|
| 62 |
+
|
| 63 |
+
@classmethod
|
| 64 |
+
def from_pretrained(cls, pretrained_model_name_or_path, **kwargs):
|
| 65 |
+
try:
|
| 66 |
+
from transformers.utils import cached_file
|
| 67 |
+
vocab_file = cached_file(
|
| 68 |
+
pretrained_model_name_or_path,
|
| 69 |
+
"vocab.json",
|
| 70 |
+
cache_dir=kwargs.get("cache_dir"),
|
| 71 |
+
force_download=kwargs.get("force_download", False),
|
| 72 |
+
resume_download=kwargs.get("resume_download", False),
|
| 73 |
+
proxies=kwargs.get("proxies"),
|
| 74 |
+
token=kwargs.get("token"),
|
| 75 |
+
revision=kwargs.get("revision"),
|
| 76 |
+
local_files_only=kwargs.get("local_files_only", False),
|
| 77 |
+
)
|
| 78 |
+
|
| 79 |
+
if vocab_file is None or not os.path.exists(vocab_file):
|
| 80 |
+
raise ValueError(f"Vocabulary file vocab.json not found in {pretrained_model_name_or_path}")
|
| 81 |
+
|
| 82 |
+
return cls(vocab_file=vocab_file, **kwargs)
|
| 83 |
+
except Exception as e:
|
| 84 |
+
# Fallback for local paths
|
| 85 |
+
vocab_file = os.path.join(pretrained_model_name_or_path, "vocab.json")
|
| 86 |
+
if not os.path.exists(vocab_file):
|
| 87 |
+
raise ValueError(f"Vocabulary file {vocab_file} not found")
|
| 88 |
+
return cls(vocab_file=vocab_file, **kwargs)
|
| 89 |
+
|
| 90 |
+
def save_pretrained(self, save_directory, filename_prefix=None):
|
| 91 |
+
os.makedirs(save_directory, exist_ok=True)
|
| 92 |
+
vocab_files = self.save_vocabulary(save_directory, filename_prefix)
|
| 93 |
+
|
| 94 |
+
tokenizer_config = {
|
| 95 |
+
"tokenizer_class": f"{self.__class__.__module__}.{self.__class__.__name__}"
|
| 96 |
+
}
|
| 97 |
+
with open(os.path.join(save_directory, "tokenizer_config.json"), "w") as f:
|
| 98 |
+
json.dump(tokenizer_config, f, indent=2)
|
| 99 |
+
|
| 100 |
+
return vocab_files
|
| 101 |
+
|
| 102 |
+
def __call__(self, sequences, padding=False, return_tensors=None, **kwargs):
|
| 103 |
+
# Handle different input formats
|
| 104 |
+
if isinstance(sequences, str):
|
| 105 |
+
# Single string: "VH|VL"
|
| 106 |
+
sequences = [sequences]
|
| 107 |
+
elif isinstance(sequences, list) and len(sequences) > 0:
|
| 108 |
+
if isinstance(sequences[0], list):
|
| 109 |
+
# List of lists: [['VH', 'VL'], ['VH2', 'VL2']]
|
| 110 |
+
sequences = [f"{pair[0]}|{pair[1]}" for pair in sequences]
|
| 111 |
+
# List of strings: ["VH|VL", "VH2|VL2"] - already correct format
|
| 112 |
+
|
| 113 |
+
# Tokenize each sequence
|
| 114 |
+
input_ids = [[self._convert_token_to_id(tok) for tok in self._tokenize(seq)] for seq in sequences]
|
| 115 |
+
# Padding
|
| 116 |
+
if padding:
|
| 117 |
+
maxlen = max(len(ids) for ids in input_ids)
|
| 118 |
+
input_ids = [ids + [self.pad_token_id] * (maxlen - len(ids)) for ids in input_ids]
|
| 119 |
+
# Return tensors if requested
|
| 120 |
+
if return_tensors == 'pt':
|
| 121 |
+
import torch
|
| 122 |
+
input_ids = torch.tensor(input_ids)
|
| 123 |
+
return {'input_ids': input_ids}
|
| 124 |
+
|
vocab.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"<": 0,
|
| 3 |
+
"M": 1,
|
| 4 |
+
"R": 2,
|
| 5 |
+
"H": 3,
|
| 6 |
+
"K": 4,
|
| 7 |
+
"D": 5,
|
| 8 |
+
"E": 6,
|
| 9 |
+
"S": 7,
|
| 10 |
+
"T": 8,
|
| 11 |
+
"N": 9,
|
| 12 |
+
"Q": 10,
|
| 13 |
+
"C": 11,
|
| 14 |
+
"G": 12,
|
| 15 |
+
"P": 13,
|
| 16 |
+
"A": 14,
|
| 17 |
+
"V": 15,
|
| 18 |
+
"I": 16,
|
| 19 |
+
"F": 17,
|
| 20 |
+
"Y": 18,
|
| 21 |
+
"W": 19,
|
| 22 |
+
"L": 20,
|
| 23 |
+
"-": 21,
|
| 24 |
+
">": 22,
|
| 25 |
+
"*": 23,
|
| 26 |
+
"X": 24,
|
| 27 |
+
"|": 25
|
| 28 |
+
}
|
vocab.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
ablang_vocab = {
|
| 2 |
+
"<": 0,
|
| 3 |
+
"M": 1,
|
| 4 |
+
"R": 2,
|
| 5 |
+
"H": 3,
|
| 6 |
+
"K": 4,
|
| 7 |
+
"D": 5,
|
| 8 |
+
"E": 6,
|
| 9 |
+
"S": 7,
|
| 10 |
+
"T": 8,
|
| 11 |
+
"N": 9,
|
| 12 |
+
"Q": 10,
|
| 13 |
+
"C": 11,
|
| 14 |
+
"G": 12,
|
| 15 |
+
"P": 13,
|
| 16 |
+
"A": 14,
|
| 17 |
+
"V": 15,
|
| 18 |
+
"I": 16,
|
| 19 |
+
"F": 17,
|
| 20 |
+
"Y": 18,
|
| 21 |
+
"W": 19,
|
| 22 |
+
"L": 20,
|
| 23 |
+
"-": 21,
|
| 24 |
+
">": 22,
|
| 25 |
+
"*": 23,
|
| 26 |
+
"X": 24,
|
| 27 |
+
"|": 25
|
| 28 |
+
}
|