1.神经网络的表示(Neural Network Representaiton)
这就是一张神经网络的图片。我们输入特征x1,x2,x3,他们被竖直的堆叠起来,这就叫做神经网络的输入层,它主要包含了神经网络的输入;然后我们看输入串旁边二点一层叫做隐藏层,在一个神经网络中,当你使用监督学习训练它的时候,训练集包含了输入x也包含了输出y,所以术语隐藏层的含义是在训练集中,这些中间节点的准确值是我们不知道的,也就是说我们看不见中间节点在训练集中应该具有的值,你可以看到输入的值,也可以看到输出的值,但是隐藏层中的东西,在训练集中你是无法看到的;在本例中最后一层只由一个节点的层被称为输出层,它负责产生预测值。
现在我们再引入几个符号,就像之前用向量x表示输入特征,这里我们用一个替代符号a[0]来表示输入特征。a表示激活的意思,它意味着网络中不同层的值会传递到他们后面的层中,输入层将x传递给隐藏层,所以我们将输入串的激活值称为a[0];下一层隐藏层也同样会产生一些激活值,我们记作a[1],具体的来讲,我们将这里的第一个单元或节点表示成为a1[1],第二个节点的值表示成a2[1]以此类推;最后输出层将产生的某个数值a,他只是一个单独的实数,所以y^值将取值为a[2],在这里我们所看到的这个例子,这只能叫做一个两层的神经网络,原因是当我们计算网络的层数时,输入层是不算总层数内,所以隐藏层作为第一层,输入串是第二层。
最后加上参数,大致就是上图的样子。
2.计算一个神经网络的输出(Computing a Neural Network's output)
我们现在这里讲一下逻辑回归和神经网络的关系:其实逻辑回归就是指只有一个神经元的神经网络!(我回头看的时候才发现自己竟然不知道他们二者之间的区别。)
关于神经网络是怎么计算的,从我们之前提及的逻辑回归开始,如下图所示。用圆圈表示神经网络的计算单元,逻辑回归的计算有两个步骤,首先你按步骤计算出z,然后在第二部中你以sigmoid函数作为激活函数计算得到z(得出a),一个神经网络只是这样子做了好多次重复计算。
回到两层的神经网络,我们从隐藏层的第一个神经元开始计算,如上图第一个最上面的箭头所指。从上图可以看出,输入与逻辑回归相似,这个神经元的计算与逻辑回归一样分为两步,小圆圈代表了计算的两个步骤。
第一步:计算z1[1],z1[1]=w1[1]Tx_b1[1];
第二步,通过激活函数计算a1[1],a1=sigmoid(z1[1]).
隐藏层的第二个以及后面的两个神经元的计算过程一样,只是注意符号表示不同,最终分别得到a2[1],a3[1],a4[1]。
同样的,当我们执行神经网络的程序,用for循环来完成的话会很低效,所以很自然的,我们直接会想到用向量化的方法来实现,接下来我们就是要把这四个等式向量化。向量化的过程是将神经网络中的一层神经元参数纵向推积起来,例如隐藏层中的w纵向推积起来变成一个(4,3)的矩阵,用符号W[1]表示。
这种方法就是我们可以根据给出的一个单独的输入特征向量,计算出一个简单神经网络的输出,那么如何一次计算出不止一个样本的神经网络输出,而是一次性计算整个训练集的输出呢?多样本向量化给我们提供了很好的方法。
3.多样本向量化(Vectoring across multiple examples)
其实是多样本向量化这个过程和逻辑回归中所做的很类似。逻辑回归是将各个训练样本组合成矩阵,对矩阵的各列进行计算;而神经网络是通过对逻辑回归中的等式简单的变形,让2神经网络计算出输出值,这种计算是所有训练样本同时进行的,下图是实现它具体的步骤:
对于一个给定的输入特征向量X,上面的四个等式可以计算出a[2]等于y^。这时针对于单一的训练样本。如果有m个训练样本,那么就需要重复这个过程,即用第一个训练样本x[1]来计算出预测值y^[1],就是第一个训练样本上得出的结果,然后用x[2]来计算出预测值y^[2];循环往复,直至x[m]计算得出y^[m]。上面的表示如果用激活函数来表示的话就是a[2](1)、a[2](2)和a[2](m)。
如果我们用一个非向量化形式来实现,对于所有的训练样本,需要让i从1到m来实现下面这四个等式:
对于上面的这个方程中的(i),是所有依赖于训练样本的变量,即将(i)添加到x,z和a。如果想计算m个训练样本 上的所有输出,就应该向量化整个计算,来简化这列。
下面我们看具体用向量化的方法来实现上面算法:
由这些公式我们可以很明显的看出,使用向量化的方法,可以代替显示循环。
4.激活函数(Activation functions)
在我们使用神经网络时,需要决定使用哪种激活函数用在隐藏层上,哪种用在输出节点上,所以选择很好的激活函数时很有必要的。
在神经网络的前向传播中,a[1]=sigmmoid(z[1])和a[2]=sigmmoid(z[2])这两步会用到sigmoid函数,公式是:。但是在一般情况下,使用其他的激活函数,例如tanh函数或双曲正切函数都在总体上优于sigmoid函数。
a=tanh(z)的值域在+1和-1之间,公式是:
实验结果表明,如果在隐藏层上使用tanh激活函数效果总是优于sigmoid函数,因为函数值域在+1和-1之间,其均值是更接近零均值的。在训练一个算法模型时,如果使用tanh函数代替sigmoid函数中心化数据,使得数据的平均值更接近0而不是0.5,但是对于二分类问题,对于输出层,因为y的值是0或1,所以我们想让y^的值介于0和1之间,而不是+1和-1之间。所以在输出层需要使用sigmoid函数,而隐藏层依然使用tanh函数。在这里,我们发现,对于不同的网络世界层,激活函数是可以不同的,为了表示不同的激活函数,在不同的层,使用方括号上标来指出g上标为[1]的激活函数,可能会和g上标为[2]不同。方括号上标[1]代表隐藏层,方括号上标[2]代表输出层。与此同时,sigmoid函数和tanh函数两者都有一个共同的缺点:在z特别大或特别小的情况下,导数的梯度或者函数的斜率会特别的小,最后就会直接接近于0,导致降低梯度下降的速度。
在机器学习另外一个很流行的函数是:修真线性单元函数(ReLu),ReLu函数的公式是:a=max(0,z)所以,只要z是真值的情况下,导数恒等于1,当z是负值的时候,导数恒等于0.
如果输出是0、1值(二分类问题),则输出层选择sigmoid函数,然后其他的所有单元都选择ReLu函数。这是很多激活函数的默认选择,如果在隐藏层上不确定使用哪个激活函数 ,我们通常使用ReLu激活函数。有时候,也会选择tanh激活函数,但ReLu的一个优点是:当z是负值的时候,导数等于0。还有另一个ReLu的版本被称为Leaky ReLu,当z是负值的时候,这个函数的值不是等于0,而是轻微的倾斜,虽然这个激活函数比ReLU激活函数的效果要好,但是在实际中我们很少使用这个Leaky ReLu激活函数。
上面介绍的四种激活函数,下面就是它们的图像:
概括:
sigmoid函数:适用于输出层是一个二分类问题;
tanh函数:适用于所有场合;
ReLu函数:最常用的默认函数 ,如果你不确定使用哪个激活函数就使用ReLu或者Leaky ReLu。
此外,自己的神经网络的应用以及特殊性,很难提前选择哪些效果更好,所有建议就是如果不确定哪一个激活函数效果更好,可以把他们都试一试,然后在验证集进行评价,看哪一种的表现更好,就是用哪个激活函数。
以上我们看到的都是非线性激活函数,那么为什么我们需要非线性激活函数呢?
事实证明:如果你使用线性激活函数或者没有使用激活函数,那么无论你的神经网络有多少层,你一直在做的也只是计算线性函数,所以不如直接去掉全部的隐藏层。总而言之,不能在隐藏层用线性激活函数,必须使用非线性激活函数,唯一可以使用线性激活函数的就是输出层。
5.激活函数的导数和神经网络的梯度下降(Derivatives of activation functions and Gradient descent for neural networks)
在神经网络中使用反向传播的时候,我们需要计算激活函数的斜率或者导数。对于上面我们提及的四种激活函数,求其导数如下:
1).sigmoid activation function
2).Tanh activation function
3).Recified Linear Unit(ReLu)
4).Leaky linear unit(Leaky ReLu)
神经网络的梯度下降算法也就是反向传播算法,下面我们展示为什么这几个特定的方程是针对我们的神经网络实现梯度下降的正确方程。
我们的单隐层神经网络会有W[1],b[1],W[2],b[2]这些参数,还有nx个表示输入特征的个数,n[1]表示隐藏单元个数,n[2]表示输出单元个数。
那么参数:矩阵W[1]的维度就是(n[1],n[0]), b[1]就是n[1]维向量,可以写成(n[1],1),就是一个列向量。矩阵W[2]的维度就是(n[2],n[1]),b[2]的维度就是([n2],1)维度。
我们还有一个神经网络的成本函数,假设我们在做二分类任务,那么成本函数等于:
训练参数需要做梯度下降,在训练神经网络的时候,随机初始化参数很重要,而不是初始化全零。当你参数初始化某些值后,每次梯度下降都会循环计算一下预测值:
dW[1]=dJ/dW[1],db[1]=dJ/db[1] ; dW[2]=dJ/dW[2],db[2]=dJ/db[2]
W[1]=W[1]-adW[1],b[1]=b[1]-adb[1]; W[2]=W[2]-adw[2],b[2]=b[2]-adb[2]
正向传播(forward propagation)方程如下:
Z[1]=W[1]X+b[1] ,A[1]=g(Z[1]),Z[2]=W[2]A[1]+b[2], A[2]=g(Z[2])
反向传播(back propagation)方程如下:
以上就是反向传播的步骤,这些都是针对所有样本进行过向量化,Y是1*m的矩阵;这里的np.sum是numpy命令,axis=1表示水平相加求和,keepims是为了确保矩阵db[2]这个训练输出的维度为(n,1)这样标准的形式。
以上就是正向传播的4个方程和反向传播的6个方程,如果我们想要实现深度学习的这些算法,我们必须正确执行正向和反向传播运算,必须能计算所有需要的导数,用梯度下降来学习神经网络的参数。
6.随机初始化(Random+Initialization)
当你训练神经网络的时候,权重随机初始化是非常重要的。对于逻辑回归,把权重初始化为0是可以的,但是对于一个神经网络来讲,如果把权重或参数都初始化为0,那么梯度下降将不会起作用。
假设有两个输入特征,n[0]=2,2个隐藏层单元,n[1]就等于2。因此与一个隐藏层相关的矩阵,即W[1]是2*2的矩阵,假设此时我们把它初始化为0的2*2的矩阵,那么b[1]也等于[0 0]T,把偏置项b初始化0是合理的,但是把w初始化为0就有问题了。如果这个问题按照这样初始化的话,我们会发现a1[1]和a[2]相等,这两个激活单元相同,那么也会导致dz[1]和dz[2]也一样,对称这些隐含单元会初始化得一样,这样输出的权值也会一模一样,由此W[2]等于[0 0].具体情况如下图所示。
由此可以推导,如果你把权重都初始化为0,那么由于隐含单元开始计算同一个函数,所有的隐含单元就睡对输出单元有同样的影响。一次迭代后同样的表达式结果仍然是相同的,即隐含单元仍是对称的。通过推导,两次,三次,无论多少次迭代,不管你训练时间多长,隐含单元仍然计算的是同样的函数,因此这种情况下超过一个隐含单元也没什么意义,因为他们计算同样的东西。
这个问题的解决方法就是随机初始化参数:把W[1]设为np.random.randn(2,2)(生成高斯分布),通常再乘上一个小的数,比如0.01,这样把它初始化为很小的随机数。然后b没有对称的问题,所以可以直接把b初始化为0,因为只要随机初始化W就会产生不同的隐含单元,可以计算不同的东西,就不会产生对称的问题(叫做symmetry breaking)。同样的,对于W[2]我们也可以随机初始化,b[2]初始化为0,公式如下: