现在话题重新回到神经网络的理论上来。那么,当神经网络从单隐层继续发展,隐层数目超过一个,就逐渐由“浅”入“深”,进入深度学习的领域(如下图所示)。当然,所谓“深”,只是相对而言的。相较于单神经元的感知器,一两个隐层的神经网络也可以称得上“深”。而大型的深度神经网络经常达到成百上千层,则十几层的网络也显得很“浅”。
浅层神经网络就可以模拟任何函数,但是需要巨大的数据量去训练它。深层神经网络解决了这个问题。相比浅层神经网络,深层神经网络可以用更少的数据量来学到更好的模型。从网络拓扑结构或数学模型上来说,深层神经网络里没有什么神奇的东西,正如费曼形容宇宙时所说:“它并不复杂,只是很多而已。”[2]
从逻辑回归到深度神经网络的演进
全连接层构建起来的神经网络,每层和下一层之间的全部节点是全部连通的,每一层的输出就是下一层的输入特征。
大家注意,因为全连接这种性质,一个深度神经网络可能包含成千上万甚至百万、千万个参数。实际上,深度神经网络就是利用参数的数量来拓展预测空间的。而要找到所有参数的正确取值是一项非常艰巨的任务,某一个参数值的小小改变都将会影响其他所有参数的行为。你们听说过“蝴蝶效应”吧,当南美洲的一只蝴蝶扇动翅膀……算了,简单来说,那就是“牵一发而动全身”。
深度神经网络也是通过损失函数来衡量该输出与预测值之间的差距,并以此作为反馈信号微调权重,以降低损失值。权重、偏置起初都是随机生成的,损失值当然很高。但随着训练和迭代的进行,权重值也在向正确的方向逐步微调,损失值也逐渐降低。为什么损失值会逐渐降低呢?秘密仍然在于梯度下降。在损失函数中对权重和偏置这些自变量做微分,找到正确的变化方向,网络的损失就会越来越小。训练的轮次够了,网络就训练好了。
深度神经网络的梯度下降和参数优化过程是通过优化器实现的,其中包括正向传播(forward propagation)算法,以及一个更为核心的深度学习算法—反向传播(Back Propagation, BP)算法。
1.正向传播
正向传播,或称前向传播,其实就是从数据的输入,一层一层进行输入和输出的传递,直到得到最后一层的预测结果,然后计算损失值的过程。
(1)从输入层开始,线性处理权重和偏置后,再经过一个激活函数处理得到中间隐层1的输出。
(2)将隐层1的输出,作为隐层2的输入,继续线性处理权重和偏置,再经过一个激活函数处理得到隐层2的输出。
(3)以此类推至隐层n。
(4)通过输出处理得到输出层的分类输出(就是样本值,也称为预测值)。
(5)在输出层,通过指定损失函数(不同类型的问题对应不同的损失函数)得到一个损失值。
这就正向传播的过程。简而言之,神经网络正向传播的过程就是计算损失的过程。
2.反向传播
反向传播就是反向计算偏微分,信息会从神经网络的高层向底层反向传播,并在这个过程中根据输出来调整权重。反向传播的思路是拿到损失函数给出的值,从结果开始,顺藤摸瓜,逐步求导,偏微分逐步地发现每一个参数应该往哪个方向调整,才能够减小损失。
简而言之,神经网络反向传播的过程就是参数优化的过程。
咖哥发言
要真正搞清楚反向传播算法的机理,需要一些数学知识,尤其是微积分的知识。我并不打算在课程上介绍。而且你们目前也不用知道其中细节,就可以用神经网络做项目。
但是,当你们出去面试或者和老板、业内人士交流时,在这种关键时刻,如果对反向传播算法一点也说不出什么来,也不合适。因此我在这里还是再多说两句……
反向传播从最终损失值开始,并从反向作用至输入层,是利用链式求导法则(chain rule)计算每个参数对损失值的贡献大小。那么,什么是链式求导法则呢?
因为神经网络的运算是逐层进行的,有许多连接在一起的张量运算,所以形成了一层层的函数链。那么在微积分中,可以给出下面的恒等式,对这种函数链进行求导:
(f(g(x)))'=f'(g(x))·g'(x)
这就是链式求导法则。将链式求导法则应用于神经网络梯度值的计算,得到的算法就是叫作反向传播,也叫反式微分(reverse-mode differential)。如果层中有激活函数,对激活函数也要求导。
综上所述,神经网络的梯度下降原理和实现其实也相当简单,和普通的线性回归以及逻辑回归一样,就是沿着梯度的反方向更新权重,损失每次都会变小一点。
下面总结一下正向和反向传播的过程。
(1)在训练集中抽取一批样本X和对应标签y。
(2)运行神经网络模型,得到这批样本X的预测值y'。
(3)计算y−y'的均方,即真值和预测值的误差。
(4)计算损失函数相对于网络参数的梯度,也就是一次反向传播。
(5)沿着梯度的反方向更新参数,即w=w−α×梯度,这样这批数据的损失就少一点点。
(6)这样一直继续下去,直到我们满意为止。
此时,咖哥突然提问:“那么大家看了上面的过程,有没有发现神经网络的内部参数优化过程和线性回归以及逻辑回归到底有什么本质区别呢?”
同学们纷纷摇头说:“没有,真没有!”
咖哥说:“即使你们并没有完全了解这其中的数学细节,也不必过于介意!举个例子,假设我是个大数学家,你们给我出了一道题—56789×34567等于多少。作为大数学家,你们认为我能手算出答案吗?”
同学们纷纷说:“会,会。”
咖哥说:“不会。因为没有必要。而现在搭建网络、计算梯度也是同样的道理。因为有太多自动计算微分和构建神经网络的工具。只要理解了基本原理,就可以在这些工具、框架上对网络进行调试,我们要做的只是解决问题,而不是炫耀数学功底。”
通过正向传播和反向传播,神经网络实现了内部参数的调整。下面,我们说说神经网络中的可调超参数,具体包括以下几个。
■优化器。
■激活函数。
■损失函数。
■评估指标。
接下来,将一一介绍各参数具体有什么用。
有一个同学举手发问:“刚才你就提到优化器调节着神经网络梯度下降的过程。这个优化器到底是什么呢?”
“嗯,问得好。”咖哥说,“优化器相当于是用来调解神经网络模型的‘手柄’。它在前面的代码中曾经出现过。”
# 编译神经网络, 指定优化器、损失函数, 以及评估指标
ann.compile(optimizer = 'adam', #优化器
loss = 'binary_crossentropy', #损失函数
metrics = ['acc']) #评估指标
编译神经网络时的optimizer = 'adam'中的adam,就是一个优化器。
优化器的引入和神经网络梯度下降的特点有关。
与线性回归和逻辑回归不同,在神经网络中,梯度下降过程是会有局部最低点出现的。也就是说,损失函数对于整个神经网络的参数来说并不总是凸函数,而是非常复杂的函数。
而且,神经网络中不仅存在局部最低点,还存在鞍点。鞍点在神经网络中比局部最低点更为“凶险”,在鞍点,函数的导数也为0。
下面通过图来直观地看看这两种“点”,如下图所示。
局部最低点和鞍点
在局部最低点和鞍点上,导数没有任何方向感,参数也不知道应该往哪里“走”。
1.神经网络权重参数随机初始化
同学们可能心想,那还搞什么梯度下降,前面讲得很清楚,凸函数这种函数形状是梯度下降得以实现的前提,现在大前提已经“崩溃”了,还谈什么最小损失呢?
类似的疑惑我也有过。当年,就是因为这一点,神经网络被认为没有前途,很不受待见。幸运的是,实际情况比我们想象的要好。在神经网络的应用中,人们发现,出现局部最低点也不是很重要的事情,如果每次训练网络都进行权重的随机初始化,那么神经网络总能够找到一个相对不错的结果。这其实也就是寄希望于一点点的运气因素。通过改变权重的初始值,在多次训练网络的过程中,即使达不到全局最低点,但通常总能收敛到一个较优的局部最低点。
这个参数随机初始化的任务,在添加层的时候,Keras已经为我们自动搞定了,如下段代码所示。有两个参数:kernel_initializer和bias_initializer。其默认设定已采用了对weight(也就是kernel)的随机初始化机制,而bias则初始化为0值。
ann.add(Dense(64,
kernel_initializer='random_uniform', # 默认权重随机初始化
bias_initializer='zeros')) # 默认偏置值为0
注意,不能初始化权重为0值,因为那会造成所有神经元节点在开始时都进行同样的计算,最终同层的每个神经元都得到相同的参数。
除了上面的参数随机初始化机制,人们还开发了一系列优化器,通过批量梯度下降和随机梯度下降来提高神经网络的效率,并解决局部最低点和鞍点的问题。
下面简单介绍一下各种优化器的特点。
2.批量梯度下降
先说一下批量梯度下降(Batch Gradient Descent,BGD)这个概念。深度学习模型不会同时处理整个数据集,而是将数据拆分成小批量,通过向量化计算方式进行处理。如果没有批量概念,那么网络一个个数据节点训练起来,速度将非常慢。
比如,前面训练网络的代码中就指定了批量大小为128,也就是同时训练128个样本,如下段代码所示。因此,通过批量梯度下降,可以提高对CPU,尤其是GPU的利用率。
history = model.fit(X_train, y_train, # 指定训练集
epochs=30, # 指定训练的轮次
batch_size=128, # 指定数据批量
validation_data=(X_test, y_test)) # 指定验证集
因此,这种同时对m个样本进行训练的过程,就被称为批量梯度下降。
对于现代计算机来说,上百个样本同时并行处理,完全不成问题。因此,批的大小,也决定了神经网络对CPU或GPU的利用率。当然,如果批量的数目过大,超出了CPU或GPU的负荷,那么效率反而会下降。因此,批量的具体值,要根据机器的性能而定。
3.随机梯度下降
BGD提升了效率,但并没有解决局部最低点的问题。因此,人们又提出了一个优化方案—随机梯度下降(Stochastic Gradient Descent,SGD)。这里的“随机”不是刚才说的随机初始化神经网络参数值,而是每次只随机选择一个样本来更新模型参数。因此,这种方法每轮次的学习速度非常快,但是所需更新的轮次也特别多。
随机梯度下降中参数更新的方向不如批量梯度下降精确,每次也并不一定是向着最低点进行。但这种波动反而有一个好处:在有很多局部最低点的盆地区域中,随机地波动可能会使得优化的方向从当前的局部最低点跳到另一个更好的局部最低点,最终收敛于一个较好的点,甚至是全局最低点。
SGD是早期神经网络中的一种常见优化器。在编译网络时,可以指定SGD优化器:
ann.compile(loss=keras.losses.categorical_crossentropy,
optimizer=keras.optimizers.SGD()) # 指定SGD优化器
当然,这种随机梯度下降和参数随机初始化一样,并不是完全可靠的解决方案,每次的参数更新并不会总是按照正确的方向进行。
4.小批量随机梯度下降
那么,前两个方法的折中,就带来了小批量随机梯度下降(Mini-Batch Gradient Descent, MBGD)。这种方法综合了BGD与SGD,在更新速度与更新轮次中间取得了一个平衡,即每次参数更新时,从训练集中随机选择m个样本进行学习。选择合理的批量大小后,MBGD既可以通过随机扰动产生跳出局部最低点的效果,又比SGD性能稳定。至此,我们找到了一个适合神经网络的梯度下降方法。
不过,仍存在一些问题需要解决。
首先,选择一个合理的学习速率很难。如果学习速率过小,则会导致收敛速度很慢;如果学习速率过大,则会阻碍收敛,即在极值点附近振荡。
一个解决的方法是在更新过程中进行学习速率的调整。在线性回归中讲过,初始的学习速率可以大一些,随着迭代的进行,学习速率应该慢慢衰减,以适应已逼近最低点的梯度。这种方法也叫作退火。然而,衰减率也需要事先设置,无法自适应每次学习时的特定数据集。
在Keras的SGD优化器中,可以通过设定学习速率和衰减率,实现MBGD:
keras.optimizers.SGD(lr=0.02, #设定优化器中的学习速率(默认值为0.01)
decay=0.1) #设定优化器中的衰减率
然而,如果模型所有的参数都使用相同的学习速率,对于数据中存在稀疏特征或者各个特征有着不同的取值范围的情况,会有些问题,因为那些很少出现的特征应该使用一个相对较大的学习速率。这种问题尤其会出现在没有经过特征缩放的数据集中。
而且,在MBGD中,卡在局部最低点和鞍点的问题,通过小批量随机化有所改善,但并没有完全被解决。
针对上述种种问题,又出现了几种新的优化参数,进一步改善梯度下降的效果。
5.动量SGD
动量SGD的思路很容易理解:想象一个小球从山坡上滑下来,如果滑到最后速度很慢,就会卡在局部最低点。此时向左移动和向右移动都会导致损失值增大。如果使用的SGD的学习速率很小,就冲不出这个局部最低点;如果其学习速率很大,就可能越过局部最低点,进入下一个下坡的轨道—这就是动量的原理(如右图所示)。
动量示意
为了在局部最低点或鞍点延续向前冲的势头,在梯度下降过程中每一步都移动小球,更新参数w时不仅要考虑当前的梯度(当前的加速度),还要考虑上一次的参数更新(当前的速度,来自之前的加速度)。
在Keras的SGD优化器中,可以通过momentum选项设定动量:
optimizer=keras.optimizers.SGD(lr=0.01, # 在优化器中设定学习速率
momentum=0.9)) # 在优化器中设定动量大小
动量解决了SGD的收敛速度慢和局部最低点这两个问题,因此在很多优化算法中动量都有应用。
6.上坡时减少动量—NAG
延续动量的思路,小球越过局部最低点后,顺着斜坡往上冲到坡顶,寻找下一个最低点的时候,从斜坡往下滚的球会盲目地选择方向。因此,更好的方式应该是在谷底加速之后、上坡之时重新放慢速度。涅斯捷罗夫梯度加速(Nesterov Accelerated Gradient,NAG)的思路就是在下坡时、增加动量之后,在越过局部最低点后的上坡过程中计算参数的梯度时又减去了动量项。
在Keras的SGD优化器中,可以通过nesterov选项设定涅斯捷罗夫梯度加速:
optimizer=keras.optimizers.SGD(lr=0.01, # 在优化器中设定学习速率
momentum=0.9, # 在优化器中设定动量大小
nesterov=True)) #设定涅斯捷罗夫梯度加速
7.各参数的不同学习速率—Adagrad
Adagrad也是一种基于梯度的优化算法,叫作自适应梯度(adaptive gradient),即不同的参数可以拥有不同的学习速率。它根据前几轮迭代时的历史梯度值来调整学习速率。对于数据集中的稀疏特征,速率较大,梯度下降步幅将较大;对非稀疏特征,则使用较小的速率更新。因此,这个优化算法适合处理含稀疏特征的数据集。比如,在文本处理的词向量(word embedding)训练过程中,对频繁出现的单词赋予较小的更新,对不经常出现的单词则赋予较大的更新。
keras.optimizers.adagrad() # 学习速率自适应
咖哥发言
具体什么是稀疏特征,什么是词向量,在循环神经网络的课程中还要讲解。
Adagrad有一个类似的变体叫Ada Delta,也是在每次迭代时利用梯度值实时地构造参数的更新值。
8.加权平均值计算二阶动量—RMSProp
均方根前向梯度下降(Root Mean Square Propogation,RMSProp),是Hinton在一次教学过程中偶然提出来的思路,它解决的是Adagrad中学习速率有时会急剧下降的问题。RMSProp抑制衰减的方法不同于普通的动量,它是采用窗口滑动加权平均值计算二阶动量,同时它也有保存Adagrad中每个参数自适应不同的学习速率的优点。
keras.optimizers.RMSprop() # RMSprop优化器
RMSProp是诸多优化器中性能较好的一种。
9.多种优化思路的集大成者—Adam
Adam全称为Adaptive Moment Estimation,相当于Adaptive + Momentum。它集成了SGD的一阶动量和RMSProp的二阶动量,而且也是一种不同参数自适应不同学习速率的方法,与Ada Delta和RMSProp的区别在于,它计算历史梯度衰减的方式类似动量,而不是使用平方衰减。
keras.optimizers.Adam(learning_rate=0.001, # 学习速率
beta_1=0.9, # 一阶动量指数衰减速率
beta_2=0.999, # 二阶动量指数衰减速率, 对于稀疏矩阵值应接近1
amsgrad=False)
就目前而言,Adam是多种优化思路的集大成者,一般是优化器的首选项。
10.涅斯捷罗夫Adam加速—Nadam
最后,还有一种优化器叫作Nadam,全称为Nesterov Adam optimizer。这种方法则是Adam优化器和Nesterov momentum涅斯捷罗夫动量的集成。
keras.optimizers.Nadam(lr=0.002, # 学习速率
beta_1=0.9, beta_2=0.999, # beta值, 动量指数衰减速率
epsilon=None, # epsilon值
schedule_decay=0.004) # 学习速率衰减设定
有点眼花缭乱了吧?优化器的选择真不少,但选择过多,有时候反而不是好事。在实践中,Adam是最常见的,目前也是口碑比较好的优化器。
咖哥发言
梯度下降中正向传播和反向传播的细节,大家理解即可,因为其代码实现早就封装在Keras或者Tensor Flow这样的框架之中了。然而,优化器的选择和设置、下面要提到的激活函数和损失函数的选择,以及评估指标的选择,则是需要我们动手配置、调试的内容。
神经网络超参数的调试,并没有一定之规,而最佳参数往往与特定数据集相关,需要在机器学习项目实战中不断尝试,才能逐渐积累经验,找到感觉。
下面说说神经网络中的激活函数(有时也叫激励函数)。
在逻辑回归中,输入的特征通过加权、求和后,还将通过一个Sigmoid逻辑函数将线性回归值压缩至[0,1]区间,以体现分类概率值。这个逻辑函数在神经网络中被称为激活函数(这个名词应该是来自生物的神经系统中神经元被激活的过程)。在神经网络中,不仅最后的分类输出层需要激活函数,而且每一层都需要进行激活,然后向下一层输入被激活之后的值。不过神经网络中间层的输出值,没有必要位于[0,1]区间,因为中间层只负责非线性激活,并不负责输出分类概率和预测结果。
那么,为什么每一层都要进行激活呢?
其原因在于,如果没有激活函数,每一层的输出都是上层输入的线性变换结果,神经网络中将只包含两个线性运算—点积和加法。
这样,无论神经网络有多少层,堆叠后的输出都仍然是输入的线性组合,神经网络的假设空间并不会有任何的扩展。为了得到更丰富的假设空间,从而充分利用多层表示的优势,就需要使用激活函数给每个神经元引入非线性因素,使得神经网络可以任意逼近任何非线性函数,形成拟合能力更强的各种非线性模型。因此,所谓“激活”,我们可以将其简单理解成神经网络从线性变换到非线性变换的过程(如下图所示)。
神经元的激活过程
最初,Sigmiod函数是唯一的激活函数,但是后来,人们逐渐地发现了其他一些更适合神经网络的激活函数Re LU、PRe LU、e LU等。
1.Sigmoid函数和梯度消失
Sigmoid函数大家都很熟悉了,它是最早出现的激活函数,可以将连续实数映射到[0,1]区间,用来体现二分类概率。在逻辑回归中,在特征比较复杂时也具有较好的效果。其公式和图像如下:
Sigmoid函数图像
但是Sigmoid函数应用于深度神经网络中时有比较致命的缺点—会出现梯度消失(gradient vanishing)的情况。梯度消失可以这样简单地理解:反向传播求误差时,需要对激活函数进行求导,将来自输出损失的反馈信号传播到更远的层。如果需要经过很多层,那么信号可能会变得非常微弱,甚至完全丢失,网络最终变得无法训练。
因此,人们开始寻找能够解决这个梯度消失问题的激活函数。
2.Tanh函数
之后就出现了类似于Sigmoid函数的Tanh函数。这个函数和Sigmoid函数很相似,也是非线性函数,可以将连续实数映射到[-1,1]区间。其公式和图像如下:
Tanh函数图像
Tanh函数是一个以0为中心的分布函数,它的速度比Sigmoid函数快,然而并没有解决梯度消失问题。
3.Re LU函数
后来人们就发现了能够解决梯度消失问题的Re LU(Rectified Linear Unit)函数。Re LU函数的特点是单侧抑制,输入信号小于等于0时,输出是0;输入信号大于0时,输出等于输入。Re LU对于随机梯度下降的收敛很迅速,因为相较于Sigmoid和Tanh在求导时的指数运算,对Re LU求导几乎不存在任何计算量。其公式和图像如下:
f(z)=max(0,z)
Re LU函数图像
Re LU既能够进行神经网络的激活,收敛速度快,又不存在梯度消失现象。因此,目前的神经网络中已经很少有人在使用Sigmoid函数了,Re LU基本上算是主流。
但是Re LU函数也有缺点,它训练的时候比较脆弱,容易“死掉”。而且不可逆,“死”了就“活”不过来了。比如,一个非常大的梯度流过一个Re LU神经元,参数更新之后,这个神经元可能再也不会对任何输入进行激活反映。所以用Re LU的时候,学习速率绝对不能设得太大,因为那样会“杀死”网络中的很多神经元。
4.Leaky Re LU和PRe LU
Re LU函数进一步发展,就出现了Leaky Re LU函数,用以解决神经元被“杀死”的问题。其公式和图像如下:
f(z)=max(εz,z)
Leaky Re LU函数图像
其中ε是很小的负数梯度值,比如0.01。这样做的目的是使负轴信息不会全部丢失,解决了ReLU神经元“死掉”的问题。因为它不会出现零斜率部分,而且它的训练速度更快。
Leaky ReLU有一种变体,叫作PReLU。PReLU把ε当作每个神经元中的一个参数,是可以动态随着梯度下降变化的。
但是Leaky Re LU有一个问题,就是在接收很大负值的情况下,Leaky Re LU会导致神经元饱和,从而基本上处于非活动状态。
5.e LU函数
因此又出现了另外一种激活函数e LU,形状与Leaky Re LU相似,但它的负值部分是对数曲线而不是直线。它兼具Re LU和Leaky Re LU的优点。
其公式和图像如下:
e LU函数图像
关于神经网络的激活函数就先讲这么多。总而言之,目前Re LU是现代神经网络中神经元激活的主流。而Leaky Re LU、PRe LU和e LU,比较新,但并不是在所有情况下都比Re LU好用,因此Re LU作为激活函数还是最常见的。至于Sigmiod和Tanh函数,目前在普通类型的神经元激活过程中不多见了。
“等等!”小冰忽然喊道。
咖哥说:“怎么了?”
小冰说:“你说Sigmiod不常用了,但是你怎么还用?”
咖哥说:“没有啊,刚才的单隐层神经网络案例,我不是已经说了全都用Re LU激活神经元了嘛。你回头看看代码。”
ann.add(Dense(units=12, input_dim=11, activation = 'relu')) # 添加输入层
ann.add(Dense(units=24, activation = 'relu')) # 添加隐层
小冰说:“你看一下刚才这句代码。前面你是用的Re LU,但是添加输出层这一句还是Sigmoid,这是你的笔误吗?”
ann.add(Dense(units=1, activation = 'sigmoid')) # 添加输出层
咖哥说:“呃,这个啊。我正要解释。”
6.Sigmoid和Softmax函数用于分类输出
刚才讲了这么多的激活函数,都是针对神经网络内部的神经元而言的,目的是将线性变换转换为层与层之间的非线性变换。
但是神经网络中的最后一层,也就是分类输出层的作用又不一样。这一层的主要目的是输出分类的概率,而不是为了非线性激活。因此,对于二分类问题来说,仍然使用Sigmoid作为逻辑函数进行分类,而且必须用它。
那么对于多分类问题呢?神经网络的输出层使用另一个函数进行分类。这个函数叫作Softmax函数,实际上它就是Sigmoid的扩展版,其公式如下:
Softmax—多分类输出层的激活函数
这个Softmax函数专用于神经网络多分类输出。对于一个输入,做Softmax之后的输出的各种概率和为1。而当类别数等于2时,Softmax回归就退化为Logistic回归,与Sigmoid函数的作用完全相同了。
在后面的课程中我们会用神经网络解决多分类问题,那时候你们将看到Softmax函数会在输出层中取代Sigmoid函数。
说完激活函数,再说说神经网络中损失函数的选择。
激活函数是属于层的参数,每一层都可以指定自己的激活函数,而损失函数是属于整个神经网络的参数。损失函数在模型优化中所起到的作用我们已经很了解了。
神经网络中损失函数的选择是根据问题类型而定的,指导原则如下。
对于连续值向量的回归问题,使用我们非常熟悉的均方误差损失函数:
# 对于连续值向量的回归问题
ann.compile(optimizer='adam',
loss='mse') # 均方误差损失函数
对于二分类问题,使用同样熟悉的二元交叉熵损失函数:
# 对于二分类问题
ann.compile(optimizer='adam',
loss='binary_crossentropy', # 二元交叉熵损失函数
metrics=['accuracy'])
对于多分类问题,如果输出是one-hot编码,则用分类交叉熵损失函数:
# 对于多分类问题
ann.compile(optimizer='adam',
loss='categorical_crossentropy', # 分类交叉熵损失函数
metrics=['accuracy'])
对于多分类问题,如果输出是整数数值,则使用稀疏分类交叉熵损失函数:
# 对于多分类问题
ann.compile(optimizer='adam',
loss='sparse_categorical_crossentropy', # 稀疏分类交叉熵损失函数
metrics=['accuracy'])
对于序列问题,如语音识别等,则可以用时序分类(Connectionist Temporal Classification, CTC) 等损失函数。
最后一个要讲的超参数是神经网络的评估指标,也就是评估网络模型好不好的标准,这个标准也叫目标函数。评估指标和损失函数有点相似,都是追求真值和预测值之间的最小误差,其差别在于:损失函数作用于训练集,用以训练机器,为梯度下降提供方向;而评估指标作用于验证集和测试集,用来评估模型。
对于一个机器学习模型来说,有的时候评估指标可以采用与损失函数相同的函数。
比如,对于线性回归模型,损失函数一般选择均方误差函数,评估指标也可以选择均方误差函数。也可以选择MAE,即平均绝对误差函数作为目标函数。因为评估过程无须梯度下降,取误差绝对值做平均即可,无须加以平方。当然,MAE相对于权重不是凸函数,因此只能用作评估模型,不能用作损失函数。
而对于分类问题模型,神经网络默认采用准确率作为评估指标,也就是比较测准的样本数占总样本的比例。
我们也强调过了,有时候用准确率评估对于类别分布很不平衡的数据集不合适,此时考虑使用精确率、召回率、F1分数,以及ROC/AUC作为评估指标。
其实,MAE或MSE也能用作分类问题的评估指标。如果那样做,则不仅是在检查测得准不准,更多的是在评估预测出的概率值有多接近真值。比如P=0.9和P=0.6,四舍五入之后,输出类别都是1。也许全部测准,但是两者带来的MAE值可不尽相同。预测概率P=0.9的算法明显比P=0.6的算法的MAE值更优,也就是更接近真值。
还可以自主开发评估指标。下面是一个Keras文档中自带的小例子,用代码自定义了一个目标函数:
# 自定义评估指标
import keras.backend as K
def mean_pred(y_true, y_pred):
return K.mean(y_pred)
ann.compile(optimizer='rmsprop', # 优化器
loss='binary_crossentropy', # 损失函数
metrics=['accuracy', mean_pred]) # 自定义的评估指标
综上,神经网络中的评估指标的选择有以下两种情况。
■对于回归问题,神经网络中使用MAE作为评估指标是常见的。
■对于普通分类问题,神经网络中使用准确率作为评估指标也是常见的,但是对于类别分布不平衡的情况,应辅以精确率、召回率、F1分数等其他评估指标。
损失函数和评估指标,有相似之处,但意义和作用又不尽相同,大家不要混淆。