1.3 Python与向量化


上一节我们主要介绍了逻辑回归, 以输出概率的形式来处理二分类问题。我们介绍了逻辑回归的Cost function表达式,并使用**梯度下降算法来计算最小化Cost function时对应的参数 w w w b b b 。**通过计算图的方式来讲述了神经网络的正向传播和反向传播两个过程。本节课我们将来探讨Python和向量化的相关知识。

1.3.1 向量化(Vectorization)

深度学习算法中,数据量很大,在程序中应该尽量减少使用loop循环语句,而可以使用向量运算来提高程序运行速度

向量化(Vectorization)就是利用矩阵运算的思想,大大提高运算速度。例如下面所示在Python中使用向量化要比使用循环计算速度快得多。

import numpy as np
import time

a = np.random.rand(1000000) # 生成一个[0,1)之间的随机N维浮点数组。
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")

输出结果类似于:

250286.989866
Vectorized version:1.5027523040771484ms
250286.989866
For loop:474.29513931274414ms

从程序运行结果上来看,该例子使用for循环运行时间是使用向量运算运行时间的约 300 300 300 倍。因此,深度学习算法中,使用向量化矩阵运算的效率要高得多。

为了加快深度学习神经网络运算速度,可以使用比CPU运算能力更强大的GPU。事实上,GPU和CPU都有并行指令(parallelization instructions),称为Single Instruction Multiple Data(SIMD)。SIMD单指令多数据流,能够复制多个操作数,并把它们打包在大型寄存器的一组指令集。SIMD能够大大提高程序运行速度,例如python的numpy库中的内建函数(built-in function)就是使用了SIMD指令。相比而言,GPU的SIMD要比CPU更强大一些

1.3.2 更多向量化的例子(More Vectorization Examples)

上一部分我们讲了应该尽量避免使用for循环而使用向量化矩阵运算。在python的numpy库中,我们通常使用np.dot()函数来进行矩阵运算。

我们将向量化的思想使用在逻辑回归算法上,尽可能减少for循环,而只使用矩阵运算。值得注意的是,算法最顶层的迭代训练的for循环是不能替换的。而每次迭代过程对 J , d w , b J,dw,b J,dw,b 的计算是可以直接使用矩阵运算。

1.3.3 向量化logistic回归(Vectorizing Logistic Regression)

在1.2节介绍过,整个训练样本构成的输入矩阵 X X X 的维度是 ( n x , m ) (n_x,m) (nx,m) ,权重矩阵 w w w 的维度是 ( n x , m ) (n_x,m) (nx,m) b b b 是一个常数值,而整个训练样本构成的输出矩阵的 Y Y Y 维度为 ( 1 , m ) (1,m) (1,m) 。利用向量化的思想,所有 m m m 个样本的线性输出可 Z Z Z 以用矩阵表示:
Z = w T X + b Z=w^TX+b Z=wTX+b
在python的numpy库中可以表示为:

Z = np.dot(w.T, X) + b
A = sigmoid(Z)

其中, w . T w.T w.T 表示 w w w 的转置。这样,我们就能够使用向量化矩阵运算代替for循环,对所有 m m m 个样本同时运算,大大提高了运算速度。

扫描二维码关注公众号,回复: 13230604 查看本文章

1.3.4 向量化logistic回归的梯度输出(Vectorizing Logistic Regression’s Gradient Output)

再来看逻辑回归中的梯度下降算法如何转化为向量化的矩阵形式。对于所有 m m m 个样本, d Z dZ dZ 的维度是 ( 1 , m ) (1,m) (1,m) ,可表示为:
d Z = A − Y dZ=A-Y dZ=AY
d b db db 可表示为:
d b = 1 m ∑ i = 1 m d z ( i ) db=\frac{1}{m}\sum_{i=1}^{m}dz^{(i)} db=m1i=1mdz(i)
对应的程序为:

db = 1 / m * np.sum(dZ)

d w dw dw 可表示为:
d w = 1 m X ⋅ d Z T dw=\frac{1}{m}X\cdot dZ^T dw=m1XdZT
对应的程序为:

dw = 1 / m * np.dot(X, dZ.T)

这样,我们把整个逻辑回归中的for循环尽可能用矩阵运算代替,对于单次迭代,梯度下降算法流程如下所示:

Z = np.dot(w.T, X) + b
A = sigmoid(Z)
dZ = A - Y
dw = 1 / m * np.dot(X, dZ.T)
db = 1 / m * np.sum(dZ)

w = w - alpha * dw
b = b - alpha * db

其中,alpha是学习因子,决定 w w w b b b 的更新速度。上述代码只是对单次训练更新而言的,外层还需要一个for循环,表示迭代次数。

1.3.5 Python中的广播(Broadcasting in Python)

下面介绍使用python的另一种技巧:广播(Broadcasting)。python中的广播机制可由下面四条表示:

  • 让所有输入数组都向其中shape最长的数组看齐,shape中不足的部分都通过在前面加 1 1 1 补齐;
  • 输出数组的shape是输入数组shape的各个轴上的最大值;
  • 如果输入数组的某个轴和输出数组的对应轴的长度相同或者其长度为 1 1 1 时,这个数组能够用来计算,否则出错;
  • 当输入数组的某个轴的长度为 1 1 1 时,沿着此轴运算时都用此轴上的第一组值。
image-20211008144515358

值得一提的是,在python程序中为了保证矩阵运算正确,可以使用reshape()函数来对矩阵设定所需的维度。这是一个很好且有用的习惯。

1.3.6 关于python/numpy向量的说明(A note on python /numpy vectors)

接下来我们将总结一些python的小技巧,避免不必要的code bug。

python中,如果我们用下列语句来定义一个向量:

a = np.random.randn(5) # [-0.43391831 -0.24814198  0.5111938  -0.89792554 -1.24439111]

这条语句生成的 a a a 的维度是 ( 5 ,   ) (5,\space) (5, )。它既不是行向量也不是列向量,我们把 a a a 叫做rank 1 array。这种定义会带来一些问题。例如我们对 a a a 进行转置,还是会得到 a a a 本身。所以,如果我们要定义 ( 5 , 1 ) (5,1) (5,1) 的列向量或者 ( 1 , 5 ) (1,5) (1,5) 的行向量,最好使用下来标准语句,避免使用rank 1 array。

a = np.random.randn(5,1) # [[-1.77381591][ 0.16036188] [ 0.71594668] [ 1.16073702] [ 0.23503555]]
b = np.random.randn(1,5) # [[ 0.22552048  0.86586865 -1.37062805 -0.14825315 -2.07090315]]
assert(a.shape == (5,1))
a.reshape((5,1))

除此之外,我们还可以使用assert语句对向量或数组的维度进行判断,assert会对内嵌语句进行判断,即判断 a a a 的维度是不是 ( 5 , 1 ) (5,1) (5,1) 的。如果不是,则程序在此处停止。使用assert语句也是一种很好的习惯,能够帮助我们及时检查、发现语句是否正确。

另外,还可以使用reshape函数对数组设定所需的维度:

1.3.7 Jupiter/iPython 笔记本的快速指南(Quick tour of Jupyter/iPython Notebooks)

Jupyter notebook(又称IPython notebook)是一个交互式的笔记本,支持运行超过40种编程语言。本课程所有的编程练习题都将在Jupyter notebook上进行,使用的语言是python。

1.3.8 (选修)logistic损失函数的解释(Explanation of logistic regression cost function(optional))

在上一节课的笔记中,我们介绍过逻辑回归的Cost function。接下来我们将简要解释这个Cost function是怎么来的。

首先,预测输出 y ^ \hat{y} y^ 的表达式可以写成:
y ^ = σ ( w T x + b ) \hat{y}=\sigma\left(w^{T} x+b\right) y^=σ(wTx+b)
其中, σ ( z ) = 1 1 + e − z \sigma(z)=\frac{1}{1+e^{-z}} σ(z)=1+ez1 y ^ \hat{y} y^ 可以看成是预测输出为正类( + 1 +1 +1)的概率:
y ^ = P ( y = 1 ∣ x ) \hat{y}=P(y=1|x) y^=P(y=1x)
那么,当 y = 1 y=1 y=1 时:
P ( y ∣ x ) = y ^ P(y|x)=\hat{y} P(yx)=y^
y = 0 y=0 y=0 时:
P ( y ∣ x ) = 1 − y ^ P(y|x)=1-\hat{y} P(yx)=1y^
我们把上面两个式子整合到一个式子中,得到:
P ( y ∣ x ) = y ^ y ( 1 − y ^ ) ( 1 − y ) P(y|x)=\hat{y}^y(1-\hat{y})^{(1-y)} P(yx)=y^y(1y^)(1y)
由于 log ⁡ \log log 函数的单调性,可以对上式 P ( y ∣ x ) P(y|x) P(yx) 进行 log ⁡ \log log 处理:
log ⁡ P ( y ∣ x ) = log ⁡ y ^ y ( 1 − y ^ ) ( 1 − y ) = y log ⁡ y ^ + ( 1 − y ) log ⁡ ( 1 − y ^ ) \log P(y \mid x)=\log \hat{y}^{y}(1-\hat{y})^{(1-y)}=y \log \hat{y}+(1-y) \log (1-\hat{y}) logP(yx)=logy^y(1y^)(1y)=ylogy^+(1y)log(1y^)
我们希望上述概率 P ( y ∣ x ) P(y|x) P(yx) 越大越好,对上式加上负号,则转化成了单个样本的Loss function,越小越好,也就得到了我们之前介绍的逻辑回归的Loss function形式。
L = − ( t log ⁡ y ^ + ( 1 − y ) log ⁡ ( 1 − y ^ ) ) L=-(t\log \hat{y}+(1-y)\log (1-\hat{y})) L=(tlogy^+(1y)log(1y^))
如果对于所有 m m m 个训练样本,假设样本之间是独立同分布的(iid),我们希望总的概率越大越好:
max ⁡ ∏ i = 1 m P ( y ( i ) ∣ x ( i ) ) \max \prod_{i=1}^{m} P\left(y^{(i)} \mid x^{(i)}\right) maxi=1mP(y(i)x(i))
同样引入 l o g log log 函数,加上负号,将上式转化为Cost function:
J ( w , b ) = − 1 m ∑ i = 1 m L ( y ^ ( i ) , y ( i ) ) = − 1 m ∑ i = 1 m y ( i ) log ⁡ y ^ ( i ) + ( 1 − y ( i ) ) log ⁡ ( 1 − y ^ ( i ) ) J(w, b)=-\frac{1}{m} \sum_{i=1}^{m} L\left(\hat{y}^{(i)}, y^{(i)}\right)=-\frac{1}{m} \sum_{i=1}^{m} y^{(i)} \log \hat{y}^{(i)}+\left(1-y^{(i)}\right) \log \left(1-\hat{y}^{(i)}\right) J(w,b)=m1i=1mL(y^(i),y(i))=m1i=1my(i)logy^(i)+(1y(i))log(1y^(i))
上式中, 1 m \frac{1}{m} m1 表示对所有 m m m 个样本的Cost function求平均,是缩放因子。

1.3.9 总结(Summary)

本节课我们主要介绍了神经网络基础——python和向量化。在深度学习程序中,使用向量化和矩阵运算的方法能够大大提高运行速度,节省时间。以逻辑回归为例,我们将其算法流程包括梯度下降转换为向量化的形式。同时,我们也介绍了python的相关编程方法和技巧。

猜你喜欢

转载自blog.csdn.net/weixin_43860866/article/details/120677944
1.3