勿谢!你需要的NumPy基础都在这了

Python 多维数组详解(numpy) 阅读详情

本文转自“机器之心”(almosthuman2014)

NumPy 是一个为 Python 提供高性能向量、矩阵和高维数据结构的科学计算库。它通过 C 和 Fortran 实现,因此用向量和矩阵建立方程并实现数值计算有非常好的性能。NumPy 基本上是所有使用 Python 进行数值计算的框架和包的基础,例如 TensorFlow 和 PyTorch,构建机器学习模型最基础的内容就是学会使用 NumPy 搭建计算过程。

基础知识

NumPy 主要的运算对象为同质的多维数组,即由同一类型元素(一般是数字)组成的表格,且所有元素通过正整数元组进行索引。在 NumPy 中,维度 (dimension) 也被称之为轴线(axes)。

比如坐标点 [1, 2, 1] 有一个轴线。这个轴上有 3 个点,所以我们说它的长度(length)为 3。而如下数组(array)有 2 个轴线,长度同样为 3。

[[ 1., 0., 0.],
[ 0., 1., 2.]]

NumPy 的数组类(array class)叫做 ndarray,同时我们也常称其为数组(array)。注意 numpy.array 和标准 Python 库中的类 array.array 是不同的。标准 Python 库中的类 array.array 只处理一维的数组,提供少量的功能。ndarray 还具有如下很多重要的属性:

  • ndarray.ndim:显示数组的轴线数量(或维度)。

  • ndarray.shape:显示在每个维度里数组的大小。如 n 行 m 列的矩阵,它的 shape 就是(n,m)。

>>> b = np.array([[1,2,3],[4,5,6]])
>>> b.shape
(2, 3)
  • ndarray.size:数组中所有元素的总量,相当于数组的 shape 中所有元素的乘积,例如矩阵的元素总量为行与列的乘积。

>>> b = np.array([[1,2,3],[4,5,6]])
>>> b.size
6
  • ndarray.dtype:显示数组元素的类型。Python 中的标准 type 函数同样可以用于显示数组类型,NumPy 有它自己的类型如:numpy.int32, numpy.int16, 和 numpy.float64,其中「int」和「float」代表数据的种类是整数还是浮点数,「32」和「16」代表这个数组的字节数(存储大小)。

  • ndarray.itemsize:数组中每个元素的字节存储大小。例如元素类型为 float64 的数组,其 itemsize 为 8(=64/8)。

>>> import numpy as np
>>> a = np.arange(15).reshape(3, 5)
>>> a
array([[ 0,  1,  2,  3,  4],
       [ 5,  6,  7,  8,  9],
       [10, 11, 12, 13, 14]])
>>> a.shape
(3, 5)
>>> a.ndim
2
>>> a.dtype.name
 int64
>>> a.itemsize
8
>>> a.size
15
>>> type(a)
<type  numpy.ndarray >
>>> b = np.array([6, 7, 8])
>>> b
array([6, 7, 8])
>>> type(b)
<type  numpy.ndarray >

创建数组

NumPy 有很多种创建数组的方法。比如,你可以用 Python 的列表(list)来创建 NumPy 数组,其中生成的数组元素类型与原序列相同。

>>> import numpy as np
>>> a = np.array([2,3,4])
>>> a
array([2, 3, 4])
>>> a.dtype
dtype( int64 )
>>> b = np.array([1.2, 3.5, 5.1])
>>> b.dtype
dtype( float64 )

一个常见的误差(error)在于调用 array 时使用了多个数值参数,而正确的方法应该是用「[]」来定义一个列表的数值而作为数组的一个参数。

>>> a = np.array(1,2,3,4)    # WRONG
>>> a = np.array([1,2,3,4])  # RIGHT

array 将序列中的序列转换为二维的数组,序列中的序列中的序列转换为三维数组,以此类推。

>>> b = np.array([(1.5,2,3), (4,5,6)])
>>> b
array([[ 1.5,  2. ,  3. ],
       [ 4. ,  5. ,  6. ]])

数组的类型也可以在创建时指定清楚:

>>> b = np.array([(1.5,2,3), (4,5,6)])
>>> c = np.array( [ [1,2], [3,4] ], dtype=complex )
>>> c
array([[ 1.+0.j,  2.+0.j],
       [ 3.+0.j,  4.+0.j]])

一般数组的内部元素初始是未知的,但它的大小是已知的。因此,NumPy 提供了一些函数可以创建有初始数值的占位符数组,这样可以减少不必要的数组增长及运算成本。

函数 zeros 可创建一个内部元素全是 0 的数组,函数 ones 可创建一个内部元素全是 1 的数组,函数 empty 可创建一个初始元素为随机数的数组,具体随机量取决于内存状态。默认状态下,创建数组的数据类型(dtype)一般是 float64。

>>> np.zeros( (3,4) )
array([[ 0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.]])
>>> np.ones( (2,3,4), dtype=np.int16 )   # dtype can also be specified
array([[[ 1, 1, 1, 1],
        [ 1, 1, 1, 1],
        [ 1, 1, 1, 1]],
       [[ 1, 1, 1, 1],
        [ 1, 1, 1, 1],
        [ 1, 1, 1, 1]]], dtype=int16)
>>> np.empty( (2,3) )                    # uninitialized, output may vary
array([[  3.73603959e-262,   6.02658058e-154,   6.55490914e-260],
       [  5.30498948e-313,   3.14673309e-307,   1.00000000e+000]])

为了创建数列,NumPy 提供一个与 range 类似的函数来创建数组:arange。

>>> np.arange( 10, 30, 5 )
array([10, 15, 20, 25])
>>> np.arange( 0, 2, 0.3 )                 # it accepts float arguments
array([ 0. ,  0.3,  0.6,  0.9,  1.2,  1.5,  1.8])

当 arange 使用浮点型参数时,因为浮点精度的有限性,arange 不能判断有需要创建的数组多少个元素。在这种情况下,换成 linspace 函数可以更好地确定区间内到底需要产生多少个数组元素。

>>> from numpy import pi
>>> np.linspace( 0, 2, 9 )                 # 9 numbers from 0 to 2
array([ 0.  ,  0.25,  0.5 ,  0.75,  1.  ,  1.25,  1.5 ,  1.75,  2.  ])
>>> x = np.linspace( 0, 2*pi, 100 )        # useful to evaluate function at lots of points
>>> f = np.sin(x)

array, zeros, zeros_like, ones, ones_like, empty, empty_like, arange, linspace, numpy.random.rand, numpy.random.randn, fromfunction, fromfile (这些函数也可以创建数组,有时间可以尝试解释)

输出数组

当你输出一个数组时,NumPy 显示这个数组的方式和嵌套列表是相似的。但将数组打印到屏幕需要遵守以下布局:

  • 最后一个轴由左至右打印

  • 倒数第二个轴为从上到下打印

  • 其余的轴都是从上到下打印,且每一块之间都通过一个空行分隔

如下所示,一维数组输出为一行、二维为矩阵、三维为矩阵列表。

>>> a = np.arange(6)                         # 1d array
>>> print(a)
[0 1 2 3 4 5]
>>>
>>> b = np.arange(12).reshape(4,3)           # 2d array
>>> print(b)
[[ 0  1  2]
 [ 3  4  5]
 [ 6  7  8]
 [ 9 10 11]]
>>>
>>> c = np.arange(24).reshape(2,3,4)         # 3d array
>>> print(c)
[[[ 0  1  2  3]
  [ 4  5  6  7]
  [ 8  9 10 11]]
 [[12 13 14 15]
  [16 17 18 19]
  [20 21 22 23]]]

上述使用的 reshape 函数可指定数组的行列数,并将所有元素按指定的维度数排列,详细介绍请看后面章节。在数组的打印中,如果一个数组所含元素数太大,NumPy 会自动跳过数组的中间部分,只输出两边。

>>> print(np.arange(10000))
[   0    1    2 ..., 9997 9998 9999]
>>>
>>> print(np.arange(10000).reshape(100,100))
[[   0    1    2 ...,   97   98   99]
 [ 100  101  102 ...,  197  198  199]
 [ 200  201  202 ...,  297  298  299]
 ...,
 [9700 9701 9702 ..., 9797 9798 9799]
 [9800 9801 9802 ..., 9897 9898 9899]
 [9900 9901 9902 ..., 9997 9998 9999]]

如果想要 NumPy 输出整个数组,你可以用 set_printoptions 改变输出设置。

>>> np.set_printoptions(threshold=np.nan)

基础运算

数组中的算术运算一般是元素级的运算,运算结果会产生一个新的数组。如下所示减法、加法、平方、对应元素乘积和逻辑运算都是元素级的操作。

>>> a = np.array( [20,30,40,50] )
>>> b = np.arange( 4 )
>>> b
array([0, 1, 2, 3])
>>> c = a-b
>>> c
array([20, 29, 38, 47])
>>> b**2
array([0, 1, 4, 9])
>>> 10*np.sin(a)
array([ 9.12945251, -9.88031624,  7.4511316 , -2.62374854])
>>> a<35
array([ True, True, False, False])

不同于许多科学计算语言,乘法算子 * 或 multiple 函数在 NumPy 数组中用于元素级的乘法运算,矩阵乘法可用 dot 函数或方法来执行。

>>> A = np.array( [[1,1],
...             [0,1]] )
>>> B = np.array( [[2,0],
...             [3,4]] )
>>> A*B                         # elementwise product
array([[2, 0],
       [0, 4]])
>>> A.dot(B)                    # matrix product
array([[5, 4],
       [3, 4]])
>>> np.dot(A, B)                # another matrix product
array([[5, 4],
       [3, 4]])

有一些操作,如 += 和 *=,其输出结果会改变一个已存在的数组,而不是如上述运算创建一个新数组。

>>> a = np.ones((2,3), dtype=int)
>>> b = np.random.random((2,3))
>>> a *= 3
>>> a
array([[3, 3, 3],
       [3, 3, 3]])
>>> b += a
>>> b
array([[ 3.417022  ,  3.72032449,  3.00011437],
       [ 3.30233257,  3.14675589,  3.09233859]])
>>> a += b                  # b is not automatically converted to integer type
Traceback (most recent call last):
  ...
TypeError: Cannot cast ufunc add output from dtype( float64 ) to dtype( int64 ) with casting rule  same_kind

当操作不同数据类型的数组时,最后输出的数组类型一般会与更普遍或更精准的数组相同(这种行为叫做 Upcasting)。

>>> a = np.ones(3, dtype=np.int32)
>>> b = np.linspace(0,pi,3)
>>> b.dtype.name
 float64
>>> c = a+b
>>> c
array([ 1.        ,  2.57079633,  4.14159265])
>>> c.dtype.name
 float64
>>> d = np.exp(c*1j)
>>> d
array([ 0.54030231+0.84147098j, -0.84147098+0.54030231j,
       -0.54030231-0.84147098j])
>>> d.dtype.name
 complex128

许多一元运算,如计算数组中所有元素的总和,是属于 ndarray 类的方法。

>>> a = np.random.random((2,3))
>>> a
array([[ 0.18626021,  0.34556073,  0.39676747],
       [ 0.53881673,  0.41919451,  0.6852195 ]])
>>> a.sum()
2.5718191614547998
>>> a.min()
0.1862602113776709
>>> a.max()
0.6852195003967595

默认状态下,这些运算会把数组视为一个数列而不论它的 shape。然而,如果在指定 axis 参数下,你可以指定针对哪一个维度进行运算。如下 axis=0 将针对每一个列进行运算,例如 b.sum(axis=0) 将矩阵 b 中每一个列的所有元素都相加为一个标量。

>>> b = np.arange(12).reshape(3,4)
>>> b
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])
>>>
>>> b.sum(axis=0)                            # sum of each column
array([12, 15, 18, 21])
>>>
>>> b.min(axis=1)                            # min of each row
array([0, 4, 8])
>>>
>>> b.cumsum(axis=1)                         # cumulative sum along each row
array([[ 0,  1,  3,  6],
       [ 4,  9, 15, 22],
       [ 8, 17, 27, 38]])

索引、截取和迭代

一维数组可以被索引、截取(Slicing)和迭代,就像 Python 列表和元组一样。注意其中 a[0:6:2] 表示从第 1 到第 6 个元素,并对每两个中的第二个元素进行操作。

>>> a = np.arange(10)**3
>>> a
array([  0,   1,   8,  27,  64, 125, 216, 343, 512, 729])
>>> a[2]
8
>>> a[2:5]
array([ 8, 27, 64])
>>> a[:6:2] = -1000    # equivalent to a[0:6:2] = -1000; from start to position 6, exclusive, set every 2nd element to -1000
>>> a
array([-1000,     1, -1000,    27, -1000,   125,   216,   343,   512,   729])
>>> a[ : :-1]                                 # reversed a
array([  729,   512,   343,   216,   125, -1000,    27, -1000,     1, -1000])
>>> for i in a:
...     print(i**(1/3.))
...
nan
1.0
nan
3.0
nan
5.0
6.0
7.0
8.0
9.0

多维数组每个轴都可以有一个索引。这些索引在元组中用逗号分隔:

>>> def f(x,y):
...     return 10*x+y
...
>>> b = np.fromfunction(f,(5,4),dtype=int)
>>> b
array([[ 0,  1,  2,  3],
       [10, 11, 12, 13],
       [20, 21, 22, 23],
       [30, 31, 32, 33],
       [40, 41, 42, 43]])
>>> b[2,3]
23
>>> b[0:5, 1]                       # each row in the second column of b
array([ 1, 11, 21, 31, 41])
>>> b[ : ,1]                        # equivalent to the previous example
array([ 1, 11, 21, 31, 41])
>>> b[1:3, : ]                      # each column in the second and third row of b
array([[10, 11, 12, 13],
       [20, 21, 22, 23]])

当有些维度没有指定索引时,空缺的维度被默认为取所有元素。

>>> b[-1]                                  # the last row. Equivalent to b[-1,:]
array([40, 41, 42, 43])

如上因为省略了第二维,b[i] 表示输出第 i 行。当然我们也可以用「:」表示省略的维度,例如 b[i] 等价于 b[i, :]。此外,NumPy 还允许使用 dots (...) 表示足够多的冒号来构建完整的索引元组。

比如,如果 x 是 5 维数组:

  • x[1,2,...] 等于 x[1,2,:,:,:],

  • x[...,3] 等于 x[:,:,:,:,3] 

  • x[4,...,5,:] 等于 x[4,:,:,5,:]

>>> c = np.array( [[[  0,  1,  2],               # a 3D array (two stacked 2D arrays)
...                 [ 10, 12, 13]],
...                [[100,101,102],
...                 [110,112,113]]])
>>> c.shape
(2, 2, 3)
>>> c[1,...]                                   # same as c[1,:,:] or c[1]
array([[100, 101, 102],
       [110, 112, 113]])
>>> c[...,2]                                   # same as c[:,:,2]
array([[  2,  13],
       [102, 113]])

多维数组中的迭代以第一条轴为参照完成,如下每一次循环都输出一个 b[i]:

>>> for row in b:
...     print(row)
...
[0 1 2 3]
[10 11 12 13]
[20 21 22 23]
[30 31 32 33]
[40 41 42 43]

然而,如果想在数组的每个元素上进行操作,可以用 flat 方法。flat 是一个在数组所有元素中运算的迭代器,如下将逐元素地对数组进行操作。

>>> for element in b.flat:
...     print(element)
...
0
1
2
3
10
11
12
13
20
21
22
23
30
31
32
33
40
41
42
43

Shape 变换

改变数组的 shape

一个数组的 shape 是由轴及其元素数量决定的,它一般由一个整型元组表示,且元组中的整数表示对应维度的元素数。

>>> a = np.floor(10*np.random.random((3,4)))
>>> a
array([[ 2.,  8.,  0.,  6.],
       [ 4.,  5.,  1.,  1.],
       [ 8.,  9.,  3.,  6.]])
>>> a.shape
(3, 4)

一个数组的 shape 可以由许多方法改变。例如以下三种方法都可输出一个改变 shape 后的新数组,它们都不会改变原数组。其中 reshape 方法在实践中会经常用到,因为我们需要改变数组的维度以执行不同的运算。

>>> a.ravel()  # returns the array, flattened
array([ 2.,  8.,  0.,  6.,  4.,  5.,  1.,  1.,  8.,  9.,  3.,  6.])
>>> a.reshape(6,2)  # returns the array with a modified shape
array([[ 2.,  8.],
       [ 0.,  6.],
       [ 4.,  5.],
       [ 1.,  1.],
       [ 8.,  9.],
       [ 3.,  6.]])
>>> a.T  # returns the array, transposed
array([[ 2.,  4.,  8.],
       [ 8.,  5.,  9.],
       [ 0.,  1.,  3.],
       [ 6.,  1.,  6.]])
>>> a.T.shape
(4, 3)
>>> a.shape
(3, 4)

ravel() 和 flatten() 都是将多维数组降位一维,flatten() 返回一份新的数组,且对它所做的修改不会影响原始数组,而 ravel() 返回的是 view,会影响原始矩阵。

在矩阵的转置中,行和列的维度将交换,且矩阵中每一个元素将沿主对角线对称变换。此外,reshape 如下所示返回修改过维度的新数组,而 resize 方法将直接修改原数组本身的维度。

>>> a
array([[ 2.,  8.,  0.,  6.],
       [ 4.,  5.,  1.,  1.],
       [ 8.,  9.,  3.,  6.]])
>>> a.resize((2,6))
>>> a
array([[ 2.,  8.,  0.,  6.,  4.,  5.],
       [ 1.,  1.,  8.,  9.,  3.,  6.]])

如果在 shape 变换中一个维度设为-1,那么这一个维度包含的元素数将会被自动计算。如下所示,a 一共有 12 个元素,在确定一共有 3 行后,-1 会自动计算出应该需要 4 列才能安排所有的元素。

>>> a.reshape(3,-1)
array([[ 2.,  8.,  0.,  6.],
       [ 4.,  5.,  1.,  1.],
       [ 8.,  9.,  3.,  6.]])

数组堆叠

数组可以在不同轴上被堆叠在一起。如下所示 vstack 将在第二个维度(垂直)将两个数组拼接在一起,而 hstack 将在第一个维度(水平)将数组拼接在一起。

>>> a = np.floor(10*np.random.random((2,2)))
>>> a
array([[ 8.,  8.],
       [ 0.,  0.]])
>>> b = np.floor(10*np.random.random((2,2)))
>>> b
array([[ 1.,  8.],
       [ 0.,  4.]])
>>> np.vstack((a,b))
array([[ 8.,  8.],
       [ 0.,  0.],
       [ 1.,  8.],
       [ 0.,  4.]])
>>> np.hstack((a,b))
array([[ 8.,  8.,  1.,  8.],
       [ 0.,  0.,  0.,  4.]])

column_stack 函数可堆叠一维数组为二维数组的列,作用相等于针对二维数组的 hstack 函数。

>>> from numpy import newaxis
>>> np.column_stack((a,b))     # with 2D arrays
array([[ 8.,  8.,  1.,  8.],
       [ 0.,  0.,  0.,  4.]])
>>> a = np.array([4.,2.])
>>> b = np.array([3.,8.])
>>> np.column_stack((a,b))     # returns a 2D array
array([[ 4., 3.],
       [ 2., 8.]])
>>> np.hstack((a,b))           # the result is different
array([ 4., 2., 3., 8.])
>>> a[:,newaxis]               # this allows to have a 2D columns vector
array([[ 4.],
       [ 2.]])
>>> np.column_stack((a[:,newaxis],b[:,newaxis]))
array([[ 4.,  3.],
       [ 2.,  8.]])
>>> np.hstack((a[:,newaxis],b[:,newaxis]))   # the result is the same
array([[ 4.,  3.],
       [ 2.,  8.]])

与 column_stack 相似,row_stack 函数相等于二维数组中的 vstack。一般在高于二维的情况中,hstack 沿第二个维度堆叠、vstack 沿第一个维度堆叠,而 concatenate 更进一步可以在任意给定的维度上堆叠两个数组,当然这要求其它维度的长度都相等。concatenate 在很多深度模型中都有应用,例如权重矩阵的堆叠或 DenseNet 特征图的堆叠。

在复杂情况中,r_ 和 c_ 可以有效地在创建数组时帮助沿着一条轴堆叠数值,它们同样允许使用范围迭代「:」生成数组。

>>> np.r_[1:4,0,4]
array([1, 2, 3, 0, 4])

当用数组为参数时,r_ 和 c_ 在默认行为下与 vstack 和 hstack 相似,但它们如 concatenate 一样允许给定需要堆叠的维度。

拆分数组

使用 hsplit 可以顺着水平轴拆分一个数组,我们指定切分后输出的数组数,或指定在哪一列拆分数组:

>>> a = np.floor(10*np.random.random((2,12)))
>>> a
array([[ 9.,  5.,  6.,  3.,  6.,  8.,  0.,  7.,  9.,  7.,  2.,  7.],
       [ 1.,  4.,  9.,  2.,  2.,  1.,  0.,  6.,  2.,  2.,  4.,  0.]])
>>> np.hsplit(a,3)   # Split a into 3
[array([[ 9.,  5.,  6.,  3.],
       [ 1.,  4.,  9.,  2.]]), array([[ 6.,  8.,  0.,  7.],
       [ 2.,  1.,  0.,  6.]]), array([[ 9.,  7.,  2.,  7.],
       [ 2.,  2.,  4.,  0.]])]
>>> np.hsplit(a,(3,4))   # Split a after the third and the fourth column
[array([[ 9.,  5.,  6.],
       [ 1.,  4.,  9.]]), array([[ 3.],
       [ 2.]]), array([[ 6.,  8.,  0.,  7.,  9.,  7.,  2.,  7.],
       [ 2.,  1.,  0.,  6.,  2.,  2.,  4.,  0.]])]

vsplit 沿着垂直轴拆分,array_split 可指定顺着哪一条轴拆分。

复制与 views

在进行数组运算或操作时,入门者经常很难判断数据到底是复制到了新的数组还是直接在原始数据上修改。这对进一步的运算有很大的影响,因此有时候我们也需要复制内容到新的变量内存中,而不能仅将新变量指向原内存。目前一般有三种复制方法,即不复制内存、浅复制以及深复制。

实际不复制

简单的任务并不会复制数组目标或它们的数据,如下先把变量 a 赋值于 b,然后修改变量 b 就会同时修改变量 a,这种一般的赋值方法会令变量间具有关联性。

>>> a = np.arange(12)
>>> b = a            # no new object is created
>>> b is a           # a and b are two names for the same ndarray object
True
>>> b.shape = 3,4    # changes the shape of a
>>> a.shape
(3, 4)

Pythan 将不定对象作为参照(references)传递,所以调用函数不会产生目标识别符的变化,也不会发生实际的内容复制。

>>> def f(x):
...     print(id(x))
...
>>> id(a)                           # id is a unique identifier of an object
148293216
>>> f(a)
148293216

View 或浅复制

不同数组对象可以共享相同数据,view 方法可以创建一个新数组对象来查看相同数据。如下 c 和 a 的目标识别符并不一致,且改变其中一个变量的 shape 并不会对应改变另一个。但这两个数组是共享所有元素的,所以改变一个数组的某个元素同样会改变另一个数组的对应元素。

>>> c = a.view()
>>> c is a
False
>>> c.base is a                        # c is a view of the data owned by a
True
>>> c.flags.owndata
False
>>>
>>> c.shape = 2,6                      # a s shape doesn t change
>>> a.shape
(3, 4)
>>> c[0,4] = 1234                      # a s data changes
>>> a
array([[   0,    1,    2,    3],
       [1234,    5,    6,    7],
       [   8,    9,   10,   11]])

分割数组输出的是它的一个 view,如下将数组 a 分割为子数组 s,那么 s 就是 a 的一个 view,修改 s 中的元素同样会修改 a 中对应的元素。

>>> s = a[ : , 1:3]     # spaces added for clarity; could also be written "s = a[:,1:3]"
>>> s[:] = 10           # s[:] is a view of s. Note the difference between s=10 and s[:]=10
>>> a
array([[   0,   10,   10,    3],
       [1234,   10,   10,    7],
       [   8,   10,   10,   11]])

深复制

copy 方法可完整地复制数组及数据,这种赋值方法会令两个变量有不一样的数组目标,且数据不共享。

>>> d = a.copy()                          # a new array object with new data is created
>>> d is a
False
>>> d.base is a                           # d doesn t share anything with a
False
>>> d[0,0] = 9999
>>> a
array([[   0,   10,   10,    3],
       [1234,   10,   10,    7],
       [   8,   10,   10,   11]])

深入理解 NumPy

广播机制

广播操作是 NumPy 非常重要的一个特点,它允许 NumPy 扩展矩阵间的运算。例如它会隐式地把一个数组的异常维度调整到与另一个算子相匹配的维度以实现维度兼容。例如将一个维度为 [3,2] 的矩阵与另一个维度为 [3,1] 的矩阵相加是合法的,NumPy 会自动将第二个矩阵扩展到等同的维度。

为了定义两个形状是否是可兼容的,NumPy 从最后开始往前逐个比较它们的维度大小。在这个过程中,如果两者的对应维度相同,或者其一(或者全是)等于 1,则继续进行比较,直到最前面的维度。若不满足这两个条件,程序就会报错。

如下展示了一个广播操作:

>>>a = np.array([1.0,2.0,3.0,4.0, 5.0, 6.0]).reshape(3,2)
>>>b = np.array([3.0])
>>>a * b

array([[  3.,   6.],
       [  9.,  12.],
       [ 15.,  18.]])

高级索引

NumPy 比一般的 Python 序列提供更多的索引方式。除了之前看到的用整数和截取的索引,数组可以由整数数组和布尔数组 indexed。

通过数组索引

如下我们可以根据数组 i 和 j 索引数组 a 中间的元素,其中输出数组保持索引的 shape。

>>> a = np.arange(12)**2                       # the first 12 square numbers
>>> i = np.array( [ 1,1,3,8,5 ] )              # an array of indices
>>> a[i]                                       # the elements of a at the positions i
array([ 1,  1,  9, 64, 25])

>>> j = np.array( [ [ 3, 4], [ 9, 7 ] ] )      # a bidimensional array of indices
>>> a[j]                                       # the same shape as j
array([[ 9, 16],
       [81, 49]])

当使用多维数组作为索引时,每一个维度就会索引一次原数组,并按索引的 shape 排列。下面的代码展示了这种索引方式,palette 可以视为简单的调色板,而数组 image 中的元素则表示索引对应颜色的像素点。

>>> palette = np.array( [ [0,0,0],                # black
...                       [255,0,0],              # red
...                       [0,255,0],              # green
...                       [0,0,255],              # blue
...                       [255,255,255] ] )       # white
>>> image = np.array( [ [ 0, 1, 2, 0 ],           # each value corresponds to a color in the palette
...                     [ 0, 3, 4, 0 ]  ] )
>>> palette[image]                            # the (2,4,3) color image
array([[[  0,   0,   0],
        [255,   0,   0],
        [  0, 255,   0],
        [  0,   0,   0]],
       [[  0,   0,   0],
        [  0,   0, 255],
        [255, 255, 255],
        [  0,   0,   0]]])
       [81, 49]])

我们也可以使用多维索引获取数组中的元素,多维索引的每个维度都必须有相同的形状。如下多维数组 i 和 j 可以分别作为索引 a 中第一个维度和第二个维度的参数,例如 a[i, j] 分别从 i 和 j 中抽取一个元素作为索引 a 中元素的参数。

>>> a = np.arange(12).reshape(3,4)
>>> a
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])
>>> i = np.array( [ [0,1],                        # indices for the first dim of a
...                 [1,2] ] )
>>> j = np.array( [ [2,1],                        # indices for the second dim
...                 [3,3] ] )
>>>
>>> a[i,j]                                     # i and j must have equal shape
array([[ 2,  5],
       [ 7, 11]])
>>>
>>> a[i,2]
array([[ 2,  6],
       [ 6, 10]])
>>>
>>> a[:,j]                                     # i.e., a[ : , j]
array([[[ 2,  1],
        [ 3,  3]],
       [[ 6,  5],
        [ 7,  7]],
       [[10,  9],
        [11, 11]]])

同样,我们把 i 和 j 放在一个序列中,然后用它作为索引:

>>> l = [i,j]
>>> a[l]                                       # equivalent to a[i,j]
array([[ 2,  5],
       [ 7, 11]])

然而,我们不能如上把 i 和 j 放在一个数组中作为索引,因为数组会被理解为索引 a 的第一维度。

>>> s = np.array( [i,j] )
>>> a[s]                                       # not what we want
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
IndexError: index (3) out of range (0<=index<=2) in dimension 0
>>>
>>> a[tuple(s)]                                # same as a[i,j]
array([[ 2,  5],
       [ 7, 11]])

另一个将数组作为索引的常用方法是搜索时间序列的最大值:

>>> time = np.linspace(20, 145, 5)                 # time scale
>>> data = np.sin(np.arange(20)).reshape(5,4)      # 4 time-dependent series
>>> time
array([  20.  ,   51.25,   82.5 ,  113.75,  145.  ])
>>> data
array([[ 0.        ,  0.84147098,  0.90929743,  0.14112001],
       [-0.7568025 , -0.95892427, -0.2794155 ,  0.6569866 ],
       [ 0.98935825,  0.41211849, -0.54402111, -0.99999021],
       [-0.53657292,  0.42016704,  0.99060736,  0.65028784],
       [-0.28790332, -0.96139749, -0.75098725,  0.14987721]])
>>>
>>> ind = data.argmax(axis=0)                  # index of the maxima for each series
>>> ind
array([2, 0, 3, 1])
>>>
>>> time_max = time[ind]                       # times corresponding to the maxima
>>>
>>> data_max = data[ind, range(data.shape[1])] # => data[ind[0],0], data[ind[1],1]...
>>>
>>> time_max
array([  82.5 ,   20.  ,  113.75,   51.25])
>>> data_max
array([ 0.98935825,  0.84147098,  0.99060736,  0.6569866 ])
>>>
>>> np.all(data_max == data.max(axis=0))
True

你也可以用数组索引作为一个分配目标:

>>> a = np.arange(5)
>>> a
array([0, 1, 2, 3, 4])
>>> a[[1,3,4]] = 0
>>> a
array([0, 0, 2, 0, 0])

然而,当索引列表中有重复时,赋值任务会执行多次,并保留最后一次结果。

>>> a = np.arange(5)
>>> a[[0,0,2]]=[1,2,3]
>>> a
array([2, 1, 3, 3, 4])

这是合理的,但注意如果你使用 Python 的 +=创建,可能不会得出预期的结果:

>>> a = np.arange(5)
>>> a[[0,0,2]]+=1
>>> a
array([1, 1, 3, 3, 4])

虽然 0 在索引列表中出现两次,第 0 个元素只会增加一次。这是因为 Python 中「a+=1」等于「a = a + 1」.

用布尔数组做索引

当我们索引数组元素时,我们在提供索引列表。但布尔值索引是不同的,我们需要清楚地选择被索引数组中哪个元素是我们想要的哪个是不想要的。

布尔索引需要用和原数组相同 shape 的布尔值数组,如下只有在大于 4 的情况下才输出 True,而得出来的布尔值数组可作为索引。

>>> a = np.arange(12).reshape(3,4)
>>> b = a > 4
>>> b                                          # b is a boolean with a s shape
array([[False, False, False, False],
       [False,  True,  True,  True],
       [ True,  True,  True,  True]])
>>> a[b]                                       # 1d array with the selected elements
array([ 5,  6,  7,  8,  9, 10, 11])

这个性质在任务中非常有用,例如在 ReLu 激活函数中,只有大于 0 才输出激活值,因此我们就能使用这种方式实现 ReLU 激活函数。

>>> a[b] = 0                                   # All elements of  a  higher than 4 become 0
>>> a
array([[0, 1, 2, 3],
       [4, 0, 0, 0],
       [0, 0, 0, 0]])

第二种使用布尔索引的方法与整数索引更加相似的;在数组的每个维度中,我们使用一维布尔数组选择我们想要的截取部分:

>>> a = np.arange(12).reshape(3,4)
>>> b1 = np.array([False,True,True])             # first dim selection
>>> b2 = np.array([True,False,True,False])       # second dim selection
>>>
>>> a[b1,:]                                   # selecting rows
array([[ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])
>>>
>>> a[b1]                                     # same thing
array([[ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])
>>>
>>> a[:,b2]                                   # selecting columns
array([[ 0,  2],
       [ 4,  6],
       [ 8, 10]])
>>>
>>> a[b1,b2]                                  # a weird thing to do
array([ 4, 10])

注意一维布尔数组的长度必须和想截取轴的长度相同。在上面的例子中,b1 的长度 3、b2 的长度为 4,它们分别对应于 a 的第一个维度与第二个维度。

线性代数

简单的数组运算

如下仅展示了简单的矩阵运算更多详细的方法可在实践中遇到在查找 API。如下展示了矩阵的转置、求逆、单位矩阵、矩阵乘法、矩阵的迹、解线性方程和求特征向量等基本运算:

>>> import numpy as np
>>> a = np.array([[1.0, 2.0], [3.0, 4.0]])
>>> print(a)
[[ 1.  2.]
 [ 3.  4.]]

>>> a.transpose()
array([[ 1.,  3.],
       [ 2.,  4.]])

>>> np.linalg.inv(a)
array([[-2. ,  1. ],
       [ 1.5, -0.5]])

>>> u = np.eye(2) # unit 2x2 matrix; "eye" represents "I"
>>> u
array([[ 1.,  0.],
       [ 0.,  1.]])
>>> j = np.array([[0.0, -1.0], [1.0, 0.0]])

>>> np.dot (j, j) # matrix product
array([[-1.,  0.],
       [ 0., -1.]])

>>> np.trace(u)  # trace
2.0

>>> y = np.array([[5.], [7.]])
>>> np.linalg.solve(a, y)
array([[-3.],
       [ 4.]])

>>> np.linalg.eig(j)
(array([ 0.+1.j,  0.-1.j]), array([[ 0.70710678+0.j        ,  0.70710678-0.j        ],
       [ 0.00000000-0.70710678j,  0.00000000+0.70710678j]]))

Parameters:
    square matrix
Returns
    The eigenvalues, each repeated according to its multiplicity.
    The normalized (unit "length") eigenvectors, such that the
    column ``v[:,i]`` is the eigenvector corresponding to the
    eigenvalue ``w[i]`` .

原文档链接:https://docs.scipy.org/doc/numpy/user/quickstart.html

欢迎扫码关注:

【NumPy基础】100道numpy练习——进阶篇 选自numpy-100,当作熟悉NumPy的练习。NumPy只是一个数值计算的工具包,在实际的算法实现中来熟悉NumPy才是有效的,因此后面不打算继续写了,到此文为止,基本的语 阅读详情

相关推荐

【NumPy基础】100道numpy练习——Apprentice篇

【NumPy基础】100道numpy练习——Apprentice篇 @author:wepon @blog:http://blog.csdn.net/u012162613/article/details/42811297 今天又用半小时扫了一下Apprentice篇里的10道exercise,不知道怎么翻译Apprentice(学徒~~)这个词,就直接以Apprentice篇作为

wepon的专栏 1万+

numpy之多维数组

python的numpy库使用方法指南

TSzero的博客 4855

Numpy基础入门知识点总结

NumPy的基础入门知识

Xx_Studying的博客 2561

Numpy - 多维数据数组

numpy包(模块)几乎总是用于Python中的数值计算。这个软件包为Python提供了高性能的向量、矩阵、张量数据类型。它是在C和Fortran中创建的,因此当计算被矢量化(用矩阵和矢量表示操作)时,性能很高。 1.模块的导入: %matplotlib inline import matplotlib.pyplot as plt from numpy import * 2.数组创建numpy 有几种初始化numpy数组的方法,例如:使用Python的list或tuple。使用旨...

数据媛 2313

numpy数组基础

numpy数组基础什么是数据分析使用python做数据分析的常用库numpy概述numpy历史numpy的核心:多维数组numpy基础ndarray数组内存中的ndarray对象ndarray数组对象的特点ndarray数组对象的创建ndarray对象属性的基本操作ndarray对象属性操作详解测试日期类型数组类型字符码ndarray数组对象的维度操作ndarray数组切片操作ndarray数组的掩码操作多维数组的组合与拆分ndarray类的其他属性 什么是数据分析 数据分析是指用适当的统计分析方法对收集来

iamcht的博客 969

VisionPro脚本实战:用C#替代QuickBuild,让复杂定位项目代码更清爽

本文探讨了如何通过C#脚本重构VisionPro复杂定位项目,替代臃肿的QuickBuild架构。通过实战案例展示了脚本化开发如何减少60%代码量并提升35%运行效率,使项目更易维护。文章详细介绍了环境搭建、多模板定位重构及高级技巧,帮助开发者突破QuickBuild限制,实现更高效的视觉应用开发。

weixin_30417487的博客 505

NumPy教程(一)—— ndarray:多维数组对象

该numpy学习笔记参考了菜鸟教程网、b站up主 孙兴华zz 的《孙兴华中文讲python数据分析三部曲》以及《北理-python数据分析与展示》,课本推荐使用《利用python进行数据分析》

m0_61766362的博客 3132

我的二进制漏洞挖掘学习路线

漏洞挖掘也分好多方向:浏览器漏挖,虚拟化,iot,车联网,应用安全等。而且也要区分一下平台,Linux,Android,Windows,还有一些其他的系统。在基础知识拿捏以后,Windows下如OD,windbg,x64dbg。IDA,GDB要会并联调。然后需要了解一下漏洞的知识,漏洞利用的知识。可以从复现漏洞,CTF来学习,学习完之后就可以通过审计源码或者反汇编二进制程序来挖掘。然后就是练就行了,多关注一下最新安全消息,安全漏洞。

qq_36918532的博客 4945

numpy.array多维数组的切片操作总结

numpy.array多维数组的切片操作总结一 常规介绍1 一维数组切片2 二维数组的切片3 维数超过 3 的多维数组,可通过 '…' 来简化操作4 numpy中对切片元素的操作会影响原数组本身5 array和list的对比6 boolean/mask index二 多维数组array[index,index]和array[index][index]的区别 一 常规介绍 1 一维数组切片 一维数组类似于一条直线(一维),其上的坐标位置用一个数字就可表示,一个数字代表一个位置,对应一个值。 >>&

LIWEI940638093的博客 1万+

移远EC20模块USB模式切换的底层逻辑与驱动适配探秘

本文深入探讨了移远EC20模块USB模式切换的底层逻辑与驱动适配机制,详细解析了AT命令控制下的USB0与WWAN0模式差异。通过分析USB协议栈、内核驱动框架及网络子系统的交互,提供了模式切换的完整操作流程和常见问题解决方案,助力开发者优化4G通信模块在嵌入式设备中的稳定连接。

cc789的博客 846

勿谢!你需要的NumPy基础都在这

<p style="text-align: center"><strong><span><strong><span><strong><span>(给</span><sp...

AI科技前线 256

WSL子系统启动报错 Wsl/Service/CreateInstance/CreateVm/HCS_E_SERVICE_NOT_AVAILABLE

WSL子系统启动报错 :Wsl/Service/CreateInstance/CreateVm/HCS_E_SERVICE_NOT_AVAILABLE 于是分析原因,最终发现是Windows功能中的虚拟机平台功能不知道什么时候被取消了。

Linux I Tell U 6万+

numpy的学习,全是源码,勿喷

numpy的学习,全是源码,勿喷

weixin_73897131的博客 1685

鸿蒙开发实战:如何用DevEco Studio 4.0一键迁移安卓Java项目(附避坑清单)

本文详细介绍了如何使用DevEco Studio 4.0将安卓Java项目一键迁移到鸿蒙系统,包括环境准备、代码适配、UI转换及常见问题解决方案。通过实战指南和避坑清单,帮助开发者高效完成项目移植,充分利用鸿蒙生态优势。

weixin_29214199的博客 385

别再死记硬背公式了!用Unity ShaderGraph从零实现一个皮肤SSS效果(附完整节点图)

本文详细介绍了如何利用Unity ShaderGraph零代码实现皮肤次表面散射(SSS)效果,包括背光透射(BTDF)和表面散射(BSSRDF)的核心原理与节点构建方法。通过可视化工具,技术美术师和Shader初学者可以直观理解光线在皮肤组织中的散射行为,快速创建真实的皮肤材质效果,适用于游戏角色渲染等场景。

weixin_30838873的博客 595

2 Numpy基础

NumPy(Numerical Python)是高性能科学计算和数据分析的基础包。使用飞桨构建神经网络模型时,通常会使用NumPy实现数据预处理和一些模型指标的计算,飞桨中的Tensor数据可以很方便的和ndarray数组进行相互转换。本质上,NumPy期望用户在执行“向量”操作时,像使用“标量”一样轻松。

immortal527的博客 1578

基于Simulink的MIMO-OFDM系统仿真建模示例

MIMO概述MIMO技术利用多个天线进行信号发送和接收,通过空间复用来提高数据传输速率,或通过空间分集来增强信号的可靠性。OFDM概述OFDM是一种将高速率数据流分成多个低速数据流并行传输的技术,每个子载波都采用调制方式(如BPSK、QPSK),并通过IFFT/FFT变换实现串并转换。MIMO-OFDM优势结合了MIMO的空间分集增益与OFDM对抗多径效应的能力,适用于宽带无线通信系统。提供了更高的数据速率和更好的频谱效率,是现代无线通信标准(如4G LTE、5G NR等)的核心技术之一。

xiaoheshang_123的博客 328

Python NumPy 基础指南

numpy基础

ai学习博客 1165

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

NumPy基础及取值操作

第一关

orchid_sea的博客 5618

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

Numpy基础

打开终端然后输入再输入激活并进入虚拟环境,然后就可以在这里下第三方库了。再输入下载numpy。

Clocky7 737

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

Numpy基础知识

Numpy学习笔记 Numpy介绍 NumPy,是Numerical Python的简称,它是目前Python数值计算中最为重要的基础包。大多数计算包都提供了基于NumPy的科学函数功能,将NumPy的数组对象作为数据交换的通用语。 以下内容将会出现在NumPy中: 基本内容 ndarray,一种高效多维数组,提供了基于数组的便捷算术操作以及灵活的广播功能 对所有数据进行快速的矩阵计算,而无须编写循环程序 对硬盘中数组数据进行读写的工具,并对内存映射文件进行操作 线性代数、随机数

FORxinchendahai的博客 1392

python-numpy基础知识

numpy基础知识学习,自学笔记。

qq_47541315的博客 5122

[educoder]1、NumPy基础及取值操作

文章目录第1关:ndarray对象2 第1关:ndarray对象 测试说明 平台会对你编写的代码进行测试,对比你输出的数值与实际正确的数值,只有所有数据全部计算正确才能进入下一关。 shape:为需要实例化出来的ndarray对象的shape; data:表示需要实例化出来的ndarray对象中元素的值。 例如:{‘shape’:[1, 2], ‘data’:[[1, 2]]}表示ndarray对象的形状为1行2列,第1行第1列的值为1,第1行第2列的值为2。 测试输入: {‘shape’:[1, 2],

m0_46233342的博客 3639

【NumPy基础】- Numpy数组和矢量计算

🤖🤖🤖🤖 欢迎浏览本博客 🤖🤖🤖🤖😆😆😆😆😆😆😆一名刚刚入行的小白👻👻👻👻从事方向:计算机视觉🔔🔔我菜就爱学,分享有误,欢迎大佬指出🔔🔔🌏🌏🌏本篇介绍:关于Numpy的相关基础知识学习。

qq_44859533的博客 2261
上一篇: PyTorch Cookbook(常用代码段集锦)
下一篇: 数字图像处理基本知识
机器学习算法那些事
博客等级 码龄9年 1633粉丝 62原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值