Python学习笔记

您所在的位置:网站首页 numpy二维数组操作 Python学习笔记

Python学习笔记

2023-08-08 08:26| 来源: 网络整理| 查看: 265

Python学习笔记——Numpy数组的移动滑窗,使用as_strided实现 `Numpy`中移动滑窗的实现为何需要移动滑窗`Numpy`中的移动滑窗移动滑窗的`as_strided`实现方法 关于`as_strided`函数的详细解析使用`as_strided`函数的危险之处

Numpy中移动滑窗的实现 为何需要移动滑窗

在量化投资分析过程中,对历史数据进行分析是一个必不可少的步骤。滑窗在历史数据分析中的重要性不言而喻。譬如移动平均、指数平滑移动平均、MACD、DMA等等价格指标的计算都无一例外需要用到滑窗。

作为一种非常受欢迎的数据分析工具,pandas中提供了专门的滑窗类:DataFrame.rolling()。通过这个滑窗类,可以非常容易地实现移动平均等等算法,但是,在某些情况下,Pandas的运行速度还是不够,需要借助Numpy的高效率进一步提升速度,这时候就需要在Numpy中实现滑窗了。

Numpy中的移动滑窗

可惜Numpy并没有提供直接简单的滑窗方法,如果使用for-loop来实现滑窗,不仅效率打折扣,而且内存占用也非常大。实际上,Numpy提供了一个非常底层的函数可以用来生成滑窗:Numpy.lib.stride_tricks.as_stried。

移动滑窗的as_strided实现方法

举一个例子,首先生成一个5000行200列的二维数组,我们需要在这个二维数组上生成一个宽度为200的滑窗,也就是说,第一个窗口包含前0~199行数据,第二个窗口包含1~200行,第三个窗口包含2~201行,以此类推,一共4801组:

In [106]: d = np.random.randint(100, size=(5000,200))

如果使用as_strided函数生成上述滑窗,需要用下面的代码,它生成一个三维数组,包括4801组200X200的矩阵,每一组200X200的矩阵代表一组滑窗:

In [107]: %timeit sd = as_strided(d, (4801,200,200), (200*8, 200*8, 8)) 5.97 µs ± 33.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

我们再尝试一下用for-loop的方法生成一个滑窗检验一下前面生成的滑窗是否正确:

In [108]: %%timeit ...: sd2 = np.zeros((4801,200,200)) ...: for i in range(4801): ...: sd2[i] = d[i:i+200] ...: 722 ms ± 98.8 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) In [109]: np.allclose(sd, sd2) Out[109]: True

从上面的代码可以看出,使用as_strided生成一组滑窗,速度竟然是for-loop的十万倍以上!那么as_strided是如何做到的呢?

关于as_strided函数的详细解析

as_strided是怎么回事呢?看它的函数解释:

Signature: as_strided(x, shape=None, strides=None, subok=False, writeable=True) Docstring: Create a view into the array with the given shape and strides. .. warning:: This function has to be used with extreme care, see notes. Parameters ---------- x : ndarray Array to create a new. shape : sequence of int, optional The shape of the new array. Defaults to "x.shape". strides : sequence of int, optional The strides of the new array. Defaults to "x.strides". subok : bool, optional If True, subclasses are preserved. writeable : bool, optional If set to False, the returned array will always be readonly. Otherwise it will be writable if the original array was. It is advisable to set this to False if possible (see Notes). Returns ------- view : ndarray

这个函数接受的第一个参数是一个数组,第二个参数是输出的数据shape,第三个参数是stride。要控制数据的输出,shape和stride都非常重要

shape的含义非常简单,就是指输出的数据的行、列、层数,这个参数是一个元组,元组的元素数量等于数组的维度。 而stride的含义就相对复杂一些,其实它的含义是指“步幅”,意思是每一个维度的数据在内存上平移的字节数量。 因为数组在内存中的存放方式是一维线性方式存放的,因此要访问数组中的某个数字就需要知道平移到哪一个内存单元,ndarray通过stride“步幅”来指定这个平移的幅度。 在as_strided函数中,stride也是一个元组,其元素的数量必须跟shape的元素数量相同,每一个元素就代表该维度的每一个数据相对前一个数据的内存间隔。 举个例子:

In [188]: d = np.random.randint(10, size=(5,3)) In [189]: d Out[189]: array([[4, 4, 6], [2, 9, 3], [5, 1, 1], [2, 0, 0], [9, 2, 3]])

上面的数据其实是以连续的形式存储在内存中的:

地址0地址1地址2地址3地址4地址5地址6地址7地址8地址9地址A地址B地址C地址D地址E445293511200923

我们之所以看到一个二维数组,是因为numpy数组的shape为(5, 3),stride为(24, 8),意思是说,我们看到的数据有5行3列,对应shape的(5, 3),每一行与前一行间隔24个字节(其实就是三个数字,因为每一个int类型占据8字节,而每一列数字比前一列相差8字节(1个数字)

理解上面的含义以后,也就能理解如何生成一个数据滑窗了,如果我们需要生成一个2X3的数据滑窗,在d上滑动,实际上可以生成一个4组,2行3列的数据视图,第一组覆盖d的第0、1两行,第二层覆盖d的第1、2两行,第三层覆盖d的第2、3两行……这样就形成了数据滑窗的效果,我们只要在新的数据视图上遍历,就能遍历整个滑窗。这样做的好处是,在整个遍历的过程中完全不需要对数据进行任何移动或复制的操作,因此速度飞快。

根据上面的思路,我们需要生成一个新的数据视图,其shape为(4, 2, 3)代表4组(从头到尾滑动4次),2行3列(滑窗的尺寸)

接下来需要确定stride,如前所述stride同样是一个包含三个元素的元组,第一个元素是两层数据之间的内存间隔,由于我们的滑窗每滑动一次下移一行,因此层stride应该是平移三个数字,也就是24个字节,行stride和列stride与原来的行列stride一致,因为我们需要原样看到按顺序的数字,因此,新的stride就是:(24, 24, 8) 我们来看看这个新的数据视图是什么样子:

In [190]: as_strided(d, shape=(4,2,3), strides=(24,24,8)) Out[190]: array([[[4, 4, 6], [2, 9, 3]], [[2, 9, 3], [5, 1, 1]], [[5, 1, 1], [2, 0, 0]], [[2, 0, 0], [9, 2, 3]]])

看!一个数据滑窗正确地出现了!

使用as_strided函数的危险之处

使用s_strided函数的最大问题是内存读取风险,在as_strided生成新的视图时,由于直接操作内存地址(这一点像极了C的指针操作),而且它并不会检查内存地址是否越界,因此如果稍有不慎,就会读到别的内存地址。关键是,如果不设置可读参数,还能直接对内存中的数据进行操作,这样就带来了无比大的风险。了解这个风险对正确操作至关重要! 例如,使用下面的stride会直接溢出到其他的未知内存地址上,并读取它的值,甚至还可以直接修改它:

In [194]: as_strided(d, shape=(5,2,3), strides=(24,24,8)) Out[194]: array([[[ 4, 4, 6], [ 2, 9, 3]], [[ 2, 9, 3], [ 5, 1, 1]], [[ 5, 1, 1], [ 2, 0, 0]], [[ 2, 0, 0], [ 9, 2, 3]], [[ 9, 2, 3], [2251799813685248, 18963, 0]]])

这时对象的第五组就映射到了三个未知的内存地址上,如果不慎修改了这三个地址上的内容,就可能造成难以预料的问题,如程序崩溃等。 所以,官方才在文档中郑重地警告:如果有可能,尽量避免使用as_strided函数



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3