首页
学习
活动
专区
圈层
工具
发布

从零开始深度学习(七):向量化

文章首发于本人CSDN账号:https://blog.csdn.net/tefuirnever

由于微信不允许外部链接,你需要点击页尾左下角的“阅读原文”,才能访问文中的链接。

1、向量化

向量化 是非常基础的去除代码中 for 循环的艺术。为什么要去除 for 循环?

当在深度学习安全领域、深度学习实践中应用深度学习算法时,会发现在代码中显式地使用 for 循环使算法很低效,同时在深度学习领域会有越来越大的数据集,因为深度学习算法处理大数据集效果很棒,所以代码运行速度非常重要,否则如果在大数据集上,代码可能花费很长时间去运行,你将要等待非常长的时间去得到结果。所以算法能应用且没有显式的 for 循环是很重要的,并且会帮助你适用于更大的数据集。所以在深度学习领域这里有一项叫做向量化的技术,是一个关键的技巧,它可以允许你的代码摆脱这些显式的 for 循环,举个栗子说明什么是向量化。

在逻辑回归中,需要去计算 ,其中 、 都是列向量。如果有很多的特征,那么就会有一个非常大的向量,所以 , ,那么如果想使用非向量化方法去计算 ,就需要用如下方式(基于 python 编程实现):

代码语言:javascript
复制
z = 0
for i in range(n_x):
    z += w[i] * x[i]
z += b

这是一个非向量化的实现,实践之后,你会发现这个是真的很慢,,,作为对比,向量化的实现将会非常直接计算 ,代码如下:

代码语言:javascript
复制
z = np.dot(w, x) + b

这是向量化方式进行计算 的方法,你会发现这个非常快,尤其是对比之前的非向量化的实现。

让我们用一个小例子说明一下,在我的我将会写一些代码(以下为教授在他的Jupyter notebook上写的Python代码,)

代码语言:javascript
复制
import time  # 导入时间库
import numpy as np  # 导入numpy库


a = np.array([1, 2, 3, 4])  # 创建一个数据a
print(a)
# [1 2 3 4]

a = np.random.rand(1000000)
# 通过round随机得到两个一百万维度的数组
b = np.random.rand(1000000)
tic = time.time()  # 现在测量一下当前时间

# 向量化的版本
c = np.dot(a, b)
toc = time.time()
# 打印一下向量化的版本的时间
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)
# 打印for循环的版本的时间
print("For loop:" + str(1000 * (toc - tic)) + "ms")  

运行结果见下图:

在上面的代码中,使用两个方法——向量化和非向量化,计算了相同的值,其中向量化版本花费了0.968毫秒,而非向量化版本的 for 循环花费了327.997毫秒,大概是300多倍,准确倍数是 338.840 倍。仅仅在这个自己举的例子中,都可以明显看到效果。这意味着如果向量化方法需要花费一分钟去运行的数据,使用 for 循环将会花费5个小时去运行。

一句话总结,向量化快!!!

2、深入理解向量化

通过 numpy内置函数避开显式的循环(loop) 的方式进行向量化,从而有效提高代码速度。根据经验,在写神经网络程序时,或者在写 逻辑(logistic)回归 时,或者在写其他神经网络模型时,应该避免写 循环(loop) 语句。虽然有时写 循环(loop) 是不可避免的,但是如果可以使用其他办法去替代计算,程序效率总是更快。

来看另外一个例子。如果想计算向量 ,这时根据矩阵乘法的定义,有 。

  • 非向量化方法:用 , 然后通过两层循环 ,可以得到:
  • 向量化方法:用

吴恩达老师手写稿如下:


下面通过另一个例子继续了解向量化。如果有一个向量 ,并且想要对向量 的每个元素做指数操作。

  • 非向量化方法:初始化向量 ,然后通过循环依次计算每个元素
  • 向量化方法:通过 pythonnumpy 内置函数,执行 命令

numpy 库有很多向量函数,比如 u=np.log 是按元素计算对数函数()、 np.abs() 是按元素计算数据的绝对值函数、np.maximum(v, 0) 是按元素计算 中每个元素和和0相比的最大值,v**2 是按元素计算元素 中每个值的平方、 1/v 是按元素计算 中每个元素的倒数等等。

PS:当想写循环时,检查 numpy 是否存在类似的内置函数。

吴恩达老师手写稿如下:

希望你现在有一点向量化的感觉了,减少一层循环可以使代码更快一些!!!

3、向量化逻辑回归

如何实现逻辑回归的向量化计算?只要实现了,就能处理整个数据集了,甚至不会用一个明确的 for 循环,听起来是不是特别地 inspiring。

先回顾一下逻辑回归的前向传播,现有 个训练样本,然后对第一个样本进行预测,;激活函数 ;计算第一个样本的预测值 。然后对第二个样本进行预测,第三个样本,依次类推。。。如果有 个训练样本,可能需要这样重复做 次。可不可以不用任何一个明确的 for 循环?

首先,定义一个 行 列的矩阵 作为训练输入(如下图中蓝色 ),numpy 形式为 。

吴恩达老师手稿如下:

前向传播过程中,如何计算 , , ……一直到 ?构建一个 的行向量用来存储 ,这样可以让所有的 值都同一时间内完成。实际上,只用了一行代码。即

为什么 要转置呢?

希望你尽快熟悉矩阵乘法,因为矩阵乘法的要求中有一条是,两个矩阵相乘,左面矩阵的列数需要等于右面矩阵的行数, 也是 , 也是 ,而 是 ,正好符合 的公式,且保证了矩阵乘法的条件。其中 这是第一个元素, 这是第二个元素, ..., 这是第 个元素。分别与 , , ...对应。所以, 是一次获得的一次获得全部。

但是细心的你会发现,为了计算 ,使用 numpy 命令 。这里有一个巧妙的地方, 是一个 的矩阵,而 是一个实数,或者可以说是一个 的矩阵,那么如何把一个向量加上一个实数?

这里简单说一下:Python 自动地把实数 扩展成一个 的行向量,只有这样才能进行矩阵相加(矩阵相加需要两个矩阵等大小)。这个操作似乎有点不可思议,它在 Python 中被称作 广播(brosdcasting),目前你不用对此感到顾虑,这在博客——深度学习入门笔记(五):神经网络的编程基础中会详细讲解!

现在说一下字母规范:大写的 是一个包含所有小写 到 的 的矩阵,而大写 则是包含所有小写 到 的 的矩阵。

简单小结一下,不要 for 循环,利用 个训练样本使用向量化的方法,一次性计算出 和 。

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

注:本节中大写字母代表向量,小写字母代表元素

如何 同时 计算 个数据的梯度,并且实现一个非常高效的 逻辑回归算法(Logistic Regression)

之前在讲梯度计算的时候(深度学习入门笔记(二):神经网络基础),列举过几个例子, , , ……等等一系列类似公式。不过当时是单样本数据计算,现在对 个数据做同样的计算,可以照着上一章讲过的,定义一个新的变量 ,每一个样本的 横向排列,就可以得到一个 的 矩阵了。

我们已经知道计算方法了,那么就差一个 ,然后就可以计算 ,刚好分别对应 ,,……

开始向量化逻辑回归的梯度输出:

首先是

向量化代码如下:

接下来是

其中, 是一个行向量。因此展开后是

向量化代码如下:

这样,就避免了在训练集上使用 for 循环。对比之前实现的逻辑回归,可以发现,没有向量化是非常低效的,代码量还多。。。

翻新后的计算如下:


前五个公式完成了前向和后向传播,后两个公式进行梯度下降更新参数。

最后的最后,终于得到了一个高度向量化的、非常高效的逻辑回归的梯度下降算法,是不是?

下一篇
举报
领券