移動標準差以及移動平均值(movstd、movmean)
最近在工作中遇到這樣一個問題:
有一個序列長度爲 的序列 ,給定一個窗大小 ,下標從0開始,計算窗大小的均值和標準差,即計算T[0:m-1]、T[1:m]、T[2:m+1]…T[n-m+1:n] 的平均值和標準差
暴力解法
最簡單的無腦的方法就是暴力循環了,很明顯這種方法特別慢,時間複雜度爲
下面爲你呈現暴力代碼
import numpy as np
import time
# generate time sequence
n = 1000 * 1000
m = 1000
T = np.random.rand(n)
# brute force
means = np.zeros(n - m + 1)
stds = np.zeros(n - m + 1)
start_time = time.time()
for i in range(n - m + 1):
means[i] = np.mean( T[i:i+m] )
end_time = time.time()
print('Running time of brute force for mean is {}s'.format((end_time - start_time)))
start_time = time.time()
for i in range(n - m + 1):
stds[i] = np.std( T[i:i+m] )
end_time = time.time()
print('Running time of brute force for std is {}s'.format((end_time - start_time)))
Running time of brute force for mean is 5.774143934249878s
Running time of brute force for std is 20.721827030181885s
movmean
有沒有辦法進行優化呢?這裏介紹移動標準差(movstd)和移動平均值(movmean)
先從移動平均值(movmean)開始,它很簡單並且符合直覺:在滑動的過程中,有很多重疊部分,我們可以利用重疊的部分,從而節約計算時間
如上圖所示,計算時可以利用前一個均值,這樣就避免了不必要的加法操作,平均值的計算複雜度降低爲
下面代碼顯示瞭如何實現 movmean
def movmean(T, m):
assert(m <= T.shape[0])
n = T.shape[0]
sums = np.zeros(n - m + 1)
sums[0] = np.sum(T[0:m])
cumsum = np.cumsum(T)
cumsum = np.insert(cumsum, 0, 0) # 在數組開頭插入一個0
sums = cumsum[m:] - cumsum[:-m]
return sums/m
start_time = time.time()
means_2 = movmean(T, m)
end_time = time.time()
print('Running time of movmean is {}s'.format((end_time - start_time)))
Running time of movmean is 0.009449005126953125s
movstd
在介紹移動標準差之前,我們先回顧下標準差計算公式:
假設有一個長度爲 3 的序列 [a, b, c],我們來計算一下它的標準差
首先計算均值:
然後標準差:
我們可以發現,標準差的計算可以用累計和來表示,而累加和是可以在 時間內完成,這就是 movstd
下面的代碼展示瞭如何計算 movstd
def movstd(T, m):
n = T.shape[0]
cumsum = np.cumsum(T)
cumsum_square = np.cumsum(T**2)
cumsum = np.insert(cumsum, 0, 0) # 在數組開頭插入一個0
cumsum_square = np.insert(cumsum_square, 0, 0) # 在數組開頭插入一個0
seg_sum = cumsum[m:] - cumsum[:-m]
seg_sum_square = cumsum_square[m:] - cumsum_square[:-m]
return np.sqrt( seg_sum_square/m - (seg_sum/m)**2 )
start_time = time.time()
stds_2 = movstd(T, m)
end_time = time.time()
print('Running time of movstd is {}s'.format((end_time - start_time)))
Running time of movstd is 0.03198814392089844s
總結
通過提前計算好累計和,移動平均和移動標準差以空間換時間,算法速度比起暴力方法提升了幾個數量級