【CV知识学习】神经网络梯度与归一化问题总结+highway network、ResNet的思考

本文主要是介绍【CV知识学习】神经网络梯度与归一化问题总结+highway network、ResNet的思考,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

 

一、梯度消失/梯度爆炸的问题

二、选择其他激活函数

三、层归一化

四、权值初始化

五、调整网络的结构


一、梯度消失/梯度爆炸的问题

首先来说说梯度消失问题产生的原因吧,虽然是已经被各大牛说烂的东西。不如先看一个简单的网络结构,

 

可以看到,如果输出层的值仅是输入层的值与权值矩阵W的线性组合,那么最终网络最终的输出会变成输入数据的线性组合。这样很明显没有办法模拟出非线性的情况。记得神经网络是可以拟合任意函数的。好了,既然需要非线性函数,那干脆加上非线性变换就好了。一般会使用sigmoid函数,得到,这个函数会把数据压缩到开区间(0,1),函数的图像如下

 

可以看到,函数的两侧非常平滑,而且无限的接近0和1,仅仅是中间部分函数接近一条直线,顺便说一下,这个函数的导数最值竟然真的是1啊,也就是x=0的位置,因而称这个函数是双端饱和的,而且它是处处可导。那么,为什么要选用它呢?看到有资料说是模拟神经学科的,但我不太懂这个,个人认为是因为(1)可以引入非线性(2)容易求导(3)可以把数据压缩,这样数据不容易发散。

另外,有一个函数与sigmoid函数很类似,就是tanh()函数,它可以把数据压缩到(-1,1)之间。

但是,我们要讲的是梯度的消失问题哦,要知道,神经网络训练的方法是BP算法(不知道还有没有其他的训练方法。。。)。BP算法的基础其实就是导数的链式法则,这个估计不需要细说了,就是有很多乘法会连接在一起。在看看sigmoid函数的图像就知道了,导数最大是1,而且大多数值都被推向两侧饱和的区域,这些区域的导数可是很小的呀~~~~可以预见到,随着网络的加深,梯度后向传播到浅层网络时,就呵呵了,基本不能引起数值的扰动,这样浅层的网络就学习不到新的特征了。

那么怎么办?我暂时看到了四种解决问题的办法,仅仅是根据我自己看的论文总结的,并非权威的说法。第一种很明显,可以通过使用别的激活函数;第二种可以使用层归一化;第三种是在权重的初始化上下功夫,第四种是构建新的网络结构~。但暂时不写,我还想记录一下看到的梯度消失/爆炸问题在另一个经典网络的出现。

======================

噔噔噔噔,对的,就是RNN。

RNN网络简单来说,就是把上层的hidden state与输入数据一同输入到神经元中进行处理(如左图),它是与序列相关的。如果把网络按照时间序列展开,可以得到右图

                

假如要求偏导数,可以看到一个连乘的式子,元素是,假如大于1,经过k个乘法后会变得异常巨大,毕竟是指数级的,如果小于1,又会变得十分小。这就是RNN中梯度爆炸与消失的问题了。

 贴一个RNN的代码,有注释,很容易看明白,来自这里

 RNN

import copy, numpy as np
np.random.seed(0)# compute sigmoid nonlinearity
def sigmoid(x):output = 1/(1+np.exp(-x))return output# convert output of sigmoid function to its derivative
def sigmoid_output_to_derivative(output):return output*(1-output)# training dataset generation
int2binary = {}
binary_dim = 8largest_number = pow(2,binary_dim)
binary = np.unpackbits(np.array([range(largest_number)],dtype=np.uint8).T,axis=1)
for i in range(largest_number):int2binary[i] = binary[i]# input variables
alpha = 0.1
input_dim = 2
hidden_dim = 16
output_dim = 1# initialize neural network weights
synapse_0 = 2*np.random.random((input_dim,hidden_dim)) - 1
synapse_1 = 2*np.random.random((hidden_dim,output_dim)) - 1
synapse_h = 2*np.random.random((hidden_dim,hidden_dim)) - 1synapse_0_update = np.zeros_like(synapse_0)
synapse_1_update = np.zeros_like(synapse_1)
synapse_h_update = np.zeros_like(synapse_h)# training logic
for j in range(10000):# generate a simple addition problem (a + b = c)a_int = np.random.randint(largest_number/2) # int versiona = int2binary[a_int] # binary encodingb_int = np.random.randint(largest_number/2) # int versionb = int2binary[b_int] # binary encoding# true answerc_int = a_int + b_intc = int2binary[c_int]# where we'll store our best guess (binary encoded)d = np.zeros_like(c)overallError = 0layer_2_deltas = list()layer_1_values = list()layer_1_values.append(np.zeros(hidden_dim))# moving along the positions in the binary encodingfor position in range(binary_dim):# generate input and outputX = np.array([[a[binary_dim - position - 1],b[binary_dim - position - 1]]])y = np.array([[c[binary_dim - position - 1]]]).T# hidden layer (input ~+ prev_hidden)layer_1 = sigmoid(np.dot(X,synapse_0) + np.dot(layer_1_values[-1],synapse_h))# output layer (new binary representation)layer_2 = sigmoid(np.dot(layer_1,synapse_1))# did we miss?... if so, by how much?layer_2_error = y - layer_2layer_2_deltas.append((layer_2_error)*sigmoid_output_to_derivative(layer_2))overallError += np.abs(layer_2_error[0])# decode estimate so we can print it outd[binary_dim - position - 1] = np.round(layer_2[0][0])# store hidden layer so we can use it in the next timesteplayer_1_values.append(copy.deepcopy(layer_1))future_layer_1_delta = np.zeros(hidden_dim)for position in range(binary_dim):X = np.array([[a[position],b[position]]])layer_1 = layer_1_values[-position-1]prev_layer_1 = layer_1_values[-position-2]# error at output layerlayer_2_delta = layer_2_deltas[-position-1]# error at hidden layerlayer_1_delta = (future_layer_1_delta.dot(synapse_h.T) + layer_2_delta.dot(synapse_1.T)) * sigmoid_output_to_derivative(layer_1)# let's update all our weights so we can try againsynapse_1_update += np.atleast_2d(layer_1).T.dot(layer_2_delta)synapse_h_update += np.atleast_2d(prev_layer_1).T.dot(layer_1_delta)synapse_0_update += X.T.dot(layer_1_delta)future_layer_1_delta = layer_1_deltasynapse_0 += synapse_0_update * alphasynapse_1 += synapse_1_update * alphasynapse_h += synapse_h_update * alpha    synapse_0_update *= 0synapse_1_update *= 0synapse_h_update *= 0# print out progressif(j % 1000 == 0):print "Error:" + str(overallError)print "Pred:" + str(d)print "True:" + str(c)out = 0for index,x in enumerate(reversed(d)):out += x*pow(2,index)print str(a_int) + " + " + str(b_int) + " = " + str(out)print "------------"

 

二、选择其他激活函数

 激活函数有很多其他的选择,常看见的有ReLU、Leaky ReLU函数。下面就说一说这两个函数,对于ReLU,函数为,观察一下它的函数图像

可以看到,它在负数的一段永远是0啊。为什么要使用这个函数呢?据说它是与神经学科有关的,这是因为稀疏激活的,这表现在负数端是抑制状态,正数兴奋激活。而且有理论也表明,稀疏的网络更准确,在googLeNet的实现中就是利用了神经网络的稀疏性。而且,在正数端导数永远为1,这就很好地解决了梯度消失的问题了。可是,它没有把数据压缩,这会使得数据的范围可能很大。

此外还有Leaky ReLU函数,这个我是在YOLO看到的,其实和ReLU差不多,就是在负数端不完全抑制了。图像如下:

 

三、层归一化

 这里记录的是Batch Normalization。主要参考(1)(2)(3)写的总结,可怜我只是个搬运工啊。

 先说一说BN解决的问题,论文说要解决 Internal covariate shift 的问题,covariate shift 是指源空间与目标空间中条件概率一致,但是边缘概率不同。在深度网络中,越深的网络对特征的扭曲就越厉害(应该是这样说吧……),但是特征本身对于类别的标记是不变的,所以符合这样的定义。BN通过把输出层的数据归一化到mean = 0, var = 1的分布中,可以让边缘概率大致相同吧(知乎魏大牛说不可以完全解决,因为均值方差相同不代表分布相同~~他应该是对的),所以题目说是reducing。

那么BN是怎么实现的呢?它是通过计算min batch 的均值与方差,然后使用公式归一化。例如激活函数是sigmoid,那么输出归一化后的图像就是

中间就是接近线性了,这样,导数几乎为常数1,这样不就可以解决梯度消失的问题了吗?

但是,对于ReLU函数,这个是否起作用呢?好像未必吧,不过我觉得这个归一化可以解决ReLU不能把数据压缩的问题,这样可以使得每层的数据的规模基本一致了。上述(3)中写到一个BN的优点,我觉得和我的想法是一致的,就是可以使用更高的学习率。如果每层的scale不一致,实际上每层需要的学习率是不一样的,同一层不同维度的scale往往也需要不同大小的学习率,通常需要使用最小的那个学习率才能保证损失函数有效下降,Batch Normalization将每层、每维的scale保持一致,那么我们就可以直接使用较高的学习率进行优化。这样就可以加快收敛了。我觉得还是主要用来减少covariate shift 的。

但是,上述归一化会带来一个问题,就是破坏原本学习的特征的分布。那怎么办?论文加入了两个参数,来恢复它本来的分布这个带入归一化的式子看一下就可以知道恢复原来分布的条件了。但是,如果恢复了原来的分布,那还需要归一化?我开始也没想明白这个问题,后来看看别人的解释,注意到新添加的两个参数,实际上是通过训练学习的,就是说,最后可能恢复,也可能没有恢复。这样可以增加网络的capicity,网络中就存在多种不同的分布了。最后抄一下BP的公式:

 

 那么在哪里可以使用这个BN?很明显,它应该使用在激活函数之前。而在此处还提到一个优点就是加入BN可以不使用dropout,为什么呢?dropout它是用来正则化增强网络的泛化能力的,减少过拟合,而BN是用来提升精度的,之所以说有这样的作用,可能有两方面的原因(1)过拟合一般发生在数据边缘的噪声位置,而BN把它归一化了(2)归一化的数据引入了噪声,这在训练时一定程度有正则化的效果。对于大的数据集,BN的提升精度会显得更重要,这两者是可以结合起来使用的。

最后贴一个算法的流程,以及结构图,结构图是来自   http://yeephycho.github.io/2016/08/03/Normalizations-in-neural-networks/   

       

 

四、权值初始化

 为了让信息可以更好的在网络中流动(不一定是梯度消失的问题),可以使用xavier的初始化方法。主要可以看知乎专栏。为了不重复别人的工作,我简单总结一下算了。注意一个问题,xavier的初始化方法的前提假设是,激活函数是线性的(其实归一化后,可能把数据集中在了一处,就好像将BN的那张图一样)。

如果输入数据x和权值w都满足均值为0,标准差为,(x可以通过归一化、白化实现)而且各数据是独立同分布的。这样,输出为,根据概率公式,z 的均值依然为0,方差为

通过递推公式可以得到,则。于是,方差的计算公式为。这里又出现了连乘,还是按照之前与1比较的讨论,那么,最好是可以让方差保持一致啦,这样数值的幅度就不会相差太大,就好像上面BN说的那样,可以收敛的更快。那么就是让连乘内的每一项都为1了,则可以推出权值的初始化为

上面说的是前向的,那么后向呢?后向传播时,如果可以让方差保持一致,同样地会有前向传播的效果,梯度可以更好地在网络中流动。由于假设是线性的,那么回流的梯度公式是,可以写成。令回流的方差不变,那么权值又可以初始化成。注意一个是前向,一个是后向的,两者的n 是不同的。取平均

最后,是使用均匀分布来初始化权值的,得到初始化的范围

另外一种MSRA的初始化的方法,可以学习http://blog.csdn.net/shuzfan/article/details/51347572,实验效果表现要好一些,但貌似xavier用的要多一些。

 

五、调整网络的结构

 解决RNN的问题,提出了一种LSTM的结构,但我对LSTM还不是太熟悉,就不装逼了。主要是总结最近看的两篇文章《Training Very Deep Networks》和《Deep Residual Learning for Image Recognition》。

Highway Network

 Highway Network主要解决的问题是,网络深度加深,梯度信息回流受阻造成网络训练困难的问题。先看下面的一张对比图片,分别是没有highway 和有highway的。

可以看到,当网络加深,训练的误差反而上升了,而加入了highway之后,这个问题得到了缓解。一般来说,深度网络训练困难是由于梯度回流受阻的问题,可能浅层网络没有办法得到调整,或者我自己YY的一个原因是(回流的信息经过网络之后已经变形了,很可能就出现了internal covariate shift类似的问题了)。Highway Network 受LSTM启发,增加了一个门函数,让网络的输出由两部分组成,分别是网络的直接输入以及输入变形后的部分。

假设定义一个非线性变换为,定义门函数,携带函数。对于门函数取极端的情况0/1会有,而对应的门函数使用sigmoid函数,则极端的情况不会出现。

一个网络的输出最终变为,注意这里的乘法是element-wise multiplication。

注意,门函数,转换的维度应该是相同的。如果不足,可以用0补或者用一个卷积层去变化。

在初始化的时候,论文是把偏置 b 初始化为负数,这样可以让携带函数 C 偏大,这样做的好处是什么呢?可以让更多的信息直接回流到输入,而不需要经过一个非线性转化。我的理解是,在BP算法时,这一定程度上增大了梯度的回流,而不会被阻隔;在前向流动的时候,把允许原始的信息直接流过,增加了容量,就好像LSTM那样,可以有long - term temporal dependencies。

 Residual Network

 ResNet的结构与Highway很类似,如果把Highway的网络变一下形会得到,而在ResNet中,直接把门函数T(x)去掉,就得到一个残差函数,而且会得到一个恒等的映射 x ,对的,这叫残差网络,它解决的问题与Highway一样,都是网络加深导致的训练困难且精度下降的问题。残差网络的一个block如下:

是的,就是这么简单,但是,网络很强大呀。而且实验证明,在网络加深的时候,依然很强大。那为什么这么强大呢?我觉得是因为identity map是的梯度可以直接回流到了输入层。至于是否去掉门函数会更好呢,这个并不知道。在作者的另一篇论文《Identity Mappings in Deep Residual Networks》中,实验证明了使用identity map会比加入卷积更优。而且通过调整激活函数和归一化层的位置到weight layer之前,称为 pre-activation,会得到更优的结果。

对于网络中的一些虚线层,他们的shortcut就连接了两个维度不同的feature,这时,有两种解决办法(1)在维度减少的部分直接使用 identity 映射,同时对于feature map增加部分用0补齐。(2)通过1*1的卷积变形得到。对于这个1*1的投影是怎么做的,可以参考VGG-16。我开始也很纳闷,例如上面的虚线,输入有64个Feature,输出是128个Feature,如果是用128个kernel做卷积,应该有64*128个feature啊。纠结很久,看了看VGG的参数个数就明白了,如下图

 

 

 

 

例如第一、二行,输入3个Feature,有64个卷积核但却有64个输出,是怎么做到的呢?看它的权值的个数的计算时(3*3*3)*64,也就是说,实际上这64个卷积核其实是有3维的通道的。对应于ResNet的64个输入,同样卷积核也是有64个channel的。

这篇关于【CV知识学习】神经网络梯度与归一化问题总结+highway network、ResNet的思考的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/193635

相关文章

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

好题——hdu2522(小数问题:求1/n的第一个循环节)

好喜欢这题,第一次做小数问题,一开始真心没思路,然后参考了网上的一些资料。 知识点***********************************无限不循环小数即无理数,不能写作两整数之比*****************************(一开始没想到,小学没学好) 此题1/n肯定是一个有限循环小数,了解这些后就能做此题了。 按照除法的机制,用一个函数表示出来就可以了,代码如下

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

sqlite3 相关知识

WAL 模式 VS 回滚模式 特性WAL 模式回滚模式(Rollback Journal)定义使用写前日志来记录变更。使用回滚日志来记录事务的所有修改。特点更高的并发性和性能;支持多读者和单写者。支持安全的事务回滚,但并发性较低。性能写入性能更好,尤其是读多写少的场景。写操作会造成较大的性能开销,尤其是在事务开始时。写入流程数据首先写入 WAL 文件,然后才从 WAL 刷新到主数据库。数据在开始

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

购买磨轮平衡机时应该注意什么问题和技巧

在购买磨轮平衡机时,您应该注意以下几个关键点: 平衡精度 平衡精度是衡量平衡机性能的核心指标,直接影响到不平衡量的检测与校准的准确性,从而决定磨轮的振动和噪声水平。高精度的平衡机能显著减少振动和噪声,提高磨削加工的精度。 转速范围 宽广的转速范围意味着平衡机能够处理更多种类的磨轮,适应不同的工作条件和规格要求。 振动监测能力 振动监测能力是评估平衡机性能的重要因素。通过传感器实时监