Numpy 跨步(strides)——高效滑动平均滤波器

Numpy 跨步(strides)——高效滑动平均滤波器

阅读更多:Numpy 教程

引言

在数字信号处理中,滤波器是一种广泛使用的工具,它可以通过数值计算来改变信号的频率响应。其中一种常用的滤波器是滑动平均滤波器,它可用于平滑时间序列数据。Numpy是一个用于科学计算的Python库,而跨步(strides)是Numpy中一个相对较少被使用的属性,但它却是高效进行滑动平均滤波器实现的核心。

在本文中,我们将学习如何使用跨步(strides)属性,在Numpy中实现滑动平均滤波器。具体来说,我们将从以下几个方面深入探讨:

  • 什么是滑动平均滤波器
  • 如何计算滑动平均值
  • 如何使用Numpy实现滑动平均滤波器
  • 如何优化Numpy实现

滑动平均滤波器

滑动平均滤波器是一种常用的平滑滤波器,它可用于去除时间序列数据中的噪声或非局部变化。在滑动平均滤波器中,数据中的一些信号特征可以被平滑地消除,例如高频信号特征和嘈杂信号特征。滑动平均滤波器通过对时间序列数据进行一系列连续的计算,生成滑动窗口下的平均值。

例如,对于一个长度为n的时间序列数据,我们将使用长度为k的滑动窗口来平滑数据,每个窗口的中心数据点为x[i]。在最常见的情况下,k的大小通常是小于n的。因此,我们需要将滑动窗口从左到右移动,以处理整个数据集。滑动平均滤波器的计算公式如下:

y[i]=\frac{1}{k}\sum_{j=i-\frac{k-1}{2}}^{i+\frac{k-1}{2}}x_j

其中,y[i]表示在当前位置上滤波器输出的平均值,x_j表示在第j个位置上的输入数据。滤波器的核心思路是将给定的时间序列x分成若干个长度为k的子序列,并在每个子序列上执行平均值计算。

为了更好地说明这个过程,我们可以考虑下面的示例。假设该滑动平均滤波器的长度为3,时间序列x由以下9个数据点组成:

x = [1, 2, 3, 4, 5, 6, 7, 8, 9]

对于该示例,我们可以选择使用步长为1的窗口来移动。计算结果如下:

y = [(1+2+3)/3, (2+3+4)/3, (3+4+5)/3, (4+5+6)/3, (5+6+7)/3, (6+7+8)/3, (7+8+9)/3]

这里得到的y即为平滑后的结果。在实现滑动平均滤波器之前,我们需要对此进行更深入的了解,我们将在下一节讨论如何计算它。

计算滑动平均值

为了计算滑动平均值,我们需要首先选择一个合适的窗口宽度k,这个宽度应该足够大,以促使平滑效果产生,但也不能太大,否则将会导致信息的丢失。在一般情况下,窗口大小一般在5到21之间。

一种计算滑动平均值的方法是创建一个大小为k的滑动窗口,并使用for循环遍历整个数组x。在每个时间步长t,我们只需要将窗口中的元素取出,并计算其平均值。该方法的时间复杂度为O(nk),其中n为x的长度。

不过,numpy提供了一种更加高效的计算方法,可以用更少的代码实现更快的滤波器。下一节将详细探讨如何使用numpy实现。

使用Numpy实现滑动平均滤波器

numpy是一个Python的科学计算库,它包含了许多有用的函数和属性,用于处理数组、矩阵、图像等各种科学数据。在numpy中实现滑动平均滤波器的关键是stride参数。

stride参数是numpy数组的一个属性,它指定生成数组时应该跳过的字节数。默认情况下,stride被设置为与数据类型和数组形状相一致。我们可以通过使用strides参数来直接设置stride,并使用相应的跨越值来在数组中跳过元素。跨越值可以是负数,表示数组应该被倒置。

为了理解numpy实现滑动平均滤波器的具体方法,让我们考虑以下实例。

import numpy as np

x = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9])

k = 3
y = np.convolve(x, np.ones(k)/k, mode='valid')

在这个例子中,我们使用numpy中的convolve函数,它可以计算x和平滑卷积核np.ones(k)/k之间的卷积。这里mode参数设置为”valid”,表示我们只考虑x中的区域,其中可以进行完整的卷积。结果数组y中的每个元素表示平滑后的数据点。

在这里,我们使用了ones函数,它返回一个由1组成的数组。我们再将它除以k,得到一个平均值为1/k的平滑卷积核。然后将该卷积核和x进行卷积,卷积的结果就是平均值为k的滑动平均滤波器的输出。

然而对于大的数据集,卷积方法并不是最优的实现方式。在下一节,我们将进一步讨论如何使用Numpy进一步优化此过程。

优化Numpy实现

为了在Numpy中实现高效的滑动平均滤波器,我们需要一些额外的技巧。下面我们将介绍两种不同的方法。

方法1:使用stride和reshaping

利用numpy中的stride属性,我们可以不使用for循环便可以将计算滑动窗口的平均值。具体实现步骤为:

  1. 初始化一个k * n的滑动窗口矩阵,其中k表示窗口大小,n表示时间序列数据点的数量。

  2. 确定矩阵的strides属性,根据滑动窗口矩阵的大小和数据类型计算跨步,以便在数据集上跳过未使用数据。

  3. 通过将时间序列数据点按列组成一个n * 1的矩阵,并将其转换为一个k列的矩阵(reshape操作),生成区分每个滑动窗口的列。

  4. 将列的平均值计算,并存储在输出向量中。

  5. 将输出向量重塑为一个k * (n-k+1)的矩阵,其中n-k+1表示所有包含在每个滑动窗口中的数据点数目。这个矩阵即为滑动平均滤波器的输出。

在下面的代码中,我们用上述方法在已知的时间序列数据上计算滑动平均值,并比较其速度和基本numpy方法实现该任务的速度。

import numpy as np

def sliding_window(a, window_size):
    shape = a.shape[:-1] + (a.shape[-1] - window_size + 1, window_size)
    strides = a.strides + (a.strides[-1],)
    return np.lib.stride_tricks.as_strided(a, shape=shape, strides=strides)

x = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9])
k = 3

#基本numpy方法计算
start = time.time()
y_numpy = np.convolve(x, np.ones(k)/k, mode='valid')
end = time.time()
print("基本numpy方法计算运行时间:", end - start)

#跨步和reshape方法计算
start = time.time()
x_windows = sliding_window(x, k)
y_window = x_windows.mean(axis=-1)
end = time.time()
print("跨步和reshape方法计算运行时间:", end - start)

print("结果是否相同:", np.allclose(y_numpy, y_window))

可见,使用跨步和reshape方法可以大大缩短计算时间,并同时计算多个滑动窗口的值。通过比较可以看出,两种方法的结果是相同的。

方法2:使用cumsum()

除了使用滑动窗口,我们还可以使用cumsum()函数实现滑动平均滤波器的计算。

cumsum()函数可以计算给定数组的累计和,并可以将所有位置上的总和存储到一个输出向量中。在本例中,我们需要计算两个累计和的差异,以确定窗口内数据点的和。我们可以使用该函数计算输入时间序列x和平均卷积核的累计和,然后对它们进行相减,并将结果赋给一个新向量。最后,我们需要以窗口大小k为为分界线计算新向量的滑动平均值。

下面的代码将展示如何使用cumsum()函数实现滑动平均滤波器的计算。

import numpy as np

x = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9])
k = 3

#计算时间序列和平均卷积核的累计和
cumsum_x = np.cumsum(np.insert(x, 0, 0))
cumsum_kernel = np.cumsum(np.insert(np.ones(k)/k, 0, 0))

#计算窗口内数据点的和
y_cumsum = (cumsum_x[k:] - cumsum_x[:-k]) / k

#计算滑动平均值
y_cumsum2 = (cumsum_kernel[k:] - cumsum_kernel[:-k]) * x[k-1:] / k

print("使用cumsum()方法推导的结果:", y_cumsum)

我们可以看到,使用cumsum()函数的方法可以利用numpy的内部优化,加速滑动平均滤波器的计算。

Python教程

Java教程

Web教程

数据库教程

图形图像教程

大数据教程

开发工具教程

计算机教程