看到一篇比较完整深入讲解Numpy文章,摘要记录。
Numpy简介
Numpy 基本上全部讲的是向量化(或者矢量化)。如果熟悉python,学习numpy面临的最大问题应该是转变思考问题的方式。
举一个简单随机游走的例子。OOP的编程方式可能会定义一个RandomWalker的类,实现一个walk的方法,每走一步后返回当前的位置信息。
OOP实现
class RandomWalker:
def __init__(self):
self.position = 0
def walk(self, n):
self.position = 0
for i in range(n):
yield self.position # 这里用到了生成器
self.position += 2*random.randint(0, 1) - 1
这种实现方法不错,容易阅读,但是不高效。
# 在notebook 中测试
%%timeit
walker = RandomWalker()
walk = [position for position in walker.walk(1000)]
844 µs ± 4.41 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
# 机器不同测试时间会有所差异
过程式实现
对这个简单的问题,可以省去类的定义,只关注walk方法。
def random_walk(n):
position = 0
walk = [position]
for i in range(n):
position += 2*random.randint(0, 1)-1
walk.append(position) # 不同于生成器,一次返回整个列表
return walk
新的方法可以节省一些cpu开销,但不会太多,函数调用和面向对象实现中的方法调用基本相似。
测试结果:780 µs ± 11 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
矢量化实现
换个角度,随机游走实际是随机数的累加,可以一次性生成所有的随机step,然后累加出结果,省去所有循环。使用itertools累加模块可以提高代码效率。
import random
def random_walk_faster(n=1000):
from itertools import accumulate # Python 3.6以后的版本支持
steps = random.choices([-1,+1], k=n)
return [0]+list(accumulate(steps))
测试结果:143 µs ± 313 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
如果使用numpy,还有进一步优化的空间。
import numpy as np
def random_walk_fastest(n=1000):
steps = np.random.choice([-1,+1], n) # 从-1,1中选择,返回size 为n 的array
return np.cumsum(steps)
测试结果:24.8 µs ± 139 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
结果是计算效率数量级的提升!
小结 ---- 矢量化的重点 一次性计算 避免循环