解释numpy中dim、shape、rank、dimension和axis的区别
我刚开始学习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 个回答
在你的情况下,
A
是一个二维数组,也就是一个矩阵,它的 形状 是 (2, 3)。根据numpy.matrix
的说明:矩阵是一种特殊的二维数组,通过操作保持其二维特性。
numpy.rank
返回一个数组的 维度 数量,这和线性代数中的 秩 概念是不同的,比如说A
是一个维度/秩为 2 的数组。np.dot(V, M)
或V.dot(M)
是将矩阵V
和M
相乘。需要注意的是,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]])
(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。
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对象视为标量。