week2-part6向量化 Vectorization

week2-part6向量化 Vectorization,第1张

week2-part6 向量化(Vectorization)

在深度学历领域,你会经常发现自己要训练相当大的数据集,因为深度学习算法处理大数据集效果很好,所以你的代码运行速度非常重要,否则你的代码在大数据集上可能需要花费相当长的时间去运行。在深度学习领域,向量化是一个关键技巧。

一、向量化

在逻辑回归中你需要去计算 z = w t x + b z = w^tx + b z=wtx+b w 、 x w、x wx都是列向量,如果你的数据有很多特征,那么就会有一个非常大的向量。其中,$w \in \mathbb{R}^{n_x}, x \in \mathbb{R}^{n_x} , 如 果 你 想 用 非 向 量 化 方 法 去 计 算 ,如果你想用非向量化方法去计算 w^tx$,你需要代码如下

z = 0
for i in range(n_x):
	z == w[i] * x[i]
z += b

这是一个非向量化的实现,你会发现这真的很慢,作为一个对比,向量化实现会非常直接快速地计算 w t x w^tx wtx,代码如下

z = np.dot(w, x) + b

让我们用一个小例子展示一下效率的对比:

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')

运行结果如下:

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

在两个方法中,计算得到了相同的值,但时间却相差约300倍。

让我们来看另外一个例子,如果你想计算向量 u = A v u = Av u=Av,根据矩阵乘法定义:$u_{i} = \sum_{j} A_{i j} v_i $。我们用非向量化方法实现,需要通过两层循环for(i):for(j),得到u[i] = u[i] + A[i][j] * v[j]。而向量化方法就可以用u = np.dot(A, v),向量化实现方法消除了两层循环使得代码运行速度更快。

如果你已经有了一个向量 v v v,并想要对其每个元素做指数 *** 作,得到向量 u u u等于 e e e v 1 v_1 v1,e的 v 2 v_2 v2,一直到 e e e v n v_n vn次方。我们可以通过循环一次计算每个元素,但若使用向量化技巧,我们仅需要一个内置函数u = np.exp(v)。事实上,numpy库有很多向量函数,当你想要写循环的时候,可以去查找一下numpy是否存在类似的内置函数,从而避免使用循环方式。

二、向量化逻辑回归

首先我们回顾一下逻辑回归的前向传播步骤,假设有 m m m个训练样本。我们对第一个样本进行预测,先计算 z ( 1 ) = w t x ( 1 ) + b z^{(1)} = w^{t}x^{(1)} + b z(1)=wtx(1)+b,然后计算激活函数 a ( 1 ) = σ ( z ( 1 ) ) a^{(1)} = \sigma(z^{(1)}) a(1)=σ(z(1)),计算第一个样本的预测值 y y y

对第二个样本进行预测, z ( 2 ) = w t x ( 2 ) + b z^{(2)} = w^{t}x^{(2)} + b z(2)=wtx(2)+b a ( 2 ) = σ ( z ( 2 ) ) a^{(2)} = \sigma(z^{(2)}) a(2)=σ(z(2))

对第三个样本进行预测, z ( 3 ) = w t x ( 3 ) + b z^{(3)} = w^{t}x^{(3)} + b z(3)=wtx(3)+b a ( 3 ) = σ ( z ( 3 ) ) a^{(3)} = \sigma(z^{(3)}) a(3)=σ(z(3))。以此类推

现在我们有 m m m个训练样本,我们可能需要一个for循环来对 m m m个样本完成前向传播。但如果我们合理使用向量化,我们可以不需要一个明确的for循环。

​ 回忆一下,我们曾经定义了一个矩阵 X X X作为我们的训练输入,将所有的训练集作为不同的列堆积在一起,我们将其写成Python numpy的形式 ( n x , m ) (n_{x}, m) (nx,m),这表示 X X X是一个 n x n_{x} nx乘以 m m m的矩阵。

X = [ ⋮ ⋮ ⋯ ⋮ x ( 1 ) x ( 2 ) ⋯ x ( m ) ⋮ ⋮ ⋯ ⋮ ] X=\left[\begin{array}{cccc} \vdots & \vdots & \cdots & \vdots \ x^{(1)} & x^{(2)} & \cdots & x^{(m)} \ \vdots & \vdots & \cdots & \vdots \end{array}\right] X=x(1)x(2)x(m)

我们构建一个 1 × m 1 \times m 1×m的矩阵,实际上也是一个行向量,同时我们准备计算 z ( 1 ) , z ( 2 ) , ⋯ z ( m ) z^{(1)}, z^{(2)}, \cdots z^{(m)} z(1),z(2),z(m)。根据矩阵运算的知识,我们知道它可以表示为 w w w的转置乘以 X X X然后加上向量 [ b , b , b ] [b, b, b] [b,b,b],即 [ z ( 1 ) z ( 2 ) ⋯ z ( m ) ] = w T X + [ b b . . . b ] [z^{(1)} z^{(2)} \cdots z^{(m)}] = w^{T}X + [bb...b] [z(1)z(2)z(m)]=wTX+[bb...b]。我们将 [ z ( 1 ) z ( 2 ) ⋯ z ( m ) ] [z^{(1)} z^{(2)} \cdots z^{(m)}] [z(1)z(2)z(m)]定义为大写的 Z Z Z,不需要for循环,我们只需要一行代码就可以一次性计算出 z z z

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

同理,和前面的np.exp(v)相似,我们通过恰当运用 σ \sigma σ也可以一次性计算所有的 a a a。这里,你可能会对代码中的+ b有所疑惑,为什么可以将实数与数组直接相加,这就涉及到了python中的广播机制,我们会在后续详细介绍。

三、向量化逻辑回归的梯度输出

在之前介绍梯度计算的时候,列举过几个例子 d z ( 1 ) = a ( 1 ) − y ( 1 ) ,   d z ( 2 ) = a ( 2 ) − y ( 2 ) ⋯ d z^{(1)} = a^{(1)} - y^{(1)},\ d z^{(2)} = a^{(2)} - y^{(2)} \cdots dz(1)=a(1)y(1), dz(2)=a(2)y(2)等一系列类似公式。现在,对 m m m个训练数据做同样的计算,我们可以类似地定义一个 d Z = [ d z ( 1 ) , d z ( 2 ) , ⋯   , d z ( m ) ] d Z = [d z^{(1)}, d z^{(2)}, \cdots, d z^{(m)}] dZ=[dz(1),dz(2),,dz(m)],所有 d z dz dz变量横向排列, d Z d Z dZ是一个 1 × m 1 \times m 1×m 的矩阵。

在之前的实现中,我们已经去掉了一个for循环,但我们仍有一个遍历训练集的循环,如下所示

dw = 0 
dw += x1 * dz1 
dw += x2 * dz2 
...
dw += xm * dzm 
dw = dw / m 
db = 0 
db += dz1 
db += dz2 
... 
db += dzm 
db = db / m

上述伪代码就是我们在之前实现做的,我们已经去掉了一个for循环,但用上述方法计算 d w dw dw d b db db仍需要一个循环遍历训练集,现在我们要做的就是将其向量化!

首先我们来看 d b db db,不难发现 d b = 1 m ∑ i = 1 m d z ( i ) db = \frac{1}{m}\sum_{i = 1}^{m}d z^{(i)} db=m1i=1mdz(i),我们在之前将所有的 d z ( i ) dz^{(i)} dz(i)已经组成了一个行向量 d Z dZ dZ,所以在Python中,我们可以简单地使用db = 1 / m * mp.sum(dZ);同理,dw = 1 / m * X * dz.T。这样,我们就避免了在训练集上使用for循环。

现在,让我们可以用向量化代码来替代之前的循环式代码

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

现在,我们没有任何一个for循环完成了前向和后向传播,也实现了对所有训练样本进行预测和求导,并使用梯度下降更新参数。但如果你希望多次迭代进行梯度下降,你仍然需要for循环放在最外层。


最后,我们得到了一个高度向量化、非常高效的逻辑回归的梯度下降算法,我们将在下一节介绍Python中的Broadcasting技术。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/922999.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-16
下一篇 2022-05-16

发表评论

登录后才能评论

评论列表(0条)

保存