【问题标题】:Calculation on my for loop and want to do it without for loop using some function计算我的 for 循环并希望使用某些函数在没有 for 循环的情况下进行计算
【发布时间】:2021-11-01 00:21:13
【问题描述】:
dec = 0.1
data = np.array([100,200,300,400,500])

我有一个这样的 for 循环

y = np.zeros(len(data))
for i in range(len(data)):
    if i == 0:
        y[i] = (1.0 - dec) * data[i]
    else:
        y[i] = (1.0 - dec) * data[i] + (dec * y[i - 1])

输出 y 是:

array([ 90.   , 189.   , 288.9  , 388.89 , 488.889])

现在我想在没有循环的情况下进行上述计算,所以如果我破坏代码并这样做

data[0] = (1.0 - dec) * data[0]
data[1:] = (1.0 - dec) * data[1:] + (dec * data[0])

输出数据为:

array([ 90, 189, 279, 369, 459])

当您比较 y 和 data 输出时,前两个值是正确的,因为它与 data[0] 相乘,这是有道理的,但稍后它应该像循环代码中的循环一样继续,那么我们如何实现呢?有没有可以处理这个的函数?这主要是为了优化我的代码,使其对数千个数据运行得更快。

预期输出与 y 输出相同。

【问题讨论】:

标签: python numpy


【解决方案1】:

我们可以使用scipy.linalg.toeplitz 来制作数据移位矩阵,然后将其乘以dec 的幂并求和列:

import numpy as np
from scipy.linalg import toeplitz

dec = 0.1
data = np.array([100,200,300,400,500])

decs = np.power(dec, np.arange(len(data)))

r = np.zeros_like(data)
r[0] = data[0]
toep = toeplitz(r, data)

output = (1 - dec) * np.sum(toep * decs.reshape(-1, 1), axis=0)

第一个decsdec 的幂的向量:

print(decs) 
#[1.e+00 1.e-01 1.e-02 1.e-03 1.e-04]

接下来,我们使用toeplitz 制作data 的移位矩阵:

print(toep)
#[[100 200 300 400 500]
# [  0 100 200 300 400]
# [  0   0 100 200 300]
# [  0   0   0 100 200]
# [  0   0   0   0 100]])

最后,我们将decs 整形为一列,将其乘以toep 并沿列求和。这个结果需要用1 - dec缩放。

这一切都有效,因为我们可以将 data[i] 的定义重写为求和而不是递归:

y[i] = (1.0 - dec) * data[i] + (dec * y[i - 1])
y[i] = (1.0 - dec) * data[i] + (dec * ((1.0 - dec) * data[i - 1] + (dec * y[i - 2])))
...
y[i] = (1.0 - dec) * (data[i] + dec * data[i - 1] + dec ** 2 * data[i - 2] + ... dec ** i * data[0])
y[i] = (1.0 - dec) * sum(dec ** j * data[i - j] for j in range(i + 1))

这可以通过归纳来证明。

从那里开始,将这些总和重写为矩阵的列,并将该矩阵转换为 numpy/scipy 中的计算。

【讨论】:

  • 我找到了另一种简单的方法来做到这一点,但我没有得到准确的输出,它是 data = (1.0 - decay) * data data[1:] = data[1:] + decay * data[0:-1] 如果我这样做,我会得到如下输出: array([ 90., 189., 288., 387., 486.]) 这类似于输出 y 但是否有一些浮动缺少值并且无法获得正确的输出?
  • 看起来这个解决方案比普通方法还要慢,因为它需要的时间是数组长度的二次方
【解决方案2】:

我知道你说过不要使用 python for 循环

但 np.vectorize 也不是真正的矢量化(它不会移动你的代码 C)它只是方便

既然你说的是 1000 条数据,所以你应该尝试 numba,因为它会将 for 循环移动到机器代码

到目前为止,我无法想到仅使用 numpy ufuncs(np.add、np.dot 等) 获得相同的 correct 输出,因为已知 ufuncs 可以矢量化(真正的 simd 矢量化),具体取决于是否编译器可以做到,所以也许现在你可以试试 numba

import numba as nb
import numpy as np

dec = 0.1
data = np.array([100,200,300,400,500])
@nb.jit((nb.int64[:],))
def f(data):
    y = np.zeros(data.shape[0])
    for i in range(y.shape[0]):
        if i == 0:
            y[i] = (1.0 - dec) * data[i]
        else:
            y[i] = (1.0 - dec) * data[i] + (dec * y[i - 1])
    return y
print(f(data))

也可以并行化(here),但我不确定在这种情况下如何正确执行。事实上,我怀疑在不添加更复杂代码的情况下是否可以解决这个问题的向量化和并行性

【讨论】:

  • 我们在 numpy(包括 ufunc)中所说的“向量化”主要是使用已编译的代码 - 在 C 中而不是 python 中执行循环。它通常不使用硬件特定的“矢量化”(除非编译器可以这样做)。
猜你喜欢
  • 1970-01-01
  • 2021-01-29
  • 2016-10-31
  • 1970-01-01
  • 1970-01-01
  • 2019-02-21
  • 2020-07-25
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多