从连续的数组切片或卷中创建矩阵

时间:2018-03-28 10:51:49

标签: python numpy

我有一个像:

这样的数组
[10 20 30 40]

我想建立一个像这样的矩阵M1

10  0  0  0
20 10  0  0
30 20 10  0
40 30 20 10

我的方法是首先从数组的连续“卷”中构建以下矩阵M2

10 20 30 40
20 10 40 30
30 20 10 40
40 30 20 10

然后用np.tril取下三角矩阵。我会感兴趣的是有效方法直接构建M2M1而不通过M2

构建M2的简单方法可能是:

import numpy as np

def M2_simple(a):
    a = np.asarray(a)
    return np.stack([a[np.arange(-i, len(a) - i)] for i in range(len(a))]).T

print(M2_simple(np.array([10, 20, 30, 40])))
# [[10 40 30 20]
#  [20 10 40 30]
#  [30 20 10 40]
#  [40 30 20 10]]

经过一番尝试后,我根据advanced indexing找到了以下更好的解决方案:

def M2_indexing(a):
    a = np.asarray(a)
    r = np.arange(len(a))[np.newaxis]
    return a[np.newaxis][np.zeros_like(r), r - r.T].T

这显然比以前快得多,但测量性能似乎仍然没有那么快(例如,它比拼贴更长的数量级,而不是所以不同操作),它需要我建立大的索引矩阵。

有没有更好的方法来构建这些矩阵?

3 个答案:

答案 0 :(得分:3)

编辑:

实际上,您可以使用相同的方法直接构建M1

import numpy as np

def M1_strided(a):
    a = np.asarray(a)
    n = len(a)
    s, = a.strides
    a0 = np.concatenate([np.zeros(len(a) - 1, a.dtype), a])
    return np.lib.stride_tricks.as_strided(
        a0, (n, n), (s, s), writeable=False)[:, ::-1]

print(M1_strided(np.array([10, 20, 30, 40])))
# [[10  0  0  0]
#  [20 10  0  0]
#  [30 20 10  0]
#  [40 30 20 10]]

在这种情况下,速度优势更好,因为您将通话保存到np.tril

N = 100
a = np.square(np.arange(N))
%timeit np.tril(M2_simple(a))
# 792 µs ± 15.3 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit np.tril(M2_indexing(a))
# 259 µs ± 9.45 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit np.tril(M2_strided(a))
# 134 µs ± 1.68 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
%timeit M1_strided(a)
# 45.2 µs ± 583 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

您可以使用np.lib.stride_tricks.as_strided更有效地构建M2矩阵:

import numpy as np
from numpy.lib.stride_tricks import as_strided

def M2_strided(a):
    a = np.asarray(a)
    n = len(a)
    s, = a.strides
    return np.lib.stride_tricks.as_strided(
        np.tile(a[::-1], 2), (n, n), (s, s), writeable=False)[::-1]

作为额外的好处,您将只使用原始数组的两倍内存(而不是平方大小)。你只需要注意不要写入这样创建的数组(如果你稍后打算调用np.tril,这应该不是问题) - 我添加了writeable=False来禁止编写操作。

与IPython进行快速比较:

N = 100
a = np.square(np.arange(N))
%timeit M2_simple(a)
# 693 µs ± 17.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit M2_indexing(a)
# 163 µs ± 1.88 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
%timeit M2_strided(a)
# 38.3 µs ± 348 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

答案 1 :(得分:2)

另一个人使用与@jdehesa's solution类似的as_strided,但是有一个消极的步伐可以让我们在结束时甩开,就像这样 -

def strided_app2(a):
    n = len(a)
    ae = np.concatenate((np.zeros(n-1,dtype=a.dtype),a))
    s = a.strides[0]
    return np.lib.stride_tricks.as_strided(ae[n-1:],(n,n),(s,-s),writeable=False)

示例运行 -

In [66]: a
Out[66]: array([10, 20, 30, 40])

In [67]: strided_app2(a)
Out[67]: 
array([[10,  0,  0,  0],
       [20, 10,  0,  0],
       [30, 20, 10,  0],
       [40, 30, 20, 10]])

进一步挖掘

深入了解每个步骤的时间,它揭示了瓶颈是连接部分。因此,我们可以使用数组初始化,为我们提供另一种选择,对于大型数组来说似乎要好得多,如此 -

def strided_app3(a):
    n = len(a)
    ae = np.zeros(2*n-1,dtype=a.dtype)
    ae[-n:] = a
    s = a.strides[0]
    return np.lib.stride_tricks.as_strided(ae[n-1:],(n,n),(s,-s),writeable=False)

计时 -

In [55]: a = np.random.rand(100000)

In [56]: %timeit M1_strided(a) #@jdehesa's soln
    ...: %timeit strided_app2(a)
    ...: %timeit strided_app3(a)
10000 loops, best of 3: 107 µs per loop
10000 loops, best of 3: 94.5 µs per loop
10000 loops, best of 3: 84.4 µs per loop

In [61]: a = np.random.rand(1000000)

In [62]: %timeit M1_strided(a) #@jdehesa's soln
    ...: %timeit strided_app2(a)
    ...: %timeit strided_app3(a)
100 loops, best of 3: 2.02 ms per loop
100 loops, best of 3: 2 ms per loop
1000 loops, best of 3: 1.84 ms per loop

In [63]: a = np.random.rand(10000000)

In [64]: %timeit M1_strided(a) #@jdehesa's soln
    ...: %timeit strided_app2(a)
    ...: %timeit strided_app3(a)
10 loops, best of 3: 25.2 ms per loop
10 loops, best of 3: 24.6 ms per loop
100 loops, best of 3: 13.9 ms per loop

答案 2 :(得分:2)

实际上,内置有:

>>> import scipy.linalg as sl
>>> sl.toeplitz([10,20,30,40], [0,0,0,0])
array([[10,  0,  0,  0],
       [20, 10,  0,  0],
       [30, 20, 10,  0],
       [40, 30, 20, 10]])