【问题标题】:Nested python loop revisited重新访问嵌套的 python 循环
【发布时间】:2014-08-05 13:40:44
【问题描述】:

我知道已经有几篇关于如何加快嵌套 python 循环的帖子,但我无法将这些帖子应用于我的问题。 我有一个嵌套的python for 循环,第一个循环遍历列表的所有元素,第二个循环遍历所有剩余的列表项,产生所有列表项的组合。一方面,我认为这很丑陋,但也很慢。有什么想法可以让它更快、更pythonic吗?

n = len(delta_vec)
for j in range(0, n - 1, 1):
    for k in range(j + 1, n, 1):
        w = 1. / np.exp((mjd_list[k] - mjd_list[j]) / avg_time)
        w_sum = w_sum + w
        P = delta_vec[j] * delta_vec[k]
        j_index = j_index + w * np.sign(P) * np.sqrt(np.abs(P))

【问题讨论】:

  • 您能否快速解释一下您查看了哪些其他帖子以及为什么这些解决方案不起作用?这将有助于防止您得到无法解决问题的答案
  • 不是一个完整的答案,而是一些提示:使用 enumerate() 而不是 range() 进行迭代更符合 Python 风格,并且可以稍微提高性能,因为 enumerate 会产生结果。此外,如果性能至关重要,您可以将函数重写为理解列表:pythonforbeginners.com/lists/list-comprehensions-in-python

标签: python performance for-loop nested vectorization


【解决方案1】:

当然,最快的方法是不使用 Python 中的任何 for 循环或 itertools 中的生成器,而是使用 Numpy 中的矢量化数学,如下所示:

n = len(delta_vec)
jj, kk = np.triu_indices(n, 1)
ww = 1. / np.exp((mjd_list[kk] - mjd_list[jj]) / avg_time)
w_sum = np.sum(ww)
PP = delta_vec[jj] * delta_vec[kk]
j_index = np.sum(ww * np.sign(PP) * np.sqrt(np.abs(PP)))

其中所有带有双字母的变量都是长度为n 的向量。请注意,您必须将列表转换为 numpy-arrays 才能正常工作。您甚至可以通过将1. / exp((a - b) / c) 替换为exp((b - a) / c) 来获得更快的速度。

说明:您正在使用直到n 的所有索引对进行计算,而不是使用具有相同索引的对(所以没有 1,1)并且对中的第一个总是最小的数字(所以没有 2 ,1)。如果您要制作所有可能对的二维矩阵,您会发现您只在对角线以上的元素上进行计算,因此您可以使用函数np.triu_index。为了证明这是可行的,您的循环会生成这些索引:

In [18]: n = 4
    ...: for j in range(0, n - 1, 1):
    ...:     for k in range(j + 1, n, 1):
    ...:         print j, k
0 1
0 2
0 3
1 2
1 3
2 3

您可以使用triu_indices 生成完全相同的索引:

In [19]: np.triu_indices(n, 1)
Out[19]: (array([0, 0, 0, 1, 1, 2]), array([1, 2, 3, 2, 3, 3]))

正确性和速度的快速测试:

import numpy as np
# generate some fake data
n = 300
mjd_vec = np.random.rand(n)
delta_vec = np.random.rand(n)
mjd_list = list(mjd_vec)
delta_list = list(delta_vec)
avg_time = 123

def f1():
    w_sum = j_index = 0
    n = len(delta_list)
    for j in range(0, n - 1, 1):
        for k in range(j + 1, n, 1):
            w = 1. / np.exp((mjd_list[k] - mjd_list[j]) / avg_time)
            w_sum = w_sum + w
            P = delta_vec[j] * delta_vec[k]
            j_index = j_index + w * np.sign(P) * np.sqrt(np.abs(P))
    return w_sum, j_index

def f2():
    n = len(delta_vec)
    jj, kk = np.triu_indices(n, 1)
    ww = 1. / np.exp((mjd_vec[kk] - mjd_vec[jj]) / avg_time)
    w_sum = np.sum(ww)
    PP = delta_vec[jj] * delta_vec[kk]
    j_index = np.sum(ww * np.sign(PP) * np.sqrt(np.abs(PP)))
    return w_sum, j_index

结果:

In [3]: f1()
Out[3]: (44839.864280781003, 18985.189058689775)

In [4]: f2()
Out[4]: (44839.864280781003, 18985.189058689775)

In [5]: timeit f1()
1 loops, best of 3: 629 ms per loop

In [6]: timeit f2()
100 loops, best of 3: 7.88 ms per loop

因此,numpy 版本产生相同的结果,并且快了 80 倍

【讨论】:

    【解决方案2】:

    看起来您正在同时迭代两个列表,从每个列表中的相同索引中获取项目,并处理所有索引对。在这种情况下,类似:

    >>> from itertools import combinations, izip
    >>> for x, y in combinations(izip([1, 2, 3], [4, 5, 6]), 2):
        print zip(x, y)
    
    
    [(1, 2), (4, 5)]
    [(1, 3), (4, 6)]
    [(2, 3), (5, 6)]
    

    这里的每个输出列表都包含来自第一个列表的对的双元组,然后是来自第二个列表的对的双元组。使用itertools 可以实现这一点,而无需构建整个列表(内部zip 除外)。

    使用您自己的变量名,如果您进行了迭代:

    for d_v, m_l in combinations(izip(delta_vec, mjd_list)):
    

    那么 zip(d_v, m_l) 会给你相当于:

    [(delta_vec[j], delta_vec[k]), (mjd_list[j], mjd_list[k])]
    

    在你目前的方法中

    或者,当您使用numpy 时,毫无疑问,我不知道一些基于数组的超快方法。

    【讨论】:

    • +1 也只是想推荐combinations,但与izip 一起使用会更好。但是,您应该解释这究竟是如何应用于计算的,因为它需要对代码进行相当多的调整。
    • 老实说,我越看越怀疑izip 然后将其压缩回去真的比仅仅迭代索引组合更简单......猜你可以像for (dj, dk), (mj, mk) in combinations(...) 这样的东西。
    • @tobias_k izip 是从两个列表中获取对,zip 是将组合放回合理的顺序(即不是[(dj, mj), (dk, mk)])。
    【解决方案3】:

    我会做出两个改变:

    1. 更改迭代

    2. 将计算代码移动到函数中

    这将有助于清理您的代码并使其更“pythonic”。至于,瓶颈只是你有很多计算要做。 Python 的设计并不快,因此如果速度确实是个问题,请考虑将计算外包给另一个更快的代码。通常编译后的代码(C 系列、FORTRAN 等)非常适合这样的东西。

    def computations(x, y, i, j):
        w = 1. / np.exp((mjd_list[i] - mjd_list[j]) / avg_time)
        w_sum = w_sum + w
        P = x * y
        j_index = j_index + w * np.sign(P) * np.sqrt(np.abs(P))
    
        return [stuff you want to keep]
    
    for i, x in enumerate(delta_vec):
        for j, y in delta_vec[i:]:
             result = computations(x, y, i, j)
    

    【讨论】:

    • 这行不通。 1) x 是索引,i 是元素; 2) 你还需要 y 的索引; 3)该方法仍然期望索引作为输入,但是您将元素(嗯,一个索引和一个元素)传递给它,并且 4)我怀疑这更快,在每次迭代中切片一个新的子列表...
    • @tobias_k,我并不是真的想要更快,只是更漂亮,我会更新以进行更改,感谢您指出
    • 一些问题:1)问题中的代码并行迭代两个列表(mjd_listdelta_vec1)。在这种情况下,我发现使用jk 进行显式索引比在一个列表上使用enumerate 并在另一个列表上使用索引的“非对称解决方案”更pythonic/clear。使用zip 可能看起来更好。 2) 外循环应该跳过最后一个元素:for i, x in enumerate(delta_vec[:-1])。 3)不需要去Fortran来加快速度,看看我的回答。 4) 正如 Tobias 所说,您在每次迭代时都会复制 delta_vec 的切片,这很慢。
    猜你喜欢
    • 2021-12-19
    • 2017-09-20
    • 1970-01-01
    • 1970-01-01
    • 2010-12-21
    • 2017-04-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多