時間序列分段法

野生胡蘿蔔發表於2020-10-17

文獻參考:An Online Algorithm for Segmenting Time Series

一、時間序列分段優點:

時間序列分段是指將長度為n的時間序列T用K條直線來擬合。因為K通常比n小得多,這種表示方式使得資料的儲存、傳輸和計算更加高效。具體來說,在資料探勘中,分段演算法可以:

  • 支援快速精確類似搜尋;
  • 支援新的距離度量,包括模糊查詢,加權查詢,多解析度查詢,動態時間扭曲和相關性反饋等;
  • 支援並行挖掘文字和時間序列;
  • 支援新的聚類和分類演算法; 支援改變點檢測

二、分段演算法總體思路

  1. 給定一個時間序列T,僅用K個片段產生最佳表示。
  2. 給定一個時間序列T,生成最佳表示,使任何段的最大誤差不超過某個使用者指定的閾值max_error
  3. 給定一個時間序列T,生成最好的表示,使所有片段的綜合誤差小於某個使用者指定的閾值total_max_error。

並非所有演算法都滿足以上條件。

三、時序分割的三種主要方法

  1. 滑動視窗(Sliding Windows):滑動視窗演算法的工作原理是在時間序列的第一個資料點上錨定一個潛在段的左點,然後試圖通過增加更長的段來接近資料的右點。在某個點i,如果潛在段的誤差大於使用者指定的閾值,則將錨點到i-1的子序列轉換為段。然後將錨點移動到位置i,重複這個過程,直到整個時間序列轉化為分段。

虛擬碼如下:
在這裡插入圖片描述

python實現:

def Sliding_Window(T, max_error, seq_range=None):
    if not seq_range:
        seq_range = (0, len(T) - 1)
    start = seq_range[0]
    end = start
    result = T
    while end < seq_range[1]:
        end += 1
        if calculate_error(T, (start, end)) <= max_error:
            result = T[start:end + 1]
        else:
            break
    if end == seq_range[1]:
        return [result]
    else:
        return [result] + Sliding_Window(T, max_error, (end, seq_range[1]))

這是最簡單的滑動視窗方法,實際應用中可以調整視窗大小,增加變數i的“跳躍長度為k”而不是1。對於k = 15 ,演算法速度快15倍,對有些資料輸出影響很小。並且由於殘差是累積增長的,隨著資料點的增加而不減少,因此不需要每一次都逐步計算i從2到最後選擇點的殘差,可以一開始設定 i = s,如果計算出來的誤差小於 max_error,再考慮增加 i。否則,就開始減少i,直到測量的誤差小於max_error。如果段的平均長度相對於其長度的標準偏差較大,這個優化可以大大提高效率。殘差的單調非減少性質也允許針對段的長度進行二分搜尋。
滑動視窗在有噪聲的情況下表現較好,但有些時候表現較差。
Park等人(2001)建議修改演算法,建立“單調變化”的分段。也就是說每個片段的點都由 t 1 ⩽ t 2 ⩽ . . . ⩽ t n t_{1}\leqslant t_{2}\leqslant ...\leqslant t_{n} t1t2...tn或者 t 1 ≥ t 2 ≥ . . . ⩾ t n t_{1}\geq t_{2}\geq ...\geqslant t_{n} t1t2...tn的順序組成。該方法在光滑的合成資料集上取得了良好的效果。但是在真實世界的有噪音資料集上,擬合結果是嚴重的過度碎片。

  1. 自上而下(Top-Down):對時間序列進行遞迴分割,直到滿足一定的停止條件。自頂向下的演算法通過考慮時間序列的每一種可能的劃分,並將其分割到最佳位置。然後測試這兩個子節,看它們的近似誤差是否低於某個使用者指定的閾值。如果沒有,演算法遞迴地繼續分割子序列,直到所有分段的逼近誤差都低於閾值。

虛擬碼如下:

在這裡插入圖片描述
python實現:

def improvement_splitting_here(T, i, seq_range):
    return calculate_error(T, (seq_range[0], i)) + calculate_error(T, (i + 1, seq_range[1]))


def Top_Down(T, max_error, seq_range=None):
    if not seq_range:
        seq_range = (0, len(T) - 1)
    best_so_far = float('inf')
    break_point = float('inf')
    for i in range(seq_range[0] + 1, seq_range[1]):
        improvement_in_approximation = improvement_splitting_here(T, i, seq_range)
        if improvement_in_approximation < best_so_far:
            break_point = i
            best_so_far = improvement_in_approximation
    left_error = calculate_error(T, (seq_range[0], break_point))
    left_seg = T[seq_range[0]:break_point + 1]
    right_error = calculate_error(T, (break_point+1, seq_range[1]))
    right_seg = T[break_point+1:seq_range[1]+1]
    if left_error > max_error:
        segleft = Top_Down(T, max_error, (seq_range[0], break_point))
    else:
        segleft = [left_seg]
    if right_error > max_error:
        segright = Top_Down(T, max_error, (break_point, seq_range[1]))
    else:
        segright = [right_seg]
    return segleft + segright

自上而下演算法應用很廣。Park等人對它進行了改進,首先掃描整個資料集,標記出波峰和波谷,用這些極值點建立初步的分段,然後對每一段使用自上而下演算法,以防個別段上的誤差極高。這種方法在平滑資料集上表現極好,但是在現實資料中容易出現過於分散的擬合情況。
Lavrenko等人使用自頂向下的演算法來支援文字和時間序列的併發挖掘。他們試圖發現新聞故事對金融市場的影響。他們的演算法包含了一些有趣的修改,包括一個基於t檢驗的新奇的停止標準。
Smyth and Ge重新表示該演算法以支援基於隱馬爾科夫模型的變化點檢測方法和模式匹配方法。

  1. 自下而上( Bottom-Up:):首先建立時間序列的最佳可能擬合直線,即用n/2個片段來擬合長度為n的時間序列。然後計算合併每隊相鄰片段的代價,然後迭代合併代價最低的片段,直到滿足停止條件。

演算法虛擬碼如下:

在這裡插入圖片描述
python程式碼:

def Bottom_Up(T, max_error):
    Seg_TS = []
    seg = []
    merge_cost = []
    for i in range(0, len(T), 2):
        Seg_TS += [T[i:i + 2]]
        seg.append((i, i + 1))
    for i in range(0, len(Seg_TS) - 1):
        merge_cost.insert(i, calculate_error(T, (seg[i][0], seg[i + 1][1])))
    while min(merge_cost) < max_error:
        index = merge_cost.index(min(merge_cost))
        Seg_TS[index] = Seg_TS[index] + Seg_TS[index + 1]
        seg[index] = (seg[index][0], seg[index + 1][1])
        del Seg_TS[index + 1]
        del seg[index + 1]
        if index > 1:
            merge_cost[index - 1] = calculate_error(T, (seg[index - 1][0], seg[index][1]))
        if index + 1 < len(merge_cost):
            merge_cost[index] = calculate_error(T, (seg[index][0], seg[index + 1][1]))
            del merge_cost[index + 1]
        else:
            del merge_cost[index]
    return Seg_TS

在這裡插入圖片描述

一般有兩種方法得到近似擬合直線——線性插值和線性迴歸。這兩種技術如圖所示。線性插值趨向於緊密地對齊連續段的端點,給分段逼近一個“平滑”的外觀。相反,線性迴歸在一些資料集上可能產生非常脫節的外觀。線性插值的美學優勢和較低的計算複雜度使其成為計算機圖形應用[9]的首選技術。但是,就歐氏距離而言,擬合直線的質量通常不如迴歸方法。我在寫程式碼的時候採用的最小二乘法擬合直線,誤差採用平方和,但在實際應用過程中,擬合方法和誤差都是可以靈活調整的。

def calculate_error(st, seq_range):
    x = arange(seq_range[0], seq_range[1] + 1)
    y = array(st[seq_range[0]:seq_range[1] + 1])
    A = ones((len(x), 2), float)
    A[:, 0] = x
    # 返回迴歸係數、殘差平方和、自變數X的秩、X的奇異值
    (p, residuals, ranks, s) = lstsq(A, y, rcond=None)
    try:
        error = residuals[0]
    except IndexError:
        error = 0.0
    return error

四、主要分段演算法特徵比較

滑動視窗演算法的主要問題是它不能向前看,缺少離線(批處理)演算法的全域性檢視。自下而上的和自頂向下的方法可以產生更好的結果,但是這種方法是離線的,並且需要掃描整個資料集。這在資料探勘上下文中是不切實際的,甚至是不可行的,因為資料的順序是兆兆位元組,或者以連續的流到達。因此,我們引入了一種新穎的方法,在此方法中,我們捕獲了滑動視窗的線上特性,同時保留了自底向上的優勢。我們稱我們的新演算法SWAB(滑動視窗和自下而上)。
SWAB演算法保持一個大小為W的快取區。初始化快取區使得有足夠的資料來建立5~6個分段。對快取區的資料應用Bottom_Up演算法,並報告最左邊的段。從快取區中刪除與報告段對應的資料,然後讀取更多的資料。讀入資料點的數量取決於傳入資料的結構。這個過程由Best_Line函式完成,該函式就是經典的Sliding_Window演算法。這些點被放入快取區,然後再次應用Bottom_Up演算法。只要資料到達(可能是永遠的),就會重複對緩衝區應用自底向上的過程,報告最左邊的段,並讀取下一個“最適合”的子序列。
Best_Line函式使用(相對較差的)滑動視窗查詢與單個段對應的資料,並將其提供給緩衝區。當資料在緩衝區中移動時,(相對較好的)自底向上演算法有機會細化分割,因為它有資料的“半全域性”檢視。當資料從緩衝區中彈出時,分割斷點通常與自底向上的批處理版本所選擇的斷點相同。
使用緩衝區允許我們獲得自底向上的資料集的“半全域性”檢視。但是,重要的是要在視窗大小上設定上限和下限。快取區允許任意增長,使我們的演算法可以迴歸到純Bottom_Up,但是快取區較小將使它惡化為Sliding_Window,可能出現過多的碎片。在我們的演算法中,我們使用了初始緩衝區的一半和兩倍作為上界和下界。
我們的演算法可以看作是出於極端Sliding_Window和Bottom_Up演算法之間的綜合體。令人驚訝的結果(如下所示)是,通過允許緩衝區只包含通常一個分段的5或6倍的資料,該演算法產生的結果與自底向上基本相同,但能夠處理永不終止的資料流。我們的新演算法只需要一個小的,恆定的記憶體量,時間複雜度是一個小的常數因子比標準的自底向上演算法。
在這裡插入圖片描述

相關文章