揭开KPI异常检测顶级AI模型面纱(3)

AI入门学习

共 4560字,需浏览 10分钟

 ·

2020-12-08 13:40

我是hitTeam,有幸在华为运营商BG全球技术服务部、华为NAIE产品部举办的2020GDE全球开发者大赛·KPI异常检测中获得二等奖,在这里跟大家分享本次比赛的方案和收获。


01


 赛事简介



本次比赛的题目是核心网关键性能指标(KPI)异常检测。主办方在比赛中提供某运营商的KPI真实数据,采样间隔为1小时。参赛选手需要根据历史一个月异常标签数据(训练数据集),训练模型并检测后续一周内各KPI(测试数据集)中的异常。


02


 赛题分析



所谓磨刀不误砍柴工,在正式进行异常检测之前, 充分观察数据的特征,有助于我们选择一个合适的方法。首先分析比赛数据, 本次比赛数据集共有二十条不同的KPI,各条KPI的特点各不相同,其中大多数有明显的周期性。通过观察数据时间戳可以确定, 训练集和测试集的KPI在时间上是连续的, 如下图所示(kpi_id=ed63c9ea-322d-40f7-bbf0-c75fb275c067),蓝色为训练集,绿色为测试集

 在异常检测场景中,大部分样本为正常样本,只有少量的样本为异常样本, 通过观察上图也可以看出来。而且在实际应用中, 在大量的样本中找出极少量的异常样本然后打上标签是一件费时费力的事情。综上所述,在实际运维中,主流的异常检测方法应该是无监督的异常检测方法。在拿到数据之前我想到用基于预测或者是基于重构的异常检测方法。

然而这两种无监督的异常检测方法在本次比赛中注定无法大展身手,原因是:数据集中有些被标注的异常点不是很明显。以上两种异常检测方法,在判断时候的值是否为异常时, 都是模型根据历史的信息,预测或者重构出当前时刻点的值, 然后根据两点的之间的差距大小来判断是否为异常, 差距越大,越有可能为异常。如下图所示, 1,2两个异常点偏离原来的轨迹交大, 使用预测或者重构出来的会与有较大的差距, 所以这两个点能够比较容易地检测出来。但是对于点3这种偏离程度不是很大的情况, 重构出来的会与没有很大的差距,这两种无监督的异常检测方法对它无能为力。
在大佬云集的激烈比赛中, 放弃这些难以检测的异常, 无疑是放弃比赛了。既然本次比赛提供了标签信息,而且提供了完整的训练集和测试集, 那我就可以为了提高检测效果而“不择手段”


03


赛题思路



在正式开始介绍我的思路之前,先介绍一下我的灵感来源:Deep Support Vector Data Description(Deep SVDD),来自论文Deep One-Class Classification: http://data.bit.uni-bonn.de/publications/ICML2018.pdf

1


一句话概括它的思想:使用一个神经网络将样本从输入空间映射到输出空间。使得在隐空间上, 正常样本聚集类中心附近。在测试时, 如果一个样本距离正常类中心太远, 就认定为异常。


01

不择手段一:充分利用标签信息。

 Deep SVDD这个方法是无监督的异常检测方法,充分利用标签信息之后, 我就可以让网络更加明确地区分正常样本和异常样本。理想的情况是想将正常样本和异常样本在输出空间聚成两簇, 如下图:
详细步骤如下:
构建一个神经网络, 我构建的神经网络很简单, 只有三层:
encoder = nn.Sequential(
    nn.Linear(input_size, 128),
    nn.ReLU(inplace=True),
    nn.Linear(128, 64),
    nn.ReLU(inplace=True),
    nn.Linear(64, output_size))
在这个网络中, 设置output_size = 3。这样的设定,一定程度上是为了方便在3维空间中将这些点画出来。

所有的正常样本记为, 所有的异常样本为, 使用 神经网络分别将他们映射到输出空间:
 
 

为了实现上图聚成两簇的效果,设计损失函数如下:
    def loss(z_nor, z_ano):
    # 计算两个类簇的中心
    nor_center = torch.mean(z_nor, dim=0)
    ano_center = torch.mean(z_ano, dim=0)
   
    # 两类中心远离, 最大化dist
    dist = torch.sum((nor_center - ano_center) ** 2)
   
    # 类内聚集, 最小化nor_dis + ano_dis
    nor_dis = torch.sum((nor_center - z_nor) ** 2)
    ano_dis = torch.sum((ano_center - z_ano) ** 2)
   
    # 综合以上两点, 整体的loss
    loss = (nor_dis + ano_dis) - dist
如果只是按照以上的loss训练, 会发现类间距离dist会不断增大,而忽略了nordis和anodis的最小化。为了解决上述问题, 引入一个超参Lambda, 调节网络的优化方向:
Lambda = 0.05
if dist.item() > max_dis:
           Lambda = 0.9          
loss = Lambda * (nor_dis + ano_dis) - (1 - Lambda) * dist

刚开始的时候, Lambda=0.05, 网络的优化方向更侧重于将dist变大,将两个类簇分开。当dist大到预设的最大值maxdis, 就调整Lambda=0.9, 让网络的优化方向更侧重于减小(nordis+ano_dis), 即让每个点往它的类簇中心靠拢。在训练过程中, 观察到一些很有意思的现象:刚开始的时候, 所有的样本都聚集在(0,0,0)点附近, 两类中心的距离dist自然也是非常接近于0,经过多轮训练之后, 网络才开始很明显地将两簇的中心分开。在其中几条kpi中, 正常样本和异常样本比较相似,甚至要经过1000轮以上训练之后才能将两类分开。

实际结果与设想的结果不尽相同, 但是对异常检测效果影响不大。实际训练和检测的结果如下图所示(kpi_id=3fe4d11f-4e06-4725-bf16-19db40a7a3e1):

从图中可以看到, 正常样本和异常样本两个簇不是很明显的两个分散的簇。所有的样本的在这个三维空间中排成一条直线。仔细想想也是合理的, 那些在网络看来像是正常又像是异常的点, 就在两簇的中间徘徊。另外,这个图看起来异常样本比正常样本多, 这是为什么呢?其实在正常样本的簇中,许多非常相像的正常样本完全重叠了,所以看起来正常样本少一点;而异常的模式却是多种多样的, 所以在这个空间中它们比较分散,看起来比较多。

02

不择手段二:调整不同的滑动窗口大小和选取标签的位置。

在实际应用中,异常检测要求及时性。当异常发生时,越快检测出异常越好。所以一般在滑动窗口采样时,都是取窗口的最后一个点的标签作为这个窗口的标签。这样做的话,在检测一个点是否为异常时, 只能根据它本身以及过去的数据来检测。然而在本次比赛中,并不要求及时响应。所以这里我为了提高检检测效果,“不择手段”地引入了未来的数据。根据不同的KPI, 选择不同的滑动窗口大小和标签在窗口里的位置。换一种说法就是:在实际应用中,对于窗口大小为, 一般是使用窗口内的点组成的序列 ,取这个窗口内最后一个点的标签作为这个窗口的标签;在本次比赛中, 可以不局限于取这个窗口的最后一点的标签作为这个这窗口的标签,只要能够提升模型的检测效果, 我可以取窗口中的任意一个点的标签作为这个窗口的标签。

03

不择手段三:不同的参数组合。

在调参过程中发现,不同的窗口大小W和不同的窗口内标签所在位置label_idx有不同的效果。在有些KPI上, 通过尝试多种W和label_idx的组合, 训练出多个网络模型。最后检测异常的时候, 综合多个网络模型的检测结果,作为最终的检测结果。


04


判断异常的方式



一开始我使用的判断异常的方式非常直观。对于测试集的每一个样本,直接根据它到正常簇和异常簇的哪一簇中心的距离更近,它到哪一簇的中心更近,就把它判定为哪一类。后来发现这种判断方式不够用:对于一些训练集里没有出现过的异常样本, 虽然它距离正常簇中心更近,却比大多数正常样本更加远离正常簇中心。基于以上观察, 判断异常的方式修改为:计算每个测试样本到正常簇中心的距离, 确定一个阈值, 当就判定该样本为异常。


05


相邻异常点



在许多时间序列异常检测论文以及一些时间序列异常检测比赛中, 常常会出现这样一种修改过的F1 score的计算方式:如果异常检测算法在该连续异常区间开始后的 不晚于T 个时间点内检测到了该连续异常区间,就认为此异常检测算法成功地检测到了整段连续异常区间,因此该异常区间内的每一个异常点都算作一次true positive(TP);否则,该连续异常区间内的每一个异常点都算作一次false negative(FN)。就举下图为例,第一行是真实标签。如果模型输出判定为异常, 那么得到的结果是point-wise alert这一行的结果。采用以上的评分方法, 经过调整之后的结果为adjusted alert这一行的结果。

这样的评分方式是有实际意义的。在实际的运维过程中, 一旦异常检测算法检测出异常并产生警报, 这时就有人工介入, 那么后续发生的异常也就有人工干预了,那就相当于这一段连续的异常,都有人工介入处理。

恰好我的模型在一些连续的异常区间中, 常常只能检测出其中一个异常点,遗憾的是本次比赛没有采用以上这种评分方式。我只能自力更生, 设定一个规则:对于算法检测出来的异常点的前一个点或者后一个点,可以认为它们很大概率也是异常点, 可以适当降低阈值。使用这种规则, 我的算法检测能够检测出更多连续的异常。


06


其他常规操作



除了以上的内容, 还有一些常规的操作:
•数据增强
对异常点进行放缩操作。在一个滑动窗口中, 固定正常点的数值, 对异常点的数值进行放缩。通过这种操作,获得了更丰富的异常类型, 方便模型的测试集中找出更多的异常点。

•数据预处理
使用线性插值了补全缺失值。
标准化: zscore。公式为:

•模型组合
不同的参数的检测结果的并集作为最终的检测结果。



07


 总 结 



在本次比赛过程中,我使用的方法要调整的参数比较多, 还好有主办方提供的NAIE平台, 不仅提供在线编程神器webIDE, 还可以提交多个训练任务,大大减轻了我在比赛中调参的负担。此外, 非常感谢希旭哥、素颜姐、小爱姐众多工作人员提供的帮助, 总是第一时间为参赛选手答疑解惑。

最后,祝华为云和NAIE越办越好, 欣欣向荣, 日升月恒!

加群交流学习

↓内推、交流加小编

浏览 31
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报