计算矩阵未成年人的Numpy Routine?

时间:2010-10-04 18:52:48

标签: python numpy

我有兴趣使用numpy来计算给定方阵的所有未成年人。是否有一种使用数组切片的光滑方式来做到这一点?我想象一个人可以旋转列,删除最后一列,旋转结果矩阵的行并删除最后一行,但我没有在numpy文档中找到任何表明这是可能的。

(问:为什么这样?A:我有一个相当大的矩阵的长序列{M_n},大约1,000,000 10,000 x 10,000矩阵,我想计算每个矩阵的行列式。每个矩阵都是从它的前身得到的通过改变一个系数来计算序列中第一个矩阵的行列式,然后计算差值det(M_ {n + 1}) - det(M_n),这是产品的速度要快得多。变化的系数及其次要。)

4 个答案:

答案 0 :(得分:24)

In [34]: arr=np.random.random((4,4))

In [35]: arr
Out[35]: 
array([[ 0.00750932,  0.47917318,  0.39813503,  0.11755234],
       [ 0.30330724,  0.67527229,  0.71626247,  0.22526589],
       [ 0.5821906 ,  0.2060713 ,  0.50149411,  0.0328739 ],
       [ 0.42066294,  0.88529916,  0.09179092,  0.39389844]])

这会给次要arr,删除第1行和第2列:

In [36]: arr[np.array([0,2,3])[:,np.newaxis],np.array([0,1,3])]
Out[36]: 
array([[ 0.00750932,  0.47917318,  0.11755234],
       [ 0.5821906 ,  0.2060713 ,  0.0328739 ],
       [ 0.42066294,  0.88529916,  0.39389844]])

所以,你可以使用这样的东西:

def minor(arr,i,j):
    # ith row, jth column removed
    return arr[np.array(list(range(i))+list(range(i+1,arr.shape[0])))[:,np.newaxis],
               np.array(list(range(j))+list(range(j+1,arr.shape[1])))]

关于这是如何工作的:

注意索引数组的形状:

In [37]: np.array([0,2,3])[:,np.newaxis].shape
Out[37]: (3, 1)

In [38]: np.array([0,1,3]).shape
Out[38]: (3,)

使用[:,np.newaxis]只是为了给第一个数组赋予形状(3,1)。

由于这些是numpy数组(而不是切片),numpy使用所谓的“花式”索引。花式索引的规则要求两个数组的形状相同,或者,当它们不相同时,使用广播来“抽取”形状以使它们匹配。

在这种情况下,第二个阵列的形状(3,)被泵送到(1,3)。但 (3,1)和(1,3)不匹配,因此(3,1)被泵送到(3,3)并且(1,3)被泵送到(3,3)。

啊,最后,两个numpy数组(广播后)具有相同的形状,(3,3)。

Numpy需要arr[<array of shape (3,3)>, <array of shape (3,3)>] 并返回一个形状数组(毫不奇怪)(3,3)。

返回数组的第(i,j)个元素将是

arr[(i,j)-th element of first array, (i,j)-th element of second array]

第一个和第二个数组在概念上看起来像这样:

first array:     second array:
[[0 0 0],        [[0, 1, 3],
 [2 2 2],         [0, 1, 3],
 [3 3 3]]         [0, 1, 3]]

答案 1 :(得分:3)

如果你一次只改变一个矩阵的一个元素,你可能最好使用Sherman-Morrison类型的公式,(wiki):这样,你就有了N ^ 2的复杂性而不是N ^ 3.

答案 2 :(得分:3)

unutbu提供的答案已经很不错了,并且优化算法@ ev-br的答案使我踏上了一段有趣的旅程。

我在下面对这个问题的回答只是为了更清楚地表达意图。

import numpy as np

arr = np.random.normal(0,1,(4,4))



def matrix_minor(arr, i, j):
    return np.delete(np.delete(arr,i,axis=0), j, axis=1)

# tests
arr

matrix_minor(arr, 0, 0)

matrix_minor(arr, 0, 1)

答案 3 :(得分:2)

前几天我正在考虑这个确切的问题,并为此做了几次尝试和性能测试,所以我将分享我的发现。

添加到 PaulDongunutbu 提供的解决方案之外,我想出了另外两个解决方案。 一个 (minor_mask()) 使用 Numpy 数组的基于掩码的奇特索引,另一个 (minor_fortran()) 是我在玩好 ol' Fortran 时想到的一个解决方案,并稍微修改它以编译努巴。将所有解决方案放在一起并执行一些基准测试:

示例代码

import numpy as np
import numba


def minor_mask(A, i, j):
    """Own solution, based on NumPy fancy indexing"""
    mask = np.ones_like(A, dtype=bool)
    mask[i, :] = False
    mask[:, j] = False

    minor = A[mask].reshape(A.shape[0] - 1, A.shape[1] - 1)

    del mask

    return minor


def minor_unutbu(A, i, j):
    """Solution provided by unutbu"""
    return A[
        np.array(list(range(i)) + list(range(i + 1, A.shape[0])))[:, np.newaxis],
        np.array(list(range(j)) + list(range(j + 1, A.shape[1]))),
    ]


def minor_pauldong(A, i, j):
    """Solution by PaulDong"""
    return np.delete(np.delete(A, i, axis=0), j, axis=1)


@numba.njit()
def minor_fortran(A, i, j):
    """
    Matrix minor calculation based on a Fortran routine.
    Compiles nicely with numba.
    """

    minor = np.zeros((A.shape[0] - 1, A.shape[0] - 1))

    for m in range(A.shape[0]):
        ishift = 0
        jshift = 0

        if m > i:
            ishift = 1

        for n in range(A.shape[1]):
            if n > j:
                jshift = 1

            minor[m - ishift, n - jshift] = A[m, n]

    return minor

性能测试

在我的机器上(i5 9600K、32 GB RAM、openSUSE Leap 15.2、Python 3.8.9、Numpy 1.20.3、Numba 0.53.1、ipykernel 5.5.5),我使用以下方法获得了大小矩阵的以下结果以下代码:

m_small = np.arange(1e4).reshape(100, 100)
m_large = np.arange(1e8).reshape(10000, 10000)

# First run for setup of Numba and comparing results
r1 = minor_mask(m_large, 10, 11)
r2 = minor_unutbu(m_large, 10, 11)
r3 = minor_pauldong(m_large, 10, 11)
r4 = minor_fortran(m_large, 10, 11)

print(np.all(r1 == r2))
# --> True
print(np.all(r2 == r3))
# --> True
print(np.all(r3 == r4))
# --> True

# Large matrices
%timeit minor_mask(m_large, 10, 10)
# 136 ms ± 1.95 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit minor_unutbu(m_large, 10, 10)
# 247 ms ± 8.31 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit minor_pauldong(m_large, 10, 10)
# 217 ms ± 3.79 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit minor_fortran(m_large, 10, 10)
# 153 ms ± 1.26 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

# Small matrices
%timeit minor_mask(m_small, 10, 10)
# 12.4 µs ± 22.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
%timeit minor_unutbu(m_small, 10, 10)
# 36.7 µs ± 140 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
%timeit minor_pauldong(m_small, 10, 10)
# 14.5 µs ± 102 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
%timeit minor_fortran(m_small, 10, 10)
#10.4 µs ± 34.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

总结

因此,我们一起发现 @unutbu 基于列表的方法在两个测试用例中的表现都最差,其次是 @PaulDongs 方法(尽管恕我直言是所有解决方案中最干净的)。
花哨的索引方法似乎对小矩阵和大矩阵都表现良好,只有针对小矩阵的编译解决方案才能超越。我预计掩码方法对于非常大的矩阵效率低下,因为我们需要将布尔掩码存储在内存中以进行掩码,但我没有在此处执行任何内存分析。

我知道在包含 numba 解决方案时,这种比较不是平等的,但我想说它仍然与 Python 的数字运算相关。