《异常检测——从经典算法到深度学习》18 USAD:多元时间序列的无监督异常检测

《异常检测——从经典算法到深度学习》

  • 0 概论

  • 1 基于隔离森林的异常检测算法

  • 2 基于LOF的异常检测算法

  • 3 基于One-Class SVM的异常检测算法

  • 4 基于高斯概率密度异常检测算法

  • 5 Opprentice——异常检测经典算法最终篇

  • 6 基于重构概率的 VAE 异常检测

  • 7 基于条件VAE异常检测

  • 8 Donut: 基于 VAE 的 Web 应用周期性 KPI 无监督异常检测

  • 9 异常检测资料汇总(持续更新&抛砖引玉)

  • 10 Bagel: 基于条件 VAE 的鲁棒无监督KPI异常检测

  • 11 ADS: 针对大量出现的KPI流快速部署异常检测模型

  • 12 Buzz: 对复杂 KPI 基于VAE对抗训练的非监督异常检测

  • 13 MAD: 基于GANs的时间序列数据多元异常检测

  • 14 对于流数据基于 RRCF 的异常检测

  • 15 通过无监督和主动学习进行实用的白盒异常检测

  • 16 基于VAE和LOF的无监督KPI异常检测算法

  • 17 基于 VAE-LSTM 混合模型的时间异常检测

  • 18 USAD:多元时间序列的无监督异常检测

  • 19 OmniAnomaly:基于随机循环网络的多元时间序列鲁棒异常检测

  • 20 HotSpot:多维特征 Additive KPI 的异常定位
    相关:

  • VAE 模型基本原理简单介绍

  • GAN 数学原理简单介绍以及代码实践

  • 单指标时间序列异常检测——基于重构概率的变分自编码(VAE)代码实现(详细解释)

18. USAD:多元时间序列的无监督异常检测

论文下载地址:https://www.researchgate.net
Smileyan 翻译 https://smileyan.cn/#/ad/USAD
源码:https://github.com/manigalati/usad
另一个源码实现(非原论文源码)https://github.com/finloop/usad-torchlightning

18.1 论文概述

论文提出了一种对多维时间序列数据的无监督的、基于对抗训练的自编码器异常检测算法。并且论文是开源的,比较详细的介绍自己论文基于那些数据集,甚至提供了一些超参数的设置参考等。

论文的主要贡献包括:

  • 论文提出了一种结合自编码器与对抗性训练的算法;
  • 论文对公开的数据集进行了实验,对提出的算法进行鲁棒性、训练速度和性能方面的分析;
  • 使用 Orange 的专用数据进行可行性研究,以分析所提出的方法是否满足公司在可扩展性、稳定性、鲁棒性、训练速度和高性能方面的要求。

18.2 算法核心架构

关于自编码器的数学原理,对抗训练的数据原理 这里不介绍,推荐参考 1 | 2。

跳过相关工作背景介绍,直接看它的图一的左边。

《异常检测——从经典算法到深度学习》18 USAD:多元时间序列的无监督异常检测_第1张图片

A E 1 ( W ) = D 1 ( E ( W ) ) ,    A E 2 ( W ) = D 2 ( E ( W ) ) (3) AE_1(W) = D_1(E(W)),\ \ AE_2(W)=D_2(E(W)) \tag{3} AE1(W)=D1(E(W)),  AE2(W)=D2(E(W))(3)

USAD 训练时分为两个过程,

  1. 对这两个 AE 网络进行训练,使得它们对正常数据具有很好地重构能力;
  2. 对这两个 AE 网络进行对抗性训练,其中 A E 1 AE_1 AE1 尝试去 “迷惑” A E 2 AE_2 AE2 A E 2 AE_2 AE2 努力不被迷惑,即识别哪些是真实数据,哪些是 A E 1 AE_1 AE1 生成的假数据。

明白这个整体过程,我们就看图说话吧,先看左边的训练部分的红色虚线框中间的内容,原始窗口数据输入的编码网络 E n c o d e r Encoder Encoder,得到隐变量 Z Z Z,接着到达 D e c o d e r 1 Decoder 1 Decoder1 然后有根绿色的实线,这是只根据 Decoder 的结果进行参数的调整,再次训练。同样那个浅蓝色框的内容也是如此训练。这个是第一阶段(AE1 minimizes the reconstruction error of W (phase
1))。

第二阶段就是 A E 1 AE_1 AE1 尝试去迷惑 A E 2 AE_2 AE2 的过程,这个迷惑的过程可以打个可能恰当可能不恰当但是忍不住一定想打的比方: A E 1 AE_1 AE1 是一个假珠宝商,在他被抓进局子之前他想去销售自己的假货, A E 2 AE_2 AE2 就是受害者群体。我们查看图片中右上角的 A E 2 ( A E 1 ( W ) ) AE_2(AE_1(W)) AE2(AE1(W)),这个单方面企图欺骗的过程就是这个表达式了。(minimizes the difference between W an the reconstructed output of AE2)

这个过程中可能会疑惑,那为什么要两个AE网络,对抗训练一个不就行了吗?那是因为作者后面还要用到。这个训练过程完成以后,可以肯定的是,这两个网络肯定不同了,已经分道扬镳,从此各干各的了。

记着看图1的右边部分,检测过程。这部分内容比较好理解。最终我们的计算结果是将两个网络的检测结果带参数相加即可。
《异常检测——从经典算法到深度学习》18 USAD:多元时间序列的无监督异常检测_第2张图片

18.3 USAD 训练算法

我对原论文的三线表进行翻译,整理后结果如下,总体过程上面已经讲过,这里并没有什么提别之处。
《异常检测——从经典算法到深度学习》18 USAD:多元时间序列的无监督异常检测_第3张图片

18.4 USAD 异常检测算法

这里最主要的难点可能是,你需要知道AE是如何用于异常检测的。这个参考6 前半截部分即可,比较容易这个地方真的不想重复了额额额。

《异常检测——从经典算法到深度学习》18 USAD:多元时间序列的无监督异常检测_第4张图片

18.5 动手实验

18.5.1 数据集下载

论文总共用到了5个公开数据集,但源码中只是针对于 SWaT dataset 数据集而展开的,可以前去SWaT官网申请访问权限,https://itrust.sutd.edu.sg/itrust-labs_datasets/dataset_info/#swat,基本上填写一些信息就可以获得。但是

我根据论文提到的数据集源码地址下载好了,并上传到 蓝奏云 中,以便于不能访问谷歌网盘的小伙伴们直接下载。

  • 论文源码中提到的数据集下载地址(谷歌网盘):
    • 正常数据 https://drive.google.com/open?id=1rVJ5ry5GG-ZZi5yI4x9lICB8VhErXwCw | 测试数据 https://drive.google.com/open?id=1iDYc0OEmidN712fquOBRFjln90SbpaE7
  • 不能访问谷歌网盘的可以从蓝奏云下载:
    • https://smileyan.lanzoul.com/iWDDz04lq1zi

18.5.2 搭建环境

根据论文源码内容,需要安装 pytorch1.6,如果希望运行在 GPU 上根据torch官方文档安装相应版本即可。(好吧我承认我没有)。

!pip install torch==1.6.0 -i https://pypi.tuna.tsinghua.edu.cn/simple

《异常检测——从经典算法到深度学习》18 USAD:多元时间序列的无监督异常检测_第5张图片

18.5.3 运行源码

前去下载源码,并上传到自己本地或云服务器端的 jupyter 中,因为提供的源码也是基于jupyter完成的。

把下载好的数据集解压,新建一个文件input,把解压后的两个 csv 文件放到 input 文件夹中。

《异常检测——从经典算法到深度学习》18 USAD:多元时间序列的无监督异常检测_第6张图片

打开 USAD.ipynb,需要选择性运行源码。

《异常检测——从经典算法到深度学习》18 USAD:多元时间序列的无监督异常检测_第7张图片
接着后面的内容就一步一步运行就可以了,一般不会出现什么错误。

18.6 源码分析

只摘取其中一部分内容进行解释,我们重点关注 usad.py 文件,这里包括整个网络的实现以及训练测试等。

18.6.1 Encoder 类

这个只是单纯的网络结构,稍微看一下就可以了。

class Encoder(nn.Module):
  def __init__(self, in_size, latent_size):
    super().__init__()
    self.linear1 = nn.Linear(in_size, int(in_size/2))
    self.linear2 = nn.Linear(int(in_size/2), int(in_size/4))
    self.linear3 = nn.Linear(int(in_size/4), latent_size)
    self.relu = nn.ReLU(True)
        
  def forward(self, w):
    out = self.linear1(w)
    out = self.relu(out)
    out = self.linear2(out)
    out = self.relu(out)
    out = self.linear3(out)
    z = self.relu(out)
    return z

18.6.2 Decoder 类

这个也是单纯的网络结构,稍微看一下就可以了。

class Decoder(nn.Module):
  def __init__(self, latent_size, out_size):
    super().__init__()
    self.linear1 = nn.Linear(latent_size, int(out_size/4))
    self.linear2 = nn.Linear(int(out_size/4), int(out_size/2))
    self.linear3 = nn.Linear(int(out_size/2), out_size)
    self.relu = nn.ReLU(True)
    self.sigmoid = nn.Sigmoid()
        
  def forward(self, z):
    out = self.linear1(z)
    out = self.relu(out)
    out = self.linear2(out)
    out = self.relu(out)
    out = self.linear3(out)
    w = self.sigmoid(out)
    return w

18.6.3 UsadModel

这里应该结合源码中给定的公式,比如说两个网络的损失函数的计算方法。

 def __init__(self, w_size, z_size):
    super().__init__()
    self.encoder = Encoder(w_size, z_size)
    self.decoder1 = Decoder(z_size, w_size)
    self.decoder2 = Decoder(z_size, w_size)
  
  def training_step(self, batch, n):
    z = self.encoder(batch)
    w1 = self.decoder1(z)
    w2 = self.decoder2(z)
    w3 = self.decoder2(self.encoder(w1))
    # 计算两个网络的损失函数
    loss1 = 1/n*torch.mean((batch-w1)**2)+(1-1/n)*torch.mean((batch-w3)**2)
    loss2 = 1/n*torch.mean((batch-w2)**2)-(1-1/n)*torch.mean((batch-w3)**2)
    return loss1,loss2

  def validation_step(self, batch, n):
    z = self.encoder(batch)
    w1 = self.decoder1(z)
    w2 = self.decoder2(z)
    w3 = self.decoder2(self.encoder(w1))
    loss1 = 1/n*torch.mean((batch-w1)**2)+(1-1/n)*torch.mean((batch-w3)**2)
    loss2 = 1/n*torch.mean((batch-w2)**2)-(1-1/n)*torch.mean((batch-w3)**2)
    return {'val_loss1': loss1, 'val_loss2': loss2}
        
  def validation_epoch_end(self, outputs):
    batch_losses1 = [x['val_loss1'] for x in outputs]
    epoch_loss1 = torch.stack(batch_losses1).mean()
    batch_losses2 = [x['val_loss2'] for x in outputs]
    epoch_loss2 = torch.stack(batch_losses2).mean()
    return {'val_loss1': epoch_loss1.item(), 'val_loss2': epoch_loss2.item()}
    
  def epoch_end(self, epoch, result):
    print("Epoch [{}], val_loss1: {:.4f}, val_loss2: {:.4f}".format(epoch, result['val_loss1'], result['val_loss2']))

18.7 总结

论文提到的算法外壳是一个对抗训练,对抗训练的核心是自编码器,并且在这个基础上进行优化,将普通的自编码器拆分成一个编码器和两个解码器,拿其中一个解码器生产假数据去对抗训练另外一个解码器,以提高它对假数据的识别能力。

Smileyan
2022.5.10 22:17

你可能感兴趣的:(异常检测,深度学习,算法)