面向机器学习的NumPy知识点汇总

NumPy 数组的维数可以对应为张量的秩(rank),即轴的数量,一维数组的秩为 1,二维数组的秩为 2,以此类推。多维数组中每一个线性的数组对应一个轴(axis)。

   a = numpy.array([[[1,2], [2,3]], [[2,3], [3,4]]])

  • ndim: 多维数据的维度(本质上是上述的 rank),a.ndim=3
  • shape: 每个维度上的形状大小(本质上是每个轴上的维数), a.shape=(2, 2, 2)
  • size: 整个数组中元素的总个数,a.size=8

生成

机器学习的样本通常会转换为 NumPy 的数组进行表征与运算,最直接的做法就是先将数据读取到一个 Python 的列表、元组等形式的数组中,再转换为 NumPy 的数组 ndarray:

1
2
3
4
5
import numpy as np

x = [1, 2, 3]
a = np.array(x) # [1 2 3]
b = np.asarray(x, dtype=np.float) # [1. 2. 3.]

可以看到,NumPy 数组是一个同质的数据结构,即数据类型 dytype 必须是一致的且事先可知的。这不同于 Python 内置的列表,也正是这个原因使得 ndarray 的运算速度更快。NumPy 还提供了许多创建特殊值的数组的方法:

生成全0/1数组

1
2
3
4
5
numpy.zeros(shape, dtype=float, order='C')
numpy.ones(shape, dtyp=float, order = 'C')

numpy.zeros_like(a, dtype=None, order='K', subok=True)
numpy.ones_like(a, dtype=None, order='K', subok=True)

其中,参数 shape 是数组形状的元组表示形式;而 *_like 函数可以生成与指定数组形状相同的全0/1的数组;参数 dtype 用来指定数据的类型。其余参数一般使用默认值即可,参数 order 表示数组在内存中的存放布局,’C’表示行优先存储,’F’表示列优先存储;subok 指示是否使用指定的 a 数组内部数据类型。

1
2
3
4
5
6
7
8
9
10
11
# 示例代码
x = np.zeros((5,))
y = np.ones((2,2), dtype=np.int)
z = np.zeros_like(y)
'''
[0. 0. 0. 0. 0.]
[[1 1]
[1 1]]
[[0 0]
[0 0]]
'''

生成数列数组

  • 指定范围的数列
    numpy.arange([start, ]stop, [step, ]dtype=None)
  • 等差数列
    numpy.linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None)
  • 等比数列
    numpy.logspace(start, stop, num=50, endpoint=True, base=10.0, dtype=None)

生成指定范围的数列是相对常用的方法,start 指明了起始值;stop 指明了终止值(不包含);step 指明了数列生成时的步子大小;retstep 指明生成的数组中是否显示间距,默认不显示。等差和等比数列生成时,num 指明了要生成的样本数量;endpoint 指明是否包含 stop 的值,值得注意的是该参数默认是包含 stop 的值的。等比序列的 base 参数对应对数 log 的底数。

1
2
3
4
5
6
7
8
9
10
# 示例代码
x = np.arange(10, 20, step=2)
y = np.linspace(1, 10, num=5)
z = np.logspace(1.0, 2.0, num=4)

'''
[10 12 14 16 18]
[ 1. 3.25 5.5 7.75 10. ]
[ 10. 21.5443469 46.41588834 100. ]
'''

生成随机数组

  • 随机生成给定维度 d0 到 dn 的 [0,1) 取值的数组
    numpy.random.rand(d0, d1, ...., dn)
  • 随机生成给定维度 d0 到 dn 的具有标准正态分布取值的数组
    numpy.random.randn(d0, d1, ..., dn)
  • 随机生成指定均值、标准差的正态分布取值的数组
    numpy.random.normal(loc=0.0, scale=1.0, size=None)
  • 在指定范围 [low,high) 内随机生成整数数组(不指定 high 则范围为[0, low))
    numpy.random.randint(low, high=None, size=None, dtype='I')
  • 从给定的数组中随机选择生成新数组
    numpy.random.choice(a, size=None, replace=True, p=None)

随机数组是机器学习中最常用的,许多模型的参数都是采用随机值进行初始化的。

1
2
3
4
5
6
7
# 示例代码
a = np.random.rand(2, 3)
b = np.random.randn(2, 3)
c = np.random.randint(1, size=5) # 不指定high,相当于[0, 1)
d = np.random.normal(loc=0, scale=0.1, size=(2,3))
e = np.random.randint(-5, 5, size=(2, 2))
f = np.random.choice(5, 3, replace=False) # a=5,会转换为np.arange(5)生成的数组

需要注意的是 rand/randn 这两个最常使用的方法中只需要传入数组的维度,它与传统的使用元组表示数组形状不同,这里的维度是作为逐个参数传入的。

生成坐标点

numpy.meshgrid(*xi, indexing='xy', sparse=False, copy=True)

为该函数传入两个数组,并指定组合模式,就可以生成一系列的坐标点。参数 xi 是一维数组(比如 x 轴上的若干点,与对应的 y 轴上的若干点);indexing 参数指明组合方法,’xy’ 表示笛卡尔模式,’ij’ 表示矩阵模式,默认为 ‘xy’。

1
2
3
4
5
6
7
8
9
10
11
# 示例代码
x = np.array([1, 2, 3])
y = np.array([4, 5, 6, 7])

xv,yv = np.meshgrid(x, y, indexing='xy')
for i in zip(xv.flat, yv.flat):
print(i, end=',')

'''
(1, 4),(2, 4),(3, 4),(1, 5),(2, 5),(3, 5),(1, 6),(2, 6),(3, 6),(1, 7),(2, 7),(3, 7),
'''

索引

转换成 ndarray 数组表征的机器学习样本,可以使用传统的下标索引来访问任一元素,Python 的切片语法 [start\:stop\:step] 同样适用,除了冒号标识以外,还可以通过使用逗号分隔具体各轴上的下标与切片操作,使用省略号表示全选某个轴上的数据:

1
2
3
4
5
6
7
8
# 示例代码
a = np.arange(16).reshape(4,4)
print(a[0]) # [0 1 2 3]
print(a[0][1]) # 1
print(a[0, 1]) # 1,等同于两个中括号的索引,逗号表明了行列关系
print(a[..., 0]) # 省略号表示全选,输出第一列 [ 0 4 8 12]
print(a[0, :2]) # 输出第一行的前两列 [0 1]
print(a[3:]) # 输出第四行 [[12 13 14 15]]

除此之外,ndarray 还提供了两种更高级的索引模式:整数数组索引布尔索引

1
2
3
4
5
6
7
8
9
# 示例代码
x = np.array([[1,2], [3,4], [5,6]])
print(x[[0,1,2], [0,1,0]]) # x在(0,0),(1,1)和(2,0)位置处的元素
print(x[x>3]) # x>3 返回布尔数组,以此作为下标返回对应位置为True的元素

'''
[1 4 5]
[4 5 6]
'''

运算

一维数组的向量与高维数组的矩阵或张量进行运算是机器学习的核心操作,而 NumPy 提供的这些操作通过提前编译好的底层 C 代码实现,相比于 Python 内置数据结构的运算速度有了巨大提升。这种使用事先编译的低级语言(如C)来对数据序列进行数学操作的过程叫做矢量化。所以,请务必铭记凡是遇到需要对 ndarray 进行遍历运算时,优先考虑是否有相应的矢量化函数调用,而避免盲目地通过 for 循环来操作。

类别 功能 函数 类别 功能 函数
一元 取绝对值 np.absolute(x) 一元 取平方根 np.sqrt(x)
一元 取正弦 np.sin(x) 一元 取余弦 np.cos(x)
一元 取正切 np.tan(x) 一元 取自然对数 np.log(x)
一元 取10为底对数 np.log10(x) 一元 取e的指数 np.exp(x)
二元 乘法 np.multiply(x, y) 或 x*y 二元 除法 np.divide(x, y) 或 x/y
二元 加法 np.add(x, y) 或 x+y 二元 减法 np.subtract(x, y) 或 x-y
二元 求幂 np.power(x, n) 或 x**n 二元 求模 np.mod(x, y) 或 x%y
二元 比大 np.maximum(x, y) 二元 比小 np.minimum(x, y)
归约 求平均 np.mean(x) 归约 求中值 np.median(x)
归约 求方差 np.var(x) 归约 求标准差 np.std(x)
归约 求最大值 np.max(x) 归约 求最小值 np.min(x)
归约 求最大值的索引 np.argmax(x) 归约 求最小值的索引 np.argmin(x)
归约 求和 np.sum(x)

上面我们总结了 NumPy 常用的矢量化函数,对于一元函数 f(x),就是将该函数作用到数组的每一个成员上。对于二元函数,则是将 g(x,y) 作用于两个数组对应位置上的元素。这里有一个很现实的问题需要解决,即如果两个数组的形状不一样亦或是有一个标量参与运算怎么办?这就要得益与 NumPy 提供的广播机制。无论是一元还是二元操作,返回值与输入值具有相同的形状,而归约操作则是对序列进行相应的运算后返回一个标量。

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
# 示例代码
x = np.array([[-1,2], [-3,4]])
y = np.array([1, 2])

print(np.exp(y)) # [2.71828183 7.3890561 ]

print(np.square(x))
'''
[[ 1 4]
[ 9 16]]
'''

print(np.divide(x, y))
'''
[[-1. 1.]
[-3. 2.]]
'''

print(3 * x)
'''
[[-3 6]
[-9 12]]
'''

print(np.maximum(x, y))
'''
[[1 2]
[1 4]]
'''

能够适用广播机制的两个形状不同的数组,需要满足以下的两条规则:

  • 两个数组各维大小从后往前比对一致

如上面的示例,x.shape 为 (2, 2), y.shape 为 (2) 从后往前比对一致则 y 会被广播后与 x 进行二元运算。再比如:

1
2
3
4
5
6
7
A = np.zeros((2,5,3,4))
B = np.zeros((3,4))
print((A+B).shape) # 输出 (2, 5, 3, 4)

A = np.zeros((2,5,3,4))
B = np.zeros((3,3))
print((A+B).shape) # ValueError: operands could not be broadcast together with shapes (2,5,3,4) (3,3)

  • 两个数组存在一些维度大小不相等时,有一个数组的该不相等维度大小为1

如果我们把维度大小值 1 理解为可以充当任何数值的“万能符”,那么该条规则也就可以统一到上一条规则中了:

1
2
3
4
5
6
7
8
9
10
11
A = np.zeros((2,5,3,4))
B = np.zeros((3,1))
print((A+B).shape) # 输出:(2, 5, 3, 4)

A = np.zeros((2,5,3,4))
B = np.zeros((2,1,1,4))
print((A+B).shape) # 输出:(2, 5, 3, 4)

A = np.zeros((2,5,3,4))
B = np.zeros((2,4,1,4))
print((A+B).shape) # ValueError: operands could not be broadcast together with shapes (2,5,3,4) (2,4,1,4)

对于归约运算,我们需要注意 axis参数 的使用,它是用来指定从哪些维度生成矢量化操作的序列。axis 可以接受单个整数或一元组的整数来描述在指定数组数据序列时要遍历哪些轴。每个合法的未被遍历的轴的索引的组合都会生成一个序列。默认情况下,所有输入元组的轴都会被包含,因此数组的所有成员都会被当作一个序列对待。

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
# 示例代码
z = np.arange(24).reshape(4, 2, 3)
'''
[[[ 0 1 2]
[ 3 4 5]]

[[ 6 7 8]
[ 9 10 11]]

[[12 13 14]
[15 16 17]]

[[18 19 20]
[21 22 23]]]
'''

print(np.sum(z, axis=0))
'''
[[36 40 44]
[48 52 56]]

# 计算过程: 指定了遍历0轴后,1轴和2轴的索引的合法组合
x[:, 0, 0] -> array([ 0, 6, 12, 18]) {总和 = 36}
x[:, 0, 1] -> array([ 1, 7, 13, 19]) {总和 = 40}
x[:, 0, 2] -> array([ 2, 8, 14, 20]) {总和 = 44}
x[:, 1, 0] -> array([ 3, 9, 15, 21]) {总和 = 48}
x[:, 1, 1] -> array([ 4, 10, 16, 22]) {总和 = 52}
x[:, 1, 2] -> array([ 5, 11, 17, 23]) {总和 = 56}
'''

print(np.sum(z, axis=(0, 2)))
'''
[120 156]

# 计算过程: 指定了遍历0轴和2轴,1轴的索引的合法值
x[:, 0, :] -> array([ 0, 1, 2, 6, 7, 8, 12, 13, 14, 18, 19, 20]) {总和 = 120}
x[:, 1, :] -> array([ 3, 4, 5, 9, 10, 11, 15, 16, 17, 21, 22, 23]) {总和 = 156}
'''

上面的示例代码中解释了如何生成矢量化操作的序列,对于归约后输出结果的形状我们可以总结为:如果 X 是一个 N 维数组,axis 关键词参数为一个NumPy序列函数提供了 j(满足 j≤N)个轴,那么这个函数将会返回一个 N−j 维数组。返回数组的形状是 X 的形状除去那 j 个轴的结果。

逻辑运算

NumPy 提供了的一系列逻辑运算来操作数组,很多这些函数使用和 NumPy 的数学函数一样的方式将逻辑操作衍射到每个数组操作,这些函数返回一个布尔对象或一个布尔类的数组(在上文的布尔索引中已用到)。

1
2
3
4
5
6
7
8
9
10
11
# 示例代码
print(y > 1) # 等效于: np.greater(y, 1)
'''
[False True]
'''

w = np.array([0, 1, 2, 3])
# any() ‘或’ 操作,任意一个元素为True,输出为True
print(np.any(w)) # True
# all() ‘与’ 操作,所有元素为True,输出为True
print(np.all(w)) # False

遍历

尽管我们反对自己遍历 NumPy 数组手动实现那些已有的矢量化运算,但是遍历访问机器学习的样本数据却是常见的操作,可以直接使用 Python 的基本 for 循环语句来实现:

1
2
3
4
5
6
7
# 示例代码
arr = np.array([[1,2,3], [4,5,6]])

for row in arr:
for elem in row:
print(elem, end=', ')
# 1, 2, 3, 4, 5, 6,

很显然,对于二维的数组按行先遍历,再对一行里的各元素逐个遍历是符合我们习惯上对二维数组的认知的,这是因为一般情况下二维数组在内存中是按照行优先的方式进行存储的。NumPy 还为我们提供了一个 ndarray 专门的迭代器:

numpy.nditer(op, flags=None, op_flags=None, order='K')

参数 op 为一个 ndarray;flags 可取 [‘f_index/c_index/multi_index’] 表示返回的是列下标、行下标或组合下标;op_flags 可取 [‘readonly/readwrite/writeonly’] 表示迭代的遍历同时是否可以修改元素;order 可取 ‘C’ 或 ‘F’ 表示遍历的顺序是行优先还是列优先,默认的 ‘K’ 表示按存储顺序即行优先遍历。由于有了 order 参数,即便存储方式不是行优先的 ndarray,我们也可以按照习惯上的行优先模式去遍历它。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 示例代码
a = np.arange(0, 12).reshape(3, 4)
it = np.nditer(a, flags=['multi_index'], op_flags=['readwrite'])
while not it.finished:
idx = it.multi_index
print(idx)
it[0] = 2 * it[0]
it.iternext()
# 也可以使用for循环: for x in np.nditer
'''
原始数组是:
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]

multi_index迭代的下标:
(0, 0),(0, 1),(0, 2),(0, 3),(1, 0),(1, 1),(1, 2),(1, 3),(2, 0),(2, 1),(2, 2),(2, 3),

修改后的数组是:
[[ 0 2 4 6]
[ 8 10 12 14]
[16 18 20 22]]
'''

有时,我们在迭代时需要获取元素的索引,那么可以使用 ndenumerate()方法:

1
2
3
4
# 示例代码
for idx, x in np.ndenumerate(arr):
print(idx, x, end=', ')
# (0, 0) 1, (0, 1) 2, (0, 2) 3, (1, 0) 4, (1, 1) 5, (1, 2) 6,

修改

在机器学习算法中有的时候需要通过改变数组的形状,才能使它们可以进行线性代数等计算操作。关于数组修改相关的方法具有 NumPy 类方法和 ndarray 数组的对象方法两种形式:

修改形状

  • 改变数组的形状,newshape建议写成整数元组形式且兼容原有形状大小
    numpy.reshape(arr, newshape, order='C')
    ndarray.reshape(newshape, order='C')
  • 返回一个展平了的一维数组副本
    ndarray.flatten(order='C')
  • 返回一个展平了的一维数组视图(修改会影响原数组)
    numpy.ravel(a, order='C')
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    # 示例代码
    a = np.arange(6).reshape((2, 3))
    b = np.reshape(a, (3, 2))
    c = a.flatten()
    d = b.ravel()

    '''
    a数组:
    [[0 1 2]
    [3 4 5]]

    b数组:
    [[0 1]
    [2 3]
    [4 5]]

    c数组(改变c,a不会变):
    [0 1 2 3 4 5]

    d数组(改变d,b也会变):
    [0 1 2 3 4 5]
    '''

翻转数组

  • 对换数组的维度(axes为整数列表,对应对换的维度,不设置则为矩阵的转置)
    numpy.transpose(arr, axes)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    # 示例代码
    a = np.arange(12).reshape((2, 3, 2))
    b = np.transpose(a, [0, 2, 1])

    '''
    [[[ 0 1]
    [ 2 3]
    [ 4 5]]

    [[ 6 7]
    [ 8 9]
    [10 11]]]

    对换数组维度后(第二维和第三维互换):
    [[[ 0 2 4]
    [ 1 3 5]]

    [[ 6 8 10]
    [ 7 9 11]]]
    '''

修改维度

  • 在指定位置插入新的轴来扩展数组的维度,axis为整数
    numpy.expand_dims(arr, axis)
  • 删除只有一维的轴来压缩数组的维度,axis可以是整数或元组,但指定的轴必须只有一维
    numpy.squeeze(arr, axis)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    # 示例代码
    a = np.array(([1,2],[3,4]))
    b = np.expand_dims(a, axis = 0)

    x = np.array([[[0], [1], [2]]])
    c = np.squeeze(x)

    '''
    b数组:
    [[[1 2]
    [3 4]]]

    c数组:
    [0 1 2]
    # 由于axis不指定则x数组中所有维度为1的轴都被删除了
    '''

连接数组

  • 沿指定轴连接相同形状的若干数组(默认是0轴)
    numpy.concatenate((a1, a2, ...), axis)
  • 沿指定轴堆叠相同形状的若干数组(默认是0轴)
    numpy.stack((a1, a2, ...), axis)
  • 水平堆叠相同形状的若干数组
    numpy.hstack((a1, a2, ...))
  • 垂直堆叠相同形状的若干数组
    numpy.vstack((a1, a2, ...))
    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
    # 示例代码
    a = np.array([[1,2], [3,4]])
    b = np.array([[5,6], [7,8]])

    print(np.concatenate((a, b)))
    print(np.stack((a, b), 1))
    print(np.hstack((a, b))) # 等于concatenate(axis=1)
    print(np.vstack((a, b))) # 等于concatenate(axis=0)

    '''
    np.concatenate((a, b))和np.vstack((a, b))的结果:
    [[1 2]
    [3 4]
    [5 6]
    [7 8]]

    np.stack((a, b), 1)的结果:
    [[[1 2]
    [5 6]]

    [[3 4]
    [7 8]]]

    np.hstack((a, b))的结果:
    [[1 2 5 6]
    [3 4 7 8]]
    '''

分割数组

  • 沿指定轴将一个数组分割为多个子数组
    numpy.split(ary, indices_or_sections, axis)
  • 将一个数组水平分割为多个子数组
    numpy.hsplit(ary, indices_or_sections)
  • 将一个数组垂直分割为多个子数组
    numpy.vsplit(ary, indices_or_sections)
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
# 示例代码
a = np.arange(16).reshape(4,4)

print(np.split(a, 4)) # indices_or_sections为整数表示均分成若干子数组
print(np.split(a, [1, 3], axis=1)) # indices_or_sections为数组表明分割位置
print(np.hsplit(a, 2))
print(np.vsplit(a, 4))

'''
将a分割成4个子数组(默认是0轴):
[array([[0, 1, 2, 3]]), array([[4, 5, 6, 7]]), array([[ 8, 9, 10, 11]]), array([[12, 13, 14, 15]])]

将a沿轴1在1,3位置上进行分割(位置点是开区间):
[array([[ 0],
[ 4],
[ 8],
[12]]), array([[ 1, 2],
[ 5, 6],
[ 9, 10],
[13, 14]]), array([[ 3],
[ 7],
[11],
[15]])]

将a水平分割成2个子数组:
[array([[ 0, 1],
[ 4, 5],
[ 8, 9],
[12, 13]]), array([[ 2, 3],
[ 6, 7],
[10, 11],
[14, 15]])]

将a垂直分割成4个子数组:
[array([[0, 1, 2, 3]]), array([[4, 5, 6, 7]]), array([[ 8, 9, 10, 11]]), array([[12, 13, 14, 15]])]
'''

统计

在机器学习任务中,一个数组往往是一个样本的表征,批量的样本被表示为高维的数组,分析批量样本的各种统计值是很有必要的一步操作。统计所涉及的也正是前文所述的归约运算,所以我们留在这里详细介绍它们的用法:

查找最大最小值

numpy.max(a, axis=None)
numpy.min(a, axis=None)

1
2
3
4
5
6
# 示例代码
a = np.array([[3,7,5], [8,4,3], [2,4,9]])

print(np.max(a)) # 不指定轴,则返回整个数组的最值:9
print(np.max(a, axis=0)) # 返回 [8 7 9]
print(np.min(a, 1)) # 返回 [3 3 2]

求最值的差

numpy.ptp(a, axis=None)

1
2
3
4
# 示例代码
print(np.ptp(a)) # 7
print(np.ptp(a, axis=0)) # [6 3 6]
print(np.ptp(a, 1)) # [4 5 7]

返回百分位数

numpy.percentile(a, q, axis=None) 百分位数,q 为0~100的数
numpy.median(a, axis=None) 中位数

1
2
3
4
5
6
# 示例代码
a = np.array([[10,7,4], [3,2,1]])

print(np.percentile(a, 50)) # 50%分为数就是中位数:3.5
print(np.median(a)) # 3.5
print(np.percentile(a, 25, axis=0)) # [4.75 3.25 1.75]

求均值

numpy.mean(a, axis=None) 求算术平均值
numpy.average(a, axis=None, weights=None) 可以指定 weights 求加权平均值

1
2
3
4
5
6
7
8
# 示例代码
a = np.array([1, 2, 3, 4])

print(np.mean(a)) # 2.5
print(np.average(a)) # 不指定weights与np.mean一样:2.5

wts = np.array([4, 3, 2, 1])
print(np.average(a, weights = wts)) # 加权平均值:2.0 = (1*4+2*3+3*2+4*1)/(4+3+2+1)

方差与标准差

numpy.var(a, axis=None)
numpy.std(a, axis=None)

1
2
3
4
5
# 示例代码
a = np.array([1, 2, 3, 4])

print(np.var(a)) # 1.25 = mean((x - x.mean())** 2)
print(np.std(a)) # 1.118034 = sqrt(mean((x - x.mean())**2))

筛选

与上述的统计操作一样,从批量样本中筛选出满足特定条件的样本也是机器学习任务中常用的操作:

返回最值索引

numpy.argmax(a, axis=None)
numpy.argmin(a, axis=None)

1
2
3
4
5
# 示例代码
a = np.array([[30,40,70], [80,20,10], [50,90,60]])

print(np.argmax(a)) # 7
print(np.argmin(a, axis=0)) # [0 1 1]

返回非零元素索引

numpy.nonzero(a)

1
2
3
4
# 示例代码
a = np.array([[30,40,0], [0,20,10], [50,0,60]])

print (np.nonzero(a)) # 对应二维数据的(i,j)下标:(array([0, 0, 1, 1, 2, 2], dtype=int64), array([0, 1, 1, 2, 0, 2], dtype=int64))

返回满足条件的元素(索引)

numpy.where(condition, x=None, y=None)

其中 condition 里包含了数组的逻辑判断;如果设置了 x 和 y 则表示:满足条件的元素位置上置为 x,不满足的置为 y;否则,只返回满足条件的元素的索引

1
2
3
4
5
6
7
# 示例代码
x = np.array([46, 57, 23, 39, 1, 10, 0, 120])

y = np.where(x > 20)
print(y) # (array([0, 1, 2, 3, 7], dtype=int64),)
print(x[y]) # 可以直接用y来取数组中的元素:[ 46 57 23 39 120]
print(np.where(x > 20, x, -1)) # [ 46 57 23 39 -1 -1 -1 120]

抽取满足条件的元素

numpy.extract(condition, arr)

其中 condition 是一个布尔索引,待抽取的数组需要传入

1
2
3
4
5
# 示例代码
x = np.arange(9.).reshape(3, 3)

condition = np.mod(x,2) == 0 # 定义条件, 选择偶数元素
print(np.extract(condition, x)) # [0. 2. 4. 6. 8.]

排序

一般地,机器学习预测结果的输出会附加其概率值,通常我们只关注最大值,但是经过排序后输出概率的 TOP-K 值也是相对常用的:

  • 返回输入数组的升序排序副本(kind 指定排序算法,默认是快排;order 是待排序的字段)
    numpy.sort(a, axis=-1, kind=None, order=None)
  • 返回数组元素从小到大的索引值
    numpy.argsort(a, axis=-1, kind=None, order=None)
1
2
3
4
5
6
7
8
9
# 示例代码
a = np.array([[3,7], [9,1]])
print(np.sort(a)) # 升序排序:[[3 7] [9 1]]
print(np.sort(a, axis=0)) # 按列排序:[[3 1] [9 7]]

dt = np.dtype([('name', 'S10'),('age', int)])
a = np.array([("raju",21), ("anil",25), ("ravi",17), ("amar",27)], dtype=dt)
print(np.sort(a, order='name')) # 按name字段排序:[(b'amar', 27) (b'anil', 25) (b'raju', 21) (b'ravi', 17)]
print(np.argsort(a)) # [3 1 0 2]

线性代数

在运算一节中介绍的函数都是“逐元素”的计算,我们仍然有许多线性代数的运算,如点积、内积等都是机器学习常用的。另外,NumPy 还提供了线性代数库 numpy.linalg (需要单独 import)整合了大量的线性代数运算,我们在本节整理了一些常用的运算方法:

  • 点积(需要满足矩阵乘法的维度要求,如果设置了out则用来存储计算结果)
    numpy.dot(a, b, out=None)
  • 逐元素乘积和
    numpy.vdot(a, b)
  • 内积(对于高维度的数组,返回最后一个轴上的和的乘积)
    numpy.inner(a, b)
  • 矩阵的行列式
    numpy.linalg.det(a)
  • 矩阵的逆矩阵
    numpy.linalg.inv(a)
  • 奇异值分解
    numpy.linalg.svd(a, full_matrices=True, compute_uv=True, hermitian=False)
  • 范数
    numpy.linalg.norm(x, ord=None, axis=None, keepdims=False)

这里重点说一下 numpy.linalg.norm 中的 ord 参数,如果设为 1,则计算 L1-范数;为 2 则计算 L2-范数(默认就是 L2);为 np.inf 则计算无穷范数(即 max(|xi|))

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
# 示例代码
import numpy.linalg

a = np.array([[1,2], [3,4]])
b = np.array([[11,12], [13,14]])
print(np.dot(a,b))
print(np.vdot(a,b))
print(np.inner(np.array([1,2,3]),np.array([0,1,0])))
print(np.inner(a,b))
print(np.linalg.det(a))
print (np.linalg.inv(a))
print(np.linalg.norm(a, ord=1, axis=0, keepdims=True))
print(np.linalg.norm(a, ord=1, axis=0, keepdims=False))

'''
# a和b的矩阵乘法 [[1*11+2*13, 1*12+2*14],[3*11+4*13, 3*12+4*14]]:
[[37 40]
[85 92]]

# a和b的逐元素乘积和 1*11 + 2*12 + 3*13 + 4*14 = 130:
130

# 两个向量的内积 1*0+2*1+3*0 = 2:
2

# 矩阵a和b的内积 [[1*11+2*12, 1*13+2*14], [3*11+4*12, 3*13+4*14]]:
[[35 41]
[81 95]]

# 矩阵a的行列式:
-2.0000000000000004

# 矩阵a的逆矩阵:
[[-2. 1. ]
[ 1.5 -0.5]]

# keeptime=True表示结果保留二维特性,keeptime=False表示结果不保留二维特性
[[4. 6.]]
[4. 6.]
'''

矩阵库

尽管大多数机器学习的矩阵运算我们都可以使用二维的 ndarray 来实现,我们还是有必要介绍一下 NumPy 提供的矩阵库,它是一个独立的库,需要在代码中单独 import 进来。

  • 转置
    ndarray.T
  • 创建空矩阵
    numpy.matlib.empty(shape, dtype, order)
  • 创建全0矩阵
    numpy.matlib.zeros()
  • 创建全1矩阵
    numpy.matlib.ones()
  • 创建对角线为1,其余位置为0的矩阵
    numpy.matlib.eye()
  • 创建单位矩阵
    numpy.matlib.identity()
  • 随机初始矩阵
    numpy.matlib.rand()
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
# 示例代码
import numpy.matlib

a = np.arange(9).reshape(3,3)
print(a.T)
print(np.matlib.empty((2, 2)))
print(np.matlib.zeros((2, 2)))
print(np.matlib.ones((2, 2)))
print(np.matlib.eye(n=2, M=3, k=0, dtype=float))
print(np.matlib.identity(3, dtype=float))
print(np.matlib.rand(3, 3))

'''
# 0~8的矩阵的转置:
[[0 3 6]
[1 4 7]
[2 5 8]]

# 空矩阵元素为极小值:
[[6.23042070e-307 7.56587584e-307]
[1.37961302e-306 6.23053614e-307]]

# 全0矩阵
[[0. 0.]
[0. 0.]]

# 全1矩阵
[[1. 1.]
[1. 1.]]

# 对角线为1的矩阵
[[1. 0. 0.]
[0. 1. 0.]]

# 单位矩阵(方阵):
[[1. 0. 0.]
[0. 1. 0.]
[0. 0. 1.]]

# 随机初始化的矩阵:
[[0.86682619 0.14418785 0.21160104]
[0.38527629 0.98463648 0.63657043]
[0.63264472 0.52298278 0.08210189]]
'''

矩阵库生成的矩阵,其数据类型为 ,不再是 ndarray 了。