[参赛经验分享]揭开KPI异常检测顶级AI模型面纱--二等奖(hitTeam团队)
举报
就挺突然
发表于 2021/01/05 10:37:07
2021/01/05
【摘要】 我是hitTeam,有幸在华为运营商BG全球技术服务部、华为NAIE产品部举办的2020GDE全球开发者大赛·KPI异常检测中获得二等奖,在这里跟大家分享本次比赛的方案和收获。01 赛事简介本次比赛的题目是核心网关键性能指标(KPI)异常检测。主办方在比赛中提供某运营商的KPI真实数据,采样间隔为1小时。参赛选手需要根据历史一个月异常标签数据(训练数据集),训练模型并检测后续一周内各KPI(...
我是hitTeam,有幸在华为运营商BG全球技术服务部、华为NAIE产品部举办的2020GDE全球开发者大赛·KPI异常检测中获得二等奖,在这里跟大家分享本次比赛的方案和收获。
本次比赛的题目是核心网关键性能指标(KPI)异常检测。主办方在比赛中提供某运营商的KPI真实数据,采样间隔为1小时。参赛选手需要根据历史一个月异常标签数据(训练数据集),训练模型并检测后续一周内各KPI(测试数据集)中的异常。
所谓磨刀不误砍柴工,在正式进行异常检测之前, 充分观察数据的特征,有助于我们选择一个合适的方法。首先分析比赛数据, 本次比赛数据集共有二十条不同的KPI,各条KPI的特点各不相同,其中大多数有明显的周期性。通过观察数据时间戳可以确定, 训练集和测试集的KPI在时间上是连续的, 如下图所示(kpi_id=ed63c9ea-322d-40f7-bbf0-c75fb275c067),蓝色为训练集,绿色为测试集。
在异常检测场景中,大部分样本为正常样本,只有少量的样本为异常样本, 通过观察上图也可以看出来。而且在实际应用中, 在大量的样本中找出极少量的异常样本然后打上标签是一件费时费力的事情。综上所述,在实际运维中,主流的异常检测方法应该是无监督的异常检测方法。在拿到数据之前我想到用基于预测或者是基于重构的异常检测方法。
然而这两种无监督的异常检测方法在本次比赛中注定无法大展身手,原因是:数据集中有些被标注的异常点不是很明显。以上两种异常检测方法,在判断
时候的值
是否为异常时, 都是模型根据历史的信息,预测或者重构出当前时刻点的值
, 然后根据
两点的之间的差距大小来判断是否为异常, 差距越大,越有可能为异常。如下图所示, 1,2两个异常点偏离原来的轨迹交大, 使用预测或者重构出来的
会与
有较大的差距, 所以这两个点能够比较容易地检测出来。但是对于点3这种偏离程度不是很大的情况, 重构出来的
会与
没有很大的差距,这两种无监督的异常检测方法对它无能为力。
在大佬云集的激烈比赛中, 放弃这些难以检测的异常, 无疑是放弃比赛了。既然本次比赛提供了标签信息,而且提供了完整的训练集和测试集, 那我就可以为了提高检测效果而“不择手段”。
在正式开始介绍我的思路之前,先介绍一下我的灵感来源:Deep Support Vector Data Description(Deep SVDD),来自论文Deep One-Class Classification: http://data.bit.uni-bonn.de/publications/ICML2018.pdf
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):
从图中可以看到, 正常样本和异常样本两个簇不是很明显的两个分散的簇。所有的样本的在这个三维空间中排成一条直线。仔细想想也是合理的, 那些在网络看来像是正常又像是异常的点, 就在两簇的中间徘徊。另外,这个图看起来异常样本比正常样本多, 这是为什么呢?其实在正常样本的簇中,许多非常相像的正常样本完全重叠了,所以看起来正常样本少一点;而异常的模式却是多种多样的, 所以在这个空间中它们比较分散,看起来比较多。
不择手段二:调整不同的滑动窗口大小和选取标签的位置。
在实际应用中,异常检测要求及时性。当异常发生时,越快检测出异常越好。所以一般在滑动窗口采样时,都是取窗口的最后一个点的标签作为这个窗口的标签。这样做的话,在检测一个点是否为异常时, 只能根据它本身以及过去的数据来检测。然而在本次比赛中,并不要求及时响应。所以这里我为了提高检检测效果,“不择手段”地引入了未来的数据。根据不同的KPI, 选择不同的滑动窗口大小和标签在窗口里的位置。换一种说法就是:在实际应用中,对于窗口大小为
, 一般是使用窗口内的点组成的序列
,取这个窗口内最后一个点
的标签作为这个窗口的标签;在本次比赛中, 可以不局限于取这个窗口的最后一点的标签作为这个这窗口的标签,只要能够提升模型的检测效果, 我可以取窗口中的任意一个点的标签作为这个窗口的标签。
在调参过程中发现,不同的窗口大小W和不同的窗口内标签所在位置label_idx有不同的效果。在有些KPI上, 通过尝试多种W和label_idx的组合, 训练出多个网络模型。最后检测异常的时候, 综合多个网络模型的检测结果,作为最终的检测结果。
一开始我使用的判断异常的方式非常直观。对于测试集的每一个样本,直接根据它到正常簇和异常簇的哪一簇中心的距离更近,它到哪一簇的中心更近,就把它判定为哪一类。后来发现这种判断方式不够用:对于一些训练集里没有出现过的异常样本, 虽然它距离正常簇中心更近,却比大多数正常样本更加远离正常簇中心。基于以上观察, 判断异常的方式修改为:计算每个测试样本到正常簇中心的距离
, 确定一个阈值
, 当
就判定该样本为异常。
在许多时间序列异常检测论文以及一些时间序列异常检测比赛中, 常常会出现这样一种修改过的F1 score的计算方式:如果异常检测算法在该连续异常区间开始后的 不晚于T 个时间点内检测到了该连续异常区间,就认为此异常检测算法成功地检测到了整段连续异常区间,因此该异常区间内的每一个异常点都算作一次true positive(TP);否则,该连续异常区间内的每一个异常点都算作一次false negative(FN)。就举下图为例,第一行是真实标签。如果模型输出
判定为异常, 那么得到的结果是point-wise alert这一行的结果。采用以上的评分方法, 经过调整之后的结果为adjusted alert这一行的结果。
这样的评分方式是有实际意义的。在实际的运维过程中, 一旦异常检测算法检测出异常并产生警报, 这时就有人工介入, 那么后续发生的异常也就有人工干预了,那就相当于这一段连续的异常,都有人工介入处理。
恰好我的模型在一些连续的异常区间中, 常常只能检测出其中一个异常点,遗憾的是本次比赛没有采用以上这种评分方式。我只能自力更生, 设定一个规则:对于算法检测出来的异常点的前一个点或者后一个点,可以认为它们很大概率也是异常点, 可以适当降低阈值。使用这种规则, 我的算法检测能够检测出更多连续的异常。
对异常点进行放缩操作。在一个滑动窗口中, 固定正常点的数值, 对异常点的数值进行放缩。通过这种操作,获得了更丰富的异常类型, 方便模型的测试集中找出更多的异常点。
标准化: zscore。公式为:
在本次比赛过程中,我使用的方法要调整的参数比较多, 还好有主办方提供的NAIE平台, 不仅提供在线编程神器webIDE, 还可以提交多个训练任务,大大减轻了我在比赛中调参的负担。此外, 非常感谢希旭哥、素颜姐、小爱姐众多工作人员提供的帮助, 总是第一时间为参赛选手答疑解惑。
最后,祝华为云和NAIE越办越好, 欣欣向荣, 日升月恒!
推荐
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
评论(0)