NumPy 核心运算:向量化与广播

在上一篇中,我们掌握了 NumPy 数组的索引与切片技巧,能够精准提取需要的数据。而 NumPy 真正的性能核心,在于向量化运算和广播机制------ 这两大特性彻底摆脱了 Python 循环的低效,让大规模数值计算速度提升百倍。本文将带你从原理到实践,吃透这两个核心概念。

一、向量化运算:摆脱循环,高效计算

向量化运算的本质是 直接对整个数组进行操作,而非单个元素循环处理。NumPy 的向量化运算由 C 语言底层实现,避开了 Python 解释器的开销,这是它比原生列表快的关键原因。

1. 算术向量化运算

NumPy 支持直接对数组进行加减乘除等算术运算,规则是 元素级运算(即两个数组对应位置的元素分别运算)。

复制代码
import numpy as np

# 创建两个一维数组
arr1 = np.array([1, 2, 3, 4])
arr2 = np.array([5, 6, 7, 8])

# 1. 标量与数组运算(广播的基础)
print(arr1 + 2)  # [3 4 5 6] → 所有元素加2
print(arr1 * 3)  # [3 6 9 12] → 所有元素乘3
print(arr1 **2)  # [ 1  4  9 16] → 所有元素平方

# 2. 数组与数组运算(元素级)
print(arr1 + arr2)  # [ 6  8 10 12]
print(arr1 - arr2)  # [-4 -4 -4 -4]
print(arr1 * arr2)  # [ 5 12 21 32] → 注意:这是元素积,不是矩阵乘法
print(arr1 / arr2)  # [0.2        0.33333333 0.42857143 0.5       ]

对比 Python 循环:计算 1000 万个数的平方,向量化运算的优势会极其明显。

复制代码
import time

# 生成1000万数据
n = 10_000_000
arr = np.arange(n)
lst = list(range(n))

# NumPy向量化运算
start = time.time()
arr_square = arr** 2
print(f"NumPy耗时:{time.time() - start:.4f} 秒")  # 约0.01秒

# Python列表循环
start = time.time()
lst_square = [x **2 for x in lst]
print(f"列表循环耗时:{time.time() - start:.4f} 秒")  # 约0.3秒(慢30倍)

2. 数学函数向量化

NumPy 提供了丰富的向量化数学函数,这些函数直接作用于数组的每个元素,无需循环。

python

运行

复制代码
arr = np.array([0, np.pi/2, np.pi])

# 三角函数
print(np.sin(arr))  # [0.0000000e+00 1.0000000e+00 1.2246468e-16]
print(np.cos(arr))  # [ 1.000000e+00  6.123234e-17 -1.000000e+00]

# 指数与对数
arr_exp = np.exp(arr)  # e的x次方
arr_log = np.log(arr + 1)  # 自然对数

# 绝对值、开方
print(np.abs(np.array([-1, -2, 3])))  # [1 2 3]
print(np.sqrt(arr1))  # [1.         1.41421356 1.73205081 2.        ]

3. 统计函数向量化

统计函数是数据分析的高频需求,NumPy 的统计函数支持指定计算轴,灵活实现行 / 列统计。核心参数 axis​ 的含义:

  • axis=0:沿列方向计算(对每列的所有行求统计值)

  • axis=1:沿行方向计算(对每行的所有列求统计值)

  • 不指定 axis:对整个数组的所有元素求统计值

    创建二维数组(3行4列)

    arr2d = np.array([[1,2,3,4],
    [5,6,7,8],
    [9,10,11,12]])

    1. 求和

    print(np.sum(arr2d)) # 78 → 所有元素和
    print(np.sum(arr2d, axis=0)) # [15 18 21 24] → 每列求和
    print(np.sum(arr2d, axis=1)) # [10 26 42] → 每行求和

    2. 均值、最大值、最小值

    print(np.mean(arr2d)) # 6.5 → 所有元素均值
    print(np.max(arr2d)) # 12 → 所有元素最大值
    print(np.min(arr2d, axis=1)) # [1 5 9] → 每行最小值

    3. 中位数、标准差、方差

    print(np.median(arr2d)) # 6.5 → 中位数
    print(np.std(arr2d)) # 3.452052529534663 → 标准差
    print(np.var(arr2d)) # 11.916666666666666 → 方差

    4. 最值索引

    print(np.argmax(arr2d)) # 11 → 最大值的扁平化索引
    print(np.argmax(arr2d, axis=0)) # [2 2 2 2] → 每列最大值的行索引

二、广播机制:不同形状数组的运算规则

在向量化运算中,我们发现标量可以直接和任意形状的数组运算,比如 arr + 2​。这背后的原理就是 NumPy 的广播机制------ 当两个数组形状不同时,NumPy 会自动扩展数组的形状,使其匹配后再进行元素级运算。

1. 广播的三大核心规则

NumPy 官方定义的广播规则,必须严格遵守:

  1. 维度对齐:比较两个数组的维度,从最右边的维度开始匹配;

  2. 缺失维度补 1:如果某个数组的维度数更少,则在其左侧补 1,直到维度数相同;

  3. 可扩展维度:对于对应维度,只有两种情况可以广播:

    • 维度大小相等;
    • 其中一个维度大小为1。
      若不满足,则抛出 ValueError: operands could not be broadcast together 错误。

2. 广播的典型场景

场景 1:标量与数组广播(最常用)

标量可以看作是形状为 ()​ 的数组,根据规则,会自动扩展为与目标数组相同的形状。

复制代码
arr = np.array([[1,2], [3,4]])  # shape: (2,2)
scalar = 2  # 扩展后 shape: (2,2)

print(arr + scalar)
# [[3 4]
#  [5 6]]

# 扩展过程示意:
# scalar → [[2,2], [2,2]] → 与arr元素级相加
场景 2:一维数组与二维数组广播

当一维数组的列数与二维数组的列数相等时,一维数组会沿行方向扩展。

复制代码
arr2d = np.array([[1,2,3], [4,5,6]])  # shape: (2,3)
arr1d = np.array([10,20,30])  # shape: (3,) → 补1后 shape: (1,3) → 扩展为 (2,3)

print(arr2d + arr1d)
# [[11 22 33]
#  [14 25 36]]

# 扩展过程示意:
# arr1d → [[10,20,30], [10,20,30]] → 与arr2d元素级相加
场景 3:维度大小为 1 的数组广播

当两个数组的维度数相同,但某一维度大小为 1 时,会沿该维度扩展。

复制代码
arr_a = np.array([[1], [2], [3]])  # shape: (3,1)
arr_b = np.array([[10,20]])  # shape: (1,2)

# 扩展过程:
# arr_a → shape (3,1) → 扩展为 (3,2) → [[1,1], [2,2], [3,3]]
# arr_b → shape (1,2) → 扩展为 (3,2) → [[10,20], [10,20], [10,20]]
print(arr_a + arr_b)
# [[11 21]
#  [12 22]
#  [13 23]]
场景 4:不兼容的广播(报错示例)
复制代码
arr1 = np.array([[1,2], [3,4]])  # shape: (2,2)
arr2 = np.array([[1,2,3], [4,5,6]])  # shape: (2,3)

# 尝试相加:最右维度 2 vs 3 → 不满足规则 → 报错
# print(arr1 + arr2)
# ValueError: operands could not be broadcast together with shapes (2,2) (2,3)

3. 广播的实战应用:数据标准化

数据标准化是机器学习预处理的必备步骤,公式为:

其中 μ 是均值,σ 是标准差。利用广播可以一行代码实现。

复制代码
# 生成3行4列的模拟数据(3个样本,4个特征)
data = np.random.randint(0, 100, size=(3, 4))
print("原始数据:")
print(data)

# 计算每列的均值和标准差
mu = np.mean(data, axis=0)  # shape: (4,)
sigma = np.std(data, axis=0)  # shape: (4,)

# 标准化:广播自动匹配维度
data_norm = (data - mu) / sigma
print("\n标准化后数据:")
print(data_norm)

三、矩阵运算:线性代数的核心

在数值计算中,矩阵乘法是高频需求,注意区分「元素级乘法」和「矩阵乘法」:

  • 元素级乘法:arr1 * arr2 或 np.multiply(arr1, arr2)
  • 矩阵乘法:arr1 @ arr2 或 np.dot(arr1, arr2) 或 np.matmul(arr1, arr2)

1. 矩阵乘法的规则

两个矩阵 A(形状 m x n )和 B(形状 n x p )相乘,结果矩阵 C的形状为 m x p ,且满足:

C_{i, j}=\\sum_{k=1}\^n A_{i, k} \\times B_{k, j}

**核心要求**:第一个矩阵的**列数**必须等于第二个矩阵的**行数**。

复制代码
# 创建两个可相乘的矩阵
A = np.array([[1,2], [3,4], [5,6]])  # shape: (3,2)
B = np.array([[7,8,9], [10,11,12]])  # shape: (2,3)

# 矩阵乘法的三种写法
print(A @ B)  # 推荐写法,Python 3.5+ 支持
# print(np.dot(A, B))
# print(np.matmul(A, B))
# 结果:
# [[ 27  30  33]
#  [ 61  68  75]
#  [ 95 106 117]]

2. 线性代数常用函数

NumPy 的 np.linalg​ 模块提供了丰富的线性代数工具:

复制代码
# 创建一个2x2矩阵
arr = np.array([[1,2], [3,4]])

# 1. 矩阵的逆(要求矩阵可逆)
arr_inv = np.linalg.inv(arr)
print(arr_inv)
# [[-2.   1. ]
#  [ 1.5 -0.5]]

# 2. 矩阵的行列式
det = np.linalg.det(arr)
print(det)  # -2.0000000000000004

# 3. 特征值与特征向量
eigenvalues, eigenvectors = np.linalg.eig(arr)
print("特征值:", eigenvalues)
print("特征向量:", eigenvectors)

# 4. 求解线性方程组 Ax = b
A = np.array([[1,1], [1,2]])
b = np.array([2,3])
x = np.linalg.solve(A, b)
print(x)  # [1. 1.] → 方程组的解

四、向量化与广播的性能优化技巧

  1. 优先使用 NumPy 内置函数:内置函数由 C 实现,比手动写 Python 循环快得多;
  2. 避免频繁创建临时数组:比如 (arr + 1) * 2 会生成两个临时数组,可合并为 arr * 2 + 2;
  3. 合理指定数据类型:用 dtype=np.float32 替代 float64,减少内存占用,提升运算速度;
  4. 利用广播减少循环:遇到「按行 / 列处理数据」的需求,优先考虑广播,而非 for 循环。

五、小结

  1. 向量化运算是 NumPy 的性能核心,直接对数组操作,避开 Python 循环开销;
  2. 广播机制是向量化运算的扩展,遵循「维度对齐、补 1、可扩展」三大规则,实现不同形状数组的运算;
  3. 矩阵乘法与元素级乘法的区别:@ 是矩阵乘法,* 是元素级乘法;
  4. 利用向量化和广播,可以高效实现数据标准化、线性代数运算等实战需求。

下一篇预告:《NumPy 常用工具:统计、排序、缺失值处理》------ 我们将聚焦实际数据处理中的高频需求,掌握统计分析、排序去重、缺失值处理的核心技巧。

相关推荐
muddjsv17 小时前
NumPy 实战:从基础到场景化应用
numpy
A尘埃1 天前
Numpy常用方法介绍
numpy
belldeep2 天前
python:mnist 数据集下载,parse
python·numpy·mnist
佛祖让我来巡山3 天前
Numpy
机器学习·数据分析·numpy·矢量运算
纪伊路上盛名在3 天前
Chap1-1 Numpy手搓神经网络—入门PyTorch
pytorch·深度学习·神经网络·numpy·工程化
癫狂的兔子4 天前
【Python】【NumPy】random.rand和random.uniform的异同点
开发语言·python·numpy
清水白石0084 天前
《深度剖析 Pandas GroupBy:底层实现机制与性能瓶颈全景解析》
开发语言·python·numpy
纪伊路上盛名在5 天前
Chap1:Neural Networks with NumPy(手搓神经网络理解原理)
python·深度学习·神经网络·机器学习·numpy·计算生物学·蛋白质
winfreedoms6 天前
ubuntu24.04安装numpy时报错error: externally-managed-environment解决办法
numpy