VETime / Test_TSB.py
yyyang0's picture
Upload 100 files
ddd3436 verified
Raw History Blame Contribute Delete
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)