我有一个向量长度n
和一个mxm
矩阵。通常m >> n
(m
比n
大得多)。我需要从对角线开始,将向量重复写入矩阵。例如:
具有4x4
零矩阵的向量v = [v_1, v_2, v_3]
结果为:
v_1, v_2, v_3, 0
0, v_1, v_2, v_3
0, 0, v_1, v_2
0, 0, 0, v_1
由于我必须经常这样做,因此它必须相当快。现在我正在用原始 python 循环遍历矩阵的每一行并将向量写入所需的位置,但这很慢。
check numpy.eye.这对你有用吗?
v = [1,2,3]
N = 5
M = 10
arr = np.sum(np.eye(N, k=i, M=10) * j for i, j in enumerate(v))
arr
>>array([[1., 2., 3., 0., 0., 0., 0., 0., 0., 0.],
[0., 1., 2., 3., 0., 0., 0., 0., 0., 0.],
[0., 0., 1., 2., 3., 0., 0., 0., 0., 0.],
[0., 0., 0., 1., 2., 3., 0., 0., 0., 0.],
[0., 0., 0., 0., 1., 2., 3., 0., 0., 0.]])
编辑(感谢hpaulj建议):如果你的矩阵非常大并且有很多0,你可以使用稀疏矩阵
from scipy.sparse import diags
arr = diags(v,offsets=[0,1,2],shape=(N,M))
print(arr.A)
>>array([[1., 2., 3., 0., 0., 0., 0., 0., 0., 0.],
[0., 1., 2., 3., 0., 0., 0., 0., 0., 0.],
[0., 0., 1., 2., 3., 0., 0., 0., 0., 0.],
[0., 0., 0., 1., 2., 3., 0., 0., 0., 0.],
[0., 0., 0., 0., 1., 2., 3., 0., 0., 0.]])
这是一个与Divakar类似的答案,但只有NumPy。它用零填充给定的向量,然后从该缓冲区生成视图:
import numpy as np
def view_into_diagonals(v, m):
# Add zeros before and after the vector
v_pad = np.pad(v, [(m - 1, m - len(v))], mode='constant')
# Current stride
s, = v_pad.strides
# Offset from which the first row starts
offset = s * (m - 1)
# Make ndarray
view = np.ndarray(shape=(m, m),
dtype=v_pad.dtype,
buffer=v_pad.data,
offset=offset,
# Each column moves one forward, each row moves one backwards
strides=(-s, s))
# Probably better not write to it
view.flags.writeable = False
return view
print(view_into_diagonals([1, 2, 3], 6))
# [[1 2 3 0 0 0]
# [0 1 2 3 0 0]
# [0 0 1 2 3 0]
# [0 0 0 1 2 3]
# [0 0 0 0 1 2]
# [0 0 0 0 0 1]]
解决这个问题的一个想法是在两侧填充适当数量的零,并沿着它获得长度m
的滑动窗口。我们可以利用基于np.lib.stride_tricks.as_strided
scikit-image's view_as_windows
来获得滑动窗口。有关使用基于as_strided
view_as_windows
的更多信息。
from skimage.util.shape import view_as_windows
def extend2D(a, m):
# Create zeros padded version
p1 = np.zeros(m-1,dtype=a.dtype)
p2 = np.zeros(m-len(a),dtype=a.dtype)
b = np.concatenate((p1,a,p2))
# Get sliding windows along it of lengths `m` and finally flip rows
return view_as_windows(b,m)[::-1]
输出只是将窗口视图滑动到输入的零填充版本中。因此,如果您需要输出具有自己的内存空间,请在输出后附加.copy()
。
示例运行 -
In [45]: a
Out[45]: array([5, 8, 6])
In [46]: extend2D(a, m=4)
Out[46]:
array([[5, 8, 6, 0],
[0, 5, 8, 6],
[0, 0, 5, 8],
[0, 0, 0, 5]])
In [47]: extend2D(a, m=5)
Out[47]:
array([[5, 8, 6, 0, 0],
[0, 5, 8, 6, 0],
[0, 0, 5, 8, 6],
[0, 0, 0, 5, 8],
[0, 0, 0, 0, 5]])
优化-I
如果您想通过使用np.lib.stride_tricks.as_strided
坚持使用 NumPy 来弄脏strided-indexing
,并在此过程中避免在上一种方法的最后一步翻转 -
def extend2D_v2(a, m):
p1 = np.zeros(m-1,dtype=a.dtype)
p2 = np.zeros(m-len(a),dtype=a.dtype)
b = np.concatenate((p1,a,p2))
s = b.strides[0]
return np.lib.stride_tricks.as_strided(b[m-1:],shape=(m,m),strides=(-s,s))
优化二
进一步优化,我们可以初始化一个零数组,然后将输入分配给它 -
def extend2D_v3(a, m):
b = np.zeros(2*m-1,dtype=a.dtype)
b[m-1:m-1+len(a)] = a
s = b.strides[0]
return np.lib.stride_tricks.as_strided(b[m-1:],shape=(m,m),strides=(-s,s))
n=100
和m=10000
随机数据数组的时序 -
In [97]: np.random.seed(0)
...: a = np.random.randint(1,9,(100))
In [98]: %timeit extend2D(a, m=10000)
...: %timeit extend2D_v2(a, m=10000)
...: %timeit extend2D_v3(a, m=10000)
10000 loops, best of 3: 51.3 µs per loop
10000 loops, best of 3: 19.6 µs per loop
100000 loops, best of 3: 12.6 µs per loop