目前所有神经网络和支撑向量机的基础都是1957年提出来的感知机。感知机(perceptron) 是二类分类的线性分类模型, 其输入为实例的特征向量, 输出为实例的类别, 取+1和–1二值。 感知机对应于输入空间(特征空间) 中将实例划分为正负两类的分离超平面, 属于判别模型。但是感知机却不能分来异或这种简单的布尔运算。后来是加入了激活函数才将感知机给拯救了回来,也才有了现在的各种神经网络。

        激活函数(activation function)运行时激活神经网络中某一部分神经元,将激活信息向后传入下一层的神经网络。神经网络之所以能解决非线性问题,本质上就是激活函数加入了非线性因素,弥补了线性模型的表达力,把“激活的神经元的特征”通过函数保留并映射到下一层。

        因为神经网络的数学基础是处处可微的,所以选取的激活函数要能保证数据输入与输出也是可微的。那么激活函数在 TensorFlow 中是如何表达的呢?激活函数不会更改输入数据的维度,也就是输入和输出的维度是相同的。 TensorFlow 中包括平滑非线性的激活函数,如 sigmoid、 tanh、 elu、 softplus 和 softsign,也包括连续但不是处处可微的函数 relu、 relu6、 crelu 和 relu_x,以及随机正则化函数 dropout。

        上述激活函数的输入均为要计算的 x(一个张量),输出均为与 x 数据类型相同的张量。常见的激活函数有 sigmoid、 tanh、 relu 和 softplus 这 4 种。下面我们就来逐一讲解。

(1) sigmoid 函数

        这是传统神经网络中最常用的激活函数之一(另一个是 tanh),对应的公式和图像如下图所示。

卷积神经网络的激活函数_sigmoid 

        使用方法如下:

import tensorflow as tf

a = tf.constant([[1.0, 2.0], [1.0, 2.0], [1.0, 2.0]])
sess = tf.Session()
print(sess.run(tf.sigmoid(a)))

        结果如下:

[[0.7310586 0.880797 ]
[0.7310586 0.880797 ]
[0.7310586 0.880797 ]]

软饱和性,一旦输入落入饱和区, f'(x)就会变得接近于 0,很容易产生梯度消失

软饱和是指激活函数 h(x)在取值趋于无穷大时,它的一阶导数趋于 0。硬饱和是指当|x| > c 时,其中 c 为常数,f '(x)=0。relu 就是一类左侧硬饱和激活函数。

梯度消失是指在更新模型参数时采用链式求导法则反向求导,越往前梯度越小。最终的结果是到达一定深度后梯度对模型的更新就没有任何贡献了。

(2) tanh 函数

        对应的公式和图像如下图所示。

卷积神经网络的激活函数_激活函数_02

        tanh 函数也具有软饱和性。因为它的输出以 0 为中心,收敛速度比 sigmoid 要快。但是仍无法解决梯度消失的问题。

 (3) relu 函数是目前最受欢迎的激活函数

        ReLU函数虽然不是Alex首次提出来的,但是当AlexNet在2012届图像识别大赛取得冠军才使其被大众所熟知。softplus可以看作是 ReLU的平滑版本。 relu定义为 f(x)=max(x,0)。softplus 定义为 f(x)=log(1+exp(x))。

卷积神经网络的激活函数_激活函数_03

        由上图可见, relu 在 x<0 时硬饱和。由于 x>0 时导数为 1,所以, relu 能够在 x>0 时保持梯度不衰减,从而缓解梯度消失问题,还能够更很地收敛,并提供了神经网络的稀疏表达能力。但是,随着训练的进行,部分输入会落到硬饱和区,导致对应的权重无法更新,称为“神经元死亡”。

a = tf.constant([-1.0, 2.0])
with tf.Session() as sess:
b = tf.nn.relu(a)
print(sess.run(b))

        结果:

[0. 2.]

(4) dropout 函数。

        一个神经元将以概率 keep_prob 决定是否被抑制。如果被抑制,该神经元的输出就为 0;如果不被抑制,那么该神经元的输出值将被放大到原来的 1/keep_prob 倍。

        在默认情况下,每个神经元是否被抑制是相互独立的。但是否被抑制也可以通过 noise_shape 来调节。当 noise_shape[i] == shape(x)[i]时, x 中的元素是相互独立的。如果 shape(x) = [k, l, m, n],x 中的维度的顺序分别为批、行、列和通道,如果 noise_shape = [k, 1, 1, n],那么每个批和通道都是相互独立的,但是每行和每列的数据都是关联的,也就是说,要不都为 0,要不都还是原来的值。使用示例如下:
 

a = tf.constant([[-1.0, 2.0, 3.0, 4.0]])
print(a)
with tf.Session() as sess:
b1 = tf.nn.dropout(a, 0.5, noise_shape = [1,4])
print(sess.run(b1))
b1 = tf.nn.dropout(a, 0.5, noise_shape = [1,4])
print(sess.run(b1))

b2 = tf.nn.dropout(a, 0.5, noise_shape = [1,1])
print(sess.run(b2))
b2 = tf.nn.dropout(a, 0.5, noise_shape = [1,1])
print(sess.run(b2))

        结果:

Tensor("Const_26:0", shape=(1, 4), dtype=float32)
[[-0. 0. 0. 0.]]
[[-2. 0. 0. 0.]]
[[-2. 4. 6. 8.]]
[[-0. 0. 0. 0.]]

激活函数的选择:

        当输入数据特征相差明显时,用 tanh 的效果会很好,且在循环过程中会不断扩大特征效果并显示出来。当特征相差不明显时, sigmoid 效果比较好。同时,用 sigmoid 和 tanh 作为激活函数时,需要对输入进行规范化,否则激活后的值全部都进入平坦区,隐层的输出会全部趋同,丧失原有的特征表达。而 relu 会好很多,有时可以不需要输入规范化来避免上述情况。因此,现在大部分的卷积神经网络都采用 relu 作为激活函数。

PS:全部摘自李嘉璇的<<TensorFlow 技术解析与实战>>第四章4.7.1节