from data_provider.data_factory import data_provider
from exp.exp_basic import Exp_Basic
from utils.tools import EarlyStopping, adjust_learning_rate, adjustment
from sklearn.metrics import precision_recall_fscore_support
from sklearn.metrics import accuracy_score
import torch.multiprocessing
torch.multiprocessing.set_sharing_strategy('file_system')
import torch
import torch.nn as nn
from torch import optim
import os
import time
import warnings
import numpy as np
import csv
import matplotlib.pyplot as plt
warnings.filterwarnings('ignore')
class Exp_Anomaly_Detection(Exp_Basic):
? ? #构造函数,传入参数args
? ? def __init__(self, args):
? ? ? ? super(Exp_Anomaly_Detection, self).__init__(args)
? ? #创建模型实例
? ? def _build_model(self):
? ? ? ? model = self.model_dict[self.args.model].Model(self.args).float()
? ? ? ? #若设置为多gpu且gpu可用,模型在多gpu运行
? ? ? ? if self.args.use_multi_gpu and self.args.use_gpu:
? ? ? ? ? ? model = nn.DataParallel(model, device_ids=self.args.device_ids)
? ? ? ? return model
? ? #从data_provider函数获取数据集合和数据加载器,并提供标志(train,val,test)
? ? def _get_data(self, flag):
? ? ? ? data_set, data_loader = data_provider(self.args, flag)
? ? ? ? return data_set, data_loader
? ? #选择优化器,该函数使用adam优化器,从传入的参数self 添加self.args.learning_rate学习率
? ? def _select_optimizer(self):
? ? ? ? model_optim = optim.Adam(self.model.parameters(), lr=self.args.learning_rate)
? ? ? ? return model_optim
? ? #选择损失函数,MSELoss(均方误差损失)
? ? def _select_criterion(self):
? ? ? ? criterion = nn.MSELoss()
? ? ? ? return criterion
? ? #验证方法,通过计算模型验证的误差来评估模型性能,即向前传播时不根据学习率计算梯度
? ? def vali(self, vali_data, vali_loader, criterion):
? ? ? ? total_loss = []
? ? ? ? #设置评估模式
? ? ? ? self.model.eval()
? ? ? ? with torch.no_grad():
? ? ? ? ? ? for i, (batch_x, _) in enumerate(vali_loader):
? ? ? ? ? ? ? ? #将转化为浮点型的数据加载到cpu或gpu
? ? ? ? ? ? ? ? batch_x = batch_x.float().to(self.device)
? ? ? ? ? ? ? ? #传入输入数据并获取输出
? ? ? ? ? ? ? ? outputs = self.model(batch_x, None, None, None)
? ? ? ? ? ? ? ? #多元素MS f_dim = -1 ?否则0 ?降维处理,保留最后数据集最后一列对应的数据集合
? ? ? ? ? ? ? ? f_dim = -1 if self.args.features == 'MS' else 0
? ? ? ? ? ? ? ? outputs = outputs[:, :, f_dim:]
? ? ? ? ? ? ? ? #返回一个与当前 graph 分离的、不再需要梯度的新张量
? ? ? ? ? ? ? ? pred = outputs.detach().cpu()
? ? ? ? ? ? ? ? true = batch_x.detach().cpu()
? ? ? ? ? ? ? ? #通过预测值、真实值计算损失函数
? ? ? ? ? ? ? ? loss = criterion(pred, true)
? ? ? ? ? ? ? ? #将loss添加total_loss列表
? ? ? ? ? ? ? ? total_loss.append(loss)
? ? ? ? #计算total_loss列表均值
? ? ? ? total_loss = np.average(total_loss)
? ? ? ? #将模型切换成训练模型
? ? ? ? self.model.train()
? ? ? ? return total_loss
? ? def train(self, setting):
? ? ? ? #加载train、val、test数据
? ? ? ? train_data, train_loader = self._get_data(flag='train')
? ? ? ? vali_data, vali_loader = self._get_data(flag='val')
? ? ? ? test_data, test_loader = self._get_data(flag='test')
? ? ? ? #创建模型存储文件
? ? ? ? path = os.path.join(self.args.checkpoints, setting)
? ? ? ? if not os.path.exists(path):
? ? ? ? ? ? os.makedirs(path)
? ? ? ? #获取时间
? ? ? ? time_now = time.time()
? ? ? ? #训练长度
? ? ? ? train_steps = len(train_loader)
? ? ? ? #早起停止函数,避免过拟合 patience 容忍升高次数
? ? ? ? early_stopping = EarlyStopping(patience=self.args.patience, verbose=True)
? ? ? ? #选择优化器
? ? ? ? model_optim = self._select_optimizer()
? ? ? ? #选择损失函数,这里选择MSE
? ? ? ? criterion = self._select_criterion()
? ? ? ? #根据配置的训练次数循环训练
? ? ? ? for epoch in range(self.args.train_epochs):
? ? ? ? ? ? iter_count = 0
? ? ? ? ? ? train_loss = []
? ? ? ? ? ? #选择训练模式
? ? ? ? ? ? self.model.train()
? ? ? ? ? ? #获取时间
? ? ? ? ? ? epoch_time = time.time()
? ? ? ? ? ? #加载训练数据计算
? ? ? ? ? ? for i, (batch_x, batch_y) in enumerate(train_loader):
? ? ? ? ? ? ? ? iter_count += 1
? ? ? ? ? ? ? ? #模型参数梯度值选择为0
? ? ? ? ? ? ? ? model_optim.zero_grad()
? ? ? ? ? ? ? ? #将转化为浮点型的数据加载到cpu或gpu
? ? ? ? ? ? ? ? batch_x = batch_x.float().to(self.device)
? ? ? ? ? ? ? ? #通过选择的模型计算
? ? ? ? ? ? ? ? outputs = self.model(batch_x, None, None, None)
? ? ? ? ? ? ? ? #跟据元素设置确定f_dim为-1或者0,多元素进行降维操作
? ? ? ? ? ? ? ? f_dim = -1 if self.args.features == 'MS' else 0
? ? ? ? ? ? ? ? outputs = outputs[:, :, f_dim:]
? ? ? ? ? ? ? ? #计算损失函数,
? ? ? ? ? ? ? ? loss = criterion(outputs, batch_x)
? ? ? ? ? ? ? ? #将loss里的高精度值添加在train_loss列表
? ? ? ? ? ? ? ? train_loss.append(loss.item())
? ? ? ? ? ? ? ? if (i + 1) % 100 == 0:
? ? ? ? ? ? ? ? ? ? print("\titers: {0}, epoch: {1} | loss: {2:.7f}".format(i + 1, epoch + 1, loss.item()))
? ? ? ? ? ? ? ? ? ? speed = (time.time() - time_now) / iter_count
? ? ? ? ? ? ? ? ? ? left_time = speed * ((self.args.train_epochs - epoch) * train_steps - i)
? ? ? ? ? ? ? ? ? ? print('\tspeed: {:.4f}s/iter; left time: {:.4f}s'.format(speed, left_time))
? ? ? ? ? ? ? ? ? ? iter_count = 0
? ? ? ? ? ? ? ? ? ? time_now = time.time()
? ? ? ? ? ? ? ? #计算当前梯度,反向传播
? ? ? ? ? ? ? ? loss.backward()
? ? ? ? ? ? ? ? #根据梯度更新网络参数
? ? ? ? ? ? ? ? model_optim.step()
? ? ? ? ? ? print("Epoch: {} cost time: {}".format(epoch + 1, time.time() - epoch_time))
? ? ? ? ? ? #计算train_loss列表均值
? ? ? ? ? ? train_loss = np.average(train_loss)
? ? ? ? ? ? #验证方法,通过计算模型验证的误差来评估模型性能,即向前传播时不根据学习率计算梯度
? ? ? ? ? ? vali_loss = self.vali(vali_data, vali_loader, criterion)
? ? ? ? ? ? test_loss = self.vali(test_data, test_loader, criterion)
? ? ? ? ? ? print("Epoch: {0}, Steps: {1} | Train Loss: {2:.7f} Vali Loss: {3:.7f} Test Loss: {4:.7f}".format(
? ? ? ? ? ? ? ? epoch + 1, train_steps, train_loss, vali_loss, test_loss))
? ? ? ? ? ? early_stopping(vali_loss, self.model, path)
? ? ? ? ? ? if early_stopping.early_stop:
? ? ? ? ? ? ? ? print("Early stopping")
? ? ? ? ? ? ? ? break
? ? ? ? ? ? adjust_learning_rate(model_optim, epoch + 1, self.args)
? ? ? ? best_model_path = path + '/' + 'checkpoint.pth'
? ? ? ? #加载训练模型
? ? ? ? self.model.load_state_dict(torch.load(best_model_path))
? ? ? ? return self.model
? ? def test(self, setting, test=0):
? ? ? ? test_data, test_loader = self._get_data(flag='test')
? ? ? ? train_data, train_loader = self._get_data(flag='train')
? ? ? ? if test:
? ? ? ? ? ? print('loading model')
? ? ? ? ? ? self.model.load_state_dict(torch.load(os.path.join('./checkpoints/' + setting, 'checkpoint.pth')))
? ? ? ? attens_energy = []
? ? ? ? folder_path = './test_results/' + setting + '/'
? ? ? ? if not os.path.exists(folder_path):
? ? ? ? ? ? os.makedirs(folder_path)
? ? ? ? #设置评估模式
? ? ? ? self.model.eval()
? ? ? ? #选择损失函数,MSELoss(均方误差损失)
? ? ? ? self.anomaly_criterion = nn.MSELoss(reduce=False)
? ? ? ? # (1) stastic on the train set
? ? ? ? #关闭梯度计算,节省内存和计算资源
? ? ? ? with torch.no_grad():
? ? ? ? ? ? #遍历加载器train_loader中每一批次的样本
? ? ? ? ? ? for i, (batch_x, batch_y) in enumerate(train_loader):
? ? ? ? ? ? ? ? #将数据的数据类型转化为浮点型,加载到GPU或CPU
? ? ? ? ? ? ? ? batch_x = batch_x.float().to(self.device)
? ? ? ? ? ? ? ? # reconstruction
? ? ? ? ? ? ? ? #通过模型进行重构
? ? ? ? ? ? ? ? outputs = self.model(batch_x, None, None, None)
? ? ? ? ? ? ? ? # criterion
? ? ? ? ? ? ? ? #计算每个样本的误差分数
? ? ? ? ? ? ? ? score = torch.mean(self.anomaly_criterion(batch_x, outputs), dim=-1)
? ? ? ? ? ? ? ? #将得到的分数转化为numpy数组
? ? ? ? ? ? ? ? score = score.detach().cpu().numpy()
? ? ? ? ? ? ? ? #将得到的分数添加attens_energy列表
? ? ? ? ? ? ? ? attens_energy.append(score)
? ? ? ? #将attens_energy列表中的所有numpy数组连接起来,并转换为一维数组train_energy
? ? ? ? attens_energy = np.concatenate(attens_energy, axis=0).reshape(-1)
? ? ? ? train_energy = np.array(attens_energy)
? ? ? ? # (2) find the threshold
? ? ? ? #清除数据集
? ? ? ? attens_energy = []
? ? ? ? test_labels = []
? ? ? ? #遍历加载器train_loader中每一批次的样本
? ? ? ? for i, (batch_x, batch_y) in enumerate(test_loader):
? ? ? ? ? ? ?#将数据的数据类型转化为浮点型,加载到GPU或CPU
? ? ? ? ? ? batch_x = batch_x.float().to(self.device)
? ? ? ? ? ? # reconstruction
? ? ? ? ? ? #通过模型进行重构
? ? ? ? ? ? outputs = self.model(batch_x, None, None, None)
? ? ? ? ? ? # criterion
? ? ? ? ? ? #计算每个样本的误差分数
? ? ? ? ? ? score = torch.mean(self.anomaly_criterion(batch_x, outputs), dim=-1)
? ? ? ? ? ? #将得到的分数转化为numpy数组
? ? ? ? ? ? score = score.detach().cpu().numpy()
? ? ? ? ? ? #将得到的分数添加attens_energy列表
? ? ? ? ? ? attens_energy.append(score)
? ? ? ? ? ? #将加载的batch_y标签添加test_labels列表
? ? ? ? ? ? test_labels.append(batch_y)
? ? ? ? ? ??
? ? ? ? #将attens_energy列表中的所有numpy数组连接起来,并转换为一维数组test_energy
? ? ? ? attens_energy = np.concatenate(attens_energy, axis=0).reshape(-1)
? ? ? ? test_energy = np.array(attens_energy)
? ? ? ? #将train_energy、test_energy列表中的数组连接起来,
? ? ? ? combined_energy = np.concatenate([train_energy, test_energy], axis=0)
? ? ? ? #根据模型参数中的异常比例anomaly_ratio,使用百分位数确定异常阈值
? ? ? ? threshold = np.percentile(combined_energy, 100 - self.args.anomaly_ratio)
? ? ? ? print("Threshold :", threshold)
? ? ? ? # (3) evaluation on the test set
? ? ? ? #计算测试数据标签,符合1不符合0
? ? ? ? pred = (test_energy > threshold).astype(int)
? ? ? ? ?#将test_labels列表中的所有数组连接起来,并转换为一维数组test_labels
? ? ? ? test_labels = np.concatenate(test_labels, axis=0).reshape(-1)
? ? ? ? test_labels = np.array(test_labels)
? ? ? ? gt = test_labels.astype(int)
? ? ? ? print("pred: ? ", pred.shape)
? ? ? ? print("gt: ? ? ", gt.shape)
? ? ? ? # (4) detection adjustment
? ? ? ? #输出修正
? ? ? ? gt, pred = adjustment(gt, pred)
? ? ? ? #转换为一维数组
? ? ? ? pred = np.array(pred)
? ? ? ? gt = np.array(gt)
? ? ? ? print("pred: ", pred.shape)
? ? ? ? print("gt: ? ", gt.shape)
? ? ? ? #计算准确率 ? 精确率或精度 ? 召回率 ? 调和标准(加权调和平均)
? ? ? ? accuracy = accuracy_score(gt, pred)
? ? ? ? precision, recall, f_score, support = precision_recall_fscore_support(gt, pred, average='binary')
? ? ? ? print("Accuracy : {:0.4f}, Precision : {:0.4f}, Recall : {:0.4f}, F-score : {:0.4f} ".format(
? ? ? ? ? ? accuracy, precision,
? ? ? ? ? ? recall, f_score))
? ? ? ? # Write prediction results to CSV
? ? ? ? with open(setting+'prediction_results.csv', 'w', newline='') as csvfile:
? ? ? ? ? ? writer = csv.writer(csvfile)
? ? ? ? ? ? writer.writerow(['Time', 'Predicted Label', 'Actual Label'])
? ? ? ? ? ? time=0
? ? ? ? ? ? for i, (batch_x, batch_y) in enumerate(test_loader):
? ? ? ? ? ? ? ? #time = i * self.args.batch_size
? ? ? ? ? ? ? ? for j in range(batch_x.size(0)):
? ? ? ? ? ? ? ? ? ? writer.writerow([time, pred[time], gt[time]])
? ? ? ? ? ? ? ? ? ? time += 1
? ? ? ? #将计算的准确率、精确率或精度、召回率、调和标准(加权调和平均)存储在.txt文档 ? ? ? ? ??
? ? ? ? f = open("result_anomaly_detection.txt", 'a')
? ? ? ? f.write(setting + " ?\n")
? ? ? ? f.write("Accuracy : {:0.4f}, Precision : {:0.4f}, Recall : {:0.4f}, F-score : {:0.4f} ".format(
? ? ? ? ? ? accuracy, precision,
? ? ? ? ? ? recall, f_score))
? ? ? ? f.write('\n')
? ? ? ? f.write('\n')
? ? ? ? f.close()
? ? ? ? return
?