Godaddy(1/5) 时间序列异常检测

欢迎关注公众号:WayneData智能世界

ChangePoint简介

变点和异常值检测是时间序列分析中的重要技术,因为它们可以帮助识别数据中的显著变化或异常情况。时间序列数据通常表现出非平稳性,这意味着数据的统计属性随时间变化。这些变化可能是由于各种因素引起的,如基本趋势的变化、数据分布的变化或罕见事件或异常的发生。变点检测可以帮助识别这些变化发生的时间,并提供有关变化根本原因的见解。本文的主要目的是尽我所能识别时间序列中的所有异常情况。

据我所知,时间序列当中存在的异常值如下几种情况

  • Additive -(AO)加性异常值不会持续存在。顾名思义,加性异常值只影响异常值发生的时间的观测值。因此,加性异常值对未来的预测没有影响,可以理解为是一种孤立事件。

  • Innovational -(IO)这种类型的异常值不仅对 X(T) 造成干扰,还会影响时间 T 之后序列中所有的观测值。可以理解为干预性异常值,一种脉冲性冲击。

  • Level shift -(LS)它们的作用是在异常值发生的时间点开始提高或降低系列的均值。均值的变化是突然且永久性的。可以使用阶跃函数函数来建模,其幅度等于omega参数。

  • Transient -(TS)这种类型的异常值在时刻 T 发生时会带有某种初始影响,然后根据衰减因子的大小,在一段时间后呈指数衰减,最终归于常态。

  • 趋势变动 - 这种是数据模式上发生的改变,可能没有突然出现的异动,但是整体结构发生了变动。

Additive

这是一种加性的异常,更像是一过性的变化,没有对周围数据产生影响,一般情况下,这种情况一般直接删除该段数据或者用周围的数据进行填充。

Innovational

这是一种类似冲击的异常,对数据产生波动也可能是趋势的影响。图中在黄线之后数据波动幅度变大,均值也些许提升。在业务场景中,更像是公司政策亦或者政府政策产生某种持续性的影响。

Level shift

这是一种类似跳跃的水平移位异常且是多次移位,时间序列被分为4个不同均值水平的部分。每个部分的开头对于前一个时间段来说都是异常值。这些异常值不像典型的异常点,而是当前值跳跃并维持一段时间的某个水平。这种情况使得历史数据对将来预测的参考意义降低,因为该部分看起来更像来自另一个序列而不是它自己。因此,模式学习变得困难。

Transient

这种变化非常类似双11,一种营销活动产生的短时间影响,图中在时间节点11开始突增,在17左右慢慢回落到常态。很显然,如果从历史(这段数据时间范围内)中找经验很难预测到这段变化。

GoDaddy异常检测

前面我们分析了时间序列异常值的类型,与可能存在的影响,接下来我们将使用Godaddy竞赛数据进行异常检测。

比赛的背景

比赛主办方GoDaddy是全球最大的企业家服务平台。他们的使命是通过为全球超过2000万客户和所有企业家提供所需的所有帮助和工具,帮助他们在网络上成长。这个竞赛的目标是预测一个给定地区每月的微型企业密度。需要使用美国县级数据训练出一个准确的模型。

我们将从以下几个方面,进行异常检测。

  • 基于差分的LOF - 这个是自定义的检测方法,能够一定程度上检测到LS,IO,AO,甚至TC。缺点是无法判定具体异常的类型。

  • chow test - 能够进行趋势改变的检测,进行结构性变动的检测。缺点是需要自定义窗口进行检测,同时也无法检测出所有的结构性变动

  • 基于ARIMA的检测 - 优点是效率高,相对精准,但是存在使用门槛,需要进行其他的结合进行具体异常类型的判定

  • 其他

基于差分的LOF

以下检测方法是一种自定义的异常检测方法。它使用LOF检测差分序列中的异常值并将其定位。此方法可以检测各种异常,包括IO、AO、LS、TC等。但是,定位可能存在一些不准确性,需要与其他方法结合使用以获得更精确的结果。下面是我自定义函数CusOulierDet,输入是时间序列的list,输出是,所有的断点checkpoint以及根据断点切割出来的片段segment

def CusOulierDet(ts, threshold):
    """
    This function detects outliers in a time series and splits it into multiple segments         for further processing.
    :param ts: The time series data.
    :param threshold: The threshold for the outlier score. Default is -3.5.
    :return: A list of all segments and a list of the split points.
    """
    # Calculate the differences between consecutive values in the time series
    differences = [ts[i+1] - ts[i] for i in range(len(ts)-1)]
    # Convert the differences into a two-dimensional array
    X = [[val] for val in differences]

    # Create the Local Outlier Factor (LOF) model and set hyperparameters and algorithm parameters
    lof = LocalOutlierFactor(n_neighbors=20, contamination=0.05, novelty=True)

    # Train the model and get the outlier scores for each data point
    lof.fit(X)
    scores = lof.negative_outlier_factor_

    # Identify the positions of the outliers
    anomalies = []
    for i, score in enumerate(scores):
        if score < threshold:
            anomalies.append(i+1)

    # If there are no outliers, return the original time series and an empty list of split points
    if not anomalies:
        return [ts], []

    # Split the original time series into segments based on the positions of the outliers and record the split points
    segments = []
    split_points = []
    start_idx = 0
    for anomaly in anomalies:
        segments.append(ts[start_idx:anomaly])
        split_points.append(anomaly)
        start_idx = anomaly

    # Add the final segment to the list of segments
    segments.append(ts[start_idx:])

    # Return the list of segments and the list of split points
    return segments, split_points

从上面结果来看,切割变点特别是LF,如序列cfip(县ID)17055,存在两次跳跃,13219存在一次跳跃。

chow test

Chow test是一种统计检验方法,用于检验一个线性回归模型中,是否存在结构性断点或者说是参数在断点处发生了变化。在Chow test中,将数据集划分为两个或多个子样本,然后估计每个子样本中的回归模型,并计算总体回归模型与子样本回归模型之间的差异。如果总体回归模型与子样本回归模型之间存在显著差异,则表明存在结构性断点。

Chow test通常用于时间序列分析、面板数据分析等领域,可以用于检验某个时间点或者某个事件引起了模型参数的突变。它的优点是易于实现,但缺点是需要对数据进行分割,而且在断点位置不清楚时,可能会产生误判。下面构建基础的chowtest函数,输入是时间x,序列值y,以及选择检测点checkpoint,输出时检测显著性P值

def chow_test(y, x, breakpoint):
    # Add a column for the intercept
    x = sm.add_constant(x)

    # Fit a linear regression model using OLS
    model_full = sm.OLS(y, x)
    results_full = model_full.fit()

    # Split the data into two parts
    x_left, x_right = x[:breakpoint], x[breakpoint:]
    y_left, y_right = y[:breakpoint], y[breakpoint:]

    # Fit linear regression models for each part
    model_left = sm.OLS(y_left, x_left)
    model_right = sm.OLS(y_right, x_right)

    # Compute the Chow test statistic and p-value
    chow_num = (results_full.ssr - (model_left.fit().ssr + model_right.fit().ssr)) / 2
    chow_denom = (model_left.fit().ssr + model_right.fit().ssr) / (len(y_left) + len(y_right) - 4)
    chow_stat = chow_num / chow_denom
    p_value = 1 - stsf.cdf(chow_stat, 2, len(y_left) + len(y_right) - 4)
    return p_value

定义窗口进行chow test

side_winds = 7
sample_size = 50
for cfip in np.random.choice(train.cfips.unique(), size=sample_size, replace=False):
#     cfip = 28101
    Candidate_dict = {} 
    threadhold = 0.05
    last_candi = 0
    
    df = train.loc[train.cfips==cfip,'microbusiness_density'].values
    xts = np.arange(len(df))

    for breakpoint in xts[side_winds:-side_winds]:
#         print(f'breakpoint:{breakpoint}')
        p_value = chow_test(df[breakpoint-side_winds:breakpoint+side_winds],
                            xts[breakpoint-side_winds:breakpoint+side_winds], 
                            side_winds)
#         print(f'p_value:{p_value}')
    #     print(p_value )
        if p_value<threadhold:
            set_candi_condi = (last_candi in Candidate_dict) & (0<breakpoint- last_candi< side_winds)
#             print(f'last_candi:{last_candi}')
    #         print( set_candi_condi,(last_candi in Candidate_dict),(0<breakpoint- last_candi< side_winds) )
            if set_candi_condi:
                if Candidate_dict[last_candi]>p_value:
                    Candidate_dict[breakpoint] = p_value
                    del Candidate_dict[last_candi]
                    last_candi = breakpoint
            else:
                last_candi = breakpoint
                Candidate_dict[breakpoint] = p_value
    Candidate = sorted(Candidate_dict.items(), key=lambda x: x[1])[:3]
    Candidate = sorted([x[0] for x in Candidate if x[1]<0.001])
    print('\n\n Candidate:',Candidate)
    pltObj = MyPlots(x=xts, y=df,barOrLine='line',
                         title='Detecting ouliers as change of trends ',
                         SeriselableName='microbusiness_density',
                         x_label='month', y_label='microbusiness_density',
                         main_title=f'CHOW TEST OF CFIP:{cfip}')
    min_ =  df.min()
    max_ =  df.max()
    pltObj.vlines(Candidate , ymin=min_ ,ymax= max_, linewidth=5,color='#F4B400', alpha=0.5)
    pltObj.show()

结果显示,一定程度上的检测出了结构性变动的节点,如42009,趋势由增到减。同时也发现一些问题,chowtest对于序列中的跳跃异常比较敏感,如18133,将其阶跃而造成的变动,当成一种结构或者趋势的变化。因此很有必要多种手段进行结合。

基于ARIMA的检测

这里我们将ARIMA,设置为无差分情况下的模型,避免其削弱异常的检测。只考虑AR(p)与MA(q)的情况。通过残差对比,找到异动点,进而判定异常。

sample_size = 5
for cfip in np.random.choice(train.cfips.unique(), size=sample_size, replace=False):

    df = train.loc[train.cfips==cfip,'microbusiness_density'].values

    # Fit ARIMA model
    model = ARIMA(df, order=(1, 0, 1))
    model_fit = model.fit()

    # Calculate residuals
    residuals = pd.Series(model_fit.resid).abs()
    forecat = model_fit.forecasts[0]

    # Calculate mean and standard deviation of residuals
    mu = residuals[1:].mean()
    sigma = residuals[1:].std()

    # Detect temporary change
    threshold = mu + 3 * sigma  # Set threshold to 3 sigma
    temporary_change = residuals[residuals > threshold]

    pltObj = MyPlots(x=xts, y=df,barOrLine='line',
                         title='Detecting ouliers as change of trends ',
                         SeriselableName='microbusiness_density',
                         x_label='month', y_label='microbusiness_density',
                         main_title=f'ar_p:{ar_p} ma_q:{ma_q} ARIMA TEST OF CFIP:{cfip}',
                        serise1=forecat,
                        color1 ='#DB4437',
                         lableName1 = 'ARIMA Forecast' 
                    )


    min_ =  df.min()
    max_ =  df.max()
    pltObj.vlines(temporary_change.index , ymin=min_ ,ymax= max_, linewidth=5,color='#F4B400', alpha=0.5)
    pltObj.show() 

在所有工具中,ARIMA结果应该是相对比较满意的情况,多数情况都能进行很好的发现,当然也不乏一些比如,比如18021这种结构性变化情况,ARIMA是不能发现的。

期待

限于时间和篇幅,我现在就先讲到这里。在ARIMA方法中,也许可以通过逐步窗口添加的并预测,然后分析残差,趋势等以及设计一些模拟函数可以准确的定位到异常的类型。最后,除了上述方法外,针对异常检测,您还可以考虑使用RupturesProphet等软件包。当然,为了实现更准确的异常检测,可能需要使用多种方法,例如投票、堆叠或组合。此外,您可以看到这只是我项目进度的1/5。在将来的笔记本中,我将涵盖以下内容:

  • 离线模型设计 - 如何构建有效的离线模型,特别是在这个比赛中;

  • 构建有效特征 - 如何构建有用的特征,比如怎样的周期特征,时间特征;

  • 离线模型设计 - 传统模型和一些测试,具体来说是ARIMA,波动率波形等一些测试

  • 模式识别 - 在数据中识别模式,归类不同的模式分别建模以及利用模式构建特征工程;

  • 善用异常结果 - 如何使用检测到的异常来构建特征。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值