距离度量的组合优化

时间:2010-05-13 19:41:28

标签: python algorithm numpy combinatorics itertools

我有一组轨迹,由沿轨迹的点和每个点的坐标组成。我将它们存储在一个3d数组中(轨迹,点,参数)。我想找到r轨迹的集合,这些轨迹具有这些轨迹的可能成对组合之间的最大累积距离。我认为我的第一次尝试是这样的:

max_dist = 0
for h in itertools.combinations ( xrange(num_traj), r):
    for (m,l) in itertools.combinations (h, 2):
        accum = 0.
        for ( i, j ) in itertools.izip ( range(k), range(k) ):
            A = [ (my_mat[m, i, z] - my_mat[l, j, z])**2 \
                    for z in xrange(k) ]
            A = numpy.array( numpy.sqrt (A) ).sum()
            accum += A
    if max_dist < accum:
        selected_trajectories = h

这需要永远,因为num_traj可以是大约500-1000,并且r可以是大约5-20。 k是任意的,但通常可以达到50。

尝试超级聪明,我把所有东西放到两个嵌套的列表推导中,大量使用itertools:

chunk = [[ numpy.sqrt((my_mat[m, i, :] - my_mat[l, j, :])**2).sum() \
        for ((m,l),i,j) in \
        itertools.product ( itertools.combinations(h,2), range(k), range(k)) ]\
        for h in itertools.combinations(range(num_traj), r) ]

除了难以辨认(!!!)之外,还需要很长时间。任何人都可以提出任何改进方法吗?

5 个答案:

答案 0 :(得分:3)

您可以从计算所有轨迹对之间的距离开始,而不是按需重新计算每对轨迹之间的距离。您可以将它们存储在字典中并根据需要进行查找。

这样你的内循环for (i,j) ...将被替换为常数时间查找。

答案 1 :(得分:2)

您可以在距离计算上放弃平方根计算...最大和也将具有最大平方和,尽管这只会产生恒定的加速。

答案 2 :(得分:2)

除了其他人提到的内容之外,这里还有一些兴趣点和建议。 (顺便说一句,mathmike建议生成一个查找列表,所有对距离都是你应该立即放置的。它从你的算法复杂性中去掉了一个O(r ^ 2)。)

首先是行

for ( i, j ) in itertools.izip ( range(k), range(k) ):
    A = [ (my_mat[m, i, z] - my_mat[l, j, z])**2 \
        for z in xrange(k) ]

可以替换为

for i in xrange(k):
    A = [ (my_mat[m, i, z] - my_mat[l, i, z])**2 \
        for z in xrange(k) ]

因为i和j在每个循环中总是相同的。这里根本不需要使用izip。

第二,关于线

A = numpy.array( numpy.sqrt (A) ).sum()

你确定这是你想要计算它的方式吗?可能是这样,但它让我感到奇怪,因为如果这更像是矢量之间的欧几里德距离那么这条线将是:

A = numpy.sqrt (numpy.array( A ).sum())

或只是

A = numpy.sqrt(sum(A))

因为我认为将A转换为numpy数组以使用numpy的sum函数会比使用内置的Python sum函数慢,但我可能错了。而且,如果它真的是你想要的欧几里德距离,那么你将以这种方式做更少的sqrt。

第三,您是否意识到您可能尝试迭代的潜在组合数量是多少?对于num_traj = 1000和r = 20的最坏情况,根据我的估计,这大约是6.79E42组合。这对你当前的方法来说非常棘手。即使对于num_traj = 500和r = 5的最佳情况,这是1.28E12组合,这是相当多的,但并非不可能。这是你在这里遇到的真正问题,因为通过参考mathmike的建议,我提到的前两点并不是很重要。

那你能做什么?好吧,你需要更聪明一些。我还不清楚用什么方法可以用来做这件事。我猜你需要以某种方式制作算法启发式算法。我有一个想法是尝试使用启发式的动态编程方法。对于每个轨迹,您可以找到每个轨迹与其他轨迹的每个配对的总和或平均值,并将其用作适应度量。在进入三重奏之前,可以放弃一些具有最低适应度的轨迹。然后你可以用三重奏做同样的事情:找到每个轨迹所涉及的所有三重奏(在剩余的可能轨迹中)的累积距离的总和或平均值,并将其用作适合度量以决定在移动之前丢弃哪些四人组。它不能保证最佳解决方案,但它应该非常好,它将大大降低我认为的解决方案的时间复杂性。

答案 3 :(得分:1)

无论如何,这可能会永远消失,因为你的算法需要大约〜O( C( N, r ) * r^2 ),其中C( N, r )是N选择r。对于较小的r(或N),这可能没问题,但如果您绝对需要找到最大值,而不是使用近似启发式,则应尝试使用不同策略进行分支绑定。这可能适用于较小的r,它可以为您节省不必要的重新计算。

答案 4 :(得分:1)

这听起来像是一个“加权集团”的问题:例如找到 r =具有最大兼容性/最大C(5,2)对权重的网络中的5个人 Google“加权集团”算法 - “clique percolation”→3k点击。
但我会选择Justin Peel的方法 因为它是可以理解和可控制的 (拿n2最好的对,从他们最好的n3三倍... 调整n2 n3 ...以轻松权衡运行时间/结果质量。)

已添加18,可以在实施后进行切割 @Jose,看看nbest []序列对你有用会很有趣。

#!/usr/bin/env python
""" cliq.py: grow high-weight 2 3 4 5-cliques, taking nbest at each stage
    weight ab = dist[a,b] -- a symmetric numpy array, diag << 0
    weight abc, abcd ... = sum weight all pairs
    C[2] = [ (dist[j,k], (j,k)) ... ]  nbest[2] pairs
    C[3] = [ (cliqwt(j,k,l), (j,k,l)) ... ]  nbest[3] triples
    ...
    run time ~ N * (N + nbest[2] + nbest[3] ...)

keywords: weighted-clique heuristic python
"""
# cf "graph clustering algorithm"

from __future__ import division
import numpy as np

__version__ = "denis 18may 2010"
me = __file__.split('/') [-1]

def cliqdistances( cliq, dist ):
    return sorted( [dist[j,k] for j in cliq  for k in cliq if j < k], reverse=True )

def maxarray2( a, n ):
    """ -> max n [ (a[j,k], (j,k)) ...]  j <= k, a symmetric """
    jkflat = np.argsort( a, axis=None )[:-2*n:-1]
    jks = [np.unravel_index( jk, a.shape ) for jk in jkflat]
    return [(a[j,k], (j,k)) for j,k in jks if j <= k] [:n]

def _str( iter, fmt="%.2g" ):
    return " ".join( fmt % x  for x in iter )

#...............................................................................

def maxweightcliques( dist, nbest, r, verbose=10 ):

    def cliqwt( cliq, p ):
        return sum( dist[c,p] for c in cliq )  # << 0 if p in c

    def growcliqs( cliqs, nbest ):
        """ [(cliqweight, n-cliq) ...] -> nbest [(cliqweight, n+1 cliq) ...] """
            # heapq the nbest ? here just gen all N * |cliqs|, sort
        all = []
        dups = set()
        for w, c in cliqs:
            for p in xrange(N):
                    # fast gen [sorted c+p ...] with small sorted c ?
                cp = c + [p]
                cp.sort()
                tup = tuple(cp)
                if tup in dups:  continue
                dups.add( tup )
                all.append( (w + cliqwt(c, p), cp ))
        all.sort( reverse=True )
        if verbose:
            print "growcliqs: %s" % _str( w for w,c in all[:verbose] ) ,
            print " best: %s" % _str( cliqdistances( all[0][1], dist )[:10])
        return all[:nbest]

    np.fill_diagonal( dist, -1e10 )  # so cliqwt( c, p in c ) << 0
    C = (r+1) * [(0, None)]  # [(cliqweight, cliq-tuple) ...]
        # C[1] = [(0, (p,)) for p in xrange(N)]
    C[2] = [(w, list(pair)) for w, pair in maxarray2( dist, nbest[2] )]
    for j in range( 3, r+1 ):
        C[j] = growcliqs( C[j-1], nbest[j] )
    return C

#...............................................................................
if __name__ == "__main__":
    import sys

    N = 100
    r = 5  # max clique size
    nbest = 10
    verbose = 0
    seed = 1
    exec "\n".join( sys.argv[1:] )  # N= ...
    np.random.seed(seed)
    nbest = [0, 0, N//2] + (r - 2) * [nbest]  # ?

    print "%s  N=%d  r=%d  nbest=%s"  % (me, N, r, nbest)

        # random graphs w cluster parameters ?
    dist = np.random.exponential( 1, (N,N) )
    dist = (dist + dist.T) / 2
    for j in range( 0, N, r ):
        dist[j:j+r, j:j+r] += 2  # see if we get r in a row
    # dist = np.ones( (N,N) )

    cliqs = maxweightcliques( dist, nbest, r, verbose )[-1]  # [ (wt, cliq) ... ]

    print "Clique weight,  clique,  distances within clique"
    print 50 * "-"
    for w,c in cliqs:
        print "%5.3g  %s  %s" % (
            w, _str( c, fmt="%d" ), _str( cliqdistances( c, dist )[:10]))
相关问题