CHAPTER 07

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。

PYTHON · 创建 ndarray
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 列表更强大,支持多维索引、布尔索引和花式索引,这在数据筛选中非常常用。

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 会自动"扩展"较小的数组。

PYTHON · 运算与广播
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 提供了高效的矩阵运算,理解这些是理解深度学习的基础。

PYTHON · 矩阵运算
# 矩阵乘法(@ 运算符,等同于 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                        # 转置

统计与常用函数

PYTHON · 统计函数与 reshape
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.shapetensor.reshape()tensor.mean() 都和 NumPy 一样。区别是 Tensor 可以在 GPU 上运算,并且支持自动微分。学好 NumPy,PyTorch 就学了一半。

综合示例:手写 Softmax 和损失函数

用 NumPy 实现深度学习中的核心运算,感受 NumPy 在 AI 中的真正用法:

PYTHON · Softmax 与交叉熵损失
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}")