【问题标题】:Fast method to cycle through multiple lists of tuples to find max of each tuple list循环遍历多个元组列表以查找每个元组列表的最大值的快速方法
【发布时间】:2021-07-20 07:56:51
【问题描述】:

我有数以万计的元组列表,其中列表中的每个元组都由一个 (int, float) 对组成。我希望能够循环遍历所有元组列表以找到 (int, float) 对,其中 float 是元组列表中浮点数的最大值。考虑几个元组列表:

[
[(0, 0.3792), (3, 0.5796)],
[0, 0.9365), (1, 0.0512), (18, 0.0123),
[(13, 0.8642)],
[(0, 0.6249), (1, 0.01), (2, 0.01), (3, 0.01), (4, 0.01), (5, 0.01)]
]

对于每个元组列表,我想找到第二个数字最大化的对(例如,对于第一个列表,我想要 (3, 0.5796);对于第四个项目,应该返回 (0, 0.6249))。我目前的做法是将元组变成numpy数组,然后找到argmax和max:

def get_max(doc: List[Tuple[int, float]]) -> Tuple[int, float]:
            
   topic_prob_array = np.array(doc, dtype=np.dtype('int,float'))
   return topic_prob_array['f0'][np.argmax(topic_prob_array['f1'])], np.max(topic_prob_array['f1'])

我希望把它变成一个 numpy 矢量化函数(通过vec_func = np.vectorized(get_max, otypes=[int,float]) 或 numpy ufunc(通过vec_func = np.fromfunc(get_max, nin=1, nout=1)。我不确定我是否正确格式化了输入和输出。我的理由是我正在发送一个单个元组列表并返回单个元组,因此 nin=1, nout=1。但是,我无法成功地运行它的矢量化版本。

我也尝试了一个不依赖numpy的解决方案:

def get_max(doc: List[Tuple[int, float]]) -> Tuple[int, float]:

   ids, probabilities = zip(*doc)
   return ids[np.argmax(probabilities)], np.max(probabilities)

两者的运行时间大致相同。对于我大约 80k 的列表,这两种实现都需要大约 1 分 10 秒。如果可能的话,我真的很想把它写下来。

【问题讨论】:

  • 你在寻找一个 numpy 的答案吗?
  • np.vectorize 尽管有这个名字,但在快速编译函数的意义上,它并没有“向量化”。 np.frompyfuncnp.vectorize 快,但仍然不比简单的列表理解快。此外,它们将“标量”值传递给函数。你想传递一个子列表。

标签: python arrays numpy tuples vectorization


【解决方案1】:

您需要为此使用numpy 吗?我们可以在整个数据集中采用函数式方法和 map max 函数以及自定义 key

from functools import partial
from operator import itemgetter

snd = itemgetter(1)
p = partial(max, key=snd)
list(map(p, data))
>>> [(3, 0.5796), (0, 0.9365), (13, 0.8642), (0, 0.6249)]

然后对原始数据集中的 80K 随机元组进行快速计时。

from random import choice

result = []
for _ in range(80_000):
    result.append(choice(data))

%timeit list(map(p, result))
42.2 ms ± 686 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

【讨论】:

  • 我想你忘了在这里定义snd。考虑到 itemgetter 导入,猜你的意思是我使用的相同基本定义?
  • 是的,很好,刚刚更新,同样的方法只是更多的功能扭曲
  • 嗯,listcomps 和map 一样是一个功能结构。也就是说,如果输入足够大,您的解决方案可能会更快(map 其中映射函数可以将所有工作推送到 C 层通常比必须运行字节码的 listcomp 在listcomp)。
  • 感谢您的回答;我以前从未听说过partial,但我可能会更频繁地开始使用它。针对我的数据集(由 83,170 个文档组成,其中一些在列表中包含多达 20 个元组),此代码产生了1min 2s ± 335 ms per loop,而@ShadowRanger 代码产生了1min 2s ± 303 ms per loop。有道理,因为代码相似。我原来的非numpy 代码是1min 11s ± 640 ms per loop。这是一个很好的改进,但我认为我受到数据结构的限制。
  • @CopyOfA:感谢您的时间;您介意说出您使用的是哪个版本的 Python 吗?我假设3.8+;您的输入足够大,在过去,我预计 map 会获胜(通过避免重复查找像 max 这样的内置函数,并避免每个项目的字节码解释器开销),但是 LOAD_GLOBAL "per操作码缓存”introduced in 3.8 可能意味着内置的加载开销低于现在 partial 的包装开销,足以弥补字节码解释器的开销。
【解决方案2】:

就像提到的@gold_cy 一样,我不确定您是否正在寻找numpy 的答案。非numpy 的答案可能是:

list_tuple = [
    [(0, 0.3792), (3, 0.5796)],
    [(0, 0.9365), (1, 0.0512), (18, 0.0123)],
    [(13, 0.8642)],
    [(0, 0.6249), (1, 0.01), (2, 0.01), (3, 0.01), (4, 0.01), (5, 0.01)]
]

[sorted(tup, key=lambda x: x[1], reverse=True).pop(0) for tup in list_tuple]
>>> [(3, 0.5796), (0, 0.9365), (13, 0.8642), (0, 0.6249)]

【讨论】:

  • 这里没有理由使用sorted;这涉及到O(n log n) 排序工作和一堆临时的lists,而key-ed max 会做O(n) 的工作并且不涉及临时的lists。
  • @ShadowRanger 嗯,很有趣 - 我以前从未见过你的回答。我会检查一下并学习一些新东西。
  • 这是我发现的常见疏忽;当max/min 会做这项工作时(或者当他们需要多个@987654335 @/min,但只是输入的一小部分,heapq.nlargest/heapq.nsmallest)。你的回答并不糟糕,只是做了一些不必要的工作。
【解决方案3】:

对此的优化非numpy 解决方案是:

from operator import itemgetter

get1 = itemgetter(1)

all_lists = [...]  # Whatever your actual list of list of tuples comes from

all_maxes = [max(lst, key=get1) for lst in all_lists]

numpy 不太可能为您带来太多收益,因为所做的工作相对便宜,而且如果您只是转换为 numpy 数组进行单个操作,则收益范围较小。

【讨论】:

  • 谢谢。我应该指定非numpy 解决方案完全没问题。我只是在想,numpy.vectorize 可能会有所帮助。这个解决方案比我实施的解决方案快了大约 10-15 秒,这很有帮助。我真的只是希望能够进一步降低处理速度,但似乎我受到数据结构的限制。无论如何,谢谢!
【解决方案4】:
In [462]: alist
Out[462]: 
[[(0, 0.3792), (3, 0.5796)],
 [(0, 0.9365), (1, 0.0512), (18, 0.0123)],
 [(13, 0.8642)],
 [(0, 0.6249), (1, 0.01), (2, 0.01), (3, 0.01), (4, 0.01), (5, 0.01)]]
In [463]: blist = alist*10000    # bigger test list

玩弄替代品,我发现这个“蛮力”功能是最快的(虽然不是很多):

def get_max3(doc):
    m = doc[0]
    for i in doc[1:]:
        if i[1]>m[1]: m=i
    return m

对于小列表,列表理解稍快,对于大列表,地图版本有优势 - 但幅度不大。

In [465]: [get_max3(i) for i in alist]
Out[465]: [(3, 0.5796), (0, 0.9365), (13, 0.8642), (0, 0.6249)]

In [466]: timeit [get_max3(i) for i in alist]
1.9 µs ± 51.9 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
In [467]: timeit list(map(get_max3,blist))
15 ms ± 7.77 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

使用numpy 的版本都慢得多;将元组列表转换为 numpy 数组(甚至是结构化数组)需要时间。

【讨论】:

  • 是的,我同意numpy,在确定解决方案的时间之后,numpy 似乎需要更长的时间,而且转换肯定在其中发挥了作用。在这种转变中并没有什么收获,所以都是损失。感谢您的帮助!
猜你喜欢
  • 2013-04-07
  • 2013-11-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-11-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多