你知道吗?就在你刷短视频的几秒钟里,全球可能有数十亿次NumPy运算正在发生!(我亲眼见过用NumPy重构后,代码速度飙升50倍的奇迹)
当我第一次接触科学计算时,曾经天真地问:“Python列表不够用吗?”(哈!多么痛的领悟)直到处理百万级气象数据时,一个简单的列表运算让我的电脑卡了整整十分钟… 而NumPy只用了一眨眼的功夫!
for
循环在数值计算中慢到令人抓狂import numpy as np
import sys
py_list = [1,2,3,4]
np_array = np.array([1,2,3,4])
print(f"列表内存: {sys.getsizeof(py_list)} bytes") # 120 bytes
print(f"数组内存: {np_array.nbytes} bytes") # 16 bytes!
啊哈!看到这7.5倍的差距了吗? ndarray的秘密在于:
# 3D数据结构秒创建
tensor = np.arange(24).reshape(2,3,4)
# 轴交换比翻书还快
swapped = np.transpose(tensor, (2,0,1))
print(swaped.shape) # (4,2,3) 瞬间完成!
这是NumPy最反直觉却最强大的特性!看这个例子:
A = np.array([[1,2,3],
[4,5,6]]) # 2x3矩阵
B = np.array([10,20,30]) # 一维数组
print(A + B)
# 输出:
# [[11 22 33]
# [14 25 36]]
发生了什么魔法? NumPy自动把B “拉伸” 成了:
[[10 20 30]
[10 20 30]]
(划重点!)广播规则三要素:
做个简单实验就知道威力:
import time
size = 1000000
py_list1 = list(range(size))
py_list2 = list(range(size))
# Python原生循环
start = time.time()
result = [a*b for a,b in zip(py_list1, py_list2)]
print(f"列表耗时: {time.time()-start:.5f}秒")
# NumPy矢量运算
np_arr1 = np.arange(size)
np_arr2 = np.arange(size)
start = time.time()
result = np_arr1 * np_arr2 # 注意这里没有循环!
print(f"NumPy耗时: {time.time()-start:.5f}秒")
测试结果(我的Mac M1):
处理超过内存的大数据集?NumPy早有准备:
big_data = np.memmap('huge_data.bin', dtype=np.float32,
mode='r+', shape=(100000, 100000))
# 像操作普通数组一样切片操作
sub_matrix = big_data[5000:6000, 20000:30000]
(文件虽在磁盘,操作如同内存!)
复杂张量运算的终极武器:
# 矩阵乘法常见写法
C = np.dot(A, B)
# Einstein summation写法
C = np.einsum('ij,jk->ik', A, B)
# 更复杂的双线性运算
result = np.einsum('ijk,kl,lmn->ijmn', tensor1, tensor2, tensor3)
优势在哪? 避免中间变量,编译器级优化!(复杂运算速度提升2-3倍很常见)
处理混合类型数据的神器:
# 定义股票数据类型
dtype = [('timestamp', 'datetime64[s]'),
('price', 'float32'),
('volume', 'int32')]
# 创建数组
stock_data = np.array([
('2023-01-01T09:30:00', 152.3, 10000),
('2023-01-01T09:31:00', 152.7, 15000)
], dtype=dtype)
# 按字段查询
high_price = stock_data[stock_data['price'] > 152.5]
比Pandas轻量! 特别适合高频金融数据处理
from PIL import Image
import numpy as np
# 图像本质就是三维数组!
img = np.array(Image.open('photo.jpg')) # shape: (height, width, channels)
# 灰度转换(加权平均)
gray = np.dot(img[...,:3], [0.2989, 0.5870, 0.1140]).astype(np.uint8)
# 边缘检测(Sobel算子)
sobel_x = np.array([[-1,0,1], [-2,0,2], [-1,0,1]])
edges_x = np.abs(np.convolve(gray.ravel(), sobel_x.ravel(), 'same').reshape(gray.shape))
(图像处理库OpenCV底层大量使用NumPy操作)
# 特征标准化
def normalize(X):
mean = np.mean(X, axis=0)
std = np.std(X, axis=0)
return (X - mean) / (std + 1e-8) # 防止除零
# One-Hot编码
def one_hot(y):
classes = np.unique(y)
return np.eye(classes.shape[0])[y]
sklearn的很多底层就是NumPy!
arr = np.arange(10)
view = arr[3:7] # 这是视图!共享内存
copy = arr[3:7].copy() # 这才是独立副本
view[0] = 999
print(arr) # [0 1 2 999 4 5 6 7 8 9] 原数据被改!
(我吃过这亏!调试两小时才发现)
a = np.array([32767], dtype=np.int16)
a += 1
print(a) # [-32768] !不是32768
解决方案: 大数运算切记得用np.int64
或float
out
参数:np.add(a, b, out=result)
np.s_
:arr[np.s_[::2]]
result = np.empty_like(a)
a *= 2
优于 a = a * 2
from numba import vectorize
@vectorize(['float32(float32, float32)'], target='parallel')
def fast_math(a, b):
return a**2 + b**3 # 编译成机器码!
(复杂循环运算速度可提升100倍+)
多年前我导师说过:“懂NumPy的人和不懂的人,写的是两种Python”。现在看来简直是真理!下次当你准备写
for
循环时——STOP! 先想想NumPy能不能矢量化解快问题。毕竟在科学计算的世界里,NumPy就是那把打开超能力大门的钥匙️
(附赠小测验:你能不用循环实现"找出数组中所有局部最大值"吗?提示:np.diff
和np.sign
组合有奇效!)