Python 数组的平均值

Python 数组的平均值,python,numpy,Python,Numpy,我有一个这样的结构: a = np.array([[np.array([1,2,3]), np.array([2])], [np.array([0,1,2,3,4]), np.array([0,4])]]) 它是一个2x2结构,每个元素可以有任意尺寸 我想得到a的每个元素的平均值: 我试着玩axis,但没用。我想得到一个新的np.array,等于[[2,2],[2,2] 一般来说,我希望能够以相同的方式在上运行任何向量化函数 怎么做?我需要快速代码,所以请避免显式循环 我所能做的就是: f =

我有一个这样的结构:

a = np.array([[np.array([1,2,3]), np.array([2])], [np.array([0,1,2,3,4]), np.array([0,4])]])
它是一个2x2结构,每个元素可以有任意尺寸

我想得到a的每个元素的平均值:

我试着玩axis,但没用。我想得到一个新的np.array,等于[[2,2],[2,2]

一般来说,我希望能够以相同的方式在上运行任何向量化函数

怎么做?我需要快速代码,所以请避免显式循环

我所能做的就是:

f = np.mean
result = np.zeros(a.shape)
for i in np.ndindex(a.shape):
  result[i] = f(a[i])

这是我能做的最好的:

a = np.array([[np.array([1,2,3]), np.array([2])], [np.array([0,1,2,3,4]), np.array([0,4])]])
np.array([ np.mean(b) for b in a.ravel()]).reshape(a.shape)
输出

array([[ 2.,  2.],
       [ 2.,  2.]])
可推广用于其他功能,如:

def ravel_func(func,arr):
    return np.asarray([ func(part) for part in arr.ravel()]).reshape(arr.shape)
速度测试,多亏了


这是我能做的最好的:

a = np.array([[np.array([1,2,3]), np.array([2])], [np.array([0,1,2,3,4]), np.array([0,4])]])
np.array([ np.mean(b) for b in a.ravel()]).reshape(a.shape)
输出

array([[ 2.,  2.],
       [ 2.,  2.]])
可推广用于其他功能,如:

def ravel_func(func,arr):
    return np.asarray([ func(part) for part in arr.ravel()]).reshape(arr.shape)
速度测试,多亏了

我想你想要的基本上就像是Ndarays的内置地图

>>> a = np.array([[np.array([1,2,3]), np.array([2])], [np.array([0,1,2,3,4]), np.array([0,4])]])
>>> vmean = np.vectorize(np.mean)
>>> vmean(a)
array([[ 2.,  2.],
       [ 2.,  2.]])
我想你想要的基本上就像是Ndarays的内置地图

>>> a = np.array([[np.array([1,2,3]), np.array([2])], [np.array([0,1,2,3,4]), np.array([0,4])]])
>>> vmean = np.vectorize(np.mean)
>>> vmean(a)
array([[ 2.,  2.],
       [ 2.,  2.]])

从你的评论来看:你有相对较少的相当大的元素。这意味着外部循环的迭代速度是不相关的,而内部循环的迭代速度是至关重要的

让我们把一些实际的数字放在这上面。你的外部数组最多有4个维度,最大10个维度。这意味着有多达10000个元素。同时,元素相当大,让我们保守地解释为只有50个。因此,你有510000个循环迭代。任何提高10000个外部迭代速度的方法都会减少事实上,这远远低于2%假设除了迭代本身没有其他工作要做,这显然是不正确的

所以,你把重点放在了错误的地方。只有500000次内部迭代才有意义。如果你可以用一个一维更高的数组替换数组数组数组,并在numpy中完成这一切,它可能会更快,但让你的代码更复杂,更难理解,只需获得1%的分数,这是愚蠢的。只需使用矢量化简单明了的回答或理解性回答

同时:

也许我应该尝试并行计算,对每个矩阵元素使用一个线程

并行性在这里是一个好主意,但不使用线程,也不是每个元素一个线程

比如说,如果你有一台8核的机器,使用8个硬件线程意味着你完成事情的速度快了近8倍。但使用10000个硬件线程并不意味着你完成事情的速度快了10000倍,甚至快了8倍。你可能要花太多时间切换上下文、分配和释放线程堆栈等,这实际上需要花费很长时间因此,您希望创建一个包含8个硬件线程的工作线程池,并将10000个任务放入一个队列中,由该池运行

此外,10000可能太细粒度了。每个作业都有一点开销,如果作业太小,则会在开销上浪费太多时间。批处理的明显方法是每个轴有1000个作业,每个作业执行一行10个元素,或者100个作业,每个作业执行一个包含100个元素的2D数组。测试批处理大小为0、1、2,和3个轴,看哪一个性能最好。如果差异很大,您可能需要尝试稍微复杂一点的批处理,比如分成3x10x10和4x10x10的3D阵列

最后,虽然Python线程是真正的操作系统,因此是硬件线程,但GIL阻止它们中的多个同时运行Python代码。numpy为此做了一些工作,但它并不完美,特别是在numpy调用之间有大量开销的情况下。解决这一问题的最简单方法是使用多处理,这让你有一个由8个独立进程组成的池,而不是一个进程中的8个线程。这大大增加了开销。你或者需要隐式复制子数组,作为任务函数的参数和返回值,或者显式复制子数组,或者通过管道复制子数组,或者将子数组放在共享内存中。但是如果你的任务大小足够大,通常不会一个问题

下面是一个如何并行化现有代码的示例:

f = np.mean
result = np.zeros(a.shape)
future_map = {}
with futures.ProcessPoolExecutor(max_workers=8) as executor:
    for i in np.ndindex(a.shape):
        future_map[executor.submit(f, a[i])] = i
    for future in futures.as_completed(future_map):
        i = future_map[future]
        result[i] = future.result()
显然,您可以简化它,例如,用dict理解替换提交循环,但我想让它尽可能清楚地显示您需要更改的内容

另外,我使用的是需要3.2+的;如果您使用的是2.7,请从PyPI安装,因为它使代码更简单;如果您需要更大的灵活性,则需要更复杂的库

最后,我不做任何批处理,因为每个任务都是单个元素,所以开销可能会非常大


但从这一点开始,尽可能简化它,然后将其转换为使用1、2和3个轴的批,如前所述。

从您的评论中可以看出:您拥有的相当大的元素数量相对较少。这意味着外部循环的迭代速度是无关的,而内部循环的迭代速度是不相关的至关重要

让我们放一些实际的数字 在这个问题上。外部阵列最多有4个维度,大小不超过10。这意味着最多有10000个元素。同时,元素相当大,让我们保守地解释为只有50个。因此,您有510000次循环迭代。为了提高10000次外部迭代的速度所做的任何事情都将使代码中的差异不到2%。事实上,这远远低于2%假设除了迭代本身没有工作要做,这显然是不正确的

所以,你把注意力放错地方了。只有内部迭代才重要。如果您可以用一个一维数组替换数组数组,并使用numpy完成所有操作,那么可能会更快,但如果只获得1%的分数,那么将代码变得更复杂、更难理解是愚蠢的。只需使用矢量化答案或理解性答案,简单明了

同时:

也许我应该尝试并行计算,对每个矩阵元素使用一个线程

并行性在这里是个好主意。但不使用线程,每个元素不使用一个线程

比如说,如果你有一台8核的机器,使用8个硬件线程意味着你完成事情的速度快了近8倍。但是使用10000个硬件线程并不意味着你完成事情的速度是10000倍,甚至是8倍。你可能会花太多时间切换上下文、分配和释放线程堆栈,等等。实际上,这比顺序版本需要更长的时间。因此,您希望创建一个包含8个硬件线程的工作线程池,并将10000个任务放入一个队列中,由该池运行

而且,10000可能是太细粒度了。每个工作都有一点开销,如果你的工作太小,你在开销上浪费了太多的时间。批量处理的明显方式是每个轴有1000个作业,每个作业做一行10个元素,或者100个作业,每个作业做一个包含100个元素的2D数组。测试0、1、2和3轴的批量大小,看看哪一个提供最佳性能。如果差异很大,您可能需要尝试稍微复杂一点的批处理,比如分成3x10x10和4x10x10的3D阵列

最后,虽然Python线程是真正的操作系统,因此是硬件线程,但GIL阻止它们中的多个同时运行Python代码。numpy做了一些工作来解决这个问题,但它并不完美,特别是如果在numpy调用之间有很多开销的话。解决这一问题的最简单方法是使用多处理,它让您拥有一个由8个独立进程组成的池,而不是一个进程中的8个线程。这大大增加了开销,您可能需要隐式地复制子数组(作为任务函数的参数和返回值),或者显式地通过管道复制子数组,或者将其放入共享内存中。但是如果你的任务规模足够大,这通常不是问题

下面是一个如何并行化现有代码的示例:

f = np.mean
result = np.zeros(a.shape)
future_map = {}
with futures.ProcessPoolExecutor(max_workers=8) as executor:
    for i in np.ndindex(a.shape):
        future_map[executor.submit(f, a[i])] = i
    for future in futures.as_completed(future_map):
        i = future_map[future]
        result[i] = future.result()
显然,您可以简化它,例如,用dict理解替换提交循环,但我想让它尽可能清楚地显示您需要更改的内容

另外,我使用的是需要3.2+的;如果您使用的是2.7,请从PyPI安装,因为它使代码更简单;如果需要更大的灵活性,则需要更复杂的库

最后,我不做任何批处理,因为每个任务都是单个元素,所以开销可能会非常大


但从这一点开始,尽可能简化它,然后将其转换为使用1、2和3轴的批,如前所述。

您使用的是列表理解,与显式循环没有太大区别。我想使用一些numpy特性更好,是的,但速度相同。您使用的是列表理解,与显式循环没有太大区别。我想使用一些numpy功能更好,是的,但速度相同。很好!顺便说一句,你必须再给我解释一下矢量化的问题是,它给人一种numpy有效性的错觉,但它表现为一个纯粹的python循环。@Jaime:+1指出这一点。为了简单起见,它仍然很有用,它比,例如,让数组的迭代器传递到map和传递到数组构造函数更好。但这不会节省任何时间。@abanert我不同意这一点:我非常不喜欢矢量化,因为当我刚开始使用numpy时,它糟糕的性能让我心碎。我不知道我会不会忘记它@Jaime:我还是更愿意看到使用矢量化的代码,并带有一个大警告:这并不比ravel上的等效循环快,只是比循环更容易阅读。即使有了这样的评论,它仍然更短,更重要的是,它更具可读性,更难出错。但我能理解你的厌恶。太好了!顺便说一句,你必须再给我解释一下矢量化的问题是,它给人一种numpy有效性的错觉,但它表现为一个纯粹的python循环。@Jaime:+1指出这一点。它对我们的孩子仍然有用
隐式(implicity)它比让数组的迭代器传递给map和数组构造函数更好。但这不会节省任何时间。@abanert我不同意这一点:我非常不喜欢矢量化,因为当我刚开始使用numpy时,它糟糕的性能让我心碎。我不知道我会不会忘记它@Jaime:我还是更愿意看到使用矢量化的代码,并带有一个大警告:这并不比ravel上的等效循环快,只是比循环更容易阅读。即使有了这样的评论,它仍然更短,更重要的是,它更具可读性,更难出错。但我能理解你的厌恶。你说的快速代码到底是什么意思?如果a总是2x2,但是它的元素可能很大,那么做一个缓慢的外部2x2循环没有任何区别,只要你仍然在内部循环上做快速迭代,就像np.mean做的那样。fast意味着尽可能快。a是N x M x。。。具有N,M,…~10,维度应该是有限的,但是你没有通过优化花费不到总时间2%的东西来尽可能快地完成一些事情。你所说的快速代码到底是什么意思?如果a总是2x2,但是它的元素可能很大,那么做一个缓慢的外部2x2循环没有任何区别,只要你仍然在内部循环上做快速迭代,就像np.mean做的那样。fast意味着尽可能快。a是N x M x。。。具有N,M,…~10,维度应该是有限的,但是你不能通过优化花费不到总时间2%的东西来尽可能快地完成一些事情。