Python 查找“;“最佳”;完全子图
在优化我的一个应用程序的性能时,我在几行(Python)代码中遇到了巨大的性能瓶颈 我有N个代币。每个令牌都有一个分配给它的值。一些标记相互矛盾(例如,标记8和标记12不能“共存”)。我的工作是找到k-best令牌组。一组令牌的值只是其中令牌值的总和 天真算法(我已经实现了…):Python 查找“;“最佳”;完全子图,python,graph,Python,Graph,在优化我的一个应用程序的性能时,我在几行(Python)代码中遇到了巨大的性能瓶颈 我有N个代币。每个令牌都有一个分配给它的值。一些标记相互矛盾(例如,标记8和标记12不能“共存”)。我的工作是找到k-best令牌组。一组令牌的值只是其中令牌值的总和 天真算法(我已经实现了…): 查找令牌的所有2^N令牌组置换 消除其中存在矛盾的标记组 计算所有剩余令牌组的值 按值对令牌组排序 选择前K个令牌组 真实世界的数字-我需要从一组20个令牌中选出前10个令牌组(我计算了1000000个排列(!),缩小
all_possibilities = sum((list(itertools.combinations(token_list, i)) for i in xrange(len(token_list)+1)), [])
all_possibilities = [list(option) for option in all_possibilities if self._no_contradiction(option)]
all_possibilities = [(option, self._probability(option)) for option in all_possibilities]
all_possibilities.sort(key = lambda result: -result[1]) # sort by descending probability
请帮忙
好的。步骤1+2的简单方法如下:首先,定义一个标记列表和一个矛盾字典(每个键都是一个标记,每个值都是一组标记)。然后,对每个令牌采取两个操作:
- 如果尚未冲突,则将其添加到
,并使用与当前添加的令牌冲突的令牌增加结果
集冲突
- 不要将其添加到
(选择忽略它)并移动到下一个标记结果
token_list = ['a', 'b', 'c']
contradictions = {
'a': set(['b']),
'b': set(['a']),
'c': set()
}
class Generator(object):
def __init__(self, token_list, contradictions):
self.list = token_list
self.contradictions = contradictions
self.max_start = len(self.list) - 1
def add_no(self, start, result, conflicting):
if start < self.max_start:
for g in self.gen(start + 1, result, conflicting):
yield g
else:
yield result[:]
def add_yes(self, token, start, result, conflicting):
result.append(token)
new_conflicting = conflicting | self.contradictions[token]
for g in self.add_no(start, result, new_conflicting):
yield g
result.pop()
def gen(self, start, result, conflicting):
token = self.list[start]
if token not in conflicting:
for g in self.add_yes(token, start, result, conflicting):
yield g
for g in self.add_no(start, result, conflicting):
yield g
def go(self):
return self.gen(0, [], set())
这是一个递归算法,因此它对几千个令牌不起作用(因为Python的堆栈限制),但您可以轻松创建一个非递归算法。这里有一个可能的“启发式优化”方法和一个小示例:
import itertools
# tokens in decreasing order of value (must all be > 0)
toks = 12, 11, 8, 7, 6, 2, 1
# contradictions (dict highestvaltok -> set of incompatible ones)
cont = {12: set([11, 8, 7, 2]),
11: set([8, 7, 6]),
7: set([2]),
2: set([1]),
}
rec_calls = 0
def bestgroup(toks, contdict, arein=(), contset=()):
"""Recursively compute the highest-valued non-contradictory subset of toks."""
global rec_calls
toks = list(toks)
while toks:
# find the top token compatible w/the ones in `arein`
toptok = toks.pop(0)
if toptok in contset:
continue
# try to extend with and without this toptok
without_top = bestgroup(toks, contdict, arein, contset)
contset = set(contset).union(c for c in contdict.get(toptok, ()))
newarein = arein + (toptok,)
with_top = bestgroup(toks, contdict, newarein, contset)
rec_calls += 1
if sum(with_top) > sum(without_top):
return with_top
else:
return without_top
return arein
def noncongroups(toks, contdict):
"""Count possible, non-contradictory subsets of toks."""
tot = 0
for l in range(1, len(toks) + 1):
for c in itertools.combinations(toks, l):
if any(cont[k].intersection(c) for k in c if k in contdict): continue
tot += 1
return tot
print bestgroup(toks, cont)
print 'calls: %d (vs %d of %d)' % (rec_calls, noncongroups(toks, cont), 2**len(toks))
我相信只要存在可行的(非矛盾的)子集,就会产生尽可能多的递归调用,但还没有证明它(所以我只是计算两者-非组
当然与解决方案无关,它只是用来检查行为属性;-)
如果这在您的“实际用例”基准测试上产生了可接受的加速,那么进一步的优化可能会引入alpha剪枝(这样您就可以停止沿着您知道的非生产性路径的递归——这就是标记中降序的动机;-)和递归消除(改为在函数中使用显式堆栈)。但是我想保持第一个版本的简单,这样就可以很容易地理解和验证它(而且,我认为进一步的优化只会起到很小的作用,比如说,最好是将典型的运行时减少一半,即使有那么多)。anO(n(logn))
或O(n+m)
令牌和字符串长度m
你的问题与NP完全群体问题的区别在于,你的“冲突”图是有结构的——也就是说,它可以投影到一维上(可以排序)
这意味着您可以分而治之;毕竟,不重叠的范围彼此没有影响,因此不需要探索完整的状态空间。特别是,动态规划解决方案将起作用
算法概要
[start,end)
(即inclusive start,exclusive end)。按令牌end对令牌列表进行排序,我们将迭代它们- 如果索引
的最佳子集包括J
,则它不能包括与该标记重叠的任何标记-尤其是,因为我们按token[J]
排序,所以该列表中有最后一个标记token.end
和K
token[K].end下面的解决方案生成所有最大不矛盾子集,利用这样一个事实,即从解决方案中省略一个元素没有意义,除非它与解决方案中的另一个元素相矛盾 在元素t不与任何剩余元素冲突的情况下,避免第二次递归的简单优化应该有助于在冲突数量较少的情况下使该解决方案有效
def solve(tokens, contradictions): if not tokens: yield set() else: tokens = set(tokens) t = tokens.pop() for solution in solve(tokens - contradictions[t], contradictions): yield solution | set([t]) if contradictions[t] & tokens: for solution in solve(tokens, contradictions): if contradictions[t] & solution: yield solution
此解决方案还表明,对于某些类型的输入,动态规划(也称为记忆)可能有助于进一步提高解决方案的性能。获取所有非冲突令牌组的一种非常简单的方法:#!/usr/bin/env python token_list = ['a', 'b', 'c'] contradictions = { 'a': set(['b']), 'b': set(['a']), 'c': set() } result = [] while token_list: token = token_list.pop() new = [set([token])] for r in result: if token not in contradictions or not r & contradictions[token]: new.append(r | set([token])) result.extend(new) print result
帮助理解问题a#!/usr/bin/env python token_list = ['a', 'b', 'c'] contradictions = { 'a': set(['b']), 'b': set(['a']), 'c': set() } result = [] while token_list: token = token_list.pop() new = [set([token])] for r in result: if token not in contradictions or not r & contradictions[token]: new.append(r | set([token])) result.extend(new) print result