提高此 python 函数的性能

Improve performance of this python function

def _partition_by_context(self, labels, contexts):
    # partition the labels by context
    assert len(labels) == len(contexts)
    by_context = collections.defaultdict(list)
    for i, label in enumerate(labels):
        by_context[contexts[i]].append(label)

    # now remove any that don't have enough samples
    keys_to_remove = []
    for key, value in by_context.iteritems():
        if len(value) < self._min_samples_context:
            keys_to_remove.append(key)
    for key in keys_to_remove:
        del by_context[key]

    return by_context
  1. labels 是一个 numpy 的浮点数数组。
  2. contexts 是一个 python 元组列表。每个元组的形式为 (unicode, int): example (u'ffcd6881167b47d492adf3f542af94c6', 2)。上下文值经常重复。例如,上下文列表中可能有 10000 个值,但只有 100 个不同的值。
  3. len(labels) == len(contexts) 为真,如第一行所断言
  4. 索引 i 处的标签与索引 i 处的上下文关联。即 labels[i]contexts[i] "go together"

此函数的要点是按上下文值对标签中的值进行分区。然后在最后,如果标签计数太低,则删除字典条目。

因此,如果所有上下文值都相同,return值将是一个包含单个条目的字典,键=上下文,值=所有标签的列表。

如果有 N 个不同的上下文值,return 值将有 N 个键(每个上下文一个),每个键的值将是与特定上下文关联的标签列表。列表中标签的顺序并不重要。

此函数使用不同的参数被调用了数百万次。我确定这是使用 gprof2dot 的瓶颈。大部分成本都在第一个 for 循环中的列表 append() 调用中。

谢谢!

尝试替换

    for i, label in enumerate(labels):
        by_context[contexts[i]].append(label)

for context, label in zip(contexts, labels):
    by_context[context].append(label)

而不是使用 keys_to_remove,尝试

n = self._min_samples_context
return {c:ls for c,ls in by_context.items() if len(ls) >= n}

看起来像这两个数组会成为一个很好的测试用例:

N = 100
labels=np.arange(N)
contexts=np.random.randint(0,len(labels)/10,len(labels))

使用这些数组,@Hugh 的改进速度提高了大约 10%。

我在其他问题上的经验表明 defaultdict 是一种收集此类值的好方法。唯一可能更快的方法是将其转换为某种 numpy 索引问题。