Coursera课程《Neural Networks and Deep Learning》 deeplearning.ai
Week2 Neural Networks Basics
2.1 Logistic Regression as a Neutral Network
2.1.1 Binary Classification 二分类
逻辑回归是一个用于二分类(binary classification)的算法。首先我们从一个问题开始说起,这里有一个二分类问题的例子,假如你有一张图片作为输入,比如这只猫,如果识别这张图片为猫,则输出标签1作为结果;如果识别出不是猫,那么输出标签0作为结果。现在我们可以用字母 \(y\)来 表示输出的结果标签,如下图所示:
计算机存储图片,是存成RGB三个颜色通道的三个矩阵。也就是说如果图片大小是64*64像素,那么就有三个64*64的矩阵。
为了把这些像素值放到一个特征向量中,我们需要把这些像素值提取出来,然后放入一个特征向量\(x\)。所以最后我们得到的这个向量,将会是\(64\times64\times3=12,288\)长度的向量。我们可以使用\(n_x=12,288\)来表示特征向量的维度。
所以在二分类问题中,我们的目标就是习得一个分类器,它以图片的特征向量作为输入,然后预测输出结果\(y\)为1还是0,也就是预测图片中是否有猫。
符号定义 :
\(x\):表示一个\(n_x\)维数据,为输入数据,维度为\((n_x,1)\);
\(y\):表示输出结果,取值为\((0,1)\);
\((x^{(i)},y^{(i)})\):表示第\(i\)组数据,可能是训练数据,也可能是测试数据,此处默认为训练数据;
\(X=[x^{(1)},x^{(2)},...,x^{(m)}]\):表示所有的训练数据集的输入值,放在一个 \(n_x×m\)的矩阵中,其中\(m\)表示样本数目;
\(Y=[y^{(1)},y^{(2)},...,y^{(m)}]\):对应表示所有训练数据集的输出值,维度为\(1×m\)。
用一对\((x,y)\)来表示一个单独的样本,\(x\)代表\(n_x\)维的特征向量,\(y\) 表示标签(输出结果)只能为0或1。有时候为了强调这是训练样本的个数,会写作\(M_{train}\),当涉及到测试集的时候,我们会使用\(M_{test}\)来表示测试集的样本数,所以这是测试集的样本数。
为了能把训练集表示得更紧凑一点,我们会定义一个矩阵用大写\(X\)的表示,它由输入向量\(x^{(1)}\)、\(x^{(2)}\)等组成,如下图放在矩阵的列中,所以现在我们把\(x^{(1)}\)作为第一列放在矩阵中,\(x^{(2)}\)作为第二列,\(x^{(m)}\)放到第\(m\)列,然后我们就得到了训练集矩阵\(X\)。所以这个矩阵有\(m\)列,\(m\)是训练集的样本数量,然后这个矩阵的高度记为\(n_x\),注意有时候可能因为其他某些原因,矩阵\(X\)会由训练样本按照行堆叠起来而不是列,如下图所示:\(x^{(1)}\)的转置直到\(x^{(m)}\)的转置,但是在实现神经网络的时候,使用左边的这种形式,会让整个实现的过程变得更加简单。
输出标签\(y\)同样的道理,为了能更加容易地实现一个神经网络,将标签\(y\)放在列中将会使得后续计算非常方便,所以我们定义大写的\(Y\)等于\({{y}^{\left( 1 \right)}},{{y}^{\left( m \right)}},...,{{y}^{\left( m \right)}}\),所以在这里是一个规模为1乘以\(m\)的矩阵。
2.2.2 Logistic Regression 逻辑回归
对于二元分类问题来讲,给定一个输入特征向量\(X\),它可能对应一张图片,你想识别这张图片识别看它是否是一只猫或者不是一只猫的图片,你想要一个算法能够输出预测,你只能称之为\(\hat{y}\),也就是你对实际值 \(y\) 的估计。更正式地来说,你想让 \(\hat{y}\) 表示 \(y\) 等于1的一种可能性或者是机会,前提条件是给定了输入特征\(X\)。换句话来说,如果\(X\)是我们在上个视频看到的图片,你想让 \(\hat{y}\) 来告诉你这是一只猫的图片的机率有多大。
\(X\)是一个\(n_x\)维的向量(相当于有\(n_x\)个特征的特征向量)。我们用\(w\)来表示逻辑回归的参数,这也是一个\(n_x\)维向量(因为\(w\)实际上是特征权重,维度与特征向量相同),参数里面还有\(b\),这是一个实数(表示偏差)。所以给出输入\(x\)以及参数\(w\)和\(b\)之后,如果让\(\hat{y}={{w}^{T}}x+b\),那么我们得到的是关于\(x\)的线性函数。但是这对于二分类问题来说不是一个很好的算法,因为我们想要让\(\hat{y}\)表示实际值\(y\)等于1的几率的话,\(\hat{y}\)应该在0到1之间。
因此在逻辑回归里,我们的输出应该是\(\hat{y}\)等于由上面得到的线性函数式子作为自变量的sigmoid函数,将线性函数转换为非线性函数。该函数的公式如下:
\(\sigma\left(z\right)=\frac{1}{1+e^{-z}}\)
因此当你实现逻辑回归时,你的工作就是去让机器学习参数\(w\)以及\(b\),这样才使得\(\hat{y}\)成为对\(y=1\)这一情况的概率的一个很好的估计。
在继续进行下一步之前,介绍一种符号惯例,可以让参数\(w\)和参数\(b\)分开。在符号上要注意的一点是当我们对神经网络进行编程时经常会让参数\(w\)和参数\(b\)分开,在这里参数\(b\)对应的是一种偏置。在之前的机器学习课程里,你可能已经见过处理这个问题时的其他符号表示。比如在某些例子里,你定义一个额外的特征称之为\({{x}_{0}}\),并且使它等于1,那么现在\(X\)就是一个\(n_x+1\)维的变量,然后你定义\(\hat{y}=\sigma \left( {{\theta }^{T}}x \right)\)的sigmoid函数。在这个备选的符号惯例里,你有一个参数向量\({{\theta }_{0}},{{\theta }_{1}},{{\theta }_{2}},...,{{\theta }_{{{n}_{x}}}}\),这样\({{\theta }_{0}}\)就充当了\(b\),这是一个实数,而剩下的\({{\theta }_{1}}\) 直到\({{\theta }_{{{n}_{x}}}}\)充当了\(w\),结果就是当你实现你的神经网络时,有一个比较简单的方法是保持\(b\)和\(w\)分开。
2.2.3 Logistic Regression Cost Function 逻辑回归的代价函数
对训练集的预测值,我们将它写成\(\hat{y}\),我们更希望它会接近于训练集中的\(y\)值。为了让模型通过学习调整参数,你需要给予一个\(m\)样本的训练集,这会让你在训练集上找到参数\(w\)和参数\(b\),来得到你的输出。
我们使用这些带有圆括号的上标来区分索引和样本,训练样本\(i\)所对应的预测值是\({{y}^{(i)}}\),是用训练样本的\({{w}^{T}}{{x}^{(i)}}+b\)然后通过sigmoid函数来得到,也可以把\(z\)定义为\({{z}^{(i)}}={{w}^{T}}{{x}^{(i)}}+b\),我们将使用这个符号\((i)\)注解,上标\((i)\)来指明数据表示\(x\)或者\(y\)或者\(z\)或者其他数据的第\(i\)个训练样本,这就是上标\((i)\)的含义。
损失函数:
损失函数又叫做误差函数,用来衡量算法的运行情况,Loss function:\(L\left( \hat{y},y \right)\).
一般我们用预测值和实际值的平方差或者它们平方差的一半,但是通常在逻辑回归中我们不这么做,因为当我们在学习逻辑回归参数的时候,会发现我们的优化目标不是凸优化,只能找到多个局部最优值,梯度下降法很可能找不到全局最优值,虽然平方差是一个不错的损失函数,但是我们在逻辑回归模型中会定义另外一个损失函数。
我们在逻辑回归中用到的损失函数是:\(L\left( \hat{y},y \right)=-y\log(\hat{y})-(1-y)\log (1-\hat{y})\)
当\(y=1\)时损失函数\(L=-\log (\hat{y})\),如果想要损失函数\(L\)尽可能得小,那么\(\hat{y}\)就要尽可能大,因为sigmoid函数取值\([0,1]\),所以\(\hat{y}\)会无限接近于1。
损失函数是在单个训练样本中定义的,它衡量的是算法在单个训练样本中表现如何,为了衡量算法在全部训练样本上的表现如何,我们需要定义一个算法的代价函数,算法的代价函数是对\(m\)个样本的损失函数求和然后除以\(m\):
\(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)}\)
损失函数只适用于像这样的单个训练样本,而代价函数是参数的总代价,所以在训练逻辑回归模型时候,我们需要找到合适的\(w\)和\(b\),来让代价函数 \(J\) 的总代价降到最低。
2.2.4 Gradient Descent 梯度下降
梯度下降法可以在你测试集上,通过最小化代价函数(成本函数)\(J(w,b)\)来训练的参数\(w\)和\(b\)。
在这个图中,横轴表示你的空间参数\(w\)和\(b\),在实践中,\(w\)可以是更高的维度,但是为了更好地绘图,我们定义\(w\)和\(b\),都是单一实数,代价函数(成本函数)\(J(w,b)\)是在水平轴\(w\)和\(b\)上的曲面,因此曲面的高度就是\(J(w,b)\)在某一点的函数值。我们所做的就是找到使得代价函数(成本函数)\(J(w,b)\)函数值是最小值,对应的参数\(w\)和\(b\)。
如图,代价函数(成本函数)\(J(w,b)\)是一个凸函数(convex function),像一个大碗一样。
如图,这就与刚才的图有些相反,因为它是非凸的并且有很多不同的局部最小值。由于逻辑回归的代价函数(成本函数)\(J(w,b)\)特性,我们必须定义代价函数(成本函数)\(J(w,b)\)为凸函数。
可以用如图那个小红点来初始化参数\(w\)和\(b\),也可以采用随机初始化的方法,对于逻辑回归几乎所有的初始化方法都有效,因为函数是凸函数,无论在哪里初始化,应该达到同一点或大致相同的点。
假定代价函数(成本函数)\(J(w)\) 只有一个参数\(w\),即用一维曲线代替多维曲线,这样可以更好画出图像。
迭代就是不断重复做如图的公式:
\(:=\)表示更新参数,
$a $ 表示学习率(learning rate),用来控制步长(step),即向下走一步的长度\(\frac{dJ(w)}{dw}\) 就是函数\(J(w)\)对\(w\) 求导(derivative),在代码中我们会使用\(dw\)表示这个结果。
整个梯度下降法的迭代过程就是不断地向左走,直至逼近最小值点。
逻辑回归的代价函数(成本函数)\(J(w,b)\)是含有两个参数的。
$\partial $ 表示求偏导符号,可以读作round,
\(\frac{\partial J(w,b)}{\partial w}\) 就是函数\(J(w,b)\) 对\(w\) 求偏导,在代码中我们会使用\(dw\) 表示这个结果,
\(\frac{\partial J(w,b)}{\partial b}\) 就是函数\(J(w,b)\)对\(b\) 求偏导,在代码中我们会使用\(db\) 表示这个结果,
小写字母\(d\) 用在求导数(derivative),即函数只有一个参数,
偏导数符号$\partial $ 用在求偏导(partial derivative),即函数含有两个以上的参数。
2.2.5 导数(Derivatives)
跳过,高数知识足够。
2.2.6 更多的导数例子(More Derivative Examples)
跳过,高数知识足够。
2.2.7 计算图(Computation Graph)
跳过,好像其实就是高数中的复合函数情况。
2.2.8 使用计算图求导数(Derivatives with a Computation Graph)
跳过,其实就是复合函数求导情况。
2.2.9 逻辑回归中的梯度下降(Logistic Regression Gradient Descent)
\[z=w^{T}x+b\]
\[\hat{y}=a=\sigma\left(z\right)\]
\[L\left(a,y\right)=-\left(ylog\left(a\right)+\left(1-y\right)log\left(1-a\right)\right)\]
所以通过上面这个过程,我们就能求导每个变量的偏导数。
最后一步反向推导,也就是计算\(w\)和\(b\)变化对代价函数\(L\)的影响,特别地,可以用:
\(d{{w}_{1}}=\frac{1}{m}\sum\limits_{i}^{m}{x_{1}^{(i)}}({{a}^{(i)}}-{{y}^{(i)}})\)
\(d{{w}_{2}}=\frac{1}{m}\sum\limits_{i}^{m}{x_{2}^{(i)}}({{a}^{(i)}}-{{y}^{(i)}})\)
\(db=\frac{1}{m}\sum\limits_{i}^{m}{({{a}^{(i)}}-{{y}^{(i)}})}\)
因此,关于单个样本的梯度下降算法,你所需要做的就是如下的事情:
使用公式\(dz=(a-y)\)计算\(dz\),
使用\(d{{w}_{1}}={{x}_{1}}\cdot dz\) 计算\(d{{w}_{1}}\), \(d{{w}_{2}}={{x}_{2}}\cdot dz\)计算\(d{{w}_{2}}\),
\(db=dz\) 来计算\(db\),
然后:
更新\({{w}_{1}}={{w}_{1}}-a d{{w}_{1}}\),
更新\({{w}_{2}}={{w}_{2}}-a d{{w}_{2}}\),
更新\(b=b-\alpha db\)。
这就是关于单个样本实例的梯度下降算法中参数更新一次的步骤。
2.2.10 m 个样本的梯度下降(Gradient Descent on m Examples)
带有求和的全局代价函数,实际上是1到\(m\)项各个损失的平均。 所以它表明全局代价函数对\({{w}_{1}}\)的微分,对\({{w}_{1}}\)的微分也同样是各项损失对\({{w}_{1}}\)微分的平均。
我们把这些装进一个具体的算法。同时你需要一起应用的就是逻辑回归和梯度下降。
我们初始化\(J=0,d{{w}_{1}}=0,d{{w}_{2}}=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
幻灯片上只应用了一步梯度下降。因此你需要重复以上内容很多次,以应用多次梯度下降。
2.2.11 向量化(Vectorization)
都是在说和for循环相比,向量化可以快速得到结果。
2.2.12 向量化的更多例子(More Examples of Vectorization)
同上。
2.2.13 向量化的逻辑回归(Vectorizing Logistic Regression)
向量化逻辑回归那些公式。
2.2.14 向量化 logistic 回归的梯度输出(Vectorizing Logistic Regression's Gradient)
现在,让我们回顾一下,看看我们之前怎么实现的逻辑回归,可以发现,没有向量化是非常低效的,如下图所示代码:
我们的目标是不使用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\)
现在我们利用前五个公式完成了前向和后向传播,也实现了对所有训练样本进行预测和求导,再利用后两个公式,梯度下降更新参数
2.2.15 Python 中的广播(Broadcasting in Python)
这是一个不同食物(每100g)中不同营养成分的卡路里含量表格,表格为3行4列,列表示不同的食物种类,从左至右依次为苹果,牛肉,鸡蛋,土豆。行表示不同的营养成分,从上到下依次为碳水化合物,蛋白质,脂肪。
那么,我们现在想要计算不同食物中不同营养成分中的卡路里百分比。
现在计算苹果中的碳水化合物卡路里百分比含量,首先计算苹果(100g)中三种营养成分卡路里总和56+1.2+1.8
= 59,然后用56/59 = 94.9%算出结果。
可以看出苹果中的卡路里大部分来自于碳水化合物,而牛肉则不同。
对于其他食物,计算方法类似。首先,按列求和,计算每种食物中(100g)三种营养成分总和,然后分别用不用营养成分的卡路里数量除以总和,计算百分比。
那么,能否不使用for循环完成这样的一个计算过程呢?
假设上图的表格是一个4行3列的矩阵\(A\),记为 \(A_{3\times 4}\),接下来我们要使用Python的numpy库完成这样的计算。我们打算使用两行代码完成,第一行代码对每一列进行求和,第二行代码分别计算每种食物每种营养成分的百分比。
在jupyter notebook中输入如下代码,按shift+Enter运行,输出如下。
下面使用如下代码计算每列的和,可以看到输出是每种食物(100g)的卡路里总和。
其中sum
的参数axis=0
表示求和运算按列执行,之后会详细解释。
接下来计算百分比,这条指令将 \(3\times 4\)的矩阵\(A\)除以一个\(1 \times 4\)的矩阵,得到了一个 \(3 \times 4\)的结果矩阵,这个结果矩阵就是我们要求的百分比含量。
下面再来解释一下A.sum(axis = 0)
中的参数axis
。axis用来指明将要进行的运算是沿着哪个轴执行,在numpy中,0轴是垂直的,也就是列,而1轴是水平的,也就是行。
而第二个A/cal.reshape(1,4)
指令则调用了numpy中的广播机制。这里使用 \(3 \times 4\)的矩阵\(A\)除以 \(1 \times 4\)的矩阵\(cal\)。技术上来讲,其实并不需要再将矩阵\(cal\) reshape
(重塑)成 \(1 \times 4\),因为矩阵\(cal\)本身已经是 \(1 \times 4\)了。但是当我们写代码时不确定矩阵维度的时候,通常会对矩阵进行重塑来确保得到我们想要的列向量或行向量。重塑操作reshape
是一个常量时间的操作,时间复杂度是\(O(1)\),它的调用代价极低。
那么一个 \(3 \times 4\) 的矩阵是怎么和 \(1 \times 4\)的矩阵做除法的呢?让我们来看一些更多的广播的例子。
在numpy中,当一个 \(4 \times 1\)的列向量与一个常数做加法时,实际上会将常数扩展为一个 \(4 \times 1\)的列向量,然后两者做逐元素加法。结果就是右边的这个向量。这种广播机制对于行向量和列向量均可以使用。
再看下一个例子。
用一个 \(2 \times 3\)的矩阵和一个 \(1 \times 3\) 的矩阵相加,其泛化形式是 \(m \times n\) 的矩阵和 \(1 \times n\)的矩阵相加。在执行加法操作时,其实是将 \(1 \times n\) 的矩阵复制成为 \(m \times n\) 的矩阵,然后两者做逐元素加法得到结果。针对这个具体例子,相当于在矩阵的第一列加100,第二列加200,第三列加300。这就是在前一张幻灯片中计算卡路里百分比的广播机制,只不过这里是除法操作(广播机制与执行的运算种类无关)。
下面是最后一个例子
这里相当于是一个 \(m \times n\) 的矩阵加上一个 \(m \times 1\) 的矩阵。在进行运算时,会先将 \(m \times 1\) 矩阵水平复制 \(n\) 次,变成一个 \(m \times n\) 的矩阵,然后再执行逐元素加法。
对于Matlab/Octave 有类似功能的函数bsxfun
。
总结一下broadcasting
,可以看看下面的图:
2.2.16 关于 python_numpy 向量的说明(A note on python or numpy vectors)
Python的特性允许我们使用广播(broadcasting)功能,这是Python的numpy程序语言库中最灵活的地方。而这是程序语言的优点,也是缺点。有时候可能会产生很细微或者看起来很奇怪的bug。比如,将一个列向量添加到一个行向量中,你会以为它报出维度不匹配或类型错误之类的错误,但是实际上你会得到一个行向量和列向量的求和。
所以我们尽量不要使用这些一维数组。相反,如果你每次创建一个数组,你都得让它成为一个列向量,产生一个\((5,1)\)向量或者你让它成为一个行向量,那么你的向量的行为可能会更容易被理解。
# don't use
a = np.random.randn(5)
# recommand
a = np.random,randn(5,1)
a = np.random.randn(1,5)
2.2.17 Jupyter/iPython Notebooks快速入门(Quick tour of Jupyter/iPython Notebooks)
介绍Jupyter。
【参考】