NumPy:科学计算基础
AI 的血液——矩阵和向量运算。神经网络的每一次计算都依赖 NumPy 的思想,PyTorch 的 Tensor 就是 GPU 加速版的 NumPy 数组。
NumPy 核心术语
- ndarray(N 维数组) NumPy 的核心数据结构,存储同类型元素的多维数组。与 Python list 相比,ndarray 连续存储在内存中,支持 CPU SIMD 指令级向量化运算,速度快 100 倍以上。
- dtype(数据类型) ndarray 中所有元素的类型,如 float32、float64、int32。AI 训练通常使用 float32(32位浮点数),在精度和显存之间取得平衡。float16 或 bfloat16 用于混合精度训练,节省显存。
-
shape(形状)
数组各维度的大小,用 tuple 表示。
(32, 3, 224, 224)表示 32 张 RGB 224×224 图像(批次×通道×高×宽)。理解 shape 是读懂 AI 代码的关键。 -
轴(Axis)
数组的某个维度。axis=0 通常是批次维,axis=1 是通道或特征维。
arr.sum(axis=0)沿 axis=0 求和(跨样本求和),arr.sum(axis=1)沿 axis=1 求和(跨特征求和)。 - 广播(Broadcasting) 不同形状的数组进行运算时,NumPy 自动将较小的数组"扩展"以匹配较大数组的形状。规则:从右往左比较各维度大小,维度为 1 的可以扩展,维度为 n 的必须与另一数组相同。
-
视图(View)vs 副本(Copy)
切片操作
a[1:3]返回视图,修改视图会影响原数组;a[1:3].copy()返回真正的副本。不理解这个区别会导致难以追踪的 bug。
ndarray —— NumPy 的核心
NumPy 最重要的对象是 ndarray(N 维数组),它是一个同类型元素构成的多维数组。与 Python 列表不同,ndarray 中所有元素类型相同,运算速度快 100 倍以上,因为底层用 C 实现了向量化运算。
向量化(Vectorization)
NumPy 的运算是对整个数组一次性操作,而不是 Python 的逐元素循环。a + b(两个 1000 元素的数组相加)在 NumPy 中比 Python for 循环快约 100 倍。这就是为什么 AI 计算必须用 NumPy/Tensor。
import numpy as np
# 从列表创建
v = np.array([1, 2, 3, 4]) # 1D:向量
m = np.array([[1,2],[3,4]]) # 2D:矩阵
t = np.array([[[1,2],[3,4]], # 3D:张量(图像常见)
[[5,6],[7,8]]])
print(v.shape) # (4,) → 4 个元素的向量
print(m.shape) # (2, 2) → 2x2 矩阵
print(t.shape) # (2, 2, 2) → 三维张量
print(v.dtype) # int64
print(v.ndim) # 1(几维)
# 特殊创建方式
np.zeros((3, 4)) # 3x4 全0矩阵
np.ones((2, 3)) # 2x3 全1矩阵
np.eye(4) # 4x4 单位矩阵(对角线为1)
np.random.rand(3, 4) # [0,1) 随机矩阵,常用于初始化权重
np.random.randn(3, 4) # 标准正态分布随机矩阵
np.arange(0, 10, 2) # [0, 2, 4, 6, 8]
np.linspace(0, 1, 5) # [0, 0.25, 0.5, 0.75, 1.0](等间距)
AI 中的数据形状
AI 中处处是矩阵:一批 32 张 28×28 灰度图像 → shape (32, 28, 28);一批 16 张 224×224 彩色图像 → shape (16, 3, 224, 224)(批量、通道、高、宽)。理解 shape 是读懂 AI 代码的关键。
索引与切片
NumPy 的索引比 Python 列表更强大,支持多维索引、布尔索引和花式索引,这在数据筛选中非常常用。
a = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
# 基本索引
print(a[0, 1]) # 2 → 第0行第1列
print(a[1]) # [4, 5, 6] → 整行
print(a[:, 0]) # [1, 4, 7] → 整列
# 切片
print(a[:2, 1:]) # [[2,3],[5,6]] → 前两行、后两列
# 布尔索引(AI 中超常用!)
scores = np.array([0.9, 0.4, 0.8, 0.2, 0.95])
mask = scores > 0.7 # [True, False, True, False, True]
print(scores[mask]) # [0.9, 0.8, 0.95]
# 直接用条件筛选(更简洁)
print(scores[scores > 0.7]) # [0.9, 0.8, 0.95]
# 花式索引:用下标数组取行
data = np.random.rand(10, 5) # 10 个样本,每个 5 维
indices = [0, 3, 7] # 取第 0、3、7 个样本
batch = data[indices] # shape: (3, 5)
数组运算与广播
NumPy 的运算都是逐元素的。两个形状相同的数组直接相加,对应位置相加。神奇的地方在于广播(Broadcasting)机制:形状不完全相同的数组也能运算,NumPy 会自动"扩展"较小的数组。
a = np.array([1, 2, 3, 4])
b = np.array([10, 20, 30, 40])
# 逐元素运算
print(a + b) # [11, 22, 33, 44]
print(a * b) # [10, 40, 90, 160]
print(a ** 2) # [1, 4, 9, 16]
# 广播:标量自动扩展
print(a * 2) # [2, 4, 6, 8]
# 广播:向量与矩阵
m = np.array([[1,2,3],[4,5,6]]) # shape (2, 3)
row = np.array([10, 20, 30]) # shape (3,)
print(m + row)
# [[11, 22, 33], ← 每行都加了 [10, 20, 30]
# [14, 25, 36]]
# AI 场景:批量归一化(减去均值,除以标准差)
batch = np.random.rand(32, 128) # 32 个样本,128 维特征
mean = batch.mean(axis=0) # 每个特征的均值,shape (128,)
std = batch.std(axis=0) # 每个特征的标准差
normed = (batch - mean) / (std + 1e-8) # 广播:shape (32,128) op (128,)
print(normed.shape) # (32, 128)
线性代数:矩阵运算
神经网络的核心运算就是矩阵乘法:输入 × 权重矩阵 + 偏置。NumPy 提供了高效的矩阵运算,理解这些是理解深度学习的基础。
# 矩阵乘法(@ 运算符,等同于 np.matmul)
X = np.array([[1, 2], [3, 4]]) # (2, 2)
W = np.array([[0.5, 0], [0, 0.5]]) # (2, 2)
print(X @ W)
# [[0.5, 1.0],
# [1.5, 2.0]]
# 模拟神经网络一层的前向传播
batch_size = 4
input_dim = 3
output_dim = 2
x = np.random.randn(batch_size, input_dim) # (4, 3) 输入
W = np.random.randn(input_dim, output_dim) # (3, 2) 权重
b = np.zeros(output_dim) # (2,) 偏置
y = x @ W + b # (4, 2) 输出
print(f"输入: {x.shape}, 权重: {W.shape}, 输出: {y.shape}")
# 常用线性代数函数
np.linalg.norm(x) # 向量/矩阵的范数
np.linalg.det(X) # 行列式
np.linalg.inv(X) # 矩阵求逆
np.linalg.eig(X) # 特征值分解(PCA 的基础)
X.T # 转置
统计与常用函数
data = np.array([[1, 2, 3], [4, 5, 6]]) # shape (2, 3)
# 统计函数
print(data.sum()) # 21(所有元素之和)
print(data.sum(axis=0)) # [5, 7, 9] → 按列求和
print(data.sum(axis=1)) # [6, 15] → 按行求和
print(data.mean()) # 3.5
print(data.max(), data.min())# 6 1
print(data.argmax()) # 5 → 最大值在扁平化后的第5个位置
print(data.argmax(axis=1)) # [2, 2] → 每行最大值的列索引
# reshape:改变形状(不改变数据)
flat = np.arange(12) # [0,1,2,...,11] shape (12,)
mat = flat.reshape(3, 4) # shape (3, 4)
cube = flat.reshape(2, 2, 3) # shape (2, 2, 3)
back = mat.reshape(-1) # -1 自动计算,等于 flatten
# AI 中常见:展平图像送入全连接层
images = np.random.rand(32, 28, 28) # 32张 28×28 图像
flattened = images.reshape(32, -1) # (32, 784)
print(flattened.shape) # (32, 784)
NumPy 与 PyTorch Tensor 的关系
PyTorch 的 torch.Tensor 和 NumPy 的 ndarray 接口几乎完全相同:tensor.shape、tensor.reshape()、tensor.mean() 都和 NumPy 一样。区别是 Tensor 可以在 GPU 上运算,并且支持自动微分。学好 NumPy,PyTorch 就学了一半。
常见误区与边界情况
视图修改陷阱
NumPy 切片返回视图而非副本!b = a[1:3] 后修改 b[0] = 99 会同时修改 a[1]。需要独立副本时用 b = a[1:3].copy()。PyTorch 中同理,.detach() 返回视图,.detach().clone() 返回副本。
广播规则的常见误解
广播不是按列对齐,而是从右到左对齐维度。(3,) 和 (2,3) 可以广播(3对3),但 (3,) 和 (3,2) 则不行(3对2不匹配)。遇到形状不匹配的报错,先打印两个数组的 shape 来排查。
dtype 不匹配导致精度损失
NumPy 运算时,如果两个数组 dtype 不同,会自动提升为更高精度的类型(如 float32 + float64 → float64)。但在 PyTorch 中,float32 + float64 会报错。统一使用 np.float32 或 np.float64,不要混用。AI 训练模型权重通常用 float32 以平衡性能和精度。
import numpy as np
# 1. 指定 dtype 减少内存(AI 常用 float32)
weights = np.random.randn(1000, 1000).astype(np.float32)
print(f"float64: {weights.astype(np.float64).nbytes / 1e6:.1f} MB")
print(f"float32: {weights.nbytes / 1e6:.1f} MB") # 节省一半内存
# 2. 避免不必要的拷贝
a = np.ones((1000, 1000))
b = a[100:200, :] # 视图,无内存拷贝
c = a[100:200, :].copy() # 副本,需要内存
# 3. 用 np.einsum 做复杂矩阵运算(Attention 机制)
Q = np.random.randn(8, 64) # 8个 query,64维
K = np.random.randn(8, 64) # 8个 key,64维
# Attention score:Q @ K.T / sqrt(d_k)
scores = np.einsum('ij,kj->ik', Q, K) / np.sqrt(64)
print(scores.shape) # (8, 8) - 8x8 的注意力矩阵
# 4. 利用 np.where 做条件选择(比 if 快)
x = np.array([-2, -1, 0, 1, 2])
# 手动实现 ReLU
relu_x = np.where(x > 0, x, 0)
print(relu_x) # [0, 0, 0, 1, 2]
综合示例:手写 Softmax 和损失函数
用 NumPy 实现深度学习中的核心运算,感受 NumPy 在 AI 中的真正用法:
import numpy as np
def softmax(x):
"""将原始分数转换为概率分布"""
# 减去最大值防止数值溢出(数学上等价)
e_x = np.exp(x - x.max(axis=-1, keepdims=True))
return e_x / e_x.sum(axis=-1, keepdims=True)
def cross_entropy_loss(probs, labels):
"""交叉熵损失:衡量预测概率与真实标签的差距"""
batch_size = probs.shape[0]
# 取每个样本在正确类别上的概率
correct_probs = probs[np.arange(batch_size), labels]
# 负对数似然
loss = -np.log(correct_probs + 1e-8).mean()
return loss
# 模拟:4 个样本,分 3 类
logits = np.array([
[2.0, 1.0, 0.5], # 样本0,真实类别是0
[0.1, 3.0, 0.3], # 样本1,真实类别是1
[0.5, 0.2, 2.8], # 样本2,真实类别是2
[1.5, 0.8, 0.3], # 样本3,真实类别是0
])
labels = np.array([0, 1, 2, 0])
probs = softmax(logits)
loss = cross_entropy_loss(probs, labels)
print("概率分布:\n", probs.round(3))
print(f"交叉熵损失: {loss:.4f}")
# 预测类别
predicted = probs.argmax(axis=1)
print(f"预测: {predicted}, 真实: {labels}")
本章小结
NumPy 是 AI 计算的基础库,核心是 ndarray(N维数组)。关键概念:shape(数组形状)、dtype(元素类型)、axis(轴/维度)。核心操作:切片/索引(注意视图 vs 副本)、向量化运算(比循环快100倍)、广播(自动扩展形状)、矩阵乘法(@ 运算符)。PyTorch 的 Tensor 接口和 NumPy 几乎完全相同,区别是 Tensor 支持 GPU 和自动微分。常见陷阱:切片是视图(修改会影响原数组)、广播规则从右到左对齐、混合 dtype 会触发自动类型提升。