在机器学习中大量的使用NumPy
作为其基础的数据结构,ndarray
是NumPy
的核心数据对象。对于ndarray
高维数组的一个非常容易产生的误解是,使用数学中的矩阵(或者叫“行列式”)概念去尝试理解更高维的场景,或者使用更高维空间去理解,这样都会导致难以较好的理解更高维(5或6维)的数组。本文使用较为直观的示例和可视化的展示,更为“标准”(文档推荐的)的方式去理解ndarray
的更高维数组。更多详细内容,可以参考阅读:
在机器学习中,经常要对多维的数组做各种操作。对高维数组建立更好的直觉理解,则有利于去理解这些操作。例如,我们考虑右侧的代码,想一想该代码的输出是什么?
>>> import numpy as np
>>> np.array([[[1],[2]],[[3],[4]]]).shape
(考虑输出是什么)
要回答这个问题,则需要建立对于多维数组结构的理解。
在NumPy: the absolute basics for beginners
中有如下一段话:
It is familiar practice in mathematics to refer to elements of a matrix by the row index first and the column index second. This happens to be true for two-dimensional arrays, but a better mental model is to think of the column index as coming last and the row index as second to last. This generalizes to arrays with any number of dimensions.
“矩阵”是“线性代数”的主要研究对象,一个\( m \times n \)的矩阵即是一个平面上的\( m \)行\( m \)列的行列式。一种常见的向高维扩展的思考方式是,会将三维数组扩展为三维空间中的数组。但,这样的扩展,非常不利于去理解更高维的数组。这里提到的方案是这样:“a better mental model is to think of the column index as coming last and the row index as second to last.”。
这种理解,也是本文的核心,概况如下:
先通过直观的书写表达,看看这样的数组应该是怎样的。
一般的矩阵(行列式表示):
\begin{bmatrix}
0 & 1 & 2 & 3 & 4 \\
5 & 6 & 7 & 8 & 9 \\
10 & 11 & 12 & 13 & 14 \\
15 & 16 & 17 & 18 & 19 \\
\end{bmatrix}
本文推荐的理解方式:
\[
\begin{bmatrix}
[0 & 1 & 2 & 3 & 4] \\
[5 & 6 & 7 & 8 & 9] \\
[10 & 11 & 12 & 13 & 14] \\
[15 & 16 & 17 & 18 & 19]
\end{bmatrix}
\]
numpy的输出:
>>> np.arange(20).reshape(4,5)
array([[ 0, 1, 2, 3, 4],
[ 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14],
[15, 16, 17, 18, 19]])
如果按照“矩阵”思想去理解这个矩阵很简单。但这里,我们重新按照上述的原则去理解这个数组。即:
形式上,这与一般的矩阵,是完全一致的。只是,思维方式,反过来了。
这个数组已经不能用简单的平面表示了,这里使用了符合上述描述的形式描述,“剩余的维度,则是通过层的方式去构建”,则有:
本文推荐的理解方式:
\[
\begin{array}{r c}
\text{Layer 1:} &
\left[
\begin{array}{c}
[0 & 1 & 2 & 3 & 4] \\
[5 & 6 & 7 & 8 & 9] \\
[10 & 11 & 12 & 13 & 14] \\
[15 & 16 & 17 & 18 & 19] \\
\end{array}
\right]
\\
\text{Layer 2:} &
\left[
\begin{array}{c}
[20 & 21 & 22 & 23 & 24] \\
[25 & 26 & 27 & 28 & 29] \\
[30 & 31 & 32 & 33 & 34] \\
[35 & 36 & 37 & 38 & 39] \\
\end{array}
\right]
\\
\text{Layer 3:} &
\left[
\begin{array}{c}
[40 & 41 & 42 & 43 & 44] \\
[45 & 46 & 47 & 48 & 49] \\
[50 & 51 & 52 & 53 & 54] \\
[55 & 56 & 57 & 58 & 59] \\
\end{array}
\right]
\end{array}
\]
>>> np.arange(60).reshape(3,4,5)
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, 24],
[25, 26, 27, 28, 29],
[30, 31, 32, 33, 34],
[35, 36, 37, 38, 39]],
[[40, 41, 42, 43, 44],
[45, 46, 47, 48, 49],
[50, 51, 52, 53, 54],
[55, 56, 57, 58, 59]]])
这时,矩阵的想法就不太好用了。这里继续按照上面的原则,考虑:
循着这样的思考模式,不断地叠加更多的“层”,就可以理解更高维度的数组了。
这里先试用“层”的思维,可视化的表示该数组如下:
\[
\left[
\begin{array}{c}
\text{Layer}^{(0)}_1
\left[
\begin{array}{c}
\text{Layer}^{(1)}_1
\left[
\begin{array}{c}
[000 & 001 & 002 & 003 & 004] \\
[005 & 006 & 007 & 008 & 009] \\
[010 & 011 & 012 & 013 & 014] \\
[015 & 016 & 017 & 018 & 019] \\
\end{array}
\right] \\
\text{Layer}^{(1)}_2
\left[
\begin{array}{c}
[020 & 021 & 022 & 023 & 024] \\
[025 & 026 & 027 & 028 & 029] \\
[030 & 031 & 032 & 033 & 034] \\
[035 & 036 & 037 & 038 & 039] \\
\end{array}
\right] \\
\text{Layer}^{(1)}_3
\left[
\begin{array}{c}
[040 & 041 & 042 & 043 & 044] \\
[045 & 046 & 047 & 048 & 049] \\
[050 & 051 & 052 & 053 & 054] \\
[055 & 056 & 057 & 058 & 059] \\
\end{array}
\right]
\end{array}
\right] \\
\text{Layer}^{(0)}_2
\left[
\begin{array}{c}
\text{Layer}^{(1)}_1
\left[
\begin{array}{c}
[060 & 061 & 062 & 063 & 064] \\
[065 & 066 & 067 & 068 & 069] \\
[070 & 071 & 072 & 073 & 074] \\
[075 & 076 & 077 & 078 & 079] \\
\end{array}
\right] \\
\text{Layer}^{(1)}_2
\left[
\begin{array}{c}
[080 & 081 & 082 & 083 & 084] \\
[085 & 086 & 087 & 088 & 089] \\
[090 & 091 & 092 & 093 & 094] \\
[095 & 096 & 097 & 098 & 099] \\
\end{array}
\right] \\
\text{Layer}^{(1)}_3
\left[
\begin{array}{c}
[100 & 101 & 102 & 103 & 104] \\
[105 & 106 & 107 & 108 & 109] \\
[110 & 111 & 112 & 113 & 114] \\
[115 & 116 & 117 & 118 & 119] \\
\end{array}
\right]
\end{array}
\right]
\end{array}
\right]
\]
>>> np.arange(120).reshape(2,3,4,5)
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, 24],
[ 25, 26, 27, 28, 29],
[ 30, 31, 32, 33, 34],
[ 35, 36, 37, 38, 39]],
[[ 40, 41, 42, 43, 44],
[ 45, 46, 47, 48, 49],
[ 50, 51, 52, 53, 54],
[ 55, 56, 57, 58, 59]]],
[[[ 60, 61, 62, 63, 64],
[ 65, 66, 67, 68, 69],
[ 70, 71, 72, 73, 74],
[ 75, 76, 77, 78, 79]],
[[ 80, 81, 82, 83, 84],
[ 85, 86, 87, 88, 89],
[ 90, 91, 92, 93, 94],
[ 95, 96, 97, 98, 99]],
[[100, 101, 102, 103, 104],
[105, 106, 107, 108, 109],
[110, 111, 112, 113, 114],
[115, 116, 117, 118, 119]]]])
继续按照上面的原则,考虑:
使用这样的模式,就可以将一个多维数组的表示平面化。并且注意到,这与ndarray
输出的形式是几乎完全一致的。
有了上面的可视化展示以及上面逐步的介绍,应该可以更容易理解前面NumPy: the absolute basics for beginners
所提到的直觉“a better mental model is to think of the column index as coming last and the row index as second to last”。
有了这个直觉,我们再来考虑最前面提到的问题:
>>> import numpy as np
>>> np.array([[[1],[2]],[[3],[4]]]).shape
最内层的列,就是最后的维度长度,这里是 1,所以就是 ? x 1
;该列所对应的行数,就是倒数第二个维度的长度,这里做如下的格式化,可以看到有两行,所以这是一个? x 2 x 1
的数组;再向上看一层,共有两个该2x1
的数组,故,该数组的shape
时:2x2x1
。
[
[
[1],
[2]
],
[
[3],
[4]
]
]
再确认最后的输出:
>>> import numpy as np
>>> np.array([[[1],[2]],[[3],[4]]]).shape
(2, 2, 1)
这种理解的核心即是“a better mental model is to think of the column index as coming last and the row index as second to last”,简单概括如下: