在线检测一般时间序列的异常值的简单算法


88

我正在处理大量时间序列。这些时间序列基本上是每10分钟进行一次网络测量,其中一些是周期性的(即带宽),而另一些则不是(即路由流量)。

我想要一种用于进行在线“异常值检测”的简单算法。基本上,我想将每个时间序列的整个历史数据保存在内存中(或保存在磁盘上),并且我想检测实时场景中的任何异常值(每次捕获一个新样本)。实现这些结果的最佳方法是什么?

我目前正在使用移动平均线来消除一些噪音,但是接下来呢?对整个数据集而言,诸如标准差,疯狂……之类的简单事情无法很好地工作(我不能假设时间序列是固定的),我想要更“准确”的东西,最好是一个黑匣子,例如:

double outlier_detection(double *向量,double值);

其中vector是包含历史数据的double数组,返回值是新样本“ value”的异常得分。


1
只是为了清楚起见,这里的一对SO原题:stackoverflow.com/questions/3390458/...
马特·帕克

1
我认为我们应该鼓励张贴者在问题的一部分中发布链接,如果他们在另一个SE网站上发布了相同的问题。

是的,您完全正确。下次,我将提到该消息是交叉发布的。
gianluca

我还建议您查看页面右侧的其他“相关”链接。这是一个很普遍的问题,以前它已经出现在各种各样的问题中。如果他们不满意,最好更新您有关具体情况的问题。
安迪W

好收获,@安迪!让我们将这个问题与另一个问题合并。
ub

Answers:


75

这是一个简单的R函数,可以找到时间序列离群值(并可以选择在图表中显示它们)。它将处理季节性和非季节性的时间序列。基本思想是找到趋势和季节成分的可靠估计值并将其减去。然后在残差中找到异常值。残留离群值的测试与标准箱线图的测试相同-假设在上四分位数和下四分位数的上方或下方大于1.5IQR的点被视为离群值。高于/低于这些阈值的IQR数将作为异常值“得分”返回。因此,分数可以是任何正数,对于非异常值,分数将为零。

我意识到您没有在R中实现此功能,但是我经常发现R函数是一个很好的起点。然后的任务是将其翻译成所需的任何语言。

tsoutliers <- function(x,plot=FALSE)
{
    x <- as.ts(x)
    if(frequency(x)>1)
        resid <- stl(x,s.window="periodic",robust=TRUE)$time.series[,3]
    else
    {
        tt <- 1:length(x)
        resid <- residuals(loess(x ~ tt))
    }
    resid.q <- quantile(resid,prob=c(0.25,0.75))
    iqr <- diff(resid.q)
    limits <- resid.q + 1.5*iqr*c(-1,1)
    score <- abs(pmin((resid-limits[1])/iqr,0) + pmax((resid - limits[2])/iqr,0))
    if(plot)
    {
        plot(x)
        x2 <- ts(rep(NA,length(x)))
        x2[score>0] <- x[score>0]
        tsp(x2) <- tsp(x)
        points(x2,pch=19,col="red")
        return(invisible(score))
    }
    else
        return(score)
}

向我+1,非常好。那么> 1.5 X四分位间距是时间相关序列离群值的共识定义吗?拥有与比例无关的参考会很好。
道格

离群值测试是针对残差的,因此希望时间相关性较小。我不了解共识,但是箱线图通常用于离群值检测,并且看起来工作得相当不错。如果有人想使该功能更高级,则有更好的方法。
罗伯·海恩德曼

真的非常感谢您的帮助,非常感谢。我现在工作很忙,但是我将尽快测试像您这样的方法,我将返回关于此问题的最终考虑。一个唯一的想法:在您的函数中,从我的角度来看,我必须手动指定时间序列的频率(在构建时),并且仅当频率大于1时才考虑季节性分量。是否有健壮的方法自动处理?
gianluca

1
是的,我假设频率是已知的并已指定。有一些方法可以自动估算频率,但这会使功能复杂化。如果您需要估算频率,请尝试询问一个单独的问题-我可能会提供答案!但是它需要的空间比我在评论中提供的空间还要大。
罗布·海恩德曼

2
@Marcin,我建议您自己刺一口。也许将您的解决方案粘贴到gist.github.com上,并在完成后发布一个SO问题,以让其他人检查您的工作?
肯·威廉姆斯

27

一个好的解决方案将包含以下几种成分:

  • 使用能移动的光滑窗户来消除不平稳感。

  • 重新表达原始数据,以便相对于平滑的残差近似对称地分布。考虑到数据的性质,它们的平方根或对数可能会给出对称残差。

  • 将控制图方法或至少控制图思维应用于残差。

就最后一个而言,控制图思维表明“常规”阈值(如四分位数以外的2 SD或IQR的1.5倍)效果不佳,因为它们触发了太多错误的失控信号。人们通常在控制图工作中使用3 SD,而IQR超出四分位数的2.5倍(甚至3倍)将是一个很好的起点。

我或多或少地概述了罗伯·海德曼(Rob Hyndman)解决方案的性质,同时又增加了两个要点:可能需要重新表达数据,以及在发出异常值时要更加保守。我不确定Loess是否适合用于在线检测器,因为它在端点上无法很好地工作。取而代之的是,您可以使用像移动中值过滤器一样简单的方法(例如在Tukey的抗性平滑中)。如果离群值不是突发的,则可以使用一个狭窄的窗口(也许有5个数据点,只有在5个组中出现3个或更多离群值时才会崩溃)。

完成分析以确定数据的良好重新表达后,就不太可能需要更改重新表达。因此,您的在线检测器实际上只需要引用最新值(最新窗口),因为它根本不会使用早期数据。如果您的时间序列真的很长,则可以进一步分析自相关和季节性(例如每天或每周反复出现的波动)以改进此过程。


3
这是实际分析的非凡答案。从来没有想到需要在四分位数之外尝试3 IQR。
约翰·罗伯逊

3
@ John,1.5 IQR是Tukey在箱形图中最长的晶须的最初建议,而3 IQR是他将点标记为“远离群值”的建议(对60年代流行短语的即兴表达)。这已内置到许多箱线图算法中。该建议在Hoaglin,Mosteller和Tukey的《理解稳健和探索性数据分析》中
ub

这证实了我一直在尝试分析的时间序列数据。窗口平均值和窗口标准偏差。((x-avg)/ sd)> 3似乎是我要标记为离群值的点。至少警告异常值,我将高于10 sd的值标记为极端错误异常值。我遇到的问题是理想的窗口长度是多少?我正在玩4-8个数据点之间的任何东西。
2013年

1
@Neo最好的选择是对数据的一部分进行实验,并通过对其余数据的测试来确认您的结论。您也可以进行更正式的交叉验证(但由于所有值的相互依赖性,因此对于时间序列数据需要特别注意)。
ub

17

(此答案回答了“ 检测未决事件”中的一个重复问题(现已关闭),该问题以图形形式显示了一些数据。)


离群值检测取决于数据的性质以及您愿意对它们假设的内容。 通用方法依赖可靠的统计信息。这种方法的精神在于以不受任何异常值影响的方式表征大量数据,然后指向不适合该表征的任何单个值。

由于这是一个时间序列,因此增加了持续不断地需要(重新)检测异常值的复杂性。如果在系列展开时要这样做,那么我们只能使用较旧的数据进行检测,而不能使用将来的数据!而且,作为对许多重复测试的保护,我们希望使用一种假阳性率非常低的方法。

这些考虑因素建议对数据进行简单,健壮的移动窗口离群值测试。有很多可能性,但是一种简单,容易理解和容易实现的方法是基于运行中的MAD:相对于中位数的中位数绝对偏差。这是一种非常稳健的衡量数据变化的方法,类似于标准偏差。外围将是几个MAD或大于中位数。

RX=1个2ññ=1150ÿ

# Parameters to tune to the circumstances:
window <- 30
threshold <- 5

# An upper threshold ("ut") calculation based on the MAD:
library(zoo) # rollapply()
ut <- function(x) {m = median(x); median(x) + threshold * median(abs(x - m))}
z <- rollapply(zoo(y), window, ut, align="right")
z <- c(rep(z[1], window-1), z) # Use z[1] throughout the initial period
outliers <- y > z

# Graph the data, show the ut() cutoffs, and mark the outliers:
plot(x, y, type="l", lwd=2, col="#E00000", ylim=c(0, 20000))
lines(x, z, col="Gray")
points(x[outliers], y[outliers], pch=19)

应用于问题所示的红色曲线之类的数据集,它会产生以下结果:

情节

数据显示为红色,中位数+ 5 * MAD阈值的30天窗口显示为灰色,而异常值(仅是灰色曲线上方的那些数据值)显示为黑色。

(只能从初始窗口的末尾开始计算阈值。对于该初始窗口内的所有数据,将使用第一个阈值:这就是为什么灰色曲线在x = 0和x = 30之间平坦的原因。)

更改参数的影响是:(a)增加的值window会趋于平滑灰色曲线,(b)增大threshold会增加灰色曲线。知道这一点,就可以获取数据的初始片段,并快速确定参数值,以最佳地将外围峰与其余数据区分开。将这些参数值应用于检查其余数据。如果图表显示该方法随着时间的推移而恶化,则意味着数据的性质正在改变,并且可能需要重新调整参数。

请注意,该方法对数据的假设很少:它们不必以正态分布;他们不需要表现出任何周期性;他们甚至不必非负数。所有这假设是,数据相当类似的方式随着时间的推移表现和外围峰值明显低于其余数据更高。


如果有人想尝试(或将其他解决方案与此处提供的解决方案进行比较),这是我用来生成问题中所示数据的代码。

n.length <- 1150
cycle.a <- 11
cycle.b <- 365/12
amp.a <- 800
amp.b <- 8000

set.seed(17)
x <- 1:n.length
baseline <- (1/2) * amp.a * (1 + sin(x * 2*pi / cycle.a)) * rgamma(n.length, 40, scale=1/40)
peaks <- rbinom(n.length, 1,  exp(2*(-1 + sin(((1 + x/2)^(1/5) / (1 + n.length/2)^(1/5))*x * 2*pi / cycle.b))*cycle.b))
y <- peaks * rgamma(n.length, 20, scale=amp.b/20) + baseline

这是一个非常有趣的解决方案,我很感激我可以不用R来实现它(仅在Web应用程序中使用纯JavaScript)。谢谢!
hgoebl 2015年

15

如果您担心采用任何特定方法的假设,一种方法是训练许多学习者使用不同的信号,然后使用集成方法并汇总学习者的“票数”以进行离群值分类。

顺便说一句,这可能值得一读或略读,因为它引用了解决该问题的几种方法。


5

我猜想复杂的时间序列模型将不适用于您,因为使用此方法检测异常值需要花费时间。因此,这是一种解决方法:

  1. 首先,根据对历史数据的手动分析来建立一年的基准“正常”流量模式,这些历史数据会说明一天中的时间,工作日与周末,一年中的月份等。

  2. 使用此基线以及一些简单的机制(例如,Carlos建议的移动平均值)来检测离群值。

您可能还需要查看统计过程控制文献中的一些想法。


1
是的,这正是我正在做的事情:直到现在,我手动将信号分成多个周期,以便可以为每个周期定义一个置信区间,信号应该在该区间内保持稳定,因此我可以使用诸如作为标准偏差,...真正的问题是我无法为所有必须分析的信号确定预期的模式,这就是为什么我正在寻找更智能的东西。
gianluca

这是一个主意:步骤1:根据历史数据一次实现和估算通用时间序列模型。这可以脱机完成。步骤2:使用结果模型来检测离群值。步骤3:以某个频率(也许每月一次?)重新校准时间序列模型(可以离线进行),以使您的步骤2检测异常值不会与当前流量模式相差太大。这对您的情况有用吗?

是的,这可能有效。我正在考虑一种类似的方法(每周重新计算基线,如果要分析数百个单变量时间序列,则这可能会占用大量CPU)。顺便说一句,真正困难的问题是“考虑噪声,趋势估计和季节性因素,用于建模完全通用信号的最佳黑盒式算法是什么?”。AFAIK,文献中的每种方法都需要一个非常困难的“参数调整”阶段,而我发现的唯一一种自动方法是Hyndman的ARIMA模型(robjhyndman.com/software/forecast)。我想念什么吗?
gianluca

请记住,我不太懒惰地研究这些参数,关键是这些值需要根据信号的预期模式进行设置,在我的情况下,我无法做任何假设。
gianluca

ARIMA模型是经典的时间序列模型,可用于拟合时间序列数据。我鼓励您探索ARIMA模型的应用。您可以等待Rob上网,也许他会提出一些想法。

5

季节性调整数据,使正常的一天看起来更接近平稳。您可以采用今天下午5:00的样本,然后减去或除以下午5:00的前30天的平均值。然后查看过去的N个标准差(使用预先调整的数据进行测量),得出异常值。可以针对每周和每天的“季节”分别进行此操作。


同样,如果信号应该具有这样的季节性,则此方法效果很好,但是如果我使用完全不同的时间序列(即一段时间内的平均TCP往返时间),则此方法将不起作用(因为这样会更好)使用包含历史数据的滑动窗口以简单的整体均值和标准差处理该数据)。
gianluca

1
除非您愿意实现一个通用的时间序列模型(这会带来延迟方面的弊端),否则我会感到悲观的是,您将找到一个通用的实现,同时又足够简单,可以适用于各种时间序列。

另一个评论:我知道一个很好的答案可能是“所以您可以估计信号的周期性,并根据信号来决定要使用的算法”,但是我没有找到真正解决这个问题的好方法(我玩过使用DFT进行频谱分析并使用自相关函数进行时间分析,但我的时间序列包含很多噪声,并且这种方法在大多数情况下会给出一些疯狂的结果)
gianluca

对您的最后一条评论的评论:这就是为什么我正在寻找一种更通用的方法,但是我需要一种“黑匣子”,因为我无法对所分析的信号做出任何假设,因此无法创建“用于学习算法的最佳参数集”。
gianluca

@gianluca暗示了潜在的ARIMA结构可以掩盖异常。错误的配方可能会导致诸如一天中的小时,星期几,假日影响等变量,也可能掩盖异常。答案很明确,您需要有一个很好的工具才能有效检测异常。用培根的话说:“对于那些了解自然之道的人来说,更容易注意到她的偏差,而另一方面,任何知道自然之道的人都会更准确地描述其行为。”
IrishStat 2012年


2

频谱分析可检测固定时间序列中的周期性。我建议您将基于频谱密度估计的频域方法作为第一步。

如果对于某些时期而言,不规则性意味着比该期间的典型峰值高得多的峰值,则具有此类不规则性的序列将不会平稳,并且频谱反响将不适用。但是,假设您已确定存在不规律性的时期,则您应该能够大致确定正常峰高,然后可以在高于该平均值的某个水平上设置阈值以指定不规律性情况。


2
您能解释一下该解决方案如何检测“局部不规则性”吗?提出一个可行的例子将非常有帮助。(说实话,我建议您这样做是因为在进行这样的练习时,我相信您会发现您的建议对于检测异常值无效。但是我可能是错的...)
whuber

1
@whuber光谱分析将仅识别所有峰的位置。下一步将是使用正弦和余弦项拟合yime序列模型,其频率由频谱分析确定,振幅由数据估计。如果不规则性意味着峰值具有非常高的振幅,那么我认为振幅的阈值将是适当的。如果局部不规则性意味着在一段时间内振幅有时会比其他振幅大得多,则该序列不是平稳的,并且频谱分析将不适用。
迈克尔·切尔尼克

1
我没有得出关于缺乏平稳性的结论。例如,规则正弦波波形与明显的泊松点过程之和是固定的,但不会表现出您想要的任何周期性。但是,您会在周期图中发现一些强峰值,但它们不会告诉您与泊松过程组件引入的不规则数据峰值相关的任何信息。
ub

1
平稳的时间序列具有恒定的平均值。如果周期性成分的峰值可以随时间变化,则可能会导致均值随时间变化,因此序列将是不稳定的。
Michael Chernick

2

由于它是时间序列数据,因此简单的指数过滤器http://en.wikipedia.org/wiki/Exponential_smoothing将使数据平滑。这是一个很好的过滤器,因为您不需要累积旧的数据点。将每个新平滑的数据值与其平滑的值进行比较。一旦偏差超过某个预定阈值(取决于您认为数据中的异常值),就可以轻松检测到异常值。

在CI中将对实时16位样本执行以下操作(我相信可以在此处找到<解释-https: //dsp.stackexchange.com/questions/378/what-is-the-best-first-order -iir逼近一个移动平均滤波器 >)

#define BITS2 2     //< This is roughly = log2( 1 / alpha ), depending on how smooth you want your data to be

short Simple_Exp_Filter(int new_sample) 
{static int filtered_sample = 0;
long local_sample = sample << 16; /*We assume it is a 16 bit sample */
filtered_sample += (local_sample - filtered_sample) >> BITS2;   
return (short) ((filtered_sample+0x8000) >> 16); //< Round by adding .5 and truncating.   
}


int main()
{
newly_arrived = function_receive_new_sample();
filtered_sample = Simple_Exp_Filter(newly_arrived);
if (abs(newly_arrived - filtered_sample)/newly_arrived > THRESHOLD)
    {
    //AN OUTLIER HAS BEEN FOUND
    }
 return 0;   
}

1

您可以使用最近N次测量的标准偏差(必须选择一个合适的N)。良好的异常分数将是测量值与移动平均值之间的标准差。


谢谢您的答复,但是如果信号表现出很高的季节性(例如,许多网络测量的特征是同时具有每日和每周模式,例如晚上与白天或周末与工作日),该怎么办?在这种情况下,基于标准差的方法将不起作用。
gianluca

例如,如果我每10分钟获取一个新样本,并且对公司的网络带宽使用情况进行异常检测,则基本上在下午6点,此指标将下降(这是预期的总体正常模式),并且在滑动窗口上计算的标准偏差将失败(因为它肯定会触发警报)。同时,如果度量值在下午4点下降(偏离正常基准),则这是一个真正的异常值。
gianluca

1

我要做的是按小时和星期几对测量结果进行分组,并比较其标准偏差。对于假期和夏季/冬季的季节性,仍然不能纠正,但在大多数情况下都是正确的。

缺点是您真的需要收集大约一年的数据才能有足够的数据,以便stddev开始有意义。


谢谢,这正是我要避免的方法(有很多样本作为基线),因为我想要一种真正的反应性方法(例如,在基线1-2周后进行在线检测,可能是“脏”)
gianluca

0

我建议以下方案,该方案应在一天左右的时间内实施:

训练

  • 收集尽可能多的样本以保存在内存中
  • 使用每个属性的标准偏差删除明显的异常值
  • 计算并存储相关矩阵以及每个属性的均值
  • 计算并存储所有样品的马氏距离

计算“异常值”:

对于您要了解其“异常值”的单个样本:

  • 从训练中获取均值,协方差矩阵和马氏距离
  • 计算样品的马氏距离 “ d”
  • 返回“ d”所在的百分位数(使用距训练的马氏距离)

那将是您的异常值:100%是极端异常值。


PS。在计算马氏距离时,请使用相关矩阵,而不要使用协方差矩阵。如果样本测量的单位和数量有所不同,则此方法更可靠。


0

对于必须快速计算离群值的情况,可以使用Rob Hyndman和Mahito Sugiyama(https://github.com/BorgwardtLab/sampling-outlier-detection,library(spoutlier),函数qsp)的思想进行计算异常值如下:

library(spoutlier)
rapidtsoutliers <- function(x,plot=FALSE,seed=123)
{
    set.seed(seed)
    x <- as.numeric(x)
    tt <- 1:length(x)
    qspscore <- qsp(x)
    limit <- quantile(qspscore,prob=c(0.95))
    score <- pmax((qspscore - limit),0)
    if(plot)
    {
        plot(x,type="l")
        x2 <- ts(rep(NA,length(x)))
        x2[score>0] <- x[score>0]
        tsp(x2) <- tsp(x)
        points(x2,pch=19,col="red")
        return(invisible(score))
    }
    else
        return(score)
}

0

异常检测需要构造描述期望的方程。介入检测在非因果关系设置中均可用。如果有像价格这样的预测变量系列,那么事情可能会变得有些复杂。此处的其他响应似乎未考虑可归因于用户指定的预测变量序列的可分配原因,例如价格,因此可能存在缺陷。出售的数量很可能取决于价格,也许是以前的价格,或者过去的出售数量。可在https://pdfs.semanticscholar.org/09c4/ba8dd3cc88289caf18d71e8985bdd11ad21c.pdf中找到异常检测的基础(脉冲,季节脉冲,水平移动和本地时间趋势)。


链接无效,请您解决。谢谢
Pankaj Joshi

完成..................
IrishStat '18
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.