如何在python中优化矩阵的数学运算

时间:2014-04-12 16:55:23

标签: python numpy matrix heuristics

我正在尝试减少使用两个矩阵执行一系列计算的函数的时间。寻找这个,我听说过numpy,但我真的不知道如何将它应用到我的问题中。此外,我认为其中一个原因是我的功能很慢,因为有许多点运算符(我在this page中听说过这个。)

数学对应于二次分配问题的分解:

QAP Factorization

我的代码是:

    delta = 0
    for k in xrange(self._tam):
        if k != r and k != s:
            delta +=
                self._data.stream_matrix[r][k] \
                * (self._data.distance_matrix[sol[s]][sol[k]] - self._data.distance_matrix[sol[r]][sol[k]]) + \
                self._data.stream_matrix[s][k] \
                * (self._data.distance_matrix[sol[r]][sol[k]] - self._data.distance_matrix[sol[s]][sol[k]]) + \
                self._data.stream_matrix[k][r] \
                * (self._data.distance_matrix[sol[k]][sol[s]] - self._data.distance_matrix[sol[k]][sol[r]]) + \
                self._data.stream_matrix[k][s] \
                * (self._data.distance_matrix[sol[k]][sol[r]] - self._data.distance_matrix[sol[k]][sol[s]])
    return delta

在大小为20(Matrix为20x20)的问题上运行此操作需要大约20个segs,瓶颈在于此功能

ncalls  tottime  percall  cumtime  percall filename:lineno(function)
303878   15.712    0.000   15.712    0.000 Heuristic.py:66(deltaC)

我尝试将map应用于for循环,但因为循环体不是函数调用,所以不可能。

我怎样才能减少时间?

EDIT1

回答eickenberg评论:

sol是一种排列,例如[1,2,3,4]。当我生成邻居解决方案时调用该函数,因此,[1,2,3,4]的邻居是[2,1,3,4]。我在原始排列中只改变了两个位置,然后调用deltaC,它计算了位置r,s swaped的解的分解(在上面的例子中r,s = 0,1)。进行这种排列是为了避免计算邻居解决方案的全部成本。我想我可以将sol[k,r,s]的值存储在局部变量中,以避免在每次迭代中查找其值。 我不知道这是否是您在评论中提出的问题。

EDIT2

最小的工作示例:

import random


distance_matrix = [[0, 12, 6, 4], [12, 0, 6, 8], [6, 6, 0, 7], [4, 8, 7, 0]]
stream_matrix = [[0, 3, 8, 3], [3, 0, 2, 4], [8, 2, 0, 5], [3, 4, 5, 0]]

def deltaC(r, s, S=None):
    '''
    Difference between C with values i and j swapped
    '''

    S = [0,1,2,3]

    if S is not None:
        sol = S
    else:
        sol = S

    delta = 0

    sol_r, sol_s = sol[r], sol[s]

    for k in xrange(4):
        if k != r and k != s:
            delta += (stream_matrix[r][k] \
                * (distance_matrix[sol_s][sol[k]] - distance_matrix[sol_r][sol[k]]) + \
                stream_matrix[s][k] \
                * (distance_matrix[sol_r][sol[k]] - distance_matrix[sol_s][sol[k]]) + \
                stream_matrix[k][r] \
                * (distance_matrix[sol[k]][sol_s] - distance_matrix[sol[k]][sol_r]) + \
                stream_matrix[k][s] \
                * (distance_matrix[sol[k]][sol_r] - distance_matrix[sol[k]][sol_s]))
    return delta


for _ in xrange(303878):
    d = deltaC(random.randint(0,3), random.randint(0,3))
print d

现在我认为更好的选择是使用NumPy。我尝试使用Matrix(),但没有提高性能。

找到最佳解决方案

好吧,最后我能够将@ TooTone的解决方案和将索引存储在一个集合中以减少时间,以避免if。时间从大约18秒下降到8秒。这是代码:

def deltaC(self, r, s, sol=None):
    delta = 0
    sol = self.S if sol is None else self.S
    sol_r, sol_s = sol[r], sol[s]

    stream_matrix = self._data.stream_matrix
    distance_matrix = self._data.distance_matrix

    indexes = set(xrange(self._tam)) - set([r, s])

    for k in indexes:
        sol_k = sol[k]
        delta += \
            (stream_matrix[r][k] - stream_matrix[s][k]) \
            * (distance_matrix[sol_s][sol_k] - distance_matrix[sol_r][sol_k]) \
            + \
            (stream_matrix[k][r] - stream_matrix[k][s]) \
            * (distance_matrix[sol_k][sol_s] - distance_matrix[sol_k][sol_r])
    return delta

为了减少时间,我认为最好的方法是编写一个模块。

1 个答案:

答案 0 :(得分:6)

在您给出的简单示例中,for k in xrange(4):循环体仅执行两次(如果r==s),或三次(如果r!=s)和初始numpy下面的实现因为一个很大的因素而变慢。 Numpy针对长向量执行计算进行了优化,如果向量很短,则开销可能超过收益。 (并注意在这个公式中,矩阵被切割成不同的维度,并且非连续索引,这只会使向量化实现更复杂)。

import numpy as np

distance_matrix_np = np.array(distance_matrix)
stream_matrix_np = np.array(stream_matrix)
n = 4

def deltaC_np(r, s, sol):
    delta = 0
    sol_r, sol_s = sol[r], sol[s]

    K = np.array([i for i in xrange(n) if i!=r and i!=s])

    return np.sum(
        (stream_matrix_np[r,K] - stream_matrix_np[s,K]) \
        *  (distance_matrix_np[sol_s,sol[K]] - distance_matrix_np[sol_r,sol[K]]) + \
        (stream_matrix_np[K,r] - stream_matrix_np[K,s]) \
        * (distance_matrix_np[sol[K],sol_s] - distance_matrix_np[sol[K],sol_r]))

在这个numpy实现中,而不是forK元素的K循环,操作将应用于numpy中(self._data.distance_matrix[sol[s]][sol[k]] - self._data.distance_matrix[sol[r]][sol[k]])中的所有元素。另请注意,您的数学表达式可以简化。左侧括号中的每个术语都是右侧括号中的术语的否定。 enter image description here

这也适用于您的原始代码。例如,(self._data.distance_matrix[sol[r]][sol[k]] - self._data.distance_matrix[sol[s]][sol[k]])等于K = np.array([i for i in xrange(n) if i!=r and i!=s]) 的-1倍,因此您进行了不必要的计算,并且可以在不使用numpy的情况下优化原始代码。

事实证明,numpy函数的瓶颈是无辜的列表理解

if r==s:
    K=np.arange(n-1)
    K[r:] += 1
else:
    K=np.arange(n-2)
    if r<s:
        K[r:] += 1
        K[s-1:] += 1
    else:
        K[s:] += 1
        K[r-1:] += 1

将其替换为矢量化代码

deltaC2

numpy函数很多更快。

运行时间图显示在下面(此答案的底部右侧是优化numpy函数之前的原始图表)。您可以看到使用优化的原始代码或numpy代码是有意义的,具体取决于矩阵的大小。

enter image description here

以下是完整的代码供参考,部分原因是其他人可以采取进一步措施。 (函数def deltaC(r, s, sol): delta = 0 sol_r, sol_s = sol[r], sol[s] for k in xrange(n): if k != r and k != s: delta += \ stream_matrix[r][k] \ * (distance_matrix[sol_s][sol[k]] - distance_matrix[sol_r][sol[k]]) + \ stream_matrix[s][k] \ * (distance_matrix[sol_r][sol[k]] - distance_matrix[sol_s][sol[k]]) + \ stream_matrix[k][r] \ * (distance_matrix[sol[k]][sol_s] - distance_matrix[sol[k]][sol_r]) + \ stream_matrix[k][s] \ * (distance_matrix[sol[k]][sol_r] - distance_matrix[sol[k]][sol_s]) return delta import numpy as np def deltaC_np(r, s, sol): delta = 0 sol_r, sol_s = sol[r], sol[s] if r==s: K=np.arange(n-1) K[r:] += 1 else: K=np.arange(n-2) if r<s: K[r:] += 1 K[s-1:] += 1 else: K[s:] += 1 K[r-1:] += 1 #K = np.array([i for i in xrange(n) if i!=r and i!=s]) #TOO SLOW return np.sum( (stream_matrix_np[r,K] - stream_matrix_np[s,K]) \ * (distance_matrix_np[sol_s,sol[K]] - distance_matrix_np[sol_r,sol[K]]) + \ (stream_matrix_np[K,r] - stream_matrix_np[K,s]) \ * (distance_matrix_np[sol[K],sol_s] - distance_matrix_np[sol[K],sol_r])) def deltaC2(r, s, sol): delta = 0 sol_r, sol_s = sol[r], sol[s] for k in xrange(n): if k != r and k != s: sol_k = sol[k] delta += \ (stream_matrix[r][k] - stream_matrix[s][k]) \ * (distance_matrix[sol_s][sol_k] - distance_matrix[sol_r][sol_k]) \ + \ (stream_matrix[k][r] - stream_matrix[k][s]) \ * (distance_matrix[sol_k][sol_s] - distance_matrix[sol_k][sol_r]) return delta import time N=200 elapsed1s = [] elapsed2s = [] elapsed3s = [] ns = range(10,410,10) for n in ns: distance_matrix_np=np.random.uniform(0,n**2,size=(n,n)) stream_matrix_np=np.random.uniform(0,n**2,size=(n,n)) distance_matrix=distance_matrix_np.tolist() stream_matrix=stream_matrix_np.tolist() sol = range(n-1,-1,-1) sol_np = np.array(range(n-1,-1,-1)) Is = np.random.randint(0,n-1,4) Js = np.random.randint(0,n-1,4) total1 = 0 start = time.clock() for reps in xrange(N): for i in Is: for j in Js: total1 += deltaC(i,j, sol) elapsed1 = (time.clock() - start) start = time.clock() total2 = 0 start = time.clock() for reps in xrange(N): for i in Is: for j in Js: total2 += deltaC_np(i,j, sol_np) elapsed2 = (time.clock() - start) total3 = 0 start = time.clock() for reps in xrange(N): for i in Is: for j in Js: total3 += deltaC2(i,j, sol_np) elapsed3 = (time.clock() - start) print n, elapsed1, elapsed2, elapsed3, total1, total2, total3 elapsed1s.append(elapsed1) elapsed2s.append(elapsed2) elapsed3s.append(elapsed3) #Check errors of one method against another #err = 0 #for i in range(min(n,50)): # for j in range(min(n,50)): # err += np.abs(deltaC(i,j,sol)-deltaC_np(i,j,sol_np)) #print err import matplotlib.pyplot as plt plt.plot(ns, elapsed1s, label='Original',lw=2) plt.plot(ns, elapsed3s, label='Optimized',lw=2) plt.plot(ns, elapsed2s, label='numpy',lw=2) plt.legend(loc='upper left', prop={'size':16}) plt.xlabel('matrix size') plt.ylabel('time') plt.show() 是您优化的原始代码,用于考虑数学表达式的简化方式。)

deltaC_np

以下是优化{{1}}

中列表理解之前的原始图表

enter image description here