Python-Numpy

发布于 2022-08-23  538 次阅读


Please refresh the page if equations are not rendered correctly.
---------------------------------------------------------------

NumPy的N维数组是同质的:一个数组只能存储单个类型的数据。比如说,单个数组可以存储8比特整数或32比特浮点数,但是不能这两种数都有。这和Python的完全不限制成员种类数的列表和元组极其不同;一个列表可以同时存储字符串,整数,和其它对象。这个对数组成员的限制带来着大量的好处;因为NumPy提前“知道”数组成员的种类是同质的,它可以将对数组成员进行的数学操作代理给优化过,提前编译的C代码。这个过程叫做矢量化(vectorization)。这么做的结果是和纯Python代码运算相比巨大的速度提升。Python需要在迭代数组成员时一个个检查成员的数据类型,因为Python一般操作的列表是不限制数据类型的。

%time a=np.sum(np.arange(10000))
Wall time: 1.01 ms

对比在Python中显性迭代这个数组并求和花的时间。Python无法利用函数成员都是同一数据类型这一信息——它必须在每一轮迭代都检查数据的类型,就像在迭代列表一样。这回大幅度地降低运算速度。

import numpy as np
def Sum_array(n):
    total=0
    for i in range(n):
        total += i
    return total
%time a=Sum_array(10000)
Wall time: 973 µs

1s = 1000ms = 10^6µs= 10^9 ns

我们在计算速度重要时应避免对Python长序列数据(不管是列表还是NumPy数组)使用显性for循环一事。

[NumPy的数学函数]

Numpy User Guide
1. 一元(unary)函数:f(x)

将一元NumPy函数 f(x) 应用于N维数组将会将 f(x) 作用于数组的每一个成员。
2. 二元(binary)函数:f(x,y)

符号是%的 “模”函数(modulo,简写为mod)的定义使其返回除法的余数_:5% 3 = 2
max(x,y)--np.maximum
min(x,y)--np.minimum

在用二元函数操作NumPy函数时,我们需要考虑两种情况:

  1. 当函数两个操作数都是(相同形状的)数组时。
  2. 当函数的一个操作数是标量(也就是单个数字),另外一个操作数是数组时。

和对数组运用一元函数时的行为相似,二元数组会对两个形状相同的数组的对应成员进行操作。

>>> x = np.array([0., 1., 2.])
>>> y = np.array([-1., 1., -2.])

# 对 `x` 和 `y` 对应的成员对进行操作
>>> x + y  # 这等值于:`np.add(x, y)`
array([-1.,  2.,  0.])

这个过程可以拓展到任何维度和形状的数组,只需要两个操作数的形状一样。

# 二元函数操作两个2维数组的例子
>>> x = np.array([[10,  2],
...               [ 3,  5]])

>>> y = np.array([[ 1,   0],
...               [ -4,  -1]])

>>> np.add(x, y)  # 等值于 `x + y`
array([[11,  2],
       [-1,  4]])

# 将 `x` 的列0和 `y` 的行1相加
>>> x[:, 0] + y[1, :]
array([6, 2])

将一个二元NumPy函数 f(x,y) 运用于两个形状相同的数组上会将 f(x,y)应用于两个数组的所有对应成员对,并将作为一个形状相同的数组返回。

NumPy二元函数操作一个标量(也就是单个数字)和一个数组时的行为。函数将对数组的每个成员进行操作;每次运算将会提供数组的某一个成员和不变的向量。这和传统的线性代数的行为完全一样。

>>> 3 * np.array([0., 1., 2.])  # 等值于:`np.multiply(3, x)`
array([ 0.,  3.,  6.])

>>> np.array([1., 2., 3.]) ** 2  # 等值于:`np.power(x, 2)`
array([ 1.,  4.,  9.])

这个过程可以扩展到任何维度和形状的数组。

# 二元函数操作一个标量和一个数组的范例
>>> x = np.array([[10,  2],
...               [ 3,  5]])

>>> np.maximum(4, x)
array([[10,  4],
       [ 4,  5]])

# 一个形状为 (2, 2, 8) 的3维数组
>>> y = np.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]]])

>>> y[0, :, ::2] * -1 #[::2]以2为步长取值
array([[  0,  -2,  -4,  -6],
       [ -8, -10, -12, -14]])

经验

将二维NumPy函数 f(x,y)应用到一个数组和一个标量将会导致函数将标量“分配”到数组的每一个成员并以此进行二元运算。

3. 序列函数
axis=0,每一列 axis=1/-1 每一行

# 创建一个形状为 (3,2) 的数组
>>> x = np.array([[0, 1],
...               [2, 3],
...               [4, 5]])

# 在轴1内对轴0求值
# 也就是说,在每一列中求其中行的和
>>> np.sum(x, axis=0)  # 等值于:x.sum(axis=0)
array([6, 9])

# 在轴0内对轴1求值
# 也就是说,在每一行中求其中列的和
>>> np.sum(x, axis=1)  # 等值于:x.sum(axis=1)
array([1, 5, 9])

# 你也可以使用负的轴索引
>>> np.sum(x, axis=-1)  # 等值于:np.sum(x, axis=1)
array([1, 5, 9])

# 在轴0和轴1内求值
# 也就是说,假装数组是1维序列来求值(默认行为)
>>> np.sum(x, axis=(0, 1))  # 等值于:x.sum(axis=(0, 1))
15

如上,axis 参数提供了在为序列函数创建输入序列时遍历哪个或哪几个轴。每个成员为没有提供的轴的组合都会创建一个序列。比如说,np.sum(x, axis=0) 等于在说:“对 x 的每一列求其行的和”。因此,以下序列将会被求和:

x[:, 0] -> array([0, 2, 4])  # 遍历列0中的所有行
x[:, 1] -> array([1, 3, 5])  # 遍历列1中的所有行

如此,x 的每一列都会被求和,最终返回一个形状为 (2,) 的数组,其成员为这两个和。相似的, np.sum(x, axis=1) 返回一个形状为 (3,) 的数组,其成员为 x 三行求和的结果。

你也可以通过提供整数“元组”(如果是列表而不是元组的话不会被接受)提供多个轴。np.sum(x, axis=(0,1)) 会告诉NumPy去遍历 x 的全部两个轴,并将 x 全部的成员当作一个序列来求和,返回一个数字。请回忆,这和不提供 axis 关键词参数时的默认行为一样。所有序列性的函数都可以接受关键词参数 axis。axis 可以接受单个整数或一元组的整数来描述在指定数组数据序列时要遍历哪些轴。每个合法的未被遍历的轴的索引的组合都会生成一个序列。默认情况下,所有输入元组的轴都会被包含,因此数组的所有成员都会被当作一个序列对待。

>>> x = np.arange(24).reshape(4,2,3)
>>> 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]]])

我们可以认为这个数组有着4页2x3的纸张。遍历 x 的轴0相当于根据每个合理的轴1和轴2索引组合在纸张之间跳跃。如此,np.mean(x, axis=0) 相当于在说:“为每一个行和列的组合求 x 在不同页的平均值”。所以这将在 x 中设定6个不同的序列来让这个序列函数进行操作:

x[:, 0, 0] -> array([ 0,  6, 12, 18])  {平均值 =  9}
x[:, 0, 1] -> array([ 1,  7, 13, 19])  {平均值 = 10}
x[:, 0, 2] -> array([ 2,  8, 14, 20])  {平均值 = 11}
x[:, 1, 0] -> array([ 3,  9, 15, 21])  {平均值 = 12}
x[:, 1, 1] -> array([ 4, 10, 16, 22])  {平均值 = 13}
x[:, 1, 2] -> array([ 5, 11, 17, 23])  {平均值 = 14}

np.mean(x, axis=0)
array([[  9.,  10.,  11.],
       [ 12.,  13.,  14.]])

NumPy使用行优先顺序(也叫做C顺序)来遍历数组。假设我们提供了两个轴,轴0和轴2,遍历这两个轴相当于为每一个轴1的索引遍历 x 的页和列。因此,这将创建两个序列:

x[:, 0, :] -> array([ 0,  1,  2,  6,  7,  8, 12, 13, 14, 18, 19, 20])  {平均值 = 10}
x[:, 1, :] -> array([ 3,  4,  5,  9, 10, 11, 15, 16, 17, 21, 22, 23])  {平均值 = 13}

>>> np.mean(x, axis=(0, 2))
array([ 10.,  13.])

如果 X 是一个 N(a×b×c)维数组,axis 关键词参数为一个NumPy序列函数提供了 j(满足 j <=N)个轴,那么这个函数将会返回一个 N-j 维数组。返回数组的形状是 X 的形状除去那 j 个轴的结果。例如上面的例子 3维数组(4×2×3)

Everything not saved will be lost.
最后更新于 2022-08-23