Download evaluate.py from gundo0102/SleepMaMi: direct link, hf CLI and curl.
- Browser
- Download file 21.4 kB
-
https://huggingface.co/gundo0102/SleepMaMi/resolve/main/evaluate.py
- Command line
-
hf download hf://gundo0102/SleepMaMi/evaluate.py
-
curl -L -o evaluate.py https://huggingface.co/gundo0102/SleepMaMi/resolve/main/evaluate.py
21.4 kB
| from collections import defaultdict | |
| import pytorch_lightning as pl | |
| from pytorch_lightning.loggers import WandbLogger | |
| from torchmetrics import ConfusionMatrix, CohenKappa | |
| from torchmetrics.classification import Accuracy, Precision, Recall, F1Score | |
| import torch | |
| import torch.nn as nn | |
| import logging | |
| import sys | |
| import os | |
| from pytorch_lightning.callbacks import ModelCheckpoint | |
| import numpy as np | |
| import data.data_loader as data_loader | |
| import yaml | |
| import models | |
| from models.utils import cox_ph_loss, CIndex, AUROC | |
| from torchmetrics import MetricCollection | |
| import random | |
| import importlib | |
| import argparse | |
| from configs.global_cfg import TARGET_RESAMPLE | |
| from configs.global_cfg import TARGET_RESAMPLE, DATASET_PATH, CLASSIFICATION_TASKS, REGRESSION_TASKS | |
| import pickle | |
| from einops import rearrange | |
| import pandas as pd | |
| torch.manual_seed(42) | |
| torch.cuda.manual_seed(42) | |
| torch.cuda.manual_seed_all(42) | |
| torch.backends.cudnn.deterministic = True | |
| torch.backends.cudnn.benchmark = False | |
| np.random.seed(42) | |
| random.seed(42) | |
| # In H100 GPUs | |
| torch.backends.cuda.matmul.allow_tf32 = False | |
| torch.backends.cudnn.allow_tf32 = False | |
| BASE_DIR = os.path.dirname(os.path.abspath(__file__)) | |
| class EvalDataModule(pl.LightningDataModule): | |
| def __init__(self, | |
| data_root, | |
| batch_size_v, | |
| transforms_v, | |
| num_workers, | |
| proportion_v, | |
| num_classes=5, | |
| splits_set=None, | |
| modalities=["EEG"], | |
| sig_len_target=30, | |
| ft_task='sleep_staging', | |
| full_night=False, | |
| seq_truncate=False | |
| ): | |
| super().__init__() | |
| self.data_root = data_root | |
| self.transforms_v = transforms_v | |
| self.batch_size_v = batch_size_v | |
| self.num_workers = num_workers | |
| self.proportion_v = proportion_v | |
| self.num_classes = num_classes | |
| self.splits_set = splits_set | |
| self.modalities = modalities | |
| self.sig_len_target = sig_len_target | |
| self.ft_task = ft_task | |
| self.full_night = full_night | |
| self.seq_truncate = seq_truncate | |
| def setup(self, stage=None): | |
| self.test_data_list = self.splits_set['test'] | |
| self.test_loader = data_loader.build_loader( | |
| self.data_root, self.test_data_list, | |
| batch_size=self.batch_size_v, shuffle=False, | |
| transforms=self.transforms_v, sampler=None, | |
| num_workers=self.num_workers, proportion=self.proportion_v, | |
| split='test', | |
| num_classes=self.num_classes, modalities=self.modalities, | |
| sig_len_target=self.sig_len_target, | |
| ft_task=self.ft_task, full_night=self.full_night, seq_truncate=self.seq_truncate) | |
| print("Test loader ready") | |
| print("Length of test_loader: ", len(self.test_loader)) | |
| def test_dataloader(self): | |
| return self.test_loader | |
| from finetune import ConvMAE1d_Finetune | |
| class ConvMAE1d_Evaluate(ConvMAE1d_Finetune): | |
| def __init__(self, *args, **kwargs): | |
| super().__init__(*args, **kwargs) | |
| self.save_results = self.cfg.get('save_results', False) | |
| if self.save_results: | |
| self.save_results_dir = "ft_results" | |
| os.makedirs(self.save_results_dir, exist_ok=True) | |
| # self.preds = defaultdict(list) | |
| # self.gt = defaultdict(list) | |
| self.results = defaultdict(dict) # Initialize nested dict for each PID | |
| self.save_emb = self.cfg.get('save_emb', False) | |
| if self.save_emb: | |
| self.save_emb_dir = "ft_results/embeddings" | |
| os.makedirs(self.save_emb_dir, exist_ok=True) | |
| self.embeddings = defaultdict(dict) | |
| self._peak_gpu_mem_bytes = None | |
| def on_test_epoch_start(self): | |
| if torch.cuda.is_available(): | |
| torch.cuda.synchronize() | |
| torch.cuda.reset_peak_memory_stats() | |
| self._peak_gpu_mem_bytes = None | |
| def forward_epoch_level(self, batch, mode='test'): | |
| inputs, labels, _, _, pids = batch | |
| # signals = inputs["EEG"].to(self.device, non_blocking=True) | |
| for key in inputs.keys(): | |
| inputs[key] = inputs[key].to(self.device, non_blocking=True) | |
| pred = self(inputs) | |
| if self.ft_task == 'apnea_seg': | |
| pred = rearrange(pred, 'n l c -> n c l') | |
| loss = self.criterion(pred, labels) | |
| pred_cls = pred.argmax(dim=1) | |
| if self.save_results: | |
| pred_cls = pred_cls.detach().cpu().numpy() | |
| labels = labels.detach().cpu().numpy() | |
| for idx, pid in enumerate(pids): | |
| if pid not in self.results: | |
| self.results[pid] = {'pred': [], 'gt': [],} | |
| self.results[pid]['pred'].append(pred_cls[idx]) | |
| self.results[pid]['gt'].append(labels[idx]) | |
| self.log_metrics(loss, pred_cls, labels, mode=mode) | |
| return loss | |
| def forward_full_night(self, batch, mode='test'): | |
| inputs, labels, mask, demo, pids = batch | |
| if self.ft_task in ['age','ahi']: | |
| labels = demo[self.ft_task].to(self.device, dtype=torch.float32, non_blocking=True) | |
| elif self.ft_task in ['sex']: | |
| labels = demo[self.ft_task].to(self.device, dtype=torch.long, non_blocking=True) - 1 | |
| for key in inputs.keys(): | |
| inputs[key] = inputs[key].to(self.device, non_blocking=True) | |
| if (mask is not None) and (mask.numel() > 0): | |
| mask = mask.to(self.device, non_blocking=True) | |
| if self.save_emb: | |
| self.model.eval() | |
| with torch.no_grad(): | |
| (feats_macro_f, feats_macro_r), feats_micro = self.model.forward_features(inputs, mask=mask) | |
| from models.utils import flip_padded_sequences | |
| feats_macro_r = flip_padded_sequences(feats_macro_r, mask) | |
| feats_macro = torch.cat([feats_macro_f, feats_macro_r], dim=-1) | |
| for idx, pid in enumerate(pids): | |
| self.embeddings[pid] = { | |
| 'macro': feats_macro[idx].detach().cpu().numpy(), | |
| 'micro': feats_micro[idx].detach().cpu().numpy(), | |
| 'mask': mask[idx].detach().cpu().numpy() if mask is not None and mask.numel() > 0 else None, | |
| 'age': demo['age'][idx].detach().cpu().numpy(), | |
| 'sex': demo['sex'][idx].detach().cpu().numpy(), | |
| 'bmi': demo['bmi'][idx].detach().cpu().numpy(), | |
| 'ahi': demo['ahi'][idx].detach().cpu().numpy() | |
| } | |
| return # Skip further processing if only saving embeddings | |
| pred = self(inputs, mask=mask) | |
| if self.ft_task == 'sleep_staging' and (mask is not None) and (mask.numel() > 0): | |
| pred = pred.permute(0,2,1)[mask] | |
| labels = labels[mask] | |
| if self.ft_task in REGRESSION_TASKS: | |
| pred = pred.squeeze() | |
| loss = self.criterion(pred, labels) | |
| if self.ft_task in CLASSIFICATION_TASKS: | |
| pred = pred.argmax(dim=1) | |
| if self.save_results: | |
| self.update_results(pred, labels, pids, demo) | |
| self.log_metrics(loss, pred, labels, mode=mode) | |
| def forward_diagnosis(self, batch, mode='train'): | |
| inputs, is_event, event_times, mask, demo, pids = batch | |
| # signals = inputs["EEG"].to(self.device, non_blocking=True) | |
| for key in inputs.keys(): | |
| inputs[key] = inputs[key].to(self.device, non_blocking=True) | |
| for key in demo.keys(): | |
| demo[key] = demo[key].to(self.device, non_blocking=True) | |
| if (mask is not None) and (mask.numel() > 0): | |
| mask = mask.to(self.device, non_blocking=True) | |
| pred = self(inputs, mask=mask, demo=demo) | |
| loss = self.criterion(pred, event_times, is_event) | |
| if mode == 'train': | |
| self.log('train_loss', loss, on_step=True, on_epoch=True, sync_dist=True, prog_bar=True) | |
| self.train_cindex.update(pred.squeeze(), event_times, is_event) | |
| self.train_auroc.update(pred.squeeze(), event_times, is_event) | |
| elif mode == 'val': | |
| self.log('val_loss', loss, on_step=True, on_epoch=True, sync_dist=True, prog_bar=True, batch_size=self.cfg['batch_size_v']) | |
| self.val_cindex.update(pred.squeeze(), event_times, is_event) | |
| self.val_auroc.update(pred.squeeze(), event_times, is_event) | |
| elif mode == 'test': | |
| self.log('test_loss', loss, on_step=True, on_epoch=True, sync_dist=True, prog_bar=True, batch_size=self.cfg['batch_size_v']) | |
| self.test_cindex.update(pred.squeeze(), event_times, is_event) | |
| self.test_auroc.update(pred.squeeze(), event_times, is_event) | |
| if self.save_results: | |
| labels = torch.stack([event_times, is_event], dim=1) | |
| self.update_results(pred, labels, pids, demo) | |
| return loss | |
| def update_results(self, pred, labels, pids, demo): | |
| def _to_numpy(x): | |
| x = x.detach() | |
| if x.dtype in (torch.bfloat16, torch.float16, torch.float32, torch.float64): | |
| x = x.float() | |
| return x.cpu().numpy() | |
| pred_np = _to_numpy(pred) | |
| labels_np = _to_numpy(labels) | |
| for idx, pid in enumerate(pids): | |
| if pid not in self.results: | |
| self.results[pid] = {'pred': [], | |
| 'gt': [], | |
| 'age': _to_numpy(demo['age'][idx]), | |
| 'sex': _to_numpy(demo['sex'][idx]), | |
| 'bmi': _to_numpy(demo['bmi'][idx]), | |
| 'ahi': _to_numpy(demo['ahi'][idx]) | |
| } | |
| self.results[pid]['pred'].append(pred_np[idx]) | |
| self.results[pid]['gt'].append(labels_np[idx]) | |
| def on_test_epoch_end(self): | |
| if torch.cuda.is_available(): | |
| torch.cuda.synchronize() | |
| self._peak_gpu_mem_bytes = torch.cuda.max_memory_allocated() | |
| peak_gb = self._peak_gpu_mem_bytes / (1024 ** 3) | |
| self.log('test_peak_gpu_mem_gb', peak_gb, on_step=False, on_epoch=True, prog_bar=False) | |
| print(f"Peak GPU memory allocated: {peak_gb:.3f} GB") | |
| if self.ft_task == 'diagnosis': | |
| cindex = self.test_cindex.compute() | |
| auroc = self.test_auroc.compute() | |
| for i in range(len(cindex)): | |
| self.log(f'test_cindex_class_{i}', cindex[i], on_step=False, on_epoch=True, prog_bar=False) | |
| self.log(f'test_auroc_class_{i}', auroc[i], on_step=False, on_epoch=True, prog_bar=False) | |
| self.log('test_cindex', torch.mean(cindex), on_step=False, on_epoch=True, prog_bar=True) | |
| self.log('test_auroc', torch.mean(auroc), on_step=False, on_epoch=True, prog_bar=True) | |
| print("Test C-Index:\n", cindex) | |
| print("Test AUROC:\n", auroc) | |
| self.test_cindex.reset() | |
| self.test_auroc.reset() | |
| else: | |
| self.log_dict(self.test_metrics.compute(), on_epoch=True, sync_dist=True) | |
| print("Test Confusion Matrix:\n", self.test_conf_matrix.compute()) | |
| if self.save_results: | |
| # with open(os.path.join(self.save_results_dir, f'preds_{self.cfg["experiment_name"]}.pickle'), 'wb') as f: | |
| # pickle.dump(self.preds, f) | |
| # with open(os.path.join(self.save_results_dir, f'gt_{self.cfg["experiment_name"]}.pickle'), 'wb') as f: | |
| # pickle.dump(self.gt, f) | |
| with open(os.path.join(self.save_results_dir, f'results_{self.cfg["experiment_name"]}.pickle'), 'wb') as f: | |
| pickle.dump(self.results, f) | |
| if self.save_emb: | |
| for pid, emb in self.embeddings.items(): | |
| with open(os.path.join(self.save_emb_dir, f'embeddings_{self.cfg["experiment_name"]}_{pid}.pickle'), 'wb') as f: | |
| pickle.dump(emb, f) | |
| def get_all_configs_from_yaml(file_path): | |
| with open(file_path, 'r') as file: | |
| configs = yaml.safe_load(file) | |
| return configs | |
| # return Config(configs) | |
| def main(): | |
| # Parse command-line arguments | |
| parser = argparse.ArgumentParser(description="Train Perceiver Model") | |
| parser.add_argument('--config', type=str, required=True, help="Name of the configuration file (without .py)") | |
| parser.add_argument('--no_wandb', action='store_true', default=False, help="Disable Weights and Biases logging") | |
| args = parser.parse_args() | |
| all_configs = get_all_configs_from_yaml(os.path.join("configs", args.config + ".yaml")) | |
| print(all_configs) | |
| # Add transforms dynamically if needed | |
| # args.transforms = get_transforms() | |
| # print(args) | |
| print("Starting Experiment: ", all_configs['experiment_name']) | |
| config_save_path = os.path.join(all_configs['config_save_path'], all_configs['experiment_name']) | |
| os.makedirs(config_save_path, exist_ok=True) | |
| with open(os.path.join(config_save_path, 'config.yaml'), 'w') as file: | |
| yaml.dump(all_configs, file) | |
| # Set data paths | |
| # data_root("datasets") directory should be in the same directory as this script | |
| data_root = os.path.join(BASE_DIR, all_configs['data_root']) | |
| datasets = all_configs.get('datasets', ['phy']) | |
| # Load data | |
| print("Loading Data") | |
| splits_set = dict() | |
| splits_set['train'] = [] | |
| splits_set['val'] = [] | |
| splits_set['test'] = [] | |
| for ds in datasets: | |
| # Use Physionet 2018 dataset | |
| if ds == 'kiss': | |
| sample_modality = all_configs.get('modalities', ['EEG'])[0] # Make sure all modalities have the same number of subjects | |
| sample_dir = os.path.join(data_root, DATASET_PATH[ds], 'test', sample_modality) | |
| chns = [f.split('.')[0] for f in os.listdir(sample_dir) if f.endswith('mmap')] | |
| info_path = os.path.join(sample_dir, f'{chns[0]}.pickle') | |
| with open(info_path, 'rb') as f: | |
| sig_info = pickle.load(f) | |
| test_split = sorted([sub for sub in sig_info['sig_info'].keys()]) | |
| if all_configs.get('save_emb',False): | |
| test_split = test_split[:500] # For saving embeddings, limit to first 500 subjects only to save space | |
| splits_set['test'].extend([('kiss',f) for f in test_split]) | |
| elif ds == 'shhs1': | |
| sample_modality = all_configs.get('modalities', ['EEG'])[0] # Make sure all modalities have the same number of subjects | |
| sample_dir = os.path.join(data_root, DATASET_PATH[ds], 'test', sample_modality) | |
| chns = [f.split('.')[0] for f in os.listdir(sample_dir) if f.endswith('mmap')] | |
| info_path = os.path.join(sample_dir, f'{chns[0]}.pickle') | |
| with open(info_path, 'rb') as f: | |
| sig_info = pickle.load(f) | |
| test_subjects = sorted([sub for sub in sig_info['sig_info'].keys()]) | |
| def get_shhs_lists(shhs_split_file): | |
| ids = pd.read_csv(os.path.join("data","shhs_meta_data", shhs_split_file), header=None, names=['a']) | |
| ids = ids['a'].values.tolist() | |
| list_files = ["shhs1-20"+id[11:15] for id in ids] | |
| return list_files | |
| list_files_test = get_shhs_lists("test_list.txt") | |
| test_subjects = set(list_files_test) & set(test_subjects) | |
| test_subjects = sorted(list(test_subjects)) | |
| if all_configs.get('save_emb',False): | |
| test_subjects = test_subjects[:500] # For saving embeddings, limit to first 500 subjects only to save space | |
| splits_set['test'].extend([('shhs1',f) for f in test_subjects]) | |
| elif 'isruc' in ds: | |
| sample_modality = all_configs.get('modalities', ['EEG'])[0] # Make sure all modalities have the same number of subjects | |
| sample_dir = os.path.join(data_root, DATASET_PATH[ds], 'test', sample_modality) | |
| info_path = os.path.join(sample_dir, f'{chns[0]}.pickle') | |
| with open(info_path, 'rb') as f: | |
| sig_info = pickle.load(f) | |
| test_subjects = sorted([sub for sub in sig_info['sig_info'].keys()]) | |
| splits_set['test'].extend([(ds,f) for f in test_subjects]) | |
| print("Number of test subjects: ", len(splits_set['test'])) | |
| if all_configs['pretrain_checkpoint']: | |
| pretrain_exp_name = all_configs['pretrain_checkpoint'].split('-')[0] | |
| pretrain_config = get_all_configs_from_yaml(os.path.join("experiments", pretrain_exp_name, "config.yaml")) | |
| use_shared = pretrain_config.get('use_shared', all_configs.get('use_shared', True)) | |
| use_moe = pretrain_config.get('use_moe', all_configs.get('use_moe', True)) | |
| else: | |
| use_shared = all_configs.get('use_shared', True) | |
| use_moe = all_configs.get('use_moe', True) | |
| sig_len_target = all_configs.get('sig_len_target', 60) | |
| data_module = EvalDataModule( | |
| data_root = all_configs['data_root'], | |
| transforms_v = None, | |
| batch_size_v = all_configs['batch_size_v'], | |
| num_workers = all_configs['num_workers'], | |
| proportion_v = all_configs['proportion_v'], | |
| num_classes = all_configs['num_classes'], | |
| splits_set = splits_set, | |
| modalities = all_configs['modalities'], | |
| sig_len_target=sig_len_target, | |
| ft_task=all_configs.get('ft_task', 'sleep_staging'), | |
| full_night=all_configs.get('full_night', False), | |
| seq_truncate=all_configs.get('seq_truncate', False) | |
| ) | |
| data_module.setup() # Setup the data module to prepare the datasets and dataloaders | |
| print("Model Loading ...") | |
| model = ConvMAE1d_Evaluate( | |
| model_name=all_configs['model_name'], | |
| num_classes = all_configs['num_classes'], | |
| modalities = all_configs['modalities'], | |
| num_experts = all_configs['num_experts'], | |
| num_experts_per = all_configs['num_experts_per'], | |
| loss = all_configs['loss'], | |
| sig_len = sig_len_target * TARGET_RESAMPLE, | |
| use_shared = use_shared, | |
| use_moe = use_moe, | |
| full_finetune=all_configs.get('full_finetune', False), # Default to False if not specified | |
| use_revin = all_configs['use_revin'], | |
| ft_task = all_configs.get('ft_task', 'sleep_staging'), | |
| patch_size=all_configs.get('patch_size', 10), | |
| from_seq_pretrain=all_configs.get('from_seq_pretrain', False), | |
| full_night=all_configs.get('full_night', False), | |
| cfg=all_configs | |
| ) | |
| print("Model Loaded") | |
| if all_configs['pretrain_checkpoint']: | |
| ckpt_file = os.path.join('experiments', all_configs['pretrain_checkpoint']) | |
| model.load_pretrained(ckpt_file) | |
| # If a pure pretraining checkpoint is specified (not a lightning finetune checkpoint), | |
| # keep the existing `load_pretrained` behavior so encoder weights are loaded. | |
| if all_configs.get('pretrain_checkpoint') and not all_configs.get('previous_checkpoint') and not all_configs.get('test_checkpoint'): | |
| ckpt_file = os.path.join('experiments', all_configs['pretrain_checkpoint']) | |
| try: | |
| model.load_pretrained(ckpt_file) | |
| except Exception as e: | |
| print(f"Warning: failed to load pretrained weights via load_pretrained(): {e}") | |
| from pytorch_lightning.callbacks import DeviceStatsMonitor | |
| monitor = DeviceStatsMonitor() | |
| # Build a trainer for evaluation only | |
| trainer = pl.Trainer( | |
| devices=all_configs['gpus'], | |
| accelerator='gpu', | |
| logger=None, | |
| precision='bf16-mixed', | |
| callbacks=[monitor], | |
| ) | |
| # Run test only (do not run training/finetuning) | |
| # print(f"Running test with checkpoint: {ckpt_path}") | |
| import time | |
| start = time.time() | |
| trainer.test(model=model, datamodule=data_module) | |
| end = time.time() | |
| print(f"Elapsed time for {len(data_module.test_loader.dataset)}:{end - start}") | |
| if __name__ == '__main__': | |
| # Add optional CLI arg for specifying checkpoint path | |
| if '--ckpt' not in sys.argv: | |
| # no-op; argparse will ignore unknown args here because we've already parsed | |
| pass | |
| main() | |