Spaces:
Paused
Paused
Erva Ulusoy commited on
Commit ·
a9a53d0
1
Parent(s): ade1370
refactor the app to cache kg and models, download kgs from hf hub and add prediction progress
Browse files- run_prothgt_app.py +96 -72
run_prothgt_app.py
CHANGED
|
@@ -60,6 +60,68 @@ from huggingface_hub import hf_hub_download
|
|
| 60 |
import copy
|
| 61 |
import json
|
| 62 |
import gzip
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
|
| 64 |
class ProtHGT(torch.nn.Module):
|
| 65 |
def __init__(self, data,hidden_channels, num_heads, num_layers, mlp_hidden_layers, mlp_dropout):
|
|
@@ -117,10 +179,8 @@ def _build_edge_label_index(heterodata, protein_ids, go_category):
|
|
| 117 |
col = term_indices.repeat(len(protein_indices))
|
| 118 |
return torch.stack([row, col], dim=0)
|
| 119 |
|
| 120 |
-
def get_available_proteins(name_file=
|
| 121 |
-
|
| 122 |
-
name_info = json.load(file)
|
| 123 |
-
return list(name_info['Protein'].keys())
|
| 124 |
|
| 125 |
def _generate_predictions(heterodata, model, edge_label_index, target_type):
|
| 126 |
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
|
|
@@ -143,9 +203,7 @@ def _create_prediction_df(predictions, heterodata, protein_ids, go_category, thr
|
|
| 143 |
'GO_term_C': 'Cellular Component'
|
| 144 |
}
|
| 145 |
|
| 146 |
-
|
| 147 |
-
with gzip.open('data/name_info.json.gz', 'rt', encoding='utf-8') as file:
|
| 148 |
-
name_info = json.load(file)
|
| 149 |
|
| 150 |
id_mapping = heterodata[go_category]['id_mapping'] # dict: GO_id -> index
|
| 151 |
n_go_terms = len(id_mapping)
|
|
@@ -206,86 +264,52 @@ def _create_prediction_df(predictions, heterodata, protein_ids, go_category, thr
|
|
| 206 |
|
| 207 |
return prediction_df
|
| 208 |
|
| 209 |
-
def generate_prediction_df(protein_ids, model_paths, model_config_paths, go_category
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 210 |
all_predictions = []
|
| 211 |
-
|
| 212 |
-
# Convert single protein ID to list if necessary
|
| 213 |
if isinstance(protein_ids, str):
|
| 214 |
protein_ids = [protein_ids]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 215 |
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
print('Loading data...')
|
| 219 |
-
# file_id = "18u1o2sm8YjMo9joFw4Ilwvg0-rUU0PXK"
|
| 220 |
-
output = "data/prothgt-kg.pt"
|
| 221 |
-
|
| 222 |
-
if not os.path.exists(output):
|
| 223 |
-
try:
|
| 224 |
-
# url = f"https://drive.google.com/uc?id={file_id}"
|
| 225 |
-
# print(f"Downloading file from {url}...")
|
| 226 |
-
# gdown.download(url, output, quiet=False)
|
| 227 |
-
# print(f"File downloaded to {output}")
|
| 228 |
-
cached_path = hf_hub_download(
|
| 229 |
-
repo_id="HUBioDataLab/ProtHGT",
|
| 230 |
-
filename="knowledge_graphs/prothgt-kg.pt",
|
| 231 |
-
repo_type="dataset",
|
| 232 |
-
)
|
| 233 |
-
|
| 234 |
-
output = "data/prothgt-kg.pt"
|
| 235 |
-
os.makedirs("data", exist_ok=True)
|
| 236 |
-
shutil.copy(cached_path, output)
|
| 237 |
-
print(f"File downloaded to {output}")
|
| 238 |
-
except Exception as e:
|
| 239 |
-
print(f"Error downloading file: {e}")
|
| 240 |
-
raise
|
| 241 |
-
else:
|
| 242 |
-
print(f"File already exists at {output}")
|
| 243 |
-
|
| 244 |
-
heterodata = torch.load(output)
|
| 245 |
-
|
| 246 |
-
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
|
| 247 |
|
| 248 |
-
for go_cat, model_config_path, model_path in zip(go_category, model_config_paths, model_paths):
|
| 249 |
print(f'Generating predictions for {go_cat}...')
|
| 250 |
-
|
|
|
|
|
|
|
| 251 |
# Build candidate edges for inference (do NOT modify graph edges)
|
| 252 |
edge_label_index = _build_edge_label_index(heterodata, protein_ids, go_cat)
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
model = ProtHGT(
|
| 260 |
-
heterodata,
|
| 261 |
-
hidden_channels=model_config['hidden_channels'][0],
|
| 262 |
-
num_heads=model_config['num_heads'],
|
| 263 |
-
num_layers=model_config['num_layers'],
|
| 264 |
-
mlp_hidden_layers=model_config['hidden_channels'][1],
|
| 265 |
-
mlp_dropout=model_config['mlp_dropout']
|
| 266 |
-
)
|
| 267 |
-
|
| 268 |
-
# Load model weights
|
| 269 |
-
model.load_state_dict(torch.load(model_path, map_location=device))
|
| 270 |
-
print(f'Loaded model weights from {model_path}')
|
| 271 |
-
|
| 272 |
-
# Generate predictions
|
| 273 |
predictions = _generate_predictions(heterodata, model, edge_label_index, go_cat)
|
| 274 |
prediction_df = _create_prediction_df(predictions, heterodata, protein_ids, go_cat, threshold=threshold)
|
| 275 |
all_predictions.append(prediction_df)
|
| 276 |
-
|
| 277 |
-
# Clean up memory
|
| 278 |
del edge_label_index
|
| 279 |
-
del model
|
| 280 |
del predictions
|
| 281 |
-
torch.cuda.empty_cache()
|
| 282 |
-
|
| 283 |
|
| 284 |
-
# Combine all predictions
|
| 285 |
final_df = pd.concat(all_predictions, ignore_index=True)
|
| 286 |
-
|
| 287 |
-
# Clean up
|
| 288 |
del all_predictions
|
| 289 |
torch.cuda.empty_cache()
|
| 290 |
-
|
| 291 |
return heterodata, final_df
|
|
|
|
| 60 |
import copy
|
| 61 |
import json
|
| 62 |
import gzip
|
| 63 |
+
from functools import lru_cache
|
| 64 |
+
|
| 65 |
+
MODELS_DIR = os.path.join("data", "models")
|
| 66 |
+
KG_PATH = os.path.join("data", "prothgt-kg.pt")
|
| 67 |
+
NAME_INFO_PATH = os.path.join("data", "name_info.json.gz")
|
| 68 |
+
|
| 69 |
+
MODEL_FILES = {
|
| 70 |
+
'GO_term_F': ("prothgt-config-molecular-function.yaml", "prothgt-model-molecular-function.pt"),
|
| 71 |
+
'GO_term_P': ("prothgt-config-biological-process.yaml", "prothgt-model-biological-process.pt"),
|
| 72 |
+
'GO_term_C': ("prothgt-config-cellular-component.yaml", "prothgt-model-cellular-component.pt"),
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
@lru_cache(maxsize=1)
|
| 77 |
+
def load_name_info(name_file=NAME_INFO_PATH):
|
| 78 |
+
"""Load node display names once per process (the file is ~24 MB uncompressed)."""
|
| 79 |
+
with gzip.open(name_file, 'rt', encoding='utf-8') as file:
|
| 80 |
+
return json.load(file)
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
def load_kg(output=KG_PATH):
|
| 84 |
+
"""Download (if needed) and load the ProtHGT knowledge graph. Callers should cache the result."""
|
| 85 |
+
if not os.path.exists(output):
|
| 86 |
+
try:
|
| 87 |
+
cached_path = hf_hub_download(
|
| 88 |
+
repo_id="HUBioDataLab/ProtHGT",
|
| 89 |
+
filename="knowledge_graphs/prothgt-kg.pt",
|
| 90 |
+
repo_type="dataset",
|
| 91 |
+
)
|
| 92 |
+
os.makedirs(os.path.dirname(output), exist_ok=True)
|
| 93 |
+
shutil.copy(cached_path, output)
|
| 94 |
+
print(f"File downloaded to {output}")
|
| 95 |
+
except Exception as e:
|
| 96 |
+
print(f"Error downloading file: {e}")
|
| 97 |
+
raise
|
| 98 |
+
print('Loading knowledge graph...')
|
| 99 |
+
return torch.load(output)
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
def load_model(go_cat, heterodata, model_config_path=None, model_path=None):
|
| 103 |
+
"""Build a ProtHGT model for one GO category and load its weights. Callers should cache the result."""
|
| 104 |
+
if model_config_path is None or model_path is None:
|
| 105 |
+
cfg_name, weights_name = MODEL_FILES[go_cat]
|
| 106 |
+
model_config_path = model_config_path or os.path.join(MODELS_DIR, cfg_name)
|
| 107 |
+
model_path = model_path or os.path.join(MODELS_DIR, weights_name)
|
| 108 |
+
|
| 109 |
+
with open(model_config_path, 'r') as file:
|
| 110 |
+
model_config = yaml.safe_load(file)
|
| 111 |
+
|
| 112 |
+
model = ProtHGT(
|
| 113 |
+
heterodata,
|
| 114 |
+
hidden_channels=model_config['hidden_channels'][0],
|
| 115 |
+
num_heads=model_config['num_heads'],
|
| 116 |
+
num_layers=model_config['num_layers'],
|
| 117 |
+
mlp_hidden_layers=model_config['hidden_channels'][1],
|
| 118 |
+
mlp_dropout=model_config['mlp_dropout']
|
| 119 |
+
)
|
| 120 |
+
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
|
| 121 |
+
model.load_state_dict(torch.load(model_path, map_location=device))
|
| 122 |
+
model.eval()
|
| 123 |
+
print(f'Loaded model weights from {model_path}')
|
| 124 |
+
return model
|
| 125 |
|
| 126 |
class ProtHGT(torch.nn.Module):
|
| 127 |
def __init__(self, data,hidden_channels, num_heads, num_layers, mlp_hidden_layers, mlp_dropout):
|
|
|
|
| 179 |
col = term_indices.repeat(len(protein_indices))
|
| 180 |
return torch.stack([row, col], dim=0)
|
| 181 |
|
| 182 |
+
def get_available_proteins(name_file=NAME_INFO_PATH):
|
| 183 |
+
return list(load_name_info(name_file)['Protein'].keys())
|
|
|
|
|
|
|
| 184 |
|
| 185 |
def _generate_predictions(heterodata, model, edge_label_index, target_type):
|
| 186 |
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
|
|
|
|
| 203 |
'GO_term_C': 'Cellular Component'
|
| 204 |
}
|
| 205 |
|
| 206 |
+
name_info = load_name_info()
|
|
|
|
|
|
|
| 207 |
|
| 208 |
id_mapping = heterodata[go_category]['id_mapping'] # dict: GO_id -> index
|
| 209 |
n_go_terms = len(id_mapping)
|
|
|
|
| 264 |
|
| 265 |
return prediction_df
|
| 266 |
|
| 267 |
+
def generate_prediction_df(protein_ids, model_paths=None, model_config_paths=None, go_category=None,
|
| 268 |
+
threshold: float = 0.0, heterodata=None, models=None, progress_callback=None):
|
| 269 |
+
"""
|
| 270 |
+
Generate GO term predictions for the given proteins.
|
| 271 |
+
|
| 272 |
+
heterodata / models can be passed in pre-loaded (e.g. from st.cache_resource) so the
|
| 273 |
+
1.6 GB knowledge graph and model weights are not reloaded on every request.
|
| 274 |
+
progress_callback(i, n, go_cat) is called before each category is scored.
|
| 275 |
+
"""
|
| 276 |
all_predictions = []
|
| 277 |
+
|
|
|
|
| 278 |
if isinstance(protein_ids, str):
|
| 279 |
protein_ids = [protein_ids]
|
| 280 |
+
if go_category is None:
|
| 281 |
+
go_category = list(MODEL_FILES.keys())
|
| 282 |
+
if model_paths is None:
|
| 283 |
+
model_paths = [None] * len(go_category)
|
| 284 |
+
if model_config_paths is None:
|
| 285 |
+
model_config_paths = [None] * len(go_category)
|
| 286 |
|
| 287 |
+
if heterodata is None:
|
| 288 |
+
heterodata = load_kg()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 289 |
|
| 290 |
+
for i, (go_cat, model_config_path, model_path) in enumerate(zip(go_category, model_config_paths, model_paths)):
|
| 291 |
print(f'Generating predictions for {go_cat}...')
|
| 292 |
+
if progress_callback is not None:
|
| 293 |
+
progress_callback(i, len(go_category), go_cat)
|
| 294 |
+
|
| 295 |
# Build candidate edges for inference (do NOT modify graph edges)
|
| 296 |
edge_label_index = _build_edge_label_index(heterodata, protein_ids, go_cat)
|
| 297 |
+
|
| 298 |
+
if models is not None and go_cat in models:
|
| 299 |
+
model = models[go_cat]
|
| 300 |
+
else:
|
| 301 |
+
model = load_model(go_cat, heterodata, model_config_path, model_path)
|
| 302 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 303 |
predictions = _generate_predictions(heterodata, model, edge_label_index, go_cat)
|
| 304 |
prediction_df = _create_prediction_df(predictions, heterodata, protein_ids, go_cat, threshold=threshold)
|
| 305 |
all_predictions.append(prediction_df)
|
| 306 |
+
|
|
|
|
| 307 |
del edge_label_index
|
|
|
|
| 308 |
del predictions
|
| 309 |
+
torch.cuda.empty_cache()
|
|
|
|
| 310 |
|
|
|
|
| 311 |
final_df = pd.concat(all_predictions, ignore_index=True)
|
|
|
|
|
|
|
| 312 |
del all_predictions
|
| 313 |
torch.cuda.empty_cache()
|
| 314 |
+
|
| 315 |
return heterodata, final_df
|