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
Files changed (1) hide show
  1. 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='data/name_info.json.gz'):
121
- with gzip.open(name_file, 'rt', encoding='utf-8') as file:
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
- # Load name information from gzipped file
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, threshold: float = 0.0):
 
 
 
 
 
 
 
 
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
- # Load dataset once
217
- # heterodata = load_dataset('HUBioDataLab/ProtHGT-KG', data_files="prothgt-kg.json.gz")
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
- # Load model config
255
- with open(model_config_path, 'r') as file:
256
- model_config = yaml.safe_load(file)
257
-
258
- # Initialize model with configuration
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() # Clear CUDA cache if using GPU
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