吴老师这四节课,主要讲述了向量化计算。虽然在大多数情况中,for循环都适用,而且是最常用的方法之一,但是对于深度学习的计算,反而是向量化的方法更为适用。下面就来探究一下原因和实践一下吧。
2.11 向量化(Vectorization)
在上一次我们介绍逻辑回归时,曾经提到要计算表达式
Z
=
w
T
x
+
b
,
\Z = w^{T}x+b,
Z=wTx+b,其中的 w 和 x 分别为
n
x
\ n_x
nx维的列向量。如果不采用向量化的方法,那么 w 和 x 分别是两个庞大的数组,用循环的方法使它们一次一次完成相应的计算,计算次数多,而且耗时长;如若采用向量化的手段计算上述表达式,对于python来说,一个表达式(需要调用numpy模块的相应函数)就足够:z = np.dot(w,x)+b
比起既耗时又费力的for循环,向量化的方法显然会让我们的研究过程更轻松愉快。
下面是一个代码例子来比较两个方法的时长:
import numpy as np
import time
a = np.random.rand(1000000)#产生一个百万维的数组
b = np.random.rand(1000000)#同上
tic = time.time()#记录下当时的时间
c = np.dot(a,b)
toc = time.time()
print(c)
print("Vectorized version"+str(1000*(toc-tic))+"ms")#将时间扩大成毫秒级
c = 0
tic = time.time()
for i in range(1000000):
c += a[i]*b[i]
toc = time.time()
print(c)
print("For loop:"+str(1000*(toc-tic))+"ms")
输出结果为:
从这个例子看出,非向量化的方法,也就是for循环的方法,要比向量化的方法多花费了34倍向量化版本的时间。在需要大量数据的深度学习模型中,向量化是很必要的减少程序运行时间的方法。
PS:运行时间减少的原因:
CPU 和GPU 都有并行化的指令,有时候也叫做SIMD指令,意思是单指令流多数据流,如果你使用了这样的内置函数,比如np.function
或者别的可以去掉for循环的函数,这样的话,python中的numpy模块就可以充分利用并行化去做更快的计算。虽然GPU比较擅长SIMD计算,但CPU也不差。
所以我们以后需要注意的是:在你的指令里,尽量避免for循环。如果你能找到一个内置函数或者别的方式去代替for循环, 那么通常会比直接使用for循环更快。
2.12 向量化的更多例子
举例#1 如果你想计算向量
U
=
A
∗
v
 
\ U = A*v\,
U=A∗v(其中u,A,v均为向量矩阵),那么对于常规的for循环思维,就是采用两重循环,用表达式
U
[
i
]
+
=
A
[
i
]
[
j
]
∗
v
[
j
]
\ U[i] += A[i][j] *v[j]
U[i]+=A[i][j]∗v[j]来完成运算;对于向量化的计算,则只需一条指令解决:U = np.dot(A,v)
举例#2 假设你的内存中已经有一个v列向量,如果你想做作用到向量v的每个元素上的指数运算,那么你会怎么做呢?
左边是显然的for循环思路,而我们依旧可以用一条指令完成运算。numpy
内置了很多函数来帮助我们满足各种各样的运算需求,比如正在应用的np.exp()
就是计算指数运算的。他相对于左边,少了for循环,所需的运算时间也会少很多。
除此之外,numpy
还有很多常用向量化计算的函数,比如:
np.log(v)
np.abs(v)
np.maxnum(v,0)
v**2 #计算v中每个元素的平方
1/v #v中每个元素取倒数
所以python对向量是预设了很多种不同的运算的,这些运算基本都支持广播操作,所以python对于转换真的很人性化呀(
举例#3 logistic回归梯度下降算法实现
显然这是一个逻辑回归梯度下降算法的for循环实现(伪)代码,我们实际上需要更新所有的
d
w
1
,
d
w
2
…
…
d
w
n
 
\ dw_1,dw_2……dw_n\,
dw1,dw2……dwn, 但这里只写了一部分(逃
而对于向量化的改进方法,就是把
d
w
1
,
d
w
2
…
…
d
w
n
 
\ dw_1,dw_2……dw_n\,
dw1,dw2……dwn变成一个向量,
之后我们就不需要再对其用for循环更新,而直接采用
将其更新,最后,为了去掉最后一个for循环,我们只需
d
w
/
=
m
 
.
\ dw /= m\,.
dw/=m.
好了现在我们把三个for循环简化成一个for循环了。
本小节主要深化了向量化的概念,同时去掉一个for循环之后,你将会发现你的代码处理速度会快很多
2.13向量化 logistic回归
不使用任何的显式for循环,来实现对整个数据集的一步迭代
1.逻辑回归的正向传播步骤:
2.实现
首先我们定义一个X来作为你训练的输入,像这样在不同的列中堆叠在一起,X是一个
n
x
∗
m
 
\ n_x *m\,
nx∗m的矩阵。首先要做的是一步完成计算
z
(
1
)
,
z
(
2
)
,
…
…
z
(
m
)
 
\ z^{(1)},z^{(2)},……z^{(m)}\,
z(1),z(2),……z(m),事实上用了一行代码。
所以第一步是求
w
T
∗
x
1
,
w
T
∗
x
2
…
…
 
\ w^T*x_1,w^T*x_2……\,
wT∗x1,wT∗x2……
第二步是加上第二项 b,
则这个
1
∗
m
 
\ 1*m\,
1∗m的行向量的第一个元素,恰好是
Z
(
1
)
 
\ Z^{(1)}\,
Z(1)的定义,以此类推,所以我们就得到了关于Z的一个向量,并一步算出了所有的Z。
z在python中的表达式:
且由于python的广播操作,你不用担心实数b是否会不适用向量之间的加法。
接下来,我们来找一个办法来计算
a
(
1
)
,
a
(
2
)
,
…
…
a
(
m
)
 
\ a^{(1)},a^{(2)},……a^{(m)}\,
a(1),a(2),……a(m),调用sigmoid函数高效率地计算出激活函数的向量集合
本小节我们实现了不使用任何for循环,就实现了从m个训练样本中一次性计算出所有的z和a,所以这就是逻辑回归正向传播一步迭代的向量化实现,同时处理所有m个训练样本
2.14 向量化 逻辑回归的梯度输出
在之前你知道了如何通过向量化计算来计算整个训练集的预测值a,本小节会讲解如何用向量化计算来计算梯度
根据上一小节的经验,我们会定义一个向量来作为我们的输出,它是一个1*m的向量
我们已经学会了如何计算A和Y,所以dZ可以用一个式子来完成计算。
参考2.12的举例#3,我们最后还有一个for循环没有实现简化,现在我们来想办法去掉这个for循环
我们可以这么做来实现db的计算:
对于dw,我们可以这么做:
向量化导数的计算的分析就完成了。
下面我们来总结一下:
但如果你想把梯度下降的操作做多次迭代,比如1000次导数梯度下降,那么你仍旧需要一次最外层的for循环:
但每一次迭代中不使用for循环已经节约了大量的时间。