神经网络基础篇:详解向量化逻辑回归(Vectorizing Logistic Regression)
向量化逻辑回归
- 讨论如何实现逻辑回归的向量化计算。这样就能处理整个数据集,甚至不会用一个明确的for循环就能实现对于整个数据集梯度下降算法的优化
首先回顾一下逻辑回归的前向传播步骤。所以,如果有 \(m\) 个训练样本,然后对第一个样本进行预测,需要这样计算。计算 \(z\),正在使用这个熟悉的公式 \(z^{(1)}=w^{T}x^{(1)}+b\) 。然后计算激活函数 \(a^{(1)}=\sigma (z^{(1)})\) ,计算第一个样本的预测值 \(y\) 。
然后对第二个样本进行预测,需要计算 \(z^{(2)}=w^{T}x^{(2)}+b\) , \(a^{(2)}=\sigma (z^{(2)})\) 。然后对第三个样本进行预测,需要计算 \(z^{(3)}=w^{T}x^{(3)}+b\) , \(a^{(3)}=\sigma (z^{(3)})\) ,依次类推。如果有 \(m\) 个训练样本,可能需要这样做 \(m\) 次,可以看出,为了完成前向传播步骤,即对的 \(m\) 个样本都计算出预测值。有一个办法可以并且不需要任何一个明确的for循环。让来看一下该怎样做。
首先,回忆一下曾经定义了一个矩阵 \(X\) 作为的训练输入,(如下图中蓝色 \(X\) )像这样在不同的列中堆积在一起。这是一个 \(n_x\) 行 \(m\) 列的矩阵。现在将它写为Python numpy的形式 $$(n_{x},m)$$ ,这只是表示 \(X\) 是一个 \(n_x\) 乘以 \(m\) 的矩阵 $$R^{n_x \times m}$$。
现在首先想做的是告诉该如何在一个步骤中计算 \(z_1\)、 \(z_2\) 、\(z_3\) 等等。实际上,只用了一行代码。所以,打算先构建一个 \(1\times m\) 的矩阵,实际上它是一个行向量,同时准备计算 \(z^{(1)}\), \(z^{(2)}\) ……一直到 \(z^{(m)}\) ,所有值都是在同一时间内完成。结果发现它可以表达为 \(w\) 的转置乘以大写矩阵 \(x\) 然后加上向量 \([b b...b]\) , \(([z^{(1)} z^{(2)}...z^{(m)}]=w^{T}+[bb...b])\) 。\([b b...b]\) 是一个 \(1\times m\) 的向量或者 \(1\times m\) 的矩阵或者是一个 \(m\) 维的行向量。所以希望熟悉矩阵乘法,会发现的 \(w\) 转置乘以 \(x^{(1)}\) , \(x^{(2)}\) 一直到 \(x^{(m)}\) 。所以 \(w\) 转置可以是一个行向量。所以第一项 \(w^{T}X\) 将计算 \(w\) 的转置乘以 \(x^{(1)}\), \(w\) 转置乘以\(x^{(2)}\) 等等。然后加上第二项 \([b b...b]\) ,最终将 \(b\) 加到了每个元素上。所以最终得到了另一个 \(1\times m\) 的向量, \([z^{(1)} z^{(2)}...z^{(m)}]=w^{T}X+[b b...b]=[w^{T}x^{(1)}+b,w^{T}x^{(2)}+b...w^{T}x^{(m)}+b]\) 。
\(w^{T}x^{(1)}+b\) 这是第一个元素,\(w^{T}x^{(2)}+b\) 这是第二个元素, \(w^{T}x^{(m)}+b\) 这是第 \(m\) 个元素。
如果参照上面的定义,第一个元素恰好是 \(z^{(1)}\) 的定义,第二个元素恰好是 \(z^{(2)}\) 的定义,等等。所以,因为\(X\)是一次获得的,当得到的训练样本,一个一个横向堆积起来,这里将 \([z^{(1)} z^{(2)} ... z^{(m)}]\) 定义为大写的 \(Z\) ,用小写 \(z\) 表示并将它们横向排在一起。所以当将不同训练样本对应的小写 \(x\) 横向堆积在一起时得到大写变量 \(X\) 并且将小写变量也用相同方法处理,将它们横向堆积起来,就得到大写变量 \(Z\) 。结果发现,为了计算 \(W^{T}X+[b b ... b]\) ,numpy命令是\(Z=np.dot(w.T,X)+b\)。这里在Python中有一个巧妙的地方,这里 \(b\) 是一个实数,或者可以说是一个 \(1\times 1\) 矩阵,只是一个普通的实数。但是当将这个向量加上这个实数时,Python自动把这个实数 \(b\) 扩展成一个 \(1\times m\) 的行向量。所以这种情况下的操作似乎有点不可思议,它在Python中被称作广播(brosdcasting),目前不用对此感到顾虑,将在下一个博客中进行进一步的讲解。话说回来它只用一行代码,用这一行代码,可以计算大写的 \(Z\),而大写 \(Z\) 是一个包含所有小写\(z^{(1)}\) 到 $ z^{(m)}$ 的 \(1\times m\) 的矩阵。这就是 \(Z\) 的内容,关于变量 \(a\) 又是如何呢?
接下来要做的就是找到一个同时计算 \([a^{(1)} a^{(2)} ... a^{(m)}]\) 的方法。就像把小写 \(x\) 堆积起来得到大写 \(X\) 和横向堆积小写 \(z\) 得到大写 \(Z\) 一样,堆积小写变量 \(a\) 将形成一个新的变量,将它定义为大写 \(A\)。在编程作业中,将看到怎样用一个向量在sigmoid函数中进行计算。所以sigmoid函数中输入大写 \(Z\) 作为变量并且非常高效地输出大写 \(A\)。
总结一下,不需要for循环,利用 \(m\) 个训练样本一次性计算出小写 \(z\) 和小写 \(a\),用一行代码即可完成。
Z = np.dot(w.T,X) + b
这一行代码:\(A=[a^{(1)} a^{(2)} ... a^{(m)}]=\sigma (Z)\) ,通过恰当地运用\(\sigma\)一次性计算所有 \(a\)。这就是在同一时间内如何完成一个所有 \(m\) 个训练样本的前向传播向量化计算。
概括一下,刚刚看到如何利用向量化在同一时间内高效地计算所有的激活函数的所有 \(a\)值。接下来,可以证明,也可以利用向量化高效地计算反向传播并以此来计算梯度。
向量化 logistic 回归的梯度输出(Vectorizing Logistic Regression's Gradient)
注:以下内容大写字母代表向量,小写字母代表元素
如何向量化计算的同时,对整个训练集预测结果\(a\),这是之前已经讨论过的内容。在本次博客中将学习如何向量化地计算\(m\)个训练数据的梯度,本次博客的重点是如何同时计算 \(m\) 个数据的梯度,并且实现一个非常高效的逻辑回归算法(Logistic Regression)。
之前在讲梯度计算的时候,列举过几个例子, \(dz^{(1)}=a^{(1)}-y^{(1)}\),\(dz^{(2)}=a^{(2)}-y^{(2)}\) ……等等一系列类似公式。现在,对 \(m\)个训练数据做同样的运算,可以定义一个新的变量 \(dZ=[dz^{(1)} ,dz^{(2)} ... dz^{(m)}]\)
,所有的 \(dz\) 变量横向排列,因此,\(dZ\) 是一个 \(1\times m\) 的矩阵,或者说,一个 \(m\) 维行向量。在之前的叙述中,已经知道如何计算\(A\),即 \([a^{(1)},a^{(2)} ... a^{(m)}]\),需要找到这样的一个行向量 \(Y=[y^{(1)} y^{(2)} ... y^{(m)}]\) ,由此,可以这样计算 \(dZ=A-Y=[a^{(1)}-y^{(1)} a^{(2)}-y^{(2)} ... a^{(m)}-y^{(m)}]\),不难发现第一个元素就是 \(dz^{(1)}\),第二个元素就是 \(dz^{(2)}\) ……所以现在仅需一行代码,就可以同时完成这所有的计算。
在之前的实现中,已经去掉了一个for循环,但仍有一个遍历训练集的循环,如下所示:
\(dw=0\)
\(dw + = x^{(1)}*{dz}^{(1)}\)
\(dw + = x^{(2)}\ *dz^{(2)}\)
………….
\(dw + = x^{(m)}*{dz}^{(m)}\)
\(dw = \frac{{dw}}{m}\)
\(db = 0\)
\(db + = {dz}^{(1)}\)
\(db + = {dz}^{(2)}\)
………….
\(db + = dz^{(m)}\)
\(db = \frac{{db}}{m}\)
上述(伪)代码就是在之前实现中做的,已经去掉了一个for循环,但用上述方法计算 \(dw\) 仍然需要一个循环遍历训练集,现在要做的就是将其向量化!
首先我们来看 \(db\),不难发现 \(db=\frac{1}{m}\sum_{i=1}^{m}dz^{(i)}\) ,
之前的叙述中,我们知道所有的\(dz^{i)}\)已经组成一个行向量 \(dZ\)了,所以在Python中,我们很容易地想到\(db=\frac{1}{m}*np.sum(dZ)\);接下来看\(dw\),我们先写出它的公式 \(dw=\frac{1}{m}*X*dz^{T}\)
其中,\(X\) 是一个行向量。因此展开后 \(dw=\frac{1}{m}*(x^{(1)}dz^{(1)}+x^{(2)}dz^{(2)}+...+x^{m}dz^{m})\) 。因此我们可以仅用两行代码进行计算:\(db=\frac{1}{m}*np.sum(dZ)\), \(dw=\frac{1}{m}*X*dz^{T}\)。这样,我们就避免了在训练集上使用for循环。
现在,让回顾一下,看看之前怎么实现的逻辑回归,可以发现,没有向量化是非常低效的,如下图所示代码:
的目标是不使用for循环,而是向量,可以这么做:
\(Z = w^{T}X + b = np.dot( w.T,X)+b\)
\(A = \sigma( Z )\)
\(dZ = A - Y\)
\({{dw} = \frac{1}{m}*X*dz^{T}\ }\)
\(db= \frac{1}{m}*np.sum( dZ)\)
\(w: = w - a*dw\)
\(b: = b - a*db\)
现在利用前五个公式完成了前向和后向传播,也实现了对所有训练样本进行预测和求导,再利用后两个公式,梯度下降更新参数。目的是不使用for循环,所以就通过一次迭代实现一次梯度下降,但如果希望多次迭代进行梯度下降,那么仍然需要for循环,放在最外层。不过还是觉得一次迭代就进行一次梯度下降,避免使用任何循环比较舒服一些。
最后,得到了一个高度向量化的、非常高效的逻辑回归的梯度下降算法,将在下次博客中讨论Python中的Broadcasting技术。