条件numpy累积和

时间:2016-02-10 07:31:08

标签: python numpy cumulative-sum

我正在寻找一种方法来计算numpy的累计和,但是如果累积总和非常大,请不要前滚该值(或将其设置为零)接近零和负面。

例如

a = np.asarray([0, 4999, -5000, 1000])
np.cumsum(a)

返回[0, 4999, -1, 999]

但是,我想在计算过程中将[2] - 值(-1)设置为零。问题是这个决定只能在计算过程中完成,因为中间结果不是先验的。

预期的数组是:[0, 4999, 0, 1000]

这样做的原因是我得到非常小的值(浮点数,而不是示例中的整数),这是由浮点计算引起的,实际上应该为零。计算累积和会使那些导致错误的值复杂化。

3 个答案:

答案 0 :(得分:1)

Kahan summation algorithm可以解决问题。不幸的是,它是not implemented in numpy。这意味着需要自定义实现:

def kahan_cumsum(x):
    x = np.asarray(x)
    cumulator = np.zeros_like(x)
    compensation = 0.0

    cumulator[0] = x[0]    
    for i in range(1, len(x)):
        y = x[i] - compensation
        t = cumulator[i - 1] + y
        compensation = (t - cumulator[i - 1]) - y
        cumulator[i] = t
    return cumulator

我不得不承认,这并不是问题所要求的。 (在示例中,cumsum的第3个输出处的值为-1是正确的)。但是,我希望这能解决问题背后的实际问题,这与浮点精度有关。

答案 1 :(得分:0)

我想知道舍入是否能满足您的要求:

np.cumsum(np.around(a,-1))
# the -1 means it rounds to the nearest 10

给出

array([   0, 5000,    0, 1000])

这与您从答案中放入预期数组的方式不完全一样,但使用around(可能将decimals参数设置为0)可能会在您将其应用于浮动问题时起作用

答案 2 :(得分:0)

可能最好的方法是在Cython中写这个位(将文件命名为cumsum_eps.pyx):

cimport numpy as cnp
import numpy as np

cdef inline _cumsum_eps_f4(float *A, int ndim, int dims[], float *out, float eps):
    cdef float sum
    cdef size_t ofs

    N = 1
    for i in xrange(0, ndim - 1):
        N *= dims[i]
    ofs = 0
    for i in xrange(0, N):
        sum = 0
        for k in xrange(0, dims[ndim-1]):
            sum += A[ofs]
            if abs(sum) < eps:
                sum = 0
            out[ofs] = sum
            ofs += 1

def cumsum_eps_f4(cnp.ndarray[cnp.float32_t, mode='c'] A, shape, float eps):
    cdef cnp.ndarray[cnp.float32_t] _out
    cdef cnp.ndarray[cnp.int_t] _shape
    N = np.prod(shape)
    out = np.zeros(N, dtype=np.float32)
    _out = <cnp.ndarray[cnp.float32_t]> out
    _shape = <cnp.ndarray[cnp.int_t]> np.array(shape, dtype=np.int)
    _cumsum_eps_f4(&A[0], len(shape), <int*> &_shape[0], &_out[0], eps)
    return out.reshape(shape)


def cumsum_eps(A, axis=None, eps=np.finfo('float').eps):
    A = np.array(A)
    if axis is None:
        A = np.ravel(A)
    else:
        axes = list(xrange(len(A.shape)))
        axes[axis], axes[-1] = axes[-1], axes[axis]
        A = np.transpose(A, axes)
    if A.dtype == np.float32:
        out = cumsum_eps_f4(np.ravel(np.ascontiguousarray(A)), A.shape, eps)
    else:
        raise ValueError('Unsupported dtype')
    if axis is not None: out = np.transpose(out, axes)
    return out

然后你可以像这样编译它(Windows,Visual C ++ 2008命令行):

\Python27\Scripts\cython.exe cumsum_eps.pyx
cl /c cumsum_eps.c /IC:\Python27\include /IC:\Python27\Lib\site-packages\numpy\core\include
F:\Users\sadaszew\Downloads>link /dll cumsum_eps.obj C:\Python27\libs\python27.lib /OUT:cumsum_eps.pyd

或者像这样(Linux使用.so扩展名/ Cygwin使用.dll扩展名,gcc):

cython cumsum_eps.pyx
gcc -c cumsum_eps.c -o cumsum_eps.o -I/usr/include/python2.7 -I/usr/lib/python2.7/site-packages/numpy/core/include
gcc -shared cumsum_eps.o -o cumsum_eps.so -lpython2.7

并使用如下:

from cumsum_eps import *
import numpy as np
x = np.array([[1,2,3,4], [5,6,7,8]], dtype=np.float32)

>>> print cumsum_eps(x)
[  1.   3.   6.  10.  15.  21.  28.  36.]
>>> print cumsum_eps(x, axis=0)
[[  1.   2.   3.   4.]
 [  6.   8.  10.  12.]]
>>> print cumsum_eps(x, axis=1)
[[  1.   3.   6.  10.]
 [  5.  11.  18.  26.]]
>>> print cumsum_eps(x, axis=0, eps=1)
[[  1.   2.   3.   4.]
 [  6.   8.  10.  12.]]
>>> print cumsum_eps(x, axis=0, eps=2)
[[  0.   2.   3.   4.]
 [  5.   8.  10.  12.]]
>>> print cumsum_eps(x, axis=0, eps=3)
[[  0.   0.   3.   4.]
 [  5.   6.  10.  12.]]
>>> print cumsum_eps(x, axis=0, eps=4)
[[  0.   0.   0.   4.]
 [  5.   6.   7.  12.]]
>>> print cumsum_eps(x, axis=0, eps=8)
[[ 0.  0.  0.  0.]
 [ 0.  0.  0.  8.]]
>>> print cumsum_eps(x, axis=1, eps=3)
[[  0.   0.   3.   7.]
 [  5.  11.  18.  26.]]

等等,当然通常eps会有一些小的值,这里的整数只是为了演示/打字的容易程度。

如果你需要这个也是双倍的_f8变体是很容易写的,另一个案例必须在cumsum_eps()中处理。

当您对实施感到满意时,您应该将其作为setup.py的正确部分 - Cython setup.py

更新#1:如果你在运行环境中有良好的编译器支持,你可以尝试[Theano] [3]来实现补偿算法或你原来的想法:

import numpy as np
import theano
import theano.tensor as T
from theano.ifelse import ifelse

A=T.vector('A')

sum=T.as_tensor_variable(np.asarray(0, dtype=np.float64))

res, upd=theano.scan(fn=lambda cur_sum, val: ifelse(T.lt(cur_sum+val, 1.0), np.asarray(0, dtype=np.float64), cur_sum+val), outputs_info=sum, sequences=A)

f=theano.function(inputs=[A], outputs=res)

f([0.9, 2, 3, 4])

将给出[0 2 3 4]输出。无论是Cython还是其中,你都可以获得至少+/-性能的本机代码。