目录
一、深度学习引言(Introduction to Deep Learning)
1、欢迎介绍
2、什么是神经网络?(What is a Neural Network)
3、神经网络的监督学习(Supervised Learning with Neural Networks)
4、 为什么深度学习会兴起?(Why is Deep Learning taking off?)
二 、神经网络的编程基础(Basics of Neural Network programming)
1、二分类
2、逻辑回归
3、逻辑回归的代价函数(Logistic Regression Cost Function)
4、梯度下降法(Gradient Descent)
5 、导数(Derivatives)
6、更多的导数例子(More Derivative Examples)
7、计算图(Computation Graph)
8、使用计算图求导数(Derivatives with a Computation Graph)
9 、逻辑回归中的梯度下降(Logistic Regression Gradient Descent)
10、m 个样本的梯度下降(Gradient Descent on m Examples)
11、向量化(Vectorization)
12、向量化的更多例子(More Examples of Vectorization)
13、向量化逻辑回归(Vectorizing Logistic Regression)
14、向量化 logistic 回归的梯度输出(Vectorizing Logistic Regression's Gradient)
15、Python 中的广播(Broadcasting in Python)
16、关于 python _ numpy 向量的说明(A note on python or numpy vectors)
这部分内容主要讲解什么是深度学习,深度学习主要能做什么事情。
深度学习改变了传统互联网业务,例如网络搜索和广告。但是深度学习同时也使得许多新产品和企业以很多方式帮助人们,从获得更好的健康关注。
深度学习做的非常好的一个方面就是读取X光图像,到生活中的个性化教育,到精准化农业,甚至到驾驶汽车以及其它一些方面。如果你想要学习深度学习的这些工具,并应用它们来做这些令人窒息的操作。在接下来的十年中,我认为我们所有人都有机会创造一个惊人的世界和社会,这就是AI(人工智能)的力量。我希望你们能在创建AI(人工智能)社会的过程中发挥重要作用。
我认为AI是最新的电力,大约在一百年前,我们社会的电气化改变了每个主要行业,从交通运输行业到制造业、医疗保健、通讯等方面,我认为如今我们见到了AI明显的令人惊讶的能量,带来了同样巨大的转变。显然,AI的各个分支中,发展的最为迅速的就是深度学习。因此现在,深度学习是在科技世界中广受欢迎的一种技巧。
在这部分内容(神经网络和深度学习)中,我们将学习神经网络的基础,将学习神经网络和深度学习,除此之外,我们将学习如何建立神经网络(包含一个深度神经网络),以及如何在数据上面训练他们。在这门课程的结尾,我们将用一个深度神经网络进行辨认猫。
由于某种原因,第一部分内容会以猫作为对象识别。
接下来的第二部分内容,我们将进行深度学习方面的实践,学习严密地构建神经网络,如何真正让它表现良好,因此我们将要学习超参数调整、正则化、诊断偏差和方差以及一些高级优化算法,比如Momentum和Adam算法,犹如黑魔法一样根据你建立网络的方式。
在下面的内容中,我们将使用两周时间来学习如何结构化你的机器学习工程。事实证明,构建机器学习系统的策略改变了深度学习的错误。举个例子:我们分割数据的方式,分割成训练集、比较集或改变的验证集,以及测试集合,改变了深度学习的错误。
所以最好的实践方式是什么呢?的训练集和测试集来自不同的贡献度在深度学习中的影响很大,那么你应该怎么处理呢?如果你听说过端对端深度学习,你也会在第三部分内容中了解到更多,进而了解到你是否需要使用它,第三部分内容的资料是相对比较独特的,我将和你分享。我们了解到的所有的热门领域的建立并且改良许多的深度学习问题。这些当今热门的资料,绝大部分大学在他们的深度学习课堂上面里面不会教的,我认为它会提供你帮助,让深度学习系统工作的更好。
在第四部分内容中,我们将会提到卷积神经网络(CNN(s)),它经常被用于图像领域,你将会在第四门课程中学到如何搭建这样的模型。
最后在第五部分内容中,你将会学习到序列模型,以及如何将它们应用于自然语言处理,以及其它问题。序列模型包括的模型有循环神经网络(RNN)、全称是长短期记忆网络(LSTM)。你将在第五部分内容中了解其中的时期是什么含义,并且有能力应用到自然语言处理(NLP)问题。
总之你将在第五部分内容中学习这些模型,以及能够将它们应用于序列数据。比如说,自然语言就是一个单词序列。你也将能够理解这些模型如何应用到语音识别或者是编曲以及其它问题。因此,通过这些课程,你将学习深度学习的这些工具,你将能够去使用它们去做一些神奇的事情,并借此来提升你的职业生涯。
我们常常用深度学习这个术语来指训练神经网络的过程。有时它指的是特别大规模的神经网络训练。那么神经网络究竟是什么呢?在这里讲解一些直观的基础知识。
让我们从一个房价预测的例子开始讲起。
假设你有一个数据集,它包含了六栋房子的信息。所以,你知道房屋的面积是多少平方英尺或者平方米,并且知道房屋价格。这时,你想要拟合一个根据房屋面积预测房价的函数。
如果你对线性回归很熟悉,你可能会说:“好吧,让我们用这些数据拟合一条直线。”于是你可能会得到这样一条直线。
但有点奇怪的是,你可能也发现了,我们知道价格永远不会是负数的。因此,为了替代一条可能会让价格为负的直线,我们把直线弯曲一点,让它最终在零结束。这条粗的蓝线最终就是你的函数,用于根据房屋面积预测价格。有部分是零,而直线的部分拟合的很好。你也许认为这个函数只拟合房屋价格。
作为一个神经网络,这几乎可能是最简单的神经网络。我们把房屋的面积作为神经网络的输入(我们称之为 ),通过一个节点(一个小圆圈),最终输出了价格(我们用 表示)。其实这个小圆圈就是一个单独的神经元。接着你的网络实现了左边这个函数的功能。
在有关神经网络的文献中,你经常看得到这个函数。从趋近于零开始,然后变成一条直线。这个函数被称作ReLU激活函数,它的全称是Rectified Linear Unit。rectify(修正)可以理解成 ,这也是你得到一个这种形状的函数的原因。
我们现在不用担心不理解ReLU函数,我们将会在这门课的后面再次看到它。
如果这是一个单神经元网络,不管规模大小,它正是通过把这些单个神经元叠加在一起来形成。如果你把这些神经元想象成单独的乐高积木,你就通过搭积木来完成一个更大的神经网络。
让我们来看一个例子,我们不仅仅用房屋的面积来预测它的价格,现在你有了一些有关房屋的其它特征,比如卧室的数量,或许有一个很重要的因素,一家人的数量也会影响房屋价格,这个房屋能住下一家人或者是四五个人的家庭吗?而这确实是基于房屋大小,以及真正决定一栋房子是否能适合你们家庭人数的卧室数。
换个话题,你可能知道邮政编码或许能作为一个特征,告诉你步行化程度。比如这附近是不是高度步行化,你是否能步行去杂货店或者是学校,以及你是否需要驾驶汽车。有些人喜欢居住在以步行为主的区域,另外根据邮政编码还和富裕程度相关(在美国是这样的)。但在其它国家也可能体现出附近学校的水平有多好。
在图上每一个画的小圆圈都可以是ReLU的一部分,也就是指修正线性单元,或者其它稍微非线性的函数。基于房屋面积和卧室数量,可以估算家庭人口,基于邮编,可以估测步行化程度或者学校的质量。最后你可能会这样想,这些决定人们乐意花费多少钱。
对于一个房子来说,这些都是与它息息相关的事情。在这个情景里,家庭人口、步行化程度以及学校的质量都能帮助你预测房屋的价格。以此为例, 是所有的这四个输入, 是你尝试预测的价格,把这些单个的神经元叠加在一起,我们就有了一个稍微大一点的神经网络。这显示了神经网络的神奇之处,虽然我已经描述了一个神经网络,它可以需要你得到房屋面积、步行化程度和学校的质量,或者其它影响价格的因素。
神经网络的一部分神奇之处在于,当你实现它之后,你要做的只是输入 ,就能得到输出 。因为它可以自己计算你训练集中样本的数目以及所有的中间过程。所以,你实际上要做的就是:这里有四个输入的神经网络,这输入的特征可能是房屋的大小、卧室的数量、邮政编码和区域的富裕程度。给出这些输入的特征之后,神经网络的工作就是预测对应的价格。同时也注意到这些被叫做隐藏单元圆圈,在一个神经网络中,它们每个都从输入的四个特征获得自身输入,比如说,第一个结点代表家庭人口,而家庭人口仅仅取决于 和 特征,换句话说,在神经网络中,你决定在这个结点中想要得到什么,然后用所有的四个输入来计算想要得到的。因此,我们说输入层和中间层被紧密的连接起来了。
值得注意的是神经网络给予了足够多的关于 和 的数据,给予了足够的训练样本有关和 。神经网络非常擅长计算从到的精准映射函数。
这就是一个基础的神经网络。你可能发现你自己的神经网络在监督学习的环境下是如此的有效和强大,也就是说你只要尝试输入一个,即可把它映射成,就好像我们在刚才房价预测的例子中看到的效果。
关于神经网络也有很多的种类,考虑到它们的使用效果,有些使用起来恰到好处,但事实表明,到目前几乎所有由神经网络创造的经济价值,本质上都离不开一种叫做监督学习的机器学习类别,让我们举例看看。
在监督学习中你有一些输入 ,你想学习到一个函数来映射到一些输出 ,比如我们之前提到的房价预测的例子,你只要输入有关房屋的一些特征,试着去输出或者估计价格 。我们举一些其它的例子,来说明神经网络已经被高效应用到其它地方。
如今应用深度学习获利最多的一个领域,就是在线广告。这也许不是最鼓舞人心的,但真的很赚钱。具体就是通过在网站上输入一个广告的相关信息,因为也输入了用户的信息,于是网站就会考虑是否向你展示广告。
神经网络已经非常擅长预测你是否会点开这个广告,通过向用户展示最有可能点开的广告,这就是神经网络在很多家公司难以置信地提高获利的一种应用。因为有了这种向你展示你最有可能点击的广告的能力,而这一点击的行为的改变会直接影响到一些大型的在线广告公司的收入。
计算机视觉在过去的几年里也取得了长足的进步,这也多亏了深度学习。你可以输入一个图像,然后想输出一个索引,范围从1到1000来试着告诉你这张照片,它可能是,比方说,1000个不同的图像中的任何一个,所以你可能会选择用它来给照片打标签。
深度学习最近在语音识别方面的进步也是非常令人兴奋的,你现在可以将音频片段输入神经网络,然后让它输出文本记录。得益于深度学习,机器翻译也有很大的发展。你可以利用神经网络输入英语句子,接着输出一个中文句子。
在自动驾驶技术中,你可以输入一幅图像,就好像一个信息雷达展示汽车前方有什么,据此,你可以训练一个神经网络,来告诉汽车在马路上面具体的位置,这就是神经网络在自动驾驶系统中的一个关键成分。
那么深度学习系统已经可以创造如此多的价值,通过智能的选择,哪些作为 哪些作为 ,来针对于你当前的问题,然后拟合监督学习部分,往往是一个更大的系统,比如自动驾驶。这表明神经网络类型的轻微不同,也可以产生不同的应用,比如说,应用到我们在上一部分内容提到的房地产领域,我们不就使用了一个普遍标准神经网络架构吗?
我们也可能见过这样的图片,这是一个卷积神经网络的例子。
我们会在后面的课程了解这幅图的原理和实现,卷积网络(CNN)通常用于图像数据。
你可能也会看到这样的图片,而且你将在以后的课程中学习如何实现它。
递归神经网络(RNN)非常适合这种一维序列,数据可能是一个时间组成部分。
你可能也听说过机器学习对于结构化数据和非结构化数据的应用,结构化数据意味着数据的基本数据库。例如在房价预测中,你可能有一个数据库,有专门的几列数据告诉你卧室的大小和数量,这就是结构化数据。或预测用户是否会点击广告,你可能会得到关于用户的信息,比如年龄以及关于广告的一些信息,然后对你的预测分类标注,这就是结构化数据,意思是每个特征,比如说房屋大小卧室数量,或者是一个用户的年龄,都有一个很好的定义。
相反非结构化数据是指比如音频,原始音频或者你想要识别的图像或文本中的内容。这里的特征可能是图像中的像素值或文本中的单个单词。
从历史经验上看,处理非结构化数据是很难的,与结构化数据比较,让计算机理解非结构化数据很难,而人类进化得非常善于理解音频信号和图像,文本是一个更近代的发明,但是人们真的很擅长解读非结构化数据。
神经网络的兴起就是这样最令人兴奋的事情之一,多亏了深度学习和神经网络,计算机现在能更好地解释非结构化数据,这是与几年前相比的结果,这为我们创造了机会。许多新的令人兴奋的应用被使用,语音识别、图像识别、自然语言文字处理,甚至可能比两三年前的还要多。因为人们天生就有本领去理解非结构化数据,你可能听说了神经网络更多在媒体非结构化数据的成功,当神经网络识别了一只猫时那真的很酷,我们都知道那意味着什么。
但结果也表明,神经网络在许多短期经济价值的创造,也是基于结构化数据的。比如更好的广告系统、更好的利润建议,还有更好的处理大数据的能力。许多公司不得不根据神经网络做出准确的预测。
因此在这门课中,我们将要讨论的许多技术都将适用,不论是对结构化数据还是非结构化数据。为了解释算法,我们将在使用非结构化数据的示例中多画一点图片,但正如你所想的,你自己团队里通过运用神经网络,我希望你能发现,神经网络算法对于结构化和非结构化数据都有用处。
神经网络已经改变了监督学习,正创造着巨大的经济价值,事实证明,基本的神经网络背后的技术理念大部分都离我们不遥远,有的是几十年,那么为什么他们现在才刚刚起步,效果那么好,下面 一部分将讨论为什么最近的神经网络已经成为你可以使用的强大工具。
推动深度学习变得如此热门的主要因素:包括数据规模、计算量及算法的创新。
深度学习和神经网络之前的基础技术理念已经存在大概几十年了,为什么它们现在才突然流行起来呢?本部分内容主要讲述一些使得深度学习变得如此热门的主要驱动因素,这将会帮助我们在我们的组织机构内发现最好的时机来应用这些东西。
在过去的几年里,很多人都问我为什么深度学习能够如此有效。当我回答这个问题时,我通常给他们画个图,在水平轴上画一个形状,在此绘制出所有任务的数据量,而在垂直轴上,画出机器学习算法的性能。比如说准确率体现在垃圾邮件过滤或者广告点击预测,或者是神经网络在自动驾驶汽车时判断位置的准确性,根据图像可以发现,如果你把一个传统机器学习算法的性能画出来,作为数据量的一个函数,你可能得到一个弯曲的线,就像图中这样,它的性能一开始在增加更多数据时会上升,但是一段变化后它的性能就会像一个高原一样。假设你的水平轴拉的很长很长,它们不知道如何处理规模巨大的数据,而过去十年的社会里,我们遇到的很多问题只有相对较少的数据量。
多亏数字化社会的来临,现在的数据量都非常巨大,我们花了很多时间活动在这些数字的领域,比如在电脑网站上、在手机软件上以及其它数字化的服务,它们都能创建数据,同时便宜的相机被配置到移动电话,还有加速仪及各类各样的传感器,同时在物联网领域我们也收集到了越来越多的数据。仅仅在过去的20年里对于很多应用,我们便收集到了大量的数据,远超过机器学习算法能够高效发挥它们优势的规模。
神经网络展现出的是,如果你训练一个小型的神经网络,那么这个性能可能会像下图黄色曲线表示那样;如果你训练一个稍微大一点的神经网络,比如说一个中等规模的神经网络(下图蓝色曲线),它在某些数据上面的性能也会更好一些;如果你训练一个非常大的神经网络,它就会变成下图绿色曲线那样,并且保持变得越来越好。因此可以注意到两点:如果你想要获得较高的性能体现,那么你有两个条件要完成,第一个是你需要训练一个规模足够大的神经网络,以发挥数据规模量巨大的优点,另外你需要能画到 轴的这个位置,所以你需要很多的数据。因此我们经常说规模一直在推动深度学习的进步,这里的规模指的也同时是神经网络的规模,我们需要一个带有许多隐藏单元的神经网络,也有许多的参数及关联性,就如同需要大规模的数据一样。事实上如今最可靠的方法来在神经网络上获得更好的性能,往往就是要么训练一个更大的神经网络,要么投入更多的数据,这只能在一定程度上起作用,因为最终你耗尽了数据,或者最终你的网络是如此大规模导致将要用太久的时间去训练,但是仅仅提升规模的的确确地让我们在深度学习的世界中摸索了很多时间。为了使这个图更加从技术上讲更精确一点,我在 轴下面已经写明的数据量,这儿加上一个标签(label)量,通过添加这个标签量,也就是指在训练样本时,我们同时输入 和标签 ,接下来引入一点符号,使用小写的字母 表示训练集的规模,或者说训练样本的数量,这个小写字母就横轴结合其他一些细节到这个图像中
在这个小的训练集中,各种算法的优先级事实上定义的也不是很明确,所以如果你没有大量的训练集,那效果会取决于你的特征工程能力,那将决定最终的性能。假设有些人训练出了一个SVM(支持向量机)表现的更接近正确特征,然而有些人训练的规模大一些,可能在这个小的训练集中SVM算法可以做的更好。因此你知道在这个图形区域的左边,各种算法之间的优先级并不是定义的很明确,最终的性能更多的是取决于你在用工程选择特征方面的能力以及算法处理方面的一些细节,只是在某些大数据规模非常庞大的训练集,也就是在右边这个会非常的大时,我们能更加持续地看到更大的由神经网络控制的其它方法,因此如果你的任何某个朋友问你为什么神经网络这么流行,我会鼓励你也替他们画这样一个图形。
所以可以这么说,在深度学习萌芽的初期,数据的规模以及计算量,局限在我们对于训练一个特别大的神经网络的能力,无论是在CPU还是GPU上面,那都使得我们取得了巨大的进步。但是渐渐地,尤其是在最近这几年,我们也见证了算法方面的极大创新。许多算法方面的创新,一直是在尝试着使得神经网络运行的更快。
作为一个具体的例子,神经网络方面的一个巨大突破是从sigmoid函数转换到一个ReLU函数,这个函数我们在之前的课程里提到过。
如果你无法理解刚才我说的某个细节,也不需要担心,可以知道的一个使用sigmoid函数和机器学习问题是,在这个区域,也就是这个sigmoid函数的梯度会接近零,所以学习的速度会变得非常缓慢,因为当你实现梯度下降以及梯度接近零的时候,参数会更新的很慢,所以学习的速率也会变的很慢,而通过改变这个被叫做激活函数的东西,神经网络换用这一个函数,叫做ReLU的函数(修正线性单元),ReLU它的梯度对于所有输入的负值都是零,因此梯度更加不会趋向逐渐减少到零。而这里的梯度,这条线的斜率在这左边是零,仅仅通过将Sigmod函数转换成ReLU函数,便能够使得一个叫做梯度下降(gradient descent)的算法运行的更快,这就是一个或许相对比较简单的算法创新的例子。但是根本上算法创新所带来的影响,实际上是对计算带来的优化,所以有很多像这样的例子,我们通过改变算法,使得代码运行的更快,这也使得我们能够训练规模更大的神经网络,或者是多端口的网络。即使我们从所有的数据中拥有了大规模的神经网络,快速计算显得更加重要的另一个原因是,训练你的神经网络的过程,很多时候是凭借直觉的,往往你对神经网络架构有了一个想法,于是你尝试写代码实现你的想法,然后让你运行一个试验环境来告诉你,你的神经网络效果有多好,通过参考这个结果再返回去修改你的神经网络里面的一些细节,然后你不断的重复上面的操作,当你的神经网络需要很长时间去训练,需要很长时间重复这一循环,在这里就有很大的区别,根据你的生产效率去构建更高效的神经网络。当你能够有一个想法,试一试,看效果如何。在10分钟内,或者也许要花上一整天,如果你训练你的神经网络用了一个月的时间,有时候发生这样的事情,也是值得的,因为你很快得到了一个结果。在10分钟内或者一天内,你应该尝试更多的想法,那极有可能使得你的神经网络在你的应用方面工作的更好、更快的计算,在提高速度方面真的有帮助,那样你就能更快地得到你的实验结果。这也同时帮助了神经网络的实验人员和有关项目的研究人员在深度学习的工作中迭代的更快,也能够更快的改进你的想法,所有这些都使得整个深度学习的研究社群变的如此繁荣,包括令人难以置信地发明新的算法和取得不间断的进步,这些都是开拓者在做的事情,这些力量使得深度学习不断壮大。
好消息是这些力量目前也正常不断的奏效,使得深度学习越来越好。研究表明我们的社会仍然正在抛出越来越多的数字化数据,或者用一些特殊的硬件来进行计算,比如说GPU,以及更快的网络连接各种硬件。我非常有信心,我们可以做一个超级大规模的神经网络,而计算的能力也会进一步的得到改善,还有算法相对的学习研究社区连续不断的在算法前沿产生非凡的创新。根据这些我们可以乐观地回答,同时对深度学习保持乐观态度,在接下来的这些年它都会变的越来越好。
这部分内容主要是学习神经网络的知识,其中需要注意的是,当实现一个神经网络的时候,我们需要直到一些非常重要的技术和技巧。例如有一个包含 个样本的训练集,你很可能习惯于用一个for循环来遍历训练集中的每个样本,但是当实现一个神经网络的时候,我们通常不直接使用for循环来遍历整个训练集,所以我们可以学会如何处理训练集。
另外在神经网络的计算中,通常先有一个叫做前向暂停(forward pause)或叫做前向传播(foward propagation)的步骤,接着有一个叫做反向暂停(backward pause) 或叫做反向传播(backward propagation)的步骤。所以这周我也会向你介绍为什么神经网络的训练过程可以分为前向传播和反向传播两个独立的部分。
逻辑回归是一个用于二分类(binary classification)的算法。首先我们从一个问题开始说起,这里有一个二分类问题的例子,假如你有一张图片作为输入,比如这只猫,如果识别这张图片为猫,则输出标签1作为结果;如果识别出不是猫,那么输出标签0作为结果。现在我们可以用字母 来表示输出的结果标签,如下图所示:
我们来看看一张图片在计算机中是如何表示的,为了保存一张图片,需要保存三个矩阵,它们分别对应图片中的红、绿、蓝三种颜色通道,如果你的图片大小为64x64像素,那么你就有三个规模为64x64的矩阵,分别对应图片中红、绿、蓝三种像素的强度值。为了便于表示,这里我画了三个很小的矩阵,注意它们的规模为5x4 而不是64x64,如下图所示:
为了把这些像素值放到一个特征向量中,我们需要把这些像素值提取出来,然后放入一个特征向量。为了把这些像素值转换为特征向量 ,我们需要像下面这样定义一个特征向量 来表示这张图片,我们把所有的像素都取出来,例如255、231等等,直到取完所有的红色像素,接着最后是255、134、…、255、134等等,直到得到一个特征向量,把图片中所有的红、绿、蓝像素值都列出来。如果图片的大小为64x64像素,那么向量 x 的总维度,将是64乘以64乘以3,这是三个像素矩阵中像素的总量。在这个例子中结果为12,288。现在我们用 ,来表示输入特征向量的维度,有时候为了简洁,我会直接用小写的 来表示输入特征向量 的维度。所以在二分类问题中,我们的目标就是习得一个分类器,它以图片的特征向量作为输入,然后预测输出结果 为1还是0,也就是预测图片中是否有猫:
接下来我们说明一些在余下课程中,需要用到的一些符号。
符号定义 :
用一对 来表示一个单独的样本, 代表 维的特征向量, 表示标签(输出结果)只能为0或1。 而训练集将由 个训练样本组成,其中 表示第一个样本的输入和输出, 表示第二个样本的输入和输出,直到最后一个样本 ,然后所有的这些一起表示整个训练集。有时候为了强调这是训练样本的个数,会写作 ,当涉及到测试集的时候,我们会使用 来表示测试集的样本数,所以这是测试集的样本数:
最后为了能把训练集表示得更紧凑一点,我们会定义一个矩阵用大写的 表示,它由输入向量、 等组成,如下图放在矩阵的列中,所以现在我们把 作为第一列放在矩阵中, 作为第二列, 放到第 列,然后我们就得到了训练集矩阵 。所以这个矩阵有 列, 是训练集的样本数量,然后这个矩阵的高度记为 ,注意有时候可能因为其他某些原因,矩阵会由训练样本按照行堆叠起来而不是列,如下图所示: 的转置直到 的转置,但是在实现神经网络的时候,使用左边的这种形式,会让整个实现的过程变得更加简单:
现在来简单温习一下: 是一个规模为 乘以 的矩阵,当你用Python实现的时候,你会看到X.shape
,这是一条Python命令,用于显示矩阵的规模,即X.shape
等于 , 是一个规模为 乘以 的矩阵。所以综上所述,这就是如何将训练样本(输入向量 的集合)表示为一个矩阵。
那么输出标签 呢? 同样的道理,为了能更好容易的实现一个神经网络,将标签 放在列中将会使得后续计算非常方便,所以我们定义大写的 等于 ,所以这里是一个规模为 1 乘以 的矩阵,同样使用Python 将表示为Y.shape
等于 ,表示这是一个规模为1乘以的矩阵。
当我们在后面的课程中实现神经网络的时候,你会发现,一个好的符号约定能够将不同训练样本的数据很好地组织起来。而我所说的数据不仅包括 或者 还包括之后你会看到的其他的量。将不同的训练样本的数据提取出来,然后就像刚刚我们对 或者 所做的那样,将他们堆叠在矩阵的列中,形成我们之后会在逻辑回归和神经网络上要用到的符号表示。如果有时候你忘了这些符号的意思,比如什么是 ,或者什么是 ,或者忘了其他一些东西。
逻辑回归学习算法适用于二分类问题,这部分内容主要介绍逻辑回归的假设函数(Hypothesis Function)。
对于二元分类问题来讲,给定一个输入特征向量 ,它可能对应一张图片,你想识别这张图片识别看它是否是一只猫或者不是一只猫的图片,你想要一个算法能够输出预测,你只能称之为 ,也就是你对实际值 的估计。更正式地来说,你想让 表示 等于1的一种可能性或者是机会,前提条件是给定了输入特征 。换句话来说,如果是我们看到的图片,你想让 来告诉你这是一只猫的图片的机率有多大。在之前的内容中所说的,是一个 维的向量(相当于有个特征的特征向量)。我们用 来表示逻辑回归的参数,这也是一个维向量(因为 实际上是特征权重,维度与特征向量相同),参数里面还有 ,这是一个实数(表示偏差)。所以给出输入 以及参数 和 之后,我们怎样产生输出预测值 ,一件你可以尝试却不可行的事是让 .
这时候我们得到的是一个关于线性输入 的线性函数,实际上这是我们在做线性回归中所用到的。但是这是对于二元分类问题来说并不是一个非常高效的算法。因为我们想让 表示实际值 的几率, 应该在 0 到 1 之间。这是一个需要解决的问题,因为 可能会比1要大得多,或者甚至为1个负值。对于我们想要的在 0 和 1 之间的概率来说他是没有意义的。因此,我们的输出应该是由 上面的输出 等于由上面得到的线性函数式子作为自变量的Sigmoid函数中,公式如上图最下面所示,将线性函数转换为非线性函数。
下图是sigmoid函数的图像,如果我们把水平轴作为 轴,那么关于 的sigmoid 函数是这样的,他是平滑的从0 到 1。可以在这里做标记,这是0,曲线与纵轴相交的截距是0.5.这就是关于 的sigmoid函数的图像。我们通常都使用 来表示 的值。
关于sigmoid函数的公式是这样的, , 在这里 是一个实数,这里要说明一些要注意的事情,如果非常大那么 将会接近于0,关于的sigmoid函数将会近似等于1除以1加上某个非常接近于0的项,因为 的指数如果是个绝对值很大的负数的话,这项将会接近于0,所以如果 很大的话, 那么关于的sigmoid函数会非常接近1。相反地,如果非常小或者说是一个绝对值很大的负数,那么关于 这项会变成一个很大的数,你可以认为这是1除以1加上一个非常非常大的数,所以这个就接近于0。实际上你看到当 变成一个绝对值很大的负数,关于 的sigmoid函数就会非常接近于0,因此当你实现逻辑回归时,你的工作就是去让机器学习参数 以及 这样才使得 成为 对这一情况的概率的一个很好的估计 .
在继续进行下一步之前,介绍一种符号惯例,可以让参数 和参数 分开。在符号上要注意的一点是当我们对神经网络进行编程时经常会让参数和参数分开,在这里参数对应的是一种偏置。在之前的机器学习课程里,你可能已经见过处理这个问题时的其他符号表示。比如在某些例子里,你定义一个额外的特征称之为 ,并且使它等于1,那么现在就是一个 加1维的变量,然后你定义 的sigmoid函数。在这个备选的符号惯例里,你有一个参数向量 ,这样 就充当了 b,这是一个实数,而剩下的 直到 充当了,结果就是当你实现你的神经网络时,有一个比较简单的方法是保持 和 w 分开。但是在这节课里我们不会使用任何这类符号惯例,所以不用去担心。 现在你已经知道逻辑回归模型是什么样子了,下一步要做的是训练参数 和参数 ,你需要定义一个代价函数,让我们在下节课里对其进行解释。
逻辑回归中代价函数也叫成本函数。
为什么需要代价函数?为了训练逻辑回归模型中的参数 和参数 ,我们需要一个代价函数,通过训练代价函数来得到参数 和参数 。先看下逻辑回归的输出函数:
为了让模型通过学习调整参数,你需要给予一个 样本的训练集,这会让你在训练集上找到参数和参数,来得到你的输出。
对训练集的预测值,我们将它写成 ,我们更希望它会接近于训练集中的 值,为了对上面的公式更详细的介绍,我们需要说明上面的定义是对一个训练样本来说的,这种形式也使用于每个训练样本,我们使用这些带有圆括号的上标来区分索引和样本,训练样本 所对应的预测值是 ,是用训练样本的 ,然后通过sigmoid函数来得到,也可以把定义为 ,我们将使用这个符号 注解,上标来指明数据表示或者或者 或者 其他数据的第 个训练样本,这就是上标的含义。
损失函数:
损失函数又叫做误差函数,用来衡量算法的运行情况,Loss function: .
我们通过这个称为的损失函数,来衡量预测输出值和实际值有多接近。一般我们用预测值和实际值的平方差或者它们平方差的一半,但是通常在逻辑回归中我们不这么做,因为当我们在学习逻辑回归参数的时候,会发现我们的优化目标不是凸优化,只能找到多个局部最优值,梯度下降法很可能找不到全局最优值,虽然平方差是一个不错的损失函数,但是我们在逻辑回归模型中会定义另外一个损失函数。
我们在逻辑回归中用到的损失函数是: 。
为什么要用这个函数作为逻辑损失函数?当我们使用平方误差作为损失函数的时候,你会想要让这个误差尽可能地小,对于这个逻辑回归损失函数,我们也想让它尽可能地小,为了更好地理解这个损失函数怎么起作用,我们举两个例子:
当 时损失函数,如果想要损失函数 尽可能得小,那么 就要尽可能大,因为sigmoid函数取值,所以会无限接近于1。
当 时损失函数,如果想要损失函数尽可能得小,那么就要尽可能小,因为sigmoid函数取值,所以会无限接近于0。
有很多的函数效果和现在这个类似,就是如果 等于1,我们就尽可能让 变大,如果等于0,我们就尽可能让 变小。 损失函数是在单个训练样本中定义的,它衡量的是算法在单个训练样本中表现如何,为了衡量算法在全部训练样本上的表现如何,我们需要定义一个算法的代价函数,算法的代价函数是对个样本的损失函数求和然后除以 :
损失函数只适用于像这样的单个训练样本,而代价函数是参数的总代价,所以在训练逻辑回归模型时候,我们需要找到合适的 和 ,来让代价函数 的总代价降到最低。 根据我们对逻辑回归算法的推导及对单个样本的损失函数的推导和针对算法所选用参数的总代价函数的推导,结果表明逻辑回归可以看做是一个非常小的神经网络。
梯度下降法可以做什么?
在你测试集上,通过最小化代价函数(成本函数) 来训练的参数 和 ,
如图,在第二行给出和之前一样的逻辑回归算法的代价函数(成本函数)
梯度下降法的形象化说明
在这个图中,横轴表示你的空间参数b和w,在实践中, 可以是更高的维度,但是为了更好地绘图,我们定义b和w,都是单一实数,代价函数(成本函数)是在水平轴b和w的曲面,因此曲面的高度就是在某一点的函数值。我们所做的就是找到使得代价函数(成本函数)函数值是最小值,对应的参数和。
如图,代价函数(成本函数)是一个凸函数(convex function),像一个大碗一样。
如图,这就与刚才的图有些相反,因为它是非凸的并且有很多不同的局部最小值。由于逻辑回归的代价函数(成本函数)特性,我们必须定义代价函数(成本函数)为凸函数。 初始化 和 ,
可以用如图那个小红点来初始化参数 和 ,也可以采用随机初始化的方法,对于逻辑回归几乎所有的初始化方法都有效,因为函数是凸函数,无论在哪里初始化,应该达到同一点或大致相同的点。
我们以如图的小红点的坐标来初始化参数 和 。
朝最陡的下坡方向走一步,不断地迭代
我们朝最陡的下坡方向走一步,如图,走到了如图中第二个小红点处。
我们可能停在这里也有可能继续朝最陡的下坡方向再走一步,如图,经过两次迭代走到第三个小红点处。
直到走到全局最优解或者接近全局最优解的地方
通过以上的三个步骤我们可以找到全局最优解,也就是代价函数(成本函数)这个凸函数的最小值点。
梯度下降法的细节化说明(仅有一个参数)
假定代价函数(成本函数) 只有一个参数 ,即用一维曲线代替多维曲线,这样可以更好画出图像。
迭代就是不断重复做如图的公式:
表示更新参数,
表示学习率(learning rate),用来控制步长(step),即向下走一步的长度 就是函数对 w 求导(derivative),在代码中我们会使用 表示这个结果
对于导数更加形象化的理解就是斜率(slope),如图该点的导数就是这个点相切于 的小三角形的高除宽。假设我们以如图点为初始化点,该点处的斜率的符号是正的,即 ,所以接下来会向左走一步。
整个梯度下降法的迭代过程就是不断地向左走,直至逼近最小值点。
假设我们以如图点为初始化点,该点处的斜率的符号是负的,即 ,所以接下来会向右走一步。
整个梯度下降法的迭代过程就是不断地向右走,即朝着最小值点方向走。
梯度下降法的细节化说明(两个参数)
逻辑回归的代价函数(成本函数)是含有两个参数的。
其中 表示求偏导符号,可以读作round, 就是函数 对 求偏导,在代码中我们会使用 表示这个结果, 就是函数对 求偏导,在代码中我们会使用 表示这个结果, 小写字母 用在求导数(derivative),即函数只有一个参数, 偏导数符号 用在求偏导(partial derivative),即函数含有两个以上的参数。
这部分内容目的是帮我们获得对微积分和导数直观的理解。
一个函数 ,它是一条直线。下面我们来简单理解下导数。让我们看看函数中几个点,假定,那么是 的3倍等于6,也就是说如果 ,那么函数 。假定稍微改变一点点的值,只增加一点,变为2.001,这时将向右做微小的移动。0.001的差别实在是太小了,不能在图中显示出来,我们把它右移一点,现在等于的3倍是6.003,画在图里,比例不太符合。请看绿色高亮部分的这个小三角形,如果向右移动0.001,那么增加0.003,的值增加3倍于右移的,因此我们说函数在 ,3是这个导数的斜率,或者说,当时,斜率是3。导数这个概念意味着斜率,导数听起来是一个很可怕、很令人惊恐的词,但是斜率以一种很友好的方式来描述导数这个概念。所以提到导数,我们把它当作函数的斜率就好了。更正式的斜率定义为在上图这个绿色的小三角形中,高除以宽。即斜率等于0.003除以0.001,等于3。或者说导数等于3,这表示当你将右移0.001,的值增加3倍水平方向的量。
现在让我们从不同的角度理解这个函数。 假设 ,此时 。 把右移一个很小的幅度,增加到5.001,。 即在 时,斜率是3,这就是表示,当微小改变变量 的值, 。一个等价的导数表达式可以这样写 ,不管你是否将 放在上面或者放在右边都没有关系。 在这部分内容中,导数讨论的情况是我们将 偏移0.001,如果你想知道导数的数学定义,导数是你右移很小的值(不是0.001,而是一个非常非常小的值)。通常导数的定义是你右移(可度量的值)一个无限小的值,增加3倍(增加了一个非常非常小的值)。也就是这个三角形右边的高度。
那就是导数的正式定义。但是为了直观的认识,我们将探讨右移 这个值,即使0.001并不是无穷小的可测数据。导数的一个特性是:这个函数任何地方的斜率总是等于3,不管 或 ,这个函数的斜率总等于3,也就是说不管的值如何变化,如果你增加0.001,的值就增加3倍。这个函数在所有地方的斜率都相等。一种证明方式是无论你将小三角形画在哪里,它的高除以宽总是3。
将给出一个更加复杂的例子,在这个例子中,函数在不同点处的斜率是不一样的,先来举个例子:
画一个函数,,如果 的话,那么 。让我们稍稍往右推进一点点,现在 ,则 (如果你用计算器算的话,这个准确的值应该为4.004。0.001 我只是为了简便起见,省略了后面的部分),如果你在这儿画,一个小三角形,你就会发现,如果把a 往右移动0.001,那么将增大四倍,即增大0.004。在微积分中我们把这个三角形斜边的斜率,称为在点 a=2 处的导数(即为4),或者写成微积分的形式,当 a=2 的时候, 由此可知,函数,在取不同值的时候,它的斜率是不同的,这和上部分内容中的例子是不同的。
这里有种直观的方法可以解释,为什么一个点的斜率,在不同位置会不同如果你在曲线上,的不同位置画一些小小的三角形你就会发现,三角形高和宽的比值,在曲线上不同的地方,它们是不同的。所以当 a=2 时,斜率为4;而当a=5 时,斜率为10 。如果你翻看微积分的课本,课本会告诉你, 函数的斜率(即导数)为 。这意味着任意给定一点 ,如果你稍微将a增大0.001,那么你会看到将增大,即增大的值为点在处斜率或导数,乘以你向右移动的距离。
现在有个小细节需要注意,导数增大的值,不是刚好等于导数公式算出来的值,而只是根据导数算出来的一个估计值。
为了总结这堂课所学的知识,我们再来看看几个例子:
假设 如果我们翻看导数公式表,你会发现这个函数的导数等于。所以这是什么意思呢,同样地举一个例子:我们再次令a=2,所以 ,如果我们又将增大一点点,你会发现 你可以自己检查一遍,如果我们取8.012,你会发现 ,和8.012很接近,事实上当 a=2时,导数值为,即3*4=12。所以导数公式,表明如果你将向右移动0.001时, 将会向右移动12倍,即0.012。
第一点,导数就是斜率,而函数的斜率,在不同的点是不同的。在第一个例子中 ,这是一条直线,在任何点它的斜率都是相同的,均为3。但是对于函数 ,它们的斜率是变化的,所以它们的导数或者斜率,在曲线上不同的点处是不同的。
第二点,如果你想知道一个函数的导数,你可参考你的微积分课本或者维基百科,然后你应该就能找到这些函数的导数公式。
可以说,一个神经网络的计算,都是按照前向或反向传播过程组织的。首先我们计算出一个新的网络的输出(前向过程),紧接着进行一个反向传输操作。后者我们用来计算出对应的梯度或导数。计算图解释了为什么我们用这种方式组织这些计算过程。在这个视频中,我们将举一个例子说明计算图是什么。让我们举一个比逻辑回归更加简单的,或者说不那么正式的神经网络的例子。
我们尝试计算函数 ,是由三个变量组成的函数,这个函数是 。计算这个函数实际上有三个不同的步骤,首先是计算 乘以 ,我们把它储存在变量 中,因此 ; 然后计算;最后输出,这就是要计算的函数 。我们可以把这三步画成如下的计算图,我先在这画三个变量,第一步就是计算 ,我在这周围放个矩形框,它的输入是 ,接着第二步 ,最后一步 。 举个例子: , 就是6, 就是5+6=11。 是3倍的 ,因此即 。如果你把它算出来,实际上得到33就是的值。 当有不同的或者一些特殊的输出变量时,例如本例中的和逻辑回归中你想优化的代价函数,因此计算图用来处理这些计算会很方便。从这个小例子中我们可以看出,通过一个从左向右的过程,你可以计算出的值。为了计算导数,从右到左(红色箭头,和蓝色箭头的过程相反)的过程是用于计算导数最自然的方式。 概括一下:计算图组织计算的形式是用蓝色箭头从左到右的计算,让我们看看下一个视频中如何进行反向红色箭头(也就是从右到左)的导数计算.
上一部分内容中一个例子使用流程计算图来计算函数J。现在我们清理一下流程图的描述,看看如何利用它计算出函数的导数。
下面用到的公式:
这是一个流程图:
假设需要计算 ,那要怎么算呢?好,比如说,我们要把这个 值拿过来,改变一下,那么 的值会怎么变呢?
所以定义上 ,现在 ,所以如果你让 增加一点点,比如到11.001,那么 ,所以我这里 增加了0.001,然后最终结果是上升到原来的3倍,所以,因为对于任何 的增量 都会有3倍增量,而且这类似于我们在上一部分中的例子,我们有,然后我们推导出 ,所以这里我们有 ,所以 ,这里 扮演了的角色。
在反向传播算法中的术语,我们看到,如果你想计算最后输出变量的导数,使用你最关心的变量对的导数,那么我们就做完了一步反向传播,在这个流程图中是一个反向步骤。
我们来看另一个例子,是多少呢?换句话说,如果我们提高的数值,对的数值有什么影响?
好,我们看看这个例子。变量 ,我们让它增加到5.001,那么对v的影响就是 ,之前,现在变成11.001,我们从上面看到现在 就变成33.003了,所以我们看到的是,如果你让增加0.001,增加0.003。那么增加 ,我是说如果你把这个5换成某个新值,那么的改变量就会传播到流程图的最右,所以 最后是33.003。所以J的增量是3乘以的增量,意味着这个导数是3。
要解释这个计算过程,其中一种方式是:如果你改变了 ,那么也会改变,通过改变 ,也会改变 ,所以 值的净变化量,当你提升这个值(0.001),当你把值提高一点点,这就是的变化量(0.003)。
我们从这个计算中看到,如果你让增加0.001,也会变化相同的大小,所以。事实上,如果你代入进去,我们之前算过,,所以这个乘积3×1,实际上就给出了正确答案,。
这张小图表示了如何计算,就是对变量的导数,它可以帮助你计算,所以这是另一步反向传播计算。
介绍一个新的符号约定,当你编程实现反向传播时,通常会有一个最终输出值是你要关心的,最终的输出变量,你真正想要关心或者说优化的。在这种情况下最终的输出变量是J,就是流程图里最后一个符号,所以有很多计算尝试计算输出变量的导数,所以输出变量对某个变量的导数,我们就用命名,所以在很多计算中你需要计算最终输出结果的导数,在这个例子里是,还有各种中间变量,比如,当你在软件里实现的时候,变量名叫什么?你可以做的一件事是,在python中,你可以写一个很长的变量名,比如,但这个变量名有点长,我们就用,但因为你一直对求导,对这个最终输出变量求导。我这里要介绍一个新符号,在程序里,当你编程的时候,在代码里,我们就使用变量名,来表示那个量。
现在,我们仔细看看最后一个例子,那么呢?想象一下,如果你改变了的值,你想要然后变化一点,让 值到达最大或最小,那么导数是什么呢?这个函数的斜率,当你稍微改变值之后。事实上,使用微积分链式法则,这可以写成两者的乘积,就是,理由是,如果你改变一点点,所以变化比如说3.001,它影响J的方式是,首先会影响,它对的影响有多大?好,的定义是,所以时这是6,现在就变成6.002了,对吧,因为在我们的例子中,所以这告诉我们当你让b增加0.001时,就增加两倍。所以,现在我想的增加量已经是的两倍,那么是多少呢?我们已经弄清楚了,这等于3,所以让这两部分相乘,我们发现。
要点:
对于那个例子,当计算所有这些导数时,最有效率的办法是从右到左计算,跟着这个红色箭头走。特别是当我们第一次计算对的导数时,之后在计算对导数就可以用到。然后对的导数,比如说这个项和这里这个项:
可以帮助计算对的导数,然后对的导数。
所以这是一个计算流程图,就是正向或者说从左到右的计算来计算成本函数J,你可能需要优化的函数,然后反向从右到左计算导数。
这一部分内容我们讨论怎样通过计算偏导数来实现逻辑回归的梯度下降算法。它的关键点是几个重要公式,其作用是用来实现逻辑回归中梯度下降算法。我们将使用计算图对梯度下降算法进行计算。开始学习逻辑回归的梯度下降算法。
在之前的内容中,我们已经知道如何计算导数,以及应用梯度下降在逻辑回归的一个训练样本上。现在我们想要把它应用在个训练样本上。
首先,让我们时刻记住有关于损失函数的定义。
当你的算法输出关于样本 的 ,是训练样本的预测值,即:。 所以我们在前面的幻灯中展示的是对于任意单个训练样本,如何计算微分当你只有一个训练样本。因此, 和 添上上标表示你求得的相应的值。如果你面对的是我们在之前的幻灯中演示的那种情况,但只使用了一个训练样本 。 现在你知道带有求和的全局代价函数,实际上是1到项各个损失的平均。 所以它表明全局代价函数对的微分,对的微分也同样是各项损失对微分的平均。
但之前我们已经知道如何对单个训练样本进行计算。所以你真正需要做的是计算这些微分,如我们在之前的训练样本上做的。并且求平均,这会给你全局梯度值,你能够把它直接应用到梯度下降算法中。
所以这里有很多细节,但让我们把这些装进一个具体的算法。同时你需要一起应用的就是逻辑回归和梯度下降。
我们初始化
代码流程:
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循环是一个小循环遍历个训练样本,第二个for循环是一个遍历所有特征的for循环。这个例子中我们只有2个特征,所以等于2并且 等于2。 但如果你有更多特征,你开始编写你的因此你有相似的计算从一直下去到。所以看来你需要一个for循环遍历所有个特征。
当你应用深度学习算法,你会发现在代码中显式地使用for循环使你的算法很低效,同时在深度学习领域会有越来越大的数据集。所以能够应用你的算法且没有显式的for循环会是重要的,并且会帮助你适用于更大的数据集。所以这里有一些叫做向量化技术,它可以允许你的代码摆脱这些显式的for循环。
我想在先于深度学习的时代,也就是深度学习兴起之前,向量化是很棒的。可以使你有时候加速你的运算,但有时候也未必能够。但是在深度学习时代向量化,摆脱for循环已经变得相当重要。因为我们越来越多地训练非常大的数据集,因此你真的需要你的代码变得非常高效。所以在接下来的几个视频中,我们会谈到向量化,以及如何应用向量化而连一个for循环都不使用。所以学习了这些,我希望你有关于如何应用逻辑回归,或是用于逻辑回归的梯度下降,事情会变得更加清晰。当你进行编程练习,但在真正做编程练习之前让我们先谈谈向量化。然后你可以应用全部这些东西,应用一个梯度下降的迭代而不使用任何for循环。
向量化是非常基础的去除代码中for循环的艺术,在深度学习安全领域、深度学习实践中,你会经常发现自己训练大数据集,因为深度学习算法处理大数据集效果很棒,所以你的代码运行速度非常重要,否则如果在大数据集上,你的代码可能花费很长时间去运行,你将要等待非常长的时间去得到结果。所以在深度学习领域,运行向量化是一个关键的技巧,让我们举个栗子说明什么是向量化。
在逻辑回归中你需要去计算,、都是列向量。如果你有很多的特征那么就会有一个非常大的向量,所以 , 所以如果你想使用非向量化方法去计算,你需要用如下方式(python)
x
z=0
for i in range(n_x)
z+=w[i]*x[i]
z+=b
这是一个非向量化的实现,你会发现这真的很慢,作为一个对比,向量化实现将会非常直接计算,代码如下:
z=np.dot(w,x)+b
这是向量化计算的方法,你将会发现这个非常快
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循环。
以下代码及运行结果截图:
当我们在写神经网络程序时,或者在写逻辑(logistic)回归,或者其他神经网络模型时,应该避免写循环(loop)语句。虽然有时写循环(loop)是不可避免的,但是我们可以使用比如numpy的内置函数或者其他办法去计算。当你这样使用后,程序效率总是快于循环(loop)。
我们将讨论如何实现逻辑回归的向量化计算。这样就能处理整个数据集,甚至不会用一个明确的for循环就能实现对于整个数据集梯度下降算法的优化。我对这项技术感到非常激动,并且当我们后面谈到神经网络时同样也不会用到一个明确的 for 循环。
概括一下,这部分内容主要讲如何利用向量化在同一时间内高效地计算所有的激活函数的所有 值。接下来,可以证明,你也可以利用向量化高效地计算反向传播并以此来计算梯度。
在之前的实现中,我们已经去掉了一个for循环,但我们仍有一个遍历训练集的循环,如下所示:
现在我们利用前五个公式完成了前向和后向传播,也实现了对所有训练样本进行预测和求导,再利用后两个公式,梯度下降更新参数。我们的目的是不使用for循环,所以我们就通过一次迭代实现一次梯度下降,但如果你希望多次迭代进行梯度下降,那么仍然需要for循环,放在最外层。不过我们还是觉得一次迭代就进行一次梯度下降,避免使用任何循环比较舒服一些。
最后,我们得到了一个高度向量化的、非常高效的逻辑回归的梯度下降算法,我们将在下次视频中讨论Python中的Broadcasting技术。
这是一个不同食物(每100g)中不同营养成分的卡路里含量表格,表格为3行4列,列表示不同的食物种类,从左至右依次为苹果,牛肉,鸡蛋,土豆。行表示不同的营养成分,从上到下依次为碳水化合物,蛋白质,脂肪。
那么,我们现在想要计算不同食物中不同营养成分中的卡路里百分比。
现在计算苹果中的碳水化合物卡路里百分比含量,首先计算苹果(100g)中三种营养成分卡路里总和56+1.2+1.8 = 59,然后用56/59 = 94.9%算出结果。
可以看出苹果中的卡路里大部分来自于碳水化合物,而牛肉则不同。
对于其他食物,计算方法类似。首先,按列求和,计算每种食物中(100g)三种营养成分总和,然后分别用不用营养成分的卡路里数量除以总和,计算百分比。
那么,能否不使用for循环完成这样的一个计算过程呢?
假设上图的表格是一个4行3列的矩阵,记为 ,接下来我们要使用Python的numpy库完成这样的计算。我们打算使用两行代码完成,第一行代码对每一列进行求和,第二行代码分别计算每种食物每种营养成分的百分比。
在jupyter notebook中输入如下代码,按shift+Enter运行,输出如下。
下面使用如下代码计算每列的和,可以看到输出是每种食物(100g)的卡路里总和。
其中sum
的参数axis=0
表示求和运算按列执行,之后会详细解释。
接下来计算百分比,这条指令将 的矩阵 除以一个的矩阵,得到了一个 的结果矩阵,这个结果矩阵就是我们要求的百分比含量。
下面再来解释一下A.sum(axis = 0)
中的参数axis
。axis用来指明将要进行的运算是沿着哪个轴执行,在numpy中,0轴是垂直的,也就是列,而1轴是水平的,也就是行。
广播机制的一般原则如下:
首先是numpy广播机制
如果两个数组的后缘维度的轴长度相符或其中一方的轴长度为1,则认为它们是广播兼容的。广播会在缺失维度和轴长度为1的维度上进行。
后缘维度的轴长度:
A.shape[-1]
即矩阵维度元组中的最后一个位置的值对于视频中卡路里计算的例子,矩阵 后缘维度的轴长度是4,而矩阵 的后缘维度也是4,则他们满足后缘维度轴长度相符,可以进行广播。广播会在轴长度为1的维度进行,轴长度为1的维度对应
axis=0
,即垂直方向,矩阵 沿axis=0
(垂直方向)复制成为 ,之后两者进行逐元素除法运算。
这部分内容主要讲Python中的numpy一维数组的特性,以及与行向量或列向量的区别。并介绍了老师在实际应用中的一些小技巧,去避免在coding中由于这些特性而导致的bug。
Python的特性允许你使用广播(broadcasting)功能,这是Python的numpy程序语言库中最灵活的地方。而我认为这是程序语言的优点,也是缺点。优点的原因在于它们创造出语言的表达性,Python语言巨大的灵活性使得你仅仅通过一行代码就能做很多事情。但是这也是缺点,由于广播巨大的灵活性,有时候你对于广播的特点以及广播的工作原理这些细节不熟悉的话,你可能会产生很细微或者看起来很奇怪的bug。例如,如果你将一个列向量添加到一个行向量中,你会以为它报出维度不匹配或类型错误之类的错误,但是实际上你会得到一个行向量和列向量的求和。
在Python的这些奇怪的影响之中,其实是有一个内在的逻辑关系的。但是如果对Python不熟悉的话,我就曾经见过的一些学生非常生硬、非常艰难地去寻找bug。所以我在这里想做的就是分享给你们一些技巧,这些技巧对我非常有用,它们能消除或者简化我的代码中所有看起来很奇怪的bug。同时我也希望通过这些技巧,你也能更容易地写没有bug的Python和numpy代码。
为了演示Python-numpy的一个容易被忽略的效果,特别是怎样在Python-numpy中构造向量,让我来做一个快速示范。首先设置,这样会生成存储在数组 中的5个高斯随机数变量。之后输出 ,从屏幕上可以得知,此时 的shape(形状)是一个的结构。这在Python中被称作一个一维数组。它既不是一个行向量也不是一个列向量,这也导致它有一些不是很直观的效果。举个例子,如果我输出一个转置阵,最终结果它会和看起来一样,所以和的转置阵最终结果看起来一样。而如果我输出和的转置阵的内积,你可能会想:乘以的转置返回给你的可能会是一个矩阵.
所以我建议当你编写神经网络时,不要在它的shape是还是或者一维数组时使用数据结构。相反,如果你设置 为 ,那么这就将置于5行1列向量中。在先前的操作里 和 的转置看起来一样,而现在这样的 变成一个新的 的转置,并且它是一个行向量。请注意一个细微的差别,在这种数据结构中,当我们输出 的转置时有两对方括号,而之前只有一对方括号,所以这就是1行5列的矩阵和一维数组的差别。
如果你输出 和 的转置的乘积,然后会返回给你一个向量的外积,是吧?所以这两个向量的外积返回给你的是一个矩阵。
就我们刚才看到的,再进一步说明。首先我们刚刚运行的命令是这个 ,而且它生成了一个数据结构 , 是,一个有趣的东西。这被称作 的一维数组,同时这也是一个非常有趣的数据结构。它不像行向量和列向量那样表现的很一致,这也让它的一些影响不那么明显。所以我建议,当你在编程练习或者在执行逻辑回归和神经网络时,你不需要使用这些一维数组。
相反,如果你每次创建一个数组,你都得让它成为一个列向量,产生一个 向量或者你让它成为一个行向量,那么你的向量的行为可能会更容易被理解。所以在这种情况下,等同于。这种表现很像 ,但是实际上却是一个列向量。同时这也是为什么当它是一个列向量的时候,你能认为这是矩阵;同时这里 将要变成,这就像行向量一样。所以当你需要一个向量时,我会说用这个或那个(column vector or row vector),但绝不会是一维数组。
我写代码时还有一件经常做的事,那就是如果我不完全确定一个向量的维度(dimension),我经常会扔进一个断言语句(assertion statement)。像这样,去确保在这种情况下是一个向量,或者说是一个列向量。这些断言语句实际上是要去执行的,并且它们也会有助于为你的代码提供信息。所以不论你要做什么,不要犹豫直接插入断言语句。如果你不小心以一维数组来执行,你也能够重新改变数组维数 ,表明一个数组或者一个数组,以致于它表现更像列向量或行向量。
我有时候看见学生因为一维数组不直观的影响,难以定位bug而告终。通过在原先的代码里清除一维数组,我的代码变得更加简洁。而且实际上就我在代码中表现的事情而言,我从来不使用一维数组。因此,要去简化你的代码,而且不要使用一维数组。总是使用 维矩阵(基本上是列向量),或者 维矩阵(基本上是行向量),这样你可以减少很多assert语句来节省核矩阵和数组的维数的时间。另外,为了确保矩阵或向量所需要的维数时,不要羞于 reshape 操作。