Download scripts/inference.py from OneScience-Group/MVGNN-PPIS: direct link, hf CLI and curl.
- Browser
- Download file 5.34 kB
-
https://huggingface.co/OneScience-Group/MVGNN-PPIS/resolve/main/scripts/inference.py
- Command line
-
hf download hf://OneScience-Group/MVGNN-PPIS/scripts/inference.py
-
curl -L -o inference.py https://huggingface.co/OneScience-Group/MVGNN-PPIS/resolve/main/scripts/inference.py
5.34 kB
| #!/usr/bin/env python3 | |
| """Run MVGNN-PPIS five-fold ensemble inference.""" | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| import sys | |
| from pathlib import Path | |
| import pandas as pd | |
| import torch | |
| PROJECT_ROOT = Path(__file__).resolve().parents[1] | |
| if str(PROJECT_ROOT) not in sys.path: | |
| sys.path.insert(0, str(PROJECT_ROOT)) | |
| from model import MVGNN, Seed_everything, model_test # noqa: E402 | |
| def project_path(value: str | Path) -> Path: | |
| """Resolve package-relative paths while preserving explicit absolute paths.""" | |
| path = Path(value).expanduser() | |
| return path if path.is_absolute() else PROJECT_ROOT / path | |
| def load_config(path: Path) -> dict: | |
| with path.open("r", encoding="utf-8") as handle: | |
| return json.load(handle) | |
| def parse_args() -> argparse.Namespace: | |
| parser = argparse.ArgumentParser( | |
| description="Predict protein-protein interaction sites with MVGNN-PPIS." | |
| ) | |
| parser.add_argument( | |
| "--config", | |
| default="conf/config.json", | |
| help="Package-relative JSON configuration file.", | |
| ) | |
| parser.add_argument("--dataset", help="Override the evaluation CSV path.") | |
| parser.add_argument("--feature-path", help="Override the precomputed feature directory.") | |
| parser.add_argument("--weight-path", help="Override the checkpoint directory.") | |
| parser.add_argument("--output-path", help="Override the prediction output directory.") | |
| parser.add_argument("--num-workers", type=int, help="DataLoader worker count.") | |
| parser.add_argument("--seed", type=int, help="Random seed.") | |
| parser.add_argument( | |
| "--device", | |
| help="PyTorch device, for example 'cuda', 'cuda:0', or 'cpu'.", | |
| ) | |
| return parser.parse_args() | |
| def required_feature_files(feature_dir: Path, protein_ids: list[str]) -> list[Path]: | |
| suffixes = ("X", "node_feature", "mask", "label", "adj") | |
| return [ | |
| feature_dir / f"{protein_id}_{suffix}.tensor" | |
| for protein_id in protein_ids | |
| for suffix in suffixes | |
| ] | |
| def main() -> None: | |
| args = parse_args() | |
| config = load_config(project_path(args.config)) | |
| paths = config["paths"] | |
| dataset_path = project_path(args.dataset or paths["dataset"]) | |
| feature_dir = project_path(args.feature_path or paths["feature_dir"]) | |
| weight_dir = project_path(args.weight_path or paths["weight_dir"]) | |
| output_dir = project_path(args.output_path or paths["output_dir"]) | |
| runtime = config["runtime"] | |
| seed = args.seed if args.seed is not None else runtime["seed"] | |
| num_workers = ( | |
| args.num_workers if args.num_workers is not None else runtime["num_workers"] | |
| ) | |
| requested_device = args.device or runtime.get("device", "auto") | |
| if requested_device == "auto": | |
| requested_device = "cuda" if torch.cuda.is_available() else "cpu" | |
| device = torch.device(requested_device) | |
| if device.type == "cuda" and not torch.cuda.is_available(): | |
| raise RuntimeError("A CUDA/HIP device was requested but torch.cuda.is_available() is False.") | |
| test_df = pd.read_csv( | |
| dataset_path, | |
| dtype={"ID": str, "sequence": str, "label": str}, | |
| keep_default_na=False, | |
| ) | |
| required_columns = {"ID", "sequence", "label"} | |
| missing_columns = required_columns.difference(test_df.columns) | |
| if missing_columns: | |
| raise ValueError( | |
| f"Dataset {dataset_path} is missing columns: {sorted(missing_columns)}" | |
| ) | |
| test_df["ID"] = test_df["ID"].str.strip() | |
| protein_ids = test_df["ID"].str.strip().drop_duplicates().tolist() | |
| missing_features = [ | |
| path for path in required_feature_files(feature_dir, protein_ids) if not path.is_file() | |
| ] | |
| if missing_features: | |
| preview = "\n".join(f" - {path}" for path in missing_features[:10]) | |
| raise FileNotFoundError( | |
| f"Missing {len(missing_features)} precomputed feature files.\n{preview}" | |
| ) | |
| folds = config["model"]["folds"] | |
| missing_weights = [ | |
| weight_dir / f"fold{fold}.ckpt" | |
| for fold in range(folds) | |
| if not (weight_dir / f"fold{fold}.ckpt").is_file() | |
| ] | |
| if missing_weights: | |
| formatted = "\n".join(f" - {path}" for path in missing_weights) | |
| raise FileNotFoundError(f"Missing model checkpoints:\n{formatted}") | |
| all_protein_data = {} | |
| for protein_id in protein_ids: | |
| all_protein_data[protein_id] = tuple( | |
| torch.load( | |
| feature_dir / f"{protein_id}_{suffix}.tensor", | |
| map_location="cpu", | |
| weights_only=True, | |
| ) | |
| for suffix in ("X", "node_feature", "mask", "label", "adj") | |
| ) | |
| Seed_everything(seed) | |
| model_config = dict(config["model"]) | |
| model_config.update( | |
| { | |
| "seed": seed, | |
| "id_name": "ID", | |
| "remark": "PRO binding site prediction", | |
| } | |
| ) | |
| print(f"Project root: {PROJECT_ROOT}") | |
| print(f"Dataset: {dataset_path}") | |
| print(f"Proteins: {len(test_df)}") | |
| print(f"Device: {device}") | |
| print(f"Checkpoints: {folds}") | |
| model_test( | |
| test_df, | |
| all_protein_data, | |
| MVGNN, | |
| model_config, | |
| weight_dir=weight_dir, | |
| output_dir=output_dir, | |
| logit=True, | |
| device=device, | |
| num_workers=num_workers, | |
| ) | |
| if __name__ == "__main__": | |
| main() | |