一次性完成curve_fit的多次迭代以获得分段函数

时间:2017-07-06 19:50:16

标签: python numpy scipy curve-fitting

我试图同时执行Scipy curve_fit的多次迭代,以避免循环,从而提高速度。

这与this problem非常相似,已经解决了。然而,功能是分段的(不连续的)这一事实使得该解决方案在这里不适用。

考虑这个例子:

import numpy as np
from numpy import random as rng
from scipy.optimize import curve_fit
rng.seed(0)
N=20
X=np.logspace(-1,1,N)
Y = np.zeros((4, N))
for i in range(0,4):
    b = i+1
    a = b
    print(a,b)
    Y[i] = (X/b)**(-a) #+ 0.01 * rng.randn(6)
    Y[i, X>b] = 1

这产生了这些数组:

enter image description here

您可以看到X==b处不连续。我可以通过迭代使用a来检索bcurve_fit的原始值:

def plaw(r, a, b):
    """ Theoretical power law for the shape of the normalized conditional density """
    import numpy as np
    return np.piecewise(r, [r < b, r >= b], [lambda x: (x/b)**-a, lambda x: 1])


coeffs=[]
for ix in range(Y.shape[0]):
    print(ix)
    c0, pcov = curve_fit(plaw, X, Y[ix])
    coeffs.append(c0)

但是这个过程可能会非常慢,具体取决于XY和循环的大小,所以我试图通过尝试coeffs来加快速度无需循环。到目前为止,我还没有运气。

可能很重要的事情:

  • XY仅包含正值
  • ab始终是正面的
  • 虽然这个例子中适合的数据是平滑的(为了简单起见),但实际数据有噪音

修改

就我而言:

y=np.ma.masked_where(Y<1.01, Y)

lX = np.log(X)
lY = np.log(y)
A = np.vstack([lX, np.ones(len(lX))]).T
m,c=np.linalg.lstsq(A, lY.T)[0]

print('a=',-m)
print('b=',np.exp(-c/m))

但即使没有任何噪音,输出也是:

a= [0.18978965578339158 1.1353633705997466 2.220234483915197 3.3324502660995714]
b= [339.4090881838179 7.95073481873057 6.296592007396107 6.402567167503574]

这比我希望得到的更糟糕。

2 个答案:

答案 0 :(得分:2)

以下是加快这种情况的三种方法。你没有提供所需的加速或准确度,甚至是矢量大小,所以买家要小心。

TL; DR

时序:

len       1      2      3      4
1000    0.045  0.033  0.025  0.022
10000   0.290  0.097  0.029  0.023
100000  3.429  0.767  0.083  0.030
1000000               0.546  0.046

1) Original Method
2) Pre-estimate with Subset
3) M Newville [linear log-log estimate](https://stackoverflow.com/a/44975066/7311767)
4) Subset Estimate (Use Less Data)

使用子集进行预估(方法2):

只需运行curve_fit两次即可实现体面的加速,第一次使用短数据子集进行快速估算。然后,该估计用于为整个数据集播种curve_fit

x, y = current_data
stride = int(max(1, len(x) / 200))
c0 = curve_fit(power_law, x[0:len(x):stride], y[0:len(y):stride])[0]
return curve_fit(power_law, x, y, p0=c0)[0]

M Newville linear log-log estimate(方法3):

使用M Newville提出的对数估计值也要快得多。由于OP关注Newville提出的初始估计方法,该方法使用curve_fit和子集来提供曲线中断点的估计。

x, y = current_data
stride = int(max(1, len(x) / 200))
c0 = curve_fit(power_law, x[0:len(x):stride], y[0:len(y):stride])[0]

index_max = np.where(x > c0[1])[0][0]
log_x = np.log(x[:index_max])
log_y = np.log(y[:index_max])
result = linregress(log_x, log_y)
return -result[0], np.exp(-result[1] / result[0])
return (m, c), result

使用更少的数据(方法4):

最后,用于前两种方法的种子机制提供了对样本数据的非常好的估计。当然这是样本数据,因此您的里程可能会有所不同。

stride = int(max(1, len(x) / 200))
c0 = curve_fit(power_law, x[0:len(x):stride], y[0:len(y):stride])[0]

测试代码:

import numpy as np
from numpy import random as rng
from scipy.optimize import curve_fit
from scipy.stats import linregress

fit_data = {}
current_data = None

def data_for_fit(a, b, n):
    key = a, b, n
    if key not in fit_data:
        rng.seed(0)
        x = np.logspace(-1, 1, n)
        y = np.clip((x / b) ** (-a) + 0.01 * rng.randn(n), 0.001, None)
        y[x > b] = 1
        fit_data[key] = x, y
    return fit_data[key]


def power_law(r, a, b):
    """ Power law for the shape of the normalized conditional density """
    import numpy as np
    return np.piecewise(
        r, [r < b, r >= b], [lambda x: (x/b)**-a, lambda x: 1])

def method1():
    x, y = current_data
    return curve_fit(power_law, x, y)[0]

def method2():
    x, y = current_data
    return curve_fit(power_law, x, y, p0=method4()[0])

def method3():
    x, y = current_data
    c0, pcov = method4()

    index_max = np.where(x > c0[1])[0][0]
    log_x = np.log(x[:index_max])
    log_y = np.log(y[:index_max])
    result = linregress(log_x, log_y)
    m, c = -result[0], np.exp(-result[1] / result[0])
    return (m, c), result

def method4():
    x, y = current_data
    stride = int(max(1, len(x) / 200))
    return curve_fit(power_law, x[0:len(x):stride], y[0:len(y):stride])

from timeit import timeit

def runit(stmt):
    print("%s: %.3f  %s" % (
        stmt, timeit(stmt + '()', number=10,
                     setup='from __main__ import ' + stmt),
        eval(stmt + '()')[0]
    ))

def runit_size(size):

    print('Length: %d' % size)
    if size <= 100000:
        runit('method1')
        runit('method2')
    runit('method3')
    runit('method4')


for i in (1000, 10000, 100000, 1000000):
    current_data = data_for_fit(3, 3, i)
    runit_size(i)

答案 1 :(得分:1)

两个建议:

  1. 使用numpy.where(可能还有argmin)查找X数据变为1的Y值,或者可能只略大于1,并截断数据到那一点 - 有效地忽略Y = 1的数据。
  2. 这可能是这样的:

    index_max = numpy.where(y < 1.2)[0][0]
    x = y[:index_max]
    y = y[:index_max]
    
    1. 使用日志 - 日志图中显示的提示,在日志日志中,幂定律现在 线性 。您不需要curve_fit,但可以在scipy.stats.linregress vs log(Y)上使用log(Y)。对于你的实际工作,这至少会为后续的契合提供良好的起始值。
    2. 跟进此问题并尝试关注您的问题,您可以尝试以下方法:

      import numpy as np 
      from scipy.stats import linregress
      
      np.random.seed(0)
      npts = 51 
      x = np.logspace(-2, 2, npts)
      YTHRESH = 1.02
      
      for i in range(5):
          b = i + 1.0 + np.random.normal(scale=0.1)
          a = b + np.random.random()
          y = (x/b)**(-a) + np.random.normal(scale=0.0030, size=npts)
          y[x>b] = 1.0
      
          # to model exponential decay, first remove the values
          # where y ~= 1 where the data is known to not decay...
          imax = np.where(y < YTHRESH)[0][0]
      
          # take log of this truncated x and y
          _x = np.log(x[:imax])
          _y = np.log(y[:imax])
      
          # use linear regression on the log-log data:
          out = linregress(_x, _y)
      
          # map slope/intercept to scale, exponent
          afit = -out.slope
          bfit = np.exp(out.intercept/afit)
      
          print(""" === Fit Example {i:3d}
                a  expected {a:4f}, got {afit:4f}
                b  expected {b:4f}, got {bfit:4f}
                """.format(i=i+1, a=a, b=b, afit=afit, bfit=bfit))
      

      希望这足以让你前进。