Deep Learning Note: 1-4 向量化

1. 逻辑回归的向量化

  前文给出了逻辑归回的模型和通过梯度下降优化逻辑回归代价函数的算法。算法描述中包含了很多循环的步骤。在具体实现中,通过向量化(Vectorization)移除显式的 for 循环,有利于充分利用硬件性能,提高执行速度。如 Python 的 NumPy 包中有很多内置函数都提供了输入和处理多维数组的能力,在内部实现了并行计算的优化。

  将之前得到的梯度下降计算各偏导的过程编写如下:

J = 0, dw1 = 0, dw2 = 0, db = 0
for i = 1 to m:
    z[i] = w.T.dot(x[i]) + b
    a[i] = sigmoid(z[i])
    J += loss(a, y)
    dz[i] = a[i] - y[i]
    dw1 += x1[i] * dz[i]
    dw2 += x2[i] * dz[i]
    db += dz[i]
J = J / m, dw1 = dw1 / m, dw2 = dw2 / m, db = db / m

  上面的代码中,x 为特征向量,每个样本的特征向量包含有两个特征 x1x2w 为权重,在有两个特征的情况下,w1w2 分别代表第一个和第二个权重。第三行的 w.T 表示 w 的转置,dot() 表示计算向量积。【注】

  当有 nx 个特征时,上面的代码可以推广为:

J = 0, dw[1] = 0, dw[2] = 0, ..., dw[nx] = 0, db = 0
for i = 1 to m:
    z[i] = w.T.dot(x[i]) + b
    a[i] = sigmoid(z[i])
    J += loss(a, y)
    dz[i] = a[i] - y[i]
    for j = 1 to nx:
        dw[j] += x[i, j] * dz[i]
    db += dz[i]
J = J / m, dw[1] = dw[1] / m, dw[2] = dw[2] / m, ..., dw[nx] = dw[nx] / m, db = db / m

  其中 x[i, j] 表示第 i 个样本的第 j 个特征值。此时代码中有两个 for 循环,第一个关于 i 的 for 用于遍历训练样本,第二个关于 j 的 for 循环用于遍历各个特征。

2. 消除对特征的循环

  首先通过向量化消除对特征的循环,也就是前面对 j 的循环。方法是将 dw 看成一个由各个特征对应的权重所组成的向量,将对各个权重 dw[j] 的计算转换为对 dw 这个向量的的计算:

J = 0, dw = np.zeros((nx, 1)), db = 0
for i = 1 to m:
    z[i] = w.T.dot(x[i]) + b
    a[i] = sigmoid(z[i])
    J += loss(a, y)
    dz[i] = a[i] - y[i]
    dw += x[i] * dz[i]
    db += dz[i]
J = J / m, dw = dw / m, db = db / m

  上面代码中,dw = np.zeros((nx, 1))dw 初始化为一个 nx × 1 的向量,第 7 行 x[i] 是第 i 个样本的特征向量,是一个 nx × 1 的向量,z[i] 是一个实数,故 x[i] * dz[i] 得到一个 nx × 1 的向量,即各个特征所对应权重的偏导,以此更新 dw

3. 逻辑回归的向量化

  在更进一步通过向量化消除对样本的循环前,先来看一下如果对逻辑回归本身进行向量化,实现同时对 m 个样本的处理。

  首先重新明确一下符号表示,定义 $X = [x^{(1)} x^{(2)} … x^{(m)} ]$,即各个样本的特征向量水平叠加后的结果,是一个 $n_x \times m$ 的矩阵。$w$ 为权重向量,是一个 $n_x \times m$ 的向量。由此可以向量化地计算:(下式中的 $b$ 虽然是一个实数,这里把看成是一个 $1 \times m$ 的向量,向量的每个值均为 $b$)

\begin{equation}
Z = w^TX + b \tag{1}
\end{equation}

  这里得到的 $Z$ 是一个 $1 \times m$ 的向量,对应每个样本。接着就可以向量化地计算:

\begin{equation}
A = \sigma(Z) \tag{2}
\end{equation}

\begin{equation}
dZ = A – Y \tag{3}
\end{equation}

  这里得到的 $A$ 也是一个 $1 \times m$ 的向量,对应每个样本(的激活值)。$Y$ 是一个 $1 \times m$ 的向量,为每个样本的真实标签。故 $dZ$ 也是一个 $1 \times m$ 的向量。进一步的,得到 Wb 的偏导:

\begin{equation}
dw = \frac{1}{m} XdZ^T \tag{4}
\end{equation}

\begin{equation}
db = \frac{1}{m} \sum_{i = 1}^m dZ \tag{5}
\end{equation}

  $n_x \times m$ 的矩阵 $X$ 与 $m \times 1$ 的向量 $dZ$ 的转置相乘,得到 $n_x \times 1$ 的向量 $dw$,为每个权重的偏导。$db$ 为 $1 \times m$ 的向量 dZ 中各个元素的平均值,是一个实数。

  通过以上计算,即可一次性完成对全部 m 个样本的处理。

4. 消除对样本的循环

  根据以上的推导,可以得到消除对样本的循环的算法:

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

  注意上面使用大写的 X 即各样本特征向量水平叠加后的结果作为输入。第 1 行 w.T.dot(X) 得到一个向量,而 b 是一个实数,Python 和 NumPy 提供了广播(Broadcasting)机制,可以自动将低维数据通过特定的规则扩展为高维数据。在 Python 的实际代码中,可以直接书写 w.T.dot(X) + b,实数 b 会被自动扩展为与 w.T.dot(X) 具有同样尺寸的向量,以完成相加的操作。

  计算得到各偏导后,更新参数:

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

  由此就完成了一步梯度下降,这个过程完全向量化,没有使用显式的 for 循环。

  然而实际使用梯度下降训练模型时,通常会在样本集合上迭代很多次,不断调整参数,使得代价函数不断减小。这个迭代的过程也是一个循环,这个循环一般无法通过向量化消除,仍会保留下来。


【注】在原课程视频中,上面代码里关于 dz[i] 的计算为 $dz^{(i)} = a^{(i)}(1 – a^{(i)})$,计算的是 $\frac{da}{dz}$。本文伪代码中所使用的 dz[i] = a[i] - y[i] 计算的是 $\frac{\partial J}{\partial z}$。【返回】