[機器學習] 奇異譜分析(SSA)原理及Python實現

最近做時間序列分析的時候需要用到奇異譜分析,發現網上可以查到的資料很有限,看paper的時候發現大部分也說得有些簡略,所以這裏看完之後總結一下。

  奇異譜分析(Singular Spectrum Analysis, SSA)是一種處理非線性時間序列數據的方法,通過對所要研究的時間序列的軌跡矩陣進行分解、重構等操作,提取出時間序列中的不同成分序列(長期趨勢,季節趨勢,噪聲等),從而進行對時間序列進行分析或去噪並用於其他一些任務。
  奇異譜分析主要包括四個步驟:嵌入——分解——分組——重構。

1. 嵌入

  SSA的分析對象是有限長一維時間序列 [x1,x2,...,xN][x_1, x_2,...,x_N]NN 爲序列長度。首先需要選擇合適的窗口長度 LL 將原始時間序列進行滯後排列得到軌跡矩陣
X=[x1x2xNL+1x2x3xNL+2xLxL+1xN]\boldsymbol{X}=\left[\begin{array}{cccc}{x_{1}} & {x_{2}} & {\cdots}& {x_{N- L+1}} \\ {x_{2}} & {x_{3}} & {\cdots} & {x_{N-L+2}} \\ {\vdots} & {\vdots} & {} & {\vdots} \\ {x_{L}} & {x_{L+1}} & {\cdots} & {x_{N}}\end{array}\right]通常情況下取 L<N/2L<N/2。令 K=NL+1K =N-L+1,則軌跡矩陣X\boldsymbol{X}L×KL\times{K}的矩陣
X=[x1x2xKx2x3xK+1xLxL+1xN]\boldsymbol{X}=\left[\begin{array}{cccc}{x_{1}} & {x_{2}} & {\cdots}& {x_{K}} \\ {x_{2}} & {x_{3}} & {\cdots} & {x_{K+1}} \\ {\vdots} & {\vdots} & {} & {\vdots} \\ {x_{L}} & {x_{L+1}} & {\cdots} & {x_{N}}\end{array}\right]

2. 分解

  接下來對軌跡矩陣進行奇異值分解,注意,這裏是對軌跡矩陣進行SVD分解。看資料的時候就是在奇異值分解這裏困惑了很久,具體來說就是將 X\boldsymbol{X} 分解爲以下形式:
X=UΣVT\boldsymbol{X}=\boldsymbol{U} \boldsymbol{\Sigma} \boldsymbol{V}^{T} 其中 U\boldsymbol{U} 稱爲左矩陣;Σ\boldsymbol{\Sigma} 僅在主對角線上有值,就是奇異值,其他元素均爲零; V\boldsymbol{V} 稱爲右矩陣。此外 UV\boldsymbol{U}、\boldsymbol{V} 均爲單位正交陣,滿足 UUT=I,VVT=I\boldsymbol{U}\boldsymbol{U}^T=\boldsymbol{I}, \boldsymbol{V}\boldsymbol{V}^T=\boldsymbol{I}
  由於直接對軌跡矩陣分解比較困難,因此首先計算軌跡矩陣的協方差矩陣:
S=XXT\boldsymbol{S} = \boldsymbol{X}\boldsymbol{X}^T 接下來對 S\boldsymbol{S} 進行特徵值分解得到特徵值 λ1>λ2>>λL0\lambda_{1}>\lambda_{2}>\cdots>\lambda_{L} \geqslant 0 和對應的特徵向量 U1,U2,,ULU_{1}, U_{2}, \cdots, U_{L}。此時U=[U1,U2,,UL]\boldsymbol{U} =[U_{1}, U_{2}, \cdots, U_{L}]λ1>λ2>>λL0\sqrt{\lambda_{1}}>\sqrt{\lambda_{2}}>\cdots>\sqrt{\lambda_{L}} \geqslant 0爲原序列的奇異譜 。並且有
X=m=1LλmUmVmT,Vm=XTUm/λm,m=1,2,...,L \boldsymbol{X}=\sum_{m=1}^{L} \sqrt{\lambda_{m}} U_{m} V_{m}^{T}, \quad V_{m}=\boldsymbol{X}^{\mathrm{T}} U_{m} / \sqrt{\lambda_{m}}, \quad m=1,2,...,L 這裏 λi\lambda_{i} 對應的特徵向量 UiU_{i} 反映了時間序列的演變型,稱爲時間經驗正交函數(T-EOF)。

實際上python已經提供了奇異值分解的函數np.linalg.svd()可以很方便的計算。關於奇異值分解更詳細的介紹可以看這篇博客

3. 分組

  關於分組,文獻中很常見的敘述是下面這樣:

簡單來說將所有的 LL 個成分分爲 cc 個不相交的組,代表着不同的趨勢成分。這樣接下來選擇主要的成分進行重構得到重構序列。Emmm。。。。這樣介紹可真是太簡潔明瞭導致動手實現的時候真是一臉懵。

  因此在實現的時候參考了另一個版本,這裏將分組和重構放到一塊吧。。。。。這個版本有助於實現但是ran半天ran不清哪裏是分組,被自己菜哭。。。。。。。。。。

4. 重構

  所以這裏接分解步。首先計算遲滯序列 XiX_iUmU_m 上的投影:
aim=XiUm=j=1Lxi+jUm,j,0iNL a_{i}^{m}=\boldsymbol{X}_{i} U_m=\sum_{j=1}^{L} x_{i+j} U_{m,j}, \quad 0\leq{i}\leq{N-L} XiX_i 表示軌跡矩陣 X\boldsymbol{X} 的第 ii 列,aima_{i}^{m}Xi\boldsymbol{X}_{i} 所反映的時間演變型在原序列的xi+1,xi+2,,xi+Lx_{i +1} , x_{i +2} ,…, x_{i +L}時段的權重, 稱爲時間主成分(TPC)。看到這裏應當發現了,由aima_{i}^{m} 構成的矩陣實際上就是沒有歸一化的右矩陣, 即 λmVm\sqrt{\lambda_{m}}V_{m}
  接下來就可以通過時間經驗正交函數和時間主成分來進行重建,具體重構過程如下:
xik={1ij=1iaijkUk,j,1iL11Lj=1LaijkUk,j,LiNL+11Ni+1j=iN+LLaijkEk,j,NL+2iN x_{i}^{k}=\left\{\begin{array}{l}{\frac{1}{i} \sum_{j=1}^{i} a_{i-j}^{k} U_{k, j}, \quad 1 \leqslant i \leqslant L-1} \\ \\{\frac{1}{L} \sum_{j=1}^{L} a_{i-j}^{k} U_{k, j}, \quad L \leqslant i \leqslant N-L+1} \\ \\ {\frac{1}{N-i+1} \sum_{j=i-N+L}^{L} a_{i-j}^{k} E_{k, j}, \quad N-L+2 \leqslant i \leqslant N}\end{array}\right. 這樣,所有重構序列的和應當等於原序列,即
xi=k=1Lxiki=1,2,N x_{i}=\sum_{k=1}^{L} x_{i}^{k} \quad i=1,2 \cdots, N 通常情況下我們使用SSA只是爲了提取原序列的主要成分,以去噪爲例,我們只需要根據奇異值的大小選擇前 k(kL)k(k \leq L) 個貢獻大的成分重構原序列即可。

python程序

#!/usr/bin/python3
# -*- coding: utf-8 -*-

'''
@Date    : 2019/11/11
@Author  : Rezero
'''

import numpy as np
import matplotlib.pyplot as plt

path = "xxxx"  # 數據集路徑

series = np.loadtxt(path)
series = series - np.mean(series)   # 中心化(非必須)

# step1 嵌入
windowLen = 20              # 嵌入窗口長度
seriesLen = len(series)     # 序列長度
K = seriesLen - windowLen + 1
X = np.zeros((windowLen, K))
for i in range(K):
    X[:, i] = series[i:i + windowLen]

# step2: svd分解, U和sigma已經按升序排序
U, sigma, VT = np.linalg.svd(X, full_matrices=False)

for i in range(VT.shape[0]):
    VT[i, :] *= sigma[i]
A = VT

# 重組
rec = np.zeros((windowLen, seriesLen))
for i in range(windowLen):
    for j in range(windowLen-1):
        for m in range(j+1):
            rec[i, j] += A[i, j-m] * U[m, i]
        rec[i, j] /= (j+1)
    for j in range(windowLen-1, seriesLen - windowLen + 1):
        for m in range(windowLen):
            rec[i, j] += A[i, j-m] * U[m, i]
        rec[i, j] /= windowLen
    for j in range(seriesLen - windowLen + 1, seriesLen):
        for m in range(j-seriesLen+windowLen, windowLen):
            rec[i, j] += A[i, j - m] * U[m, i]
        rec[i, j] /= (seriesLen - j)
        
rrr = np.sum(rec, axis=0)  # 選擇重構的部分,這裏選了全部

plt.figure()
for i in range(10):
    ax = plt.subplot(5,2,i+1)
    ax.plot(rec[i, :])

plt.figure(2)
plt.plot(series)
plt.show()

運行程序結果如下,左邊是原始序列,右邊是按奇異值排序的前十個成分序列,可以看到除了前幾個剩餘的基本都可以視爲噪聲序列。
在這裏插入圖片描述
如果取前五個序列重構,最後重構出的序列如下

相比原序列可以看到重構出的序列明顯比原序列平滑,但是同時保持了總體的變化情況。

參考資料

https://www.cnblogs.com/endlesscoding/p/10033527.html
基於SSA的GPS座標序列去噪及季節信號提取

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章