Download Test_TSB.py from yyyang0/VETime: direct link, hf CLI and curl.
- Browser
- Download file 14.1 kB
-
https://huggingface.co/yyyang0/VETime/resolve/main/Test_TSB.py
- Command line
-
hf download hf://yyyang0/VETime/Test_TSB.py
-
curl -L -o Test_TSB.py https://huggingface.co/yyyang0/VETime/resolve/main/Test_TSB.py
14.1 kB
| # -*- coding: utf-8 -*- | |
| # Author: Qinghua Liu <liu.11085@osu.edu> | |
| # License: Apache-2.0 License | |
| import argparse | |
| import logging | |
| import multiprocessing as mp | |
| import os | |
| import random | |
| import time | |
| from concurrent.futures import ProcessPoolExecutor, as_completed | |
| from datetime import datetime | |
| from pathlib import Path | |
| from typing import Any, Dict, List, Optional, Tuple | |
| import numpy as np | |
| import pandas as pd | |
| import torch | |
| import torch.nn.functional as F | |
| import torchvision.transforms as transforms | |
| from tqdm import tqdm | |
| from TSB_AD.utils.slidingWindows import find_length_rank | |
| from dataset.dataloader import create_random_mask | |
| from dataset.pre_image import ts2image_Test | |
| from evaluation.metrics import get_metrics | |
| from model.TS_encoder.config import default_config_t | |
| from model.TS_encoder.ts_model import TS_Model | |
| from model.VETime import VETIME | |
| from model.Vision_encoder.V_encoder import V_model | |
| SEED = 2024 | |
| torch.manual_seed(SEED) | |
| torch.cuda.manual_seed(SEED) | |
| torch.cuda.manual_seed_all(SEED) | |
| np.random.seed(SEED) | |
| random.seed(SEED) | |
| torch.backends.cudnn.benchmark = False | |
| torch.backends.cudnn.deterministic = True | |
| logging.basicConfig( | |
| level=logging.INFO, | |
| format='%(asctime)s - %(levelname)s - %(message)s', | |
| datefmt='%Y-%m-%d %H:%M:%S' | |
| ) | |
| logger = logging.getLogger(__name__) | |
| os.environ["CUDA_VISIBLE_DEVICES"] = "0" | |
| PASS_LIST = [ | |
| "Daphnet", | |
| "CATSv2", | |
| "SWaT", | |
| "LTDB", | |
| "TAO", | |
| "Exathlon", | |
| "MITDB", | |
| "MSL", | |
| "SMAP", | |
| "SMD", | |
| "SVDB", | |
| "OPP", | |
| ] | |
| USE_LIST = [ | |
| "IOPS", | |
| "MGAB", | |
| "NAB", | |
| "NEK", | |
| "Power", | |
| "SED", | |
| "Stock", | |
| "TODS", | |
| "WSD", | |
| "UCR", | |
| "YAHOO", | |
| "MSL", | |
| "SMAP", | |
| ] | |
| DATA_INIT_SETTING = { | |
| "img_size": 224, | |
| "T_sqrt": False, | |
| } | |
| def dataloader_TSB(data, labels,data_setting,patch_size): | |
| time_series = np.array(data, dtype=float) | |
| lengths = time_series.shape[0] | |
| target_length = ((lengths + patch_size-1) // patch_size) * patch_size | |
| ts = (time_series - time_series.mean(axis=0, keepdims=True)) / (time_series.std(axis=0, keepdims=True) + 1e-8) | |
| img,period,pad_value = ts2image_Test(ts,patch_size,**data_setting) | |
| image_inputs = torch.tensor(img, dtype=torch.float32).unsqueeze(0) | |
| period = torch.tensor(period, dtype=torch.float32).unsqueeze(0) | |
| pad_value = torch.tensor(pad_value, dtype=torch.float32).unsqueeze(0) | |
| ts = torch.tensor(ts, dtype=torch.float32).unsqueeze(0) | |
| labels=torch.tensor(labels, dtype=torch.float32).unsqueeze(0) | |
| padded_ts = torch.nn.functional.pad( | |
| ts.transpose(1, 2), | |
| pad=(0, target_length-lengths), | |
| mode='constant', | |
| value=0.0 | |
| ).transpose(1, 2) | |
| padded_labels = torch.nn.functional.pad( | |
| labels, | |
| pad=(0, target_length-lengths), | |
| mode='constant', | |
| value=-1 | |
| ) | |
| B, target_length, num_features = padded_ts.shape | |
| attention_mask= torch.ones(B, target_length, dtype=torch.bool) | |
| attention_mask[:, ts.shape[1]:] = False | |
| mask_time_series,mask = create_random_mask(padded_ts, attention_mask,patch_size) | |
| return { | |
| 'time_series': padded_ts, | |
| 'mask_time_series':mask_time_series, | |
| 'image': image_inputs, | |
| 'mask': mask, | |
| 'labels': padded_labels, | |
| 'attention_mask': attention_mask, | |
| 'period':period, | |
| 'p_value':pad_value, | |
| } | |
| def TSB_test( | |
| model, | |
| args_test, | |
| data_setting=DATA_INIT_SETTING, | |
| device='cuda:0', | |
| dataset_setting=PASS_LIST, | |
| for_m =False | |
| ): | |
| import os | |
| import time | |
| import pandas as pd | |
| import torch | |
| from tqdm import tqdm | |
| patch_size = model.patch_size | |
| target_dir = args_test.target_dir | |
| model_name = args_test.model_name | |
| file_list = args_test.file_list | |
| os.makedirs(target_dir, exist_ok=True) | |
| print('Testing on TSB-AD datasets...') | |
| model.eval() | |
| model.to(device) | |
| runtime_log = [] | |
| progress_bar = tqdm(file_list, desc=f"[Stage 1] Saving results for {model_name}") | |
| for filename in progress_bar: | |
| if any(filter_item in filename for filter_item in dataset_setting): | |
| continue | |
| output_path = os.path.join(target_dir, f'{filename.split(".")[0]}_results.pkl') | |
| file_path = os.path.join(args_test.dataset_dir, filename) | |
| df = pd.read_csv(file_path).dropna() | |
| datas = df.iloc[:, :-1].values.astype(float) | |
| labels_full = df['Label'].astype(int).to_numpy() | |
| train_index = int(filename.split('.')[0].split('_')[-3]) | |
| data = datas[train_index:, :] | |
| labels = labels_full[train_index:] | |
| start_time = time.time() | |
| batch = {k: v.to(device) for k, v in dataloader_TSB(data, labels, data_setting, patch_size).items()} | |
| labels_tensor = batch["labels"] | |
| images = batch["image"] | |
| time_series = batch["time_series"] | |
| att_mask = batch["attention_mask"] | |
| with torch.no_grad(): | |
| if len(labels) > model.MAX_L: | |
| data_splits = model.split_data(images, time_series, att_mask, labels_tensor) | |
| logits_list = [] | |
| for data_part in data_splits: | |
| img_part, ts_part, att_mask_p, label_part = data_part | |
| images_folded, init_img_size = model.vit_encoder.fold_image( | |
| img_part, batch['period'].cpu().numpy(), batch['p_value'], **data_setting | |
| ) | |
| local_embeddings, _, _, _ = model(images_folded, ts_part, att_mask_p, init_img_size) | |
| _, logits_part = model.anomaly_detection_loss(local_embeddings, label_part) | |
| logits_list.append(logits_part) | |
| logits = torch.cat(logits_list, dim=1) | |
| else: | |
| images_folded,init_img_size = model.vit_encoder.fold_image( | |
| images, batch['period'].cpu().numpy(), batch['p_value'], **data_setting | |
| ) | |
| local_embeddings, _, _, _ = model(images_folded, time_series, att_mask, init_img_size) | |
| _, logits = model.anomaly_detection_loss(local_embeddings, labels_tensor) | |
| probs = torch.softmax(logits, dim=-1)[:, :, 1].detach().squeeze().cpu().numpy() | |
| labels_np = labels_tensor.squeeze().cpu().numpy() | |
| values = time_series.detach().squeeze().cpu().numpy() | |
| pd.DataFrame({ | |
| 'value': values.tolist(), | |
| 'label': labels_np.tolist(), | |
| 'anomaly_score': probs.tolist(), | |
| }).to_pickle(output_path) | |
| run_time = time.time() - start_time | |
| print(f"Saved {output_path} (time: {run_time:.4f}s)") | |
| runtime_log.append({ | |
| 'filename': filename, | |
| 'run_time_seconds': run_time | |
| }) | |
| log_df = pd.DataFrame(runtime_log) | |
| csv_save_path = os.path.join(os.getcwd(), f'runtime_log_{model_name}.csv') | |
| log_df.to_csv(csv_save_path, index=False) | |
| TSB_test_parallel_postprocess(args_test, data_setting, dataset_setting) | |
| def _process_single_result_file(args): | |
| result_path, filename, sliding_window, args_test = args | |
| try: | |
| df = pd.read_pickle(result_path) | |
| probs = np.array(df['anomaly_score'].tolist()) | |
| labels = np.array(df['label'].tolist()) | |
| pred_threshold = np.mean(probs) + 3 * np.std(probs) | |
| evaluation_result = get_metrics(probs, labels, slidingWindow=sliding_window, pred=probs > pred_threshold) | |
| return { | |
| 'filename': filename, | |
| 'length': len(labels), | |
| 'metrics': evaluation_result, | |
| } | |
| except Exception as e: | |
| print(f"❌ Error processing {filename}: {e}") | |
| return None | |
| def TSB_test_parallel_postprocess( | |
| args_test, | |
| data_setting=DATA_INIT_SETTING, | |
| dataset_setting=PASS_LIST, | |
| num_workers=80 | |
| ): | |
| target_dir = args_test.target_dir | |
| file_list = args_test.file_list | |
| tasks = [] | |
| for filename in file_list: | |
| if any(filter_item in filename for filter_item in dataset_setting): | |
| continue | |
| result_path = os.path.join(target_dir, f'{filename.split(".")[0]}_results.pkl') | |
| if not os.path.exists(result_path): | |
| continue | |
| file_path = os.path.join(args_test.dataset_dir, filename) | |
| df = pd.read_csv(file_path).dropna() | |
| datas = df.iloc[:, 0:-1].values.astype(float) | |
| slidingWindow = find_length_rank(datas[:,0].reshape(-1, 1), rank=1) | |
| tasks.append((result_path, filename, slidingWindow, args_test)) | |
| results = [] | |
| mp.set_start_method('spawn', force=True) | |
| with ProcessPoolExecutor(max_workers=num_workers) as executor: | |
| futures = [executor.submit(_process_single_result_file, task) for task in tasks] | |
| for future in tqdm(as_completed(futures), total=len(futures), desc="[Stage 2] Post-processing"): | |
| res = future.result() | |
| if res: | |
| results.append(res) | |
| write_csv = [] | |
| col_w = None | |
| for res in results: | |
| row = [res['filename'], res['length'], 0] + list(res['metrics'].values()) # Time 设为 0 | |
| write_csv.append(row) | |
| if col_w is None: | |
| col_w = ['file', 'Length', 'Time'] + list(res['metrics'].keys()) | |
| w_csv = pd.DataFrame(write_csv, columns=col_w) | |
| use_data = USE_LIST | |
| summary_rows = [] | |
| for dataset_name in use_data: | |
| mask = w_csv['file'].str.contains(dataset_name, case=True, na=False) | |
| subset = w_csv[mask] | |
| if len(subset) == 0: | |
| continue | |
| numeric_cols = subset.select_dtypes(include=[np.number]).columns | |
| mean_values = subset[numeric_cols].mean(axis=0).round(4) | |
| summary_row = {'file': f"{dataset_name}_MEAN", 'Time': mean_values.get('Time', 0)} | |
| for col in col_w[2:]: | |
| summary_row[col] = mean_values.get(col, np.nan) | |
| summary_rows.append(summary_row) | |
| if summary_rows: | |
| summary_df = pd.DataFrame(summary_rows, columns=col_w) | |
| final_csv = pd.concat([w_csv, summary_df], ignore_index=True) | |
| else: | |
| final_csv = w_csv | |
| timestamp = datetime.now().strftime("%m%d-%H") | |
| suffix = "_sq.csv" if data_setting["T_sqrt"] else "_P.csv" | |
| output_csv = f'{args_test.save_dir}/{args_test.model_name}_{data_setting["img_size"]}_{timestamp}{suffix}' | |
| os.makedirs(args_test.save_dir, exist_ok=True) | |
| final_csv.to_csv(output_csv, index=False) | |
| print(f"📊 Final results saved to: {output_csv}") | |
| return output_csv | |
| import torch | |
| import numpy as np | |
| class EarlyStopping: | |
| def __init__(self, patience=3, verbose=False, delta=0, path='checkpoint.pth'): | |
| self.patience = patience | |
| self.verbose = verbose | |
| self.delta = delta | |
| self.path = path | |
| self.best_score = None | |
| self.early_stop = False | |
| self.counter = 0 | |
| self.val_loss_min = np.inf | |
| def __call__(self, val_loss, model): | |
| score = -val_loss | |
| if self.best_score is None: | |
| self.best_score = score | |
| self.save_checkpoint(val_loss, model) | |
| elif score < self.best_score + self.delta: | |
| self.counter += 1 | |
| print(f'EarlyStopping counter: {self.counter} out of {self.patience}') | |
| if self.counter >= self.patience: | |
| self.early_stop = True | |
| else: | |
| self.best_score = score | |
| self.save_checkpoint(val_loss, model) | |
| self.counter = 0 | |
| def save_checkpoint(self, val_loss, model): | |
| if self.verbose: | |
| print(f'Validation loss decreased ({self.val_loss_min:.6f} --> {val_loss:.6f}). Saving model ...') | |
| self.val_loss_min = val_loss | |
| if __name__ == '__main__': | |
| parser = argparse.ArgumentParser(description='Testing on TSB-AD') | |
| parser.add_argument('--dataset_dir', type=str, default='./dataset/TSB-AD/Datasets/TSB-AD-U') | |
| parser.add_argument('--model_name', default= 'VETime', type=str, help='Name of the model') | |
| parser.add_argument('--save_dir', type=str, default='./output/metrics/uni/') | |
| parser.add_argument('--output_file_path', default='./output/result.json',type=str, help='Path to the output file') | |
| parser.add_argument('--device', type=str, default='cuda:0', help='Device to use for evaluation') | |
| parser.add_argument('--data_setting', type=str, default=DATA_INIT_SETTING, help='Device to use for evaluation') | |
| parser.add_argument('--ts_path', type=str, default='./checkpoints/weight_ts/full_mask_anomaly_head_pretrain_checkpoint_best.pth' | |
| , help='TS_weight') | |
| parser.add_argument('--vetime_path', type=str, default='./checkpoints/VETime.pth' | |
| , help='VETime_weight') | |
| parser.add_argument('--vision_name', type=str, default='mae_visualize_base.pth' | |
| , help='vision_weight') | |
| parser.add_argument('--num_workers', type=int, default=10 | |
| , help='Number of workers for parallel processing') | |
| args_test = parser.parse_args() | |
| args_test.target_dir = os.path.join(args_test.save_dir, args_test.model_name) | |
| os.makedirs(args_test.target_dir, exist_ok = True) | |
| args_test.file_list = sorted(os.listdir(args_test.dataset_dir)) | |
| args_test.output_file_path = args_test.output_file_path.replace('result.json', f'{args_test.model_name.replace("/", "-")}_result.json') | |
| device =args_test.device | |
| vision_model = V_model(args_test.vision_name,unpatch=True) | |
| config_v = vision_model.config | |
| ts_model = TS_Model(default_config_t) | |
| model = VETIME(config_v,vision_model,default_config_t,ts_model,args_test.model_name) | |
| model.eval().to(device) | |
| if args_test.vetime_path!=None: | |
| state_dict = torch.load(args_test.vetime_path, map_location='cpu') | |
| model.load_state_dict(state_dict, strict=False) | |
| TSB_test(model,args_test,args_test.data_setting,device,PASS_LIST,False) |