个人笔记(笔记基于吴恩达深度学习课程与中国海洋大学黄海广博士的深度学习笔记总结而成)
2.1 二分分类
本周主要内容:神经网络编程基础知识
使用逻辑回归函数来阐述正向传播与反向传播
logistic回归是一个用于二分分类的算法
例子:
计算机保存一张图片要保存成三个独立的矩阵(红,绿,蓝),每个像素点都对应矩阵中的一个数字(亮度),像下图那样,把生成的三个矩阵组成一个特征向量x(先排列红像素值,排列完之后再排列其他颜色的像素值,直到排列完),这是一个维度为n=64*64*3=12288的一列的特征向量(假设图片的像素为64*64),记为。
符号定义:
x:表示一个n维数据,为输入数据,维度为;
y:表示输出结果,取值为;
:表示第i组数据,可能是训练数据,也可能是测试数据,此处默认为训练数据;
:表示所有的训练数据集的输入值,放在一个的矩阵中,其中m表示样本数目;
:对应表示所有训练数据集的输出值,维度为1*m。
python函数实现X与Y的矩阵(使用python命令讲训练样本集表示为一个矩阵)
X.shape等于
Y.shape等于(1,m)
2.2 logistic回归(逻辑回归函数)
对实际y值的估计记为,要求[0,1]
w用来表示逻辑回归的参数,这也是一个维向量(因为实际上是特征权重,维度与特征向量相同)
参数里面还有b,这是一个实数(表示偏差)。
得到关于x的线性函数,做线性回归时会用到的函数,所以对于逻辑回归这个二元分类问题来说不是一个好的算法(因为对于输出值有取值范围[0,1],其中0代表概率为0,1代表概率为1,确定预测结果),所以我们要对这个函数生成的y估计值进行数据处理,使得他永远处于[0,1]之间,而且不影响原函数的性质。因此我们引入sigmoid函数。
如图所示,sigmoid函数的函数值永远处于[0,1]之间,且当Z越大时函数值就越趋近于1,当Z越小时函数值就越趋近于0。
2.3 logistic回归的代价/成本函数(Logistic Regression Cost Function)
为了训练逻辑回归函数的w和b,我们需要一个代价函数,通过训练代价函数来得到参数和参数。
对训练集的预测值,我们将它写成,我们更希望它会接近于训练集中的值。
损失函数:
损失函数又叫做误差函数,用来衡量算法的运行情况/效果,Loss function:,通过L来预测输出值与实际值有多接近。
通常使用的损失函数为预测值与实际值的平方差或者其二分之一,但是在逻辑回归中一般不用这个方法,因为在学习逻辑回归参数的时候,发现我们的优化目标不是凸函数,会出现多个局部最优解,无法使用梯度下降法来寻找全局最优解。
我们的目的就是让L尽可能的接近于0,才能说明预测非常准确。
逻辑回归中用到的损失函数是:
损失函数是在单个训练样本中定义的,他衡量了在单个训练样本上的表现。
Cost function 成本/代价函数
适用于全体样本集,算法的代价函数是对m个样本的损失函数求和,再除以m,我们要找到合适的w和b,来使得代价函数尽可能的小。
根据我们对逻辑回归算法的推导及对单个样本的损失函数的推导和针对算法所选用参数的总代价函数的推导,结果表明逻辑回归可以看做是一个非常小的神经网络,在下一节中,我们会看到神经网络会做什么。
在实践中w和b可以使更高的维度,为了画图中方便表示,我们设它为单一实数。
我们所要做的就是找到J的最小值,函数图的最低点,对应的w和b的取值。
初始化w和b:
对于凸函数,可以采用随机初始化的方法,因为对于逻辑回归几乎所有的初始化方法都有效,并且对于凸函数,无论初始化点在哪,都能通过梯度下降法找到全局最值点或大致与其相同的点。
梯度下降法:朝着最陡(梯度最大)的下坡方向走一步,不断的迭代,到达下一个点,再朝着目前最陡的方向走一步。直到到达全局最优解或近似最优解的地方。
逻辑回归的代价函数(成本函数)是含有两个参数的。
迭代就是不断重复做上面两个公式
:=表示更新参数,
α表示学习率(learning rate),用来控制步长(step),即向下走一步的长度
整个梯度下降法的迭代过程就是不断的走,直至逼近或到达最小值点,如下图所示(只含有w,不考虑b的情况),左图为斜率>0,右图为斜率<0。
2.5 导数
2.6 更多导数的例子
2.7 计算图
一个神经网络的计算,都是按照前向或反向传播过程组织的。首先我们计算出一个新的网络的输出(前向过程),紧接着进行一个反向传输操作。后者我们用来计算出对应的梯度或导数。计算图解释了为什么我们用这种方式组织这些计算过程。计算图组织计算的形式是用蓝色箭头从左到右的计算,下一节讲解如何进行反向红色箭头(也就是从右到左)的导数计算。
2.8 计算图的导数计算
用到的求导公式:(链式法则)
这是一个流程图:
输出变量对某个变量的导数,我们就用命名。
在python中,定义变量名叫,简化后写作dJ_dvar;da表示J对a的导数。
2.9 逻辑回归中的梯度下降法
单个实例样本的一次传播过程
正向传播计算L:
反向传播计算导数:
单个样本的梯度下降法的流程:
再更新w,b
完成一次参数的更新。
2.10 m个样本的梯度下降法
上图中只有n=2个特征,所以不需要进行遍历,但是一般来讲,这个算法是需要两次for循环。
同时应用逻辑回归和梯度下降法,初始化
代码如下:
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; #除以m计算平均值
dw1/= m;
dw2/= m;
db/= m;
w=w-alpha*dw #α为学习率
b=b-alpha*db
缺点:此方法在逻辑回归上你需要编写两个for循环。第一个for循环是一个小循环遍历m个训练样本,第二个for循环是一个遍历所有特征的for循环。在应用深度学习算法的时候,代码中显式的使用for循环会使算法很低效,并且不适用于很大的数据集。所以现在有一些向量化技术(vectorization),它可以允许你的代码摆脱这些显式的for循环。
2.11 向量化(Vectorization)
向量化是消除代码中显式for循环的一种技术。
什么叫向量化:
在逻辑回归中你需要去计算,其中w和x都是列向量,如果你有很多特征,那么你的列向量会非常大,所以,非向量化方法计算Z,python代码如下:
z=0
for i in range(n_x)
z+=w[i]*x[i]
z+=b
向量化方法直接计算Z,使用numpy内置函数的python代码如下:
z=np.dot(w,x)+b
一个小例子:
import numpy as np #导入numpy库
a = np.array([1,2,3,4]) #创建一个数据a,相当于python中自带的list
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循环的版本的时间
用时对比:
CPU和GPU都有并行化的指令,他们有时候会叫做SIMD指令,这个代表了一个单独指令多维数据,这个的基础意义是,如果你使用了built-in函数,像np.function或者并不要求你实现循环的函数,它可以让python充分利用并行化计算。GPU更加擅长SIMD计算。
2.12 向量化的更多例子
例1:计算向量,矩阵乘法的定义为:
上图左侧非向量化实现,用到numpy函数生成n维列向量(各元素为0)初始化向量,通过两次for循环得到;使用向量化方法,直接。
例2:对已经向量的每个元素做指数操作。
向量化方法:
事实上,numpy库有很多向量函数,比如 u=np.log
是计算对数函数log、 np.abs()
是计算数据的绝对值、np.maximum()
计算元素y中的最大值,你也可以 np.maximum(v,0)
、 v**2代表获得元素y每个值得平方、获取元素y的倒数等等。所以当你想写循环时候,检查numpy是否存在类似的内置函数,从而避免使用循环(loop)方式。
例3:把刚才的方法,运用到逻辑回归的梯度下降上。
我们现在要做的就是去掉第二个for循环,如果你有很多特征值。
不需要初始化dw1,dw2.·······都为0,我们重新定义dw为一个向量,对第二个for循环,做一个向量操作,,最后dw/=m,从而去掉了第二个for循环。提升代码效率,提高训练速度。
回顾一下逻辑回归的前向传播步骤,一共m个训练样本,先对第一个样本进行预测,计算,,得出激活函数a,计算出第一个样本的预测值y,再对第二个样本进行预测,同样的操作,直至第m个。使用for循环,一共需要循环m次。
共m个元素,使用python numpy的内置函数,同时计算Z1,Z2,.....Zm,得到一个矩阵Z。numpy命令,其中b为一个实数或者说是一个1*1的矩阵/向量,但是当前面的向量+b时,python解释器自动把b扩展成1*m的行向量,这种操作被叫做广播(brosdcasting),计算出所有的a,我们记为A,。
2.14 向量化逻辑回归的梯度输出
**大写字母代表向量,小写字母代表元素
本节重点是如何同时计算m个数据的梯度,实现一个高效的逻辑回归算法。
在2.12的例3中,我们已经利用向量化的方法去掉了代码中第二个for循环,,但是还有一个遍历训练集的循环,公式步骤如下:
计算dw还需要一个循环遍历训练集,我们要将其向量化。首先看db,通估观察,,dZ是一个行向量,所以在python中;再看dw,,其中X是行向量,所以展开后。因此我们用梁行代码进行计算,从而避免使用for循环:
下面我们来走一遍向量化的逻辑回归的一次迭代步骤:
上图最原始没有进行优化的步骤,使用向量化方法进行一次迭代:
利用5个公式完成了一次前向传播和后向传播,再利用后面两个公式进行梯度下降更新参数,这样就完成成了一次迭代实现了一次梯度下降,如果需要多次梯度下降,那么只能对这些公式完整的使用一次for循环,放在最外层。
最后,我们得到了一个高度向量化的、非常高效的逻辑回归的梯度下降算法。
2.15 Python中的广播(Broadcasting in Python)
广播是一种技术手段,它可以使你的python代码效率更高。
它与MATLAB/Octave中的bsxfun函数的作用类似,但不完全相同。
下面用一个例子来介绍numpy中的Broadcasting中主要用于神经网络/深度学习中的广播形式。
不同食物中不同营养成分的卡路里含量表,左边依次表示为:碳水化合物,蛋白质,脂肪。
我们想要计算不同食物中不同营养餐成分中的卡路里百分比。
例如苹果中的碳水化合物卡路里百分比含量,首先计算苹果(100g)中三种营养成分卡路里总和56+1.2+1.8 = 59,然后用56/59 = 94.9%算出结果。
如果要计算所有的食物,那么就要按照上面的方法进行循环操作,如何避免使用for循环呢?
我们要使用numpy库中的函数来完成,分为两段代码:第一段对每列求和,第二段分别计算每种食物每种营养成分的百分比。
如上图,定义出矩阵。
对每一列分别求和,其中axis=0表示按列。axis用来指明将要进行的运算是沿着哪个轴执行,在numpy中,0轴是垂直的,也就是列,而1轴是水平的,也就是行。
计算百分比,用3*4的矩阵A除以1*4的矩阵,得到一个3*4的矩阵,就是百分比。这条指令是调用了numpy中的广播机制,而里面的reshape(1,4)是将矩阵cal重塑成1*4的矩阵(当你不确定cal矩阵的维度的时候可以使用,作为保险),reshape是一个常量时间的操作,时间复杂度是O(1),调用代价极低。
而对于其中的矩阵除法,则类似于上一节的b,自动扩展矩阵,例子如下:
下面直接粘上黄博对于numpy广播机制的理解,我觉得总结的很好,很清晰。
2.16 关于 python/numpy 向量的说明
python中numpy库的一维数组的使用,优化代码避免出现不必要的bug。
python巨大的灵活性使得它可以通过一行或者几行完成很多事情,但正是由于代码的灵活性,如果不注意就会出现很多奇怪或者很难发现的bug。例如,如果你要计算两个行向量相加,但是你却写成了一个行向量和一个列向量相加,你以为它会报错,但是由于numpy库中广播功能的存在,python解释器会自动进行行列向量自动扩展补全的向量求和,而非报错,最终造成结果的错误。
python-numpy中一个很容易被忽略的地方:
例1:
首先我生成一个存储在数组a中的5个高斯随机数变量,输出a的形状(shape)是一个(5,)的结构,被称作一个一维数组。既不是行向量,也不是列向量。如果我对a进行转置a.T,那么输出结果看不出效果和a一样,再做另一个操作,a*a的转置,本应该输出一个矩阵,但是结果却输出一个数值。
如果设置a为(5,1)5行1列的列向量,则a的转置就成为1行5列的行向量,这样做运算就是正常的了。并且这样生成的数组(向量)两端有两个中括号[[]],而(5)生成的数组两端只有一个中括号[]。
产生一个数据结构,得到(5,),它被称作a的一维数组,同时也是一个非常有趣的数据结构。在进行神经网络编程时不要使用这个数组。
如果不确定一个向量它是否是一个向量,或者说不能确定它的维度,那么使用断言函数assert,如果判断是则会向下执行,如果不对则会返回false。如果的确不是一个向量或者不是一个你想要的向量,那么我们接下来可以使用reshape函数来改变a的维度。
所以尽可能的事先确定好你的向量就是一个列向量/行向量,这样就可以剩下一些代码来判断和修正,提高代码效率。
交互式shell命令。
可用来测试代码,做笔记,按shift+enter执行代码。
在运行一个单元格cell时,你也可以选择运行其中的一块代码区域。通过点击Cell菜单的Run Cells执行这部分代码。
当你在编写执行一段代码时,实际上notebook会使用一个内核在服务器上运行这段代码,如果你正在长时间运行代码,刚好程序运行出错或者崩溃,你可以点击Kernel,选择Restart,就会重新恢复。
这节课将证明一下逻辑回归函数的损失函数。
预测值y帽,表示的是在给定输入值x的情况下,y等于1的概率;也就是说,如果已经y=1,那么2式等价于3式,即,预测值等于给定x的情况下y的概率,又由于我们讨论的是二分问题的损失函数,所以y只能取值0和1,所以当y=0时,概率为1-y帽。
如果不讨论分情况,把这个分段函数整合成一个函数,公式如下:
由于我们要做的就是尽可能的使得p(y|x)最大,我们使用log函数对等式变形,不改变单调性。简化后等式右边:
-------------------------------②
这正是前面提到的损失函数L的负数。
再说损失函数Loss function:,我们要做的就是使损失函数L尽可能的小。
综上,我们要使得p(y|x)最大,也就是要使得1式等式右边最大,即2式最大,我们令2式=-L,即要使L最小,我们把这里的L命名为损失函数,所以得到损失函数的表达式为:
这样就得到了单个训练样本的损失函数的表达式。
下面来看一下m个样本的训练集的损失函数。
我们假设所有样本独立同分布,所有这些样本的联合概率就是每个样本概率的乘积:
------------------------①
我们引入一个新的概念,概率论or统计学中的最大似然估计,即求出一组参数,使得式子能取得最大值。
所以要使得P取得最大值,就要使得1式右边取得最大值。我们令其为L(θ)。求L的最大值,根据最大似然估计的方法,取对数,再求导求极值即可得到最大值。
上图第三步是根据前面一个训练集时推导出来的。最后得出结论,要令这个L(y帽,y)最小,所以我们令这个L为逻辑回归函数的成本函数J(w,b),为了方便,可以对成本函数做适当的缩放,添加额外的常数因子,所以得到最终的成本函数:
。
最大似然估计的定义与一般步骤见百度百科:最大似然估计