NumPy 科学计算入门
NumPy 是 Python 科学计算的基石——几乎所有数据科学、机器学习库(Pandas、PyTorch、TensorFlow、scikit-learn)都构建在它之上。本页带你从零掌握 ndarray 的核心用法。前置阅读:线性代数基础。
这个库是什么
Section titled “这个库是什么”NumPy(Numerical Python)提供了一个高性能的多维数组对象 ndarray,以及对其进行高效运算的函数集合。它是用 C 和 Fortran 编写的底层计算内核,Python 只是一层薄薄的接口——这就是它比纯 Python 快 10 到 100 倍的原因。
2005 年,Travis Oliphant 将 Numarray 和 Numeric 两个老库合并为 NumPy,从此成为 Python 科学计算生态的事实标准。你可以把它理解为 Python 世界里的”计算引擎”:所有上层框架的 Tensor 操作,底层最终都委托给类似 NumPy 的数组运算。截至 2025 年,NumPy 每月的下载量已超过 4 亿次,是 PyPI 生态中下载量排名前列的库之一。
2024 年里程碑:NumPy 2.0 于 2024 年 6 月正式发布——这是自 1.0 发布近 7 年来的第一个大版本。它引入了全新的字符串 dtype(
np.dtypes.StringDType)、重构的 C-API、默认真除法、增强的 SIMD(Single Instruction Multiple Data,单指令多数据流,即一条 CPU 指令同时处理多个数据的并行技术)自动向量化支持,以及对 Array API 标准(一个由 Data APIs 联盟推动的跨库数组操作接口规范)的持续完善。NumPy 2.x 系列(2.1 / 2.2 于 2024 年底至 2025 年初发布)在此基础上持续优化性能,特别是字符串操作和 ufunc(universal function,通用函数,即对数组每个元素独立施加同一运算的向量化函数)的执行速度。
安装与环境配置
Section titled “安装与环境配置”# 方式一:pip 单独安装pip install numpy
# 方式二:随数据科学全家桶安装(推荐新手)pip install numpy scipy pandas matplotlib scikit-learn
# 验证安装与版本python -c "import numpy as np; print(np.__version__)"# NumPy 2.x 用户应看到 2.1.0 或更高版本号NumPy 没有额外的系统依赖,Windows / macOS / Linux 均开箱即用。导入时约定俗成写为 import numpy as np,几乎所有教程和开源项目都遵循这一惯例。
从 1.x 迁移到 2.0 的注意点:NumPy 2.0 移除了大量已弃用的类型别名——
np.float、np.int、np.bool、np.complex等不再可用(需改为np.float64、np.int64等完整写法)。此外,异常类移到了np.exceptions命名空间下(如np.exceptions.AxisError)。如果你的旧代码报错,多半是这些改动引起的。
ndarray:统一类型的高性能多维数组
Section titled “ndarray:统一类型的高性能多维数组”把 Python 列表想象成一排可以放任何东西的”格子”——每个格子里既能放数字也能放字符串,灵活但低效(每个元素都是一个完整的 Python 对象,携带引用计数、类型指针等额外开销,通常占 28 字节以上)。而 ndarray 是一块连续的内存,里面只存同一种类型的数据,就像一排整齐的储物柜,每个柜子大小一模一样。
- 同质(homogeneous):数组中所有元素类型相同(如全是
float64,每个占 8 字节),这是性能的关键。CPU 可以利用缓存局部性(cache locality,即连续访问内存时 CPU 缓存命中率更高的特性)大幅加速。 - 维度(ndim):数组的轴数。一维是向量,二维是矩阵,三维及以上是张量(tensor)。
- 形状(shape):每个轴上的大小,如
(3, 4)表示 3 行 4 列。 - 数据类型(dtype):元素类型,如
int32、float64、bool。
为什么连续内存这么快? 数学上,一个 float64 数组 占据 字节的连续空间,基地址为 base,则第 个元素的地址为 ——一个乘法加法即可寻址。而 Python 列表存储的是对象指针,每个元素指向堆中分散的 PyObject,访问时需要额外的指针解引用和缓存失效。这就是同样存 100 万个浮点数,NumPy 约需 8 MB,Python 列表约需 36 MB,且访问速度慢一个数量级的原因。
import numpy as npimport sys
# 对比内存占用:存 100 万个浮点数py_list = [float(i) for i in range(1_000_000)]np_arr = np.array(py_list, dtype=np.float64)
print(f"Python 列表: {sys.getsizeof(py_list) / 1e6:.1f} MB(不含元素对象)")print(f"NumPy 数组: {np_arr.nbytes / 1e6:.1f} MB")print(f"压缩比: {sys.getsizeof(py_list) / np_arr.nbytes:.1f}x")广播机制(Broadcasting)
Section titled “广播机制(Broadcasting)”当两个数组形状不同时,NumPy 不会报错,而是自动”扩展”较小的数组使运算能够逐元素进行——这就是广播。类比理解:你有一个 3×4 的矩阵(12 个数字),想给每列加上一个不同的偏移量(4 个数字的一维数组)。广播会自动把这一维数组”虚拟复制”成 3 行(注意:并非真正复制,只在计算时模拟复制效果),然后逐元素相加,无需手写循环。
广播规则(从末尾对齐,逐轴比较):对于两个数组的每个维度,如果它们的形状满足以下条件之一,则可广播:
- 维度大小相等
- 其中一个维度大小为 1(将被拉伸)
例如形状 (3, 4) 与 (4,) 广播:末尾对齐后,(3, 4) 对 (1, 4),其中 4 == 4 ✓、第二轴 1 被拉伸为 3 ✓,结果为 (3, 4)。
矢量化(Vectorization)
Section titled “矢量化(Vectorization)”NumPy 的运算不是逐元素 Python 循环,而是整体一次性交由 C 层批量处理。写 a + b 而不是 for i in range(len(a)): a[i] + b[i],这就是矢量化——代码更短、更快、更接近数学公式。
性能直觉:在内部,a + b(两个含 100 万元素的数组)被翻译成一条 C 循环,编译器还可能通过 SIMD(单指令多数据流)让一条 CPU 指令同时处理 4-8 个 float64。而 Python for 循环每步要做类型检查、装箱拆箱、引用计数更新——慢了 50-100 倍是正常的。
import numpy as npimport time
n = 5_000_000a = np.random.randn(n)b = np.random.randn(n)
# 方式一:Python 循环(慢得离谱,仅演示)start = time.perf_counter()c_loop = np.empty(n)for i in range(n): c_loop[i] = a[i] + b[i]t_loop = time.perf_counter() - start
# 方式二:矢量化(推荐)start = time.perf_counter()c_vec = a + bt_vec = time.perf_counter() - start
print(f"Python 循环: {t_loop:.3f}s")print(f"NumPy 矢量化: {t_vec:.4f}s")print(f"加速比: {t_loop / t_vec:.0f}x")# 典型输出:Python 循环 ~2.5s,NumPy 矢量化 ~0.005s,加速 ~500x架构与工作流
Section titled “架构与工作流”例 1:数组的多种创建方式
Section titled “例 1:数组的多种创建方式”import numpy as np
# 从 Python 列表创建a = np.array([1, 2, 3, 4, 5])print(a.shape, a.dtype) # (5,) int64
# 创建特殊数组:全零、全一、等差、随机zeros = np.zeros((2, 3)) # 2行3列全零矩阵ones = np.ones(5) # 长度5的全一向量seq = np.arange(0, 10, 2) # [0, 2, 4, 6, 8] 等差序列linspace = np.linspace(0, 1, 5) # [0, 0.25, 0.5, 0.75, 1.0] 均匀分点rand = np.random.randn(3, 3) # 3x3 标准正态分布随机矩阵
print(f"zeros:\n{zeros}") # f-string 是推荐的格式化方式print(f"rand 均值={rand.mean():.3f}") # 随机矩阵的均值应接近 0
# 新版推荐:使用 Generator API(更现代、更快、可重现行更好)rng = np.random.default_rng(42) # 创建一个随机数生成器rand2 = rng.standard_normal((3, 3))print(f"Generator 均值={rand2.mean():.3f}")例 2:矢量化运算与广播
Section titled “例 2:矢量化运算与广播”import numpy as np
# 矢量化:数组与数组、数组与标量的逐元素运算a = np.array([1, 2, 3, 4])b = np.array([10, 20, 30, 40])print(a + b) # [11 22 33 44] —— 逐元素相加,无需循环print(a * 2) # [2 4 6 8] —— 标量广播到每个元素
# 广播:不同形状的数组运算matrix = np.array([[1, 2, 3], # shape (2, 3) [4, 5, 6]])offset = np.array([10, 20, 30]) # shape (3,) 自动广播成 (2, 3)print(matrix + offset)# [[11 22 33]# [14 25 36]] 每行都加上了 offset
# 通用函数(ufunc):对每个元素独立施加数学运算print(np.sqrt(a)) # [1. 1.414 1.732 2.]print(np.exp(a)) # e 的各次方例 3:索引、切片与条件筛选
Section titled “例 3:索引、切片与条件筛选”import numpy as np
a = np.arange(12).reshape(3, 4) # 3行4列矩阵print(a)# [[ 0 1 2 3]# [ 4 5 6 7]# [ 8 9 10 11]]
# 基础索引:行、列、子块print(a[1, 2]) # 第2行第3列 → 6print(a[0:2, 1:3]) # 前2行的第2~3列 → [[1,2],[5,6]]
# 花式索引:用整数数组一次性取多行print(a[[0, 2]]) # 第1行和第3行
# 布尔索引:按条件筛选元素mask = a > 5print(a[mask]) # [6 7 8 9 10 11] 所有大于5的元素print(a[a % 2 == 0]) # [0 2 4 6 8 10] 所有偶数
# ⚠️ 切片返回的是"视图"(view)而非副本,修改会影响原数组sub = a[0, :] # 视图sub[0] = 999print(a[0, 0]) # 999 —— 原数组被修改了!例 4:线性代数运算与 SVD 分解
Section titled “例 4:线性代数运算与 SVD 分解”线性代数是机器学习的数学语言。一个矩阵可以理解为”对向量的线性变换”——左乘矩阵就是把向量旋转、拉伸、投影。下面演示几种最常见的操作及其数学含义。
import numpy as np
# 矩阵乘法(用 @ 运算符,等价于 np.matmul)# 数学含义:(A @ B)ᵢⱼ = Σₖ Aᵢₖ · Bₖⱼ,即第 i 行与第 j 列的点积A = np.array([[1, 2], [3, 4]])B = np.array([[5, 6], [7, 8]])print(A @ B) # [[19 22], [43 50]]
# 常用线性代数运算print(np.linalg.inv(A)) # 矩阵求逆 A⁻¹,满足 A @ A⁻¹ = Iprint(np.linalg.det(A)) # 行列式 |A|,衡量线性变换的"体积缩放系数"print(np.linalg.eig(A)) # 特征值 λ 与特征向量 v,满足 A @ v = λv
# SVD 分解:将矩阵分解为 U @ Σ @ Vᵀ# 数学含义:任意矩阵 M(不必是方阵)都可分解为# M = U · Σ · Vᵀ# 其中 U(左奇异向量)和 V(右奇异向量)是正交矩阵,Σ 是对角矩阵(奇异值降序)。# SVD 在推荐系统、数据压缩(低秩近似)、PCA(主成分分析)中广泛应用M = np.random.randn(4, 3) # 4x3 随机矩阵U, S, Vt = np.linalg.svd(M) # 奇异值分解print(f"奇异值: {S}") # 降序排列的非负值
# 验证重构:M ≈ U @ diag(S) @ Vt(仅当 M 为 m×n 且取前 n 个分量时精确等式成立)reconstructed = U[:, :3] @ np.diag(S) @ Vt[:3, :]print(f"重构误差: {np.max(np.abs(reconstructed - M)):.2e}")
# 实用技巧:用 SVD 做低秩近似——只保留前 k 个最大奇异值k = 2 # 保留前 2 个分量,丢弃最小的 1 个M_low = U[:, :k] @ np.diag(S[:k]) @ Vt[:k, :]print(f"低秩近似误差: {np.max(np.abs(M_low - M)):.2e}")例 5:手写最小二乘法——用 NumPy 解线性回归
Section titled “例 5:手写最小二乘法——用 NumPy 解线性回归”线性回归是最基础的机器学习模型。给定数据矩阵 (形状 )和目标向量 ,我们想找到参数向量 ,使得预测值 最接近 。根据最小二乘法推导,最优解为:
这个公式被称为正规方程(Normal Equation)。下面我们用 NumPy 一步步实现:
import numpy as np
# 1. 构造模拟数据:y ≈ 2·x + 1 + 噪声np.random.seed(42)n, d = 100, 1X_raw = np.random.randn(n, d) * 2 # 100 个特征值true_theta = np.array([[2.0]]) # 真实斜率 = 2true_bias = 1.0 # 真实截距 = 1y = X_raw @ true_theta + true_bias + np.random.randn(n, 1) * 0.5 # 加高斯噪声
# 2. 添加偏置列:把 X 变为 [1, x],这样 θ 的第一项就是截距X = np.hstack([np.ones((n, 1)), X_raw]) # 形状 (100, 2)print(f"X shape: {X.shape}, y shape: {y.shape}")
# 3. 用正规方程求解:θ = (XᵀX)⁻¹ Xᵀy# 数学推导:最小化 ‖Xθ - y‖² → 对 θ 求导置零 → 得到此式XtX = X.T @ X # 2x2 矩阵Xty = X.T @ y # 2x1 向量theta = np.linalg.inv(XtX) @ Xty # 求逆 + 矩阵乘法
# 4. 更稳定的写法:用 np.linalg.solve(避免显式求逆,数值更稳定)theta_stable = np.linalg.solve(XtX, Xty)
print(f"求解结果(截距, 斜率): {theta.ravel()}")print(f"真实值: [{true_bias}, {true_theta.ravel()[0]}]")# 输出应接近 [1.0, 2.0]
# 5. 一步到位:NumPy 内置最小二乘法 lstsqtheta_lstsq, residuals, rank, sv = np.linalg.lstsq(X, y, rcond=None)print(f"lstsq 结果: {theta_lstsq.ravel()}")为什么推荐
solve而非inv? 计算逆矩阵 再乘以 的计算量是 且数值不稳定(条件数差的矩阵会放大浮点误差)。而np.linalg.solve(A, b)内部用 LU 分解(将矩阵分解为下三角 × 上三角),更高效也更准确。在工程实践中”不要显式求逆”是线性代数计算的金科玉律。
常用 API 速查
Section titled “常用 API 速查”| API | 用途 | 示例 |
|---|---|---|
np.array(list) | 从列表创建数组 | np.array([1,2,3]) |
np.zeros(shape) | 创建全零数组 | np.zeros((3,4)) |
np.ones(shape) | 创建全一数组 | np.ones(5) |
np.arange(start,stop,step) | 等差序列 | np.arange(0,10,2) |
np.linspace(a,b,n) | 均匀分点 | np.linspace(0,1,5) |
rng.standard_normal(shape) | 标准正态随机数(新 API) | rng.standard_normal((3,3)) |
rng.random(shape) | [0,1) 均匀分布随机数(新 API) | rng.random((5,)) |
arr.reshape(shape) | 改变形状 | arr.reshape(3,4) |
arr.T | 转置 | matrix.T |
np.concatenate | 拼接数组 | np.concatenate([a,b]) |
arr.sum(axis) | 沿轴求和 | arr.sum(axis=0) |
arr.mean(axis) | 沿轴求均值 | arr.mean(axis=1) |
np.dot(a,b) / a @ b | 矩阵乘法 | A @ B |
np.linalg.svd(M) | 奇异值分解 | U,S,Vt = np.linalg.svd(M) |
np.linalg.solve(A,b) | 解线性方程组 Ax=b(推荐) | np.linalg.solve(A, b) |
np.linalg.lstsq(X,y) | 最小二乘法 | θ, *_ = np.linalg.lstsq(X, y) |
np.linalg.inv(M) | 矩阵求逆(谨慎使用) | np.linalg.inv(A) |
np.argmax(arr) | 最大值索引 | np.argmax(scores) |
新随机数 API 提示:
np.random.seed()和np.random.randn()属于旧版 Legacy API,仍然可用但已不推荐。新代码请用rng = np.random.default_rng(seed)创建 Generator 对象,然后调用rng.standard_normal()、rng.random()、rng.normal()等方法——速度更快、统计性质更好、多线程安全性也更好。
- 优先矢量化,避免 Python 循环:对数组的逐元素操作,用
a + b或np.sqrt(a)而非for循环。如果发现自己在写for i in range(len(arr)),几乎一定有更好的写法。 - 注意 dtype 陷阱:整数数组除以整数在旧版中会截断(
np.array([1,2])/2得到[0,1])。需要浮点结果时先arr.astype(np.float64)。NumPy 2.0 已改为默认真除法,但仍需留意。 - 视图(view)与副本(copy):切片
a[0:3]返回的是视图——修改切片会影响原数组。需要独立副本时用a[0:3].copy()。这是最常见的隐式 bug 来源。判断方法:a[0:3].base is a为True说明是视图。 - 大数组注意内存:
np.zeros((10000, 10000), dtype=np.float64)占用约 800 MB。能用 float32 就别用 float64,省一半内存。 - 随机数种子:需要可复现结果时,新版推荐使用 Generator API:
rng = np.random.default_rng(42)。相比旧的np.random.seed(42),Generator 在多进程场景下不会互相干扰。 - 避免显式求逆:解方程 用
np.linalg.solve(A, b),不要写np.linalg.inv(A) @ b——前者更快更稳。
典型应用场景
Section titled “典型应用场景”- 机器学习数据预处理:特征矩阵就是 ndarray,所有 scikit-learn 模型的输入输出都是它。详见数据预处理。
- 神经网络底层计算:PyTorch / TensorFlow 的 Tensor 本质上是”带自动微分和 GPU 支持的 NumPy 数组”。理解 NumPy 就理解了深度学习 80% 的计算逻辑。详见反向传播。
- 图像处理:一张彩色图片就是一个
ndarray,形状为(H, W, 3),三个通道分别对应红绿蓝。PIL / OpenCV 读取的图片都可转为 ndarray 操作。 - 统计与数据分析:均值、方差、协方差、相关系数等统计量,NumPy 都有现成函数。详见概率论基础。
- 梯度计算的底层数学:梯度下降每一步的矩阵运算,底层都是 NumPy 在执行。详见梯度下降与优化器。
与同类工具对比
Section titled “与同类工具对比”| 特性 | NumPy | PyTorch Tensor | TensorFlow Tensor | JAX Array |
|---|---|---|---|---|
| 定位 | 通用科学计算 | 深度学习训练 | 深度学习训练 | 高性能可微计算 |
| GPU 加速 | 不支持 | 原生支持 | 原生支持 | 原生支持(TPU 也可) |
| 自动微分 | 不支持 | 内置 autograd(自动微分引擎,即自动计算梯度的系统) | tf.GradientTape | 内置 autograd |
| 不可变性 | 可变(原地修改) | 可变 | 可变 | 不可变(函数式) |
| API 风格 | — | 高度仿 NumPy | 高度仿 NumPy | 高度仿 NumPy |
| Array API 标准 | 2.0 起原生支持 | 支持(torch.array_api) | 实验性支持 | 原生遵循 |
| 适用场景 | 数据处理 / 线性代数 | 研究与生产 | 生产部署 | 研究与大规模训练 |
关键洞察:PyTorch、TensorFlow、JAX 都刻意模仿 NumPy 的 API 设计——学会 NumPy,迁移到这些框架几乎零成本。2023 年起,Data APIs 联盟推动的 Array API 标准正在让这些库的接口进一步统一:未来用 xp(通用 array API 命名空间)写的代码可以在 NumPy、PyTorch、CuPy、JAX 之间无缝切换,只需更换 xp = numpy 或 xp = torch 的后端绑定。
- NumPy 官方文档:numpy.org/doc — 最权威的参考,包含完整的 API 手册和入门教程,NumPy 2.0 迁移指南也在此处。
- NumPy 2.0 Release Notes:numpy.org/doc/stable/release/2.0.0-notes.html — 详列 2.0 大版本的所有重大改动与新增特性,升级必读。
- Array API Standard:data-apis.org/array-api — 跨库统一数组操作接口规范,是 2024-2025 年科学计算生态最重要的标准化进展之一。
- Travis Oliphant,「Guide to NumPy」(2006/2015 更新版):NumPy 作者本人编写的权威指南,深入讲解 ndarray 的内存模型和设计哲学。
- Jake VanderPlas,「Python Data Science Handbook」(第 2 版, 2025):免费在线书,前两章详尽覆盖 NumPy 实战技巧,配有大量可运行示例;第 2 版已更新到 NumPy 2.x。
- Nicolas Rougier,「100 NumPy Exercises」:100 道从入门到进阶的练习题,GitHub 上可找到,是巩固熟练度的最佳练习材料。
- Eli Bendersky,「From Python to NumPy」:在线免费书,专注于如何将”Python 思维”迁移为”NumPy 向量化思维”,对理解广播与矢量化极有帮助。