code小技巧:在编写代码时,要格外注意参数的储存形式,不同的算法对于其的要求不一样,建议在调用算法的函数时输出参数的格式来确保是其要求的向量或矩阵形式。如果原来是矩阵,可以通过拆开值并按顺序传入的形式改为向量,这样如果后面有重新转成矩阵的需求时可以保证参数里各项的值都跟原来的一样。
梯度检测(Gradient checking)
实际上,在我们在代码里通过神经网络模型时,在编写反向传播算法的代码时很容易遇到很多bug。当它跟梯度下降或其他算法一起工作时,尽管看起来程序可以正常运行,代价函数的值在每次迭代后也在不断减小,最后得到的神经网络的误差会比没有bug的误差高出一个量级,而我们很可能是看不出来这是因为bug导致而去检查其他地方或者干脆直接使用这个错误的误差。为了应对这种情况,我们提出了梯度检测。
假设是实数,代价函数的图像如下所示:
参数为实数时求其导数近似值
如图,我们在代价函数上取横坐标为的两个点,将其连接起来,根据斜率公式我们可以得到两点间斜率表达式为:,当分母很小时(一般取),其值近似于代价函数上横坐标为点的斜率。下面我们来看更普遍的情况,假如是一个n维向量:
代价函数对于每一个参数求偏导
我们可以看到,如果有n个参数需要我们求偏导,我们可以用上述类似的方式,将其转换为用极限求斜率的方式来求,只是这里为了不让数值出现问题我们采用。假如我们用这种方式算出来的偏导数近似于神经网络用反向传播求出来的,我们就可以确定代码在实现反向传播的过程中没有出现bug。注意,在验证过反向传播的结果没有问题之后,在进行梯度下降前要确保其内循环代码里的梯度检验代码块被注释或者禁用,不然每次梯度迭代一次都运行一次梯度检验,其庞大的计算会让你的代码运行时间变得非常久。
随机初始化(Random initialization)
在之前的逻辑回归的分类问题中,我们总是将参数初始化为n维的0向量,但事实上如果你在神经网络中也这么做,它将起不到任何作用。
之所以这么说,是因为在神经网络中,参数代表的是该层神经元的权重,输入层的参数都是0向量,代表权重一样,那么求得的隐藏层对应的神经元互相之间也都一样,也就意味着其在每一次梯度下降中,代价函数对该参数的的偏导都是一样的,这代表每个隐藏层的神经元所计算的特征都是一样的,这与其计算不同特征的目的是矛盾的,这样计算后在输出层就只能得到一个特征,基于此,我们会把神经网络的参数进行随机初始化,如下:
随机初始化参数
如图,对于输入层的每个参数,我们在的范围内随机取值,保证输入不同的特征后,其在下一层的输出函数是不一样的,避免了对称权重(即输入层的每个参数都一样)情况的出现。
现在,我们已经学习了神经网络的输入,架构,过程算法,输出以及如何优化,让我们把这些结合到一起,看看完整地使用神经网络来解决问题是怎样的一个流程。我们将其分为六个步骤。
在这之前,我们先确定输入的特征量,并选择神经网络的架构。通常来说,我们只会设置一个隐藏层,其中隐藏层的神经元的个数越多越好,不过要注意随着其个数的增多,计算量也会随之增大;如果有多个隐藏层,要保证你每个隐藏层的神经元的个数相等。接着我们来确定输出层的个数。假设是二元分类问题,输出层只有一个且值为0或1;如果是多元分类问题,则输出至少是3个,且输出的形式为向量。然后我们就可以开始解决问题了:
- 对权重进行随机初始化的操作,将其设置为很接近于0的随机数;
- 执行前向传播算法,得到每个输入的输出;
- 编写代码计算代价函数;
- 执行反向传播算法,算出代价函数对于每个参数的偏导数;
- 执行梯度检验,对比反向传播数值计算得到的偏导数,确保两种方法得到的值基本相同,误差在两位小数以内,确定无误后关闭代码里的梯度检验功能;
- 使用梯度下降或更高级的优化算法(像共轭梯度法,LBFGS等)来最小化代价函数的值;
- 编写代码检验训练效果。
学习内容来自于b站吴恩达大佬课程:https://www.bilibili.com/video/BV1By4y1J7A5?spm_id_from=333.788.player.switch&vd_source=867b8ecbd62561f6cb9b4a83a368f691&p=1