3D numpy数组的每个元素的高效一维线性回归

 17114php 发布于 2023-02-13 12:12

我有3D堆栈的蒙版数组.我想对每一行的值进行线性回归,col(空间索引)沿轴0(时间).这些堆叠的尺寸各不相同,但典型的形状可能是(50,2000,2000).我的空间有限但时间密集的测试用例具有以下几个方面:

stack.ma_stack.shape

(1461,390,327)

我做了一个快速测试循环每一行,col:

from scipy.stats.mstats import linregress
#Ordinal dates
x = stack.date_list_o
#Note: idx should be row, col
def sample_lstsq(idx):
    b = stack.ma_stack[:, idx[0], idx[1]]
    #Note, this is masked stats version
    slope, intercept, r_value, p_value, std_err = linregress(x, b)
    return slope

out = np.zeros_like(stack.ma_stack[0])
for row in np.arange(stack.ma_stack.shape[1]):
    for col in np.arange(stack.ma_stack.shape[2]):
        out[row, col] = sample_lstsq((row, col))

这很有效(慢慢地).我知道必须有一个更有效的方法.

我开始玩索引数组和np.vectorize,但我认为这实际上不会提供任何真正的改进.我考虑过把所有东西都扔给熊猫或试图移植到Cython,但我希望我能坚持使用NumPy/SciPy.或者并行解决方案是提高性能的最佳选择?

另外,有没有人对NumPy/SciPy线性回归选项进行基准测试?我遇到了以下选项,但没有测试过自己:

scipy.stats.linregress

numpy.linalg.leastsq

numpy.polyfit(度= 1)

我希望有一种现有的方法可以提供显着的性能提升而不需要太多的实现工作.谢谢.


2013年12月3日编辑@ 02:29

@HYRY建议的方法对于上面描述的样本数据集(在所有维度(空间和时间)中)是连续的(未屏蔽的),完美地工作(约15秒运行时).但是,当将包含缺失数据的掩码数组传递给np.linalg.leastsq时,所有掩码值都会用fill_value(defualt 1E20)填充,这会导致伪线性拟合.

幸运的是,numpy蒙面数组模块有np.ma.polyfit(deg = 1),它可以处理像np.linalg.leastsq这样的2D y数组.看一下源代码(https://github.com/numpy/numpy/blob/v1.8.0/numpy/ma/extras.py#L1852),ma polyfit只是np.polyfit的一个包装器,它使用了一个组合掩码来自x和y面具.当y中缺失数据的位置不变时,这适用于2D y.

不幸的是,我的数据在空间和时间上有可变的丢失数据位置.这是另一个堆栈的示例:

In [146]: stack.ma_stack.shape
Out [146]: (57, 1889, 1566)

对单个索引进行采样会返回具有6个未屏蔽值的时间序列:

In [147]: stack.ma_stack[:,0,0]
Out [147]: 
masked_array(data = [-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
 519.7779541015625 -- -- -- 518.9047241210938 -- -- -- -- -- -- --
 516.6539306640625 516.0836181640625 515.9403686523438 -- -- -- --
 514.85205078125 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --],
             mask = [ True  True  True  True  True  True  True  True  True  True  True  True
  True  True  True  True  True  True  True  True  True False  True  True
  True False  True  True  True  True  True  True  True False False False
  True  True  True  True False  True  True  True  True  True  True  True
  True  True  True  True  True  True  True  True  True],
       fill_value = 1e+20)

对不同位置进行采样会从不同时间片返回不同数量的未屏蔽值:

In [148]: stack.ma_stack[:,1888,1565]
Out[148]:
masked_array(data = [-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
 729.0936889648438 -- -- -- 724.7155151367188 -- -- -- -- -- -- --
 722.076171875 720.9276733398438 721.9603881835938 -- 720.3294067382812 --
 -- 713.9591064453125 709.8037719726562 707.756103515625 -- -- --
 703.662353515625 -- -- -- -- 708.6276245117188 -- -- -- -- --],
             mask = [ True  True  True  True  True  True  True  True  True  True  True  True
  True  True  True  True  True  True  True  True  True False  True  True
  True False  True  True  True  True  True  True  True False False False
  True False  True  True False False False  True  True  True False  True
  True  True  True False  True  True  True  True  True],
       fill_value = 1e+20)

每个索引的最小未屏蔽值数为6,max为45.因此每个位置至少有一些屏蔽值.

作为参考,我的x(时间序数)值都是未屏蔽的:

In [150]: stack.date_list_o
Out[150]:
masked_array(data = [ 733197.64375     733962.64861111  733964.65694444  733996.62361111
  733999.64236111  734001.63541667  734033.64305556  734071.64722222
  734214.675       734215.65694444  734216.625       734226.64722222
  734229.63819444  734232.65694444  734233.67847222  734238.63055556
  734238.63055556  734245.65277778  734245.65277778  734255.63125
  734255.63125     734307.85        734326.65138889  734348.63888889
  734348.63958333  734351.85        734363.70763889  734364.65486111
  734390.64722222  734391.63194444  734394.65138889  734407.64652778
  734407.64722222  734494.85        734527.85        734582.85
  734602.65486111  734664.85555556  734692.64027778  734741.63541667
  734747.85        734807.85555556  734884.85555556  734911.65763889
  734913.64375     734917.64236111  734928.85555556  734944.71388889
  734961.62777778  735016.04583333  735016.62777778  735016.85555556
  735036.65347222  735054.04583333  735102.63125     735119.61180556
  735140.63263889],
             mask = False,
       fill_value = 1e+20)

所以我重塑stack.ma_stack并运行polyfit:

newshape = (stack.ma_stack.shape[0], stack.ma_stack.shape[1]*stack.ma_stack.shape[2])
print newshape
#(57, 2958174)
y = stack.ma_stack.reshape(newshape)
p = np.ma.polyfit(x, y, deg=1)

但是按照~1500列,y中的每一行都被"累积"屏蔽,我得到一些投诉和空输出:

RankWarning: Polyfit may be poorly conditioned
** On entry to DLASCL, parameter number  4 had an illegal value
...

因此,似乎在不同位置使用缺少数据的2D y将不起作用.我需要一个使用每个y列中所有可用的未屏蔽数据的最小拟合.可能有一种方法可以通过仔细压缩x和y并跟踪未屏蔽的索引来实现此目的.

还有其他想法吗?大熊猫开始看起来可能是一个很好的解决方案.


2013年12月3日编辑20:29

@HYRY提供了一种解决方案,适用于时间(轴= 0)维度中的缺失值.我必须稍微修改以处理空间(轴= 1,2)维度中的缺失值.如果特定空间索引只有一个未屏蔽的条目及时,我们当然不希望尝试线性回归.这是我的实现:

def linreg(self):
    #Only compute where we have n_min unmasked values in time
    n_min = 3
    valid_idx = self.ma_stack.count(axis=0).filled(0) >= n_min
    #Returns 2D array of unmasked columns
    y = self.ma_stack[:, valid_idx]

    #Extract mask for axis 0 - invert, True where data is available
    mask = ~y.mask
    #Remove masks, fills with fill_value
    y = y.data
    #Independent variable is time ordinal
    x = self.date_list_o
    x = x.data

    #Prepare matrices and solve
    X = np.c_[x, np.ones_like(x)]
    a = np.swapaxes(np.dot(X.T, (X[None, :, :] * mask.T[:, :, None])), 0, 1)
    b = np.dot(X.T, (mask*y))
    r = np.linalg.solve(a, b.T)

    #Create output grid with original dimensions
    out = np.ma.masked_all_like(self.ma_stack[0])
    #Fill in the valid indices
    out[valid_idx] = r[:,0]

运行时非常快 - 这里讨论的阵列尺寸仅为5-10秒.

1 个回答
  • 如果我理解正确,你想做很多线性回归y = k * x + b,只有一个x,但很多y,y你想要计算kb.

    如果形状x是50,y是(50,1000),你可以使用numpy.linalg.lstsq,这里是一些演示:

    import numpy as np
    
    x = np.random.rand(50)
    k = np.random.rand(1000)
    b = np.random.rand(1000)
    
    y = np.outer(x, k) + b + np.random.normal(size=(50, 1000), scale=1e-10)
    
    r = np.linalg.lstsq(np.c_[x, np.ones_like(x)], y)[0]
    
    print np.allclose(r[0], k)
    print np.allclose(r[1], b)
    

    对于具有形状的y(50,2000,2000),您可以将其重塑为(50,2000*2000).

    编辑

    这是我对蒙面数组的解决方案.公式是:

    在此输入图像描述

    准备Y为2-dim阵列形状(1889*1566,57),X为2-dim阵列形状(57,2).掩码为具有与Y相同形状的bool数组,True表示Y中的值可用.

    a用形状计算数组(1889*1566,2,2),b形状(1889*1566,2),然后调用numpy.linalg.solve(a, b),这里有一些演示代码:

    import numpy as np
    
    N = 50
    M = 1000
    
    x = np.random.rand(N)
    X = np.c_[x, np.ones_like(x)]
    beta = np.random.rand(M, 2)
    Y = np.dot(beta, X.T)
    Y += np.random.normal(scale=0.1, size=Y.shape)
    mask = np.random.randint(0, 2, size=Y.shape).astype(np.bool)
    
    a = np.swapaxes(np.dot(X.T, (X[None, :, :] * mask[:, :, None])), 0, 1)
    b = np.dot(X.T, (mask*Y).T)
    beta2 = np.linalg.solve(a, b.T)
    
    i = 123
    print "real:", beta[i]
    print "by solve:", beta2[i]
    
    m = mask[i]
    x2 = X[m]
    y2 = Y[i, m]
    print "by lstsq:", np.linalg.lstsq(x2, y2)[0]
    

    输出第123个系数:

    real: [ 0.35813131  0.29736779]
    by solve: [ 0.38088499  0.30382547]
    by lstsq: [ 0.38088499  0.30382547]
    

    您还可以a通过以下代码计算数组,我认为它将使用比上述方法更少的内存:

    a2 = np.empty((M, 2, 2))
    xm = mask * x
    a2[:, 0, 0] = (xm*xm).sum(1)
    a2[:, 1, 0] = (xm*mask).sum(1)
    a2[:, 0, 1] = a2[:, 1, 0]
    a2[:, 1, 1] = (mask).sum(1)
    
    print np.allclose(a2, a)
    

    2023-02-13 12:16 回答
撰写答案
今天,你开发时遇到什么问题呢?
立即提问
热门标签
PHP1.CN | 中国最专业的PHP中文社区 | PNG素材下载 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有