欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【深度学习实战】向量化技术 实现 CPU/GPU并行计算

程序员文章站 2022-07-11 15:37:05
...

本文主要介绍的是,当我们 实践应用深度学习算法,去具体实现大网络(网络的参数个数 nn 很大)多样本(样本数量 mm 很大) 情形下的计算处理时,如何通过向量化的方式,使得代码更加简化,计算更加高效

举个栗子

拿逻辑回归中 z=wTx+bz=w^Tx+b 的计算举例,wwxx都是列向量,维度为 nxn_x。如果你有很多的特征,那么就会有一个非常大的向量。

非向量化(for循环)做法

如果按照正常的非向量化的编程逻辑,实现思路如下(Python)。

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

向量化做法

作为对比,向量化的实现能够非常直接地实现 wTxw^Tx 的计算。

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

除了代码简洁化之外,向量化还能够通过,我们看下面的例子。

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)
b = np.random.rand(1000000)  # 通过round随机得到两个一百万维度的数组
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)
print("For loop:" + str(1000 * (toc - tic)) + "ms")  # 打印for循环的版本的时间

运行结果如下图:
【深度学习实战】向量化技术 实现 CPU/GPU并行计算
在上面的代码中,使用两个方法——向量化和非向量化,进行了相同的计算,向量化版本花费了0.968毫秒,而非向量化版本的 for 循环花费了327.997毫秒,非向量化版本花费的时间多了300多倍。这意味着如果向量化方法需要花费一分钟去运行的数据,使用 for 循环将会花费5个小时去运行。

所以我们可以很直观地看出,向量化有多快。

向量化快速计算背后的原因

你可能听过很多类似如下的话,“大规模的深度学习使用了GPU或者图像处理单元实现”。但其实上面的示例我只用了CPU去实现。

事实上,CPU和GPU都有并行化的指令,他们有时候会叫做SIMD指令,这个代表了一个单独指令处理多维数据(GPU更加擅长SIMD计算,CPU虽不及GPU,不过事实上也不是太差)。这个基础的意义在于,如果你使用一些built-in函数,比如np.function或者其它不要求你实现循环的函数,它可以让python的充分利用CPU或者GPU的并行化计算能力。

向量化的实现方式

我们主要可以通过 numpy内置函数避开显式的循环(loop) 的方式进行向量化。

根据经验,在写神经网络程序时,应该避免写 循环(loop) 语句。不过有的 循环(loop) 还是不可避免的,比如需要多次迭代进行梯度下降。

向量化的具体例子

对向量中每个元素进行数学运算

比如做指数操作。

非向量化方法:初始化向量 u=np.zeros(n,1)u=np.zeros(n,1),然后通过循环依次计算每个元素 viv^{i}

向量化方法:通过 python 的 numpy 内置函数,执行 u=np.exp(v) 命令

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

神经网络的前向传播(以逻辑回归举例)

假设我们需要对 m 个训练样本进行前向传播运算。

如果是非向量化的正常做法,我们挨个样本地进行计算,首先要对第一个样本进行预测,z(1)=wTx(1)+bz^{(1)}=w^{T}x^{(1)}+b,计算**函数 a(1)=σ(z(1))a^{(1)}=\sigma (z^{(1)}),再计算第一个样本的预测值 yy。然后对第二个样本进行预测,第三个样本,依次类推。。。 mm 个训练样本,就需要这样重复做 mm 次。

使用向量化的做法,我们可以将训练输入定义成一个 nxn_xmm 列的矩阵 XX 的形式,即一个(nx,m)( n_x,m)形状的numpy数组。同样,我们构建一个 1×m1×m的行向量 ZZ 用来存储 [z(1),z(m),...,z(m)][z^{(1)},z^{(m)},...,z^{(m)}],然后 ZZ 就可以通过下面的方式求得。
【深度学习实战】向量化技术 实现 CPU/GPU并行计算
以上操作就可以通过 Z=np.dot(w.T,X)+bZ=np.dot(w.T,X)+b 一句 numpy 命令来实现。

同样的,从 ZZ[a(1),a(m),...,a(m)][a^{(1)},a^{(m)},...,a^{(m)}] 我们也可以使用向量方式来实现统一的 sigmoid函数 运算。
【深度学习实战】向量化技术 实现 CPU/GPU并行计算

神经网络的梯度下降(以逻辑回归举例)

那么,要如何同时计算 mm 个数据的梯度?

根据 wwbb 的梯度计算公式:

dw=1mi=1mx(i)dz(i)dw=\frac{1}{m}\sum_{i=1}^{m}{x^{(i)}dz^{(i)}}

db=1mi=1mdz(i)db=\frac{1}{m}\sum_{i=1}^{m}{dz^{(i)}}

其中,dz(1)=a(1)y(1)dz^{(1)}=a^{(1)}-y^{(1)} , dz(2)=a(2)y(2)dz^{(2)}=a^{(2)}-y^{(2)}

那么,我们可以将所有的 dzdz 按列堆叠(横向排列),定义一个 1×m1×m 形状的新的变量 dZ=[dz(1),dz(2)...dz(m)]dZ=[dz^{(1)} ,dz^{(2)} ... dz^{(m)}]。再定义 Y=[y(1),y(2)...y(m)]Y=[y^{(1)} ,y^{(2)} ... y^{(m)}],则 dZ=AYdZ=A-Y

那么很容易就能得到 dbdb 的向量化代码,

db=1mnp.sum(dZ)db= \frac{1}{m} ∗ np.sum(dZ)

对于dwdw,为了更直观,我们先将其展开,得到
【深度学习实战】向量化技术 实现 CPU/GPU并行计算
dwdw 的向量化代码

dw=1mXdZ.Tdw= \frac{1}{m} ∗ X * dZ.T

最终我们得到高度向量化的,高效的,完整的逻辑回归的实现
【深度学习实战】向量化技术 实现 CPU/GPU并行计算
前五个公式完成了前向和后向传播,后两个公式进行梯度下降更新参数。

个人小结

  • 在进行神经网络的具体实现时,要学会通过向量化,进行简化表示,简化代码和运算加速
  • 简单来说,向量化其实就是用 转化为向量,矩阵运算的形式 来对多参数多样本的运算进行统一的概括的表示,同时还能够利用到CPU/GPU的并行计算能力
  • 在进行向量化之前,首先把 深度的学习符号表示,以及 基本的单个样本的前向后向的计算流程 掌握清楚

吴恩达 DeepLearning.ai