神经网络编程基础

这是吴恩达deeplearning第一部分第二周的笔记,更新完毕

神经网络的计算过程:

通常有两个过程,第一个是正向过程,称为正向传播步骤,第二个是反向过程,称为反向传播步骤。这是两个分开的过程。

二分分类问题:

以一个例子来说明什么是二分分类问题,给一张图片作为输入,输出图片是否有猫,标签1代表图片有猫,标签0代表图片没有猫,通过问题的一个输入,需要做到回答有或者没有两种答案。

因此,在二分分类问题中,目标是通过训练一个分类器,通过输入x,预测输出y是0还是1。

符号约定1:

1.如果x是一个向量,那么n_x就是向量的维度,即向量的元素个数,通常简记为n。

2.用一对(x, y)来表示一个样本,x是一个有n维的特征向量,这个向量的元素是输入的各种特征。以上面的猫图片为例,通常一张图片可由三个矩阵表示,分别是R(红色), G(绿色),B(蓝色),每个矩阵是64*64,对应64*64个像素,每个像素都有一个像素值,代表着图片在这一点某一颜色的亮度。将三个矩阵的各个像素值取出来放到x中,得到64*64*3维的特征向量x。y是输入。

3.第一周有提到样本规模设为m,所以m是一个样本集规模。一个样本集通常用:{(x^(1), y^(2)),(x^(2),y^(2)),(x^(3),y^(3))……(x^(m),y^(m))}表示。有的时候,样本集用来训练一个模型,那么样本集规模用m_train表示训练样本集规模,同样的m_test用来表示测试样本集规模,这其实是通过下标的方式,强调不同用途的样本集。

4.既然样本集的x是特征向量,那么可以把样本集的输入放到一个输入矩阵中,使得输入看的更加紧凑,用X表示。

输入矩阵是列向量的形式,而不是行向量的形式,就在于深度学习中,用列向量表示在之后使用时更为简便。所以,这是一个n*m维度矩阵。

5.当然,可以把输出y放到输出矩阵中。这时候Y就是一个1*m的输出矩阵。

Sigmoid函数:

这是sigmoid函数,它将在logistic回归中发挥重要的作用!

sigmoid函数的表达式是:

观察图像,z是自变量,通常是一个输入。函数图像在z==0时输出为0.5,而在z无限趋近于正无穷这样很大的一个数,输出无限接近1,当z无限趋近于负无穷,输出无限接近0。也就是说这是一个有界函数。

Logistic回归:

Logistic回归是一种学习算法,通常用在监督学习中输出y为0或1的情形,这是一个二分分类问题,因此Logistic回归可用在二分分类问题上。

Logistic回归究竟在干什么事情?Logistic回归尝试将输入——特征向量x通过计算得到输出y的一个预测值,也就是说,在二分分类问题上,Logistic回归得到一个x后,计算出一个介于0到1之间的值,这个值就是y的预测值,如果预测值为1,那么以上面的猫图为例,就可以得出这是一张猫图的答案,反之如果得到0,那么就可以得出这绝对不是一张猫图的结论。那么如果得到介于0到1之间的预测值,则预测值反应的是一个概率,这个值越接近1,这张图片就越有可能是猫图。

Logistic回归表达式是:

输入x是一个n维的特征向量。b是一个实数。w也是一个n维的特征向量,所以w和x的内积是合法的,因为w和x具有相同的维度。w通常被称作Logistic回归的参数,它的意义是特征向量x的一个权重,从这个方面来理解为什么x和w具有相同维度,因为x有多少个输入数据元素,就对应有多少个权重,这就是w元素个数和x元素个数相同的原因。

为什么会有sigmoid?这是因为,通常在二分分类中,我们的y是介于0到1之间的,所以如果单纯地y由线性函数刻画,那么y可以是一个很大很大的数,也可以是一个趋向负无穷的很小很小的数。这不符合我们的要求。我们回顾一下sigmoid函数,它把负无穷到正无穷的数映射为介于0和1之间的值。所以,这时候w·x+b就是sigmoid函数中的自变量z,输出y就可以满足我们的要求。

Logistic回归的成本函数:

通常,我们通过Logistic回归预测的y和真实的y有一定的误差,我们可以定义一个损失函数,这个函数和预测值y以及真实值y有关。我们希望通过样本集的不断输入,训练出一个Logistic回归,这个函数里的w和b使得预测值y和真实y之间的误差最小,那么此时的损失函数对应的w和b就是误差最小时的w和b。

通常,我们可以用预测值和真实值的差的平方来描绘误差大小,但是在Logistic回归中我们不这么做。我们定义一个效果相同的损失函数L:

y帽是预测值。从函数表达式来看,损失函数L与y帽和y有关,是一个二元函数。

当真实值y为1时,把y=1代入损失函数L,得到:

我们希望接近真实情况的误差尽可能小,也就是让在y=1情况下的L尽可能小,那么预测值y帽就要尽可能大,接近1(默认对数函数单调递增),这也是符合我们的正常逻辑。

同理,当真实值y为0时,损失函数L为:

如果让损失值尽可能小,那么根据函数单调性,y帽就要尽可能小,接近0,这也符合我们的逻辑。

一对y帽和y实际上是一个训练样本,损失函数L衡量的是一个训练样本下,算法的优劣,w和b是否合适。那么如果将一个样本集考虑进来——

我们就可以定义一个成本函数J,它是所有损失值的平均值,它衡量了所有训练样本下,算法的优劣,w和b是否合适。

梯度下降法:

前面我们提到,我们为了得到合适的Logistic回归函数,需要确定w和b,通常我们通过测试得到的y帽是一个预测值,它与真实值y有一定误差,我们通过每个单一样本的误差函数积累得到整个训练集的成本函数J。现在我们要做的事情是确定合适的w和b,使得成本函数J尽可能小。

假设w和b是实数,我们可以建立这样的三维空间:

成本函数是一个曲面,因为它是一个二元函数,而且具有一定的凹凸性,曲面最低点是我们想要的。

梯度下降法,是要初始化一个起点,然后通过不断地朝最低点走,得到成本函数J的最小值,同时也就得到了最合适的w和b。

忽略b,从侧面来看,得到这样的成本函数J曲线。

为了得到最低点的w,我们对w重复进行以下更新迭代:

:=是更新的意思,可以直接理解为等号=即可。

迭代式中的α是学习率,它与J关于w的导数的乘积表示w更新一次的步长。如果w初始化在最低点右边,那么w实际上在更新时往左移动,反之,w初始化在最低点左边,那么w实际上在更新时往右移动,这是由于导数在不同侧的正负性决定的。

对于w,我们重复迭代,得到最合适的w,同理,我们对b也重复迭代,也会得到最合适的b。

符号约定2:

  1. 上面梯度下降法中成本函数J对w的导数可以简记为dw,同理,J对b的导数可以简记为db。对x的导数可以简记为dx。

  1. 损失函数L中的log底数默认为e,即对数是ln。

理论实现单个样本在梯度下降迭代中的导数:

前面我们已经知道,通过w和b的一次次迭代更新,最终得到合适的w和b使得我们的成本函数J最小。观察一下,w和b迭代式里面的导数dw和db(为什么是这个符号,前面符号约定2已经设定好了)是不清楚怎么实现的。

对于单次梯度下降,对应着一个训练样本,对应有一个损失函数L,成本函数J没有意义。假设训练样本输入x为一个2维向量,w和b也一样,为2维向量,它们各自有两个特征元素。x有x1, x2。w有w1,w2。b是一个实数。回顾一下损失函数L表达式。

画出L函数的链,并据此求出各个导数。

各个导数:

一一带入即可,具体导数怎么算出来的,问高数老师。

然后,我们就可以一一更新w和b了:

理论实现样本集的一次梯度下降:

对于一个样本集,它一定存在一个成本函数,是所有单样本的误差平均和。单样本误差函数L的w和b都已经可以实现迭代,那么整个样本集的成本函数J也能实现w和b的迭代。考虑成本函数对w的偏导,就像先前误差函数L对w的偏导一样,记为dw,同理db。

从公式可以看到,我们成本函数J对w求偏导,相当于每一个样本损失函数L对w偏导的和。同理J对b的偏导。

这样,我们只需要找累加器,把每个样本损失函数L对w和b的偏导值累计起来,然后求平均即可,程序不难实现。

下面可以被理解为是一种伪代码,符号的形式也是先前已经约定好了:

J = 0
dw1 = 0
dw2 = 0
db = 0
For i in m:
    z(i) = w·x(i) + b
    a(i) = σ(z(i))
    J += -(y(i) · ln(a(i)) + (1 - y(i)) · ln(1 - a(i)))
    dz(i) += a(i) - y(i)
    dw1(i) += x1(i) · dz(i)
    dw2(i) += x2(i) · dz(i)
    db(i) += dz(i)
J /= m
dw1 /= m
dw2 /= m
db /= m
w1 = w - α · dw1
w2 = w - α · dw2
b = b - db

注意,这是所有样本的一次梯度下降,如果要执行k次,那么在上述全部代码整体咋外部套上一个For循环。

向量化Logistic回归:

正如我们所见,上面的基础实现代码,使用了两个for循环来实现一次梯度迭代,这是非常低效的(当样本有不止一个特征值时,w就有不止一个特征值,比如说有n个特征值,那么dw1(i) += x1(i) · dz(i)和dw2(i) += x2(i)·dz(i)还要套上一个for循环,从1到n)。前面提到,样本x可以被堆叠成一个矩阵,或者说按照分块矩阵,可以相当于被堆叠成一个1*m的行向量形式的矩阵。

w是一个n*1的列向量,w的转置是一个行向量,那么w.T · X就得到了一个矩阵。

这是一个行向量形式的分块矩阵,如果我们对每个W.T·x加上b,就得到了每个样本的z所堆叠形成的行向量形式的分块矩阵Z。通过观察我们知道,每个z都是一个实数,所以其实Z就是一个行向量。

那么通过激活函数σ(在这里其实就是sigmoid函数)得到的a所堆叠起来的也是一个行向量A:

这个A就是所有样本经过Logistic回归得到的预测值所堆叠而成的矩阵。现在我们已经将Logistic回归的输入x,权重w,初步预测值z,激活预测值a都表示成了矩阵的形式,这些矩阵代表一个样本集的所有数据,那么这时候,使用Python里面的numpy库,就能实现对代码的优化,从而达到消除2个for循环的目的。

举个简单的例子,我们知道计算z的公式是z = w.T·x + b,所以,这时候就可以写成:Z = w.T·X + b;使用numpy中的dot函数可以实现矩阵的乘法,然后由于b是一个实数,因此加法操作实际上在进行广播再进行元素加法操作,代码如下:

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

只需要一行代码,就能把所有样本的z计算出来,省略了一个从1到m的for循环,对应上面伪代码的:

For i to m:(伪代码)

同样地,我们将实际值y堆叠起来,成为一个行向量Y,形式在本文开头已经提过。

现在我们再来回顾一下上面的伪代码,这是一个样本集实现一次梯度下降的伪代码,一次梯度下降,需要计算出成本函数J对w的导数dw,以及J对b的导数db,由于这时候是一个样本集,所以我们考虑把所有样本的dw累加起来,然后求平均,不就得到了整个样本集成本函数J对w的导数dw了吗,同样的,db也是这么做。

回顾了伪代码,我们用numpy来实现一下这个过程。

import numpy as np
import torch

J = 0; n = 2; m = 3; b = 5    #初始化成本函数J,输入x的特征个数n,样本数m,实数b
dw = np.zeros(n, 1)
db = np.zeros(n, 1)    #dw和db要做累积,因此要初始化成值为0的n维度向量
X = np.array([[1, 2, 3],
             [2, 3, 4]])    #    设置一个样本集为一个2行3列的矩阵
Y = np.mat([4, 2, 7])    #    设置Y为样本集各个样本的实际值y堆叠起来的矩阵
w = np.mat([3, 2])    #    设置权重w,维度和单个样本x相同

Z = np.dot(w.T, X) + b    #    开始计算z,根据公式得出矩阵Z
A = torch.sigmoid(Z)    #    对Z进行激活,得到矩阵A
J = (-1) * (np.dot(Y, np.log(A)) + np.dot((1 - Y), np.log(1 - A)))
#    根据样本集的激活值A和实际值Y计算成本函数J
dz = A - Y    #    关键一步:计算dz
dw = np.dot(X, dz.T) / m    #计算最后的dw
db = np.sum(dz) / m    #计算最后的db
w = w - a * dw    
b = b - a * db   #依据学习率a,更新w和b

在这个代码里,dw为什么是X乘以dz的倒置,而不是X的倒置乘以dz?首先,谁需要倒置,取决于我们需要哪种结果。因为,对哪个矩阵进行倒置得到的结果并不一样,如果对矩阵进行分块,我们不难发现,矩阵最终一定回归到行向量或者是列向量的形式。

如果是X乘以dz的倒置,可以动笔模拟模拟。dz其实是一个行向量形式的分块矩阵,形状就像Y,A,X一样,如果把他们都看成行向量,那么内积就是一个矩阵,外积就是一个值。在这里,我们需要dw是一个值,因为w的更新需要w加减一个具体的值,所以是X乘以dz的倒置,相反,X的倒置乘以dz是行向量内积,得到的是一个矩阵,不符合需求。

当然,我们实现的是样本集的一次梯度下降,如果需要实现k次梯度下降,我们还是需要一个for循环。但是,本来应该有三个for循环的,通过numpy向量化实现,我们已经节约了两个for循环了。

深度学习中的Python广播:

我们发现,在上面的代码实现有一条这样的代码:

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

不陌生,这是我们求z的公式。这里假设w是n*1向量,X是n*m矩阵,b是一个具体的数,w和X的矩阵乘法之后得到一个1*m的行向量,可是b维度是1*1,直接把他们相加,怎么就不报错呢?

所以,这里Python使用的是广播的手段:broadcasting。

在深度学习中常用的Python广播手段主要在元素四则运算中。举几个例子:

4*1列向量加上1*1实数100得到的结果是什么?结果是:

再举个例子,结果又是什么?

答案是:

Python在元素四则运算的广播,会将两个操作对象的维度通过复制的手段,达到相同,然后再进行四则运算。第一个例子,100会通过向下复制,变成一个元素值都为100的4*1列向量,然后再与左边的列向量进行元素加法。第二个例子,[100 200 300]会通过向下复制,变成:

然后再进行元素加法。

其实,Python广播不止作用于元素加法,元素减法,元素乘法,元素除法都是可以的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值