NP2 Numpy Basics

· Tech

总览

本文围绕 Numpy 的一些基本概念和常见接口展开讨论。前文提到 Numpy 主要以向量、矩阵等方式展开计算,而这些表示离不开 ndarray 的定义。为此,我们需要理解 Numpy 数组的基本性质及其用法。主要需要理解 Numpy 数组的创建与属性,尤其需要注意二维数组时的 index、slice 以及 axis 等相关概念。在机器学习的情境下,需要对 Numpy 数组建立基本理解,以进一步了解各步算法进行的计算。

本文首先介绍 Numpy 数组的基本属性,随后围绕其各个接口展开讨论。

1. Numpy 数组与其初始化

大多数 NumPy 数组都有一些限制,比如说:

  • 数组里的所有元素必须是同一种数据类型。
  • 数组一旦创建,总大小就不能再改了。
  • 形状必须是“矩形”的,不能是“锯齿状”的。

一般通过 python 序列来初始化 numpy 数组:

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

同样像 python list 一样,Python的切片表示法(注意前闭后开)也可以用于索引。

注意

一个主要的区别是,对 python 列表进行切片索引会把元素复制到一个新列表里,但对 numpy 数组进行切片则会返回一个视图(view):一个指向原数组中数据的对象。原数组可以通过这个视图来修改。

要实现深拷贝的话,需要使用 b = a.copy()

>>> a[0] = 10
>>> a
array([10,  2,  3,  4,  5,  6])
>>> a[:3]
array([10, 2, 3])
>>> b = a[3:]
>>> b
array([4, 5, 6])
>>> b[0] = 40
>>> a
array([ 10,  2,  3, 40,  5,  6])

二维及更高维的数组可以通过嵌套的 Python 序列来初始化:

>>> a = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])
>>> a
array([[ 1,  2,  3,  4],
       [ 5,  6,  7,  8],
       [ 9, 10, 11, 12]])

``
在 NumPy 里,数组的一个维度有时候也叫作一个轴(axis)。这个叫法可能有助于区分数组本身的维数和数组所表示数据的维数。比如说,数组a可以表示三个点,每个点都位于一个四维空间中,但a只有两个轴。

2. 数组属性

介绍 ndim、shape、size、dtype :

  • ndim : The number of dimensions of an array.
  • shape : a tuple of non-negative integers that specify the number of elements along each dimension.
  • size : The fixed, total number of elements in array.
  • dtype : Arrays are typically “homogeneous”(同类的), meaning that they contain elements of only one “data type”.

示例:

>>> a.ndim
2

>>> a.shape
(3, 4)
>>> len(a.shape) == a.ndim
True

>>> a.size
12
>>> import math
>>> a.size == math.prod(a.shape)
True

>>> a.dtype
dtype('int64')  # "int" for integer, "64" for 64-bit

3. 如何创建一个基本数组

介绍 np.zeros(), np.ones(), np.empty(), np.arange(), np.linspace():

  • np.zeros() 用于创建一个全零的数组:
>>> np.zeros(2)
array([0., 0.])
  • np.ones() 用于创建一个全一的数组:
>>> np.ones(2)
array([1., 1.])
  • np.empty() 用于创建一个数组,但它的初始内容是随机的,取决于内存的状态:
>>> # Create an empty array with 2 elements
>>> np.empty(2) 
array([3.14, 42.  ])  # may vary
  • np.arange() 非常常用,有下面几种用法:
  1. 创建一个包含一系列元素的数组
>>> np.arange(4)
array([0, 1, 2, 3])
  1. 通过指定第一个数字、最后一个数字和步长,来创建一个包含一系列均匀间隔的数组
>>> np.arange(2, 9, 2)
array([2, 4, 6, 8])
  • np.linspace() 用于创建一个数组,其中的值在指定的区间内按线性间隔分布:
>>> np.linspace(0, 10, num=5)
array([ 0. ,  2.5,  5. ,  7.5, 10. ])

注意

虽然默认的数据类型是浮点数(np.float64),但我们一般需要用 dtype 这个关键字来明确指定想要的数据类型。e.g. x = np.ones(2, dtype=np.int64)

4. 排序、拼接与重塑

介绍 np.sort(), np.concatenate(), np.reshape():

  • 用 np.sort() 给数组排序很简单。调用这个函数时,可以指定轴、排序算法和顺序。
>>> arr = np.array([2, 1, 5, 3, 7, 4, 6, 8])
>>> np.sort(arr)
array([1, 2, 3, 4, 5, 6, 7, 8])

其中指定 axis 很重要,可以把 axis 理解为 shape 中对应位置的维度。例如 a.shape=(2,3),那么 axis=0 对应长度为 2 的第 0 维,axis=1 对应长度为 3 的第 1 维。对于 sort,axis=0 表示沿第 0 维排序,因此表现为每列内部排序(或者说沿行方向排序);axis=1 表示沿第 1 维排序,因此表现为每行内部排序(或者说沿列方向排序)。默认 axis=-1,表示最后一个维度。

  • np.concatenate() 用于拼接数组,例如:
>>> x = np.array([[1, 2],
                  [3, 4]]) # (2, 2)

>>> y = np.array([[5, 6]]) # (1, 2)
>>> np.concatenate((x, y), axis=0)
array([[1, 2],
       [3, 4],
       [5, 6]]) # (3, 2)
       
>>> np.concatenate((x, y), axis=1)
error # 第 0 维不一致,无法凭借

注意只有 axis=k 那一维可以不同,其他维度必须完全相同。

  • 使用 arr.reshape() 会返回一个改变形状后的数组,但不会改动原来的数据。只要记住,当你用 reshape 方法时,你想生成的新数组,元素总数必须和原数组一样。
>>> a = np.arange(6)
>>> print(a)
[0 1 2 3 4 5]

>>> b = a.reshape(3, 2)
>>> print(b)
[[0 1]
 [2 3]
 [4 5]]
 
>>> np.reshape(a, shape=(1, 6), order='C')
array([[0, 1, 2, 3, 4, 5]])

上式中:

  • a is the array to be reshaped.
  • shape is the new shape you want. You can specify an integer or a tuple of integers. If you specify an integer, the result will be an array of that length. The shape should be compatible with the original shape.
  • order: C means to read/write the elements using C-like index order, F means to read/write the elements using Fortran-like index order, A means to read/write the elements in Fortran-like index order if a is Fortran contiguous in memory, C-like order otherwise. (This is an optional parameter and doesn’t need to be specified.)
    • C 风格表示最后一个索引变化的最快
    • Fortran 风格表示第一个索引变化的最快

5. 索引、子集和切片

可以像切Python列表那样,对NumPy数组进行索引和切片:

>>> data = np.array([1, 2, 3])

>>> data[1]
2
>>> data[0:2]
array([1, 2])
>>> data[1:]
array([2, 3])
>>> data[-2:]
array([2, 3])

如果想从数组里挑出符合某些条件的值,用 NumPy 可以直接做到。

我们给出下列数组:

>>> a = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])
  1. 打印出数组中所有小于5的值:
>>> print(a[a < 5])
[1 2 3 4]
  1. 选择大于或等于5的数字,然后用这个条件来索引一个数组:
>>> five_up = (a >= 5)
>>> print(a[five_up])
[ 5  6  7  8  9 10 11 12]
  1. 选择能被2整除的元素:
>>> divisible_by_2 = a[a%2==0]
>>> print(divisible_by_2)
[ 2  4  6  8 10 12]
  1. 使用 & 和 | 运算符选择满足两个条件的元素:
>>> c = a[(a > 2) & (a < 11)]
>>> print(c)
[ 3  4  5  6  7  8  9 10]

6. 基本的数组运算

包含 addition, subtraction, multiplication, division :

>>> data = np.array([1, 2])
>>> ones = np.ones(2, dtype=np.int_)

>>> data + ones
array([2, 3])

>>> data - ones
array([0, 1])

>>> data * data
array([1, 4])

>>> data / data
array([1., 1.])

要计算数组里所有元素的总和,可以用 sum()。这个函数对一维数组、二维数组,甚至更高维度的数组都管用。

>>> b = np.array([[1, 1], [2, 2]])
>>> b.sum(axis=0)
array([3, 3])

>>> b.sum(axis=1)
array([2, 4])

7. 矩阵的创建

可以把Python的列表的列表传进去,创建一个矩阵,用NumPy来表示它们:

>>> data = np.array([[1, 2], [3, 4], [5, 6]])
>>> data
array([[1, 2],
       [3, 4],
       [5, 6]])

使用索引与切片的情形:

>>> data[0, 1]
2

>>> data[1:3]
array([[3, 4],
       [5, 6]])

>>> data[0:2, 0]
array([1, 3])

理解为 data[a:b, c:d]:

  • 逗号前切片 axis = 0(沿行方向)
  • 逗号后切片 axis = 1(沿列方向)

使用 min|max|sum:

>>> data.max()
6
>>> data.min()
1
>>> data.sum()
21

用 axis 参数按行或按列来汇总:

>>> data = np.array([[1, 2], [5, 3], [4, 6]])
>>> data
array([[1, 2],
       [5, 3],
       [4, 6]])
>>> data.max(axis=0)
array([5, 6]) # 沿行方向汇总两列
>>> data.max(axis=1)
array([2, 5, 6]) # 沿列方向汇总三行

一旦创建了矩阵,如果两个矩阵大小相同,就可以用算术运算符把它们相加和相乘:

>>> data = np.array([[1, 2], [3, 4]])
>>> ones = np.array([[1, 1], [1, 1]])
>>> data + ones
array([[2, 3],
       [4, 5]])

可以对不同大小的矩阵做这些算术运算,但前提是其中一个矩阵只有一列或一行。在这种情况下,NumPy 会使用它的广播规则(broadcasting) 来进行运算。

>>> data = np.array([[1, 2], [3, 4], [5, 6]])
>>> ones_row = np.array([[1, 1]])
>>> data + ones_row
array([[2, 3],
       [4, 5],
       [6, 7]])

可以用 ones()、zeros() 和 random() 来创建一个二维数组,只要给它们传一个描述矩阵维度的元组就行:

>>> np.ones((3, 2))
array([[1., 1.],
       [1., 1.],
       [1., 1.]])
>>> np.zeros((3, 2))
array([[0., 0.],
       [0., 0.],
       [0., 0.]])
>>> rng.random((3, 2)) 
array([[0.01652764, 0.81327024],
       [0.91275558, 0.60663578],
       [0.72949656, 0.54362499]])  # may vary

参考

  1. Numpy 官网教程

    https://numpy.org/doc/stable/user/absolute_beginners.html

cicada@blog:~