http://www.ai-start.com/dl2017/html/lesson1-week2.html

神经网络的编程基础(Basics of Neural Network programming)

二分类(Binary Classification)

我们来看看一张图片在计算机中是如何表示的,为了保存一张图片,需要保存三个矩阵,它们分别对应图片中的红、绿、蓝三种颜色通道,如果你的图片大小为64x64像素,那么你就有三个规模为64x64的矩阵,分别对应图片中红、绿、蓝三种像素的强度值。为了便于表示,这里我画了三个很小的矩阵,注意它们的规模为5x4 而不是64x64,如下图所示:

为了把这些像素值放到一个特征向量中,我们需要把这些像素值提取出来,然后放入一个特征向量xxx。为了把这些像素值转换为特征向量 xxx,我们需要像下面这样定义一个特征向量 xxx 来表示这张图片,我们把所有的像素都取出来,例如255、231等等,直到取完所有的红色像素,接着最后是255、134、…、255、134等等,直到得到一个特征向量,把图片中所有的红、绿、蓝像素值都列出来。如果图片的大小为64x64像素,那么向量 xxx 的总维度,将是64乘以64乘以3,这是三个像素矩阵中像素的总量。在这个例子中结果为12,288。现在我们用nx=12,288n_x=12,288nx​=12,288,来表示输入特征向量的维度,有时候为了简洁,我会直接用小写的nnn来表示输入特征向量xxx的维度。所以在二分类问题中,我们的目标就是习得一个分类器,它以图片的特征向量作为输入,然后预测输出结果yyy为1还是0,也就是预测图片中是否有猫:

接下来我们说明一些在余下课程中,需要用到的一些符号。

符号定义 :

xxx:表示一个nxn_xnx​维数据,为输入数据,维度为(nx,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)):表示第iii组数据,可能是训练数据,也可能是测试数据,此处默认为训练数据;

X=[x(1),x(2),...,x(m)]X=[x^{(1)},x^{(2)},...,x^{(m)}]X=[x(1),x(2),...,x(m)]:表示所有的训练数据集的输入值,放在一个 nx×mn_x×mnx​×m的矩阵中,其中mmm表示样本数目;

Y=[y(1),y(2),...,y(m)]Y=[y^{(1)},y^{(2)},...,y^{(m)}]Y=[y(1),y(2),...,y(m)]:对应表示所有训练数据集的输出值,维度为1×m1×m1×m。

用一对(x,y)(x,y)(x,y)来表示一个单独的样本,xxx代表nxn_xnx​维的特征向量,yyy 表示标签(输出结果)只能为0或1。 而训练集将由mmm个训练样本组成,其中(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)),然后所有的这些一起表示整个训练集。有时候为了强调这是训练样本的个数,会写作MtrainM_{train}Mtrain​,当涉及到测试集的时候,我们会使用MtestM_{test}Mtest​来表示测试集的样本数,所以这是测试集的样本数:

最后为了能把训练集表示得更紧凑一点,我们会定义一个矩阵用大写XXX的表示,它由输入向量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)放到第mmm列,然后我们就得到了训练集矩阵XXX。所以这个矩阵有mmm列,mmm是训练集的样本数量,然后这个矩阵的高度记为nxn_xnx​,注意有时候可能因为其他某些原因,矩阵XXX会由训练样本按照行堆叠起来而不是列,如下图所示:x(1)x^{(1)}x(1)的转置直到x(m)x^{(m)}x(m)的转置,但是在实现神经网络的时候,使用左边的这种形式,会让整个实现的过程变得更加简单:

现在来简单温习一下:XXX是一个规模为nxn_xnx​乘以mmm的矩阵,当你用Python实现的时候,你会看到X.shape,这是一条Python命令,用于显示矩阵的规模,即X.shape等于(nx,m)(n_x,m)(nx​,m),XXX是一个规模为nxn_xnx​乘以mmm的矩阵。所以综上所述,这就是如何将训练样本(输入向量XXX的集合)表示为一个矩阵。

那么输出标签yyy呢?同样的道理,为了能更加容易地实现一个神经网络,将标签yyy放在列中将会使得后续计算非常方便,所以我们定义大写的YYY等于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乘以mmm的矩阵,同样地使用Python将表示为Y.shape等于(1,m)(1,m)(1,m),表示这是一个规模为1乘以mmm的矩阵。

逻辑回归(Logistic Regression)

对于二元分类问题来讲,给定一个输入特征向量XXX,它可能对应一张图片,你想识别这张图片识别看它是否是一只猫或者不是一只猫的图片,你想要一个算法能够输出预测,你只能称之为y^\hat{y}y^​,也就是你对实际值 yyy 的估计。更正式地来说,你想让 y^\hat{y}y^​ 表示 yyy 等于1的一种可能性或者是机会,前提条件是给定了输入特征XXX。换句话来说,如果XXX是我们在上个视频看到的图片,你想让 y^\hat{y}y^​ 来告诉你这是一只猫的图片的机率有多大。在之前的视频中所说的,XXX是一个nxn_xnx​维的向量(相当于有nxn_xnx​个特征的特征向量)。我们用www来表示逻辑回归的参数,这也是一个nxn_xnx​维向量(因为www实际上是特征权重,维度与特征向量相同),参数里面还有bbb,这是一个实数(表示偏差)。所以给出输入xxx以及参数www和bbb之后,我们怎样产生输出预测值y^\hat{y}y^​,一件你可以尝试却不可行的事是让y^=wTx+b\hat{y}={{w}^{T}}x+by^​=wTx+b。

这时候我们得到的是一个关于输入xxx的线性函数,实际上这是你在做线性回归时所用到的,但是这对于二元分类问题来讲不是一个非常好的算法,因为你想让y^\hat{y}y^​表示实际值yyy等于1的机率的话,y^\hat{y}y^​ 应该在0到1之间。这是一个需要解决的问题,因为wTx+b{{w}^{T}}x+bwTx+b可能比1要大得多,或者甚至为一个负值。对于你想要的在0和1之间的概率来说它是没有意义的,因此在逻辑回归中,我们的输出应该是y^\hat{y}y^​等于由上面得到的线性函数式子作为自变量的sigmoid函数中,公式如上图最下面所示,将线性函数转换为非线性函数。

下图是sigmoid函数的图像,如果我把水平轴作为zzz轴,那么关于zzz的sigmoid函数是这样的,它是平滑地从0走向1,让我在这里标记纵轴,这是0,曲线与纵轴相交的截距是0.5,这就是关于zzz的sigmoid函数的图像。我们通常都使用zzz来表示wTx+b{{w}^{T}}x+bwTx+b的值。

关于sigmoid函数的公式是这样的,σ(z)=11+e−z\sigma \left( z \right)=\frac{1}{1+{{e}^{-z}}}σ(z)=1+e−z1​,在这里zzz是一个实数,这里要说明一些要注意的事情,如果zzz非常大那么e−z{{e}^{-z}}e−z将会接近于0,关于zzz的sigmoid函数将会近似等于1除以1加上某个非常接近于0的项,因为eee 的指数如果是个绝对值很大的负数的话,这项将会接近于0,所以如果zzz很大的话那么关于zzz的sigmoid函数会非常接近1。相反地,如果zzz非常小或者说是一个绝对值很大的负数,那么关于e−z{{e}^{-z}}e−z这项会变成一个很大的数,你可以认为这是1除以1加上一个非常非常大的数,所以这个就接近于0。实际上你看到当zzz变成一个绝对值很大的负数,关于zzz的sigmoid函数就会非常接近于0,因此当你实现逻辑回归时,你的工作就是去让机器学习参数www以及bbb这样才使得y^\hat{y}y^​成为对y=1y=1y=1这一情况的概率的一个很好的估计。

在继续进行下一步之前,介绍一种符号惯例,可以让参数www和参数bbb分开。在符号上要注意的一点是当我们对神经网络进行编程时经常会让参数www和参数bbb分开,在这里参数bbb对应的是一种偏置。在之前的机器学习课程里,你可能已经见过处理这个问题时的其他符号表示。比如在某些例子里,你定义一个额外的特征称之为x0{{x}{0}}x0,并且使它等于1,那么现在XXX就是一个nxn_xnx​加1维的变量,然后你定义y^=σ(θTx)\hat{y}=\sigma \left( {{\theta }^{T}}x \right)y^​=σ(θTx)的sigmoid函数。在这个备选的符号惯例里,你有一个参数向量θ0,θ1,θ2,...,θnx{{\theta }{0}},{{\theta }{1}},{{\theta }{2}},...,{{\theta }{{{n}{x}}}}θ0,θ1,θ2,...,θnx,这样θ0{{\theta }{0}}θ0就充当了bbb,这是一个实数,而剩下的θ1{{\theta }{1}}θ1 直到θnx{{\theta }{{{n}{x}}}}θnx充当了www,结果就是当你实现你的神经网络时,有一个比较简单的方法是保持bbb和www分开。但是在这节课里我们不会使用任何这类符号惯例,所以不用去担心。 现在你已经知道逻辑回归模型是什么样子了,下一步要做的是训练参数www和参数bbb,你需要定义一个代价函数,让我们在下节课里对其进行解释。

逻辑回归的代价函数(Logistic Regression Cost Function)

为什么需要代价函数:
为了训练逻辑回归模型的参数参数www和参数bbb我们,需要一个代价函数,通过训练代价函数来得到参数www和参数bbb。先看一下逻辑回归的输出函数:

为了让模型通过学习调整参数,你需要给予一个mmm样本的训练集,这会让你在训练集上找到参数www和参数bbb,,来得到你的输出。

对训练集的预测值,我们将它写成y^\hat{y}y^​,我们更希望它会接近于训练集中的yyy值,为了对上面的公式更详细的介绍,我们需要说明上面的定义是对一个训练样本来说的,这种形式也使用于每个训练样本,我们使用这些带有圆括号的上标来区分索引和样本,训练样本iii所对应的预测值是y(i){{y}^{(i)}}y(i),是用训练样本的wTx(i)+b{{w}^{T}}{{x}^{(i)}}+bwTx(i)+b然后通过sigmoid函数来得到,也可以把zzz定义为z(i)=wTx(i)+b{{z}^{(i)}}={{w}^{T}}{{x}^{(i)}}+bz(i)=wTx(i)+b,我们将使用这个符号(i)(i)(i)注解,上标(i)(i)(i)来指明数据表示xxx或者yyy或者zzz或者其他数据的第iii个训练样本,这就是上标(i)(i)(i)的含义。

损失函数:
损失函数又叫做误差函数,用来衡量算法的运行情况,Loss function:L(y^,y)L\left( \hat{y},y \right)L(y^​,y).

我们通过这个LLL称为的损失函数,来衡量预测输出值和实际值有多接近。一般我们用预测值和实际值的平方差或者它们平方差的一半,但是通常在逻辑回归中我们不这么做,因为当我们在学习逻辑回归参数的时候,会发现我们的优化目标不是凸优化,只能找到多个局部最优值,梯度下降法很可能找不到全局最优值,虽然平方差是一个不错的损失函数,但是我们在逻辑回归模型中会定义另外一个损失函数。

我们在逻辑回归中用到的损失函数是:L(y^,y)=−ylog⁡(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=1y=1y=1时损失函数L=−log⁡(y^)L=-\log (\hat{y})L=−log(y^​),如果想要损失函数LLL尽可能得小,那么y^\hat{y}y^​就要尽可能大,因为sigmoid函数取值[0,1][0,1][0,1],所以y^\hat{y}y^​会无限接近于1。

当y=0y=0y=0时损失函数L=−log⁡(1−y^)L=-\log (1-\hat{y})L=−log(1−y^​),如果想要损失函数LLL尽可能得小,那么y^\hat{y}y^​就要尽可能小,因为sigmoid函数取值[0,1][0,1][0,1],所以y^\hat{y}y^​会无限接近于0。

在这门课中有很多的函数效果和现在这个类似,就是如果yyy等于1,我们就尽可能让y^\hat{y}y^​变大,如果yyy等于0,我们就尽可能让 y^\hat{y}y^​ 变小。 损失函数是在单个训练样本中定义的,它衡量的是算法在单个训练样本中表现如何,为了衡量算法在全部训练样本上的表现如何,我们需要定义一个算法的代价函数,算法的代价函数是对mmm个样本的损失函数求和然后除以mmm: J(w,b)=1m∑i=1mL(y^(i),y(i))=1m∑i=1m(−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)=m1​i=1∑m​L(y^​(i),y(i))=m1​i=1∑m​(−y(i)logy^​(i)−(1−y(i))log(1−y^​(i))) 损失函数只适用于像这样的单个训练样本,而代价函数是参数的总代价,所以在训练逻辑回归模型时候,我们需要找到合适的www和bbb,来让代价函数 JJJ 的总代价降到最低。 根据我们对逻辑回归算法的推导及对单个样本的损失函数的推导和针对算法所选用参数的总代价函数的推导,结果表明逻辑回归可以看做是一个非常小的神经网络,在下一个视频中,我们会看到神经网络会做什么。

梯度下降法(Gradient Descent)

梯度下降法可以做什么?

在你测试集上,通过最小化代价函数(成本函数)J(w,b)J(w,b)J(w,b)来训练的参数www和bbb,


如图,在第二行给出和之前一样的逻辑回归算法的代价函数(成本函数)

梯度下降法的形象化说明

在这个图中,横轴表示你的空间参数www和bbb,在实践中,www可以是更高的维度,但是为了更好地绘图,我们定义www和bbb,都是单一实数,代价函数(成本函数)J(w,b)J(w,b)J(w,b)是在水平轴www和bbb上的曲面,因此曲面的高度就是J(w,b)J(w,b)J(w,b)在某一点的函数值。我们所做的就是找到使得代价函数(成本函数)J(w,b)J(w,b)J(w,b)函数值是最小值,对应的参数www和bbb。

如图,代价函数(成本函数)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)为凸函数。 初始化www和bbb,

可以用如图那个小红点来初始化参数www和bbb,也可以采用随机初始化的方法,对于逻辑回归几乎所有的初始化方法都有效,因为函数是凸函数,无论在哪里初始化,应该达到同一点或大致相同的点。

我们以如图的小红点的坐标来初始化参数www和bbb。

  1. 朝最陡的下坡方向走一步,不断地迭代

    我们朝最陡的下坡方向走一步,如图,走到了如图中第二个小红点处。

    我们可能停在这里也有可能继续朝最陡的下坡方向再走一步,如图,经过两次迭代走到第三个小红点处。

3.直到走到全局最优解或者接近全局最优解的地方

通过以上的三个步骤我们可以找到全局最优解,也就是代价函数(成本函数)J(w,b)J(w,b)J(w,b)这个凸函数的最小值点。

梯度下降法的细节化说明(仅有一个参数)

假定代价函数(成本函数)J(w)J(w)J(w) 只有一个参数www,即用一维曲线代替多维曲线,这样可以更好画出图像。

迭代就是不断重复做如图的公式:

:=:=:=表示更新参数,

$a $ 表示学习率(learning rate),用来控制步长(step),即向下走一步的长度dJ(w)dw\frac{dJ(w)}{dw}dwdJ(w)​ 就是函数J(w)J(w)J(w)对www 求导(derivative),在代码中我们会使用dwdwdw表示这个结果

对于导数更加形象化的理解就是斜率(slope),如图该点的导数就是这个点相切于 J(w)J(w)J(w)的小三角形的高除宽。假设我们以如图点为初始化点,该点处的斜率的符号是正的,即dJ(w)dw>0\frac{dJ(w)}{dw}>0dwdJ(w)​>0,所以接下来会向左走一步。

整个梯度下降法的迭代过程就是不断地向左走,直至逼近最小值点。

假设我们以如图点为初始化点,该点处的斜率的符号是负的,即dJ(w)dw<0\frac{dJ(w)}{dw}<0dwdJ(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) 对www 求偏导,在代码中我们会使用dwdwdw 表示这个结果, ∂J(w,b)∂b\frac{\partial J(w,b)}{\partial b}∂b∂J(w,b)​ 就是函数J(w,b)J(w,b)J(w,b)对bbb 求偏导,在代码中我们会使用dbdbdb 表示这个结果, 小写字母ddd 用在求导数(derivative),即函数只有一个参数, 偏导数符号$\partial $ 用在求偏导(partial derivative),即函数含有两个以上的参数。

计算图(Computation Graph)


计算图组织计算的形式是用蓝色箭头从左到右的计算,让我们看看下一个视频中如何进行反向红色箭头(也就是从右到左)的导数计算,让我们继续下一个视频的学习。

使用计算图求导(Derivatives with a Computation Graph)

在上一个视频中,我们看了一个例子使用流程计算图来计算函数J。现在我们清理一下流程图的描述,看看你如何利用它计算出函数JJJ的导数。

下面用到的公式:

dJdu=dJdvdvdu\frac{dJ}{du}=\frac{dJ}{dv}\frac{dv}{du}dudJ​=dvdJ​dudv​ , dJdb=dJdududb\frac{dJ}{db}=\frac{dJ}{du}\frac{du}{db}dbdJ​=dudJ​dbdu​ , dJda=dJdududa\frac{dJ}{da}=\frac{dJ}{du}\frac{du}{da}dadJ​=dudJ​dadu​

这是一个流程图:

假设你要计算dJdv\frac{{dJ}}{{dv}}dvdJ​,那要怎么算呢?好,比如说,我们要把这个vvv值拿过来,改变一下,那么JJJ的值会怎么变呢?

所以定义上J=3vJ = 3vJ=3v,现在v=11v=11v=11,所以如果你让vvv增加一点点,比如到11.001,那么J=3v=33.003J =3v =33.003J=3v=33.003,所以我这里vvv增加了0.001,然后最终结果是JJJ上升到原来的3倍,所以dJdv=3\frac{{dJ}}{{dv}}=3dvdJ​=3,因为对于任何 vvv 的增量JJJ都会有3倍增量,而且这类似于我们在上一个视频中的例子,我们有f(a)=3af(a)=3af(a)=3a,然后我们推导出df(a)da=3\frac{{df}(a)}{{da}}= 3dadf(a)​=3,所以这里我们有J=3vJ=3vJ=3v,所以dJdv=3\frac{{dJ}}{{dv}} =3dvdJ​=3,这里JJJ扮演了fff的角色,在之前的视频里的例子。

在反向传播算法中的术语,我们看到,如果你想计算最后输出变量的导数,使用你最关心的变量对vvv的导数,那么我们就做完了一步反向传播,在这个流程图中是一个反向步骤。

我们来看另一个例子,dJda\frac{{dJ}}{da}dadJ​是多少呢?换句话说,如果我们提高aaa的数值,对JJJ的数值有什么影响?

好,我们看看这个例子。变量a=5a=5a=5,我们让它增加到5.001,那么对v的影响就是a+ua+ua+u,之前v=11v=11v=11,现在变成11.001,我们从上面看到现在JJJ 就变成33.003了,所以我们看到的是,如果你让aaa增加0.001,JJJ增加0.003。那么增加aaa,我是说如果你把这个5换成某个新值,那么aaa的改变量就会传播到流程图的最右,所以JJJ最后是33.003。所以J的增量是3乘以aaa的增量,意味着这个导数是3。

要解释这个计算过程,其中一种方式是:如果你改变了aaa,那么也会改变vvv,通过改变vvv,也会改变JJJ,所以JJJ值的净变化量,当你提升这个值(0.001),当你把aaa值提高一点点,这就是JJJ的变化量(0.003)。

首先a增加了,vvv也会增加,vvv增加多少呢?这取决于dvda\frac{{dv}}{da}dadv​,然后vvv的变化导致JJJ也在增加,所以这在微积分里实际上叫链式法则,如果aaa影响到vvv,vvv影响到JJJ,那么当你让aaa变大时,JJJ的变化量就是当你改变aaa时,vvv的变化量乘以改变vvv时JJJ的变化量,在微积分里这叫链式法则。

我们从这个计算中看到,如果你让aaa增加0.001,vvv也会变化相同的大小,所以dvda=1\frac{{dv}}{da}= 1dadv​=1。事实上,如果你代入进去,我们之前算过dJdv=3\frac{{dJ}}{{dv}} =3dvdJ​=3,dvda=1\frac{{dv}}{da} =1dadv​=1,所以这个乘积3×1,实际上就给出了正确答案,dJda=3\frac{{dJ}}{da} = 3dadJ​=3。

这张小图表示了如何计算,dJdv\frac{{dJ}}{{dv}}dvdJ​就是JJJ对变量vvv的导数,它可以帮助你计算dJda\frac{{dJ}}{da}dadJ​,所以这是另一步反向传播计算。

现在我想介绍一个新的符号约定,当你编程实现反向传播时,通常会有一个最终输出值是你要关心的,最终的输出变量,你真正想要关心或者说优化的。在这种情况下最终的输出变量是J,就是流程图里最后一个符号,所以有很多计算尝试计算输出变量的导数,所以输出变量对某个变量的导数,我们就用dvardvardvar命名,所以在很多计算中你需要计算最终输出结果的导数,在这个例子里是JJJ,还有各种中间变量,比如a、b、c、u、va、b、c、u、va、b、c、u、v,当你在软件里实现的时候,变量名叫什么?你可以做的一件事是,在python中,你可以写一个很长的变量名,比如dFinalOutputvardvar{dFinalOutputvar}_{dvar}dFinalOutputvardvar​,但这个变量名有点长,我们就用dJdvardJ_dvardJd​var,但因为你一直对dJdJdJ求导,对这个最终输出变量求导。我这里要介绍一个新符号,在程序里,当你编程的时候,在代码里,我们就使用变量名dvardvardvar,来表示那个量。

好,所以在程序里是dvardvardvar表示导数,你关心的最终变量JJJ的导数,有时最后是LLL,对代码中各种中间量的导数,所以代码里这个东西,你用dvdvdv表示这个值,所以dv=3dv=3dv=3,你的代码表示就是da=3da=3da=3。

好,所以我们通过这个流程图完成部分的后向传播算法。我们在下一张幻灯片看看这个例子剩下的部分。

我们清理出一张新的流程图,我们回顾一下,到目前为止,我们一直在往回传播,并计算dv=3dv=3dv=3,再次,dvdvdv是代码里的变量名,其真正的定义是dJdv\frac{{dJ}}{{dv}}dvdJ​。我发现da=3da=3da=3,再次,dadada是代码里的变量名,其实代表dJda\frac{{dJ}}{da}dadJ​的值。

大概手算了一下,两条直线怎么计算反向传播。

好,我们继续计算导数,我们看看这个值uuu,那么dJdu\frac{dJ}{du}dudJ​是多少呢?通过和之前类似的计算,现在我们从u=6u=6u=6出发,如果你令uuu增加到6.001,那么vvv之前是11,现在变成11.001了,JJJ 就从33变成33.003,所以JJJ 增量是3倍,所以dJdu=3\frac{{dJ}}{du}= 3dudJ​=3。对uuu的分析很类似对a的分析,实际上这计算起来就是dJdv⋅dvdu\frac{{dJ}}{dv}\cdot \frac{{dv}}{du}dvdJ​⋅dudv​,有了这个,我们可以算出dJdv=3\frac{{dJ}}{dv} =3dvdJ​=3,dvdu=1\frac{{dv}}{du} = 1dudv​=1,最终算出结果是3×1=33×1=33×1=3。

所以我们还有一步反向传播,我们最终计算出du=3du=3du=3,这里的dududu当然了,就是dJdu\frac{{dJ}}{du}dudJ​。

现在,我们仔细看看最后一个例子,那么dJdb\frac{{dJ}}{db}dbdJ​呢?想象一下,如果你改变了bbb的值,你想要然后变化一点,让JJJ 值到达最大或最小,那么导数是什么呢?这个JJJ函数的斜率,当你稍微改变bbb值之后。事实上,使用微积分链式法则,这可以写成两者的乘积,就是dJdu⋅dudb\frac{{dJ}}{du}\cdot \frac{{du}}{db}dudJ​⋅dbdu​,理由是,如果你改变bbb一点点,所以bbb变化比如说3.001,它影响J的方式是,首先会影响uuu,它对uuu的影响有多大?好,uuu的定义是b⋅cb\cdot cb⋅c,所以b=3b=3b=3时这是6,现在就变成6.002了,对吧,因为在我们的例子中c=2c=2c=2,所以这告诉我们dudb=2\frac{{du}}{db}= 2dbdu​=2当你让bbb增加0.001时,uuu就增加两倍。所以dudb=2\frac{{du}}{db} =2dbdu​=2,现在我想uuu的增加量已经是bbb的两倍,那么dJdu\frac{{dJ}}{du}dudJ​是多少呢?我们已经弄清楚了,这等于3,所以让这两部分相乘,我们发现dJdb=6\frac{{dJ}}{db}= 6dbdJ​=6。

好,这就是第二部分的推导,其中我们想知道 uuu 增加0.002,会对JJJ 有什么影响。实际上dJdu=3\frac{{dJ}}{du}=3dudJ​=3,这告诉我们u增加0.002之后,JJJ上升了3倍,那么JJJ 应该上升0.006,对吧。这可以从dJdu=3\frac{{dJ}}{du}= 3dudJ​=3推导出来。

如果你仔细看看这些数学内容,你会发现,如果bbb变成3.001,那么uuu就变成6.002,vvv变成11.002,然后J=3v=33.006J=3v=33.006J=3v=33.006,对吧?这就是如何得到dJdb=6\frac{{dJ}}{db}= 6dbdJ​=6。

为了填进去,如果我们反向走的话,db=6db=6db=6,而dbdbdb其实是Python代码中的变量名,表示dJdb\frac{{dJ}}{db}dbdJ​。

我不会很详细地介绍最后一个例子,但事实上,如果你计算dJdc=dJdu⋅dudc=3×3\frac{{dJ}}{dc} =\frac{{dJ}}{du}\cdot \frac{{du}}{dc} = 3 \times 3dcdJ​=dudJ​⋅dcdu​=3×3,这个结果是9。

我不会详细说明这个例子,在最后一步,我们可以推出dc=9dc=9dc=9。

所以这个视频的要点是,对于那个例子,当计算所有这些导数时,最有效率的办法是从右到左计算,跟着这个红色箭头走。特别是当我们第一次计算对vvv的导数时,之后在计算对aaa导数就可以用到。然后对uuu的导数,比如说这个项和这里这个项:


可以帮助计算对bbb的导数,然后对ccc的导数。

所以这是一个计算流程图,就是正向或者说从左到右的计算来计算成本函数J,你可能需要优化的函数,然后反向从右到左计算导数。如果你不熟悉微积分或链式法则,我知道这里有些细节讲的很快,但如果你没有跟上所有细节,也不用怕。在下一个视频中,我会再过一遍。在逻辑回归的背景下过一遍,并给你介绍需要做什么才能编写代码,实现逻辑回归模型中的导数计算。

逻辑回归中的梯度下降Logistic Regression Gradient Descent)

本节我们讨论怎样通过计算偏导数来实现逻辑回归的梯度下降算法。它的关键点是几个重要公式,其作用是用来实现逻辑回归中梯度下降算法。但是在本节视频中,我将使用计算图对梯度下降算法进行计算。我必须要承认的是,使用计算图来计算逻辑回归的梯度下降算法有点大材小用了。但是,我认为以这个例子作为开始来讲解,可以使你更好的理解背后的思想。从而在讨论神经网络时,你可以更深刻而全面地理解神经网络。接下来让我们开始学习逻辑回归的梯度下降算法。

假设样本只有两个特征x1{{x}{1}}x1和x2{{x}{2}}x2,为了计算zzz,我们需要输入参数w1{{w}{1}}w1、w2{{w}{2}}w2 和bbb,除此之外还有特征值x1{{x}{1}}x1和x2{{x}{2}}x2。因此zzz的计算公式为: z=w1x1+w2x2+bz={{w}{1}}{{x}{1}}+{{w}{2}}{{x}{2}}+bz=w1x1+w2x2+b 回想一下逻辑回归的公式定义如下: y^=a=σ(z)\hat{y}=a=\sigma (z)y^​=a=σ(z) 其中z=wTx+bz={{w}^{T}}x+bz=wTx+b σ(z)=11+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)=1m∑imL(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​∑im​L(y^​(i),y(i)) 假设现在只考虑单个样本的情况,单个样本的代价函数定义如下: L(a,y)=−(ylog⁡(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)) 其中aaa是逻辑回归的输出,yyy是样本的标签值。现在让我们画出表示这个计算的计算图。 这里先复习下梯度下降法,www和bbb的修正量可以表达如下:

w:=w−a∂J(w,b)∂ww:=w-a \frac{\partial J(w,b)}{\partial w}w:=w−a∂w∂J(w,b)​,b:=b−a∂J(w,b)∂bb:=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),我们需要做的仅仅是修改参数www和bbb的值。前面我们已经讲解了如何在单个训练样本上计算代价函数的前向步骤。现在让我们来讨论通过反向计算出导数。 因为我们想要计算出的代价函数L(a,y)L(a,y)L(a,y)的导数,首先我们需要反向计算出代价函数L(a,y)L(a,y)L(a,y)关于aaa的导数,在编写代码时,你只需要用dadada 来表示dL(a,y)da\frac{dL(a,y)}{da}dadL(a,y)​ 。 通过微积分得到: dL(a,y)da=−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) 如果你不熟悉微积分,也不必太担心,我们会列出本课程涉及的所有求导公式。那么如果你非常熟悉微积分,我们鼓励你主动推导前面介绍的代价函数的求导公式,使用微积分直接求出L(a,y)L(a,y)L(a,y)关于变量aaa的导数。如果你不太了解微积分,也不用太担心。现在我们已经计算出dadada,也就是最终输出结果的导数。 现在可以再反向一步,在编写Python代码时,你只需要用dzdzdz来表示代价函数LLL关于zzz 的导数dLdz\frac{dL}{dz}dzdL​,也可以写成dL(a,y)dz\frac{dL(a,y)}{dz}dzdL(a,y)​,这两种写法都是正确的。 dLdz=a−y\frac{dL}{dz}=a-ydzdL​=a−y 。 因为dL(a,y)dz=dLdz=(dLda)⋅(dadz)\frac{dL(a,y)}{dz}=\frac{dL}{dz}=(\frac{dL}{da})\cdot (\frac{da}{dz})dzdL(a,y)​=dzdL​=(dadL​)⋅(dzda​), 并且dadz=a⋅(1−a)\frac{da}{dz}=a\cdot (1-a)dzda​=a⋅(1−a), 而 dLda=(−ya+(1−y)(1−a))\frac{dL}{da}=(-\frac{y}{a}+\frac{(1-y)}{(1-a)})dadL​=(−ay​+(1−a)(1−y)​),因此将这两项相乘,得到:

dz=dL(a,y)dz=dLdz=(dLda)⋅(dadz)=(−ya+(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 - ydz=dzdL(a,y)​=dzdL​=(dadL​)⋅(dzda​)=(−ay​+(1−a)(1−y)​)⋅a(1−a)=a−y

视频中为了简化推导过程,假设nx{{n}_{x}}nx​ 这个推导的过程就是我之前提到过的链式法则。如果你对微积分熟悉,放心地去推导整个求导过程,如果不熟悉微积分,你只需要知道dz=(a−y)dz=(a-y)dz=(a−y)已经计算好了。

现在进行最后一步反向推导,也就是计算www和bbb变化对代价函数LLL的影响,特别地,可以用: dw1=1m∑imx1(i)(a(i)−y(i))d{{w}{1}}=\frac{1}{m}\sum\limits{i}^{m}{x_{1}^{(i)}}({{a}^{(i)}}-{{y}^{(i)}})dw1=m1​∑imx1(i)​(a(i)−y(i)) dw2=1m∑imx2(i)(a(i)−y(i))d{{w}{2}}=\frac{1}{m}\sum\limits{i}^{m}{x_{2}^{(i)}}({{a}^{(i)}}-{{y}^{(i)}})dw2=m1​∑imx2(i)​(a(i)−y(i)) db=1m∑im(a(i)−y(i))db=\frac{1}{m}\sum\limits_{i}^{m}{({{a}^{(i)}}-{{y}^{(i)}})}db=m1​i∑m​(a(i)−y(i)) 视频中, dw1d{{w}{1}}dw1 表示∂L∂w1=x1⋅dz\frac{\partial L}{\partial {{w}{1}}}={{x}{1}}\cdot dz∂w1∂L​=x1⋅dz, dw2d{{w}{\text{2}}}dw2 表示∂L∂w2=x2⋅dz\frac{\partial L}{\partial {{w}{2}}}={{x}{2}}\cdot dz∂w2∂L​=x2⋅dz, db=dzdb=dzdb=dz。 因此,关于单个样本的梯度下降算法,你所需要做的就是如下的事情: 使用公式dz=(a−y)dz=(a-y)dz=(a−y)计算dzdzdz, 使用dw1=x1⋅dzd{{w}{1}}={{x}{1}}\cdot dzdw1=x1⋅dz 计算dw1d{{w}{1}}dw1, dw2=x2⋅dzd{{w}{2}}={{x}{2}}\cdot dzdw2=x2⋅dz计算dw2d{{w}{2}}dw2, db=dzdb=dzdb=dz 来计算dbdbdb, 然后: 更新w1=w1−adw1{{w}{1}}={{w}{1}}-a d{{w}{1}}w1=w1−adw1, 更新w2=w2−adw2{{w}{2}}={{w}{2}}-a d{{w}{2}}w2=w2−adw2, 更新b=b−αdbb=b-\alpha dbb=b−αdb。 这就是关于单个样本实例的梯度下降算法中参数更新一次的步骤。

现在你已经知道了怎样计算导数,并且实现针对单个训练样本的逻辑回归的梯度下降算法。但是,训练逻辑回归模型不仅仅只有一个训练样本,而是有mmm个训练样本的整个训练集。因此在下一节视频中,我们将这些思想应用到整个训练样本集中,而不仅仅只是单个样本上。

m个样本的梯度下降(Gradient Descent on m Examples)

在之前的视频中,你已经看到如何计算导数,以及应用梯度下降在逻辑回归的一个训练样本上。现在我们想要把它应用在mmm个训练样本上。

首先,让我们时刻记住有关于损失函数J(w,b)J(w,b)J(w,b)的定义。

J(w,b)=1m∑i=1mL(a(i),y(i))J(w,b)=\frac{1}{m}\sum\limits_{i=1}^{m}{L({{a}^{(i)}},{{y}^{(i)}})}J(w,b)=m1​i=1∑m​L(a(i),y(i))

当你的算法输出关于样本yyy的a(i){{a}^{(i)}}a(i),a(i){{a}^{(i)}}a(i)是训练样本的预测值,即:σ(z(i))=σ(wTx(i)+b)\sigma ( {{z}^{(i)}})=\sigma( {{w}^{T}}{{x}^{\left( i \right)}}+b)σ(z(i))=σ(wTx(i)+b)。 所以我们在前面的幻灯中展示的是对于任意单个训练样本,如何计算微分当你只有一个训练样本。因此dw1d{{w}{1}}dw1,dw2d{{w}{\text{2}}}dw2和dbdbdb 添上上标iii表示你求得的相应的值。如果你面对的是我们在之前的幻灯中演示的那种情况,但只使用了一个训练样本(x(i),y(i))({{x}^{(i)}},{{y}^{(i)}})(x(i),y(i))。 现在你知道带有求和的全局代价函数,实际上是1到mmm项各个损失的平均。 所以它表明全局代价函数对w1{{w}{1}}w1的微分,对w1{{w}{1}}w1的微分也同样是各项损失对w1{{w}_{1}}w1​微分的平均。

但之前我们已经演示了如何计算这项,即之前幻灯中演示的如何对单个训练样本进行计算。所以你真正需要做的是计算这些微分,如我们在之前的训练样本上做的。并且求平均,这会给你全局梯度值,你能够把它直接应用到梯度下降算法中。

所以这里有很多细节,但让我们把这些装进一个具体的算法。同时你需要一起应用的就是逻辑回归和梯度下降。

我们初始化J=0,dw1=0,dw2=0,db=0J=0,d{{w}{1}}=0,d{{w}{2}}=0,db=0J=0,dw1=0,dw2=0,db=0

代码流程:

J=0;dw1=0;dw2=0;db=0;
for i = 1 to mz(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循环是一个小循环遍历mmm个训练样本,第二个for循环是一个遍历所有特征的for循环。这个例子中我们只有2个特征,所以nnn等于2并且nx{{n}{x}}nx 等于2。 但如果你有更多特征,你开始编写你的因此dw1d{{w}{1}}dw1,dw2d{{w}{2}}dw2,你有相似的计算从dw3d{{w}{3}}dw3一直下去到dwnd{{w}_{n}}dwn​。所以看来你需要一个for循环遍历所有nnn个特征。

当你应用深度学习算法,你会发现在代码中显式地使用for循环使你的算法很低效,同时在深度学习领域会有越来越大的数据集。所以能够应用你的算法且没有显式的for循环会是重要的,并且会帮助你适用于更大的数据集。所以这里有一些叫做向量化技术,它可以允许你的代码摆脱这些显式的for循环。

我想在先于深度学习的时代,也就是深度学习兴起之前,向量化是很棒的。可以使你有时候加速你的运算,但有时候也未必能够。但是在深度学习时代向量化,摆脱for循环已经变得相当重要。因为我们越来越多地训练非常大的数据集,因此你真的需要你的代码变得非常高效。所以在接下来的几个视频中,我们会谈到向量化,以及如何应用向量化而连一个for循环都不使用。所以学习了这些,我希望你有关于如何应用逻辑回归,或是用于逻辑回归的梯度下降,事情会变得更加清晰。当你进行编程练习,但在真正做编程练习之前让我们先谈谈向量化。然后你可以应用全部这些东西,应用一个梯度下降的迭代而不使用任何for循环。

向量化(Vectorization)

代码运行速度非常重要,否则如果在大数据集上,你的代码可能花费很长时间去运行,你将要等待非常长的时间去得到结果。所以在深度学习领域,运行向量化是一个关键的技巧,让我们举个栗子说明什么是向量化。

在逻辑回归中你需要去计算z=wTx+bz={{w}^{T}}x+bz=wTx+b,www、xxx都是列向量。如果你有很多的特征那么就会有一个非常大的向量,所以w∈Rnxw\in {{\mathbb{R}}^{{{n}{x}}}}w∈Rnx , x∈Rnxx\in{{\mathbb{R}}^{{{n}{x}}}}x∈Rnx,所以如果你想使用非向量化方法去计算wTx{{w}^{T}}xwTx,你需要用如下方式(python)

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

这是一个非向量化的实现,你会发现这真的很慢,作为一个对比,向量化实现将会非常直接计算wTx{{w}^{T}}xwTx,代码如下:

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

这是向量化计算wTx{{w}^{T}}xwTx的方法,你将会发现这个非常快。

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循环。

向量化的更多例子(More Examples of Vectorization)

当我们在写神经网络程序时,或者在写逻辑(logistic)回归,或者其他神经网络模型时,应该避免写循环(loop)语句。虽然有时写循环(loop)是不可避免的,但是我们可以使用比如numpy的内置函数或者其他办法去计算。当你这样使用后,程序效率总是快于循环(loop)。

让我们看另外一个例子。如果你想计算向量u=Avu=Avu=Av,这时矩阵乘法定义为,矩阵乘法的定义就是:ui=∑jAijviu_{i} =\sum_{j}^{}{A_{\text{ij}}v_{i}}ui​=∑j​Aij​vi​,这取决于你怎么定义uiu_{i}ui​值。同样使用非向量化实现,u=np.zeros(n,1)u=np.zeros(n,1)u=np.zeros(n,1), 并且通过两层循环for(i):for(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] 。现在就有了iii 和 jjj 的两层循环,这就是非向量化。向量化方式就可以用u=np.dot(A,v)u=np.dot(A,v)u=np.dot(A,v),右边这种向量化实现方式,消除了两层循环使得代码运行速度更快。


下面通过另一个例子继续了解向量化。如果你已经有一个向量vvv,并且想要对向量vvv的每个元素做指数操作,得到向量uuu等于eee的v1v_1v1​,eee的v2v_2v2​,一直到eee的vnv_nvn​次方。这里是非向量化的实现方式,首先你初始化了向量u=np.zeros(n,1)u=np.zeros(n,1)u=np.zeros(n,1),并且通过循环依次计算每个元素。但事实证明可以通过python的numpy内置函数,帮助你计算这样的单个函数。所以我会引入import numpy as np,执行 u=np.exp(v)u=np.exp(v)u=np.exp(v) 命令。注意到,在之前有循环的代码中,这里仅用了一行代码,向量vvv作为输入,uuu作为输出。你已经知道为什么需要循环,并且通过右边代码实现,效率会明显的快于循环方式。

事实上,numpy库有很多向量函数。比如 u=np.log是计算对数函数(logloglog)、 np.abs() 计算数据的绝对值、np.maximum(v, 0) 按元素计算vvv中每个元素和和0相比的最大值,v**2 代表获得元素 vvv 每个值的平方、 1/v 获取 vvv 中每个元素的倒数等等。所以当你想写循环时候,检查numpy是否存在类似的内置函数,从而避免使用循环(loop)方式。


那么,将刚才所学到的内容,运用在逻辑回归的梯度下降上,看看我们是否能简化两个计算过程中的某一步。这是我们逻辑回归的求导代码,有两层循环。在这例子我们有nnn个特征值。如果你有超过两个特征时,需要循环 dw1dw_1dw1​ 、dw2dw_2dw2​ 、dw3dw_3dw3​ 等等。所以 jjj 的实际值是1、2 和 nxn_xnx​,就是你想要更新的值。所以我们想要消除第二循环,在这一行,这样我们就不用初始化 dw1dw_1dw1​ , dw2dw_2dw2​ 都等于0。去掉这些,而是定义 dwdwdw 为一个向量,设置 u=np.zeros(n(x),1)u=np.zeros(n(x),1)u=np.zeros(n(x),1)。定义了一个xxx行的一维向量,从而替代循环。我们仅仅使用了一个向量操作 dw=dw+x(i)dz(i)dw=dw+x^{(i)}dz^{(i)}dw=dw+x(i)dz(i) 。最后,我们得到 dw=dw/mdw=dw/mdw=dw/m 。现在我们通过将两层循环转成一层循环,我们仍然还有这个循环训练样本。



希望这个视频给了你一点向量化感觉,减少一层循环使你代码更快,但事实证明我们能做得更好。

向量化逻辑回归(Vectorizing Logistic Regression)

我们已经讨论过向量化是如何显著加速你的代码,在本次视频中我们将讨论如何实现逻辑回归的向量化计算。这样就能处理整个数据集,甚至不会用一个明确的for循环就能实现对于整个数据集梯度下降算法的优化。我对这项技术感到非常激动,并且当我们后面谈到神经网络时同样也不会用到一个明确的 for 循环。

让我们开始吧,首先我们回顾一下逻辑回归的前向传播步骤。所以,如果你有 mmm 个训练样本,然后对第一个样本进行预测,你需要这样计算。计算 zzz,我正在使用这个熟悉的公式 z(1)=wTx(1)+bz^{(1)}=w^{T}x^{(1)}+bz(1)=wTx(1)+b 。然后计算激活函数 a(1)=σ(z(1))a^{(1)}=\sigma (z^{(1)})a(1)=σ(z(1)) ,计算第一个样本的预测值 yyy 。

然后对第二个样本进行预测,你需要计算 z(2)=wTx(2)+bz^{(2)}=w^{T}x^{(2)}+bz(2)=wTx(2)+b , a(2)=σ(z(2))a^{(2)}=\sigma (z^{(2)})a(2)=σ(z(2)) 。然后对第三个样本进行预测,你需要计算 z(3)=wTx(3)+bz^{(3)}=w^{T}x^{(3)}+bz(3)=wTx(3)+b , a(3)=σ(z(3))a^{(3)}=\sigma (z^{(3)})a(3)=σ(z(3)) ,依次类推。如果你有 mmm 个训练样本,你可能需要这样做 mmm 次,可以看出,为了完成前向传播步骤,即对我们的 mmm 个样本都计算出预测值。有一个办法可以并且不需要任何一个明确的for循环。让我们来看一下你该怎样做。

首先,回忆一下我们曾经定义了一个矩阵 XXX 作为你的训练输入,(如下图中蓝色 XXX )像这样在不同的列中堆积在一起。这是一个 nxn_xnx​ 行 mmm 列的矩阵。我现在将它写为Python numpy的形式 (nx,m)(n_{x},m)(nx​,m) ,这只是表示 XXX 是一个 nxn_xnx​ 乘以 mmm 的矩阵 Rnx×mR^{n_x \times m}Rnx​×m。


现在我首先想做的是告诉你该如何在一个步骤中计算 z1z_1z1​、 z2z_2z2​ 、z3z_3z3​ 等等。实际上,只用了一行代码。所以,我打算先构建一个 1×m1\times m1×m 的矩阵,实际上它是一个行向量,同时我准备计算 z(1)z^{(1)}z(1), z(2)z^{(2)}z(2) ……一直到 z(m)z^{(m)}z(m) ,所有值都是在同一时间内完成。结果发现它可以表达为 www 的转置乘以大写矩阵 xxx 然后加上向量 [bb...b][b b...b][bb...b] , ([z(1)z(2)...z(m)]=wT+[bb...b])([z^{(1)} z^{(2)}...z^{(m)}]=w^{T}+[bb...b])([z(1)z(2)...z(m)]=wT+[bb...b]) 。[bb...b][b b...b][bb...b] 是一个 1×m1\times m1×m 的向量或者 1×m1\times m1×m 的矩阵或者是一个 mmm 维的行向量。所以希望你熟悉矩阵乘法,你会发现的 www 转置乘以 x(1)x^{(1)}x(1) , x(2)x^{(2)}x(2) 一直到 x(m)x^{(m)}x(m) 。所以 www 转置可以是一个行向量。所以第一项 wTXw^{T}XwTX 将计算 www 的转置乘以 x(1)x^{(1)}x(1), www 转置乘以x(2)x^{(2)}x(2) 等等。然后我们加上第二项 [bb...b][b b...b][bb...b] ,你最终将 bbb 加到了每个元素上。所以你最终得到了另一个 1×m1\times m1×m 的向量, [z(1)z(2)...z(m)]=wTX+[bb...b]=[wTx(1)+b,wTx(2)+b...wTx(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] 。

wTx(1)+bw^{T}x^{(1)}+bwTx(1)+b 这是第一个元素,wTx(2)+bw^{T}x^{(2)}+bwTx(2)+b 这是第二个元素, wTx(m)+bw^{T}x^{(m)}+bwTx(m)+b 这是第 mmm 个元素。

如果你参照上面的定义,第一个元素恰好是 z(1)z^{(1)}z(1) 的定义,第二个元素恰好是 z(2)z^{(2)}z(2) 的定义,等等。所以,因为XXX是一次获得的,当你得到你的训练样本,一个一个横向堆积起来,这里我将 [z(1)z(2)...z(m)][z^{(1)} z^{(2)} ... z^{(m)}][z(1)z(2)...z(m)] 定义为大写的 ZZZ ,你用小写 zzz 表示并将它们横向排在一起。所以当你将不同训练样本对应的小写 xxx 横向堆积在一起时得到大写变量 XXX 并且将小写变量也用相同方法处理,将它们横向堆积起来,你就得到大写变量 ZZZ 。结果发现,为了计算 WTX+[bb...b]W^{T}X+[b b ... b]WTX+[bb...b] ,numpy命令是Z=np.dot(w.T,X)+bZ=np.dot(w.T,X)+bZ=np.dot(w.T,X)+b。这里在Python中有一个巧妙的地方,这里 bbb 是一个实数,或者你可以说是一个 1×11\times 11×1 矩阵,只是一个普通的实数。但是当你将这个向量加上这个实数时,Python自动把这个实数 bbb 扩展成一个 1×m1\times m1×m 的行向量。所以这种情况下的操作似乎有点不可思议,它在Python中被称作广播(brosdcasting),目前你不用对此感到顾虑,我们将在下一个视频中进行进一步的讲解。话说回来它只用一行代码,用这一行代码,你可以计算大写的 ZZZ,而大写 ZZZ 是一个包含所有小写z(1)z^{(1)}z(1) 到 $ z^{(m)}$ 的 1×m1\times m1×m 的矩阵。这就是 ZZZ 的内容,关于变量 aaa 又是如何呢?

我们接下来要做的就是找到一个同时计算 [a(1)a(2)...a(m)][a^{(1)} a^{(2)} ... a^{(m)}][a(1)a(2)...a(m)] 的方法。就像把小写 xxx 堆积起来得到大写 XXX 和横向堆积小写 zzz 得到大写 ZZZ 一样,堆积小写变量 aaa 将形成一个新的变量,我们将它定义为大写 AAA。在编程作业中,你将看到怎样用一个向量在sigmoid函数中进行计算。所以sigmoid函数中输入大写 ZZZ 作为变量并且非常高效地输出大写 AAA。你将在编程作业中看到它的细节。

总结一下,在这张幻灯片中我们已经看到,不需要for循环,利用 mmm 个训练样本一次性计算出小写 zzz 和小写 aaa,用一行代码即可完成。

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σ一次性计算所有 aaa。这就是在同一时间内你如何完成一个所有 mmm 个训练样本的前向传播向量化计算。

概括一下,你刚刚看到如何利用向量化在同一时间内高效地计算所有的激活函数的所有 aaa值。接下来,可以证明,你也可以利用向量化高效地计算反向传播并以此来计算梯度。让我们在下一个视频中看该如何实现。

向量化logistic回归的梯度输出(Vectorizing Logistic Regression’s Gradient)

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

如何向量化计算的同时,对整个训练集预测结果aaa,这是我们之前已经讨论过的内容。在本次视频中我们将学习如何向量化地计算mmm个训练数据的梯度,本次视频的重点是如何同时计算 mmm 个数据的梯度,并且实现一个非常高效的逻辑回归算法**(Logistic Regression**)。

之前我们在讲梯度计算的时候,列举过几个例子, dz(1)=a(1)−y(1)dz^{(1)}=a^{(1)}-y^{(1)}dz(1)=a(1)−y(1),dz(2)=a(2)−y(2)dz^{(2)}=a^{(2)}-y^{(2)}dz(2)=a(2)−y(2) ……等等一系列类似公式。现在,对 mmm个训练数据做同样的运算,我们可以定义一个新的变量 dZ=[dz(1),dz(2)...dz(m)]dZ=[dz^{(1)} ,dz^{(2)} ... dz^{(m)}]dZ=[dz(1),dz(2)...dz(m)] ,所有的 dzdzdz 变量横向排列,因此,dZdZdZ 是一个 1×m1\times m1×m 的矩阵,或者说,一个 mmm 维行向量。在之前的幻灯片中,我们已经知道如何计算AAA,即 [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)] ,由此,我们可以这样计算 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)}]dZ=A−Y=[a(1)−y(1)a(2)−y(2)...a(m)−y(m)],不难发现第一个元素就是 dz(1)dz^{(1)}dz(1),第二个元素就是 dz(2)dz^{(2)}dz(2) ……所以我们现在仅需一行代码,就可以同时完成这所有的计算。

在之前的实现中,我们已经去掉了一个for循环,但我们仍有一个遍历训练集的循环,如下所示:

dw=0dw=0dw=0

dw+=x(1)∗dz(1)dw + = x^{(1)}*{dz}^{(1)}dw+=x(1)∗dz(1)

dw+=x(2)∗dz(2)dw + = x^{(2)}\ *dz^{(2)}dw+=x(2) ∗dz(2)

………….

dw+=x(m)∗dz(m)dw + = x^{(m)}*{dz}^{(m)}dw+=x(m)∗dz(m)

dw=dwmdw = \frac{{dw}}{m}dw=mdw​

db=0db = 0db=0

db+=dz(1)db + = {dz}^{(1)}db+=dz(1)

db+=dz(2)db + = {dz}^{(2)}db+=dz(2)

………….

db+=dz(m)db + = dz^{(m)}db+=dz(m)

db=dbmdb = \frac{{db}}{m}db=mdb​

上述(伪)代码就是我们在之前实现中做的,我们已经去掉了一个for循环,但用上述方法计算 dwdwdw 仍然需要一个循环遍历训练集,我们现在要做的就是将其向量化!

首先我们来看 dbdbdb,不难发现 db=1m∑i=1mdz(i)db=\frac{1}{m}\sum_{i=1}^{m}dz^{(i)}db=m1​i=1∑m​dz(i) , 之前的讲解中,我们知道所有的dzi)dz^{i)}dzi)已经组成一个行向量 dZdZdZ了,所以在Python中,我们很容易地想到db=1mnp.sum(dZ)db=\frac{1}{m}np.sum(dZ)db=m1​np.sum(dZ);接下来看dwdwdw,我们先写出它的公式 dw=1mXdzTdw=\frac{1}{m}Xdz^{T}dw=m1​XdzT 其中,XXX 是一个行向量。因此展开后 dw=1m(x(1)dz(1)+x(2)dz(2)+...+xmdzm)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) 。因此我们可以仅用两行代码进行计算:db=1m∗np.sum(dZ)db=\frac{1}{m}*np.sum(dZ)db=m1​∗np.sum(dZ), dw=1mXdzTdw=\frac{1}{m}Xdz^{T}dw=m1​XdzT。这样,我们就避免了在训练集上使用for循环。

现在,让我们回顾一下,看看我们之前怎么实现的逻辑回归,可以发现,没有向量化是非常低效的,如下图所示代码:

我们的目标是不使用for循环,而是向量,我们可以这么做:

Z=wTX+b=np.dot(w.T,X)+bZ = w^{T}X + b = np.dot( w.T,X)+bZ=wTX+b=np.dot(w.T,X)+b

A=σ(Z)A = \sigma( Z )A=σ(Z)

dZ=A−YdZ = A - YdZ=A−Y

dw=1mXdzT{{dw} = \frac{1}{m}Xdz^{T}\ }dw=m1​XdzT

db=1m∗np.sum(dZ)​db= \frac{1}{m}*np.sum( dZ)​db=m1​∗np.sum(dZ)​

w:=w−a∗dww: = w - a*dww:=w−a∗dw

b:=b−a∗dbb: = b - a*dbb:=b−a∗db

现在我们利用前五个公式完成了前向和后向传播,也实现了对所有训练样本进行预测和求导,再利用后两个公式,梯度下降更新参数。我们的目的是不使用for循环,所以我们就通过一次迭代实现一次梯度下降,但如果你希望多次迭代进行梯度下降,那么仍然需要for循环,放在最外层。不过我们还是觉得一次迭代就进行一次梯度下降,避免使用任何循环比较舒服一些。

最后,我们得到了一个高度向量化的、非常高效的逻辑回归的梯度下降算法,我们将在下次视频中讨论Python中的Broadcasting技术。

Python 中的广播(Broadcasting in Python)

首先是numpy广播机制

如果两个数组的后缘维度的轴长度相符或其中一方的轴长度为1,则认为它们是广播兼容的。广播会在缺失维度和轴长度为1的维度上进行。

后缘维度的轴长度:A.shape[-1] 即矩阵维度元组中的最后一个位置的值

对于视频中卡路里计算的例子,矩阵 A3,4A_{3,4}A3,4​ 后缘维度的轴长度是4,而矩阵 cal1,4cal_{1,4}cal1,4​ 的后缘维度也是4,则他们满足后缘维度轴长度相符,可以进行广播。广播会在轴长度为1的维度进行,轴长度为1的维度对应axis=0,即垂直方向,矩阵 cal1,4\text{cal}{1,4}cal1,4 沿axis=0(垂直方向)复制成为 KaTeX parse error: Expected '}', got '_' at position 10: \text{cal_̲temp}{3,4} ,之后两者进行逐元素除法运算。

现在解释上图中的例子

矩阵 Am,nA_{m,n}Am,n​ 和矩阵 B1,nB_{1,n}B1,n​ 进行四则运算,后缘维度轴长度相符,可以广播,广播沿着轴长度为1的轴进行,即 B1,nB_{1,n}B1,n​ 广播成为 Bm,n′{B_{m,n}}'Bm,n​′ ,之后做逐元素四则运算。

矩阵 Am,nA_{m,n}Am,n​ 和矩阵 Bm,1B_{m,1}Bm,1​ 进行四则运算,后缘维度轴长度不相符,但其中一方轴长度为1,可以广播,广播沿着轴长度为1的轴进行,即 Bm,1B_{m,1}Bm,1​ 广播成为 Bm,n′{B_{m,n}}'Bm,n​′ ,之后做逐元素四则运算。

矩阵 Am,1A_{m,1}Am,1​ 和常数$ R$ 进行四则运算,后缘维度轴长度不相符,但其中一方轴长度为1,可以广播,广播沿着缺失维度和轴长度为1的轴进行,缺失维度就是axis=0,轴长度为1的轴是axis=1,即RRR广播成为 Bm,1′{B_{m,1}}'Bm,1​′ ,之后做逐元素四则运算。

最后,对于Matlab/Octave 有类似功能的函数bsxfun。

总结一下broadcasting,可以看看下面的图:

关于 python _ numpy 向量的说明(A note on python or numpy vectors)

本节主要讲Python中的numpy一维数组的特性,以及与行向量或列向量的区别。并介绍了老师在实际应用中的一些小技巧,去避免在coding中由于这些特性而导致的bug。

Python的特性允许你使用广播(broadcasting)功能,这是Python的numpy程序语言库中最灵活的地方。而我认为这是程序语言的优点,也是缺点。优点的原因在于它们创造出语言的表达性,Python语言巨大的灵活性使得你仅仅通过一行代码就能做很多事情。但是这也是缺点,由于广播巨大的灵活性,有时候你对于广播的特点以及广播的工作原理这些细节不熟悉的话,你可能会产生很细微或者看起来很奇怪的bug。例如,如果你将一个列向量添加到一个行向量中,你会以为它报出维度不匹配或类型错误之类的错误,但是实际上你会得到一个行向量和列向量的求和。

在Python的这些奇怪的影响之中,其实是有一个内在的逻辑关系的。但是如果对Python不熟悉的话,我就曾经见过的一些学生非常生硬、非常艰难地去寻找bug。所以我在这里想做的就是分享给你们一些技巧,这些技巧对我非常有用,它们能消除或者简化我的代码中所有看起来很奇怪的bug。同时我也希望通过这些技巧,你也能更容易地写没有bug的Python和numpy代码。

为了演示Python-numpy的一个容易被忽略的效果,特别是怎样在Python-numpy中构造向量,让我来做一个快速示范。首先设置a=np.random.randn(5)a=np.random.randn(5)a=np.random.randn(5),这样会生成存储在数组 aaa 中的5个高斯随机数变量。之后输出 aaa,从屏幕上可以得知,此时 aaa 的shape(形状)是一个(5,)(5,)(5,)的结构。这在Python中被称作一个一维数组。它既不是一个行向量也不是一个列向量,这也导致它有一些不是很直观的效果。举个例子,如果我输出一个转置阵,最终结果它会和aaa看起来一样,所以aaa和aaa的转置阵最终结果看起来一样。而如果我输出aaa和aaa的转置阵的内积,你可能会想:aaa乘以aaa的转置返回给你的可能会是一个矩阵。但是如果我这样做,你只会得到一个数。


所以建议你编写神经网络时,不要使用shape为 (5,)、(n,) 或者其他一维数组的数据结构。相反,如果你设置 aaa 为(5,1)(5,1)(5,1),那么这就将置于5行1列向量中。在先前的操作里 aaa 和 aaa 的转置看起来一样,而现在这样的 aaa 变成一个新的 aaa 的转置,并且它是一个行向量。请注意一个细微的差别,在这种数据结构中,当我们输出 aaa 的转置时有两对方括号,而之前只有一对方括号,所以这就是1行5列的矩阵和一维数组的差别。


如果你输出 aaa 和 aaa 的转置的乘积,然后会返回给你一个向量的外积,是吧?所以这两个向量的外积返回给你的是一个矩阵。


就我们刚才看到的,再进一步说明。首先我们刚刚运行的命令是这个 (a=np.random.randn(5))(a=np.random.randn(5))(a=np.random.randn(5)),它生成了一个数据结构aaa,其中a.shapea.shapea.shape是(5,)(5,)(5,)。这被称作 aaa 的一维数组,同时这也是一个非常有趣的数据结构。它不像行向量和列向量那样表现的很一致,这使得它带来一些不直观的影响。所以我建议,当你在编程练习或者在执行逻辑回归和神经网络时,你不需要使用这些一维数组。

相反,如果你每次创建一个数组,你都得让它成为一个列向量,产生一个(5,1)(5,1)(5,1)向量或者你让它成为一个行向量,那么你的向量的行为可能会更容易被理解。所以在这种情况下,a.shapea.shapea.shape等同于(5,1)(5,1)(5,1)。这种表现很像 aaa,但是实际上却是一个列向量。同时这也是为什么当它是一个列向量的时候,你能认为这是矩阵(5,1)(5,1)(5,1);同时这里 a.shapea.shapea.shape 将要变成(1,5)(1,5)(1,5),这就像行向量一样。所以当你需要一个向量时,我会说用这个或那个(column vector or row vector),但绝不会是一维数组。

我写代码时还有一件经常做的事,那就是如果我不完全确定一个向量的维度(dimension),我经常会扔进一个断言语句(assertion statement)。像这样,去确保在这种情况下是一个(5,1)(5,1)(5,1)向量,或者说是一个列向量。这些断言语句实际上是要去执行的,并且它们也会有助于为你的代码提供信息。所以不论你要做什么,不要犹豫直接插入断言语句。如果你不小心以一维数组来执行,你也能够重新改变数组维数 a=reshapea=reshapea=reshape,表明一个(5,1)(5,1)(5,1)数组或者一个(1,5)(1,5)(1,5)数组,以致于它表现更像列向量或行向量。


我有时候看见学生因为一维数组不直观的影响,难以定位bug而告终。通过在原先的代码里清除一维数组,我的代码变得更加简洁。而且实际上就我在代码中表现的事情而言,我从来不使用一维数组。因此,要去简化你的代码,而且不要使用一维数组。总是使用 n×1n \times 1n×1 维矩阵(基本上是列向量),或者 1×n1 \times n1×n 维矩阵(基本上是行向量),这样你可以减少很多assert语句来节省核矩阵和数组的维数的时间。另外,为了确保你的矩阵或向量所需要的维数时,不要羞于 reshape 操作。

Jupyter/iPython Notebooks快速入门(Quick tour of Jupyter/iPython Notebooks)

logistic 损失函数的解释(Explanation of logistic regression cost function)


回想一下,在逻辑回归中,需要预测的结果y^\hat{y}y^​,可以表示为y^=σ(wTx+b)\hat{y}=\sigma(w^{T}x+b)y^​=σ(wTx+b),σ\sigmaσ是我们熟悉的SSS型函数 σ(z)=σ(wTx+b)=11+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^​ 是给定训练样本 xxx 条件下 yyy 等于1的概率。换句话说,如果y=1y=1y=1,在给定训练样本 xxx 条件下y=y^y=\hat{y}y=y^​;反过来说,如果y=0y=0y=0,在给定训练样本xxx条件下 yyy 等于1减去y^(y=1−y^)\hat{y}(y=1-\hat{y})y^​(y=1−y^​),因此,如果 y^\hat{y}y^​ 代表 y=1y=1y=1 的概率,那么1−y^1-\hat{y}1−y^​就是 y=0y=0y=0的概率。接下来,我们就来分析这两个条件概率公式。

这两个条件概率公式定义形式为 p(y∣x)p(y|x)p(y∣x)并且代表了 y=0y=0y=0 或者 y=1y=1y=1 这两种情况,我们可以将这两个公式合并成一个公式。需要指出的是我们讨论的是二分类问题的损失函数,因此,yyy的取值只能是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=1y=1y=1,由于y=1y=1y=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=1y=1y=1时 p(y∣x)=y^p(y|x)=\hat{y}p(y∣x)=y^​(图中绿色部分)。

第二种情况,当 y=0y=0y=0 时 p(y∣x)p(y|x)p(y∣x) 等于多少呢? 假设y=0y=0y=0,y^\hat{y}y^​的yyy次方就是 y^\hat{y}y^​ 的0次方,任何数的0次方都等于1,因此 p(y∣x)=1×(1−y^)1−yp(y|x)=1×{(1-\hat{y})}^{1-y}p(y∣x)=1×(1−y^​)1−y ,前面假设 y=0y=0y=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=0y=0y=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 函数是严格单调递增的函数,最大化 log(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对数,就是计算 log(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) 代入),通过对数函数化简为:

ylogy^+(1−y)log(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)) ,前面有一个负号的原因是当你训练学习算法时需要算法输出值的概率是最大的(以最大的概率预测这个值),然而在逻辑回归中我们需要最小化损失函数,因此最小化损失函数与最大化条件概率的对数 log(p(y∣x))log(p(y|x))log(p(y∣x)) 关联起来了,因此这就是单个训练样本的损失函数表达式。


在 mmm个训练样本的整个训练集中又该如何表示呢,让我们一起来探讨一下。

让我们一起来探讨一下,整个训练集中标签的概率,更正式地来写一下。假设所有的训练样本服从同一分布且相互独立,也即独立同分布的,所有这些样本的联合概率就是每个样本概率的乘积:

P(labels in training set)=∏i=1mP(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=1m​P(y(i)∣x(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))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=1m​P(y(i)∣x(i))=∑i=1m​logP(y(i)∣x(i))=∑i=1m​−L(y^​(i),y(i))

在统计学里面,有一个方法叫做最大似然估计,即求出一组参数,使这个式子取最大值,也就是说,使得这个式子取最大值,∑i=1m−L(y^(i),y(i))\sum_{i= 1}^{m}{- L(\hat y^{(i)},y^{(i)})}∑i=1m​−L(y^​(i),y(i)),可以将负号移到求和符号的外面,−∑i=1mL(y^(i),y(i))- \sum_{i =1}^{m}{L(\hat y^{(i)},y^{(i)})}−∑i=1m​L(y^​(i),y(i)),这样我们就推导出了前面给出的logistic回归的成本函数J(w,b)=∑i=1mL(y^(i),y(^i))J(w,b)= \sum_{i = 1}^{m}{L(\hat y^{(i)},y^{\hat( i)})}J(w,b)=∑i=1m​L(y^​(i),y(^​i))。


由于训练模型时,目标是让成本函数最小化,所以我们不是直接用最大似然概率,要去掉这里的负号,最后为了方便,可以对成本函数进行适当的缩放,我们就在前面加一个额外的常数因子1m\frac{1}{m}m1​,即:J(w,b)=1m∑i=1mL(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=1m​L(y^​(i),y(i))。

总结一下,为了最小化成本函数J(w,b)J(w,b)J(w,b),我们从logistic回归模型的最大似然估计的角度出发,假设训练集中的样本都是独立同分布的条件下。尽管这节课是选修性质的,但还是感谢观看本节视频。我希望通过本节课您能更好地明白逻辑回归的损失函数,为什么是那种形式,明白了损失函数的原理,希望您能继续完成课后的练习,前面课程的练习以及本周的测验,在课后的小测验和编程练习中,祝您好运。

《吴恩达深度学习》学习笔记002_神经网络的编程基础(Basics of Neural Network programming)相关推荐

  1. 深度学习入门首推资料--吴恩达深度学习全程笔记分享

    本文首发于微信公众号"StrongerTang",可打开微信搜一搜,或扫描文末二维码,关注查看更多文章. 原文链接:(https://mp.weixin.qq.com/s?__bi ...

  2. 吴恩达深度学习教程——中文笔记网上资料整理

    吴恩达深度学习笔记整理 内容为网上博主博文整理,如有侵权,请私信联系. 课程内容: Coursera:官方课程安排(英文字幕).付费用户在课程作业中可以获得作业评分,每门课程修完可获得结课证书:不付费 ...

  3. 吴恩达深度学习课程翻译

    第一门课 神经网络和深度学习(Neural Networks and Deep Learning) 第一周:深度学习引言(Introduction to Deep Learning) 1.1 欢迎(W ...

  4. [转载]《吴恩达深度学习核心笔记》发布,黄海广博士整理!

    红色石头 深度学习专栏 深度学习入门首推课程就是吴恩达的深度学习专项课程系列的 5 门课.该专项课程最大的特色就是内容全面.通俗易懂并配备了丰富的实战项目.今天,给大家推荐一份关于该专项课程的核心笔记 ...

  5. 737 页《吴恩达深度学习核心笔记》发布,黄海广博士整理!

    点击上方"AI有道",选择"置顶"公众号 重磅干货,第一时间送达 深度学习入门首推课程就是吴恩达的深度学习专项课程系列的 5 门课.该专项课程最大的特色就是内容 ...

  6. 799页!吴恩达深度学习笔记.PDF

    吴恩达深度学习课程,是公认的最优秀的深度学习课程之一,目前没有教材,只有视频,本文提供完整笔记下载,这本笔记非常适合和深度学习入门. 0.导语 黄海广博士和同学将吴恩达老师深度学习视频课程做了完整的笔 ...

  7. 吴恩达深度学习课程的漫画版来了!(漫画、视频、笔记都可以下载了!)

    吴恩达深度学习课程,个人认为是对初学者最友好的课程,非常系统.初学者如果希望快速入门,建议从这门课开始.由于是视频课,除了课程笔记之外,可以先看看课程漫画,更有助于理解. 尽管是英文版,但英文水平达到 ...

  8. github标星8331+:吴恩达深度学习课程资源(完整笔记、中英文字幕视频、python作业,提供百度云镜像!)...

    吴恩达老师的深度学习课程(deeplearning.ai),可以说是深度学习入门的最热门课程,我和志愿者编写了这门课的笔记,并在github开源,star数达到8331+,曾经有相关报道文章.为解决g ...

  9. 吴恩达深度学习课程笔记(初步认识神经网络)

    吴恩达深度学习课程笔记1 课程主要内容 1.神经网络与深度学习介绍 2.Improving Deep Neural Networks:超参数调整,正则化,优化方法 3.结构化机器学习工程:比如如何分割 ...

最新文章

  1. android listview edittext 焦点冲突,Android开发之ListView+EditText-要命的焦点和软键盘问题解决办法...
  2. GetWindowThreadProcessId
  3. C#和java的语法区别
  4. 软件架构设计_软件架构设计的三个维度,软件架构师需要知道的点,了解一下吧...
  5. 如何评价「施一公请辞清华大学副校长,全职执掌西湖大学」?你如何看待西湖大学的发展前景?
  6. 二本院校女毕业生,想去北漂,求建议
  7. OAuth 2.0 in Web API #Reprinted
  8. 【119天】尚学堂高琪Java300集视频精华笔记(24-30)
  9. C++软件工程师的发展前景如何?
  10. 人家出轨你为什么那么嗨
  11. Java 实现word、excel、ppt、txt等办公文件在线预览功能!
  12. 网络营销之网络炒作案例分析、精髓及方法讨论
  13. 用汇编语言编程的计算机
  14. 基于OTSU算法和基本粒子群优化算法的双阈值图像分割
  15. whm修改tmp目录空间大小为4096M
  16. 计算机网络五要素,网络安全五个基本要素是什么
  17. ZYNQ Linux 移植:包含petalinux移植和手动移植debian9
  18. Azure Stack技术深入浅出系列6:Azure Stack一体机探究 — 揭开黑盒子的神秘面纱
  19. 使用R包networkD3绘制炫酷的动态关系网络
  20. Java系列(34)——可变字符串

热门文章

  1. RGB图像卷积生成Feature map特征图过程
  2. python实现栅栏加密 超简易列表版本
  3. 【项目】Vue3+TS 动态路由 面包屑 查询重置 列表
  4. MYSQL的删除和重装
  5. partial部分类
  6. 差商近似1阶导数matlab,常微分方程的解法 (一): 常微分方程的离散化 :差商近似导数、数值积分方法、Taylor 多项式近似...
  7. Adaptive large neighborhood search Heuristics for the vehicle routing problem with stochastic deman
  8. 改造Kindeditor之:自定义图片上传插件。 外加给图片增加水印效果的选择。
  9. 台式计算机usb接口无反应6,如何解决电脑的USB接口没反应,详细教您如何解决
  10. 陈天桥染指手机传言再起 数位红担当重任