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

11

我对Python和Numpy都比较陌生。我看了几篇教程,但仍然对dim、rank、shape、axis和dimension等概念有些困惑。我的思维好像被矩阵表示法困住了。所以,如果你说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()函数的文档相关?如果解释可以形象化表述就更好了。


2
你熟悉矩阵乘法吗? - unutbu
@unutbu 当然可以!但是,如果你执行dot(V,M)并将其视为矩阵乘法,则是错误的,因为V是N:1而M是N:N。但是,dot(M,V)是正确的。然而,执行dot(V,M)仍会给出结果!!! - Jack Twain
@AlexTwain那是不可能的。看看我的答案,或者展示你的代码。 - zhangxaochen
2
这个答案介绍了形状。链接 - Gareth Rees
3个回答

14

NumPy数组的维度必须以数据结构的意义理解,而不是数学意义,即它是获取标量值所需的标量索引数量。(*)

例如,这是一个3维数组:

>>> 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

轴大致等同于维度;它在广播操作中使用:

>>> 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]])

坦白说,我认为这个“rank”的定义令人困惑,因为它既不符合属性ndim的名称,也不符合线性代数定义的rank
现在关于np.dot,你需要理解的是,在NumPy中有三种表示向量的方式:1维数组、形状为(n, 1)的列向量或形状为(1, n)的行向量。(实际上还有更多的方式,例如作为一个形状为(1, n, 1)的数组,但这些方式非常少见。)当两个参数都是1维时,np.dot执行向量乘法,当一个参数是1维而另一个参数是2维时,执行矩阵-向量乘法,否则执行(广义的)矩阵乘法:
>>> 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对象视为标量。

我不理解沿轴求和的概念。这是如何完成的?您能详细说明一下吗? - Jack Twain
1
@AlexTwain:用矩阵更容易解释。让 A = array([[1, 2, 3], [4, 5, 6]])。那么 A.sum(axis=0) == array([5, 7, 9]),即每列的总和,而 A.sum(axis=1) == array([ 6, 15]),即每行的总和。 - Fred Foo
说实话,我觉得“rank”的定义很令人困惑。这就是为什么“rank”被弃用并替换为“ndim”的原因。https://docs.scipy.org/doc/numpy/release.html#rank-function - endolith

4
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数组——至少我从来没有这样做过。我几乎完全关注形状。

(2,) * (2,2)
   \   /
    \ /
    (2,)

只需想象“中间”轴是虚线,并从结果形状中消失即可。


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,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在某些轴上执行求和操作,因此该轴被从结果中删除。这就是为什么将np.dot应用于形状分别为(2,)和(2,2)的数组会得到形状为(2,)的数组。两个数组的第一个2被相加,从而消除了两个2,只剩下第二个数组的2。

我的问题正是关于轴的概念。我不明白像sum(m, axes=0)或axes=1这样的轴是什么意思。另外,在你的例子中,我认为将(2,)*(2,2)相乘应该得到(2,2),因为你从最右边和最左边取维度。就像((N,M,M,P)) --- 变成 (N,P)一样。 - Jack Twain
非常感谢,但我仍然缺少的是当您消除一个轴时,对arr发生了什么的可视化。假设您消除axis = 0,那么arr会是什么样子。axis = 1也是一样。我无法在脑海中想象出来。 - Jack Twain
我不确定我理解这个问题。np.sum(arr,axis = 0)不会影响arr。它返回一个新的数组,不会改变arr。至于可视化方面,请注意上面部分,我描述了像“5等于1 + 4”之类的事情。在结果中寻找5,在arr中寻找1和4。你看到这是如何对同一列中的数字求和的吗? - unutbu
这正是我问的原因。我的想象中,这是在行而不是列上完成的,因为它选择了一列,总和向下移动,即沿着行移动。我看不出这如何“消除”第0维。我无法理解它是如何被消除或其意义。 - Jack Twain
当你看到 axis=0 时,可以将其理解为 axis 0 对应于2D中的“行”(我们可以稍后考虑更高维度)。这指定了求和的“运动方向”。迭代沿着行向下移动。由于同一列中的所有数字都被聚合成一个数字,因此行轴消失(被消除),剩下的只有每列一个数字。因此结果少了一个轴。被消除的轴是表示行的轴,即0轴。因此,当您看到 axis=0 时,对形状的净影响是 0轴 被消除。 - unutbu

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:NV.dot(M) 将引发一个 ValueError

e.g.:

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的文档字符串所述:
对于2-D数组,它相当于矩阵乘法;对于1-D数组,它相当于向量的内积(不进行复共轭)。如果其中一个参数是向量,则它还执行向量-矩阵乘法。例如: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]])

但如果V是一个一维向量,那么dot(V, M)仍然有效!我认为我的困惑更多地与V的形状(N,)有关。我不理解形状(N,)和(N,1)之间的区别以及它与dot()文档的关系。 - Jack Twain

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接