飞桨PaddlePaddle深度学习实战
上QQ阅读APP看书,第一时间看更新

1.3.1 NumPy操作

NumPy(Numerical Python extension)是一个第三方的Python包,用于科学计算。这个库的前身是1995年就开始开发的一个用于数组运算的库。经过了长时间的发展,它基本上成为绝大部分Python科学计算的基础包,当然也包括所有提供Python接口的深度学习框架。

1.基本模块

(1)array模块

数组(Array)是NumPy中最基础的数据结构。其最关键的属性是维度和元素类型,在NumPy中,可以非常方便地创建各种不同类型的多维数组,并且执行一些基本操作。在深度学习中,如果神经元之间的连接关系涉及的参数是数组,便可利用array模块进行设定,如代码清单1-1所示。

代码清单1-1 array的基本操作


import numpy as np

a = [1, 2, 3, 4]         # a是python中的list类型
b = np.array(a)          # 数组化之后b的类型变为 array
type(b)                  # b的类型 <type 'numpy.ndarray'>

b.shape                  # shape参数表示array的大小,这里是4
b.argmax()               # 调用arg max()函数可以求得array中的最大值的索引,这里是3
b.max()                  # 调用max()函数可以求得array中的最大值,这里是4
b.mean()                 # 调用mean()函数可以求得array中的平均值,这里是2.5

注意到在导入NumPy的时候,代码中将np作为NumPy的别名。这是一种习惯性的用法,后面的章节中也默认这么使用。在机器学习中常用到的矩阵的转置操作可以首先通过matrix构建矩阵,再用transpose函数来实现转置,如代码清单1-2所示。

代码清单1-2 NumPy中实现矩阵转置


import numpy as np
x=np.array(np.arange(12).reshape((3,4)))
 
'''
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]
'''
t = x.transpose()
'''
[[ 0  4  8]
 [ 1  5  9]
 [ 2  6 10]
 [ 3  7 11]] 
'''

对于一维的array,array支持所有Python列表(List)支持的下标操作方法,所以在此没有特别列出,如代码清单1-3所示。

代码清单1-3 NumPy基础数学运算


import numpy as np

# 绝对值,1
a = np.abs(-1)

# sin函数,1.0
b = np.sin(np.pi/2)

# tanh逆函数,0.50000107157840523
c = np.arctanh(0.462118)

# e为底的指数函数,20.085536923187668
d = np.exp(3)

# 2的3次方,8
f = np.power(2, 3)

# 点积,1*3+2*4=11
g = np.dot([1, 2], [3, 4])

# 开方,5
h = np.sqrt(25)

# 求和,10
l = np.sum([1, 2, 3, 4])

# 平均值,5.5
m = np.mean([4, 5, 6, 7])

# 标准差,0.96824583655185426
p = np.std([1, 2, 3, 2, 1, 3, 2, 0])

(2)random模块

NumPy中的随机模块包含了与随机数产生和统计分布相关的基本函数。Python本身也有随机模块random,不过NumPy的random功能更丰富,随机模块一般会用于深度学习中一些随机数的生成、seed的生成以及初始值的设定,具体的用法请看代码清单1-4。

代码清单1-4 random模块相关操作


import numpy as np

# 设置随机数种子
np.random.seed(42)

# 产生一个1x3,[0,1)之间的浮点型随机数
# array([[ 0.37454012,  0.95071431,  0.73199394]])
# 后面的例子就不在注释中给出具体结果了
np.random.rand(1, 3)

# 产生一个[0,1]之间的浮点型随机数
np.random.random()

# 从a中有放回地随机采样7个
a = np.array([1, 2, 3, 4, 5, 6, 7])
np.random.choice(a, 7)

# 从a中无放回地随机采样7个
np.random.choice(a, 7, replace=False)

# 对a进行乱序并返回一个新的array
b = np.random.permutation(a)
   
# 生成一个长度为9的随机bytes序列并作为str返回
# '\x96\x9d\xd1?\xe6\x18\xbb\x9a\xec'
np.random.bytes(9)

随机模块可以很方便地做一些快速模拟去验证结论,在神经网络中也能够做一些快速的网络构造。如考虑一个非常违反直觉的概率题:一个选手去参加一个TV秀,有三扇门,其中一扇门后有奖品,这扇门只有主持人知道。选手先随机选一扇门,但并不打开,主持人看到后,会打开其余两扇门中没有奖品的一扇门。然后主持人问选手:是否要改变一开始的选择?

这个问题的答案是应该改变一开始的选择。在第一次选择的时候,选错的概率是2/3,选对的概率是1/3。第一次选择之后,主持人相当于帮忙剔除了一个错误答案,所以如果一开始选的是错的,这时候换掉就对了;而如果一开始就选对了,则这时候换掉就错了。根据以上分析,一开始选错的概率就是换掉之后选对的概率(2/3),这个概率大于一开始就选对的概率(1/3),所以应该换。虽然道理上是这样的,但是通过推理仍不明白怎么办?没关系,用随机模拟就可以轻松得到答案。

这一部分请读者作为练习自行完成。

2.广播机制

对于array,默认执行对位运算。涉及多个array的对位运算需要array的维度一致,如果一个array的维度与另一个array的维度不一致,则在没有对齐的维度上分别执行对位运算,这种机制称为广播(Broadcasting),具体通过代码清单1-5理解。

代码清单1-5 广播机制的理解


import numpy as np

a = np.array([
    [1, 2, 3],
    [4, 5, 6]
])

b = np.array([
    [1, 2, 3],
    [1, 2, 3]
])

'''
维度一样的array,对位计算
array([[2, 4, 6],
       [5, 7, 9]])
'''
a + b

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

'''
广播机制让计算的表达式保持简洁
d和c的每一行分别进行运算
array([[ 3,  4,  5],
       [ 6,  7,  8],
       [ 9, 10, 11],
       [12, 13, 14]])
'''
c + d

3.向量化

读者在1.2.1节已经初步了解到,向量化在深度学习中的应用十分广泛,它是提升计算效率的主要手段之一,对于在机器学习中缩短每次训练的时间具有重要意义,当可用工作时间不变的情况下,更短的单次训练时间可以让程序员有更多的测试机会,进而更快、更好地调整神经网络的结构和参数。代码清单1-6~1-8通过一个矩阵相乘的例子展示了向量化对于代码计算速度的提升效果。

在代码清单1-6中首先导入了numpy和time库,它们分别被用于数学计算和统计运行时间。然后准备数据,这里初始化两个1000000维的随机向量v1和v2,v作为计算结果初始化为零。

代码清单1-6 导入库和数据初始化


import numpy as np
import time
#初始化两个1000000维的随机向量v1,v2用于矩阵相乘计算
v1 = np.random.rand(1000000)      
v2 = np.random.rand(1000000)
v = 0

在代码清单1-7中,设置变量tic和toc分别为计算开始和结束时间。在非向量化版本中,两个向量相乘的计算过程使用for循环实现。

代码清单1-7 矩阵相乘(非向量化版本)


#矩阵相乘-非向量化版本
tic = time.time()
for i in range(1000000):
    v += v1[i] * v2[i]
toc = time.time()
print("非向量化-计算时间:" + str((toc - tic)*1000)+"ms"+"\n")

在代码清单1-8中,同样使用变量tic和toc记录计算开始和结束时间。向量化版本使用NumPy库的numpy.dot()计算矩阵相乘。

代码清单1-8 矩阵相乘(向量化版本)


#矩阵相乘-向量化版本
tic = time.time()
v = np.dot(v1, v2)
toc = time.time()
print("向量化-计算时间:" + str((toc - tic)*1000)+"ms")

为了保证计算结果相同,我们输出了二者的计算结果,确保计算无误。最后的输出结果为“非向量化-计算时间为578.0208ms,向量化-计算时间为1.1038ms”。可以观察到效率提升效果十分显著。非向量化版本计算时间约为向量化版本计算时间的500倍。可见向量化对于计算速度的提升非常显著,尤其在长时间的深度学习训练中,向量化可以帮助开发者节省更多时间。