本周测验见:第二周:Neural Network Basics 测验, 10 个问题
本周编程作业见:第二周:练习编程作业Python Basics with numpy (optional);第二周:编程作业 Logistic Regression with a Neural Network mindset
这周我们将学习神经网络的基础知识,其中需要注意的是,当实现一个神经网络的时候,我们需要知道一些非常重要的技术和技巧。例如有一个包含 m m m个样本的训练集,你很可能习惯于用一个for循环来遍历训练集中的每个样本,但是当实现一个神经网络的时候,我们通常不直接使用for循环来遍历整个训练集,所以在这周的课程中你将学会如何处理训练集。
另外在神经网络的计算中,通常先有一个叫做前向暂停(forward pause)或叫做前向传播(foward propagation)的步骤,接着有一个叫做反向暂停(backward pause) 或叫做反向传播**(backward propagation**)的步骤。所以这周我也会向你介绍为什么神经网络的训练过程可以分为前向传播和反向传播两个独立的部分。
在课程中我将使用逻辑回归(logistic regression)来传达这些想法,以使大家能够更加容易地理解这些概念。
逻辑回归是一个用于二分类(binary classification)的算法。首先我们从一个问题开始说起,这里有一个二分类问题的例子,假如你有一张图片作为输入,比如这只猫,如果识别这张图片为猫,则输出标签1作为结果;如果识别出不是猫,那么输出标签0作为结果。现在我们可以用字母 y y y来 表示输出的结果标签,如下图所示:
我们来看看一张图片在计算机中是如何表示的,为了保存一张图片,需要保存三个矩阵,它们分别对应图片中的红、绿、蓝三种颜色通道,如果你的图片大小为64x64像素,那么你就有三个规模为64x64的矩阵,分别对应图片中红、绿、蓝三种像素的强度值。为了便于表示,这里我画了三个很小的矩阵,注意它们的规模为5x4 而不是64x64,如下图所示:
为了把这些像素值放到一个特征向量中,我们需要把这些像素值提取出来,然后放入一个特征向量 x x x。为了把这些像素值转换为特征向量 x x x,我们需要像下面这样定义一个特征向量 x x x 来表示这张图片,我们把所有的像素都取出来,例如255、231等等,直到取完所有的红色像素,接着最后是255、134、…、255、134等等,直到得到一个特征向量,把图片中所有的红、绿、蓝像素值都列出来。如果图片的大小为64x64像素,那么向量 x x x 的总维度,将是64乘以64乘以3,这是三个像素矩阵中像素的总量。在这个例子中结果为12,288。现在我们用 n x = 12 , 288 n_x=12,288 nx=12,288,来表示输入特征向量的维度,有时候为了简洁,我会直接用小写的 n n n来表示输入特征向量 x x x的维度。所以在二分类问题中,我们的目标就是习得一个分类器,它以图片的特征向量作为输入,然后预测输出结果 y y y为1还是0,也就是预测图片中是否有猫:
接下来我们说明一些在余下课程中,需要用到的一些符号。
符号定义 :
x x x:表示一个 n x n_x nx维数据,为输入数据,维度为 ( n x , 1 ) (n_x,1) (nx,1);
y y y:表示输出结果,取值为 ( 0 , 1 ) (0,1) (0,1);
( x ( i ) , y ( i ) ) (x^{(i)},y^{(i)}) (x(i),y(i)):表示第 i i i组数据,可能是训练数据,也可能是测试数据,此处默认为训练数据;
X = [ x ( 1 ) , x ( 2 ) , . . . , x ( m ) ] X=[x^{(1)},x^{(2)},...,x^{(m)}] X=[x(1),x(2),...,x(m)]:表示所有的训练数据集的输入值,放在一个 n x × m n_x×m nx×m的矩阵中,其中 m m m表示样本数目;
Y = [ y ( 1 ) , y ( 2 ) , . . . , y ( m ) ] Y=[y^{(1)},y^{(2)},...,y^{(m)}] Y=[y(1),y(2),...,y(m)]:对应表示所有训练数据集的输出值,维度为 1 × m 1×m 1×m。
用一对 ( x , y ) (x,y) (x,y)来表示一个单独的样本, x x x代表 n x n_x nx维的特征向量, y y y 表示标签(输出结果)只能为0或1。
而训练集将由 m m m个训练样本组成,其中 ( x ( 1 ) , y ( 1 ) ) (x^{(1)},y^{(1)}) (x(1),y(1))表示第一个样本的输入和输出, ( x ( 2 ) , y ( 2 ) ) (x^{(2)},y^{(2)}) (x(2),y(2))表示第二个样本的输入和输出,直到最后一个样本 ( x ( m ) , y ( m ) ) (x^{(m)},y^{(m)}) (x(m),y(m)),然后所有的这些一起表示整个训练集。有时候为了强调这是训练样本的个数,会写作 M t r a i n M_{train} Mtrain,当涉及到测试集的时候,我们会使用 M t e s t M_{test} Mtest来表示测试集的样本数,所以这是测试集的样本数:
最后为了能把训练集表示得更紧凑一点,我们会定义一个矩阵用大写 X X X的表示,它由输入向量 x ( 1 ) x^{(1)} x(1)、 x ( 2 ) x^{(2)} x(2)等组成,如下图放在矩阵的列中,所以现在我们把 x ( 1 ) x^{(1)} x(1)作为第一列放在矩阵中, x ( 2 ) x^{(2)} x(2)作为第二列, x ( m ) x^{(m)} x(m)放到第 m m m列,然后我们就得到了训练集矩阵 X X X。所以这个矩阵有 m m m列, m m m是训练集的样本数量,然后这个矩阵的高度记为 n x n_x nx,注意有时候可能因为其他某些原因,矩阵 X X X会由训练样本按照行堆叠起来而不是列,如下图所示: x ( 1 ) x^{(1)} x(1)的转置直到 x ( m ) x^{(m)} x(m)的转置,但是在实现神经网络的时候,使用左边的这种形式,会让整个实现的过程变得更加简单:
现在来简单温习一下: X X X是一个规模为 n x n_x nx乘以 m m m的矩阵,当你用Python实现的时候,你会看到X.shape
,这是一条Python命令,用于显示矩阵的规模,即X.shape
等于 ( n x , m ) (n_x,m) (nx,m), X X X是一个规模为 n x n_x nx乘以 m m m的矩阵。所以综上所述,这就是如何将训练样本(输入向量 X X X的集合)表示为一个矩阵。
那么输出标签 y y y呢?同样的道理,为了能更加容易地实现一个神经网络,将标签 y y y放在列中将会使得后续计算非常方便,所以我们定义大写的 Y Y Y等于 y ( 1 ) , y ( m ) , . . . , y ( m ) {{y}^{\left( 1 \right)}},{{y}^{\left( m \right)}},...,{{y}^{\left( m \right)}} y(1),y(m),...,y(m),所以在这里是一个规模为1乘以 m m m的矩阵,同样地使用Python将表示为Y.shape
等于 ( 1 , m ) (1,m) (1,m),表示这是一个规模为1乘以 m m m的矩阵。
重温逻辑回归学习算法,该算法适用于二分类问题,本节将主要介绍逻辑回归的Hypothesis Function(假设函数)。
对于二元分类问题来讲,给定一个输入特征向量 X X X,它可能对应一张图片,你想识别这张图片识别看它是否是一只猫或者不是一只猫的图片,你想要一个算法能够输出预测,你只能称之为 y ^ \hat{y} y^,也就是你对实际值 y y y 的估计。更正式地来说,你想让 y ^ \hat{y} y^ 表示 y y y 等于1的一种可能性或者是机会,前提条件是给定了输入特征 X X X。换句话来说,如果 X X X是我们在上个视频看到的图片,你想让 y ^ \hat{y} y^ 来告诉你这是一只猫的图片的机率有多大。在之前的视频中所说的, X X X是一个 n x n_x nx维的向量(相当于有 n x n_x nx个特征的特征向量)。我们用 w w w来表示逻辑回归的参数,这也是一个 n x n_x nx维向量(因为 w w w实际上是特征权重,维度与特征向量相同),参数里面还有 b b b,这是一个实数(表示偏差)。所以给出输入 x x x以及参数 w w w和 b b b之后,我们怎样产生输出预测值 y ^ \hat{y} y^,一件你可以尝试却不可行的事是让 y ^ = w T x + b \hat{y}={{w}^{T}}x+b y^=wTx+b。
这时候我们得到的是一个关于输入 x x x的线性函数,实际上这是你在做线性回归时所用到的,但是这对于二元分类问题来讲不是一个非常好的算法,因为你想让 y ^ \hat{y} y^表示实际值 y y y等于1的机率的话, y ^ \hat{y} y^ 应该在0到1之间。这是一个需要解决的问题,因为 w T x + b {{w}^{T}}x+b wTx+b可能比1要大得多,或者甚至为一个负值。对于你想要的在0和1之间的概率来说它是没有意义的,因此在逻辑回归中,我们的输出应该是 y ^ \hat{y} y^等于由上面得到的线性函数式子作为自变量的sigmoid函数中,公式如上图最下面所示,将线性函数转换为非线性函数。
下图是sigmoid函数的图像,如果我把水平轴作为 z z z轴,那么关于 z z z的sigmoid函数是这样的,它是平滑地从0走向1,让我在这里标记纵轴,这是0,曲线与纵轴相交的截距是0.5,这就是关于 z z z的sigmoid函数的图像。我们通常都使用 z z z来表示 w T x + b {{w}^{T}}x+b wTx+b的值。
关于sigmoid函数的公式是这样的, σ ( z ) = 1 1 + e − z \sigma \left( z \right)=\frac{1}{1+{{e}^{-z}}} σ(z)=1+e−z1,在这里 z z z是一个实数,这里要说明一些要注意的事情,如果 z z z非常大那么 e − z {{e}^{-z}} e−z将会接近于0,关于 z z z的sigmoid函数将会近似等于1除以1加上某个非常接近于0的项,因为 e e e 的指数如果是个绝对值很大的负数的话,这项将会接近于0,所以如果 z z z很大的话那么关于 z z z的sigmoid函数会非常接近1。相反地,如果 z z z非常小或者说是一个绝对值很大的负数,那么关于 e − z {{e}^{-z}} e−z这项会变成一个很大的数,你可以认为这是1除以1加上一个非常非常大的数,所以这个就接近于0。实际上你看到当 z z z变成一个绝对值很大的负数,关于 z z z的sigmoid函数就会非常接近于0,因此当你实现逻辑回归时,你的工作就是去让机器学习参数 w w w以及 b b b,这样才使得 y ^ \hat{y} y^成为对 y = 1 y=1 y=1这一情况的概率的一个很好的估计。
在继续进行下一步之前,介绍一种符号惯例,可以让参数 w w w和参数 b b b分开。在符号上要注意的一点是当我们对神经网络进行编程时经常会让参数 w w w和参数 b b b分开,在这里参数 b b b对应的是一种偏置。在之前的机器学习课程里,你可能已经见过处理这个问题时的其他符号表示。比如在某些例子里,你定义一个额外的特征称之为 x 0 {{x}_{0}} x0,并且使它等于1,那么现在 X X X就是一个 n x n_x nx加1维的变量,然后你定义 y ^ = σ ( θ T x ) \hat{y}=\sigma \left( {{\theta }^{T}}x \right) y^=σ(θTx)的sigmoid函数。在这个备选的符号惯例里,你有一个参数向量 θ 0 , θ 1 , θ 2 , . . . , θ n x {{\theta }_{0}},{{\theta }_{1}},{{\theta }_{2}},...,{{\theta }_{{{n}_{x}}}} θ0,θ1,θ2,...,θnx,这样 θ 0 {{\theta }_{0}} θ0就充当了 b b b,这是一个实数,而剩下的 θ 1 {{\theta }_{1}} θ1 直到 θ n x {{\theta }_{{{n}_{x}}}} θnx充当了 w w w,结果就是当你实现你的神经网络时,有一个比较简单的方法是保持 b b b和 w w w分开。但是在这节课里我们不会使用任何这类符号惯例,所以不用去担心。
为了训练逻辑回归模型的参数参数 w w w和参数 b b b我们,需要一个代价函数,通过训练代价函数来得到参数 w w w和参数 b b b。先看一下逻辑回归的输出函数:
为了让模型通过学习调整参数,你需要给予一个 m m m样本的训练集,这会让你在训练集上找到参数 w w w和参数 b b b,,来得到你的输出。
对训练集的预测值,我们将它写成 y ^ \hat{y} y^,我们更希望它会接近于训练集中的 y y y值,为了对上面的公式更详细的介绍,我们需要说明上面的定义是对一个训练样本来说的,这种形式也使用于每个训练样本,我们使用这些带有圆括号的上标来区分索引和样本,训练样本 i i i所对应的预测值是 y ( i ) {{y}^{(i)}} y(i),是用训练样本的 w T x ( i ) + b {{w}^{T}}{{x}^{(i)}}+b wTx(i)+b然后通过sigmoid函数来得到,也可以把 z z z定义为 z ( i ) = w T x ( i ) + b {{z}^{(i)}}={{w}^{T}}{{x}^{(i)}}+b z(i)=wTx(i)+b,我们将使用这个符号 ( i ) (i) (i)注解,上标 ( i ) (i) (i)来指明数据表示 x x x或者 y y y或者 z z z或者其他数据的第 i i i个训练样本,这就是上标 ( i ) (i) (i)的含义。
损失函数:
损失函数又叫做误差函数,用来衡量算法的运行情况,Loss function: L ( y ^ , y ) L\left( \hat{y},y \right) L(y^,y).
我们通过这个 L L L称为的损失函数,来衡量预测输出值和实际值有多接近。一般我们用预测值和实际值的平方差或者它们平方差的一半,但是通常在逻辑回归中我们不这么做,因为当我们在学习逻辑回归参数的时候,会发现我们的优化目标不是凸优化,只能找到多个局部最优值,梯度下降法很可能找不到全局最优值,虽然平方差是一个不错的损失函数,但是我们在逻辑回归模型中会定义另外一个损失函数。
我们在逻辑回归中用到的损失函数是: L ( y ^ , y ) = − y log ( y ^ ) − ( 1 − y ) log ( 1 − y ^ ) L\left( \hat{y},y \right)=-y\log(\hat{y})-(1-y)\log (1-\hat{y}) L(y^,y)=−ylog(y^)−(1−y)log(1−y^)
为什么要用这个函数作为逻辑损失函数?当我们使用平方误差作为损失函数的时候,你会想要让这个误差尽可能地小,对于这个逻辑回归损失函数,我们也想让它尽可能地小,为了更好地理解这个损失函数怎么起作用,我们举两个例子:
当 y = 1 y=1 y=1时损失函数 L = − log ( y ^ ) L=-\log (\hat{y}) L=−log(y^),如果想要损失函数 L L L尽可能得小,那么 y ^ \hat{y} y^就要尽可能大,因为sigmoid函数取值 [ 0 , 1 ] [0,1] [0,1],所以 y ^ \hat{y} y^会无限接近于1。
当 y = 0 y=0 y=0时损失函数 L = − log ( 1 − y ^ ) L=-\log (1-\hat{y}) L=−log(1−y^),如果想要损失函数 L L L尽可能得小,那么 y ^ \hat{y} y^就要尽可能小,因为sigmoid函数取值 [ 0 , 1 ] [0,1] [0,1],所以 y ^ \hat{y} y^会无限接近于0。
在这门课中有很多的函数效果和现在这个类似,就是如果 y y y等于1,我们就尽可能让 y ^ \hat{y} y^变大,如果 y y y等于0,我们就尽可能让 y ^ \hat{y} y^ 变小。
损失函数是在单个训练样本中定义的,它衡量的是算法在单个训练样本中表现如何,为了衡量算法在全部训练样本上的表现如何,我们需要定义一个算法的代价函数,算法的代价函数是对 m m m个样本的损失函数求和然后除以 m m m:
J ( w , b ) = 1 m ∑ i = 1 m L ( y ^ ( i ) , y ( i ) ) = 1 m ∑ i = 1 m ( − y ( i ) log y ^ ( i ) − ( 1 − y ( i ) ) log ( 1 − y ^ ( i ) ) ) J\left( w,b \right)=\frac{1}{m}\sum\limits_{i=1}^{m}{L\left( {{{\hat{y}}}^{(i)}},{{y}^{(i)}} \right)}=\frac{1}{m}\sum\limits_{i=1}^{m}{\left( -{{y}^{(i)}}\log {{{\hat{y}}}^{(i)}}-(1-{{y}^{(i)}})\log (1-{{{\hat{y}}}^{(i)}}) \right)} J(w,b)=m1i=1∑mL(y^(i),y(i))=m1i=1∑m(−y(i)logy^(i)−(1−y(i))log(1−y^(i)))
损失函数只适用于像这样的单个训练样本,而代价函数是参数的总代价,所以在训练逻辑回归模型时候,我们需要找到合适的 w w w和 b b b,来让代价函数 J J J 的总代价降到最低。
梯度下降法可以做什么?
在你测试集上,通过最小化代价函数(成本函数) J ( w , b ) J(w,b) J(w,b)来训练的参数 w w w和 b b b,
如图,在第二行给出和之前一样的逻辑回归算法的代价函数(成本函数)
梯度下降法的形象化说明
在这个图中,横轴表示你的空间参数 w w w和 b b b,在实践中, w w w可以是更高的维度,但是为了更好地绘图,我们定义 w w w和 b b b,都是单一实数,代价函数(成本函数) J ( w , b ) J(w,b) J(w,b)是在水平轴 w w w和 b b b上的曲面,因此曲面的高度就是 J ( w , b ) J(w,b) J(w,b)在某一点的函数值。我们所做的就是找到使得代价函数(成本函数) J ( w , b ) J(w,b) J(w,b)函数值是最小值,对应的参数 w w w和 b b b。
如图,代价函数(成本函数) J ( w , b ) J(w,b) J(w,b)是一个凸函数(convex function),像一个大碗一样。
如图,这就与刚才的图有些相反,因为它是非凸的并且有很多不同的局部最小值。由于逻辑回归的代价函数(成本函数) J ( w , b ) J(w,b) J(w,b)特性,我们必须定义代价函数(成本函数) J ( w , b ) J(w,b) J(w,b)为凸函数。
1. 初始化 w w w和 b b b,
可以用如图那个小红点来初始化参数 w w w和 b b b,也可以采用随机初始化的方法,对于逻辑回归几乎所有的初始化方法都有效,因为函数是凸函数,无论在哪里初始化,应该达到同一点或大致相同的点。
我们以如图的小红点的坐标来初始化参数 w w w和 b b b。
2. 朝最陡的下坡方向走一步,不断地迭代
我们朝最陡的下坡方向走一步,如图,走到了如图中第二个小红点处。
我们可能停在这里也有可能继续朝最陡的下坡方向再走一步,如图,经过两次迭代走到第三个小红点处。
3.直到走到全局最优解或者接近全局最优解的地方
通过以上的三个步骤我们可以找到全局最优解,也就是代价函数(成本函数) J ( w , b ) J(w,b) J(w,b)这个凸函数的最小值点。
梯度下降法的细节化说明(仅有一个参数)
假定代价函数(成本函数) J ( w ) J(w) J(w) 只有一个参数 w w w,即用一维曲线代替多维曲线,这样可以更好画出图像。
迭代就是不断重复做如图的公式:
: = := :=表示更新参数,
a a a 表示学习率(learning rate),用来控制步长(step),即向下走一步的长度 d J ( w ) d w \frac{dJ(w)}{dw} dwdJ(w) 就是函数 J ( w ) J(w) J(w)对 w w w 求导(derivative),在代码中我们会使用 d w dw dw表示这个结果
对于导数更加形象化的理解就是斜率(slope),如图该点的导数就是这个点相切于 J ( w ) J(w) J(w)的小三角形的高除宽。假设我们以如图点为初始化点,该点处的斜率的符号是正的,即 d J ( w ) d w > 0 \frac{dJ(w)}{dw}>0 dwdJ(w)>0,所以接下来会向左走一步。
整个梯度下降法的迭代过程就是不断地向左走,直至逼近最小值点。
假设我们以如图点为初始化点,该点处的斜率的符号是负的,即 d J ( w ) d w < 0 \frac{dJ(w)}{dw}<0 dwdJ(w)<0,所以接下来会向右走一步。
整个梯度下降法的迭代过程就是不断地向右走,即朝着最小值点方向走。
梯度下降法的细节化说明(两个参数)
逻辑回归的代价函数(成本函数) J ( w , b ) J(w,b) J(w,b)是含有两个参数的。
∂ \partial ∂ 表示求偏导符号,可以读作round,
∂ J ( w , b ) ∂ w \frac{\partial J(w,b)}{\partial w} ∂w∂J(w,b) 就是函数 J ( w , b ) J(w,b) J(w,b) 对 w w w 求偏导,在代码中我们会使用 d w dw dw 表示这个结果,
∂ J ( w , b ) ∂ b \frac{\partial J(w,b)}{\partial b} ∂b∂J(w,b) 就是函数 J ( w , b ) J(w,b) J(w,b)对 b b b 求偏导,在代码中我们会使用 d b db db 表示这个结果,
小写字母 d d d 用在求导数(derivative),即函数只有一个参数,
偏导数符号 ∂ \partial ∂ 用在求偏导(partial derivative),即函数含有两个以上的参数。
本节我们讨论怎样通过计算偏导数来实现逻辑回归的梯度下降算法。
假设样本只有两个特征 x 1 {{x}_{1}} x1和 x 2 {{x}_{2}} x2,为了计算 z z z,我们需要输入参数 w 1 {{w}_{1}} w1、 w 2 {{w}_{2}} w2 和 b b b,除此之外还有特征值 x 1 {{x}_{1}} x1和 x 2 {{x}_{2}} x2。因此 z z z的计算公式为:
z = w 1 x 1 + w 2 x 2 + b z={{w}_{1}}{{x}_{1}}+{{w}_{2}}{{x}_{2}}+b z=w1x1+w2x2+b
回想一下逻辑回归的公式定义如下:
y ^ = a = σ ( z ) \hat{y}=a=\sigma (z) y^=a=σ(z)
其中 z = w T x + b z={{w}^{T}}x+b z=wTx+b
σ ( z ) = 1 1 + e − z \sigma \left( z \right)=\frac{1}{1+{{e}^{-z}}} σ(z)=1+e−z1
损失函数:
L ( y ^ ( i ) , y ( i ) ) = − y ( i ) log y ^ ( i ) − ( 1 − y ( i ) ) log ( 1 − y ^ ( i ) ) L( {{{\hat{y}}}^{(i)}},{{y}^{(i)}})=-{{y}^{(i)}}\log {{\hat{y}}^{(i)}}-(1-{{y}^{(i)}})\log (1-{{\hat{y}}^{(i)}}) L(y^(i),y(i))=−y(i)logy^(i)−(1−y(i))log(1−y^(i))
代价函数:
J ( w , b ) = 1 m ∑ i m L ( y ^ ( i ) , y ( i ) ) J\left( w,b \right)=\frac{1}{m}\sum\nolimits_{i}^{m}{L( {{{\hat{y}}}^{(i)}},{{y}^{(i)}})} J(w,b)=m1∑imL(y^(i),y(i))
假设现在只考虑单个样本的情况,单个样本的代价函数定义如下:
L ( a , y ) = − ( y log ( a ) + ( 1 − y ) log ( 1 − a ) ) L(a,y)=-(y\log (a)+(1-y)\log (1-a)) L(a,y)=−(ylog(a)+(1−y)log(1−a))
其中 a a a是逻辑回归的输出, y y y是样本的标签值。现在让我们画出表示这个计算的计算图。
这里先复习下梯度下降法, w w w和 b b b的修正量可以表达如下:
w : = w − a ∂ J ( w , b ) ∂ w w:=w-a \frac{\partial J(w,b)}{\partial w} w:=w−a∂w∂J(w,b), b : = b − a ∂ J ( w , b ) ∂ b b:=b-a\frac{\partial J(w,b)}{\partial b} b:=b−a∂b∂J(w,b)
如图:在这个公式的外侧画上长方形。然后计算:
y ^ = a = σ ( z ) \hat{y}=a=\sigma(z) y^=a=σ(z)
也就是计算图的下一步。最后计算损失函数 L ( a , y ) L(a,y) L(a,y)。
因此,为了使得逻辑回归中最小化代价函数 L ( a , y ) L(a,y) L(a,y),我们需要做的仅仅是修改参数 w w w和 b b b的值。前面我们已经讲解了如何在单个训练样本上计算代价函数的前向步骤。现在让我们来讨论通过反向计算出导数。
因为我们想要计算出的代价函数 L ( a , y ) L(a,y) L(a,y)的导数,首先我们需要反向计算出代价函数 L ( a , y ) L(a,y) L(a,y)关于 a a a的导数,在编写代码时,你只需要用 d a da da 来表示 d L ( a , y ) d a \frac{dL(a,y)}{da} dadL(a,y) 。
通过微积分得到:
d L ( a , y ) d a = − y / a + ( 1 − y ) / ( 1 − a ) \frac{dL(a,y)}{da}=-y/a+(1-y)/(1-a) dadL(a,y)=−y/a+(1−y)/(1−a)
现在可以再反向一步,在编写Python代码时,你只需要用 d z dz dz来表示代价函数 L L L关于 z z z 的导数 d L d z \frac{dL}{dz} dzdL,也可以写成 d L ( a , y ) d z \frac{dL(a,y)}{dz} dzdL(a,y),这两种写法都是正确的。
d L d z = a − y \frac{dL}{dz}=a-y dzdL=a−y 。
因为 d L ( a , y ) d z = d L d z = ( d L d a ) ⋅ ( d a d z ) \frac{dL(a,y)}{dz}=\frac{dL}{dz}=(\frac{dL}{da})\cdot (\frac{da}{dz}) dzdL(a,y)=dzdL=(dadL)⋅(dzda),
并且 d a d z = a ⋅ ( 1 − a ) \frac{da}{dz}=a\cdot (1-a) dzda=a⋅(1−a),
而 d L d a = ( − y a + ( 1 − y ) ( 1 − a ) ) \frac{dL}{da}=(-\frac{y}{a}+\frac{(1-y)}{(1-a)}) dadL=(−ay+(1−a)(1−y)),因此将这两项相乘,得到:
d z = d L ( a , y ) d z = d L d z = ( d L d a ) ⋅ ( d a d z ) = ( − y a + ( 1 − y ) ( 1 − a ) ) ⋅ a ( 1 − a ) = a − y {dz} = \frac{{dL}(a,y)}{{dz}} = \frac{{dL}}{{dz}} = \left( \frac{{dL}}{{da}} \right) \cdot \left(\frac{{da}}{{dz}} \right) = ( - \frac{y}{a} + \frac{(1 - y)}{(1 - a)})\cdot a(1 - a) = a - y dz=dzdL(a,y)=dzdL=(dadL)⋅(dzda)=(−ay+(1−a)(1−y))⋅a(1−a)=a−y
现在进行最后一步反向推导,也就是计算 w w w和 b b b变化对代价函数 L L L的影响,特别地,可以用:
d w 1 = 1 m ∑ i m x 1 ( i ) ( a ( i ) − y ( i ) ) d{{w}_{1}}=\frac{1}{m}\sum\limits_{i}^{m}{x_{1}^{(i)}}({{a}^{(i)}}-{{y}^{(i)}}) dw1=m1i∑mx1(i)(a(i)−y(i))
d w 2 = 1 m ∑ i m x 2 ( i ) ( a ( i ) − y ( i ) ) d{{w}_{2}}=\frac{1}{m}\sum\limits_{i}^{m}{x_{2}^{(i)}}({{a}^{(i)}}-{{y}^{(i)}}) dw2=m1i∑mx2(i)(a(i)−y(i))
d b = 1 m ∑ i m ( a ( i ) − y ( i ) ) db=\frac{1}{m}\sum\limits_{i}^{m}{({{a}^{(i)}}-{{y}^{(i)}})} db=m1i∑m(a(i)−y(i))
d w 1 d{{w}_{1}} dw1 表示 ∂ L ∂ w 1 = x 1 ⋅ d z \frac{\partial L}{\partial {{w}_{1}}}={{x}_{1}}\cdot dz ∂w1∂L=x1⋅dz,
d w 2 d{{w}_{\text{2}}} dw2 表示 ∂ L ∂ w 2 = x 2 ⋅ d z \frac{\partial L}{\partial {{w}_{2}}}={{x}_{2}}\cdot dz ∂w2∂L=x2⋅dz,
d b = d z db=dz db=dz。
因此,关于单个样本的梯度下降算法,你所需要做的就是如下的事情:
使用公式 d z = ( a − y ) dz=(a-y) dz=(a−y)计算 d z dz dz,
使用 d w 1 = x 1 ⋅ d z d{{w}_{1}}={{x}_{1}}\cdot dz dw1=x1⋅dz 计算 d w 1 d{{w}_{1}} dw1, d w 2 = x 2 ⋅ d z d{{w}_{2}}={{x}_{2}}\cdot dz dw2=x2⋅dz计算 d w 2 d{{w}_{2}} dw2,
d b = d z db=dz db=dz 来计算 d b db db,
然后:
更新 w 1 = w 1 − a d w 1 {{w}_{1}}={{w}_{1}}-a d{{w}_{1}} w1=w1−adw1,
更新 w 2 = w 2 − a d w 2 {{w}_{2}}={{w}_{2}}-a d{{w}_{2}} w2=w2−adw2,
更新 b = b − α d b b=b-\alpha db b=b−αdb。
这就是关于单个样本实例的梯度下降算法中参数更新一次的步骤。
现在你已经知道了怎样计算导数,并且实现针对单个训练样本的逻辑回归的梯度下降算法。但是,训练逻辑回归模型不仅仅只有一个训练样本,而是有 m m m个训练样本的整个训练集。
你已经知道如何计算导数,以及应用梯度下降在逻辑回归的一个训练样本上。现在我们想要把它应用在 m m m个训练样本上。
首先,让我们时刻记住有关于损失函数 J ( w , b ) J(w,b) J(w,b)的定义。
J ( w , b ) = 1 m ∑ i = 1 m L ( a ( i ) , y ( i ) ) J(w,b)=\frac{1}{m}\sum\limits_{i=1}^{m}{L({{a}^{(i)}},{{y}^{(i)}})} J(w,b)=m1i=1∑mL(a(i),y(i))
当你的算法输出关于样本 y y y的 a ( i ) {{a}^{(i)}} a(i), a ( i ) {{a}^{(i)}} a(i)是训练样本的预测值,即: σ ( z ( i ) ) = σ ( w T x ( i ) + b ) \sigma ( {{z}^{(i)}})=\sigma( {{w}^{T}}{{x}^{\left( i \right)}}+b) σ(z(i))=σ(wTx(i)+b)。
所以我们在前面的幻灯中展示的是对于任意单个训练样本,如何计算微分当你只有一个训练样本。因此 d w 1 d{{w}_{1}} dw1, d w 2 d{{w}_{\text{2}}} dw2和 d b db db 添上上标 i i i表示你求得的相应的值。如果你面对的是我们在之前的幻灯中演示的那种情况,但只使用了一个训练样本 ( x ( i ) , y ( i ) ) ({{x}^{(i)}},{{y}^{(i)}}) (x(i),y(i))。
现在你知道带有求和的全局代价函数,实际上是1到 m m m项各个损失的平均。 所以它表明全局代价函数对 w 1 {{w}_{1}} w1的微分,对 w 1 {{w}_{1}} w1的微分也同样是各项损失对 w 1 {{w}_{1}} w1微分的平均。
但之前我们已经演示了如何计算这项,即之前幻灯中演示的如何对单个训练样本进行计算。所以你真正需要做的是计算这些微分,如我们在之前的训练样本上做的。并且求平均,这会给你全局梯度值,你能够把它直接应用到梯度下降算法中。
所以这里有很多细节,但让我们把这些装进一个具体的算法。同时你需要一起应用的就是逻辑回归和梯度下降。
我们初始化 J = 0 , d w 1 = 0 , d w 2 = 0 , d b = 0 J=0,d{{w}_{1}}=0,d{{w}_{2}}=0,db=0 J=0,dw1=0,dw2=0,db=0
代码流程:
J=0;dw1=0;dw2=0;db=0;
for i = 1 to m
z(i) = wx(i)+b;
a(i) = sigmoid(z(i));
J += -[y(i)log(a(i))+(1-y(i))log(1-a(i));
dz(i) = a(i)-y(i);
dw1 += x1(i)dz(i);
dw2 += x2(i)dz(i);
db += dz(i);
J/= m;
dw1/= m;
dw2/= m;
db/= m;
w=w-alpha*dw
b=b-alpha*db
幻灯片上只应用了一步梯度下降。因此你需要重复以上内容很多次,以应用多次梯度下降。。
但这种计算中有两个缺点,也就是说应用此方法在逻辑回归上你需要编写两个for循环。第一个for循环是一个小循环遍历 m m m个训练样本,第二个for循环是一个遍历所有特征。这个例子中我们只有2个特征,所以 n n n等于2并且 n x {{n}_{x}} nx 等于2。 但如果你有更多特征,你开始编写你的因此 d w 1 d{{w}_{1}} dw1, d w 2 d{{w}_{2}} dw2,你有相似的计算从 d w 3 d{{w}_{3}} dw3一直下去到 d w n d{{w}_{n}} dwn。。
当你应用深度学习算法,你会发现在代码中显式地使用for循环使你的算法很低效,同时在深度学习领域会有越来越大的数据集。所以能够应用你的算法且没有显式的for循环会是重要的,并且会帮助你适用于更大的数据集。所以这里有一些叫做向量化技术,它可以允许你的代码摆脱这些显式的for循环。
向量化是非常基础的去除代码中for循环的艺术,在深度学习安全领域、深度学习实践中,你会经常发现自己训练大数据集,因为深度学习算法处理大数据集效果很棒,所以你的代码运行速度非常重要,否则如果在大数据集上,你的代码可能花费很长时间去运行,你将要等待非常长的时间去得到结果。所以在深度学习领域,运行向量化是一个关键的技巧,让我们举个栗子说明什么是向量化。
在逻辑回归中你需要去计算 z = w T x + b z={{w}^{T}}x+b z=wTx+b, w w w、 x x x都是列向量。如果你有很多的特征那么就会有一个非常大的向量,所以 w ∈ R n x w\in {{\mathbb{R}}^{{{n}_{x}}}} w∈Rnx , x ∈ R n x x\in{{\mathbb{R}}^{{{n}_{x}}}} x∈Rnx,所以如果你想使用非向量化方法去计算 w T x {{w}^{T}}x wTx,你需要用如下方式(python)
z=0
for i in range(n_x)
z+=w[i]*x[i]
z+=b
这是一个非向量化的实现,你会发现这真的很慢,作为一个对比,向量化实现将会非常直接计算 w T x {{w}^{T}}x wTx,代码如下:
z=np.dot(w,x)+b
这是向量化计算 w T x {{w}^{T}}x wTx的方法,你将会发现这个非常快。
让我们用一个小例子说明一下,在我的我将会写一些代码(以下为教授在他的Jupyter notebook上写的Python代码,)
import numpy as np #导入numpy库
a = np.array([1,2,3,4]) #创建一个数据a
print(a)
# [1 2 3 4]
import time #导入时间库
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循环的版本的时间
在两个方法中,向量化和非向量化计算了相同的值,如你所见,向量化版本花费了1.5毫秒,非向量化版本的for循环花费了大约几乎500毫秒,非向量化版本多花费了300倍时间。所以在这个例子中,仅仅是向量化你的代码,就会运行300倍快。这意味着如果向量化方法需要花费一分钟去运行的数据,for循环将会花费5个小时去运行。
一句话总结,以上都是再说和for循环相比,向量化可以快速得到结果。
你可能听过很多类似如下的话,“大规模的深度学习使用了GPU或者图像处理单元实现”,但是我做的所有的案例都是在jupyter notebook上面实现,这里只有CPU,CPU和GPU都有并行化的指令,他们有时候会叫做SIMD指令,这个代表了一个单独指令多维数据,这个的基础意义是,如果你使用了built-in函数,像np.function
或者并不要求你实现循环的函数,它可以让python的充分利用并行化计算,这是事实在GPU和CPU上面计算,GPU更加擅长SIMD计算,但是CPU事实上也不是太差,可能没有GPU那么擅长吧。接下来你将看到向量化怎么能够加速你的代码,经验法则是,无论什么时候,避免使用明确的for循环。
你知道了怎样通过numpy内置函数和避开显式的循环(loop)的方式进行向量化,从而有效提高代码速度。
经验提醒我,当我们在写神经网络程序时,或者在写逻辑(logistic)回归,或者其他神经网络模型时,应该避免写循环(loop)语句。虽然有时写循环(loop)是不可避免的,但是我们可以使用比如numpy的内置函数或者其他办法去计算。当你这样使用后,程序效率总是快于循环(loop)。
让我们看另外一个例子。如果你想计算向量 u = A v u=Av u=Av,这时矩阵乘法定义为: u i = ∑ j A ij v i u_{i} =\sum_{j}^{}{A_{\text{ij}}v_{i}} ui=∑jAijvi,这取决于你怎么定义 u i u_{i} ui值。同样使用非向量化实现, u = n p . z e r o s ( n , 1 ) u=np.zeros(n,1) u=np.zeros(n,1), 并且通过两层循环 f o r ( i ) : f o r ( j ) : for(i):for(j): for(i):for(j):,得到 u [ i ] = u [ i ] + A [ i ] [ j ] ∗ v [ j ] u[i]=u[i]+A[i][j]*v[j] u[i]=u[i]+A[i][j]∗v[j] 。现在就有了 i i i 和 j j j 的两层循环,这就是非向量化。向量化方式就可以用 u = n p . d o t ( A , v ) u=np.dot(A,v) u=np.dot(A,v),右边这种向量化实现方式,消除了两层循环使得代码运行速度更快。
下面通过另一个例子继续了解向量化。如果你已经有一个向量 v v v,并且想要对向量 v v v的每个元素做指数操作,得到向量 u u u等于 e e e的 v 1 v_1 v1, e e e的 v 2 v_2 v2,一直到 e e e的 v n v_n vn次方。这里是非向量化的实现方式,首先你初始化了向量 u = n p . z e r o s ( n , 1 ) u=np.zeros(n,1) u=np.zeros(n,1),并且通过循环依次计算每个元素。但事实证明可以通过python的numpy内置函数,帮助你计算这样的单个函数。所以我会引入import numpy as np
,执行 u = n p . e x p ( v ) u=np.exp(v) u=np.exp(v) 命令。注意到,在之前有循环的代码中,这里仅用了一行代码,向量 v v v作为输入, u u u作为输出。你已经知道为什么需要循环,并且通过右边代码实现,效率会明显的快于循环方式。
事实上,numpy库有很多向量函数。比如 u=np.log
是计算对数函数( l o g log log)、 np.abs()
是计算数据的绝对值、np.maximum()
计算元素 y y y中的最大值,你也可以 np.maximum(v,0)
、 v ∗ ∗ 2 v**2 v∗∗2 代表获得元素 y y y 每个值得平方、 1 v \frac{1}{v} v1 获取元素 y y y 的倒数等等。所以当你想写循环时候,检查numpy是否存在类似的内置函数,从而避免使用循环(loop)方式。
那么,将刚才所学到的内容,运用在逻辑回归的梯度下降上,看看我们是否能简化两个计算过程中的某一步。这是我们逻辑回归的求导代码,有两层循环。在这例子我们有 n n n个特征值。如果你有超过两个特征时,需要循环 d w 1 dw_1 dw1 、 d w 2 dw_2 dw2 、 d w 3 dw_3 dw3 等等。所以 j j j 的实际值是1、2 和 n x n_x nx,就是你想要更新的值。所以我们想要消除第二循环,在这一行,这样我们就不用初始化 d w 1 dw_1 dw1 , d w 2 dw_2 dw2 都等于0。去掉这些,而是定义 d w dw dw 为一个向量,设置 u = n p . z e r o s ( n ( x ) , 1 ) u=np.zeros(n(x),1) u=np.zeros(n(x),1)。定义了一个 x x x行的一维向量,从而替代循环。我们仅仅使用了一个向量操作 d w = d w + x ( i ) d z ( i ) dw=dw+x^{(i)}dz^{(i)} dw=dw+x(i)dz(i) 。最后,我们得到 d w = d w / m dw=dw/m dw=dw/m 。现在我们通过将两层循环转成一层循环,我们仍然还有这个循环训练样本。
我们已经讨论过向量化是如何显著加速你的代码,我们将讨论如何实现逻辑回归的向量化计算。这样就能处理整个数据集,甚至不会用一个明确的for循环就能实现对于整个数据集梯度下降算法的优化。我对这项技术感到非常激动,并且当我们后面谈到神经网络时同样也不会用到一个明确的 for 循环。
让我们开始吧,首先我们回顾一下逻辑回归的前向传播步骤。所以,如果你有 m m m 个训练样本,然后对第一个样本进行预测,你需要这样计算。计算 z z z,我正在使用这个熟悉的公式 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 个训练样本,你可能需要这样做 m m m 次,可以看出,为了完成前向传播步骤,即对我们的 m m m 个样本都计算出预测值。有一个办法可以并且不需要任何一个明确的for循环。让我们来看一下你该怎样做。
首先,回忆一下我们曾经定义了一个矩阵 X X X 作为你的训练输入,(如下图中蓝色 X X X )像这样在不同的列中堆积在一起。这是一个 n x n_x nx 行 m m m 列的矩阵。我现在将它写为Python numpy的形式 ( n x , m ) (n_{x},m) (nx,m) ,这只是表示 X X X 是一个 n x n_x nx 乘以 m m m 的矩阵 R n x × m R^{n_x \times m} Rnx×m。
现在我首先想做的是告诉你该如何在一个步骤中计算 z 1 z_1 z1、 z 2 z_2 z2 、 z 3 z_3 z3 等等。实际上,只用了一行代码。所以,我打算先构建一个 1 × m 1\times m 1×m 的矩阵,实际上它是一个行向量,同时我准备计算 z ( 1 ) z^{(1)} z(1), z ( 2 ) z^{(2)} z(2) ……一直到 z ( m ) z^{(m)} z(m) ,所有值都是在同一时间内完成。结果发现它可以表达为 w w w 的转置乘以大写矩阵 x x x 然后加上向量 [ b b . . . b ] [b b...b] [bb...b] , ( [ z ( 1 ) z ( 2 ) . . . z ( m ) ] = w T + [ b b . . . b ] ) ([z^{(1)} z^{(2)}...z^{(m)}]=w^{T}+[bb...b]) ([z(1)z(2)...z(m)]=wT+[bb...b]) 。 [ b b . . . b ] [b b...b] [bb...b] 是一个 1 × m 1\times m 1×m 的向量或者 1 × m 1\times m 1×m 的矩阵或者是一个 m m m 维的行向量。所以希望你熟悉矩阵乘法,你会发现的 w w w 转置乘以 x ( 1 ) x^{(1)} x(1) , x ( 2 ) x^{(2)} x(2) 一直到 x ( m ) x^{(m)} x(m) 。所以 w w w 转置可以是一个行向量。所以第一项 w T X w^{T}X wTX 将计算 w w w 的转置乘以 x ( 1 ) x^{(1)} x(1), w w w 转置乘以 x ( 2 ) x^{(2)} x(2) 等等。然后我们加上第二项 [ b b . . . b ] [b b...b] [bb...b] ,你最终将 b b b 加到了每个元素上。所以你最终得到了另一个 1 × m 1\times m 1×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 ] [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] [z(1)z(2)...z(m)]=wTX+[bb...b]=[wTx(1)+b,wTx(2)+b...wTx(m)+b] 。
w T x ( 1 ) + b w^{T}x^{(1)}+b wTx(1)+b 这是第一个元素, w T x ( 2 ) + b w^{T}x^{(2)}+b wTx(2)+b 这是第二个元素, w T x ( m ) + b w^{T}x^{(m)}+b wTx(m)+b 这是第 m m m 个元素。
如果你参照上面的定义,第一个元素恰好是 z ( 1 ) z^{(1)} z(1) 的定义,第二个元素恰好是 z ( 2 ) z^{(2)} z(2) 的定义,等等。所以,因为 X X X是一次获得的,当你得到你的训练样本,一个一个横向堆积起来,这里我将 [ z ( 1 ) z ( 2 ) . . . z ( m ) ] [z^{(1)} z^{(2)} ... z^{(m)}] [z(1)z(2)...z(m)] 定义为大写的 Z Z Z ,你用小写 z z z 表示并将它们横向排在一起。所以当你将不同训练样本对应的小写 x x x 横向堆积在一起时得到大写变量 X X X 并且将小写变量也用相同方法处理,将它们横向堆积起来,你就得到大写变量 Z Z Z 。结果发现,为了计算 W T X + [ b b . . . b ] W^{T}X+[b b ... b] WTX+[bb...b] ,numpy命令是 Z = n p . d o t ( w . T , X ) + b Z=np.dot(w.T,X)+b Z=np.dot(w.T,X)+b。它只用这一行代码,你可以计算大写的 Z Z Z,而大写 Z Z Z 是一个包含所有小写 z ( 1 ) z^{(1)} z(1) 到 z ( m ) z^{(m)} z(m) 的 1 × m 1\times m 1×m 的矩阵。这就是 Z Z Z 的内容,关于变量 a a a 又是如何呢?
我们接下来要做的就是找到一个同时计算 [ a ( 1 ) a ( 2 ) . . . a ( m ) ] [a^{(1)} a^{(2)} ... a^{(m)}] [a(1)a(2)...a(m)] 的方法。就像把小写 x x x 堆积起来得到大写 X X X 和横向堆积小写 z z z 得到大写 Z Z Z 一样,堆积小写变量 a a a 将形成一个新的变量,我们将它定义为大写 A A A。在编程作业中,你将看到怎样用一个向量在sigmoid函数中进行计算。所以sigmoid函数中输入大写 Z Z Z 作为变量并且非常高效地输出大写 A A A。你将在编程作业中看到它的细节。
总结一下,在这张幻灯片中我们已经看到,不需要for循环,利用 m m m 个训练样本一次性计算出小写 z z z 和小写 a a a,用一行代码即可完成。
Z = np.dot(w.T,X) + b
这一行代码: A = [ a ( 1 ) a ( 2 ) . . . a ( m ) ] = σ ( Z ) A=[a^{(1)} a^{(2)} ... a^{(m)}]=\sigma (Z) A=[a(1)a(2)...a(m)]=σ(Z) ,通过恰当地运用 σ \sigma σ一次性计算所有 a a a。这就是在同一时间内你如何完成一个所有 m m m 个训练样本的前向传播向量化计算。
概括一下,你刚刚看到如何利用向量化在同一时间内高效地计算所有的激活函数的所有 a a a值。接下来,可以证明,你也可以利用向量化高效地计算反向传播并以此来计算梯度。
注:本节中大写字母代表向量,小写字母代表元素
如何向量化计算的同时,对整个训练集预测结果 a a a,这是我们之前已经讨论过的内容。在本次视频中我们将学习如何向量化地计算 m m m个训练数据的梯度,本次视频的重点是如何同时计算 m m m 个数据的梯度,并且实现一个非常高效的逻辑回归算法(Logistic Regression)。
之前我们在讲梯度计算的时候,列举过几个例子, d z ( 1 ) = a ( 1 ) − y ( 1 ) dz^{(1)}=a^{(1)}-y^{(1)} dz(1)=a(1)−y(1), d z ( 2 ) = a ( 2 ) − y ( 2 ) dz^{(2)}=a^{(2)}-y^{(2)} dz(2)=a(2)−y(2) ……等等一系列类似公式。现在,对 m m m个训练数据做同样的运算,我们可以定义一个新的变量 d Z = [ d z ( 1 ) , d z ( 2 ) . . . d z ( m ) ] dZ=[dz^{(1)} ,dz^{(2)} ... dz^{(m)}] dZ=[dz(1),dz(2)...dz(m)]
,所有的 d z dz dz 变量横向排列,因此, d Z dZ dZ 是一个 1 × m 1\times m 1×m 的矩阵,或者说,一个 m m m 维行向量。在之前的幻灯片中,我们已经知道如何计算 A A A,即 [ a ( 1 ) , a ( 2 ) . . . a ( m ) ] [a^{(1)},a^{(2)} ... a^{(m)}] [a(1),a(2)...a(m)],我们需要找到这样的一个行向量 Y = [ y ( 1 ) y ( 2 ) . . . y ( m ) ] Y=[y^{(1)} y^{(2)} ... y^{(m)}] Y=[y(1)y(2)...y(m)] ,由此,我们可以这样计算 d Z = A − Y = [ a ( 1 ) − y ( 1 ) a ( 2 ) − y ( 2 ) . . . a ( m ) − y ( m ) ] dZ=A-Y=[a^{(1)}-y^{(1)} a^{(2)}-y^{(2)} ... a^{(m)}-y^{(m)}] dZ=A−Y=[a(1)−y(1)a(2)−y(2)...a(m)−y(m)],不难发现第一个元素就是 d z ( 1 ) dz^{(1)} dz(1),第二个元素就是 d z ( 2 ) dz^{(2)} dz(2) ……所以我们现在仅需一行代码,就可以同时完成这所有的计算。
在之前的实现中,我们已经去掉了一个for循环,但我们仍有一个遍历训练集的循环,如下所示:
d w = 0 dw=0 dw=0
d w + = x ( 1 ) ∗ d z ( 1 ) dw + = x^{(1)}*{dz}^{(1)} dw+=x(1)∗dz(1)
d w + = x ( 2 ) ∗ d z ( 2 ) dw + = x^{(2)}\ *dz^{(2)} dw+=x(2) ∗dz(2)
………….
d w + = x ( m ) ∗ d z ( m ) dw + = x^{(m)}*{dz}^{(m)} dw+=x(m)∗dz(m)
d w = d w m dw = \frac{{dw}}{m} dw=mdw
d b = 0 db = 0 db=0
d b + = d z ( 1 ) db + = {dz}^{(1)} db+=dz(1)
d b + = d z ( 2 ) db + = {dz}^{(2)} db+=dz(2)
………….
d b + = d z ( m ) db + = dz^{(m)} db+=dz(m)
d b = d b m db = \frac{{db}}{m} db=mdb
上述(伪)代码就是我们在之前实现中做的,我们已经去掉了一个for循环,但用上述方法计算 d w dw dw 仍然需要一个循环遍历训练集,我们现在要做的就是将其向量化!
首先我们来看 d b db db,不难发现 d b = 1 m ∑ i = 1 m d z ( i ) db=\frac{1}{m}\sum_{i=1}^{m}dz^{(i)} db=m1i=1∑mdz(i)
之前的讲解中,我们知道所有的 d z i ) dz^{i)} dzi)已经组成一个行向量 d Z dZ dZ了,所以在Python中,我们很容易地想到 d b = 1 m ∗ n p . s u m ( d Z ) db=\frac{1}{m}*np.sum(dZ) db=m1∗np.sum(dZ)接下来看 d w dw dw,我们先写出它的公式 d w = 1 m ∗ X ∗ d z T dw=\frac{1}{m}*X*dz^{T} dw=m1∗X∗dzT
其中, X X X 是一个行向量。因此展开后 d w = 1 m ∗ ( x ( 1 ) d z ( 1 ) + x ( 2 ) d z ( 2 ) + . . . + x m d z m ) dw=\frac{1}{m}*(x^{(1)}dz^{(1)}+x^{(2)}dz^{(2)}+...+x^{m}dz^{m}) dw=m1∗(x(1)dz(1)+x(2)dz(2)+...+xmdzm) 因此我们可以仅用两行代码进行计算: d b = 1 m ∗ n p . s u m ( d Z ) db=\frac{1}{m}*np.sum(dZ) db=m1∗np.sum(dZ) d w = 1 m ∗ X ∗ d z T dw=\frac{1}{m}*X*dz^{T} dw=m1∗X∗dzT这样,我们就避免了在训练集上使用for循环。
现在,让我们回顾一下,看看我们之前怎么实现的逻辑回归,可以发现,没有向量化是非常低效的,如下图所示代码:
**
我们的目标是不使用for循环,而是向量,我们可以这么做:
Z = w T X + b = n p . d o t ( w . T , X ) + b Z = w^{T}X + b = np.dot( w.T,X)+b Z=wTX+b=np.dot(w.T,X)+b
A = σ ( Z ) A = \sigma( Z ) A=σ(Z)
d Z = A − Y dZ = A - Y dZ=A−Y
d w = 1 m ∗ X ∗ d z T {{dw} = \frac{1}{m}*X*dz^{T}\ } dw=m1∗X∗dzT
d b = 1 m ∗ n p . s u m ( d Z ) db= \frac{1}{m}*np.sum( dZ) db=m1∗np.sum(dZ)
w : = w − a ∗ d w w: = w - a*dw w:=w−a∗dw
b : = b − a ∗ d b b: = b - a*db b:=b−a∗db
现在我们利用前五个公式完成了前向和后向传播,也实现了对所有训练样本进行预测和求导,再利用后两个公式,梯度下降更新参数。我们的目的是不使用for循环,所以我们就通过一次迭代实现一次梯度下降,但如果你希望多次迭代进行梯度下降,那么仍然需要for循环,放在最外层。不过我们还是觉得一次迭代就进行一次梯度下降,避免使用任何循环比较舒服一些。
最后,我们得到了一个高度向量化的、非常高效的逻辑回归的梯度下降算法。
我们已经分析了逻辑回归的损失函数表达式,在这节我将给出一个简洁的证明来说明逻辑回归的损失函数为什么是这种形式。
回想一下,在逻辑回归中,需要预测的结果 y ^ \hat{y} y^,可以表示为 y ^ = σ ( w T x + b ) \hat{y}=\sigma(w^{T}x+b) y^=σ(wTx+b), σ \sigma σ是我们熟悉的 S S S型函数 σ ( z ) = σ ( w T x + b ) = 1 1 + e − z \sigma(z)=\sigma(w^{T}x+b)=\frac{1}{1+e^{-z}} σ(z)=σ(wTx+b)=1+e−z1 。我们约定 y ^ = p ( y = 1 ∣ x ) \hat{y}=p(y=1|x) y^=p(y=1∣x) ,即算法的输出 y ^ \hat{y} y^ 是给定训练样本 x x x 条件下 y y y 等于1的概率。换句话说,如果 y = 1 y=1 y=1,在给定训练样本 x x x 条件下 y = y ^ y=\hat{y} y=y^;反过来说,如果 y = 0 y=0 y=0,在给定训练样本 x x x条件下 y y y 等于1减去 y ^ ( y = 1 − y ^ ) \hat{y}(y=1-\hat{y}) y^(y=1−y^),因此,如果 y ^ \hat{y} y^ 代表 y = 1 y=1 y=1 的概率,那么 1 − y ^ 1-\hat{y} 1−y^就是 y = 0 y=0 y=0的概率。接下来,我们就来分析这两个条件概率公式。
这两个条件概率公式定义形式为 p ( y ∣ x ) p(y|x) p(y∣x)并且代表了 y = 0 y=0 y=0 或者 y = 1 y=1 y=1 这两种情况,我们可以将这两个公式合并成一个公式。需要指出的是我们讨论的是二分类问题的损失函数,因此, y y y的取值只能是0或者1。上述的两个条件概率公式可以合并成如下公式:
p ( y ∣ x ) = y ^ y ( 1 − y ^ ) ( 1 − y ) p(y|x)={\hat{y}}^{y}{(1-\hat{y})}^{(1-y)} p(y∣x)=y^y(1−y^)(1−y)
接下来我会解释为什么可以合并成这种形式的表达式: ( 1 − y ^ ) (1-\hat{y}) (1−y^)的 ( 1 − y ) (1-y) (1−y)次方这行表达式包含了上面的两个条件概率公式,我来解释一下为什么。
第一种情况,假设 y = 1 y=1 y=1,由于 y = 1 y=1 y=1,那么 ( y ^ ) y = y ^ {(\hat{y})}^{y}=\hat{y} (y^)y=y^,因为 y ^ \hat{y} y^的1次方等于 y ^ \hat{y} y^, 1 − ( 1 − y ^ ) ( 1 − y ) 1-{(1-\hat{y})}^{(1-y)} 1−(1−y^)(1−y)的指数项 ( 1 − y ) (1-y) (1−y)等于0,由于任何数的0次方都是1, y ^ \hat{y} y^乘以1等于 y ^ \hat{y} y^。因此当 y = 1 y=1 y=1时 p ( y ∣ x ) = y ^ p(y|x)=\hat{y} p(y∣x)=y^(图中绿色部分)。
第二种情况,当 y = 0 y=0 y=0 时 p ( y ∣ x ) p(y|x) p(y∣x) 等于多少呢?
假设 y = 0 y=0 y=0, y ^ \hat{y} y^的 y y y次方就是 y ^ \hat{y} y^ 的0次方,任何数的0次方都等于1,因此 p ( y ∣ x ) = 1 × ( 1 − y ^ ) 1 − y p(y|x)=1×{(1-\hat{y})}^{1-y} p(y∣x)=1×(1−y^)1−y ,前面假设 y = 0 y=0 y=0 因此 ( 1 − y ) (1-y) (1−y)就等于1,因此 p ( y ∣ x ) = 1 × ( 1 − y ^ ) p(y|x)=1×(1-\hat{y}) p(y∣x)=1×(1−y^)。因此在这里当 y = 0 y=0 y=0时, p ( y ∣ x ) = 1 − y ^ p(y|x)=1-\hat{y} p(y∣x)=1−y^。这就是这个公式(第二个公式,图中紫色字体部分)的结果。
因此,刚才的推导表明 p ( y ∣ x ) = y ^ ( y ) ( 1 − y ^ ) ( 1 − y ) p(y|x)={\hat{y}}^{(y)}{(1-\hat{y})}^{(1-y)} p(y∣x)=y^(y)(1−y^)(1−y),就是 p ( y ∣ x ) p(y|x) p(y∣x) 的完整定义。由于 log 函数是严格单调递增的函数,最大化 l o g ( p ( y ∣ x ) ) log(p(y|x)) log(p(y∣x)) 等价于最大化 p ( y ∣ x ) p(y|x) p(y∣x) 并且地计算 p ( y ∣ x ) p(y|x) p(y∣x) 的 log对数,就是计算 l o g ( y ^ ( y ) ( 1 − y ^ ) ( 1 − y ) ) log({\hat{y}}^{(y)}{(1-\hat{y})}^{(1-y)}) log(y^(y)(1−y^)(1−y)) (其实就是将 p ( y ∣ x ) p(y|x) p(y∣x) 代入),通过对数函数化简为: y l o g y ^ + ( 1 − y ) l o g ( 1 − y ^ ) ylog\hat{y}+(1-y)log(1-\hat{y}) ylogy^+(1−y)log(1−y^)
而这就是我们前面提到的损失函数的负数 ( − L ( y ^ , y ) ) (-L(\hat{y},y)) (−L(y^,y)) ,前面有一个负号的原因是当你训练学习算法时需要算法输出值的概率是最大的(以最大的概率预测这个值),然而在逻辑回归中我们需要最小化损失函数,因此最小化损失函数与最大化条件概率的对数 l o g ( p ( y ∣ x ) ) log(p(y|x)) log(p(y∣x)) 关联起来了,因此这就是单个训练样本的损失函数表达式。
在 m m m个训练样本的整个训练集中又该如何表示呢,让我们一起来探讨一下。
让我们一起来探讨一下,整个训练集中标签的概率,更正式地来写一下。假设所有的训练样本服从同一分布且相互独立,也即独立同分布的,所有这些样本的联合概率就是每个样本概率的乘积:
P ( labels in training set ) = ∏ i = 1 m P ( y ( i ) ∣ x ( i ) ) P\left(\text{labels in training set} \right) = \prod_{i =1}^{m}{P(y^{(i)}|x^{(i)})} P(labels in training set)=∏i=1mP(y(i)∣x(i))。
如果你想做最大似然估计,需要寻找一组参数,使得给定样本的观测值概率最大,但令这个概率最大化等价于令其对数最大化,在等式两边取对数:
l o g p ( labels in training set ) = l o g ∏ i = 1 m P ( y ( i ) ∣ x ( i ) ) = ∑ i = 1 m l o g P ( y ( i ) ∣ x ( i ) ) = ∑ i = 1 m − L ( y ^ ( i ) , y ( i ) ) logp\left( \text{labels in training set} \right) = log\prod_{i =1}^{m}{P(y^{(i)}|x^{(i)})} = \sum_{i = 1}^{m}{logP(y^{(i)}|x^{(i)})} = \sum_{i =1}^{m}{- L(\hat y^{(i)},y^{(i)})} logp(labels in training set)=log∏i=1mP(y(i)∣x(i))=∑i=1mlogP(y(i)∣x(i))=∑i=1m−L(y^(i),y(i))
在统计学里面,有一个方法叫做最大似然估计,即求出一组参数,使这个式子取最大值,也就是说,使得这个式子取最大值, ∑ i = 1 m − L ( y ^ ( i ) , y ( i ) ) \sum_{i= 1}^{m}{- L(\hat y^{(i)},y^{(i)})} ∑i=1m−L(y^(i),y(i)),可以将负号移到求和符号的外面, − ∑ i = 1 m L ( y ^ ( i ) , y ( i ) ) - \sum_{i =1}^{m}{L(\hat y^{(i)},y^{(i)})} −∑i=1mL(y^(i),y(i)),这样我们就推导出了前面给出的logistic回归的成本函数 J ( w , b ) = ∑ i = 1 m L ( y ^ ( i ) , y ( ^ i ) ) J(w,b)= \sum_{i = 1}^{m}{L(\hat y^{(i)},y^{\hat( i)})} J(w,b)=∑i=1mL(y^(i),y(^i))。
由于训练模型时,目标是让成本函数最小化,所以我们不是直接用最大似然概率,要去掉这里的负号,最后为了方便,可以对成本函数进行适当的缩放,我们就在前面加一个额外的常数因子 1 m \frac{1}{m} m1,即: J ( w , b ) = 1 m ∑ i = 1 m L ( y ^ ( i ) , y ( i ) ) J(w,b)= \frac{1}{m}\sum_{i = 1}^{m}{L(\hat y^{(i)},y^{(i)})} J(w,b)=m1∑i=1mL(y^(i),y(i))。
参考:吴恩达deeplearning.ai系列课程;黄海广深度学习笔记