NumPy全面学习笔记
NumPy(Numerical Python)是Python科学计算的核心库,提供高效的多维数组对象(ndarray)及丰富的数值计算工具。相比Python原生列表,NumPy数组具备同构数据存储、连续内存布局、向量化运算三大优势,运算速度提升10-100倍,是数据分析、机器学习、科学计算的基石。
1. NumPy数组基础:类型与转换规则
1.1 整数数组与浮点数数组
NumPy数组的核心特性之一是数据类型同构(所有元素类型一致),其中整数(integer)和浮点数(float)是最常用的两种类型,对应不同的应用场景:
类型分类 | 常见 dtype | 特点 | 应用场景 |
---|---|---|---|
整数数组 | int8 /int16 /int32 /int64 | 无小数部分,范围随位数增加而扩大 | 索引、计数、离散值存储(如标签) |
浮点数数组 | float16 /float32 /float64 | 支持小数,精度随位数增加而提高 | 科学计算、连续值存储(如温度、坐标) |
核心操作示例:
import numpy as np# 1. 创建整数数组(指定dtype)
int_arr = np.array([1, 2, 3, 4], dtype=np.int32)
print("整数数组类型:", int_arr.dtype) # 输出: int32
print("整数数组形状:", int_arr.shape) # 输出: (4,)# 2. 创建浮点数数组(自动推断或指定)
float_arr1 = np.array([1.1, 2.2, 3.3]) # 自动推断为float64
float_arr2 = np.array([4, 5, 6], dtype=np.float16) # 强制指定为float16
print("float_arr1类型:", float_arr1.dtype) # 输出: float64# 3. 类型转换(astype返回新数组,原数组不变)
converted_arr = int_arr.astype(np.float64)
print("转换后类型:", converted_arr.dtype) # 输出: float64
print("原数组类型:", int_arr.dtype) # 输出: int32(不变)
1.2 同化定理(类型提升规则)
当不同类型的NumPy数组进行运算时,结果的类型会向精度更高、范围更广的类型“同化”,以避免数据丢失,这一规则称为“同化定理”。
核心规则与示例:
- 低精度 → 高精度:
int8
+int32
→int32
- 整数 → 浮点数:
int64
+float32
→float64
- 特殊类型优先:
bool
(本质是1位整数)与其他类型运算 → 转为对应类型
# 示例1:整数与浮点数同化
a = np.array([1, 2], dtype=np.int32)
b = np.array([3.1, 4.2], dtype=np.float32)
c = a + b
print("a + b 类型:", c.dtype) # 输出: float64(向更高精度同化)# 示例2:低精度与高精度同化
d = np.array([5, 6], dtype=np.int8) # 范围: -128~127
e = np.array([7, 8], dtype=np.int64) # 范围: -9e18~9e18
f = d * e
print("d * e 类型:", f.dtype) # 输出: int64(向更广范围同化)
1.3 共同改变定理(类型与形状协同转换)
“共同改变定理”指NumPy数组的类型转换与形状调整需遵循“元素总数守恒”和“数据兼容性”原则,两者可协同操作,但需避免数据丢失或逻辑错误。
核心操作与约束:
- 形状调整(
reshape
):需保证调整前后元素总数一致(np.prod(原shape) = np.prod(新shape)
) - 类型与形状协同:先确保类型兼容(如float转int会丢失小数),再调整形状
- 视图(view)与副本(copy):
reshape
可能返回视图(共享内存),astype
始终返回副本(独立内存)
# 示例:类型与形状协同转换
# 1. 原始数组(2行3列,int32)
arr = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.int32)
print("原始形状:", arr.shape) # 输出: (2, 3),元素总数: 6# 2. 先转类型(int32→float64),再调整形状(2,3)→(3,2)
step1 = arr.astype(np.float64) # 副本,类型改变
step2 = step1.reshape((3, 2)) # 视图(内存连续),形状改变
print("最终形状:", step2.shape) # 输出: (3, 2)
print("最终类型:", step2.dtype) # 输出: float64# 3. 错误示例:形状调整元素总数不匹配
try:arr.reshape((2, 4)) # 2*4=8 ≠ 6,报错
except ValueError as e:print("错误:", e) # 输出: cannot reshape array of size 6 into shape (2,4)
2. 数组维度:表示、形状与转换
2.1 数组维度(表示及形状)
NumPy中“维度”(Dimension)又称“轴”(Axis),是数组的层级结构描述。维度通过ndim
属性获取,形状通过shape
属性(元组形式)表示,元组中每个元素对应某一轴的元素数量。
不同维度数组的表示:
维度 | 名称 | 用途 | shape示例 | 轴的含义(以shape=(N,C,H,W)为例) |
---|---|---|---|---|
0D | 标量(Scalar) | 单个数值 | () | 无轴,仅1个元素 |
1D | 向量(Vector) | 一维数据 | (5,) | 轴0:5个元素(如时间序列) |
2D | 矩阵(Matrix) | 二维表格 | (3,4) | 轴0:3行,轴1:4列(如数据表) |
3D | 张量(Tensor) | 三维数据 | (2,3,4) | 轴0:2个矩阵,轴1:3行,轴2:4列(如RGB图像) |
4D | 高维张量 | 批量数据 | (10,3,224,224) | 轴0:10张图,轴1:3通道(RGB),轴2:224高,轴3:224宽 |
核心属性示例:
# 0D数组(标量)
scalar = np.array(5)
print("0D - ndim:", scalar.ndim, "shape:", scalar.shape, "size:", scalar.size)
# 输出: 0D - ndim: 0 shape: () size: 1# 3D数组(2个2行3列的矩阵)
tensor = np.array([[[1,2,3],[4,5,6]], [[7,8,9],[10,11,12]]])
print("3D - ndim:", tensor.ndim, "shape:", tensor.shape, "size:", tensor.size)
# 输出: 3D - ndim: 3 shape: (2, 2, 3) size: 12(2*2*3=12)
2.2 不同维度数组的转换
维度转换是NumPy的核心操作,分为升维(增加轴)、降维(删除轴)、维度调整(改变轴的元素数量)三类,需结合业务场景选择合适方法。
1. 升维(增加轴)
通过np.newaxis
(关键字)或np.expand_dims
(函数)在指定位置插入新轴(长度为1),常用于广播运算前的维度对齐。
# 原始1D数组
arr = np.array([1, 2, 3]) # shape: (3,)# 方法1:np.newaxis升维(轴0插入新轴)
arr_2d_axis0 = arr[np.newaxis, :] # shape: (1, 3)(1行3列)
# 方法2:np.newaxis升维(轴1插入新轴)
arr_2d_axis1 = arr[:, np.newaxis] # shape: (3, 1)(3行1列)# 方法3:np.expand_dims(指定axis参数)
arr_3d = np.expand_dims(arr, axis=1) # shape: (3, 1)
print("arr_2d_axis0 shape:", arr_2d_axis0.shape) # 输出: (1, 3)
print("arr_3d shape:", arr_3d.shape) # 输出: (3, 1)
2. 降维(删除轴)
通过np.squeeze
删除所有长度为1的轴,或指定axis
删除特定长度为1的轴;通过reshape(-1)
将任意维度转为1D数组(-1
表示自动计算元素数量)。
# 原始3D数组(shape: (1, 3, 1))
arr = np.array([[[1], [2], [3]]])# 方法1:np.squeeze删除所有长度为1的轴
arr_squeezed = np.squeeze(arr) # shape: (3,)
# 方法2:np.squeeze指定删除轴0
arr_squeeze_axis0 = np.squeeze(arr, axis=0) # shape: (3, 1)# 方法3:reshape(-1)转为1D
arr_1d = arr.reshape(-1) # shape: (3,)
print("arr_squeezed shape:", arr_squeezed.shape) # 输出: (3,)
print("arr_1d shape:", arr_1d.shape) # 输出: (3,)
3. 维度调整(reshape
)
核心原则:调整前后元素总数一致,支持“自动计算”(-1
仅可出现一次)。
# 原始2D数组(shape: (4, 3),元素总数12)
arr = np.arange(12).reshape(4, 3) # np.arange生成0-11的连续整数# 调整为3D数组(2个2行3列的矩阵)
arr_3d = arr.reshape(2, 2, 3) # shape: (2, 2, 3)
# 调整为1D数组(自动计算长度)
arr_1d = arr.reshape(-1) # shape: (12,)
# 调整为2D数组(自动计算列数)
arr_2d_auto = arr.reshape(6, -1) # shape: (6, 2)(12/6=2)
print("arr_2d_auto shape:", arr_2d_auto.shape) # 输出: (6, 2)
3. 数组索引与切片:精准获取数据
3.1 数组索引(基础索引 + 花式索引)
索引是通过“位置”或“条件”获取数组元素的操作,NumPy支持基础索引(单元素/连续范围)和花式索引(离散位置/布尔条件),两者核心区别是返回结果是否为副本(花式索引返回副本,基础索引返回视图)。
1. 基础索引(按位置索引)
适用于获取单个元素或连续范围的元素,语法与Python列表类似,但支持多轴同时索引(用逗号分隔各轴)。
# 1D数组索引
arr_1d = np.array([10, 20, 30, 40, 50])
print("1D[2]:", arr_1d[2]) # 输出: 30(正索引)
print("1D[-1]:", arr_1d[-1]) # 输出: 50(负索引,倒数第一个)# 2D数组索引(轴0:行,轴1:列)
arr_2d = np.array([[1,2,3], [4,5,6], [7,8,9]])
print("2D[1,2]:", arr_2d[1, 2]) # 输出: 6(第2行第3列,行索引1,列索引2)
print("2D[0]:", arr_2d[0]) # 输出: [1 2 3](第1行所有列)# 3D数组索引(轴0:矩阵,轴1:行,轴2:列)
arr_3d = np.array([[[1,2],[3,4]], [[5,6],[7,8]]]) # shape: (2,2,2)
print("3D[1,0,1]:", arr_3d[1, 0, 1]) # 输出: 6(第2个矩阵,第1行,第2列)
2. 花式索引(按离散位置/布尔条件索引)
- 整数数组索引:用整数数组指定离散位置,返回与索引数组形状一致的结果
- 布尔索引:用布尔数组指定条件,返回所有
True
对应的元素(1D数组)
# 示例1:整数数组索引(2D数组)
arr_2d = np.array([[1,2,3], [4,5,6], [7,8,9]])
row_idx = [0, 2] # 行索引:第1、3行
col_idx = [1, 2] # 列索引:第2、3列
# 方式1:获取(0,1)、(2,2)两个元素
print("整数索引1:", arr_2d[row_idx, col_idx]) # 输出: [2 9]
# 方式2:获取第1、3行的第2、3列(子矩阵)
print("整数索引2:\n", arr_2d[row_idx][:, col_idx])
# 输出:
# [[2 3]
# [8 9]]# 示例2:布尔索引(筛选条件元素)
arr = np.array([1, 2, 3, 4, 5, 6])
# 条件:元素为偶数
bool_mask = arr % 2 == 0
print("布尔索引结果:", arr[bool_mask]) # 输出: [2 4 6]# 2D数组布尔索引(筛选行)
bool_row = arr_2d.sum(axis=1) > 10 # 行求和大于10的行(第2、3行)
print("筛选行结果:\n", arr_2d[bool_row])
# 输出:
# [[4 5 6]
# [7 8 9]]
3.2 矩阵切片(获取子数组)
切片是获取数组连续子范围的操作,语法为arr[start:end:step]
(左闭右开区间),支持多轴同时切片,且返回结果为视图(修改切片会影响原数组),这与Python列表切片的行为一致,但效率更高。
核心规则与示例:
start
:起始位置(默认0)end
:结束位置(默认数组长度,不包含)step
:步长(默认1,负数表示反向切片)- 多轴切片:用逗号分隔各轴的切片规则
- 省略号(
...
):代替多个:
,表示“剩余所有轴”
# 1D数组切片
arr_1d = np.array([10,20,30,40,50,60])
print("1D[1:4]:", arr_1d[1:4]) # 输出: [20 30 40](索引1-3)
print("1D[::2]:", arr_1d[::2]) # 输出: [10 30 50](步长2,隔一个取一个)
print("1D[::-1]:", arr_1d[::-1]) # 输出: [60 50 40 30 20 10](反向切片)# 2D数组切片(行切片, 列切片)
arr_2d = np.array([[1,2,3,4], [5,6,7,8], [9,10,11,12]])
# 切片1:第2-3行,第2-4列(索引1-2行,1-3列)
slice1 = arr_2d[1:3, 1:4]
print("slice1:\n", slice1)
# 输出:
# [[ 6 7 8]
# [10 11 12]]# 切片2:所有行,第1、3列(步长2)
slice2 = arr_2d[:, ::2]
print("slice2:\n", slice2)
# 输出:
# [[ 1 3]
# [ 5 7]
# [ 9 11]]# 3D数组切片(省略号用法)
arr_3d = np.array([[[1,2],[3,4]], [[5,6],[7,8]], [[9,10],[11,12]]]) # shape: (3,2,2)
# ... 代替前两个轴(轴0和轴1),切片轴2的第2个元素
slice3 = arr_3d[..., 1]
print("slice3 shape:", slice3.shape) # 输出: (3, 2)
print("slice3:\n", slice3)
# 输出:
# [[ 2 4]
# [ 6 8]
# [10 12]]# 关键特性:切片是视图,修改影响原数组
slice1[0, 0] = 999 # 修改切片的(0,0)元素
print("修改后原数组:\n", arr_2d)
# 输出(原数组第2行第2列变为999):
# [[ 1 2 3 4]
# [ 5 999 7 8]
# [ 9 10 11 12]]
4. 数组高级操作:转置、翻转、拼接与分裂
4.1 转置、翻转、重置、拼接、分裂
这类操作用于调整数组的结构(轴顺序、元素顺序、维度组合),是数据预处理的核心工具,需重点掌握各操作的适用场景与轴参数设置。
1. 转置(Transpose):交换轴的顺序
转置是改变轴的排列顺序,核心函数为np.transpose
(支持指定轴顺序)和arr.T
(仅适用于2D数组,等价于np.transpose(arr, (1,0))
)。
# 2D数组转置(矩阵转置:行变列,列变行)
arr_2d = np.array([[1,2,3], [4,5,6]]) # shape: (2,3)
transpose_2d = arr_2d.T # 等价于 np.transpose(arr_2d)
print("2D转置 shape:", transpose_2d.shape) # 输出: (3,2)
print("2D转置:\n", transpose_2d)
# 输出:
# [[1 4]
# [2 5]
# [3 6]]# 3D数组转置(指定轴顺序)
arr_3d = np.array([[[1,2],[3,4]], [[5,6],[7,8]]]) # shape: (2,2,2)
# 交换轴0和轴1(原shape: (2,2,2) → 新shape: (2,2,2))
transpose_3d = np.transpose(arr_3d, (1,0,2))
print("3D转置 shape:", transpose_3d.shape) # 输出: (2,2,2)
print("3D转置[0,1]:", transpose_3d[0,1]) # 输出: [5 6](原arr_3d[1,0])
2. 翻转(Flip):沿轴反转元素顺序
翻转是沿指定轴反向排列元素,核心函数包括np.flip
(任意轴)、np.flipud
(垂直翻转,轴0)、np.fliplr
(水平翻转,轴1)。
arr_2d = np.array([[1,2,3], [4,5,6], [7,8,9]])# 垂直翻转(轴0:行顺序反转)
flip_ud = np.flipud(arr_2d)
print("垂直翻转:\n", flip_ud)
# 输出:
# [[7 8 9]
# [4 5 6]
# [1 2 3]]# 水平翻转(轴1:列顺序反转)
flip_lr = np.fliplr(arr_2d)
print("水平翻转:\n", flip_lr)
# 输出:
# [[3 2 1]
# [6 5 4]
# [9 8 7]]# 沿轴1翻转(等价于水平翻转)
flip_axis1 = np.flip(arr_2d, axis=1)
print("沿轴1翻转:\n", flip_axis1) # 与flip_lr结果一致
3. 重置(Reshape/Resize/Pad):调整形状与填充
reshape
:固定元素总数,调整形状(前已讲)np.resize
:灵活调整形状,元素不足时重复填充,超出时截断np.pad
:给数组添加“ padding ”(边缘填充),支持多种填充模式
# np.resize示例(元素不足时重复)
arr = np.array([1,2,3])
resized = np.resize(arr, (2,3)) # 目标shape: (2,3),需6个元素
print("np.resize结果:\n", resized)
# 输出(原数组[1,2,3]重复为[1,2,3,1,2,3]):
# [[1 2 3]
# [1 2 3]]# np.pad示例(周围填充1层0)
arr_2d = np.array([[1,2],[3,4]])
# pad_width: 各轴填充层数((上,下), (左,右))
padded = np.pad(arr_2d, pad_width=((1,1), (1,1)), mode='constant', constant_values=0)
print("np.pad结果:\n", padded)
# 输出:
# [[0 0 0 0]
# [0 1 2 0]
# [0 3 4 0]
# [0 0 0 0]]
4. 拼接(Concatenate):合并多个数组
拼接是将多个形状兼容的数组沿指定轴合并,核心函数包括np.concatenate
(通用)、np.vstack
(垂直拼接,轴0)、np.hstack
(水平拼接,轴1),关键约束:除拼接轴外,其他轴的形状必须完全一致。
# 准备两个形状兼容的数组(shape: (2,3))
arr1 = np.array([[1,2,3], [4,5,6]])
arr2 = np.array([[7,8,9], [10,11,12]])# 1. 垂直拼接(轴0:行数增加,列数不变)
v_stack = np.vstack((arr1, arr2)) # 等价于 np.concatenate((arr1, arr2), axis=0)
print("垂直拼接 shape:", v_stack.shape) # 输出: (4,3)
print("垂直拼接:\n", v_stack)
# 输出:
# [[ 1 2 3]
# [ 4 5 6]
# [ 7 8 9]
# [10 11 12]]# 2. 水平拼接(轴1:列数增加,行数不变)
h_stack = np.hstack((arr1, arr2)) # 等价于 np.concatenate((arr1, arr2), axis=1)
print("水平拼接 shape:", h_stack.shape) # 输出: (2,6)
print("水平拼接:\n", h_stack)
# 输出:
# [[ 1 2 3 7 8 9]
# [ 4 5 6 10 11 12]]
5. 分裂(Split):拆分一个数组
分裂是将一个数组沿指定轴拆分为多个子数组,核心函数包括np.split
(通用)、np.vsplit
(垂直分裂,轴0)、np.hsplit
(水平分裂,轴1),关键约束:拆分后的子数组元素数量需整除原数组对应轴的长度(或指定拆分位置)。
# 原始数组(shape: (4,3))
arr = np.array([[1,2,3], [4,5,6], [7,8,9], [10,11,12]])# 1. 垂直分裂为2个等长数组(轴0:4行→2行+2行)
vsplit1 = np.vsplit(arr, 2) # 等价于 np.split(arr, 2, axis=0)
print("垂直分裂1 - 子数组1 shape:", vsplit1[0].shape) # 输出: (2,3)
print("垂直分裂1 - 子数组2:\n", vsplit1[1])
# 输出:
# [[ 7 8 9]
# [10 11 12]]# 2. 按指定位置分裂(轴1:3列→1列+2列)
hsplit2 = np.hsplit(arr, [1]) # 等价于 np.split(arr, [1], axis=1)
print("水平分裂2 - 子数组1 shape:", hsplit2[0].shape) # 输出: (4,1)
print("水平分裂2 - 子数组2:\n", hsplit2[1])
# 输出:
# [[ 2 3]
# [ 5 6]
# [ 8 9]
# [11 12]]
5. 数组运算:元素级、广播与矩阵运算
5.1 数组与系数之间的运算
“系数”指单个数值(标量),数组与系数的运算属于元素级运算(系数与数组每个元素逐一运算),无需循环,直接通过运算符或NumPy函数实现,效率极高。
支持的运算类型:
运算类别 | 运算符 | NumPy函数 | 示例 |
---|---|---|---|
加法 | + | np.add | arr + 5 |
减法 | - | np.subtract | arr - 2 |
乘法 | * | np.multiply | arr * 3 |
除法 | / | np.divide | arr / 2 |
地板除 | // | np.floor_divide | arr // 2 |
取余 | % | np.mod | arr % 3 |
幂运算 | ** | np.power | arr ** 2 |
示例:
arr = np.array([[1,2,3], [4,5,6]], dtype=np.float64)
coeff = 2 # 系数# 加法:每个元素加2
add_res = arr + coeff
print("数组+系数:\n", add_res)
# 输出:
# [[3. 4. 5.]
# [6. 7. 8.]]# 幂运算:每个元素平方
power_res = np.power(arr, coeff) # 等价于 arr ** 2
print("数组平方:\n", power_res)
# 输出:
# [[ 1. 4. 9.]
# [16. 25. 36.]]# in-place运算(直接修改原数组,节省内存)
arr *= coeff # 等价于 np.multiply(arr, coeff, out=arr)
print("in-place乘法后原数组:\n", arr)
# 输出:
# [[ 2. 4. 6.]
# [ 8. 10. 12.]]
5.2 数组与数组之间的运算
数组与数组的运算分为元素级运算(Hadamard积)和矩阵运算(点积),前者要求数组形状完全一致,后者要求前数组列数等于后数组行数。
1. 元素级运算(Hadamard积)
对应位置元素逐一运算,形状必须完全相同,运算规则与“数组-系数”运算一致。
arr1 = np.array([[1,2,3], [4,5,6]])
arr2 = np.array([[7,8,9], [10,11,12]])# 元素级加法
add_res = arr1 + arr2
print("元素级加法:\n", add_res)
# 输出:
# [[ 8 10 12]
# [14 16 18]]# 元素级乘法(非矩阵乘法)
mul_res = arr1 * arr2
print("元素级乘法:\n", mul_res)
# 输出(1*7, 2*8, ..., 6*12):
# [[ 7 16 27]
# [40 55 72]]# 元素级比较运算(返回布尔数组)
cmp_res = arr1 > arr2
print("元素级比较:\n", cmp_res)
# 输出:
# [[False False False]
# [False False False]]
2. 矩阵运算(点积)
线性代数中的矩阵乘法,核心函数为np.matmul
(或@
运算符),规则:若A
是(m,n)
矩阵,B
是(n,p)
矩阵,则结果C
是(m,p)
矩阵,且C[i,j] = sum(A[i,k] * B[k,j])
(k
从0到n-1)。
# 2D数组(矩阵)点积
A = np.array([[1,2], [3,4]]) # shape: (2,2)
B = np.array([[5,6], [7,8]]) # shape: (2,2)# 方法1:np.matmul
dot1 = np.matmul(A, B)
# 方法2:@运算符(Python 3.5+)
dot2 = A @ B
print("矩阵点积:\n", dot1)
# 输出(按矩阵乘法规则计算):
# [[1*5+2*7, 1*6+2*8],
# [3*5+4*7, 3*6+4*8]]
# → [[19 22]
# [43 50]]# 错误示例:形状不兼容(A列数≠B行数)
C = np.array([[1,2,3], [4,5,6]]) # shape: (2,3)
try:np.matmul(A, C) # A列数2 ≠ C行数2?实际兼容(结果shape=(2,3))# 若C是(3,2),则A@C会报错(2≠3)
except ValueError as e:print("错误:", e)
5.3 广播(Broadcasting):形状不同数组的运算
广播是NumPy的核心特性,用于解决形状不同但兼容的数组运算问题,通过“虚拟复制”(逻辑上扩展数组,不实际占用内存)实现元素级运算,大幅简化代码并节省内存。
广播的核心规则(3条):
- 维度补齐:将两个数组的形状元组“左对齐”,在较短形状前补1,使维度数相同。
例:(3,) → (1,3)
,(2,3) → (2,3)
;(1,2,3) → (1,2,3)
,(4,1,3) → (4,1,3)
。 - 维度兼容检查:对补齐后的每个维度,若两个数组的维度大小相同或其中一个为1,则兼容;否则报错。
- 虚拟扩展:将维度大小为1的轴“复制”到与另一数组对应维度相同的大小,然后进行元素级运算。
广播示例:
# 示例1:1D数组与2D数组广播
arr1 = np.array([1,2,3]) # shape: (3,) → 补齐后(1,3)
arr2 = np.array([[4], [5], [6]]) # shape: (3,1) → 补齐后(3,1)
# 广播后:arr1→(3,3),arr2→(3,3),元素级加法
broadcast_res = arr1 + arr2
print("广播结果 shape:", broadcast_res.shape) # 输出: (3,3)
print("广播结果:\n", broadcast_res)
# 输出:
# [[5 6 7]
# [6 7 8]
# [7 8 9]]# 示例2:3D数组广播
arr3 = np.array([[[1,2]], [[3,4]]]) # shape: (2,1,2)
arr4 = np.array([[5,6], [7,8]]) # shape: (2,2) → 补齐后(1,2,2)
# 广播后:arr3→(2,2,2),arr4→(2,2,2),元素级乘法
broadcast_mul = arr3 * arr4
print("3D广播结果 shape:", broadcast_mul.shape) # 输出: (2,2,2)
print("3D广播结果[0,1]:", broadcast_mul[0,1]) # 输出: [14 16](2*7, 2*8)# 错误示例:维度不兼容
arr5 = np.array([[1,2],[3,4]]) # shape: (2,2)
arr6 = np.array([[5,6,7],[8,9,10]]) # shape: (2,3)
try:arr5 + arr6 # 补齐后(2,2) vs (2,3),轴1大小2≠3,不兼容
except ValueError as e:print("广播错误:", e) # 输出: operands could not be broadcast together with shapes (2,2) (2,3)
6. 矩阵高级运算与复合函数
6.1 矩阵乘积(线性代数运算)
除基础矩阵点积外,NumPy的np.linalg
模块提供了丰富的线性代数运算,涵盖矩阵求逆、行列式、特征值等,是科学计算的核心工具。
核心线性代数函数:
函数 | 功能 | 示例 |
---|---|---|
np.linalg.inv | 求方阵的逆矩阵(要求行列式≠0) | inv_A = np.linalg.inv(A) |
np.linalg.det | 计算方阵的行列式 | det_A = np.linalg.det(A) |
np.linalg.eig | 计算方阵的特征值与特征向量 | eig_vals, eig_vecs = np.linalg.eig(A) |
np.linalg.solve | 求解线性方程组Ax = b | x = np.linalg.solve(A, b) |
np.linalg.norm | 计算矩阵/向量的范数(如L2范数) | norm_A = np.linalg.norm(A, ord=2) |
示例:
# 1. 矩阵求逆与行列式
A = np.array([[1,2], [3,4]]) # 2阶方阵
det_A = np.linalg.det(A) # 计算行列式(1*4 - 2*3 = -2)
inv_A = np.linalg.inv(A) # 求逆矩阵
print("行列式det(A):", det_A) # 输出: -2.0
print("逆矩阵inv(A):\n", inv_A)
# 输出:
# [[-2. 1. ]
# [ 1.5 -0.5]]# 验证:A @ inv(A) ≈ 单位矩阵(浮点误差可忽略)
identity = A @ inv_A
print("A @ inv(A) ≈ 单位矩阵:\n", identity)
# 输出:
# [[1.00000000e+00 1.11022302e-16]
# [0.00000000e+00 1.00000000e+00]]# 2. 求解线性方程组 Ax = b
b = np.array([5, 11]) # 方程组:1x1 + 2x2 =5;3x1 +4x2=11
x = np.linalg.solve(A, b) # 求解x1, x2
print("方程组解x:", x) # 输出: [1. 2.](x1=1, x2=2)
print("验证A@x == b:", np.allclose(A @ x, b)) # 输出: True(验证正确)
6.2 复合函数
复合函数指多个NumPy函数的组合使用,或自定义函数通过“向量化”适配数组运算。NumPy内置函数均为向量化函数(直接作用于数组所有元素,无需循环),效率远高于Python原生循环。
1. 内置函数复合示例
NumPy提供三角函数、指数对数、聚合函数等,可直接组合使用:
arr = np.array([1, 2, 3, 4], dtype=np.float64)# 复合1:指数→正弦→平方
comp1 = np.sin(np.exp(arr)) ** 2
print("exp→sin→平方:", np.round(comp1, 4)) # 输出: [0.9093 0.0141 0.9999 0.9854]# 复合2:绝对值→对数→均值(避免log(0)或负数值)
arr2 = np.array([-5, 0, 5, 10])
comp2 = np.mean(np.log(np.abs(arr2) + 1)) # +1避免log(0)
print("abs→log→均值:", np.round(comp2, 4)) # 输出: 1.5601
2. 自定义函数向量化
通过np.vectorize
将普通Python函数转为向量化函数,可直接作用于数组(注意:np.vectorize
本质是循环,效率低于内置函数,仅用于简单场景)。
# 自定义Python函数(仅支持标量输入)
def custom_func(x):if x > 0:return x ** 2 + np.sin(x)else:return -x + np.cos(x)# 转为向量化函数
vec_func = np.vectorize(custom_func)# 作用于数组
arr = np.array([-2, -1, 0, 1, 2])
res = vec_func(arr)
print("自定义向量化函数结果:", np.round(res, 4))
# 输出: [3.5838 1.5403 1. 1.8415 4.9093]
3. 聚合函数复合
聚合函数(如sum
、mean
、max
)沿指定轴压缩数组维度,可与元素级函数复合使用:
arr_2d = np.array([[1,2,3], [4,5,6], [7,8,9]])# 复合:每行元素平方后求和
row_sum_sq = np.sum(arr_2d ** 2, axis=1)
print("每行平方和:", row_sum_sq) # 输出: [14 77 194]# 复合:每列元素正弦后求最大值
col_max_sin = np.max(np.sin(arr_2d), axis=0)
print("每列正弦最大值:", np.round(col_max_sin, 4)) # 输出: [0.6570 0.9894 0.4121]
7. 布尔型数组
布尔型数组(dtype为bool
)是元素仅为True
(1)或False
(0)的特殊数组,主要用于条件筛选、计数统计和逻辑运算,是NumPy数据清洗的核心工具。
7.1 布尔数组的创建
常见创建方式包括元素级比较运算和特殊检测函数:
arr = np.array([1, 2, 3, 4, 5, 6])
arr2 = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])# 1. 比较运算创建
bool1 = arr > 3 # 元素>3 → [False False False True True True]
bool2 = arr2 % 2 == 0 # 偶数元素 → [[False True False], [ True False True], [False True False]]# 2. 特殊检测函数创建
arr3 = np.array([1.0, np.nan, 3.0, np.inf, -np.inf])
bool3 = np.isnan(arr3) # 检测NaN → [False True False False False]
bool4 = np.isinf(arr3) # 检测无穷大 → [False False False True True]print("bool2:\n", bool2)
# 输出:
# [[False True False]
# [ True False True]
# [False True False]]
7.2 布尔数组的核心用途
1. 条件筛选(布尔索引)
通过布尔数组筛选出True
对应的元素,返回1D数组(副本):
arr = np.array([10, 20, 30, 40, 50])
bool_mask = (arr > 20) & (arr < 50) # 20<元素<50 → [False False True True False]
filtered = arr[bool_mask]
print("筛选结果:", filtered) # 输出: [30 40]# 2D数组筛选(保留满足条件的行)
arr2 = np.array([[1,2,3], [4,5,6], [7,8,9]])
row_mask = arr2.sum(axis=1) > 10 # 行求和>10 → [False True True]
filtered_rows = arr2[row_mask]
print("筛选行结果:\n", filtered_rows)
# 输出:
# [[4 5 6]
# [7 8 9]]
2. 计数与逻辑判断
通过np.sum
(统计True
数量,因True=1
、False=0
)、np.any
(存在至少一个True
返回True
)、np.all
(所有元素为True
返回True
)实现统计与判断:
arr = np.array([1, 2, 3, 4, 5, 6, 7, 8])
bool_mask = arr % 2 == 0 # 偶数掩码 → [F T F T F T F T]# 统计True数量(偶数个数)
true_count = np.sum(bool_mask)
print("偶数个数:", true_count) # 输出: 4# 逻辑判断:是否存在偶数(any)
has_even = np.any(bool_mask)
print("存在偶数:", has_even) # 输出: True# 逻辑判断:是否全为偶数(all)
all_even = np.all(bool_mask)
print("全为偶数:", all_even) # 输出: False# 2D数组逻辑判断(按行/列)
arr2 = np.array([[1,2,3], [4,5,6], [7,8,9]])
row_has_even = np.any(arr2 % 2 == 0, axis=1) # 每行是否有偶数
print("每行是否有偶数:", row_has_even) # 输出: [True True True]
3. 布尔运算组合
通过&
(与)、|
(或)、~
(非)实现多条件组合(注意:需用括号包裹单个条件):
arr = np.array([1, 2, 3, 4, 5, 6])
# 条件1:元素>2;条件2:元素<5 → 组合条件:2<元素<5
comb_mask = (arr > 2) & (arr < 5)
print("2<元素<5的结果:", arr[comb_mask]) # 输出: [3 4]# 条件1:元素是偶数;条件2:元素>4 → 组合条件:偶数或>4
comb_mask2 = (arr % 2 == 0) | (arr > 4)
print("偶数或>4的结果:", arr[comb_mask2]) # 输出: [2 4 5 6]# 非运算:排除偶数
not_even = ~(arr % 2 == 0)
print("非偶数结果:", arr[not_even]) # 输出: [1 3 5]
8. 数组与张量(相同点和不同点)
在数值计算领域,“数组”(NumPy ndarray)和“张量”(Tensor)是两个密切相关但不完全等同的概念,需明确两者的联系与区别,避免混淆。
8.1 核心定义
- NumPy数组(ndarray):NumPy库中的多维数据容器,支持同构数据存储、向量化运算和基础维度操作,是Python中“低维张量”的实现载体(通常指0D-4D)。
- 张量(Tensor):数学概念,指“任意维度的数组”,可描述0D(标量)、1D(向量)、2D(矩阵)、3D及以上的高维数据,在深度学习框架(如TensorFlow、PyTorch)中被扩展为支持自动微分、GPU加速的计算单元。
8.2 相同点
相同点 | 说明 | 示例 |
---|---|---|
数据存储结构 | 均为“维度-元素”的层级结构,用shape 描述各维度大小,支持同构数据(元素类型一致) | NumPy数组shape=(2,3) 与PyTorch张量shape=(2,3) 均表示2行3列的二维数据 |
基础维度操作 | 均支持转置、切片、拼接、分裂等维度调整操作,语法高度相似 | NumPy的np.transpose 与PyTorch的torch.transpose 功能一致 |
元素级运算 | 均支持向量化的元素级运算(加法、乘法等),无需循环 | NumPy的arr1 + arr2 与TensorFlow的tf.add(arr1, arr2) 均为元素级加法 |
广播机制 | 低维张量(如NumPy数组)与高维张量均支持广播规则,解决形状不兼容的运算问题 | NumPy数组(3,1) 与PyTorch张量(1,3) 可通过广播实现(3,3) 的元素级运算 |
示例:相同操作对比
# NumPy数组操作
np_arr1 = np.array([[1,2],[3,4]])
np_arr2 = np.array([[5,6],[7,8]])
np_transpose = np.transpose(np_arr1) # 转置
np_add = np_arr1 + np_arr2 # 元素级加法# PyTorch张量操作(与NumPy语法相似)
import torch
torch_tensor1 = torch.tensor([[1,2],[3,4]])
torch_tensor2 = torch.tensor([[5,6],[7,8]])
torch_transpose = torch.transpose(torch_tensor1, 0, 1) # 转置
torch_add = torch_tensor1 + torch_tensor2 # 元素级加法print("NumPy转置:\n", np_transpose)
print("PyTorch转置:\n", torch_transpose)
# 两者输出一致:
# [[1 3]
# [2 4]]
8.3 不同点
对比维度 | NumPy数组(ndarray) | 张量(以深度学习框架为例) |
---|---|---|
维度范围 | 主要支持0D-4D,高维(如5D+)操作较少见,效率较低 | 天然支持高维(如5D、6D),适配深度学习场景(如视频数据:batch×time×C×H×W) |
计算加速 | 仅支持CPU计算,无内置GPU加速能力(需依赖其他库如CuPy) | 原生支持GPU加速,可通过cuda() 方法将张量转移到GPU,大幅提升高维计算效率 |
自动微分 | 无自动微分功能,需手动推导梯度(不适用于深度学习训练) | 核心特性:支持自动微分(如PyTorch的requires_grad=True 、TensorFlow的自动梯度带),是反向传播的基础 |
动态性 | 静态数据结构,创建后维度和数据类型需显式修改 | 部分框架支持动态张量(如PyTorch),可动态调整形状;TensorFlow 2.x支持动态图,更灵活 |
应用场景 | 适用于科学计算、数据分析、传统机器学习(如数据预处理、特征工程) | 适用于深度学习(如神经网络训练、图像/视频处理、自然语言处理) |
示例:张量特有的GPU加速与自动微分
# 1. GPU加速(PyTorch张量)
torch_tensor = torch.tensor([[1,2],[3,4]]).cuda() # 转移到GPU
print("GPU张量设备:", torch_tensor.device) # 输出: cuda:0(表示第1块GPU)# 2. 自动微分(PyTorch张量)
x = torch.tensor([2.0], requires_grad=True) # 开启梯度追踪
y = x ** 2 + 3*x # 定义函数y = x² + 3x
y.backward() # 反向传播求导
print("x的梯度(dy/dx=2x+3):", x.grad) # 输出: tensor([7.])(2*2+3=7)# NumPy数组无此功能(报错)
try:np_x = np.array([2.0])np_x.requires_grad = True # NumPy数组无requires_grad属性
except AttributeError as e:print("NumPy错误:", e) # 输出: 'numpy.ndarray' object has no attribute 'requires_grad'
9. NumPy使用注意事项
在实际使用NumPy时,需注意以下常见问题,避免内存泄漏、运算错误或效率低下:
9.1 视图(View)与副本(Copy)的区别
- 视图:共享原数组内存,修改视图会同步修改原数组,创建时不占用额外内存(如
reshape
、切片)。 - 副本:独立内存空间,修改副本不影响原数组,创建时占用额外内存(如
astype
、花式索引、np.copy
)。
避坑建议:通过arr.base
判断是否为视图(视图的base
指向原数组,副本的base
为None
):
arr = np.array([1,2,3,4])
view = arr[1:3] # 切片→视图
copy = arr[[1,2]] # 花式索引→副本view[0] = 999 # 修改视图
print("原数组(视图修改影响):", arr) # 输出: [ 1 999 3 4]copy[0] = 888 # 修改副本
print("原数组(副本修改无影响):", arr) # 输出: [ 1 999 3 4]print("view.base:", view.base is arr) # 输出: True(视图)
print("copy.base:", copy.base is arr) # 输出: False(副本)
9.2 数据类型兼容性
- 整数转浮点数:安全(如
int32→float64
),但浮点数转整数会丢失小数部分(无四舍五入)。 - 低精度转高精度:安全(如
float16→float64
),高精度转低精度可能溢出(如int64→int8
,值超过-128~127会出错)。
避坑建议:转换前用np.iinfo
(整数)或np.finfo
(浮点数)查看类型范围,避免溢出:
# 浮点数转整数(丢失小数)
float_arr = np.array([1.9, 2.1])
int_arr = float_arr.astype(np.int32)
print("浮点数转整数:", int_arr) # 输出: [1 2](非四舍五入)# 高精度转低精度(溢出)
int64_arr = np.array([200], dtype=np.int64)
int8_arr = int64_arr.astype(np.int8) # int8范围-128~127
print("int64→int8溢出:", int8_arr) # 输出: [-56](溢出后错误值)# 查看类型范围
print("int8范围:", np.iinfo(np.int8).min, "~", np.iinfo(np.int8).max) # 输出: -128 ~ 127
9.3 广播规则的严格性
- 广播仅适用于“维度补齐后各维度大小相同或其中一个为1”的情况,否则会报错。
- 避免过度依赖广播,复杂广播逻辑可能降低代码可读性,建议显式用
np.expand_dims
调整维度。
避坑建议:运算前打印数组shape
,验证是否符合广播规则:
arr1 = np.array([[1,2],[3,4]]) # shape: (2,2)
arr2 = np.array([1,2,3]) # shape: (3,)
try:arr1 + arr2 # 补齐后(2,2) vs (1,3),轴1大小2≠3,不兼容
except ValueError as e:print("广播错误:", e) # 输出: operands could not be broadcast together with shapes (2,2) (3,)# 正确做法:显式调整维度
arr2_expand = np.expand_dims(arr2, axis=0) # shape: (1,3)
# 仍不兼容,需进一步调整为(2,3)(如重复数据)
arr2_tile = np.tile(arr2_expand, (2,1)) # shape: (2,3)
9.4 浮点数精度问题
- NumPy浮点数(如
float32
/float64
)存在精度误差,避免直接用==
判断浮点数相等。 - 建议用
np.allclose(a, b, rtol=1e-05, atol=1e-08)
判断浮点数是否接近(允许微小误差)。
避坑示例:
a = np.array([0.1 + 0.2]) # 0.1+0.2=0.30000000000000004(浮点数精度误差)
b = np.array([0.3])print("a == b:", a == b) # 输出: [False](直接判断错误)
print("np.allclose(a, b):", np.allclose(a, b)) # 输出: True(正确判断)
9.5 维度操作的元素总数守恒
reshape
、resize
等维度调整操作需保证“原数组元素总数 = 新数组元素总数”,否则报错。- 高维数组操作前,用
arr.size
查看元素总数,避免维度不匹配。
避坑示例:
arr = np.arange(6) # size=6,shape=(6,)
try:arr.reshape((2,4)) # 2*4=8≠6,元素总数不守恒
except ValueError as e:print("维度错误:", e) # 输出: cannot reshape array of size 6 into shape (2,4)# 正确做法:保证元素总数一致
arr_reshape = arr.reshape((2,3)) # 2*3=6,正确
9.6 线性代数运算的形状约束
- 矩阵点积(
np.matmul
)要求“前数组列数 = 后数组行数”,否则报错。 - 求逆矩阵(
np.linalg.inv
)仅适用于方阵且行列式≠0(非奇异矩阵),否则报错。
避坑示例:
A = np.array([[1,2],[3,4]]) # shape: (2,2)(方阵)
B = np.array([[1,2,3],[4,5,6]]) # shape: (2,3)# 矩阵点积(A列数2 = B行数2,正确)
dot_correct = np.matmul(A, B)
print("正确点积shape:", dot_correct.shape) # 输出: (2,3)# 错误:A行数2 ≠ B列数3,形状不兼容
try:dot_wrong = np.matmul(B, A) # B列数3 ≠ A行数2
except ValueError as e:print("点积错误:", e) # 输出: matmul: Input operand 1 has a mismatch in its core dimension 0