需要在 python 中加快对 numpy 数组的操作

Need to speed up the operations on numpy arrays in python

我正在通过将 Cplex 作为库导入 Python 来求解整数规划模型。假设优化问题具有以下形式的约束 (Ax = b)x0+x1+x1+x3 = 1

此约束中 x 变量的索引为 0、1、1 和 3。这些存储在列表中:indices=[0,1,1,3] 这些变量的系数也存储在另一个列表coeff = [1,1,1,1]

Cplex 不能接受重复的索引,所以约束应该是这样的:

x0+2x1+x3 = 1

所以这两个列表应该像这样更新:

indices=[0,1,3]
 coeff = [1,2,1]

我有这个函数,它将索引和系数作为两个参数,并为我提供了操作列表:

def manipulate(indices, coeff):
    u = np.unique(indices)
    sums  = { ui:np.sum([  coeff[c[0]] for c in np.argwhere(indices == ui) ]) for     ui in u }
    return list(sums.keys()),list(sums.values())

所以,manipulate([0,1,1,3], [1,1,1,1]) returns ([0, 1, 3], [1, 2, 1]).

我的问题是当我有这么多变量时,列表的长度可以达到一百万,而我有数百万个这样的约束。当我使用 cplex 解决我的优化问题时,程序变得非常慢。我跟踪了每个函数所花费的时间,并意识到我的代码中最耗时的部分是这些计算,我猜这是因为使用了 numpy。我需要使这个函数更有效率,以期减少执行时间。您能否与我分享有关如何更改功能操作的任何想法和建议?

非常感谢,

如果不寻求基于本机代码的扩展,可能总会有妥协:

  • Numpy/矢量化方法错过了基于散列的算法,恕我直言,将遭受算法复杂性的缺点(例如,需要排序;需要进行多次传递……)

  • Python 基于散列的方法将受到缓慢循环的影响。

尽管如此,我确实认为,您的方法在某种程度上 接近了两个世界中最糟糕的地方,并且您可以有所收获

一些代码

from time import perf_counter as pc
from collections import defaultdict
import numpy as np
np.random.seed(0)

def manipulate(indices, coeff):
    u = np.unique(indices)
    sums  = { ui:np.sum([  coeff[c[0]] for c in np.argwhere(indices == ui) ]) for     ui in u }
    return list(sums.keys()),list(sums.values())

# this assumes pre-sorted indices
def manipulate_alt(indices, coeff):
  unique, indices = np.unique(indices, return_index=True)
  cum_coeffs = np.cumsum(coeff)
  bla = cum_coeffs[indices-1]
  blub = np.roll(bla, -1)
  bluab = np.ediff1d(blub, to_begin=blub[0])

  return unique.tolist(), bluab.tolist()

def manipulate_pure_py(indices, coeff):
  final = defaultdict(int)
  n = len(indices)
  for i in range(n):
    final[indices[i]] += coeff[i]

  return list(final.keys()), list(final.values())

# BAD NON-SCIENTIFIC BENCHMARK
# ----------------------------

ITERATIONS = 10
SIZE = 1000000

accu_time_manipulate = 0.0
accu_time_manipulate_alt = 0.0
accu_time_manipulate_py = 0.0

for i in range(ITERATIONS):
  indices = np.sort(np.random.randint(0, 10000, size=SIZE))
  coeffs = np.random.randint(1, 100, size=SIZE)

  start = pc()
  sol = manipulate(indices, coeffs)
  end = pc()
  accu_time_manipulate += (end-start)

  start = pc()
  sol_alt = manipulate_alt(indices, coeffs)
  end = pc()
  accu_time_manipulate_alt += (end-start)

  start = pc()
  sol_py = manipulate_pure_py(indices, coeffs)
  end = pc()
  accu_time_manipulate_py += (end-start)

  assert sol[0] == sol_alt[0]
  assert sol[1] == sol_alt[1]

  assert sol[0] == sol_py[0]
  assert sol[1] == sol_py[1]


print(accu_time_manipulate)
print(accu_time_manipulate_alt)
print(accu_time_manipulate_py)

时间

164.34614480000005
0.24998690000001744
8.751806900000059

备注

  • 基准不好
  • 我 100% 不信任基于 numpy 的 hack
  • 只需使用简单的基于字典的方法(或采用本机代码路线)
  • (取决于建模任务:您也可以在构建模型时结合此 merging:直接执行,不要延迟到 post-processing)