Skip to content

NumPy 科学计算入门

NumPy 是 Python 科学计算的基石——几乎所有数据科学、机器学习库(Pandas、PyTorch、TensorFlow、scikit-learn)都构建在它之上。本页带你从零掌握 ndarray 的核心用法。前置阅读:线性代数基础。

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,通用函数,即对数组每个元素独立施加同一运算的向量化函数)的执行速度。

Terminal window
# 方式一: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 数组 [x0,x1,…,xn−1][x_0, x_1, \ldots, x_{n-1}] 占据 8n8n 字节的连续空间,基地址为 base,则第 ii 个元素的地址为 base+8i\text{base} + 8i——一个乘法加法即可寻址。而 Python 列表存储的是对象指针,每个元素指向堆中分散的 PyObject,访问时需要额外的指针解引用和缓存失效。这就是同样存 100 万个浮点数,NumPy 约需 8 MB,Python 列表约需 36 MB,且访问速度慢一个数量级的原因。

import numpy as np
import 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")

当两个数组形状不同时,NumPy 不会报错,而是自动”扩展”较小的数组使运算能够逐元素进行——这就是广播。类比理解:你有一个 3×4 的矩阵(12 个数字),想给每列加上一个不同的偏移量(4 个数字的一维数组)。广播会自动把这一维数组”虚拟复制”成 3 行(注意:并非真正复制,只在计算时模拟复制效果),然后逐元素相加,无需手写循环。

广播规则(从末尾对齐,逐轴比较):对于两个数组的每个维度,如果它们的形状满足以下条件之一,则可广播:

  1. 维度大小相等
  2. 其中一个维度大小为 1(将被拉伸)

例如形状 (3, 4) 与 (4,) 广播:末尾对齐后,(3, 4) 对 (1, 4),其中 4 == 4 ✓、第二轴 1 被拉伸为 3 ✓,结果为 (3, 4)。

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 np
import time
n = 5_000_000
a = 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 + b
t_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
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}")
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 的各次方
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列 → 6
print(a[0:2, 1:3]) # 前2行的第2~3列 → [[1,2],[5,6]]
# 花式索引:用整数数组一次性取多行
print(a[[0, 2]]) # 第1行和第3行
# 布尔索引:按条件筛选元素
mask = a > 5
print(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] = 999
print(a[0, 0]) # 999 —— 原数组被修改了!

线性代数是机器学习的数学语言。一个矩阵可以理解为”对向量的线性变换”——左乘矩阵就是把向量旋转、拉伸、投影。下面演示几种最常见的操作及其数学含义。

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⁻¹ = I
print(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 解线性回归”

线性回归是最基础的机器学习模型。给定数据矩阵 XX(形状 n×dn \times d)和目标向量 yy,我们想找到参数向量 θ\theta,使得预测值 XθX\theta 最接近 yy。根据最小二乘法推导,最优解为:

θ=(X⊤X)−1X⊤y\theta = (X^\top X)^{-1} X^\top y

这个公式被称为正规方程(Normal Equation)。下面我们用 NumPy 一步步实现:

import numpy as np
# 1. 构造模拟数据:y ≈ 2·x + 1 + 噪声
np.random.seed(42)
n, d = 100, 1
X_raw = np.random.randn(n, d) * 2 # 100 个特征值
true_theta = np.array([[2.0]]) # 真实斜率 = 2
true_bias = 1.0 # 真实截距 = 1
y = 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 内置最小二乘法 lstsq
theta_lstsq, residuals, rank, sv = np.linalg.lstsq(X, y, rcond=None)
print(f"lstsq 结果: {theta_lstsq.ravel()}")

为什么推荐 solve 而非 inv? 计算逆矩阵 A−1A^{-1} 再乘以 bb 的计算量是 O(n3)O(n^3) 且数值不稳定(条件数差的矩阵会放大浮点误差)。而 np.linalg.solve(A, b) 内部用 LU 分解(将矩阵分解为下三角 × 上三角),更高效也更准确。在工程实践中”不要显式求逆”是线性代数计算的金科玉律。

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 在多进程场景下不会互相干扰。
  • 避免显式求逆:解方程 Ax=bAx = b 用 np.linalg.solve(A, b),不要写 np.linalg.inv(A) @ b——前者更快更稳。
  • 机器学习数据预处理:特征矩阵就是 ndarray,所有 scikit-learn 模型的输入输出都是它。详见数据预处理。
  • 神经网络底层计算:PyTorch / TensorFlow 的 Tensor 本质上是”带自动微分和 GPU 支持的 NumPy 数组”。理解 NumPy 就理解了深度学习 80% 的计算逻辑。详见反向传播。
  • 图像处理:一张彩色图片就是一个 ndarray,形状为 (H, W, 3),三个通道分别对应红绿蓝。PIL / OpenCV 读取的图片都可转为 ndarray 操作。
  • 统计与数据分析:均值、方差、协方差、相关系数等统计量,NumPy 都有现成函数。详见概率论基础。
  • 梯度计算的底层数学:梯度下降每一步的矩阵运算,底层都是 NumPy 在执行。详见梯度下降与优化器。
特性NumPyPyTorch TensorTensorFlow TensorJAX 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 向量化思维”,对理解广播与矢量化极有帮助。