时间序列(Time-Series)exp_anomaly_detection.py代码解析

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(" iters: {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(' speed: {:.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 + "  
")
        f.write("Accuracy : {:0.4f}, Precision : {:0.4f}, Recall : {:0.4f}, F-score : {:0.4f} ".format(
            accuracy, precision,
            recall, f_score))
        f.write('
')
        f.write('
')
        f.close()
        return