神经网络神经元概念部分有需要会单独再讲
激活函数
1. Sigmoid
Sigmoid 非线性激活函数的数学表达式是 σ(z) = ,其图形如图 3.14所示。目前我们知道 Sigmoid 激活函数是将一个实数输入转化到 0~1 之间的输出,具体来说也就是将越大的负数转化到越靠近 0,越大的正数转化到越靠近 1。历史上 Sigmoid 函数频繁地使用,因为其具有良好的解释性。
但是最近几年,Sigmoid 激活函数已经越来越少地被人使用了,主要是因为 Sigmoid 函数有以下两大缺点:
(1) Sigmoid 函数会造成梯度消失。一个非常不好的特点就是 Sigmoid 函数在靠近 1 和 0 的两端时,梯度会几乎变成 0,我们前面讲过梯度下降法通过梯度乘上学习率来更新参数,因此如果梯度接近 0。那么没有任何信息来更新参数,这样就会造成模型不收敛。另外,如果使用 Sigmoid 函数,那么需要在初始化权重的时候也必须非常小心如果初始化的时候权重太大,那么经过激活函数也会导致大多数神经元变得饱和,没有办法更新参数。
(2) Sigmoid 输出不是以 0 为均值,这就会导致经过 Sigmoid 激活函数之后的输出,作为后面一层网络的输入的时候是非 0 均值的,这个时候如果输入进人下一层神经元的时候全是正的,这就会导致梯度全是正的,那么在更新参数的时候永远都是正梯度。怎么理解呢?比如进入下一层神经元的输入是,参数是 w 和 b。那么输出就是 ,这个时候,所以如果是 0均值的数据,那么梯度就会有正有负。但是这个问题并不是太严重,因为一般神经网络在训练的时候都是按 batch (批)进行训练的,这个时候可以在一定程度上缓解这个问题,所以说虽然 0 均值这个问题会产生一些不好的影响,但是总体来讲跟上一个缺点:梯度消失相比还是要好很多。
2. Tanh
Tanh 激活函数是上面 Sigmoid 激活函数的变形,其数学表达为,图形如图 3.15 所示。
它将输入的数据转化到 -1 ~ 1 之间,可以通过图像看出它将输出变成了 0 均值在一定程度上解决了 Sigmoid 函数的第二个问题,但是它仍然存在梯度消失的问题。因此实际上 Tanh 激活函数总是比 Sigmoid 激活函数更好。
3. ReLU
ReLU 激活函数 (Rectifed Linear Unit) 近几年变得越来越流行,它的数学表达式为 ,换句话说,这个激活函数只是简单地将大于 0 的部分保留,将小于0的部分变成0,它的图形如图3.16所示。
下面我们简单来介绍一下 ReLU 激活函数的优缺点 。
ReLU 的优点:
(1) 相比于 Sigmoid 激活函数和 Tanh 激活函数,ReLU 激活函数能够极大地加速随机梯度下降法的收敛速度,这因为它是线性的,且不存在梯度消失的问题。
(2) 相比于 Sigmoid 激活函数和 Tanh 激活函数的复杂计算而言,ReLU 的计算方法更加简单,只需要一个阈值过滤就可以得到结果,不需要进行一大堆复杂的运算。
ReLU 的缺点:
训练的时候很脆弱,比如一个很大的梯度经过 ReLU 激活函数,更新参数之后,会使得这个神经元不会对任何数据有激活现象。如果发生这种情况之后,经过 ReLU 的梯度永远都会是0。也就意味着参数无法再更新了,因为 ReLU 激活函数本质上是一个不可逆的过程,因为它会直接去掉输入小于0的部分。在实际操作中可以通过设置比较小的学习率来避免这个小问题。
4. Leaky ReLU
Leaky ReLU 激活函数是 ReLU 激活函数的变式,主要是为了修复 ReLU 激活函数中训练比较脆弱的这个缺点,不将 x<0 的部分变成 0,而给它一个很小的负的斜率比如 0.01,它的数学形式可以表现为 ,其中a是一个很小的常数,这样就可以使得输入小于0的时候也有一个小的梯度。关于 Leaky ReLU 激活函数的效果,众说纷纭,一些实验证明很好,一些实验证明并不好。
同时也有人提出可以对a进行参数化处理,也就是说可以在网络的训练过程中对a也进行更新,但是否对所有情况都有效,目前也不清楚。
5. Maxout
另外一种激活函数的类型并不是作用在一种输出结果的形式,而是这种 Maxout 的类型,可以发现 ReLU 激活函数只是 Maxout 中的特殊形式。因此 Maxout 既有着 ReLU 激活函数的优点,同时也避免了 ReLU激活函数训练脆弱的缺点。不过,它也有一个缺点,那就是它加倍了模型的参数,导致了模型的存储变大。
通过上面的部分我们简单地介绍了一些激活函数的优缺点,在实际我们使用较多的还是ReLU激活函数,但是需要注意学习率的设定不要太大了;一定不要使用 Sigmoid 激活函数,可以试试 Tanh 激活函数,但是一般它的效果都比 ReLU 和 Maxout 差。最后一点,我们在实际使用中也很少使用混合类型的激活函数,也就是说一般在同一个网络中我们都使用同一种类型的激活函数。
神经网络的结构
神经网络是一个由神经元构成的无环图,换句话说一些神经元的输出会变成另外一些神经元的输入,环是不被允许的,因为这样会造成网络中的无限循环。同时神经网络一般是以层来组织的,最常见的神经网络就是全连接神经网络,其中两个相邻层中每一个层的所有神经元和另外一个层的所有神经元相连,每个层内部的神经元不相连,如图 3.17和图 3.18所示。
在之前的线性模型和 Logistic 回归中,我们已经接触到了 nn.Linear(in,out),它就是在PyTorch 里用来表示一个全连接神经网络层的函数,比如输入层4个节点,输出2个节点,可以用nn.Linear(4,2) 来表示,同时 nn.Linear(in,out,bias=False) 可以不使用偏置,默认是 True。
一般而言,N层神经网络并不会把输入层算进去,因此一个一层的神经网络是指没有隐藏层、只有输人层和输出层的神经网络。Logistic 回归就是一个一层的神经网络。
输出层一般是没有激活函数的,因为输出层通常表示一个类别的得分或者回归的一个实值的目标,所以输出层可以是任意的实数。
模型的表示能力与容量
前面我们通过脑神经结构引出了神经网络的层结构,如果从数学的角度来解释神经网络,那么神经网络就是由网络中的参数决定的函数簇。所谓的函数簇就是一系列的函数,这些函数都是由网络的参数决定的。提出了函数簇之后,我们就想明确这个函数簇的表达能力,也就是我们想知道是否有函数是这个函数簇无法表达的?
这个问题在1989年就被人证明过,拥有至少一个隐藏层的神经网络可以逼近任何的连续函数。如果一个只有一层隐藏层的神经网络就能够逼近任何连续函数,为什么我们还要使用更多层的网络呢?
这个问题可以这么去理解,理论上讲增加的网络层可以看成是一系列恒等变换的网络层,也就是说这些网络层对输入不做任何变换,这样这个深层的网络结构至少能够达到与这个浅层网络相同的效果;同时在实际使用中我们也发现更深层的网络具有更好的表现力,同时有着更好的优化结果。
在实际中,我们可能会发现一个三层的全连接神经网络比一个两层的全连接神经网络表现更好,但是更深的网络结构,比如4层、5层、6层等对全连接神经网络效果提升就不大了。而与此对比的是卷积神经网络的深层结构则会有更好的效果,下一章我们会详细介绍。
知道了多层网络有着比较好的表现能力之后,如何来设置网络的参数呢?比如我们应该设计为几层,每层的节点又应该设计为多少个等。首先需要注意的是如果我们增大网络的层数和每层的节点数,相当于在增大网络的容量,容量的增大意味着网络有着更大的潜在表现能力,可以用图3.19来说明一下不同网络容量训练之后的效果。
上面三张图分别是三个网络模型做二分类得到的结果,每个网络模型都是一个隐藏层,但是每个隐藏层的节点数目不一样,从左到右分别是3个、6个和20个隐藏节点,这三个模型训练之后得到的结果完全不一样,可以看到隐藏节点越多的模型能够表示更加复杂的模型,然而根据我们想要的结果,其实最左边的模型才是最好的,最右边的模型虽然有着更加复杂的形状,但是它忽略了潜在的数据关系,将噪声的干扰放大了,这种效果被称为过拟合(overfittimg)。
从最右边的图片我们可以看出虽然模型成功地将红色的点和绿色的点完全分开但是却付出了很大的代价,将区域分割成了很多分离的区域,最左边的图虽然模型没能将所有的点都完全分开,但是形成了两个大区域,在实际应用中,这样的结果抗噪能力和泛化能力反而更强。
通过上面的讨论,我们知道了如果数据不太复杂,那么容量较小的模型反而有着更好的效果,是不是我们可以用小的模型去处理呢?答案并非如此,首先通过这样的方式没有办法很好地衡量到底多小的模型才算是小的模型,其次小的模型在使用如梯度下降法等训练的时候通常更难。
因为神经网络的损失函数一般是非凸的,容量小的网络更容易陷入局部极小点而达不到最优的效果,同时这些局部最小点的方差特别大,换句话说,也就是每个局部最优点的差异都特别大,所以你在训练网络的时候训练10次可能得到的结果有很大的差异。但是对于容量更大的神经网络,它的局部极小点的方差特别小,也就是说训练多次虽然可能陷人不同的局部极小点,但是它们之间的差异是很小的,这样训练就不会完全依靠随机初始化
所以我们更希望使用大容量的网络去训练模型,同时运用一些方法来控制网络的过拟合,这些方法在之后会详细讲解。