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