【问题标题】:Linear least-squares solution for 3d inputs3d 输入的线性最小二乘解
【发布时间】:2017-10-09 18:48:53
【问题描述】:

问题

假设我有两个具有以下形状的数组:

  • y.shape(z, b)。将其想象成 z (b,) y 向量的集合。
  • x.shape(z, b, c)。将其想象为 z (b, c) 多元 x 矩阵的集合。

我的目的是找到最小二乘系数解的z 独立向量。 IE。第一个解决方案是在x[0] 上回归y[0],其中这些输入的形状分别为(b, )(b, c)。 (b 观察,c 特征。)结果将是形状 (z, c)

一些示例数据

np.random.seed(123)
x = np.random.randn(190, 20, 3)
y = np.random.randn(190, 20)  # Assumes no intercept term

# First vector of coefficients
np.linalg.lstsq(x[0], y[0])[0]
# array([-0.12823781, -0.3055392 ,  0.11602805])

# Last vector of coefficients
np.linalg.lstsq(x[-1], y[-1])[0]
# array([-0.02777503, -0.20425779,  0.22874169])

NumPy 的最小二乘求解器lstsq 无法对这些进行运算。 (我的预期结果是形状 (190, 3),或 190 个向量,每个向量有 3 个系数。每个 (3,) 向量是 n=20 的回归中的一个系数集。)

是否有一种解决方法可以将系数矩阵包装到一个结果数组中?我在想可能是matrix formulation

对于一维 y 和二维 x 这将是:

def coefs(y, x):
    return np.dot(np.linalg.inv(np.dot(x.T, x)), np.dot(x.T, y))

但我无法让它接受上面的 2d y 和 3d x

最后,我很好奇为什么lstsq 在这里遇到麻烦。关于为什么输入必须最多为 2d 是否有一个简单的答案?

【问题讨论】:

  • 我认为你应该更专注于解释你的任务,而不是到处乱扔形状和目标形状。这张图片也让人感觉断章取意。也许也添加一些小例子。如果您可以将目标构建为函数,则始终可以使用 scipy 的最小平方或最小平方。但了解任务将有助于分析 lstsq 方法。
  • @sascha 已编辑,如果我仍然不清楚,请告诉我
  • 将其转换为可以被 lstsq 使用的东西似乎并不难;但我不推荐它。如果这些是独立回归,为什么不单独计算它们。将这些结合起来在某些计算方式上没有帮助(更多相反;尤其是在使用密集求解器时),只会使事情变得更复杂。那么这种需求/想法从何而来?
  • @sascha 主要来自这样一个事实,即循环通过z 调用lstsq 与上面的coefs 在2d/3d 数组上逐元素操作相比似乎效率低下。但是完全不了解内部结构,我可能是错的。
  • 恕我直言:您的问题是 lstsq 的假设。它是为某些特定情况而构建的,而您的情况本身并不兼容。您需要在一个大 A 垫子中构建此信息(引入了很多零);即使使用稀疏求解器(lstsq 是密集求解器),这也会慢得多。虽然 python 循环并不快;这将由内部 lstsq 调用的成本决定。

标签: python numpy linear-regression mathematical-optimization


【解决方案1】:

这里有一些演示:

  • 我的cmets中提到的问题
  • 对 looped-lstsq 与一步嵌入 lstsq 的主要实证分析
  • (最后出现了一些令人惊讶的结果,需要与一粒盐一起服用):

代码

import numpy as np
import scipy.sparse as sp
from sklearn.datasets import make_regression
from time import perf_counter as pc
np.set_printoptions(edgeitems=3,infstr='inf',
                    linewidth=160, nanstr='nan', precision=1,
                    suppress=False, threshold=1000, formatter=None)

""" Create task """
Z, B, C = 4, 3, 2

Zs = []
Bs = []
for i in range(Z):
    X, y, = make_regression(n_samples=B, n_features=C, random_state=i)
    Zs.append(X)
    Bs.append(y)
Zs = np.array(Zs)
Bs = np.array(Bs)

""" Independent looping """
print('LOOPED CALLS')
start = pc()
result = np.empty((Z, C))
for z in range(Z):
    result[z] = np.linalg.lstsq(Zs[z], Bs[z])[0]
end = pc()
print('lhs-shape: ', Zs.shape)
print('lhs-dense-fill-ratio: ', np.count_nonzero(Zs) / np.product(Zs.shape))
print('used time: ', end-start)
print(result)

""" Embedding in one """
print('EMBEDDING INTO ONE CALL')
Zs_ = sp.block_diag([Zs[i] for i in range(Z)]).todense()  # convenient to use scipy.sparse
                                                          # oops: there is a dense-one too: 
                                                          # -> scipy.linalg.block_diag
Bs_ = Bs.flatten()

start = pc()  # one could argue if transform above should be timed too!
result_ = np.linalg.lstsq(Zs_, Bs_)[0]
end = pc()
print('lhs-shape: ', Zs_.shape)
print('lhs-dense-fill-ratio: ', np.count_nonzero(Zs_) / np.product(Zs_.shape))
print('used time: ', end-start)
print(result_)

输出

LOOPED CALLS
lhs-shape:  (4, 3, 2)
lhs-dense-fill-ratio:  1.0
used time:  0.0005415275241778155
[[ 89.2  43.8]
 [ 68.5  41.9]
 [ 61.9  20.5]
 [  5.1  44.1]]
EMBEDDING INTO ONE CALL
lhs-shape:  (12, 8)
lhs-dense-fill-ratio:  0.25
used time:  0.00015907748341232328
[ 89.2  43.8  68.5  41.9  61.9  20.5   5.1  44.1]

每个案例的 lstsq 问题维度

虽然原始数据看起来像:

[[[ 2.2  1. ]
  [-1.   1.9]
  [ 0.4  1.8]]

 [[-1.1 -0.5]
  [-2.3  0.9]
  [-0.6  1.6]]

 [[ 1.6 -2.1]
  [-0.1 -0.4]
  [-0.8 -1.8]]

 [[-0.3 -0.4]
  [ 0.1 -1.9]
  [ 1.8  0.4]]]
[[ 242.7   -5.4  112.9]
 [ -95.7 -121.4   26.2]
 [  57.9  -12.   -88.8]
 [ -17.1  -81.6   28.4]]

每个解决方案看起来像:

LHS
[[ 2.2  1. ]
 [-1.   1.9]
 [ 0.4  1.8]]
RHS
[ 242.7   -5.4  112.9]

嵌入式问题(一个解决步骤)如下所示:

LHS
[[ 2.2  1.   0.   0.   0.   0.   0.   0. ]
 [-1.   1.9  0.   0.   0.   0.   0.   0. ]
 [ 0.4  1.8  0.   0.   0.   0.   0.   0. ]
 [ 0.   0.  -1.1 -0.5  0.   0.   0.   0. ]
 [ 0.   0.  -2.3  0.9  0.   0.   0.   0. ]
 [ 0.   0.  -0.6  1.6  0.   0.   0.   0. ]
 [ 0.   0.   0.   0.   1.6 -2.1  0.   0. ]
 [ 0.   0.   0.   0.  -0.1 -0.4  0.   0. ]
 [ 0.   0.   0.   0.  -0.8 -1.8  0.   0. ]
 [ 0.   0.   0.   0.   0.   0.  -0.3 -0.4]
 [ 0.   0.   0.   0.   0.   0.   0.1 -1.9]
 [ 0.   0.   0.   0.   0.   0.   1.8  0.4]]
RHS
[ 242.7   -5.4  112.9  -95.7 -121.4   26.2   57.9  -12.   -88.8  -17.1  -81.6   28.4]

鉴于 lstsq 的假设/标准形式,没有办法在不引入大量零的情况下嵌入这种独立性假设!

lstsq 是:

  • 无法利用稀疏性,因为核心算法是密集算法
    • 看看转换后的形状:这在内存和计算方面会很重!
  • 无法使用 fit 0 中的信息来加快 fit 1 的速度
    • 毕竟它们是独立的;理论上没有信息增益
  • 能够进行大量矢量化(但总体上没有帮助)

您的示例形状

为您的特定形状修剪输出,这一次:也测试一个稀疏求解器

添加代码(最后)

print('EMBEDDING -> sparse-solver')
Zs_ = sp.csc_matrix(Zs_)  # sparse!
start = pc()
result__ = sp.linalg.lsmr(Zs_, Bs_)[0]
end = pc()
print('lhs-shape: ', Zs_.shape)
print('lhs-dense-fill-ratio: ', Zs_.nnz / np.product(Zs_.shape))
print('used time: ', end-start)
print(result__)

输出

LOOPED CALLS
lhs-shape:  (190, 20, 3)
lhs-dense-fill-ratio:  1.0
used time:  0.01716980329027777

[ 11.9  31.8  29.6]
...
[ 44.8  28.2  62.3]]


EMBEDDING INTO ONE CALL
lhs-shape:  (3800, 570)
lhs-dense-fill-ratio:  0.00526315789474
used time:  0.6774500271820254
[ 11.9  31.8  29.6 ... 44.8  28.2  62.3]

EMBEDDING -> sparse-solver
lhs-shape:  (3800, 570)
lhs-dense-fill-ratio:  0.00526315789474
used time:  0.0038423098412817547            # a bit of a surprise
[ 11.9  31.8  29.6 ...  44.8  28.2  62.3]

结论

一般来说:独立解决

在某些情况下,使用 sparse-solver 方法可以更快地解决上述任务,但是这里的分析很困难,因为我们正在比较两种完全不同的算法(直接与迭代),对于其他数据,结果可能会以某种显着的方式发生变化。

【讨论】:

  • 赏金积分。当我有能力的时候来找你。我确实有一个挥之不去的问题。我的问题中提到的矩阵方法对于像这样的扩展维度输入是否可行?例如,我在想np.matmul 如何能够将 3d 输入视为 2d 矩阵的堆栈,如果我稍微操纵输入的形状,我的问题中定义的 coefs 方法可能会起作用。只是在黑暗中拍摄。
  • 这很难回答,内部也很复杂。 lstsq 的隐藏魔法是奇异值分解,它再次建立在||Ax - b|| 的标准形式上。这部分,SVD 是最重要的一步,我们不能在这里使用这种方法做很多事情。嵌入在这里只是受到伤害,因为它仍然很密集并且我们增加了维度。总结一下:lstsq 不仅仅是一些矩阵向量或向量向量的产品,而是显式调用dgelsd。现在我不知道你在寻找什么样的其他封闭式方法。
  • 也许有一些替代形式,它有自己的优点和缺点。但总的来说:我们现在已经进行了分解/独立计算。每种经过调整的方法都将使用该事实! (在上面的实验中,我希望在稀疏情况下使用的迭代方法在使用循环方法时会更好;对于一些不小的实例)。一切都取决于您的数据和确切的任务。如果外环受到伤害,您应该研究 cython 和 co。消除这个瓶颈。但我强烈建议先进行基准测试,以确保没有优化无关紧要的内容。
  • 对于使用直接算法的单个 lstsq(密集;非嵌入式)实例,SVD 方法可能会尽可能快(LAPACK 高度优化:算法、SIMD、缓存)。可能还有其他算法更适合其他数据(如上面的稀疏实验所示)。但这需要更多分析。
  • 我也不认为您的矩阵方法有多大帮助(仍然需要低效嵌入)。您是否查看了具有相同公式的wikis overviewInverting the matrix of the normal equations;在某些情况下包括一些关于非最佳经验性能的 cmets),然后是 numpy 使用的基于 SVD 的方法(Orthogonal decomposition methods)。但这些方法都是封闭形式的方法。一阶方法等替代方法的行为不同,并行梯度计算是个好主意
【解决方案2】:

这是线性代数解决方案,其速度与 @sascha 的循环 version 相当,适用于较小的数组。

print('Matrix formulation')
start = pc()
result = np.squeeze(np.matmul(np.linalg.inv(np.matmul(Zs.swapaxes(1,2), Zs)),
                    np.matmul(Zs.swapaxes(1,2), np.atleast_3d(Bs))))
end = pc()
print('used time: ', end-start)
print(result)

输出:

Matrix formulation
used time:  0.00015713176480858237
[[ 89.2  43.8]
 [ 68.5  41.9]
 [ 61.9  20.5]
 [  5.1  44.1]]

但是,@sascha 的答案很容易在更大的输入中胜出,尤其是随着第三维大小的增长(外生变量/特征的数量)。

Z, B, C = 400, 300, 20

Zs = []
Bs = []
for i in range(Z):
    X, y, = make_regression(n_samples=B, n_features=C, random_state=i)
    Zs.append(X)
    Bs.append(y)
Zs = np.array(Zs)
Bs = np.array(Bs)

# --------

print('Matrix formulation')
start = pc()

result = np.squeeze(np.matmul(np.linalg.inv(np.matmul(Zs.swapaxes(1,2), Zs)),
                    np.matmul(Zs.swapaxes(1,2), np.atleast_3d(Bs))))

end = pc()
print('used time: ', end-start)
print(result)

# --------

print('Looped calls')
start = pc()

result = np.empty((Z, C))
for z in range(Z):
    result[z] = np.linalg.lstsq(Zs[z], Bs[z])[0]

end = pc()
print('used time: ', end-start)
print(result)

输出:

Matrix formulation
used time:  0.24000779996413257
[[  1.2e+01   1.3e-15   6.3e+01 ...,  -8.9e-15   5.3e-15  -1.1e-14]
 [  5.8e+01   2.7e-14  -4.8e-15 ...,   8.5e+01  -1.5e-14   1.8e-14]
 [  1.2e+01  -1.2e-14   4.4e-16 ...,   6.0e-15   8.6e+01   6.0e+01]
 ..., 
 [  2.9e-15   6.6e+01   1.1e-15 ...,   9.8e+01  -2.9e-14   8.4e+01]
 [  2.8e+01   6.1e+01  -1.2e-14 ...,  -2.5e-14   6.3e+01   5.9e+01]
 [  7.0e+01   3.3e-16   8.4e+00 ...,   4.1e+01  -6.2e-15   5.8e+01]]
Looped calls
used time:  0.17400113389658145
[[  1.2e+01   7.1e-15   6.3e+01 ...,  -2.8e-14   1.1e-14  -4.8e-14]
 [  5.8e+01  -5.7e-14  -4.9e-14 ...,   8.5e+01  -5.3e-15   6.8e-14]
 [  1.2e+01   3.6e-14   4.5e-14 ...,  -3.6e-15   8.6e+01   6.0e+01]
 ..., 
 [  6.3e-14   6.6e+01  -1.4e-13 ...,   9.8e+01   2.8e-14   8.4e+01]
 [  2.8e+01   6.1e+01  -2.1e-14 ...,  -1.4e-14   6.3e+01   5.9e+01]
 [  7.0e+01  -1.1e-13   8.4e+00 ...,   4.1e+01  -9.4e-14   5.8e+01]]

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-02-10
    • 1970-01-01
    • 2017-08-27
    • 2012-08-29
    • 1970-01-01
    • 2010-11-26
    • 2019-02-25
    • 2023-04-02
    相关资源
    最近更新 更多