NeuralNetwork and DeepLearing----Shallow neural networks

摘自黄海广等人笔记

1.**函数(Activation funcions)

使用一个神经网络时,需要确定哪种**函数用在隐藏层上,哪种用在输出节点上。
sigmoid函数:a = σ(z) = 11+ez
更通常的情况下,使用不同的函数g(z[1]),g可以是除了sigmoid函数以外的非线性函数。tanh函数(双曲线正切函数)是总体上都优于sigmoid函数的**函数。
a = tanh(z) = ezezez+ez
事实上,tanh函数是sigmoid的向下平移和伸缩后的结果。对它进行了变形后,穿过了(0,0)点,并且值域介于-1 ~ 1之间。
结果表明,如果在隐藏层上使用**函数,效果总是优于sigmoid函数。因为函数值在-1到1的**函数,其均值更接近零均值。在训练一个算法模型时,如果使用tanh函数代替sigmoid函数中心化数据,使得数据的平均值更接近0而不是0.5,这会使下一层学习简单一点。
在讨论优化算法时,有一点要说明:基本不再用sigmoid**函数了,tanh函数在所有场合都优于sigmoid函数。
但有一个例外:二分类的问题,对于输出层,因为y的值是0或者1,所以想让y的值介于0和1之间,所以使用sigmoid**函数。所以对于某些例子中,对隐藏层使用tanh函数,输出层使用sigmoid函数。
所以,在不同的神经网络层中,**函数可以不同。为了表示不同的**函数,在不同的层中,使用方括号上标来指出g上标为[1]的**函数,可能会跟g上标为[2]不同。方括号上标[1]代表隐藏层,方括号上标[2]表示输出层。

sigmoid和tanh两者共同的缺点是:
在z特别大或者特别小的情况下,导数的梯度或者函数的斜率会特别小,最后就会接近0,导致降低梯度下降的速度。

在机器学习另外一个很流行的函数是:修正线性单元的函数(ReLU),ReLu函数图像如下图,a = max(0,z),所以,只要z是正值的情况下,导数恒等于1,当z是负值的时候,导数恒等于0。从实际上来说,当使用z的导数时,z=0的导数是没有定义的。但是当编程实现的时候,z的取值刚好等于0.0000000,这个值相当小,所以,在实践中,不需要担心这个值,z是等于0的时候, 假设一个导数是1或者0效果都可以。

这有一些选择**函数的经验法则:
如果输出时0,1值(二分类),则输出层选择sigmoid函数,然后其他的所有单位都选择Relu函数。
这是很多**函数的默认选择,如果在隐藏层上不确定使用哪个**函数,那么通常会使用Relu。有时也会用tanh**函数,但Relu的一个优点是:当z是负值的时候,导数等于0。
这里也有另一个版本的Relu被称为LeakyRelu。
当z是负值时,这个函数的值不等于0,而是轻微的倾斜,如图。
这个函数通常比Relu**函数效果要好,尽管在实际中LeakyRelu使用的并不多。
NeuralNetwork and DeepLearing----Shallow neural networks

两者的优点:

第一,在z的区间变动很大的情况下,**函数的导数或者**函数的斜率都会远大于0,在程序实现就是一个 if-else 语句,而 sigmoid 函数需要进行浮点四则运算,在实践中,使用 ReLu **函数神经网络通常会比使用 sigmoid 或者 tanh **函数学习的更快。
第二, sigmoid 和 tanh 函数的导数在正负饱和区的梯度都会接近于 0,这会造成梯度弥散,而 Relu 和 Leaky ReLu 函数大于 0 部分都为常熟,不会产生梯度弥散现象。 (同时应该注意到的是, Relu 进入负半区的时候,梯度为 0,神经元此时不会训练,产生所谓的稀疏性,而 Leaky ReLu 不会有这问题)

z在Relu的梯度一半都是0,但是有足够的隐藏层使得z值大于0,所以多大多数的训练数据来说学习过程仍然可以很快。
快速概括一下不同的**函数的过程和结论。

函数 总结
sigmoid 除了输出层是一个二分类的问题,基本不用
tanh 几乎适合所有的场合
Relu 最常用的默认函数,如果不确定用哪个**函数,就是用Relu或者LeakyRelu

在选择自己神经网络的**函数时,有一定的直观感受,在深度学习中的经常遇到一个问题: 在编写神经网络的时候, 会有很多选择: 隐藏层单元的个数、 **函数的选择、 初始化权值……这些选择想得到一个对比较好的指导原则是挺困难的。

鉴于以上三个原因,以及在工业界的见闻,提供一种直观的感受,哪一种工业界用的多,哪一种用的少。但是,自己的神经网络的应用,以及其特殊性,是很难提前知道选择哪些效果更好。所以通常的建议是:如果不确定哪一个**函数效果更好,可以把它们都试试,然后在验证集或者发展集上进行评价。然后看哪一种表现的更好,就去使用它。

为自己的神经网络的应用测试这些不同的选择,会在以后检验自己的神经网络或者评估算法的时候,看到不同的效果。如果仅仅遵守使用默认的 ReLu **函数,而不要用其他的激励函数,那就可能在近期或者往后,每次解决问题的时候都使用相同的办法。

2.为什么需要非线性**函数?

理解为什么使用非线性**函数对于神经网络十分关键, 但是没懂,之后放。

注意:
不能再隐藏层用线性**函数,可以用relu或者tanh或者leakyReLu或者其他非线性**函数,唯一可以用线性**函数的通常就是输出层;
在隐藏层用线性**函数非常少见。

3.**函数的导数

在神经网络中使用反向传播的时候,需要计算**函数的斜率或者导数。针对以下是四种**,求其导数如下:
NeuralNetwork and DeepLearing----Shallow neural networks

4.神经网络的梯度下降

你的单隐层神经网络会有W[1]b[1],W[2],b[2]这些参数,还有个nx表示输入特征的个数,n[1]表示隐藏单元个数,n[2]表示输出单元个数。
具体的过程暂时保留,有时间学习再补充。

5.随机初始化

当你训练神网的时候,权重随机初始化很重要,对于逻辑回归,把权重初始化为0是可以的。但对于神网,如果初始化为0,那么梯度下降将不会起作用。

为什么呢?
有两个输入特征, n[0] = 2, 2 个隐藏层单元n[1]就等于 2。 因此与一个隐藏层相关的矩阵,或者说W[1]是 2*2 的矩阵,假设把它初始化为 0 的 2*2 矩阵,b[1]也等于 [00]T,把偏置项b初始化为 0 是合理的,但是把w初始化为 0 就有问题了。 那这个问题如果按照这样初始化的话,你总是会发现a1[1]a2[1]相等,这个**单元和这个**单元就会一样。因为两个隐含单元计算同样的函数,当你做反向传播计算时,这会导致dz1[1]dz2[1]也会一样,对称这些隐含单元会初始化得一样,这样输出的权值也会一模一样,由此W[2]等于[0 0]:
NeuralNetwork and DeepLearing----Shallow neural networks

但是如果你这样初始化这个神经网络,那么这两个隐含单元就会完全一样,因此他们完全对称,也就意味着计算同样的函数,并且肯定的是最终经过每次训练的迭代,这两个隐含单元仍然是同一个函数,令人困惑。 dW会是一个这样的矩阵,每一行有同样的值因此我们做权重更新把权重w[1]w[1] − adW每次迭代后的w[1],第一行等于第二行。

由此可以推导,如果你把权重都初始化为 0,那么由于隐含单元开始计算同一个函数,所有的隐含单元就会对输出单元有同样的影响。一次迭代后同样的表达式结果仍然是相同的,即隐含单元仍是对称的。通过推导,两次、三次、无论多少次迭代,不管你训练网络多长时间,隐含单元仍然计算的是同样的函数。因此这种情况下超过 1 个隐含单元也没什么意义,因为他们计算同样的东西。当然更大的网络,比如你有 3 个特征,还有相当多的隐含单元.

如果你要初始化成 0,由于所有的隐含单元都是对称的,无论你运行梯度下降多久,他们一直计算同样的函数。这没有任何帮助,因为你想要两个不同的隐含单元计算不同的函数,这 个 问 题 的 解决 方 法 就 是 随 机 初 始 化 参 数 。 你 应 该 这 么 做 : 把 w[1]设 为np.random.randn(2,2)(生成高斯分布),通常再乘上一个小的数,比如 0.01,这样把它初始化为很小的随机数。然后b没有这个对称的问题(叫做 symmetry breaking problem(不明白)),所以可以把 b 初始化为 0,因为只要随机初始化W你就有不同的隐含单元计算不同的东西,因此不会有 symmetry breaking 问题了。相似的,对于w[2]你可以随机初始化, w[2]可以初始化为 0。
W[1] = np.random.randn(2,2)* 0.01 ,
b[1] = np.zeros((2,1))
W[2] = np.random.randn(2,2)* 0.01 ,b[2] = 0
你也许会疑惑,这个常数从哪里来,为什么是 0.01,而不是 100 或者 1000。我们通常倾向于初始化为很小的随机数。因为如果你用 tanh 或者 sigmoid **函数,或者说只在输出层有一个 Sigmoid,如果(数值)波动太大,当你计算**值时z[1] = z[1] = W[1]x + b[1]a[1] = σ(z[1]) = g[1](z[1]),如果W很大, z就会很大。 z的一些值a就会很大或者很小,因此这种情况下你很可能停在 tanh/sigmoid 函数的平坦的地方(见图 3.8.2),这些地方梯度很小也就意味着梯度下降会很慢,因此学习也就很慢。

回顾一下:
如果w很大,那么你很可能最终停在(甚至在训练刚刚开始的时候) z很大的值,这会造成 tanh/Sigmoid **函数饱和在龟速的学习上,如果你没有 sigmoid/tanh **函数在你整个的神经网络里,就不成问题。但如果你做二分类并且你的输出单元是 Sigmoid函数,那么你不会想让初始参数太大,因此这就是为什么乘上0 .01 或者其他一些小数是合理的尝试。对于W[2]一样,就是 np.random.randn((1,2)),我猜会是乘以 0.01。

事实上有时有比 0.01 更好的常数,当你训练一个只有一层隐藏层的网络时(这是相对浅的神经网络,没有太多的隐藏层),设为 0.01 可能也可以。但当你训练一个非常非常深的神经网络,你可能会选择一个不同于的常数而不是 0.01。但是无论如何它通常都会是个相对小的数。