说明:
本系列文章翻译斯坦福大学的课程:Convolutional Neural Networks for Visual Recognition的课程讲义 原文地址:http://cs231n.github.io/。 最好有Python基础(但不是必要的),Python的介绍见该课程的module0。
本节的code见地址:
https://github.com/anthony123/cs231n/tree/master/module1-5(working on)如果在code中发现bug或者有什么不清楚的地方,可以及时给我留言,因为code没有经过很严格的测试。
课程目录:
- 不和大脑科学类比的快速介绍
- 模拟一个神经元
- 生物学动机及连接
- 作为线性分类器的单独神经元
- 经常使用的激活函数
- 神经网络结构
- 层级结构
- 前向反馈计算的例子
- 表征能力
- 层数及每层大小的设置
- 总结
- 附加资源
快速介绍
介绍清楚神经网络,而不与脑科学类比,也是可能的。在线性分类那节课中, 我们介绍了使用公式s=Wx计算不同类别的分数,其中W是一个矩阵,x是包含一张图片所有像素的一个列向量。在CIFAR-10的例子中,x是[3072x1]的列向量,W是一个[10x3072]的矩阵,所以输出的分数是一个10个类别分数的向量。
但是,一个简单的神经网络可能计算s=W2max(0,W1x),其中W1可能是[100x3072]的矩阵,它把图像转变为100维度的中间向量。函数max(0,-)是一个逐元素的非线性函数。对于非线性函数,我们有几种选择(这节课的后面会介绍),但是max函数是一个非常常用的函数,它将低于0的数值置为0。最后,W2的大小为[10x100]。所以,最终我们可以得到10个对应于类别分数的数字。我们可以注意到,非线性是计算的主要花销。如果没有非线性计算,那么就剩下简单的线性计算,计算的函数也变成输入向量的线性函数。非线性函数对最终的结果非常重要。参数W2,W1都是通过随机梯度下降学习到的,它们的梯度值都是通过链式法则获得的(使用反向传播计算)。
一个三层的网络可以看成是s=W3max(0,W2max(0,W1x)), 其中,所有的参数W3,W2和W1都是需要学习的参数。中间隐藏向量的大小是网络的超参数,后面我们会讲到如何设置它们。现在我们从神经元/网络的视角来解释这些计算。
模拟一个神经元
神经网络领域起源于对神经生物系统的模拟,后来发现这种结构在机器学习的任务中可以达到很好的结果。我们首先介绍对这个领域有启发的神经生物系统。
生物学动机及其连接
大脑基本的计算单元是神经元。在人类的神经元中,大概有860亿个神经元。这些神经元由大概10^14 ~ 10^15个突触连接。下面的图显示了一个生物的神经元(上)及其数学模型(下)。每个神经元从树突接收信号,并沿着轴突产生输出信号。轴突最终产生分支,并通过与其他神经元树突的突触进行连接。在神经元的计算模型中,沿着轴突传播的信号(x0)与另外一个神经元树突的突触量(w0)进行乘法操作(w0*x0)。其中主要的思想在于突触量(权重W)是可学习的,并且可以控制这个神经元对另外一个神经元的影响力度(及方向:兴奋(正权重)和抑制(负权重))。在基本的模型中,树突将信号放入细胞体中,并在那里进行求和操作。如果最终的和值超过一个阈值,那么神经元就会唤醒,并沿着轴突传送神经冲动。在计算模型中,我们假设神经冲动的时间点不是非常重要,重要的是神经冲动的频率。基于这种频率编码的理论,我们把神经元的激活频率模拟成一个激活函数f, 可以用来表示沿轴突传播的神经冲动的频率。过去,一般选用sigmoid函数σ作为激活函数,因为它将一个实数输入(信号求和之后)转变为一个0到1的数值。在这节课的后面我们会讲解不同的激活函数。
一个单独神经元的前向传导的示例代码如下:
class Neuron(object):
# ...
def forward(inputs):
"""assume inputs and weights are 1-D numpy arrays
and bias is an number """
cell_body_sum = np.sum(inputs * self.weights) + self.bias
firing_rate = 1.0 / (1.0 + math.exp(-cell_body_sum))
# sigmoid activation function
return firing_rate
也就是说,每个神经元先对输入及权重进行点乘操作,并加入偏置值,最后应用非线性函数(激活函数), 在上面情况下,激活函数为:
粗糙的模型
需要强调的是, 这个生物神经元的模型非常粗糙。比如,真实的神经元有不同的种类,而且不同的神经元有不同的性质。生物神经元的树突计算是非常复杂的非线性计算。突触也不是一个单独的权重,而是一个复杂的非线性动态系统。很多神经系统产生神经冲动的准确时机非常重要,所以频率编码并不适用。由于这些及其他的简化,所以如果你将神经网路与真实的脑神经类比的例子讲给一个具有神经科学背景的人,那么你要做好准备接受他们的嘲笑。
单个神经元作为线性分类器
模拟神经元前向计算的数据公式是不是有些熟悉?在线性分类的讲解中,我们知道一个神经元可以“喜欢”(在1附近激活)或者“不喜欢”(在0处激活)输入空间的某些线性分类。所以,在神经元的输出使用一个合适的损失函数,我们可以将一个神经元转化成一个线性分类器。
二元softmax分类器 我们可以将
解释为其中一种类别的概率 P(yi=1|xi;w)。那么,另外一种类别的概率为P(yi=0|xi;w) = 1 – P(yi=1|xi;w),因为两种类别的概率之和为1。利用这种解释,我们可以构建我们熟悉的交叉熵损失,优化它便能变成二元softmax分类器(也称之为逻辑回归)。因为sigmoid函数将输出限制在0~1,所以对这个分类器的预测就是查看概率是否大于0.5。
二元SVM分类器 我们也可以将最大边缘的铰链损失放在神经元的输出后面,并且把它训练成一个二元SVM分类器。
正则解释 在生物学的视角下, 在SVM/Softmax情况下的正则损失可以解释为渐进遗忘,因为它使得每个参数更新之后,突触权重w趋向于0。
一个单独的神经元可以用来实现一个二元分类器(比如 二元Softmax 或者二元SVM分类器)
经常使用的激活函数
每一个激活函数(非线性函数)接收一个输入数值,并对这个值进行某种固定的数学操作。在实践中,你可能会遇到几种不同的激活函数:
Sigmoid函数 Sigmoid非线性的数学公式为
其图像如上图所示。它接收一个实数,并把它映射到0~1范围内。特别地,大的正数变成1,大的负数变成0。Sigmoid在过去用的非常多,因为它很好的解释了神经元的激活频率。从一点都没有激活(0)到完全激活(1)。但是,现在Sigmoid函数用的很少,因为它有两个缺点:
Sigmoid饱和容易使梯度消失。 Sigmoid的一个不好的特点在于当神经元的激活值为0或者1的附近,它的梯度就接近0。在反向传播过程中,局部梯度将与这个门的输出梯度相乘,所以如果局部梯度接近于0,那么就会使得整个梯度都变成0,从而使得权值不会发生改变。而且,我们也需要注意,不能使得权值的初始值过大。比如,如果初始的权重过大,那么大多数的神经元梯度都会接近0,那么整个网络就不会学习。
Sigmoid函数不是以零为中心。 这会导致后续层也会接收到不是以零为中心的数据。这对梯度下降会有不好的影响。因为如果进入神经元的数据总是正数的话(比如,x>0 f=Wx+b),那么反向传播过程中,权重w的梯度变得要么全是正数,要么全是负数(取决于整个f的梯度符号)。这会导致权值更新呈现出之字形。但是一旦跨批量数据相加,权重的最终更新可能会有不同的符号,这样就可以在一定程度上解决这个问题。因此,虽然这会导致一些不方便,但是相对于上面提到的饱和激活问题,它不会产生非常严重的后果。
Tanh Tanh函数如上图右所示。它把实数映射到[-1,1]。像Sigmoid函数那样,它的激活会饱和,但是和Sigmoid函数不一样,它的输出是在[-1,1]范围内。因此,在实践中,Tanh永远比Sigmoid函数好。其实,tanh神经元也是一个Sigmoid的一个变形,因为tanh(x) = 2σ(2x)-1.
ReLU 在最近几年,修正线性单元非常流行。它的计算函数为f(x) = max(0,x)。下面列举一些ReLu的优点和缺点:
(+): 实验发现相比于Sigmoid和Tanh,它能够加快随机梯度下降的聚合。这被认为是由于它的线性,不饱和的形式。
(+): 相比Sigmoid和Tanh,ReLu的计算更加简单。
(-):不幸的是,ReLU在训练的过程中,可能会很脆弱,容易“死亡”。比如,通过ReLU神经元的大的梯度流可能永远变成零。从而会导致部分数据在训练的过程中丢失。如果你在训练的过程中由于设置过高的学习速率,导致多达40%的网络死亡,那么可以通过将学习速率降低来试着解决这个问题。
leaky ReLU 它是一个尝试解决死亡ReLU问题的变形。当x<0时,leaky ReLU有一个小的负斜率(比如:0.01)。函数表达式为f(x) = 1(x<0)(ax) + 1(x >= 0)(x) ,其中a是一个小的常数。有些人报告说在这种形式的激活函数取得了成功,但是这个结果不总是稳定的。斜率a可以放在每个神经元的参数列表中。但是,现在跨任务的一致性还不是很清楚。
maxout 另外一种比较流行的选择是maxout,它是ReLU和leaky ReLU的通用版。maxout神经元计算下面这个函数
我们可以发现,ReLU和Leaky ReLU 是这个函数的特殊情况(比如: 当w1,b1=0,就变成了ReLU)。所以maxout可以享受到ReLU所有的好处,而且没有ReLU的缺点(ReLU死亡)。然而,不好的一点在于,它也使得参数的个数翻倍。
关于常见的神经元及其激活函数的讨论也就结束了。最后补充一点,在一个神经网络中,使用不同类型的神经元的做法非常少见,尽管这样做也并没有什么问题。
总结: “我该使用什么类型的神经元呢?” 使用ReLU,但是要小心设置学习速率并注意网络的死亡率。如果出现了这种情况,你可以试着使用leaky ReLU或者Maxout。永远不要使用Sigmoid.。使用Tanh,但是不要期望它会表现的比ReLU或Maxout好。
神经网络架构
层级组织结构
作为图结构中神经元的神经网络 神经元可以看成是由无环图连接的神经元的集合。也就是说,一些神经元的输出是另外一些神经元的输入。环形图是不允许的,因为这会导致网络前向传播的无限循环。这些连接起来的神经元并不是无定形的,而是被组织成层级结构。对于普通的神经网络来说,最常见的层级类型就是全连接层,即两个相邻的层之间两两都有连接,但是在一个层内,神经元之间没有连接。下面是两个全连接层网络的例子:
命名规范 注意,当我们计算神经网络的层数时,我们并不包括输入层。因此,一层的神经网络没有隐藏层(输入直接映射到输出)。在那种情况下,我们可能会听到有人称逻辑回归或者SVM是单层的神经网络。你也有时候听到别人把神经网络称之为人工神经网络(Artificial Neural Network)或者 多层感知网络(Multi-Layer Perceptrons(MLP))。很多不喜欢神经网络与真实脑相似论断的人倾向于把神经元称之为单元。
输出层 不像神经网络其他层那样,输出层的神经元不包括激活函数。(或者你可以认为激活函数是单位激活函数(identity activation function))。这是因为最后的输出函数经常用来表示类别分数(分类问题中),其中的数值为实数 ,或者是实数的目标(在回归中)。
确定神经网络的大小 两个经常使用来测量神经网络大小的度量是神经元的个数和参数的多少(更常见)。以上图的两个神经网络为例
- 第一个网络(上)有4+2个神经元(不包括输入神经元),[3x4] + [4x2] = 20 个权重和4+2个偏置量,一共有26个可学习的参数。
- 第二个网络(下)有4+4+1=9个神经元,[3x4]+[4x4]+[4x1]=32个权重和4+4+1=9个权重,一共有41个可学习的参数。
现代的卷积神经网络包括1亿个量级的参数,通常包括10-20层(所以称之为深度学习)。然而,之后我们会学习到,由于参数共享,有效的连接数会远远大于这个量级。具体内容我们会在卷积神经网络那节涉及。
前馈计算的例子
矩阵乘法与激活函数交替出现并重复 神经网络被组织成层级结构的一个最基本的理由是这种结构使得利用矩阵向量操作来计算神经网络变得简单和有效。以上面的三层神经网络为例,输入是[3x1]的向量。一层的所有连接强度可以存储在一个矩阵里面。比如,第一个隐藏层权重矩阵W1的大小是[4x3], 所有单元的偏置值在大小为[4x1]的向量b1中。每一个单独的神经元将它的权重放在W1的某一行中。所以矩阵向量乘法np.dot(W1,x)计算这层所有神经元的激活值。类似地,W2是一个[4x4]的矩阵,它存储着第二个隐藏层的所有连接,W3是最后一层(输出层)的一个[1x4]的矩阵。这个三层神经网络的整个前向传播是简单的三个矩阵乘法,但是和激活函数交替出现。
#一个三层神经网络的前向传播
f = lambda x: 1.0/(1.0+np.exp(-x)) #激活函数
x = np.random.randn(3,1) #随机输入向量
h1 = f(np.dot(W1, x) + b1) #计算第一个隐藏层的激活值
h2 = f(np.dot(W2, h1) + b2) #计算第二个隐藏层的激活值
out = np.dot(W3, h2) + b3 #输出神经元
在上面的代码中,W1,W2,W3,b1,b2,b3都是神经网络可学习的参数。注意输入可能不是一个单独的向量矩阵,x也可能是训练数据的整个批量(其中,可能每一列是一个输入图像),所以单个输入都可以并发地计算。
一个全连接层的前向传播对应于一个矩阵乘法,再加上一个偏置向量和激活函数计算
表征能力
一种观察全连接神经网络的视角是,把它看做是一个以权重为参数的函数群。一个自然的问题就产生了: 这个函数群的表征能力有多大?也就是说,是否存在不能用神经网络模拟的函数?结果发现至少一个隐藏层的神经网络是一个万能逼近器(universal approximator)。也就是说,给出一个连续函数f(x)和一个ϵ<0,存在一个有一个隐藏层(有一个非线性函数,比如:sigmoid)的神经网络g(x), 使得∀x, ∣ f (x) − g(x) ∣< ϵ。换句话说,神经网络可以逼近任何连续函数。
如果一个隐藏层足以逼近任何函数,那么为什么还要使用更多的层呢?答案是神经网络是在数学上,是一个万能逼近器,但是在实践中,这是一个相对没有意义的陈述。一方面,指示碰撞和(sum of indicator bumps)函数g(x) = Σi ci𝟙(ai < x < bi), 其中参数向量a,b,c也是一个万能逼近器,没有人会建议我们在机器学习中使用这种函数形式。神经网络在实践中效果好,就是因为它简洁表达的,平整的函数能够非常容易地满足我们在实践中遇到数据的统计学特征,也非常容易使用优化算法学习(比如,梯度下降)。类似地,实践发现,更深的网络(多个隐藏层)比只有一个隐藏层的网络工作地更好,尽管它们的表达能力一样。
除此之外,实践发现,一个三层的网络比一个两层的网络效果更好,但是更深的网络并不会使得效果更好。这和卷积神经网络不同。实践发现深度是一个优秀的认知系统的关键。
设置层的数目及它们的大小
当我们面临一个实践问题时,我们怎么决定使用何种架构?我们应该不使用隐藏层,使用一个隐藏层?两个隐藏层?每一层的大小为多大?首先,当我们提高神经网络的大小及层数,网络的容量也会增加。也就是说,函数可表征的空间也会增加,因为神经元可以相互合作,表征出许多不同的函数。例如,假设我们有一个二维空间上的一个二元分类问题。我们可以训练三个不同的神经网络,每个网络的隐藏层数不一样。我们可以得到以下的分类器:
从上面的图片我们可以看出,更多神经元的神经网络能够表征出更复杂的函数。然而,这既是一个优点(我们可以处理更加复杂的数据)也是一个缺点(容易过拟合)。当一个高容量的模型拟合数据中的噪音而不是数据内在的关系时,容易产生过拟合。例如,上图中20个隐藏层的模型能过拟合所有的数据,但是也产生了一些分离的区域。三个隐藏层的模型只有初略分类数据的表征能力。它把数据分为两个部分,将绿色区域的红色点解释为杂质。在实践中,这可以提高在测试数据中的延展性。
基于我们上面的讨论,为了防止过拟合,对于不够复杂的数据,似乎我们应该使用更小的神经网络。但是,这是错误的。在神经网络中,我们还有很多其他的方法来阻止过拟合。在后续的课程中我们会讲到(例如 L2正则化,dropout,输入噪音等)。在实践中,我们经常使用这些方法来避免过拟合,而不是通过减少神经元的数目。
其中背后的原因在于,小的神经网络很难使用局部方法(如梯度下降)进行训练。损失函数只有相对较少的局部极小值,而且很多极小值都非常容易聚合,但是结果并不好(有较高的损失值)。相反,更大的神经网络拥有更多的局部极小值,而且这些极小值比实际的极小值更好。因为神经网络是非凸函数,所以很难从数学的角度来分析。但是还是有一些理解这些目标函数的尝试。例如 这篇论文: The Loss Surfaces of Multilayer Networks. 在实践中,你会发现,一个小的神经网络的损失值会有很大的变数。在有的情况下,你很幸运,它能聚合到一个好的地方,但是在一些情况下,你可能只能聚合到一个不好的地方。但是,如果你训练一个大的网络,你需要发现许多不同的解决方案,但是最终的损失的变量都会更小。也就是说,所有的解决方法都是一样好,从而使得最终的结果更少地依赖随机初始化。
正则强度是一个控制神经网络过拟合的好的方案。我们来看一下不同设置所达到的结果:
所以你不应该由于害怕过拟合而使用更小的网络。相反,你应该使用一个你能承受的计算量的大型网络,并且使用正则技术来控制过拟合。
总结
- 我们介绍了生物神经元的及其非常粗糙的一个模型。
- 我们讨论了在实践中使用的几种激活函数。其中ReLU是最常用的激活函数。
- 我们介绍了神经网络, 其中神经元由全连接层连接。相邻层的神经元两两连接,而每一层内的神经元则没有连接。
- 这种层级结构使得基于矩阵乘法与激活函数交替运算的方式计算网络参数更加有效
- 我们可以把神经网络看成是一个万能函数逼近器,但是我们也讨论到这种性质并不能直接导致它的广泛使用。神经网络之所有被使用,是因为它们基于正确的,来源于实践的,关于函数的功能性的假设。
- 我们发现更大的网络几乎都比更小的网络效果更好,但是更好的网络需要更强的正则项,否则,会导致过拟合。我们会在后面的课程中介绍更多的正则项的形式(特别是dropout)。