NumPy:科学计算基础
AI 的血液——矩阵和向量运算。神经网络的每一次计算都依赖 NumPy 的思想,PyTorch 的 Tensor 就是 GPU 加速版的 NumPy 数组。
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 就学了一半。
综合示例:手写 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}")