在深度学历领域,你会经常发现自己要训练相当大的数据集,因为深度学习算法处理大数据集效果很好,所以你的代码运行速度非常重要,否则你的代码在大数据集上可能需要花费相当长的时间去运行。在深度学习领域,向量化是一个关键技巧。
一、向量化在逻辑回归中你需要去计算 z = w t x + b z = w^tx + b z=wtx+b, w 、 x w、x w、x都是列向量,如果你的数据有很多特征,那么就会有一个非常大的向量。其中,$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=m1∑i=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技术。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)