解释numpy中dim、shape、rank、dimension和axis的区别

11 投票
3 回答
34554 浏览
提问于 2025-04-17 20:25

我刚开始学习Python和NumPy,读了好几个教程,但还是对维度、秩、形状、轴和维数之间的区别感到困惑。我脑海中总是停留在矩阵的概念上。如果你说A是一个这样的矩阵:

A = 

1 2 3
4 5 6

那么我只会想到一个2x3的矩阵(两行三列)。在这里,我明白它的形状是2x3。但我真的无法跳出二维矩阵的思维。我不明白,比如说dot()函数的文档中提到的“对于N维,它是在a的最后一个轴和b的倒数第二个轴上的求和乘积”。这让我很困惑,完全理解不了。如果V是一个N:1的向量,而M是一个N:N的矩阵,那么dot(V,M)或dot(M,V)是怎么工作的,它们之间有什么区别,我也搞不明白。

所以,有人能给我解释一下什么是N维数组,什么是形状,什么是轴,以及它们和dot()函数的文档有什么关系吗?如果能用图示来解释这些概念就太好了。

3 个回答

1

在你的情况下,

  1. A 是一个二维数组,也就是一个矩阵,它的 形状 是 (2, 3)。根据 numpy.matrix 的说明:

    矩阵是一种特殊的二维数组,通过操作保持其二维特性。

  2. numpy.rank 返回一个数组的 维度 数量,这和线性代数中的 概念是不同的,比如说 A 是一个维度/秩为 2 的数组。

  3. np.dot(V, M)V.dot(M) 是将矩阵 VM 相乘。需要注意的是,numpy.dot 会尽可能进行乘法运算。如果 V 是 N:1 的形状,而 M 是 N:N 的形状,那么 V.dot(M) 会引发一个 ValueError 错误。

例如:

In [125]: a
Out[125]: 
array([[1],
       [2]])

In [126]: b
Out[126]: 
array([[2, 3],
       [1, 2]])

In [127]: a.dot(b)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-127-9a1f5761fa9d> in <module>()
----> 1 a.dot(b)

ValueError: objects are not aligned

编辑:

我不明白形状 (N,) 和 (N,1) 之间的区别,这和 dot() 的文档有关。

形状为 (N,) 的 V 表示一个长度为 N 的一维数组,而形状为 (N, 1) 的话则表示一个有 N 行、1 列的二维数组:

In [2]: V = np.arange(2)

In [3]: V.shape
Out[3]: (2,)

In [4]: Q = V[:, np.newaxis]

In [5]: Q.shape
Out[5]: (2, 1)

In [6]: Q
Out[6]: 
array([[0],
       [1]])

正如 np.dot 的说明中所说:

对于二维数组,它等同于矩阵乘法,而对于一维数组,则是向量的内积(不涉及复共轭)。

如果其中一个参数是向量,它还会执行向量与矩阵的乘法。比如说 V.shape==(2,); M.shape==(2,2)

In [17]: V
Out[17]: array([0, 1])

In [18]: M
Out[18]: 
array([[2, 3],
       [4, 5]])

In [19]: np.dot(V, M)  #treats V as a 1*N 2D array
Out[19]: array([4, 5]) #note the result is a 1D array of shape (2,), not (1, 2)

In [20]: np.dot(M, V)  #treats V as a N*1 2D array
Out[20]: array([3, 5]) #result is still a 1D array of shape (2,), not (2, 1)

In [21]: Q             #a 2D array of shape (2, 1)
Out[21]: 
array([[0],
       [1]])

In [22]: np.dot(M, Q)  #matrix multiplication
Out[22]: 
array([[3],            #gets a result of shape (2, 1)
       [5]])
4
(2,) * (2,2)
   \   /
    \ /
    (2,)

np.dot 是矩阵乘法的一种扩展。普通的矩阵乘法中,一个形状为 (N,M) 的矩阵与一个形状为 (M,P) 的矩阵相乘,结果会是一个形状为 (N,P) 的矩阵。可以把结果的形状想象成把这两个形状挤在一起形成的((N,M,M,P)),然后去掉中间的数字 M(得到 (N,P))。这就是 np.dot 在扩展到更高维数组时所保持的特性。

当文档中提到:

“对于 N 维来说,它是在 a 的最后一个轴和 b 的倒数第二个轴上的求和乘积。”

这就是在说这个意思。一个形状为 (u,v,M) 的数组与一个形状为 (w,x,y,M,z) 的数组进行点乘,结果会是一个形状为 (u,v,w,x,y,z) 的数组。


让我们看看这个规则在以下代码中的应用:

In [25]: V = np.arange(2); V
Out[25]: array([0, 1])

In [26]: M = np.arange(4).reshape(2,2); M
Out[26]: 
array([[0, 1],
       [2, 3]])

首先,简单的部分:

In [27]: np.dot(M, V)
Out[27]: array([1, 3])

这里没有什么意外;这只是矩阵与向量的乘法。

现在考虑:

In [28]: np.dot(V, M)
Out[28]: array([2, 3])

看看 V 和 M 的形状:

In [29]: V.shape
Out[29]: (2,)

In [30]: M.shape
Out[30]: (2, 2)

所以 np.dot(V,M) 就像是一个形状为 (2,) 的矩阵与一个形状为 (2,2) 的矩阵相乘,结果应该是一个形状为 (2,) 的矩阵。

V 的最后一个(也是唯一的)轴和 M 的倒数第二个轴(也就是 M 的第一个轴)上进行乘法和求和,最后只留下 M 的最后一个轴。

如果你想想象一下:np.dot(V, M) 看起来就像 V 有 1 行 2 列:

[[0, 1]] * [[0, 1],
            [2, 3]] 

所以,当 V 乘以 M 时,np.dot(V, M) 等于:

[[0*0 + 1*2],     [2, 
 [0*1 + 1*3]]   =  3] 

不过,我并不推荐用这种方式去想象 NumPy 数组——至少我从来不这样做。我几乎只关注形状。

你只需要考虑“中间”的轴被点乘,并从结果的形状中消失。


np.sum(arr, axis=0) 告诉 NumPy 在 arr 中求和,去掉 0 轴。如果 arr 是二维的,0 轴就是行。所以例如,如果 arr 看起来像这样:

In [1]: arr = np.arange(6).reshape(2,3); arr
Out[1]: 
array([[0, 1, 2],
       [3, 4, 5]])

那么 np.sum(arr, axis=0) 会沿着列求和,从而 去掉 0 轴(也就是行)。

In [2]: np.sum(arr, axis=0)
Out[2]: array([3, 5, 7])

3 是 0+3 的结果,5 是 1+4,7 是 2+5。

注意 arr 的形状是 (2,3),在求和后,0 轴被 移除,所以结果的形状是 (3,)。0 轴的长度是 2,每个和都是由这 2 个元素相加得到的。形状 (2,3) “变成” (3,)。你可以提前知道结果的形状!这可以帮助你理清思路。

为了测试你的理解,考虑 np.sum(arr, axis=1)。现在 1 轴被移除了。所以结果的形状将是 (2,),结果中的每个元素将是 3 个值的和。

In [3]: np.sum(arr, axis=1)
Out[3]: array([ 3, 12])

3 是 0+1+2,12 是 3+4+5。


所以我们看到,求和一个轴会 去掉 这个轴。这个和 np.dot 有关系,因为 np.dot 进行的计算是一个 求和 的乘积。由于 np.dot 在某些轴上进行求和操作,这个轴就会从结果中移除。这就是为什么将形状为 (2,) 和 (2,2) 的数组应用 np.dot 会得到形状为 (2,) 的数组。两个数组中的第一个 2 被求和,消除了这两个,留下了第二个数组中的第二个 2。

14

NumPy数组的维度要从数据结构的角度来理解,而不是从数学的角度来看,也就是说,它是你需要多少个标量索引才能得到一个标量值。

比如,这就是一个三维数组:

>>> X = np.arange(24).reshape(2, 3, 4)
>>> X
array([[[ 0,  1,  2,  3],
        [ 4,  5,  6,  7],
        [ 8,  9, 10, 11]],

       [[12, 13, 14, 15],
        [16, 17, 18, 19],
        [20, 21, 22, 23]]])

索引一次会得到一个二维数组(矩阵):

>>> X[0]
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])

索引两次会得到一个一维数组(向量),索引三次则会得到一个标量。

X的秩就是它的维度数量:

>>> X.ndim
3
>>> np.rank(X)
3

轴(Axis)大致上可以理解为维度;在广播操作中会用到这个概念:

>>> X.sum(axis=0)
array([[12, 14, 16, 18],
       [20, 22, 24, 26],
       [28, 30, 32, 34]])
>>> X.sum(axis=1)
array([[12, 15, 18, 21],
       [48, 51, 54, 57]])
>>> X.sum(axis=2)
array([[ 6, 22, 38],
       [54, 70, 86]])

说实话,我觉得这个“秩”的定义有点让人困惑,因为它既不符合属性ndim的名称,也不符合线性代数中“秩”的定义。

关于np.dot,你需要知道的是,在NumPy中表示向量有三种方式:一维数组、形状为(n, 1)的列向量,或者形状为(1, n)的行向量。(实际上,还有更多方式,比如形状为(1, n, 1)的数组,但这些比较少见。)当两个参数都是一维数组时,np.dot会进行向量乘法;当一个参数是一维数组,另一个是二维数组时,它会进行矩阵与向量的乘法;否则,它会进行(广义的)矩阵乘法:

>>> A = np.random.randn(2, 3)
>>> v1d = np.random.randn(2)
>>> np.dot(v1d, A)
array([-0.29269547, -0.52215117,  0.478753  ])
>>> vrow = np.atleast_2d(v1d)
>>> np.dot(vrow, A)
array([[-0.29269547, -0.52215117,  0.478753  ]])
>>> vcol = vrow.T
>>> np.dot(vcol, A)
Traceback (most recent call last):
  File "<ipython-input-36-98949c6de990>", line 1, in <module>
    np.dot(vcol, A)
ValueError: matrices are not aligned

规则是“对a的最后一个轴和b的倒数第二个轴进行求和乘积”,这与矩阵乘法的常见定义相匹配并且进行了推广。

(*)dtype=object的数组有点例外,因为它们将任何Python对象视为标量。

撰写回答