趋近智
一旦数据加载到 NumPy 数组中,接下来很自然地就是进行计算。NumPy 在科学计算和机器学习 (machine learning)中广受欢迎的主要原因之一是它能够对整个数组进行快速的矢量化 (quantization)操作,而无需显式的 Python for 循环。在此,我们分析 NumPy 如何处理数组计算,并介绍通用函数(ufuncs)的原理。
标准的 Python 算术运算符直接作用于 NumPy 数组,逐元素地执行操作。这是区分 NumPy 数组与 Python 列表的基本特性。
思考两个数组:
import numpy as np
arr1 = np.array([1, 2, 3, 4])
arr2 = np.array([10, 20, 30, 40])
# 逐元素加法
print(arr1 + arr2)
# Output: [11 22 33 44]
# 逐元素减法
print(arr2 - arr1)
# Output: [ 9 18 27 36]
# 逐元素乘法
print(arr1 * arr2)
# Output: [ 10 40 90 160]
# 逐元素除法
print(arr2 / arr1)
# Output: [10. 20. 30. 40.]
# 逐元素幂运算
print(arr1 ** 2)
# Output: [ 1 4 9 16]
这些操作会创建包含结果的新数组。操作在数组的对应元素之间执行。这种逐元素行为也适用于比较:
arr1 = np.array([1, 5, 3, 7])
arr2 = np.array([2, 4, 3, 8])
# 逐元素比较
print(arr1 > arr2)
# Output: [False True False False]
print(arr1 == arr2)
# Output: [False False True False]
这些比较返回布尔数组,这对于索引和条件逻辑非常有用,我们稍后会看到。
这里的主要优势是性能。NumPy 的操作是用 C 语言实现的,并在内存块上操作,这使得它们在数值计算方面比通过循环迭代 Python 列表显著更快。
尽管标准运算符很方便,但 NumPy 通过其通用函数(ufuncs)提供了一套更丰富的数学函数。ufunc 是一种对 ndarray 对象中的数据执行逐元素操作的函数。可以将它们视为简单函数的矢量化 (quantization)包装器,这些函数接受一个或多个标量输入并生成一个或多个标量输出。
通用函数大致可以分类为:
这些函数作用于单个数组,对每个元素执行操作。
arr = np.arange(1, 6) # 创建 [1 2 3 4 5]
print(arr)
# 每个元素的平方根
print(np.sqrt(arr))
# Output: [1. 1.41421356 1.73205081 2. 2.23606798]
# 每个元素的指数(e^x)
print(np.exp(arr))
# Output: [ 2.71828183 7.3890561 20.08553692 54.59815003 148.4131591 ]
# 自然对数(以 e 为底)
# 注意:log(0) 是 -inf,负数的 log 是 nan
arr_pos = np.array([1, np.e, np.e**2])
print(np.log(arr_pos))
# Output: [0. 1. 2.]
# 每个元素的正弦(假定为弧度)
angles = np.array([0, np.pi/2, np.pi])
print(np.sin(angles))
# Output: [0.0000000e+00 1.0000000e+00 1.2246468e-16] # 注意接近 0 时的浮点精度
# 绝对值
arr_neg = np.array([-1, 2, -3.5])
print(np.abs(arr_neg))
# Output: [1. 2. 3.5]
其他常见的一元通用函数包括 np.cos、np.tan、np.log10、np.ceil、np.floor、np.round、np.isnan(检查是否为非数字)和 np.isinf(检查是否为无穷大)。
这些函数接受两个数组作为输入(尽管广播,即下一节会讲到的内容,允许兼容的形状),并逐元素执行操作。许多二元通用函数与算术运算符相对应。
arr1 = np.array([1, 5, 3, 8])
arr2 = np.array([2, 4, 3, 7])
# 逐元素加法(等同于 arr1 + arr2)
print(np.add(arr1, arr2))
# Output: [ 3 9 6 15]
# 逐元素最大值
print(np.maximum(arr1, arr2))
# Output: [2 5 3 8]
# 逐元素最小值
print(np.minimum(arr1, arr2))
# Output: [1 4 3 7]
# 逐元素幂(有时等同于 arr1 ** arr2)
# 如果需要更复杂的底数/指数类型,请使用 np.power
print(np.power(arr1, arr2))
# Output: [ 1 625 27 2097152]
# 模数 / 余数
print(np.mod(arr1, arr2)) # 等同于 arr1 % arr2
# Output: [1 1 0 1]
其他有用的二元通用函数包括 np.subtract、np.multiply、np.divide、np.floor_divide、np.greater、np.less_equal、np.logical_and、np.logical_or 等。
通用函数在处理大型数组时效率优势变得明显。让我们比较一下使用 Python 循环和 NumPy 通用函数计算 0 到 999,999 之间数字的正弦的性能。
# 创建一个大型数组
large_arr = np.arange(1_000_000)
# 使用 Python 循环(仅为演示,速度慢!)
import math
result_loop = [0.0] * 1_000_000 # 预分配列表
# %timeit for i in range(1_000_000): result_loop[i] = math.sin(large_arr[i])
# 在典型硬件上:每次循环约 200-300 毫秒
# 使用 NumPy 通用函数
# %timeit result_ufunc = np.sin(large_arr)
# 在典型硬件上:每次循环约 5-10 毫秒
(注意:%timeit 是 IPython 的魔术命令。如果在标准 Python 脚本中运行,您将使用 timeit 模块进行性能测试。)
NumPy 通用函数通常快几个数量级,因为循环发生在高度优化、编译的 C 代码中,并在连续的内存块上操作,而不是为每个元素解释 Python 代码。这种速度对于处理机器学习 (machine learning)中常见的大型数据集非常重要。
通常,您需要根据数组值应用逻辑。使用通过比较生成的布尔数组以及 np.where 等函数,提供了一种强大、矢量化 (quantization)的方法来实现此目的,同时避免了缓慢的 Python 循环。
np.where 是三元表达式(x if condition else y)的矢量化等价物。它接受三个参数 (parameter):一个条件(布尔数组)和两个值或数组( 和 )。它返回一个新的数组,其中条件为 True 的位置从 中取元素,条件为 False 的位置从 中取元素。
# 示例数据:测量读数
readings = np.array([10, -5, 22, -8, 15, 0])
# 将负读数替换为 0,正读数保持不变
cleaned_readings = np.where(readings < 0, 0, readings)
print(cleaned_readings)
# Output: [10 0 22 0 15 0]
# 根据阈值分配类别标签
threshold = 12
labels = np.where(readings > threshold, 'High', 'Low')
print(labels)
# Output: ['Low' 'Low' 'High' 'Low' 'High' 'Low']
# 使用两个数组作为 x 和 y
arr_a = np.array([100, 200, 300, 400])
arr_b = np.array([1, 2, 3, 4])
condition = np.array([True, False, True, False])
result = np.where(condition, arr_a, arr_b)
print(result)
# Output: [100 2 300 4]
您也可以对布尔数组执行聚合操作:
bool_arr = np.array([True, False, True, False])
# 检查是否有任何元素为 True
print(np.any(bool_arr))
# Output: True
# 检查是否所有元素都为 True
print(np.all(bool_arr))
# Output: False
# 计算 True 元素的数量(True 评估为 1,False 评估为 0)
print(np.sum(bool_arr))
# Output: 2
这些条件和聚合工具使得复杂的、基于数组的逻辑能够简洁地表达并高效地执行。
熟练掌握数组计算和通用函数对于编写高效的 NumPy 代码非常必要。它们让您能够自然地表达计算并实现高性能,构成了更复杂操作以及与 Pandas 和 Scikit-learn 等库交互的基础。下一节将介绍广播,它将这些逐元素操作扩展到形状不完全相同的数组。
这部分内容有帮助吗?
© 2026 ApX Machine LearningAI伦理与透明度•