快速更新残差平方和

时间:2018-03-19 16:46:38

标签: python numpy sum

当我知道只有一小部分条款正在发生变化时,我想找到一种快速更新残差平方和的方法。让我更详细地描述一下这个问题。

我有来自嘈杂的阶梯函数数据的N个数据点。

N = 100000
realStepList = [200, 500, 900]

x = np.zeros(N)
for realStep in realStepList:
    x[realStep:] += 1
x+=np.random.randn(len(x))*0.1 #Add noise

我想计算此数据的残差平方和以及任意步骤位置列表。我就是这样做的。

a = [0, 250, 550, N] 
def Q(x, a):
    q = np.sum([np.sum((x[ai:af] - i)**2) for i, (ai,af) in enumerate(zip(a[:-1],a[1:]))])
    return q

a是我的潜在步骤列表。使用始终以0作为第一个元素并将N作为最后一个元素的列表更容易。

这相对较慢,因为它是N个方块的总和。但是,我意识到,如果我将a更改为相对较小的数量,则这些N项中的大多数将保持不变,这意味着我无需再次计算它们。

所以,假设我已经如上所述计算了Q(x,a)。我现在有另一个清单

b = [aa + dd for aa, dd in zip(a, d)]

其中d是两个列表之间的差异。而不是像上面那样计算Q(x,b)N元素的另一个和),我想找到

deltaQ(x, a, d)这样

Q(x, b) = Q(x,a) + deltaQ(x, a, d)

我写过这样一个函数,但它很慢而且很草率。事实上,它比<{1}} 更慢

Q

我们的想法是识别def deltaQ(x, a, d): z = np.zeros(len(x)) J = np.zeros(len(x)) s = 0 for j, [dd, aa] in enumerate(zip(d, a[1:-1])): if dd >= 0: z[aa:aa+dd] += 1 s += sum(x[aa:aa+dd]) if dd < 0: z[aa+dd:aa] += -1 s += -sum(x[aa+dd:aa]) J[aa:] += 1 dq = 2*s - sum((J**2 - (J-z)**2)) return dq 中受影响的所有点。例如,如果原始列表为xa = [0, 5, 10],那么只有与b = [0, 7, 10]对应的字词会在总和中发生变化。我用列表x[5:7]跟踪这个。然后我根据这个来计算变化。

我认为我不是世界上第一个遇到这个问题的人。所以我的问题是:

有没有一种快速的方法来计算残差平方和中的差值,因为这通常比从头开始重新计算新总和的元素少得多?

1 个答案:

答案 0 :(得分:2)

首先,我能够使用原始代码运行Q,只修改N,以便在相当标准的笔记本电脑上获得以下时间(没什么太花哨的):

N = 1e6: 0.00236s per loop
N = 1e7: 0.0260s per loop
N = 1e8: 0.251 per loop

这个过程在N = 1e9处进行交换,但是如果有足够的RAM可用,我会发现2.5秒的时间对于那个大小是可以接受的。

话虽这么说,我通过将调用np.sum的结果更改为内部np.ndarray.sumnp.power,获得了10%的加速:

def Q1(x, a):
    return sum(((x[ai:af] - i)**2).sum() for i, (ai, af) in enumerate(zip(a[:-1], a[1:])))

现在这是一个慢三倍的版本:

def offset(x, a):
    d = np.zeros(x.shape, dtype=np.int)
    d[a[1:-1]] = 1
    # Add out=d to make this run 4 times slower
    return np.cumsum(d)

def Q2(x, a):
    return np.sum((x - offset(x, a))**2)

为什么这有帮助?好吧,请注意offset的作用:它会将x重新调整为您选择的基线。从长远来看,这有两件事。首先,您获得的矢量化解决方案比您目前提出的解决方案要多得多。其次,它允许您根据您选择的不同b数组重写delta函数,而不必计算d,如果len(a) != len(b),甚至可能无法实现。{/} p>

delta为(x - i)2 - (x - i)2。如果你扩大了所有的混乱,你得到(j - i)(j + i - 2x)jioffset返回的步骤的值。这不仅大大简化了计算,而且j - i是您需要计算增量的掩码:

def deltaQ1(x, a, b):
    i = offset(x, a)
    j = offset(x, b)
    d = j - i
    mask = d.astype(np.bool)
    return (d[mask] * (j[mask] + i[mask] - 2 * x[mask])).sum()

此功能的运行速度比原始实施速度快10到15倍(但请注意,ab代替ad输入)。调用Q1(x, b) - Q1(x, a)的速度仍然快两倍。新功能还会创建一堆临时数组,但这些数组很容易减少。

<强>计时

以下是我的计算机上的一些示例时间,以及上面显示的时间(使用提供的数据,a = [0, 250, 550, N]b = [0, 180, 565, N],因此d = [0, -70, 15, 0],相关:

原始残留物:

Q:  147µs per loop
Q1: 135µs per loop <-- Use this one!
Q2: 453µs per loop

残差增量:

deltaQ: 8363µs per loop
deltaQ1: 656µs per loop
Q(x, b) - Q(x, a): 297µs per loop
Q1(x, b) - Q1(x, a): 275µs per loop  <-- Best solution?

最后的注意事项:我的印象是您的delta函数的原始实现不正确。它与Q(x, b) - Q(x, a)的结果不一致,但deltaQ1(x, a, b)的结果不同。

<强> TL; DR

请不要过早优化。如果你做对了,当然可以编写一个专门的C函数来为你保存i - ji + j,这对你来说会更快,但我怀疑你会得到多少里程数矢量化管道。部分原因是你最终会花费大量时间来弄清楚一组复杂的指数是如何相互作用的,而不仅仅是将数字加在一起。