【深度学习】1.2 神经网络基础

二分分类

正向传播步骤
反向传播步骤

logistics回归是一个用于二分分类的算法。
若输入图像是一个6464像素的,那么就会有三个6464的矩阵,分别对应Red、Green、Blue矩阵,表示红、绿、蓝这三种像素的亮度。
把这些像素值都提出来,放入一个特征向量x(列向量,其总维度为64643=12288)。用nx表示输入的特征向量x的维度,在这里为12288,有时为了简便可以用n表示。

(x,y)表示一个单独的样本,x是nx维的特征向量,标签y值为0或1,
训练集由m个训练样本构成,m表示训练样本的个数,可写为mtrain
(x(1),y(1))表示样本1的输入和输出,
(x(2),y(2))表示样本2的输入和输出,
(x(m),y(m))表示最后一个样本m的输入和输出。
mtest表示测试集的样本数
矩阵X=[x(1),x(2),…,x(m)](m列,nx行)为训练样本,也可以为上面这个的转置(nx列,m行),多用第一种形式。用python实现的时候,代码为X.shape(nx, m)
矩阵Y=[y(1),y(2),…,y(m)](1*m矩阵)为标签。在python中的代码为Y.shape(1, m)

logistic回归

这是一个二元分类问题。
已知的输入特征向量x,可能是一张图,希望识别出这是不是一张关于猫的图。需要一个算法,给出一个预测值,则y(上面一个^,下面叫它y hat)就是对y的预测,也就是一个概率;当输入特征x满足条件时,y就是1。
已知logistic回归的参数是w,也是一个nx向量,而b是一个实数。所以已知输入x和参数w和b,如何计算输出y hat?
在这里插入图片描述
而sigmoid(z)的图形为:
在这里插入图片描述
在这里插入图片描述
z很大时,这个值接近1;z很小时,甚至为负值,这个值接近0。

这样我们就只用训练w和b,通常就它们看作是两个独立的参数。

logistic回归损失函数

为了训练w和b,需要定义一个成本函数(cost function)。
训练样本x(i)对应的预测值是y-hat(i),上标i用来指明数据,表示x或者y或者z和第i个训练样本有关。

损失函数(loss function)L也叫误差函数(error function),可以用来衡量算法的运行情况,可以定义为y-hat和y的差的平方或者它们差的平方的1/2。
在这里插入图片描述
总之,通过定义这个损失函数L来衡量预测输出值y-hat和y的实际值有多接近。
梯度下降法,可能找不到全局最优值(之后会讲)。所以在logistic回归中,会定义另一个不同的损失函数,它起着与误差平方相似的左右,这些会给我们一个凸的优化问题,这样就会很方便后续的优化处理。

在logistic回归中,使用的是L(y hat,y)=-(y*log(y hat)+(1-y)*log(1-(y hat))
在这里插入图片描述
如果我们使用误差平方,希望它越小越好;而对于这个损失函数,我们也希望它尽可能地小。下面举以下两个例子来说明为什么可以用这个函数来作为损失函数:
①如果y=1,那么L(y-hat,y)=-log(y hat),若想让L尽可能小,那么就意味着让log(y hat)尽可能的大,意味着(y hat)尽可能大,但因为y hat是simoid函数得出的,范围在0-1之间,所以永远不会比1大,所以要让y hat接近1。
②如果y=0,么L(y-hat,y)=-log(1-(y hat)),在学习过程中,想让L小一些,那么意味着想要log(1-(y hat))大一些,让1-(y hat)大一些,让y hat小一些,又因为y hat只能介于0-1之间,所以要让y hat接近0。
损失函数L是单个训练样本中定义的,它衡量了在单个训练样本上的表现。

成本函数J衡量的是在全体训练样本上的表现。
J(w,b)=(1/m)*求和L(y-hat(i),y(i)
在这里插入图片描述
因此,我们要找到合适的w和b,使成本函数尽可能小。
logistic回归可以看作是一个非常小的神经网络,在这里插入图片描述

梯度下降法

本小节介绍如何使用梯度下降法来训练或学习训练集上的参数w和b。
在这里插入图片描述
这个图中的横轴表示空间参数w和b,在实践中,w可以是更高维的,但为了便于绘图,让w是一个实数,b也是一个实数。成本函数J(w,b)是在水平轴w和b上的曲面,曲面的高度表示了J(w,b)在某一点的值。我们所要作的就是找到这样的w和b,使其对应的成本函数J为最小值。
可以看到,成本函数J是一个凸函数,(对于非凸的,比如像波浪形的函数,会有很多不同的局部最优)凸函数这个性质是我们使用logistic回归的这个特定成本函数J的重要原因之一。
为了找到更好的参数值,需要用某初始值初始化w和b。对于logistic回归而言,几乎任意的初始化方法都有效,通常用0来进行初始化,但我们一般不这么做。
而梯度下降法所作的就是从初始点开始朝最陡的下坡方向走一步,在梯度下降一步后,或许在那里停下,因为它正试图沿着最快下降的方向往下走,或者说尽可能快的往下走,这是梯度下降的一次迭代。不断迭代的这条线很有希望收敛到它的全局最优解,或接近全局最优解。

假设只有一个参数w,那么也就是不断更新w,
在这里插入图片描述
其中,α为学习率,后面会提到;蓝色那里是导数,是该点所在处的斜率,有正有负,这就是对参数w的更新或者变化量。
当我们开始编写代码来实现梯度下降,我们会使用代码中变量名的约定,用dw去表示导数那一块,也就是用dw作为导数的变量名,即w:=w-α dw。

而成本函数是一个含有w和b的函数。在这种情况下,梯度下降的内循环与上面类似,不断更新w和b。w:=w-α dw和b:=b-α db是实际更新参数时进行的操作。
在这里插入图片描述
微积分中,当函数J有两个或两个以上的变量,不适用小写字母d而使用上图蓝色那种的偏导数符号。如果J只有一个变量,就是用小写字母d表示导数。

计算图

可以说一个神经网络的计算都是按照前向或者反向传播过程来实现的。首先计算出神经网络的输出,紧接着进行一个反向传输操作,后者用来计算出对应的梯度或者导数

用J(a,b,c)=3(a+b*c)来举例

计算这个函数,实际上有三个不同的步骤。分别为计算bc,将其储存在变量u中,u=bc;然后计算v=a+u;最后输出J=3*v。
流程图为:
在这里插入图片描述
在logistic回归中,J是想要最小化的成本函数。可以看出,用过一个从左向右的过程,可以计算出J的值。为了计算导数,需要一个从右向左的过程(下一个小标题的内容)。

计算图的导数计算

dJ/dv = 3,对于任何v的增量,J都会有三倍增量。
在反向传播算法中的术语,如果想计算最后输出变量的导数,使用最关心的变量对v的导数,那么这就完成了一步反向传播,这就是一个反向步。

如果我们提高a的数值,对J的数值有什么影响呢?
dJ/da = dJ/dv * dv/da = 3*1 =3(链式法则),这个影响的过程是,改变了a的值,就会改变v的值,通过改变v也会改变J。这是另一步反向传播计算。

当我们编程实现反向传播时,通常会有一个最终输出值是我们所要关心的。在上面这种情况下,我们真正想要关心的最终输出变量是J,所以有很多计算尝试计算输出变量的导数,所以d输出变量对某个变量的导数,我们就用d var命名。为了简便书写,我们就用dJ/dvar表示。在python代码中,dJ/da就用da去表示,dJ/dv就用dv去表示。

在上述例子中,还有的反向传播为:dJ/du = 3, dJ/db = dJ/du * du/db = 3×c = 3×2 = 6,dJ/da = 9。

当计算所有这些导数时,最有效率的方法时从右向左计算。

logistic回归中的梯度下降法

这一小节介绍怎么计算偏导数来实现logistic回归中的梯度下降法。它的核心关键点是其中有几个重要的公式用来实现logistic回归的梯度下降法。
logistic回归的公式为:
在这里插入图片描述
现在只考虑单个样本的情况,关于该样本的损失函数如上,其中a是logistic回归的输出,y是样本的基本真值标签值。

假设样本只有两个特征x1和x2。为了计算z,需要输入参数w1、w2和b,还有样本特征值x1和x2。因此用来计算z的偏导数计算公式为z=w1*x1+w2*x2+b,然后计算y hat=a=sigma(z),最后计算L(a,y)。所以在logistic回归中,我们需要变换参数w和b的值,来最小化损失函数。这是前向传播。
在这里插入图片描述

下面来计算偏导数,要想计算损失函数L的导数,首先我们要向前一步,先计算损失函数关于变量a的导数dL/da=-y/a+(1-y)/(1-a)(在代码中,只需要使用da来表示这个变量)。
现在可以再向前一步,计算dz,也就是损失函数关于z的导数,dL/dz=dL/da*da/dz=[-y/a+(1-y)/(1-a)]× [a (1-a)]=-y(1-a)+a(1-y)=a-y。
现在向后传播的最后一步了,计算一下w和b需要如何变化。关于w1的导数dw1=dL/dw1=x1*dz,dw2=x2*dz,db=dz。
在这里插入图片描述

因此关于单个样本的梯度下降法就完成了,使用公式dz=a-y计算dw1、dw2和db。然后更新w1,为w1减去学习率乘以dw1;类似地,更新w2:=w2-α*dw2更新b:=b-α ×db。
这就是单个样本实例的一次梯度更新步骤。

m个样本的梯度下降

在这里插入图片描述

上一小节只使用了一个训练样本(x(i),y(i)),可以得到dw1(i)、dw2(i)、db(i)
而全局成本函数,是一个求和,实际上是1到m项损失函数和的平均。它表明,全局成本函数对w1的导数也同样是各项损失函数对w1导数的平均。这样就可以得到全局梯度值,它可以直接应用到梯度下降算法中。
在这里插入图片描述
所以,我们要做的就是:
1.
初始化
J=0,dw1=0,dw2=0,db=0
2.
使用for循环遍历训练集,同时计算相应的每个训练样本的导数

-for i=1 to m
------z(i) = wTx(i)+b
------a(i) = σ(z(i))
------J += -[y(i)loga(i)+(1-y(i))log(1-a(i))]
------dz(i) = a(i)-y(i)
------dw1+= x1(i)dz(i)
------dw2+= x2(i)dz(i)
(#假设只有两个特征值x1和x2,n=2,如果有更多的特征值,在代码中需要再用一个for语句一直使其计算到dwn,即使其遍历所有n个特征值)
------db += dz(i)
-J /= m
-dw1 /= m
-dw2 /= m
-db /= m

这样就得到了成本函数J对各个参数w1、w2和b的导数。其中,使用dw1、dw2和db作为累加器,所以在这些计算之后,dw1等于全局成本函数对w1的导数,对dw2和db也是一样。
dw1、dw2没有上标i,因为在上述代码中,它们作为累加器去求取整个训练集上的和;相反,dz(i) 是对应于单个训练样本的dz,所以它的上标i表示它对应第i个训练样本的计算。
3.
应用一步梯度下降法
去更新w1 、w2 和b。
4.
重复以上内容很多次以应用多次梯度下降法
在深度学习中,数据集很大,所以算法使用完全不用显式for循环很重要

向量化

向量化摆脱显式for循环是一种很常见的方法。

什么是向量化?
在logistic回归中,z = wTx+b,其中,w是列向量,x也是一个列向量。如果有很多特征值,那么它们就是非常大的向量,所以w和x都是R内的nx维向量。

如果使用非向量化的方法,那么python中的代码将会是:

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

作为对比,一个向量化的实现将会非常直接计算wTx。在python或者numpy,需要使用命令:

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

(相较CPU而言,GPU更擅长SIMD计算)

向量化的更多例子

经验:当编写新的网络,或者做的是回归,那么一定要尽量避免for循环,能不用就不用。如果可以使用内置函数或者其它办法去计算循环,通常会比直接用for循环更快。
下面看两个例子:

如果想计算向量u作为矩阵A和另一个向量v的乘积,乘法规则是:uij(Aijvj),代码是

u = np.zeros((n,1))
for i ...:
	for j ...:
		u[i] += A[i][j] * v[j]

这是一个双重for循环,对i和j循环。所以这是一个非向量化的版本。
向量化实现如下:

u = np.dot(A,v)

假设内存中已经有一个向量v,如果想作指数运算,作用到向量v的每个元素,可以令u等于那个向量,即:
在这里插入图片描述
代码为:

u = np.zeros(n, 1)#初始化让u成为一个0向量
for i in range(n):
	u[i] = math.exp(v[i])

这是一个非向量化的实现,而向量化的实现如下:

import numpy as np
u = np.exp(v)

事实上,numpy库中有很多内置函数,比如:
"np.log(v)"会逐个计算每个元素的log;
"np.abs(v)"会计算绝对值;
"np.maximum(v,0)"会计算所有元素中的最大值,找出v中所有元素和0之间相比的最大值;
"v**2"就是v中每个元素的平方;
"1/v"就是每个元素求倒数…
因此,当想要使用for循环时,就去看看可不可以用numpy库中的内置函数计算。

我们用来计算logistic回归导数的程序为:
在这里插入图片描述
想要把上面这个向量化,那么我们就不用初始化dw1和dw2,而将其看作是一个向量dw,令dw=np.zeros((n-x,1)).

J = 0#这是一段不太严谨的代码,主要是修改了dw那里
dw = np.zeros((nx,1))
for i = 1 to m:
	z[i] = ...
	...
	dw += x[i] * dz[i]
	db += dz[i]
J /= m
dw /= m
db /= m

这样上面的特征值可以不仅仅是两个x1和x2了,可以是nx个,而且去掉了一个for循环,仅剩一个for循环了。

向量化logistic回归

首先回顾一下logistic回归的正向传播步骤。
如果有m个训练样本,那么对第一个样本进行预测:
在这里插入图片描述
计算出z,然后计算激活函数,计算在第一个样本的y hat。然后继续去对第二个训练样本做一个预测,再对第三个样本做一个预测:
在这里插入图片描述
以此类推,这样重复m次。
为了执行正向传播步骤,需要对m个训练样本都计算出预测结果。
有一个办法可以不需要任何一个显示的for循环。

之前定义过一个矩阵X=[x(1),x(2),…,x(m)]T来作为训练的输入(这是一个nx*m的矩阵),即X是把所有训练样本堆叠起来得到的,一个挨着一个,横向堆叠。

令Z = [z(1) ,z(2),…,z(m)] = wTX + [b, b,…, b]1×m = [wTx(1), wTx(2), … , wTx(m)]1×m + [b, b,…, b]1×m = wTx(1)+b, wTx(2)+b, … , wTx(m)+b]1×m,其中, wT是一个行向量。
为了计算Z,在python numpy中的指令为:

Z = np.dot(w.T , X) + b

在以上代码中,b是一个实数,也可以说是一个1×1的矩阵。但在python中,当你把这个向量加上这个实数时,python会自动地把实数b扩展成一个1×m的行向量。(这在python中叫做广播broadcasting

令A = [a(1) ,a(2),…,a(m)] = σ(Z),即可同一时间计算所有a。

向量化logistic回归的梯度输出

计算梯度时,有dz(1) = a(1) -y(1), dz(2) = a(2) -y(2)
因此,定义一个新的变量dZ = [dz(1), dz(2), … , dz(m)]1×m,所有的dz变量横向排列。
再定义 A = [a(1) ,a(2),…,a(m)] , Y = [y(1) ,y(2),…,y(m)] 。
那么就可以用dZ = A - Y = [a(1)-y(1), a(2)-y(2), …, a(m)-y(m)]来同时计算所有的dz。

那么如果想实现:

db = 0
db += dz[1]
db += dz[2]
...
db += dz[m]
db /= m

的向量化,可以用以下代码:

db = np.sum(dZ) /m

对于dw同理:

dw = 0
dw += x[1]*dz[1]
dw += x[2]*dz[2]
...
dw += x[m]*dz[m]
dw /= m

想实现它的向量化,那么就让dw等于Xn×m(dZ1×mT/m。

python中的广播

举个例子:
在这里插入图片描述
在这个矩阵中,列举出了100克四种不同食物中,来自碳水化合物、蛋白质、脂肪的卡路里数量。
若想知道四种食物中,来自碳水化合物、蛋白质和脂肪热量的百分比,那么就先将这四列分别加起来,然后用每个元素去除以所在列的总和。

令上面这个3×4的矩阵为矩阵A,然后用一行python代码就可以对各列求和,从而得到四个数字,对应四种不同食物的卡路里总量。
然后用第二行python代码让四列每一列都除以对应的和,就可以得到百分比了。

import numpy as np

#创建矩阵A
A = np.array([ [56.0, 0.0, 4.4, 68.0], [1.2, 104.0, 52.0, 8.0], [1.8, 135.0, 99.0, 0.9] ])
print(A)

会得到:
在这里插入图片描述

cal = A.sum(axis=0) #axis=0意味着竖直相加
print(cal)

在这里插入图片描述
这样就得到了四种食物中的卡路里总数。其中,axis=0意味着希望python在竖直方向求和,axis=1意味着希望python在水平方向求和。

percentage = 100 * A / cal.reshape(1, 4)
print(percentage)

在这里插入图片描述
这样就得到了百分比,即100克苹果中有94.9%的卡路里来自碳水化合物。2%的卡路里来自蛋白质,3%的卡路里来自脂肪。

如何让一个3×4的矩阵除以一个1×4的矩阵呢?

若有一个4×1的列向量[1 2 3 4]T,让它和一个数字100相加,python会将这个数字展开成一个4×1的向量[100 100 100 100]T,如下:
在这里插入图片描述
若有一个m×n的矩阵,让它加上一个1×n的矩阵,那么python会将1×n的矩阵复制m次,再去做加法,如下:
在这里插入图片描述
若有一个m×n的矩阵,让它加上一个m×1矩阵,那么python也会将m×1的矩阵水平复制n次,再去做加法,如下:
在这里插入图片描述
综上,如果一个m×n矩阵,加减乘除一个1×n的矩阵,那么python就会将它复制m次变成一个m×n矩阵,然后再逐元素做加减乘除;如果一个m×n矩阵,加减乘除一个m×1的矩阵,那么python就会将它复制n次变成一个m×n矩阵,然后再逐元素做加减乘除。
如果一个m×1矩阵,加减乘除一个实数,那么python会把这个实数复制m次成为一个m×1矩阵;如果一个1×n矩阵,加减乘除一个实数,那么python会把这个实数复制n次成为一个1×n矩阵。
(可在numpy中搜索broadcasting了解更广义的定义)

关于python/numpy向量的说明

import numpy as np
a = np.random.randn(5)
print(a.shape)

结果是(5,),这表明该数组秩为1,既不是行向量也不是列向量,且aT仍为a,a与aT的乘积为一个数。此时创建的是一个数据结构,通常叫它秩为1的数组。所以不要使用这种形式的创建方法。
如果想令a为5×1矩阵的话,可以这样写代码:

a = np.random.randn(5,1)

通常不确定时,可以使用assert()去声明这是一个向量,从而确保它是一个某一维度的向量,代码如下:

assert(a.shape == (5,1))

若已经得到了一个秩为1的数组,那么可以利用reshape()去转换,代码如下:

a = a.reshape((5,1))

总之,不要使用秩为1的数组,用向量,否则会出现很多bug!

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值