小言_互联网的博客

深度神经网络基础

463人阅读  评论(0)

以下文章摘录自:

《机器学习观止——核心原理与实践》

京东: https://item.jd.com/13166960.html

当当:http://product.dangdang.com/29218274.html

(由于博客系统问题,部分公式、图片和格式有可能存在显示问题,请参阅原书了解详情)

 

 

 

第1章          深度神经网络基础

本章内容我们将重点讲解深度神经网络的部分核心组成元素,它们的演进历程、背后原理,以及其它一些基础知识,为后续章节的学习扫清障碍。

1.1    神经元

我们知道,人类社会很多划时代的科技创新,都是从面向大自然的学习和观察中提炼出来的,比如飞机、潜艇等等。因而人们在研究AI时,自然不会放过“智能”的天然来源——也就是人类自身的大脑和神经系统了。在人工智能学科创立的前几年,神经病理学有了一个重大的发现,即人类大脑是由神经元组成的。结合图灵的计算理论,人们逐渐对如何“仿造”人类大脑有了一些模糊的认知。

图 ‑ 神经元经典结构(参考wikipedia)

 

生物的神经元是神经系统中最基本的组成单元,它有两种工作状态,即兴奋或者抑制。一般情况下,神经元会处于抑制状态;而当接收到外界的刺激信息,并达到一个阈值时,神经元会被激活。此时神经元会转向兴奋状态,并向与它连接的其它神经元传播物质——激活函数就是受此启发而提出来的。

人工神经元和上述的神经元工作机制非常类似,典型的实现如下所示:

图 ‑ 人工神经元结构

 

上图中的神经元包括了3个部分,即输入、输出和激活函数。输入部分来源于其它神经元,并且带有权重;这些输入信息经过加权和等处理后,再结合激活函数得到最终的结果,这就是输出了。激活函数根据不同场景有多种表现形式,我们放在下一小节进行专门的讲解。

接下来,我们来看一下人工神经元与生物神经元的对比分析。

图 ‑ 每个神经元的连接数呈现快速增长趋势

1. 自适应线性单元 (Widrow and Hoff, 1960)

2. 神经认知机 (Fukushima, 1980)

3. GPU-加速 卷积网络 (Chellapilla et al., 2006)

4. 深度玻尔兹曼机 (Salakhutdinov and Hinton, 2009a)

5. 无监督卷积网络 (Jarrett et al., 2009b)

6. GPU-加速 多层感知机 (Ciresan et al., 2010)

7. 分布式自编码器 (Le et al., 2012)

8. Multi-GPU 卷积网络 (Krizhevsky et al., 2012a)

9. COTS HPC 无监督卷积网络 (Coates et al., 2013)

10. GoogLeNet (Szegedy et al., 2014a)

 

通过上图不难发现,人工神经元的连接数随着时间的推移,呈现出快速增长的趋势:上世纪50年代,人工神经元的连接数还低于fruit fly等低级动物,若干年后这一数量已经达到了较小哺乳动物(如小鼠)的水平。而进入2000年后,部分人工神经网络的神经元连接数量则可以与猫持平或者接近了。

与连接数量相对应的是神经网络规模,后者也同样呈现快速增长态势。不过就目前神经网络的神经元数量而言,它和人类的差距还非常之大(这一点显然和前面的神经元连接数量的情况不同)。参考下图的统计信息:

图 ‑ 人工神经网络与大自然生物的规模对比,差距明显

注:数字代表各种不同的人工神经网络模型,具体对应的是:

1. 感知机 (Rosenblatt, 1958, 1962)

2. 自适应线性单元 (Widrow and Hoff, 1960)

3. 神经认知机 (Fukushima, 1980)

4. 早期后向传播网络 (Rumelhart et al., 1986b)

5. 用于语音识别的循环神经网络 (Robinson and Fallside, 1991)

6. 用于语音识别的多层感知机 (Bengio et al., 1991)

7. 均匀场sigmoid信念网络 (Saul et al., 1996)

8. LeNet-5 (LeCun et al., 1998c)

9. 回声状态网络 (Jaeger and Haas, 2004)

10. 深度信念网络 (Hinton et al., 2006a)

 

人类大脑是一个非常复杂的系统,它通常可以在极低的功耗下快速给出响应,这是目前神经网络所“无法企及”的。

1.2    激活函数

随着深度神经网络的高速发展,激活函数也迎来了一轮又一轮的“革新换代”。因而我们有必要花一小节的篇幅,来总结一下神经网络发展历程中被广泛使用过的一些激活函数,为后续的学习打下一定基础。

1.2.1  Sigmoid

激活函数之所以重要,在于它可以为神经网络模型带来“非线性”物质,从而极大增强深度网络的表示能力——因此它也被人们称为“非线性映射函数”。据悉,激活函数的灵感来源于生物的神经元,后者是神经系统中最基本的组成单元。它有两种工作状态,即兴奋或者抑制。一般情况下,神经元会处于抑制状态;而当接收到外界的刺激信息,并达到一个阈值时,神经元会被激活。此时神经元会转向兴奋状态,并向与它连接的其它神经元传播物质。

图 ‑ 生物神经元结构

 

人工神经元和上述的神经元工作机制非常类似。如我们在前一小节学习到的,它的实现如下所示:

图 ‑ 人工神经元结构

 

上图中的activation function就是我们本小节的主角了。

在众多激活函数中,Sigmoid可以说是深度神经网络前期使用得最为广泛的一种激活函数了。它对应的公式也并不复杂,如下所示:

f(x) = 

其函数图形相对平滑,参见下图的描述:

图 ‑ sigmoid函数

 

可见sigmoid将输入值映射到了一个0到1之间的区间。

不难理解,sigmoid函数在其定义域内是处处可导的,其导数为:

 

 

那么Sigmoid有什么缺陷,为什么现在深度神经网络中已经比较少出现它的身影了呢?

经过不少学者的持续研究,他们针对sigmoid提出了如下几个观点:

l  过饱和,梯度丢失问题

图 ‑ sigmoid函数梯度

 

从上图不难看出,当x大于5,或者小于-5时,梯度值已经趋向于0了——这样导致的结果就是误差在反向传播的过程中很难传递到前面,换句话说就是网络很可能无法完成正常的训练过程

l  收敛缓慢,有可能会震荡

l  值域的均值非零

我们知道sigmoid的输出都是大于0的,这会导致什么问题呢?

神经元的处理过程如下图所示:

它接收前面一层的输出xi,然后乘以权重wi,最后和bias一起输入到激活函数f中产生结果。用公式表示就是:

或者:

其中w是一个向量,代表的是(w1, w2…)。所以在反向传播算法的计算过程中,针对w的梯度计算公式就是:

 =  

=   x

因为上层layer的输出结果是下一个layer的输入,所以在这种情况下会有很多layer的x总是为正值,这样导致的结果就是w向量中的所有元素(w1, w2…)总是全部为正或者全部为负——换句话说,神经网络在训练过程中会是如下图所示的一种低效率的学习方式(zig zag path):

图 ‑ sigmoid非zero-center所带来的训练低效率问题

 

这显然不是我们想要看到的结果。

l  计算量偏大

等等

 

由于上述这些缺点,Sigmoid便逐步淡出人们的视野了。

1.2.2  Tanh

Tanh是为了解决前述sigmoid的均值非零问题而提出来的一种激活函数,又被称为双曲正切函数(Hyperbolic Tangent Function)。它的函数定义如下所示:

它与sigmoid的关系是:

由于上述公式中ez有可能小于e-z,所以它有可能出现负数的情况。具体而言,tanh的取值范围是:

[-1, 1]

对应的函数图像如下:

图 ‑ tanh函数图像

 

从tanh表现出的图像对称性,我们不难发现它的值域均值为0。同时,实践也证明了tanh比sigmoid的神经网络训练效果要好——特别是对于特征相差较明显的机器学习场景,其效果往往比较出众。

不过,tanh也还是没有解决sigmoid的梯度饱和问题。

1.2.3  ReLU

ReLU是Rectified Linear Unit的缩写,它的表达公式更简单:

用直白的话来讲,就是当x小于零时ReLU取0,否则取x。

其对应的函数图像如下所示:

图 ‑ ReLU函数图像

 

ReLU的主要优点在于:

l  函数简单,计算量小

相比于sigmoid和tanh,这一点是显而易见的

l  收敛速度快

这一点已经被大量的实验所证实,它在SGD方法下的收敛速度比tanh等快6+倍。具体可以参考如下论文中的阐述:

http://www.cs.toronto.edu/~fritz/absps/imagenet.pdf

图 ‑ 采用ReLU(实线)的网络收敛到25%错误率的速度比tanh快6倍

 

l  可以缓解梯度饱和问题

我们可以看下ReLU的梯度函数,参考下图:

图 ‑ ReLU的梯度函数

 

不难发现,ReLU在x>=0的部分消除了Sigmoid函数的梯度饱和现象。

 

当然,ReLU虽然有不少优点,但也有致命缺陷:例如当x<0时,梯度对应的是0,从而会出现无法完成网络的正常训练的情况——我们称之为“死区”。

通常认为如下两种情况容易导致死区的发生:

l  情况1:参数初始化不合理

在某些情况下,不适当的参数初始化可能会导致死区现象的产生——虽然业界认为这种情况相对比较少见

l  情况2:学习率的设定不合理

如果学习率设置得太高,就有可能导致模型在训练过程中参数更新太大,从而使网络不幸进入死区状态的问题

1.2.4  Leaky ReLU

有需求就有市场——Leaky ReLU就是为了缓解前述死区问题的改进版本。它的实现同样不复杂,公式如下:

 

可以看到,Leaky ReLU与ReLU的区别在于x<0的区域——前者通过增加一个超参数,而不是置0来避免死区现象的发生。不过由于 的值是人工设定的,如何选择合适的值就成了一个难点。因而在实际训练过程中,Leaky ReLU的性能并不总是会让人满意。

图 ‑ Leaky ReLU

 

所以人们又尝试设计了其它一些ReLU,比如参数化ReLU和随机化ReLU等等。我们将在下一小节做一个简单的介绍。

1.2.5  ReLU的其它变种

ReLU还有其它一些变种,比如为了解决Leaky ReLU的超参数不好选择的问题所提出来的参数化ReLU和随机化ReLU等等。它们的基本思路很简单:既然人们不好选择超参数的值,那么有没有办法让训练过程来自动完成这个工作呢?所以就有了:

l  把变成一个训练参数,由训练过程来自动调参

这就是参数化ReLU的实现了。通常表达为:

在《delving deep into rectifiers surpassing human-level performance on imagenet classification》这篇paper中,作者将参数化ReLU与ReLU在同一模型上进行了横向性能对比,可以看到后者作为激活函数,在同等条件下的表现有可能优于ReLU。参考下述表格:

表格 ‑ 参数ReLU的性能表现

l  随机选择的值

图 ‑ Randomized ReLU

 

这就是随机化ReLU的实现了。不过随机并不意味着“随意”,事实上它的取值也是有限定条件的:例如在训练过程中的取值分布需要服从“连续性均匀分布”。参考下述的表达公式:

其中 ~ U(l, u),l < u 且 l, u 

 

除此之外,ReLU还有Noisy ReLUs、ELU等其它变种,它们都是针对某些特定问题所提出来的解决方案。

图 ‑ ELU激活函数

 

感兴趣的读者可以自行查阅相关资料来了解详情。

1.2.6  激活函数的选择

面对这么多的激活函数,我们应该如何选择呢?

从项目实践的角度来看,有如下一些建议:

l  如何选择最佳的激活函数,目前业界并没有统一的理论指导。所以仍然需要我们根据项目的实际诉求,结合实验数据来判定何为最佳选择

l  在分类问题上,建议首先尝试ReLU。这也是最为常用的一种激活函数

l  当然,在使用ReLU的过程中,大家还需要特别注意学习率的设定,以及模型参数的初始值等问题

l  在上述基础上,结合模型的具体表现可以考虑使用PReLU等其它激活函数,来尝试进一步提升模型的性能

1.3    前向传播和BP(Back Propagation)算法

监督学习算法的核心目标,实际上就是寻找一个最佳函数,使得它的输入值和对应输出值可以和训练样本高度匹配。这自然就涉及到一个问题,即如何在迭代训练过程中逐步调整算法中的各种参数,“有条不紊”地得出最佳函数呢?

在深度神经网络中,这个问题的答案是Back Propagation。

 

 

i

i1

i2

h1

h2

w11

w12

w13

w14

o1

o2

w21

w22

w23

w24

b1

b2

 

图 ‑ 后向传播算法范例

 

对于初学者而言,后向传播算法可能并不好理解,因而接下来我们会结合一个范例来做细化分析。如上图所示,这个范例包含了一个简单的神经网络结构,其中input layer有两个节点,分别为i1和i2;hidden layer的两个节点为h1和h2;output layer则为o1和o2。层间采取全连接,以wij(i为层级,j为序号)为权重。另外,b1和b2为bias值。

我们先编写一段简单的tensorflow代码,并以此为基础来剖析后向传播的计算过程。

sess = tf.Session()

bias1 = tf.Variable(0.5, 'bias1')
bias2 = tf.Variable(0.6, 'bias2')

input_data = tf.Variable([[0.5,0.8]])
output_data = tf.Variable([[1.0, 2.0]]) //预期值

hidden_layer_weights = tf.Variable([[0.1,0.2], [0.3,0.4]])
output_layer_weights = tf.Variable([[0.5,0.6], [0.7,0.8]])

init = tf.global_variables_initializer()
sess.run(init)

上述代码段中,我们首先创建一个输入数据变量input_data,一个输出变量output_data,隐藏层权重hidden_layer_weights和输出层权重output_layer_weights。接着我们通过global_variables_initializer来为变量做初始化,并利用Session将操作传递到tensorflow后台。

##print weights
print('hidden weights:'+'\n'+str(sess.run(hidden_layer_weights)))
print('output weights:'+'\n'+str(sess.run(output_layer_weights)))

紧接着的两行代码用于打印出当前两个网络层的权重值,以便我们后续可以和更新后的权重值做对比分析。结果如下:

接下来程序进入神经网络的构建环节,同时还要完成网络值的前向传播计算。我们分为几个步骤来做详细分析。

##forward propagation
hidden_layer_net = tf.matmul(input_data, hidden_layer_weights) + bias1
print('hidden layer net:'+'\n'+str(sess.run(hidden_layer_net)))

Step1@前向传播. 建立网络隐藏层,所采用的计算公式为:

input_data矩阵*hidden_layer_weights权重矩阵+偏置bias1

具体运算过程为:

[0.5, 0.8] *  + 0.5= [0.5*0.1+0.8*0.3, 0.5*0.2+0.8*0.4] = [0.79, 0.92]

打印出的结果为:

 

hidden_layer_sigmoid = tf.sigmoid(hidden_layer_net)
print('hidden layer sigmoid:'+'\n'+str(sess.run(hidden_layer_sigmoid)))

Step2@前向传播. 应用激活函数sigmoid,得出隐藏层结果。上述打印语句的输出为:

以hidden_layer_sigmoid的第1个元素为例,它的具体运算过程为:

sigmoid(0.79) =  = 0.68783134

 

output_layer_net = tf.matmul(hidden_layer_sigmoid, output_layer_weights) + bias2
print('output layer net:'+'\n'+str(sess.run(output_layer_net)))

Step3@前向传播. 建立网络输出层,所采用的计算公式和隐藏层一致,因而不再赘述。打印结果值如下:

它的具体运算过程为:

[0.68783134 , 0.71504211] *   + 0.6 = [0.68783134*0.5+0.71504211*0.7, 0.68783134*0.6+0.71504211*0.8] = [1.44444513 , 1.58473253]

 

Step4@前向传播. 输出层仍然采用sigmoid激活函数,得到的最终结果值为:

[ 0.80914205  0.82987368]

 

这样前向传播的计算过程就完成了。由此也可以看到神经网络虽然理论上比较繁复,但背后的计算过程还是比较好理解的。

接下来我们需要思考的问题是,如何有效更新网络参数,以使神经网络的输出结果和预期值尽可能一致呢(在本范例中指的是output_data 代表的1.0和2.0)?大家应该还记得我们在梯度下降算法章节中所学习到的知识,无非就是遵循如下几个核心步骤:

l  计算loss function

l  计算各网络参数与loss function之间的梯度,实际上就是衡量各参数对精度损失的影响程度

l  朝着梯度值下降的方向更新各网络参数

l  循环往复直至达到结束条件

 

深度神经网络中采用的梯度下降算法和上述实现并无本质区别,主要的不同点在于:

l  神经网络参数数量通常很多,而且各网络层是间接计算关系,意味着大部分的网络参数和最终的损失函数值都没有直接的函数依赖。因而我们可以先从与损失函数有直接连接的输出层开始计算梯度,并依次将误差值向后面的网络层传递——这也是back propagation这个名称的由来。

l  需要应用到偏导数求解知识

 

接下来,我们先来计算第一次前向计算后的误差值。Loss funtion采用的是平和差公式:

#loss function
loss = tf.square(output_data[0][0] - output_layer_sigmoid[0][0])/2 + tf.square(output_data[0][1] - output_layer_sigmoid[0][1])/2
print('loss before training:'+'\n'+str(sess.run(loss)))

打印结果为:

紧接着我们就可以开始计算输出层的权重值更新了(下面以w21为例来为大家做详细阐述,其它参数值的计算过程是类似的,读者可以做为练习自行推导)。

由本小节开头的网络构建图不难得出如下的函数关系:

 = (output_data1 - output_layer_sigmoid1)2/2

 = (output_data2 - output_layer_sigmoid2)2/2

 

output_layer_sigmoid1 = sigmoid(output_layer_net)

 

output_layer_net = w21*hidden_layer_sigmoid1 + w23*hidden_layer_sigmoid2 + bias2

 

因而误差值和w21的偏导数关系如下:

 =  = **

 

其中:

 = *2*(output_layer_sigmoid1- output_data1)

= 0.80914205 -1

= -0.19085795

 

同时:

 = sigmoid(output_layer_net)*(1- sigmoid(output_layer_net))

                  =  * (1- )

                  = 0.809142 * (1- 0.809142)

                  = 0.15443117

 

另外:

 = hidden_layer_sigmoid1

              = 0.68783134

 

因而:

 = -0.19085795*0.15443117*0.68783134

        = -0.02027342741225278062901

 

得到梯度值后,w21就可以做更新操作了,公式如下:

w21 = w21 -  * training_rate

= 0.5 + 0.0202734*0.1

= 0.50202734

   

为了测试上述计算结果的准确性,下面我们继续通过程序来做验证,如下所示:

#prepare for training
train_step = tf.train.GradientDescentOptimizer(0.1).minimize(loss)
sess.run(train_step)

print('hidden layer weights updated:'+'\n'+str(sess.run(hidden_layer_weights)))
print('output layer weights updated:'+'\n'+str(sess.run(output_layer_weights)))

print('loss after training:'+'\n'+str(sess.run(loss)))

我们采用了tensorflow的梯度下降算法优化器,步长为0.1,并执行了一步training。最终输出结果为:

从output layer weights的更新值可以看出,其中的w21和我们前面的手工计算结果是完全一致的,从而验证了整个推导过程的准确性。其它参数的计算推演也大同小异,大家可以自行分析,限于篇幅这里不再一一阐述。

1.4    损失函数

我们知道,神经网络是利用基于误差的反向传播算法来完成参数的更新的,这意味着用于计算误差的损失函数(也被称为目标函数,或者代价函数。简单来讲,损失函数用于描述模型对于结果的“不满意程度”)就扮演了非常重要的角色。和激活函数的情况类似,人们在不断探索实践的过程中总结了很多种类型的损失函数。如果从深度学习的任务类型来看的话,可以大致分为:

l  分类场景下的损失函数

l  回归场景下的损失函数

l  其它任务类型(Attribute Classification)的损失函数

等几个大类

 

同时,每个大类下面往往又包含了很多具体的细分类型。我们将在后续小节中针对部分经典的损失函数进行讲解。

1.4.1  分类场景

分类场景下的损失函数包括但不限于:

l  0-1损失

l  Log损失

l  Hinge损失

l  指数损失

l  感知损失

l  交叉熵损失函数

l  合页损失函数

l  坡道损失函数

等等

 

上面所列的损失函数在深度神经网络中不全都是“常客”。因而接下来的内容中,我们侧重于介绍在实际深度学习项目中大家会经常接触到的那些损失函数(为了叙述的连贯性,其中也包括了非神经网络模型所采用的损失函数)。

1.4.1.1          合页损失函数(hinge loss)

合页损失函数是在SVM,即支持向量机模型中常用的loss function,所以我们也称之为SVM Loss。为了叙述方便,我们做如下假设:

l  (xi, yi) 代表的是一张image图像,以及它所对应的数据标签

l  函数 s = f(xi) 代表的是模型针对xi给出的预测值

 

那么hinge loss的表达式如下所示:

 

如果以图形绘制出来的话,就是下面这个样子:

图 ‑ hinge loss示意图

 

下面我们结合一个范例,来让大家更直观地理解hinge的计算过程。

如下所示是一个多类别模型给出的预测数值:

表格 ‑ hinge loss范例

类别\图像

cat

3.2

1.3

2.2

car

5.1

4.9

2.5

frog

-1.7

2.0

-3.1

 

针对第一张cat图像,此模型产生的损失为:

L1= max(0, 5.1 - 3.2 + 1) +max(0, -1.7 - 3.2 + 1)

= max(0, 2.9) + max(0, -3.9)

= 2.9 + 0

= 2.9

同理我们可以得到另外两个训练样本所产生的损失分别为:

L2 = 0

L3 = 12.9

 

一个模型在N个训练样本的总体损失计算公式为:

所以针对我们这个场景,Loss结果为:

L = (2.9 + 0 + 12.9)/3

  = 5.27

 

Hinge Loss的代码实现范例如下所示:

 

另外,为了降低模型的过拟合问题,我们通常会给复杂的模型加一个penalty——具体会体现在Loss Function上,如下所示:

可以看到除了我们之前的Loss函数外,新的损失函数还多了一个R(W),它代表的是Regularization函数,而且有多种表现形式。比如:

l  L1 regularization

对应的公式为:

l  L2 regularization

对应的公式为:

l  Elastic net (L1 + L2)

对应的公式为:

 

下面示意图可以帮助大家更好地理解增加了正则化的损失函数实现:

图 ‑ 带有正则化的损失函数

 

除此之外,神经网络还有其它常用的正则化手段,例如:

l  Dropout

即在训练过程中,以概率P随机丢弃一些神经元的操作。参见如下示意图:

图 ‑ Dropout示意图

 

l  Max norm regularization

Max-Norm Regularization 通常结合dropout来使用,这样可以达到更好的效果

1.4.1.2          交叉熵损失函数(cross-entropy loss function)

交叉熵损失函数又被人们称为Softmax损失函数,可以说是深度神经网络中应用最为广泛的一种loss function。其对应的公式如下所示:

可以看到它和softmax层的实现非常类似,这同时也是其名称的由来。

我们仍然沿用前一小节所提供的范例,针对第一张图像可以分别得到如下预测值:

类别\图像

cat

3.2

car

5.1

frog

-1.7

 

那么softmax loss function的计算过程如下:

3.2                   0.13

5.1    计算softmaxà  0.87 

-1.7                  0.00

 

Li_softmax  =  -log(0.13)

         =  0.89

 

因为它只针对正确类别的预测概率执行-log操作,所以取值范围为[0, ]。

1.4.2  回归场景

回归场景任务下,我们常用的损失函数包括但不限于:

l  平均绝对误差(Mean Absolute Error,MAE)

l  均方误差(Mean Square Error)

l  均方根误差(Root Mean Square Error)

l  平滑平均绝对误差(Huber Loss)

等经典实现

1.4.2.1          平均绝对误差(Mean Absolute Error,MAE)

平均绝对误差,又被称为L1误差,常见表达式如下所示:

1.4.2.2          均方误差(Mean Square Error)

均方误差在某些资料中,也被称为L2误差,常用表达式如下所示:

1.4.2.3          均方根误差(Root Mean Square Error)

均方根误差和前述的L2比较类似,其常见表达式如下所示:

1.4.2.4          平滑平均绝对误差(Huber Loss)

总的来说,L1损失函数对于异常值的鲁棒性更好,但它的导数并不连续,使得最优解的寻找过程相对低效;而L2虽然对于异常值的表现不如前者,但是在寻找最优解的过程中更为稳定,可以说两者各有优缺点。

Huber Loss吸收了L1和L2各自的优点——它既保持了鲁棒性,同时还具备可微特性。

其常见表达式如下所示:

其中简单来讲代表的是“斜度”。下面我们可以通过一个代码范例来加深理解。

核心代码部分如下所示:

import tensorflow as tf

import matplotlib.pyplot as plt

 

sess = tf.Session()

 

x_function = tf.linspace(-1., 1., 500)

target = tf.constant(0.)

 

##L1损失函数

L1_function = tf.abs(target - x_function)

L1_output = sess.run(L1_function)

 

##L2损失函数

L2_function = tf.square(target - x_function)

L2_output = sess.run(L2_function)

 

##huber损失函数,我们取3个“斜度”来做为对比

delta1 = tf.constant(0.2)

pseudo_huber1 = tf.multiply(tf.square(delta1), tf.sqrt(1. + tf.square((target - x_function)/delta1)) - 1.)

pseudo_huber1_output = sess.run(pseudo_huber1)

 

delta2 = tf.constant(1.)

pseudo_huber2 = tf.multiply(tf.square(delta2), tf.sqrt(1. + tf.square((target - x_function) / delta2)) - 1.)

pseudo_huber2_output = sess.run(pseudo_huber2)

 

delta3 = tf.constant(5.)

pseudo_huber3 = tf.multiply(tf.square(delta3), tf.sqrt(1. + tf.square((target - x_function) / delta2)) - 1.)

pseudo_huber3_output = sess.run(pseudo_huber3)

 

##将上述结果绘制出来,帮助大家直观地理解它们之间的区别

x_array = sess.run(x_function)

plt.plot(x_array, L2_output, 'b-', label='L2')

plt.plot(x_array, L1_output, 'r--', label='L1')

plt.plot(x_array, pseudo_huber1_output, 'm,', label='Pseudo-Huber (0.2)')

plt.plot(x_array, pseudo_huber2_output, 'k-.', label='Pseudo-Huber (1.0)')

plt.plot(x_array, pseudo_huber3_output, 'g:', label='Pseudo-Huber (5.0)')

plt.ylim(-0.2, 0.4)

plt.legend(loc='lower right', prop={'size': 11})

plt.title('LOSS FUNCTIONS')

plt.show()

绘制结果如下图所示:

图 ‑ 几种损失函数对比

 

当然,回归类任务中还有其它一些类型的损失函数,例如tukey’s biweight等。感兴趣的读者可以自行查阅相关资料做进一步学习。


转载:https://blog.csdn.net/uiop78uiop78/article/details/116107168
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场