我经常使用大量模拟。有时,我需要计算粒子集的质心。我注意到在许多情况下,numpy.mean()返回的平均值是错误的。我可以弄清楚这是由于累加器的饱和。为了避免这个问题,我可以在小组粒子中将所有粒子的总和分开,但这是不舒服的。任何人都有如何以优雅的方式解决这个问题的想法?
为了提高你的好奇心,下面的例子产生了类似于我在模拟中观察到的东西:
import numpy as np
a = np.ones((1024,1024), dtype=np.float32)*30504.00005
如果检查最大值和最小值,则得到:
a.max()
30504.0
a.min()
30504.0
但是,平均值是:
a.mean()
30687.236328125
你可以弄清楚这里出了什么问题。使用dtype = np.float64时不会发生这种情况,因此解决单精度问题应该不错。
答案 0 :(得分:5)
这不是NumPy问题,它是一个浮点问题。同样的情况发生在C:
float acc = 0;
for (int i = 0; i < 1024*1024; i++) {
acc += 30504.00005f;
}
acc /= (1024*1024);
printf("%f\n", acc); // 30687.304688
问题是浮点精度有限;随着累加器值相对于添加到其中的元素的增长,相对精度会下降。
一种解决方案是通过构造加法器树来限制相对增长。这是C中的一个例子(我的Python不够好......):
float sum(float *p, int n) {
if (n == 1) return *p;
for (int i = 0; i < n/2; i++) {
p[i] += p[i+n/2];
}
return sum(p, n/2);
}
float x[1024*1024];
for (int i = 0; i < 1024*1024; i++) {
x[i] = 30504.00005f;
}
float acc = sum(x, 1024*1024);
acc /= (1024*1024);
printf("%f\n", acc); // 30504.000000
答案 1 :(得分:2)
您可以使用np.mean
关键字参数调用dtype
,该参数指定累加器的类型(默认为与浮点数组的数组相同的类型)。
因此调用a.mean(dtype=np.float64)
将解决您的玩具示例,也许您的问题可能是更大的阵列。
答案 2 :(得分:2)
您可以使用内置math.fsum
来部分解决此问题,该内置>>> fsum(a.ravel())/(1024*1024)
30504.0
会追踪部分总和(文档包含指向AS配方原型的链接):
numpy
据我所知,{{1}}没有模拟。
答案 3 :(得分:0)
快速而肮脏的回答
assert a.ndim == 2
a.mean(axis=-1).mean()
这给出了1024 * 1024矩阵的预期结果,但对于较大的数组当然不会这样......
如果计算平均值不会成为代码中的瓶颈,我会在python中实现自己的ad-hoc算法:但是细节取决于你的数据结构。
如果计算均值是瓶颈,那么一些专门的(并行)约简算法可以解决这个问题。
修改强>
这种方法可能看起来很愚蠢,但肯定可以缓解问题,并且几乎与.mean()
本身一样有效。
In [65]: a = np.ones((1024,1024), dtype=np.float32)*30504.00005
In [66]: a.mean()
Out[66]: 30687.236328125
In [67]: a.mean(axis=-1).mean()
Out[67]: 30504.0
In [68]: %timeit a.mean()
1000 loops, best of 3: 894 us per loop
In [69]: %timeit a.mean(axis=-1).mean()
1000 loops, best of 3: 906 us per loop
提供更明智的答案需要更多关于数据结构,大小和目标架构的信息。