楼层: 首页/ 软件技术/ Python AI 数据科学/ NumPy:数值计算地基
2

NumPy:数值计算地基

NumPy Arrays & Broadcasting

为什么不用 Python 列表?因为慢。NumPy 把数据放在一块连续的内存里,底层是 C,你写一行 Python,它在底下跑一圈 C。处理十万行数据,Python 列表要几秒,NumPy 几毫秒。Pandas、PyTorch 全建立在 NumPy 之上,所以这关绕不过去。

ndarray:多维数组

import numpy as np # 创建数组 a = np.array([1, 2, 3, 4]) print(a) # [1 2 3 4] print(a.shape) # (4,) 形状 print(a.dtype) # int64 数据类型 print(a.ndim) # 1 几维 # 批量创建:zeros / ones / arange / linspace / random print(np.zeros((2, 3))) # 2行3列全0 print(np.arange(0, 10, 2)) # [0 2 4 6 8] 从0到10步长2 print(np.linspace(0, 1, 5)) # [0. 0.25 0.5 0.75 1. ] 5个等分点 print(np.random.rand(3)) # [0.xxxx 0.xxxx 0.xxxx] 0~1均匀分布 # 二维数组(矩阵) m = np.array([[1, 2], [3, 4]]) print(m.shape) # (2, 2)

索引、切片、布尔索引

a = np.array([10, 20, 30, 40, 50]) # 普通索引 print(a[0]) # 10 print(a[1:3]) # [20 30] # 布尔索引:用条件当"筛子" print(a > 25) # [False False True True True] print(a[a > 25]) # [30 40 50] 只要大于25的 # 花式索引:传一个索引数组 print(a[[0, 2, 4]]) # [10 30 50]

逐元素运算与广播

论广播(broadcasting)是什么

两个形状不同的数组相加,NumPy 会自动把小的那个"扩展"到大的形状——这就是广播。比如 (3,3) 的矩阵加一个 (3,) 的向量,向量会被"复制"三行。规则:从右往左对齐,维度为 1 的那一维会被自动扩展。这是 NumPy 最优雅的设计,也是最容易踩坑的地方。

# 逐元素运算:不用写循环 a = np.array([1, 2, 3]) b = np.array([10, 20, 30]) print(a + b) # [11 22 33] print(a * 2) # [2 4 6] 标量广播 print(a * b) # [10 40 90] 逐元素乘(不是矩阵乘!) # 矩阵乘法:@ 或 np.dot m1 = np.array([[1, 2], [3, 4]]) m2 = np.array([[5, 6], [7, 8]]) print(m1 @ m2) # [[19 22] # [43 50]] # 常用统计函数 x = np.array([1, 2, 3, 4, 5]) print(x.sum(), x.mean(), x.max(), x.min(), x.std()) # 15 3.0 5 1 1.414... print(x.argmax()) # 4 最大值的下标 # 重塑 reshape、转置 T、拼接 concatenate y = np.arange(12) # [0..11] z = y.reshape(3, 4) # 变成3行4列 print(z.T) # 转置成4行3列

性能对比:NumPy vs Python 循环

import time size = 1_000_000 py_list = list(range(size)) np_arr = np.arange(size) start = time.time() py_result = [x * 2 for x in py_list] print("Python 循环:", time.time() - start) start = time.time() np_result = np_arr * 2 print("NumPy 向量化:", time.time() - start) # Python 循环:约 0.05 秒 # NumPy 向量化:约 0.002 秒 快 25 倍

NumPy 进阶:线性代数与随机数

# 线性代数 v = np.array([3, 4]) print(np.linalg.norm(v)) # 5.0 向量长度 A = np.array([[1,2], [3,4]]) print(np.linalg.inv(A)) # 矩阵求逆 print(np.linalg.eig(A)) # 特征值特征向量 # 沿轴操作:axis=0 是跨行(每列),axis=1 是跨列(每行) m = np.array([[1, 2], [3, 4]]) print(m.sum(axis=0)) # [4 6] 每列求和 print(m.sum(axis=1)) # [3 7] 每行求和 # 随机数(新版 API,推荐) rng = np.random.default_rng(42) print(rng.integers(0, 10, size=5)) # 5个0-9随机整数 print(rng.normal(0, 1, 5)) # 标准正态分布采样 # 排序与唯一值 x = np.array([3, 1, 2, 3, 1]) print(np.sort(x)) # [1 1 2 3 3] print(np.unique(x)) # [1 2 3]

广播机制:完整案例

论广播三规则

① 维度少的那一方,前面补 1 直到维度对齐;② 某一维上两边要么相等,要么其中一边是 1,否则报错;③ 结果形状取每维的最大值。一句话:"从尾巴对齐,1 可以被拉长成任何值"。

# 案例1:标量广播 —— 标量对数组的每个元素 print(np.array([1, 2, 3]) * 10) # [10 20 30] # 案例2:向量(3,) 加到矩阵(2,3) 的每一行 mat = np.array([[1, 2, 3], [4, 5, 6]]) vec = np.array([10, 20, 30]) print(mat + vec) # [[11 22 33] # [14 25 36]] # 案例3:列向量(2,1) 加行向量(1,3) —— 生成(2,3) 矩阵 col = np.array([[1], [2]]) # shape (2,1) row = np.array([[10, 20, 30]]) # shape (1,3) print(col + row) # [[11 21 31] # [12 22 32]] # 案例4:(3,) + (3,1) = (3,3) —— 最经典的"形状意外" a = np.array([0, 1, 2]) # (3,) b = np.array([[100], [200], [300]]) # (3,1) print(a + b) # [[100 101 102] # [200 201 202] # [300 301 302]] # 案例5:常见错误 —— (3,) 和 (4,) 不能广播 # np.array([1,2,3]) + np.array([1,2,3,4]) # ValueError: operands could not be broadcast together with shapes (3,) (4,)

高级索引:布尔索引与花式索引

# 布尔索引:用一个布尔数组当"筛子",返回满足条件的元素(拷贝) data = np.array([10, -20, 30, -40, 50]) mask = data > 0 print(mask) # [ True False True False True] print(data[mask]) # [10 30 50] # 把所有负数替换成 0(非常常用) data[data < 0] = 0 print(data) # [10 0 30 0 50] # 多条件组合:&(与)、|(或)、~(非),每个条件要加括号 arr = np.arange(10) print(arr[(arr >= 3) & (arr <= 7)]) # [3 4 5 6 7] # 花式索引(fancy indexing):传一个整数数组选行/列 X = np.array([[1, 2], [3, 4], [5, 6], [7, 8]]) print(X[[0, 2, 3]]) # 取第0、2、3行 # [[1 2] # [5 6] # [7 8]] print(X[[0, 1], [ 0, 1]]) # 取 (0,0) 和 (1,1) # [1 4] # np.where:条件表达式,"三元运算符"的向量化版 scores = np.array([95, 55, 78, 40]) result = np.where(scores >= 60, "及格", "不及格") print(result) # ['及格' '不及格' '及格' '不及格']
视图 vs 拷贝:面试必考

切片(slice)是视图,花式索引和布尔索引是拷贝。视图共享内存,改视图会改原数组;拷贝是独立的。例:X = np.arange(10); y = X[1:4]; y[0] = 999 会把 X[1] 也改成 999;但 z = X[[1,2,3]] 改 z 不影响 X。想要显式拷贝用 .copy()。

线性代数子库 np.linalg

import numpy as np A = np.array([[4, 2], [1, 3]], dtype=np.float64) # 1. 矩阵乘法(@ 或 np.dot) B = np.array([[1, 0], [0, 1]]) print(A @ B) # [[4. 2.] # [1. 3.]] # 2. 转置 print(A.T) # [[4. 1.] # [2. 3.]] # 3. 逆矩阵(存在的条件:行列式非 0) A_inv = np.linalg.inv(A) print(np.round(A @ A_inv, 10)) # 应等于单位阵 # [[1. 0.] # [0. 1.]] # 4. 行列式 print(np.linalg.det(A)) # 10.0 # 5. 特征值与特征向量 eigvals, eigvecs = np.linalg.eig(A) print(eigvals) # [5. 2.] 特征值 print(np.round(eigvecs, 3)) # 每列是对应特征向量 # 6. SVD 奇异值分解(PCA、推荐系统、压缩都用它) U, S, Vt = np.linalg.svd(A) print(S) # 奇异值(降序) # 7. 解线性方程组 Ax = b b = np.array([1, 2]) x = np.linalg.solve(A, b) print(x) # [ 0.2 -0.6] 即 x=(A^-1)b # 8. 范数:L2 范数(向量长度)、L1 范数(绝对值和) v = np.array([3, 4]) print(np.linalg.norm(v)) # 5.0 print(np.linalg.norm(v, ord=1)) # 7.0

NumPy 章节面试题

面试 · NumPy

Q1. NumPy 里视图(view)和拷贝(copy)的区别?

查看答案

切片操作(如 arr[1:3])返回视图,共享底层内存,改视图会改原数组;花式索引 arr[[1,2,3]] 和布尔索引返回拷贝,改副本不影响原数组。需要独立副本时用 .copy()。

Q2. 广播机制的规则是什么?

查看答案

从右边开始逐维对齐;某一维上两边要么相等,要么其中一边长度为 1(自动扩展),否则报错。维度不同时把少的那个前面补 1。

Q3. * 和 @ 在 NumPy 里有什么区别?

查看答案

* 是逐元素乘(Hadamard 积);@ / np.dot / np.matmul 是矩阵乘法。搞混是经典 bug。

Q4. 为什么 arr.sum(axis=0) 是对列求和?

查看答案

axis=0 表示"沿着第 0 维走",也就是把第 0 维压缩掉,剩下的就是每一列的和;axis=1 同理压缩第 1 维得到每行和。记法:"axis 是被吃掉的那个维度"。

Q5. 怎么找数组里最大值的下标?

查看答案

arr.argmax() 返回展平后最大值的下标;多维时 np.unravel_index(arr.argmax(), arr.shape) 可以还原成多维坐标。