numpy
的常用功能,保证日常绝大多数场景的使用。可作为机器学习或深度学习的先修课程,也可作为快速备查手册。
值得一提的是,深度学习的各大框架很多 API 和 numpy
也是一脉相承的哦,可以说 numpy
玩儿熟了,几个深度学习框架的不少 API 也同时学会了。
教程原则如下:
使用说明如下:
特别需要提醒的是,您在使用的过程中无须过多关注 API 各种参数细节,教程提供的用法足以应付绝大部分场景,更深入的使用方式可以自行根据需要探索或学习后续的《基础教程》。
# 导入 library
import numpy as np
# 画图工具
import matplotlib.pyplot as plt
本节主要介绍 array 的创建和生成。为什么会把这个放在最前面呢?主要有以下两个方面原因:
所以,先学会如何快速拿到一个 array 是有很多益处的。本节我们主要介绍以下几种常用的创建方式:
其中,最常用的一般是 linspace/logspace 和 random,前者常常用在画坐标轴上,后者则用于生成「模拟数据」。举例来说,当我们需要画一个函数的图像时,X 往往使用 linspace 生成,然后使用函数公式求得 Y,再 plot;当我们需要构造一些输入(比如 X)或中间输入(比如 Embedding、hidden state)时,random 会异常方便。
⭐⭐ 重点掌握传入 list 创建一个 array 即可:np.array(list)
⚠️ 需要注意的是:「数据类型」。如果您足够仔细的话,可以发现下面第二组代码第 2 个数字是「小数」(注:Python 中 1. == 1.0),而 array 是要保证每个元素类型相同的,所以会帮您把 array 转为一个 float 的类型。
# 一个 list
np.array([1,2,3])
array([1, 2, 3])
# 二维(多维类似)
# 注意,有一个小数哦
np.array([[1, 2., 3], [4, 5, 6]])
array([[1., 2., 3.],
[4., 5., 6.]])
# 您也可以指定数据类型
np.array([1, 2, 3], dtype=np.float16)
array([1., 2., 3.], dtype=float16)
# 如果指定了 dtype,输入的值都会被转为对应的类型,而且不会四舍五入
lst = [[1, 2, 3],
[4, 5, 6.8]]
np.array(lst, dtype=np.int32)
array([[1, 2, 3],
[4, 5, 6]], dtype=int32)
# 一个 tuple
np.array((1.1, 2.2))
array([1.1, 2.2])
# tuple,一般用 list 就好,不需要使用 tuple
np.array([(1.1, 2.2, 3.3), (4.4, 5.5, 6.6)])
array([[1.1, 2.2, 3.3],
[4.4, 5.5, 6.6]])
# 转换而不是上面的创建,其实是类似的,无须过于纠结
np.asarray((1,2,3))
array([1, 2, 3])
np.asarray(([1., 2., 3.], (4., 5., 6.)))
array([[1., 2., 3.],
[4., 5., 6.]])
⭐⭐
range 是 Python 内置的整数序列生成器,arange 是 numpy 的,效果类似,会生成一维的向量。我们偶尔会需要使用这种方式来构造 array,比如:
⚠️ 需要注意的是:在 reshape
时,目标的 shape 需要的元素数量一定要和原始的元素数量相等。
np.arange(12).reshape(3, 4)
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
# 注意,是小数哦
np.arange(12.0).reshape(4, 3)
array([[ 0., 1., 2.],
[ 3., 4., 5.],
[ 6., 7., 8.],
[ 9., 10., 11.]])
np.arange(100, 124, 2).reshape(3, 2, 2)
array([[[100, 102],
[104, 106]],
[[108, 110],
[112, 114]],
[[116, 118],
[120, 122]]])
# shape size 相乘要和生成的元素数量一致
np.arange(100., 124., 2).reshape(2,3,4)
ValueError: cannot reshape array of size 12 into shape (2,3,4)
⭐⭐⭐
OK,这是我们遇到的第一个比较重要的 API,前者需要传入 3 个参数:开头,结尾,数量;后者需要额外传入一个 base,它默认是 10。
⚠️ 需要注意的是:第三个参数并不是步长。
np.linspace
# 线性
np.linspace(0, 9, 10).reshape(2, 5)
array([[0., 1., 2., 3., 4.],
[5., 6., 7., 8., 9.]])
np.linspace(0, 9, 6).reshape(2, 3)
array([[0. , 1.8, 3.6],
[5.4, 7.2, 9. ]])
# 指数 base 默认为 10
np.logspace(0, 9, 6, base=np.e).reshape(2, 3)
array([[1.00000000e+00, 6.04964746e+00, 3.65982344e+01],
[2.21406416e+02, 1.33943076e+03, 8.10308393e+03]])
# _ 表示上(最近)一个输出
# logspace 结果 log 后就是上面 linspace 的结果
np.log(_)
array([[0. , 1.8, 3.6],
[5.4, 7.2, 9. ]])
下面我们更进一步看一下:
N = 20
x = np.arange(N)
y1 = np.linspace(0, 10, N) * 100
y2 = np.logspace(0, 10, N, base=2)
plt.plot(x, y2, '*');
plt.plot(x, y1, 'o');
# 检查每个元素是否为 True
# base 的 指数为 linspace 得到的就是 logspace
np.alltrue(2 ** np.linspace(0, 10, N) == y2)
True
⚠️ 补充:关于 array 的条件判断
# 不能直接用 if 判断 array 是否符合某个条件
arr = np.array([1, 2, 3])
cond1 = arr > 2
cond1
array([False, False, True])
if cond1:
print("这不行")
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
# 即便你全是 True 它也不行
arr = np.array([1, 2, 3])
cond2 = arr > 0
cond2
array([ True, True, True])
if cond2:
print("这还不行")
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
# 咱们只能用 any 或 all,这个很容易犯错,请务必注意。
if cond1.any():
print("只要有一个为True就可以,所以——我可以")
只要有一个为True就可以,所以——我可以
if cond2.all():
print("所有值为True才可以,我正好这样")
所有值为True才可以,我正好这样
⭐
创建全 1/0 array 的快捷方式。需要注意的是 np.zeros_like
或 np.ones_like
,二者可以快速生成给定 array 一样 shape 的 0 或 1 向量,这在需要 Mask 某些位置时可能会用到。
⚠️ 需要注意的是:创建出来的 array 默认是 float 类型。
np.ones(3)
array([1., 1., 1.])
np.ones((2, 3))
array([[1., 1., 1.],
[1., 1., 1.]])
np.zeros((2,3,4))
array([[[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.]],
[[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.]]])
# 像给定向量那样的 0 向量(ones_like 是 1 向量)
np.zeros_like(np.ones((2,3,3)))
array([[[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.]],
[[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.]]])
⭐⭐⭐⭐⭐
如果要在这一节选一个最重要的 API,那一定是 random
无疑了,这里我们只介绍几个比较常用的「生产」数据相关的 API。它们经常用于随机生成训练或测试数据,神经网路初始化等。
⚠️ 需要注意的是:这里我们统一推荐使用新的 API 方式创建,即通过 np.random.default_rng()
先生成 Generator
,然后再在此基础上生成各种分布的数据(记忆更加简便清晰)。不过我们依然会介绍就的 API 用法,因为很多代码中使用的还是旧的,您可以混个眼熟。
# 0-1 连续均匀分布
np.random.rand(2, 3)
array([[0.42508994, 0.5842191 , 0.09248675],
[0.656858 , 0.88171822, 0.81744539]])
# 单个数
np.random.rand()
0.29322641374172986
# 0-1 连续均匀分布
np.random.random((3, 2))
array([[0.17586271, 0.5061715 ],
[0.14594537, 0.34365713],
[0.28714656, 0.40508807]])
# 指定上下界的连续均匀分布
np.random.uniform(-1, 1, (2, 3))
array([[ 0.66638982, -0.65327069, -0.21787878],
[-0.63552782, 0.51072282, -0.14968825]])
# 上面两个的区别是 shape 的输入方式不同,无伤大雅了
# 不过从 1.17 版本后推荐这样使用(以后大家可以用新的方法)
# rng 是个 Generator,可用于生成各种分布
rng = np.random.default_rng(42)
rng
Generator(PCG64) at 0x111B5C5E0
# 推荐的连续均匀分布用法
rng.random((2, 3))
array([[0.77395605, 0.43887844, 0.85859792],
[0.69736803, 0.09417735, 0.97562235]])
# 可以指定上下界,所以更加推荐这种用法
rng.uniform(0, 1, (2, 3))
array([[0.47673156, 0.59702442, 0.63523558],
[0.68631534, 0.77560864, 0.05803685]])
# 随机整数(离散均匀分布),不超过给定的值(10)
np.random.randint(10, size=2)
array([6, 3])
# 随机整数(离散均匀分布),指定上下界和 shape
np.random.randint(0, 10, (2, 3))
array([[8, 6, 1],
[3, 8, 1]])
# 上面推荐的方法,指定大小和上界
rng.integers(10, size=2)
array([9, 7])
# 上面推荐的方法,指定上下界
rng.integers(0, 10, (2, 3))
array([[5, 9, 1],
[8, 5, 7]])
# 标准正态分布
np.random.randn(2, 4)
array([[-0.61241167, -0.55218849, -0.50470617, -1.35613877],
[-1.34665975, -0.74064846, -2.5181665 , 0.66866357]])
# 上面推荐的标准正态分布用法
rng.standard_normal((2, 4))
array([[ 0.09130331, 1.06124845, -0.79376776, -0.7004211 ],
[ 0.71545457, 1.24926923, -1.22117522, 1.23336317]])
# 高斯分布
np.random.normal(0, 1, (3, 5))
array([[ 0.30037773, -0.17462372, 0.23898533, 1.23235421, 0.90514996],
[ 0.90269753, -0.5679421 , 0.8769029 , 0.81726869, -0.59442623],
[ 0.31453468, -0.18190156, -2.95932929, -0.07164822, -0.23622439]])
# 上面推荐的高斯分布用法
rng.normal(0, 1, (3, 5))
array([[ 2.20602146, -2.17590933, 0.80605092, -1.75363919, 0.08712213],
[ 0.33164095, 0.33921626, 0.45251278, -0.03281331, -0.74066207],
[-0.61835785, -0.56459129, 0.37724436, -0.81295739, 0.12044035]])
总之,一般会用的就是2个分布:均匀分布和正态(高斯)分布。另外,size
可以指定 shape。
rng = np.random.default_rng(42)
# 离散均匀分布
rng.integers(low=0, high=10, size=5)
array([0, 7, 6, 4, 4])
# 连续均匀分布
rng.uniform(low=0, high=10, size=5)
array([6.97368029, 0.94177348, 9.75622352, 7.61139702, 7.86064305])
# 正态(高斯)分布
rng.normal(loc=0.0, scale=1.0, size=(2, 3))
array([[-0.01680116, -0.85304393, 0.87939797],
[ 0.77779194, 0.0660307 , 1.12724121]])
⭐
这小节主要用于加载实现存储好的权重参数或预处理好的数据集,有时候会比较方便,比如训练好的模型参数加载到内存里用来提供推理服务,或者耗时很久的预处理数据直接存起来,多次实验时不需要重新处理。
⚠️ 需要注意的是:存储时不需要写文件名后缀,会自动添加。
# 直接将给定矩阵存为 a.npy
np.save('./data/a', np.array([[1, 2, 3], [4, 5, 6]]))
# 可以将多个矩阵存在一起,名为 `b.npz`
np.savez("./data/b", a=np.arange(12).reshape(3, 4), b=np.arange(12.).reshape(4, 3))
# 和上一个一样,只是压缩了
np.savez_compressed("./data/c", a=np.arange(12).reshape(3, 4), b=np.arange(12.).reshape(4, 3))
# 加载单个 array
np.load("data/a.npy")
array([[1, 2, 3],
[4, 5, 6]])
# 加载多个,可以像字典那样取出对应的 array
arr = np.load("data/b.npz")
arr["a"]
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
arr["b"]
array([[ 0., 1., 2.],
[ 3., 4., 5.],
[ 6., 7., 8.],
[ 9., 10., 11.]])
# 后缀都一样,你干脆当它和上面的没区别即可
arr = np.load("data/c.npz")
arr["b"]
array([[ 0., 1., 2.],
[ 3., 4., 5.],
[ 6., 7., 8.],
[ 9., 10., 11.]])