繁体   English   中英

在python中向量化6 for循环累积和

Vectorize a 6 for loop cumulative sum in python

提示:本站收集StackOverFlow近2千万问答,支持中英文搜索,鼠标放在语句上弹窗显示对应的参考中文或英文, 本站还提供   中文繁体   英文版本   中英对照 版本,有任何建议请联系yoyou2525@163.com。

数学问题是:

在此输入图像描述

总和中的表达式实际上比上面的表达式复杂得多,但这是一个最小的工作示例,不会使事情过于复杂。 我用Python编写了6个嵌套for循环,并且正如预期的那样表现非常糟糕(真正的表单执行得很糟糕,需要评估数百万次),即使在Numba,Cython和朋友的帮助下也是如此。 这里使用嵌套for循环和累积和来编写:

import numpy as np

def func1(a,b,c,d):
    '''
    Minimal working example of multiple summation
    '''
    B = 0
    for ai in range(0,a):
        for bi in range(0,b):
            for ci in range(0,c):
                for di in range(0,d):
                    for ei in range(0,ai+bi):
                        for fi in range(0,ci+di):
                            B += (2)**(ei-fi-ai-ci-di+1)*(ei**2-2*(ei*fi)-7*di)*np.math.factorial(ei)


    return a, b, c, d, B

表达式由4个数字作为输入控制,对于func1(4,6,3,4)B的输出为21769947.844726562。

我已经四处寻找帮助,并找到了几个Stack帖子,其中一些例子是:

NumPy中的外部产品:矢量化六个嵌套循环

在Python / Numpy中使用不同的数组形状矢量化三重循环

Python矢量化嵌套for循环

我试图使用我从这些有用的帖子中学到的东西,但经过多次尝试,我一直得出错误的答案。 即使对其中一个内部总和进行矢量化也会为真正的问题带来巨大的性能提升,但总和范围不同的事实似乎让我失望了。 有没有人有关于如何推进这个的任何提示?

4 个回复

编辑3:

最终(我认为)版本,更清晰,更快速地结合了max9111的答案

import numpy as np
from numba import as nb

@nb.njit()
def func1_jit(a, b, c, d):
    # Precompute
    exp_min = 5 - (a + b + c + d)
    exp_max = b
    exp = 2. ** np.arange(exp_min, exp_max + 1)
    fact_e = np.empty((a + b - 2))
    fact_e[0] = 1
    for ei in range(1, len(fact_e)):
        fact_e[ei] = ei * fact_e[ei - 1]
    # Loops
    B = 0
    for ai in range(0, a):
        for bi in range(0, b):
            for ci in range(0, c):
                for di in range(0, d):
                    for ei in range(0, ai + bi):
                        for fi in range(0, ci + di):
                            B += exp[ei - fi - ai - ci - di + 1 - exp_min] * (ei * ei - 2 * (ei * fi) - 7 * di) * fact_e[ei]
    return B

这已经比以前的任何选项都快得多,但我们仍然没有利用多个CPU。 一种方法是在函数本身内,例如并行化外循环。 这会在每次调用时增加一些开销来创建线程,因此对于小输入实际上有点慢,但对于更大的值应该明显更快:

import numpy as np
from numba import as nb

@nb.njit(parallel=True)
def func1_par(a, b, c, d):
    # Precompute
    exp_min = 5 - (a + b + c + d)
    exp_max = b
    exp = 2. ** np.arange(exp_min, exp_max + 1)
    fact_e = np.empty((a + b - 2))
    fact_e[0] = 1
    for ei in range(1, len(fact_e)):
        fact_e[ei] = ei * fact_e[ei - 1]
    # Loops
    B = np.empty((a,))
    for ai in nb.prange(0, a):
        Bi = 0
        for bi in range(0, b):
            for ci in range(0, c):
                for di in range(0, d):
                    for ei in range(0, ai + bi):
                        for fi in range(0, ci + di):
                            Bi += exp[ei - fi - ai - ci - di + 1 - exp_min] * (ei * ei - 2 * (ei * fi) - 7 * di) * fact_e[ei]
        B[ai] = Bi
    return np.sum(B)

或者,如果您有许多要评估函数的点,也可以在该级别进行并行化。 这里a_arrb_arrc_arrd_arr是要评估函数的值的向量:

from numba import as nb

@nb.njit(parallel=True)
def func1_arr(a_arr, b_arr, c_arr, d_arr):
    B_arr = np.empty((len(a_arr),))
    for i in nb.prange(len(B_arr)):
        B_arr[i] = func1_jit(a_arr[i], b_arr[i], c_arr[i], d_arr[i])
    return B_arr

最佳配置取决于您的输入,使用模式,硬件等,因此您可以根据您的情况组合不同的想法。


编辑2:

实际上,忘记我之前说过的话。 最好的是JIT编译算法,但是以更有效的方式。 首先计算昂贵的部分(我采用指数和阶乘),然后将其传递给编译的循环函数:

import numpy as np
from numba import njit

def func1(a, b, c, d):
    exp_min = 5 - (a + b + c + d)
    exp_max = b
    exp = 2. ** np.arange(exp_min, exp_max + 1)
    ee = np.arange(a + b - 2)
    fact_e = scipy.special.factorial(ee)
    return func1_inner(a, b, c, d, exp_min, exp, fact_e)

@njit()
def func1_inner(a, b, c, d, exp_min, exp, fact_e):
    B = 0
    for ai in range(0, a):
        for bi in range(0, b):
            for ci in range(0, c):
                for di in range(0, d):
                    for ei in range(0, ai + bi):
                        for fi in range(0, ci + di):
                            B += exp[ei - fi - ai - ci - di + 1 - exp_min] * (ei * ei - 2 * (ei * fi) - 7 * di) * fact_e[ei]
    return B

在我的实验中,这是迄今为止最快的选项,并且只占用很少的额外内存(只有预先计算的值,输入上的大小为线性)。

a, b, c, d = 4, 6, 3, 4
# The original function
%timeit func1_orig(a, b, c, d)
# 2.07 ms ± 33.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
# The grid-evaluated function
%timeit func1_grid(a, b, c, d)
# 256 µs ± 25 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
# The precompuation + JIT-compiled function
%timeit func1_jit(a, b, c, d)
# 19.6 µs ± 3.25 µs per loop (mean ± std. dev. of 7 runs, 100000 loops each)

那么总是可以选择网格评估整个事情:

import numpy as np
import scipy.special

def func1(a, b, c, d):
    ai, bi, ci, di, ei, fi = np.ogrid[:a, :b, :c, :d, :a + b - 2, :c + d - 2]
    # Compute
    B = (2.) ** (ei - fi - ai - ci - di + 1) * (ei ** 2 - 2 * (ei * fi) - 7 * di) * scipy.special.factorial(ei)
    # Mask out of range elements for last two inner loops
    m = (ei < ai + bi) & (fi < ci + di)
    return np.sum(B * m)

print(func1(4, 6, 3, 4))
# 21769947.844726562

我用scipy.special.factorial因为很明显np.factorial不会因为某些原因数组。

Obivously,因为你增加了参数的这种内存的成本会增长非常快。 代码实际上执行的计算比必要的多,因为两个内部循环具有不同的迭代次数,因此(在此方法中)您必须使用最大的,然后删除您不需要的。 希望是矢量化将弥补这一点。 一个小的IPython基准:

a, b, c, d = 4, 6, 3, 4
# func1_orig is the original loop-based version
%timeit func1_orig(a, b, c, d)
# 2.9 ms ± 110 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
# func1 here is the vectorized version
%timeit func1(a, b, c, d)
# 210 µs ± 6.34 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

编辑:

请注意,以前的方法也不是一个全有或全无的事情。 您可以选择仅对某些循环进行网格评估。 例如,两个最里面的循环可以像这样矢量化:

def func1(a, b, c, d):
    B = 0
    e = np.arange(a + b - 2).reshape((-1, 1))
    f = np.arange(c + d - 2)
    for ai in range(0, a):
        for bi in range(0, b):
            ei = e[:ai + bi]
            for ci in range(0, c):
                for di in range(0, d):
                    fi = f[:ci + di]
                    B += np.sum((2.) ** (ei - fi - ai - ci - di + 1) * (ei ** 2 - 2 * (ei * fi) - 7 * di) * scipy.special.factorial(ei))
    return B

这仍然有循环,但它确实避免了额外的计算,并且内存要求低得多。 哪一个最好取决于我猜的输入大小。 在我的测试中,使用原始值(4,6,3,4),这甚至比原始函数慢; 此外,对于这种情况,似乎在每个循环上为eifi创建新数组比在预先创建的循环上操作更快。 但是,如果将输入乘以4(14,24,12,16),那么这比原始(约x5)快得多,尽管仍然比完全矢量化的(约x3)慢。 另一方面,我可以计算输入的值,用十(40,60,30,40)来缩放这个(在~5分钟内)而不是前一个因为内存(我没有测试如何)它需要与原始功能一起使用)。 使用@numba.jit帮助,虽然不是很大(由于阶乘函数不能使用nopython )。 您可以尝试使用或多或少的循环向量化,具体取决于输入的大小。

这只是对@jdehesa答案的评论。

如果Numba本身不支持某个功能,通常建议您自己实现它。 在分解的情况下,这不是一项复杂的任务。

import numpy as np
import numba as nb

@nb.njit()
def factorial(a):
  res=1.
  for i in range(1,a+1):
    res*=i
  return res

@nb.njit()
def func1(a, b, c, d):
    B = 0.

    exp_min = 5 - (a + b + c + d)
    exp_max = b
    exp = 2. ** np.arange(exp_min, exp_max + 1)

    fact_e=np.empty(a + b - 2)
    for i in range(a + b - 2):
      fact_e[i]=factorial(i)

    for ai in range(0, a):
        for bi in range(0, b):
            for ci in range(0, c):
                for di in range(0, d):
                    for ei in range(0, ai + bi):
                        for fi in range(0, ci + di):
                            B += exp[ei - fi - ai - ci - di + 1 - exp_min] * (ei * ei - 2 * (ei * fi) - 7 * di) * fact_e[ei]
    return B

并行版本

@nb.njit(parallel=True)
def func_p(a_vec,b_vec,c_vec,d_vec):
  res=np.empty(a_vec.shape[0])
  for i in nb.prange(a_vec.shape[0]):
    res[i]=func1(a_vec[i], b_vec[i], c_vec[i], d_vec[i])
  return res

a_vec=np.random.randint(low=2,high=10,size=1000000)
b_vec=np.random.randint(low=2,high=10,size=1000000)
c_vec=np.random.randint(low=2,high=10,size=1000000)
d_vec=np.random.randint(low=2,high=10,size=1000000)

res_2=func_p(a_vec,b_vec,c_vec,d_vec)

在您的示例中,单线程版本导致5.6μs (在第一次运行之后)。

并行版本几乎会导致另一个Number_of_Cores加速计算许多值。 请记住,并行版本的编译开销较大(第一次调用时大于0.5秒)。

使用此cartesian_product函数,您可以将嵌套循环转换为矩阵,然后您可以以矢量化方式简单地计算各自的嵌套sigma:

In [37]: def nested_sig(args):
    ...:     base_prod = cartesian_product(*arrays)
    ...:     second_prod = cartesian_product(base_prod[:,:2].sum(1), base_prod[:,2:].sum(1))
    ...:     total = np.column_stack((base_prod, second_prod))
    ...:     # the items in each row denotes the following variables in order:
    ...:     # ai, bi, ci, di, ei, fi
    ...:     x = total[:, 4] - total[:, 5] - total[:, 0] - total[:, 2] - total[:, 3] + 1
    ...:     y = total[:, 4] - total[:, 5]
    ...:     result = np.power(2, x) * (np.power(total[:, 4], 2) - 2*y - 7*total[:, 3]) * np.math.factorial(total[:,4])
    ...:     return result

我看到你的代码有三个改进来源:

  • range(0,a) 在此输入图像描述

  • 你在内循环中做了很多工作

  • 您以随机的方式对术语求和,对于较大的条目存在精度损失的风险。

这里有一个试图提高这一点的版本(可能还不是很好)。

@numba.njit
def func1o(a,b,c,d):
    "2**(ei-fi-ai-ci-di+1)*(ei**2-2*(ei*fi)-7*di)*ei!"                    
    POW=2.;                 SUM=0.;              
    L=[]
    for ai in arange(0.,a+1):
        for bi in range(0,b+1):
            for ci in range(0,c+1):
                for di in range(0,d+1):
                    FACT=1.
                    for ei in arange(0,ai+bi+1):
                        for fi in range(0,ci+di+1):
                            L.append(POW*SUM*FACT)
                            POW /= 2
                            SUM -= 2*ei
                        POW *= 2    
                        SUM += 2*(ei-fi)+1
                        FACT *= ei+1
                    POW /=2
                    SUM -= 7*di
                POW /= 2
        POW /= 2
    A=np.array(L)
    I=np.abs(A).argsort()
    return A[I].sum()    
3 在Python中向量化循环

我有一个以下循环,其中我正在如下计算不同大小的批次的softmax转换 由于此for循环未向量化,因此这是我代码中的瓶颈。 有什么可能的解决方案以使其更快。 我试过使用numba @jit ,这使其速度更快但还不够。 我想知道是否还有另一种方法可以使其更快或矢量化/并行化。 ...

6 加快/向量化Python中的嵌套循环

我已经实现了Python脚本,并且需要使用4个嵌套循环。 我已经意识到这会使解决方案非常缓慢。 我还注意到,Matlab中的类似循环比Python中的循环要快得多。 1)为什么Matlab中的相同循环比Python中的循环更快? 2)如何改善我的Python代码(例如矢量化)? ...

7 这两个循环如何在Python中向量化?

我在检索近40万个值values ,它是由本身相当缓慢的(未显示的代码),然后我试图通过一个Kalmann过滤器做这些值的预测,第一循环正在一点点过一分钟的时间,第二分钟的两分半钟,我认为第一分钟可以向量化,但是我不确定如何,尤其是window_sma 。 第二个循环我不确定如何处理增加x数组 ...

8 在Python中向量化具有重复索引的循环

我正在尝试优化一个被调用很多(几百万次)的代码段,因此任何类型的速度改进(希望删除for循环)都将是很棒的。 我正在计算某些第j个粒子与所有其他粒子的相关函数 C_j(| r-r'|)= sqrt(E((s_j(r')-s_k(r))^ 2))的平均值。 我的想法是要有一个变量 ...

9 向量化python中的简单函数:避免double for循环

我是python的新手。 我正在尝试做一个非常简单的事情,评估将浮点数作为2D网格输入的非平凡函数。 以下代码完全符合我的要求,但是由于double for循环,它很慢。 最后,阵列电位器包含了我想要的所有信息,但是现在我想提高效率。 我知道纯python很慢,特别是在循环(如ID ...

2016-02-28 22:20:08 2 132   python
10 如何在python中向量化嵌套的for循环

我最关心效率。 我有很长的ID列表,还有第二个较短的ID列表。 我想在第二个列表中存储与第一个列表中的每个ID对应的ID的位置(每个ID在每个列表中应该只出现一次)。 我已经编写了一个嵌套的for循环来执行此操作,但是由于第一个列表包含超过1000个元素,而第二个列表包含超过80k个元素, ...

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2022 STACKOOM.COM