numpy框架探究
性质与创建
简述
ndarray(N-dimensional array,即N维数组)是NumPy的核心数据结构。可将ndarray看作是一个“智能容器”。相比于python原生数据结构,他有更好的特性,因此性能更优。他在数学上等同于一个张量,不过有些性质为了程序设计的合理性,并不符合数学直觉,后面会一一谈到。
ndarray之所以比python原生数据结构更高效,主要由以下三个性质决定,从这里也可以明显看出:ndarray在内存管理上更像c,而不是python
- 同质数据类型:ndarray要求所有元素具有相同的数据类型。这种同质性使得ndarray在内存管理和数据操作上更加高效,避免了类型转换带来的额外开销。
- 多维结构:ndarray支持多维结构,能够表示各种复杂的数据形式。
- 连续内存存储:NumPy 的 ndarray 采用连续内存存储,即其在内存中连续分配空间。与之不同,Python 列表元素在内存里分散存储,各元素有独立内存地址,且列表含指向这些元素的指针。这种分散存储在访问元素时需多次指针跳转,致使访问效率低。而 ndarray 凭借连续内存存储,计算机只需依据元素索引和数组起始地址进行简单计算,就能直接定位元素内存位置,实现快速访问。
轴(axis)
轴定义了ndarray的维度,数学上几维张量就有几个轴,ndarray亦是如此,以计算机视觉中常常涉及到的三通道彩色图像为例
对于一个三通道的彩色图像,通常用一个形状为
(height, width, channels)
的三维 ndarray
来表示,其中: -
axis = 0
:对应图像的高度方向,也就是垂直方向。沿着这个轴操作,会对图像的不同行进行处理。
-
axis = 1
:对应图像的宽度方向,即水平方向。沿着这个轴操作,会对图像的不同列进行处理。
-
axis = 2
:对应图像的通道方向,即红、绿、蓝三个通道。沿着这个轴操作,会对同一像素位置的不同颜色通道进行处理。
这种定义的好处是设计接口同一,但也导致会一些反数学直觉的现象:比如形状(1,3)和形状为(3,)的ndarray不是同一个东西,前者是一个二维矩阵,后者是一个一维向量,这将导致他们在数学运算上展示出一些差异。
使用np.array()创建数组
numpy.array()
是 NumPy 库中用于创建数组的核心函数,
1 |
|
详细解释各个参数的含义:
object
object
表示要转换为 ndarray
的输入数据。可以是多种类型的对象,常见的有: -
列表(list):例如 [1, 2, 3]
或
[[1, 2], [3, 4]]
,分别可以转换为一维和二维数组。 -
元组(tuple):像 (1, 2, 3)
也能被转换为一维数组。 -
其他对象:包括ndarray对象,pdread出来的对象等
1 |
|
dtype
dtype
用于指定数组元素的数据类型,在选择数据类型时,性能差异是一个需要重点考虑的因素。常见的数据类型包括
Python 内置的 int、float、bool
等,以及 NumPy
特有的具体位数类型,如 np.int32、np.float64
等。后者效率远远高于前者,因为python原生数据包含结构引用计数和类型指针等额外信息。不过在不指定
dtype
时,NumPy 多数情况会自动使用合适的 NumPy
数据类型,但为了性能和代码的健壮性,明确指定 dtype
是更好的做法。
在机器学习中,对于大规模数据集,选择合适的数据类型至关重要。例如,图像数据通常可以使用
np.uint8
类型,而神经网络中的权重和偏置通常使用
np.float32
以平衡精度和计算速度。
1 |
|
copy(不常用)
可选参数,是一个布尔值,默认为 True
。如果
copy
为
True
,则会创建输入对象的一个副本;如果为
False
,则尽可能不复制,而是直接使用输入对象的数据。当输入本身就是
ndarray
时,这个参数会影响是否创建新的数组对象。
order(不常用)
可选参数,用于指定数组在内存中的存储顺序,有三个可选值:
subok(不常用)
在机器学习库中,有时会自定义数组子类以实现特定的功能。如果需要保留这些子类的属性和方法,将 subok 设置为 True;否则,使用默认的 False 会返回普通的 ndarray。
ndmin
确保数组具有最小的维度,这在处理一些需要固定维度输入的机器学习模型时非常有用。例如,某些模型要求输入数据至少是二维的,使用
ndmin
可以保证输入数据符合要求。
使用函数生成数组
np.arange()
类似于python的range()
函数,接受参数为首项开,末项闭合,步长可指定
np.arange(start,stop,step,dtype=None)
在超参数调优时,我们可能需要对某个超参数进行等间隔取值来评估模型性能。例如,在调整学习率时,我们可以使用
np.arange
生成一系列等间隔的学习率值。 1
2
3
4
5
6
7
8
9# 生成一系列等间隔的学习率值
learning_rates = np.arange(0.01, 0.1, 0.01)
for lr in learning_rates:
model = LogisticRegression(C=1/lr)
model.fit(X_train, y_train)
score = model.score(X_test, y_test)
print(f"Learning rate: {lr}, Accuracy: {score}")
np.linspace()/np.logspace()
np.linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None)
:用于创建一个线性等分的数组>在绘制模型的损失曲线或准确率曲线时,我们需要在一定范围内均匀地取一些点作为横坐标。这时候可以使用
np.linspace 生成一系列等间隔的训练轮数作为画图用的横坐标
1
2
3
4
5
6
7# 假设我们训练了 100 轮,我们想以 10 为轮数间隔画图
epochs = np.linspace(1, 100, 10)
# losses为记录好的损失变化数组
plt.plot(epochs, losses)
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.show()
1 |
|
np.random.normal()
np.random.normal(loc=0.0, scale=1.0, size=None)
:用于生成服从正态分布的随机数数组,loc
是均值(默认为 0),scale 是标准差(默认为 1),size
是数组的形状(接受一个元组)
常常用于权重的初始化
快速生成可以直接用 1
2# 0-1 连续均匀分布
np.random.rand(2, 3)
np.ones(元组)/np.zeros(元组)
1 |
|
统计与属性
以下是对 NumPy 中常用统计和属性相关函数用法的总结:主要明白两点基本原则 - shape,ndim,size是属性,不加括号 - 凡是按照轴操作的函数,操作完后都会减少一个维度
尺寸相关
np.shape
- 功能:返回一个元组,表示数组形状。 ####
arr.ndim
- 功能:返回一个整数表示数组维度。
arr.size
- 功能:返回一个整数表示数组元素个数
最值
np.max(arr,axis=?)
和 np.min(arr,axis=?)
功能:分别返回数组中的最大值和最小值。可以通过
axis
参数指定在哪个轴上进行操作。易错:
axis=0
,axis=1
操作后数组的维度会减少,返回的是一维数组。
1 |
|
平均求和标准差
np.average(arr,axis=?,weights=?arr)
- 功能:计算数组的(加权)平均值。可通过
axis
参数指定计算轴,weights
参数指定权重。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17import numpy as np
arr = np.array([[1, 2, 3],
[4, 5, 6]])
weights = np.array([0.2, 0.3, 0.5])
# 不指定 axis,计算整个数组的加权平均
avg = np.average(arr)
print(f"整个数组平均值: {avg},类型: {type(avg)}") # 输出浮点数 3.5
# 指定 axis=0,按列计算加权平均
avg_col = np.average(arr, axis=0, weights=weights)
print(f"按列加权平均值: {avg_col},类型: {type(avg_col)},形状: {np.shape(avg_col)}") # 输出一维数组
# 指定 axis=1,按行计算加权平均
avg_row = np.average(arr, axis=1, weights=weights)
print(f"按行加权平均值: {avg_row},类型: {type(avg_row)},形状: {np.shape(avg_row)}") # 输出一维数组 - 易错点:若指定
weights
,其形状要与计算轴的形状匹配。
另外,不加权重的时候可以直接用np.mean(arr,axis=?)
np.sum(arr,axis=?)
- 功能:计算数组元素的总和。可通过
axis
参数指定计算轴。
np.std
- 功能:计算数组的标准差。可通过
axis
参数指定计算轴。
这里标准差涉及到总体标准差和样本标准差的区别,np.std提供了一个一接口调整自由度
总体标准差用 $$ \sigma = \sqrt{\frac{\sum_{i = 1}^{N}(x_i - \mu)^2}{N}} $$
在 np.std
函数中,当 ddof
(Delta Degrees of
Freedom,自由度)参数设置为 0 时,计算的就是总体标准差。示例代码如下:
1
2
3
4
5
6
7
8import numpy as np
# 定义一个数组
data = np.array([1, 2, 3, 4, 5])
# 计算总体标准差
population_std = np.std(data, ddof=0)
print("总体标准差:", population_std)
样本标准差用 $$
s = \sqrt{\frac{\sum_{i = 1}^{n}(x_i - \bar{x})^2}{n - 1}}
$$ 在 np.std
函数中,把 ddof
参数设置为
1 时,计算的就是样本标准差。
形状与变化
注意:本章节中涉及的所有操作都是在创建视图 -
视图:是原数组的一个引用,与原数组共享底层数据存储。对视图的修改会直接反映到原数组上,反之亦然。
1
2
3
4
5
6b = a.reshape(3,4)
# 此时a不变
b[0,0]=100 # 或者b[0][0]=100
# 此时a[0,0]也会变
np.expand_dims(arr,axis=?)
- 功能:在数组的指定位置插入新的轴,从而增加数组的维度。新
axis
插入后,原来位于该位置的轴,以及之后的轴的编号都会依次向后移动一位。 例如: - 对于一维数组
arr = np.array([1, 2, 3])
,其形状为(3,)
。- 当
axis = 0
时,在第 0 个位置插入新轴,原来的元素都在新轴的下一级,所以形状变为(1, 3)
。 - 当
axis = 1
时,在第 1 个位置插入新轴,原来的每个元素都被单独放在新轴的一个位置,形状变为(3, 1)
。
- 当
- 示例:在深度学习中,很多模型要求输入具有
(batch_size, channels, height, width)
的格式。当我们有一个单张图像(假设为灰度图),其形状为(height, width)
时,需要将其扩展为(1, 1, height, width)
以满足模型输入要求。1
2
3
4
5
6
7
8import numpy as np
# 模拟一张 28x28 的灰度图像
image = np.random.rand(28, 28)
# 扩展维度以匹配模型输入
input_image = np.expand_dims(np.expand_dims(image, axis=0), axis=0)
print("原图像形状:", image.shape) # 输出: (28, 28)
print("扩展维度后的图像形状:", input_image.shape) # 输出: (1, 1, 28, 28)
np.squeeze
- 功能:从数组中移除所有长度为 1 的轴。
1 |
|
- 机器学习实战代码示例:在模型预测后,输出结果可能会带有不必要的单维度轴。例如,一个模型对单样本进行预测,输出形状为
(1, 10)
,我们可以使用np.squeeze
去掉多余的维度,得到(10,)
的结果。
np.reshape((元组))
或 arr.reshape((元组))
- 功能:改变数组的形状,但保持元素总数不变。
- 由于python可以自动解包,不传入元组,直接
arr.reshape(n,m)
也行1
2
3
4
5
6
7
8
9
10
11import numpy as np
arr = np.arange(12)
# 使用 np.reshape
reshaped_arr_1 = np.reshape(arr, (3, 4))
# 使用 arr.reshape
reshaped_arr_2 = arr.reshape((3, 4))
print("原数组形状:", arr.shape) # 输出: (12,)
print("使用 np.reshape 后的形状:", reshaped_arr_1.shape) # 输出: (3, 4)
print("使用 arr.reshape 后的形状:", reshaped_arr_2.shape) # 输出: (3, 4)
注意: - reshape函数返回一个原数组的视图,不会使原数组形状改变。 - 可以用-1代替某个不想计算的维度,用于自动计算,如果只使用一个-1相当于flatten
arr.T
- 功能:对于二维数组,交换行和列;对于更高维数组,反转维度顺序。
- 一般代码示例:
1
2
3
4
5
6
7import numpy as np
arr = np.array([[1, 2, 3], [4, 5, 6]])
transposed_arr = arr.T
print("原数组形状:", arr.shape) # 输出: (2, 3)
print("转置后数组形状:", transposed_arr.shape) # 输出: (3, 2)
np.transpose
- 功能:可以指定轴的新顺序来对数组进行转置,比
arr.T
更灵活。1
2
3
4
5
6
7
8import numpy as np
arr = np.arange(24).reshape((2, 3, 4))
# 指定轴的新顺序进行转置
transposed_arr = np.transpose(arr, (1, 0, 2))
print("原数组形状:", arr.shape) # 输出: (2, 3, 4)
print("转置后数组形状:", transposed_arr.shape) # 输出: (3, 2, 4) - 机器学习示例:在深度学习中,不同的深度学习框架对数据的存储格式可能不同。例如,有些框架要求图像数据的格式为
(channels, height, width)
,而我们的数据可能是(height, width, channels)
格式,这时可以使用np.transpose
进行转换。1
2
3
4
5
6
7
8
9import numpy as np
# 模拟一张 28x28 的 RGB 图像,格式为 (height, width, channels)
image = np.random.rand(28, 28, 3)
# 转换为 (channels, height, width) 格式
transposed_image = np.transpose(image, (2, 0, 1))
print("原图像形状:", image.shape) # 输出: (28, 28, 3)
print("转置后图像形状:", transposed_image.shape) # 输出: (3, 28, 28)
分解与组合
切片和索引
总体上来说,切片的方式大致是 1
arr[row,col]
索引或切片方式 | 代码示例 | 功能说明 | 是否返回视图 |
---|---|---|---|
取单行 | arr[0] |
取数组的第 0 行 | 是 |
取单元素 | arr[0, 1] |
取数组第 0 行第 1 列的元素 | 否(返回单个元素值) |
连续行切片 | arr[0:3] |
取数组的第 1 - 3 行(索引 0 到 2) | 是 |
离散行索引 | arr[[0, 3]] |
取数组的第 1 行和第 4 行(索引 0 和 3) | 否(返回副本) |
行从某行到最后 | arr[3:] |
从数组中提取第 4 行(索引为 3)开始直至最后一行的元素 | 视图 |
行从开始到某行(开) | arr[:3] |
相当于arr[0:3] |
视图 |
行带步长切片 | arr[1: 4: 2] |
从数组中提取第 2 行至第 4 行(对应索引 1 到 3)的元素,按步长 2 选取,即选取第 2 行和第 4 行 | 视图 |
想要结合列进行索引和切片,在逗号后进行列的操作即可
索引或切片类型 | 代码示例 | 功能详细说明 | 返回结果性质 |
---|---|---|---|
行和列连续切片 | arr[1:3, 1] |
从数组中提取第 2 行至第 3 行(对应索引 1 到 2)里第 2 列(索引为 1)的元素 | 视图 |
行和列离散索引 | arr[[1,3], [0]] |
从数组中提取第 2 行和第 4 行(对应索引 1 和 3)里第 1 列(索引为 0)的元素 | 副本 |
行和列连续范围切片 | arr[:3, 1:3] |
从数组中提取第 1 行至第 3 行(对应索引 0 到 2)里第 2 列至第 3 列(对应索引 1 到 2)的元素 | 视图 |
行和列带步长切片 | arr[1:4:2, 0:3:2] |
从数组中提取第 2 行至第 4 行(对应索引 1 到 3),按步长 2 选取;同时提取第 1 列至第 3 列(对应索引 0 到 2),按步长 2 选取相应元素 | 视图 |
冒号索引列 | arr[:,1] |
从数组中提取所有行里第 2 列(索引为 1)的元素 | 视图 |
总体来说,涉及离散索引(如使用列表或数组指定不连续的索引)时通常返回副本,而连续切片操作大多返回视图。可以通过检查数组的
base
属性来判断返回的是视图还是副本,如果
arr.base
是原数组,则 arr
是视图;如果
arr.base
为 None
,则 arr
是副本。
切片和索引在机器学习中用的是很多的,举几个常用例子
数据集划分
1 |
|
特征选择
1 |
|
批量训练
使用批量梯度下降算法进行模型训练,需要将数据集划分为多个小批量。切片操作可以实现批量数据的提取。
1
2
3
4
5
6
7
8
9# 批量大小
batch_size = 10
# 遍历每个批量
for i in range(0, len(data), batch_size):
batch_data = data[i:i + batch_size]
batch_labels = labels[i:i + batch_size]
# 这里可以进行模型训练操作
print(f"第 {i // batch_size + 1} 个批量的数据形状:", batch_data.shape)
拼接
维度不变:拼接
np.concatenate
函数用于沿着指定的轴将多个数组连接在一起,要求除了指定轴之外的其他轴的形状必须一致
1 |
|

可以用于特征拼接或者数据集合并等操作
维度增加:堆叠
np.stack
函数用于沿着新的轴将多个数组堆叠在一起,要求所有输入数组的形状必须完全相同。
axis
用于指定新的,用于堆叠的轴的方向 1
2import numpy as np
np.stack((a1, a2, ...), axis=0)
在计算机视觉中,有时需要将多个单通道的图像合并成多通道图像。例如,将红、绿、蓝三个单通道的图像合并成一个 RGB 图像。
1 |
|
筛选和过滤
布尔数组直接筛选
用numpy写比for循环快得多,NumPy 的布尔索引筛选耗时远远小于 for 循环筛选耗时,并且随着数组规模的增大,这种性能差距会更加明显。这是因为 NumPy 的向量化操作能够并行处理数组元素,而 for 循环是串行执行的,效率较低。
1 |
|
三参数np.where()
三参数形式:np.where(condition, x, y)
参数: - condition:同样是一个布尔数组,用于指定筛选条件。 - x 和
y:可以是数组或者标量。当 condition 中的元素为 True
时,结果数组中对应位置的值取自 x;当 condition 中的元素为 False
时,结果数组中对应位置的值取自 y。 - 返回值:返回一个新的数组,其形状与
condition 相同,数组中的元素根据条件从 x 或 y 中选取。 1
2# 不满足条件的赋值,将 <=50 的替换为 -1
np.where(arr > 50, arr, -1)
数据集中可能存在缺失值(如 NaN),可以使用 np.where
将缺失值替换为特定的值 1
2cleaned_data = np.where(np.isnan(data), mean_value, data)
print("处理后的数据:", cleaned_data)1
2
3
4
5# 模拟特征数据
features = np.array([0.2, 0.7, 0.1, 0.9, 0.4])
# 根据阈值 0.5 进行二值化
binary_features = np.where(features > 0.5, 1, 0)
print("二值化后的特征:", binary_features)
矩阵与运算
以下是两个表格,分别总结了 NumPy 常见运算和矩阵运算的用法:
表格一:NumPy 常见运算总结
运算类型 | 函数/操作符 | 功能描述 | 示例代码 |
---|---|---|---|
逐元素算术运算 | + 、- 、* 、/ |
对数组进行四则运算 | arr * 2 |
** |
对数组进行幂运算 | arr ** 2 |
|
np.sqrt |
对数组元素开方 | np.sqrt(arr) |
|
np.log |
对数组元素求自然对数 | np.log(arr) |
|
np.mod |
对数组元素取模,可指定多个被除数 | np.mod(arr, 3) np.mod(arr, arr - 5) |
|
条件运算 | np.minimum |
将数组中超过指定值的元素替换为该值 | np.minimum(arr, 5) |
np.maximum |
将数组中低于指定值的元素替换为该值 | np.maximum(arr, 5) |
|
np.round |
对数组元素进行四舍五入 | np.round(np.sqrt(arr), 2) |
|
np.floor |
对数组元素向下取整 | np.floor(np.sqrt(arr)) |
|
np.ceil |
对数组元素向上取整 | np.ceil(np.sqrt(arr)) |
|
广播机制 | 操作符结合不同形状数组 | 较小数组在较大数组上广播以适配形状 | a + [1, 2, 3, 4] a + [[1], [2], [3]] np.mod(a, [1, 2, 3, 4]) |
表格二:NumPy 矩阵运算总结
运算类型 | 函数/操作符 | 功能描述 | 示例代码 | 高维差异说明 |
---|---|---|---|---|
矩阵乘法 | np.dot |
计算两个数组的点积,常用于矩阵乘法 | np.dot(a, b) a.dot(b) |
处理高维数组时规则与 matmul 不同 |
np.matmul 、@ |
计算两个数组的矩阵乘积 | np.matmul(a, b) a @ b |
会将矩阵像元素一样堆叠在一起广播 | |
点积 | np.vdot |
计算两个数组的点积 | np.vdot(a, a) |
等价于 np.sum(a * a) |
内积 | np.inner |
计算两个数组的内积 | np.inner(a, a) |
等价于 a.dot(a.T) |
行列式 | np.linalg.det |
计算方阵的行列式 | np.linalg.det(c) |
- |
逆矩阵 | np.linalg.inv |
计算方阵的逆矩阵 | np.linalg.inv(c) |
- |
关于维度的说明
1 |
|
一般而言使用中间的一维向量参与到矩阵运算中即可,不用特意定义好行向量或者列向量,因为广播机制会自动把一维向量赋予行向量或列向量的形式。最后的运算结果如果是向量则为(n,)如果是数值就是数值
如果使用1.3的定义方式也可,但是如果最后的计算结果为数值m的话,事实上会得到[[m]]
的形式,需要手动取arr[0,0]获取数值参与其他只接受数值的运算!