A. ResNet网络
ResNet (Resial Neural Network,残差网络)由微软研究院何凯明等人提出的,通过在深度神经网络中加入残差单元(Resial Unit)使得训练深度比以前更加高效。ResNet在2015年的ILSVRC比赛中夺得冠军,ResNet的结构可以极快的加速超深神经网络的训练,模型准确率也有非常大的提升。
在ResNet之前,瑞士教授Schimidhuber提出了Highway Network,其原理与ResNet非常相似。通常认为神经网络的深度对其性能非常重要,但是网络越深训练越困难,Highway Network的目标就是解决极深的神经网络难以训练的问题。
Highway Network相当于修改了每一层激活函数,此前激活函数只是对输入做一次非线性变换y=H(x, Wh), 而Highway Network则允许保留一部分比例的原始输入x,即y=H(x, Wh)* T(x , Wt)+x*C(x, Wc),其中T为变换系数,C为保留系数,论文中令C=1-T。这样前面一层的信息,有一定比例可以不经过矩阵乘法和非线性变换,直接传输到下一层,仿佛一条信息高速公路,因此得名Highway Network。
结果显示,B比A略好,这是因为A中的零填充确实没有残差学习。而C比B稍好,这是由于投影快捷连接引入了额外参数。但A、B、C之间的细微差异表明投影连接对于解决退化问题不是至关重要的,而不/少使用投影连接可以减少内存/时间复杂性和模型大小。而且无参数恒等快捷连接对于瓶颈架构(3层残差学习单元)尤为重要,因为瓶颈架构中层具有较小的输入输出,快捷连接是连接到两个高维端,此时恒等快捷连接无需参数,而使用投影的话则会显示时间和模型复杂度加倍。因此,恒等快捷连接可以为瓶颈设计得到更有效的模型。
最后,作者尝试了更深的1000层以上的神经网络,发现神经网络仍然能够较好的学习,但是其测试误差比100多层的残差网络要差,而训练误差则与100多层的残差网络相似,作者认为这可能是由于过拟合导致的,可通过加大正则化来解决这一问题。
在ResNet V1中,作者研究通过加入残差单元使得训练深度达到上百层的神经网络成为可能,解决了梯度消失/爆炸的问题。而在ResNet V2中作者进一步证明了恒等映射(Identity mapping)的重要性。同时作者还提出了一种新的残差单元(采用了预激活)使得训练变得更简单,同时还提高了模型的泛化能力。
在ResNet V2中,作者提出了不止在残差单元内部,而是在整个神经网络中都创建了‘直接’的计算传播路径。在ResNet V1中,残差学习单元的
上式同样表明了在一个mini-batch中不可能出现梯度消失的现象,因为上式求导的第二部分对于一个mini-batch来说,不可能所有样本其导数都为-1,因此,可能会出现权重很小的情况,但是不会出现梯度消失的情况。
通过研究这些不同的快捷连接,作者发现大部分快捷连接方式无法很好地收敛,其中很大部分是由于使用这些快捷连接后或多或少会出现梯度消失或者梯度爆炸的现象,最后结果显示恒等映射效果最好。
虽然恒等映射在这些方法中表写结果最好,仍需引起注意的是1×1的卷积捷径连接引入了更多的参数,本应该比恒等捷径连接具有更加强大的表达能力。事实上,shortcut-only gating 和1×1的卷积涵盖了恒等捷径连接的解空间(即,他们能够以恒等捷径连接的形式进行优化)。然而,它们的训练误差比恒等捷径连接的训练误差要高得多,这表明了这些模型退化问题的原因是优化问题,而不是表达能力的问题。
在上图b中,采用先加后BN再激活的方法,此时f(x)就包含了BN和ReLU。这样的结果比原始a要差。这主要是因为BN层改变了流经快捷连接的信号,阻碍了信息的传递。
在c中,ReLU在相加之前,此时f(x)=x,为恒等映射。此时残差单元中的F(x)输出经由ReLU后变为非负,然而一个“残差”函数的输出应该是(−∞,+∞) 的。造成的结果就是,前向传递的信号是单调递增的。这会影响表达能力,结果也变得更差了。
结果显示,只使用ReLU预激活(d)的结果与原始ResNet结果很接近,这个与ReLU层不与BN层连接使用,因此无法获得BN所带来的好处。而当BN和ReLU都使用在预激活上时(e),结果得到了可观的提升。
预激活的影响有两个方面:第一,由于f(x)也是恒等映射,相比于V1优化变得更加简单;第二,在预激活中使用BN能提高模型的正则化。
对于f(x)为恒等映射的好处:一方面若使用f= ReLU,如果信号是负的时候会造成一定的影响,无法传递有用的负信号,而当残差单元很多时,这个影响将会变得尤为突出;另一方面当f是一个恒等映射时,信号在两个单元间能够很直接的传递。
在ResNet V1中作者提出了残差学习单元,并从理论和实验上证明使用直连的shortcuts有助于解决深度达到上百层的神经网络的训练问题。而在ResNet V2中作者证明了在shortcuts中使用直接映射(即H(x) = h(x) + F(x)中h(x) = x)得到的效果最好。在ResNext中作者将bottleneck拆分成多个分支,提出了神经网络中的第三个维度(另外两个维度分别为depth,神经网络层数深度,width,宽度,channel数),命名为 Cardinality ,并在多个数据集中证明了将bottleneck拆分能够降低训练错误率和提高准确率。
ResNext的灵感来源于VGG/ResNet和Inception:(1)在VGG、ResNet中,作者使用了相同结构的卷积层进行了堆叠,构建了层数很深但是结构简单的神经网络;(2)而在Inception中,提出了一种叫做 split-transform-merge 的策略,将输入(采用1x1 卷积核)分裂为几个低维 embedding,再经过一系列特定卷积层的变换,最后连接在一起。
而在ResNet中,作者将原ResNet bottleneck中的一条path拆分为多个分支(multi branch),以此分支数量提出神经网络中的第三个重要维度——Cardinality。这一想法结合了VGG中的相同结构堆叠和Inception中的split-transform-merge策略,即如上图所示,每个bottleneck 拆分为多个分支进行堆叠,这些分支的结构相同(这里借鉴了VGG的思想),而具体到分支的结构时又采用了Inception的split-transform-merge策略。与Inception不同的是Inception的每个分支结构都是需要认为的设计,而在ResNext中每个分支结构都相同。最终每个bottleneck的输出就变成了:
这些所有的bottlenecks结构都遵循两个原则:
作者提出了 三种效果相同的ResNext的表示方法,如下图所示:
其中a,b 结构相似,只是在merge这一步的地方不同,而c则借鉴了AlexNet中分组卷积的思想,将输入和输出都分为多个组。
作者首先评估权衡了cardinality和width的关系。
接着,作者又评估了使用增加cardinality和depth/width来增加模型复杂度后的效果:
最后,作者还研究了shortcuts对于ResNext的重要性,在ResNet-50中,不使用shortcuts准确率下降了7%,而在ResNext-50中准确率也下降了4%,说明shortcuts对于残差网络来说确实是非常重要的。
简言之,增加cardinality比增加depth和width效果要好,同时,shortcuts对于模型的准确率也是至关重要的。
参考:
Deep Resial Learning for Image Recognition.
Aggregated Resial Transformations for Deep Neural Networks.
Identity Mappings in Deep Resial Networks.
ResNet论文翻译——中文版
Identity Mappings in Deep Resial Networks(译)
TensorFlow实现经典卷积网络. 黄文坚,唐源
B. 十分钟一起学会ResNet残差网络
深度卷积网络自然的整合了低中高不同层次的特征,特征的层次可以靠加深网络的层次来丰富。从而,在构建卷积网络时,网络的深度越高,可抽取的特征层次就越丰富。所以一般我们会倾向于使用更深层次的网络结构,以便取得更高层次的特征。但是在使用深层次的网络结构时我们会遇到两个问题,梯度消失,梯度爆炸问题和网络退化的问题。
但是当使用更深层的网络时,会发生梯度消失、爆炸问题,这个问题很大程度通过标准的初始化和正则化层来基本解决,这样可以确保几十层的网络能够收敛,但是随着网络层数的增加,梯度消失或者爆炸的问题仍然存在。
还有一个问题就是网络的退化,举个例子,假设已经有了一个最优化的网络结构,是18层。当我们设计网络结构的时候,我们并不知道具体多少层次的网络时最优化的网络结构,假设设计了34层网络结构。那么多出来的16层其实是冗余的,我们希望训练网络的过程中,模型能够自己训练这五层为恒等映射,也就是经过这层时的输入与输出完全一样。但是往往模型很难将这16层恒等映射的参数学习正确,那么就一定会不比最优化的18层网络结构性能好,这就是随着网络深度增加,模型会产生退化现象。它不是由过拟合产生的,而是由冗余的网络层学习了不是恒等映射的参数造成的。
ResNet是在2015年有何凯明,张翔宇,任少卿,孙剑共同提出的,ResNet使用了一个新的思想,ResNet的思想是假设我们涉及一个网络层,存在最优化的网络层次,那么往往我们设计的深层次网络是有很多网络层为冗余层的。那么我们希望这些冗余层能够完成恒等映射,保证经过该恒等层的输入和输出完全相同。具体哪些层是恒等层,这个会有网络训练的时候自己判断出来。将原网络的几层改成一个残差块,残差块的具体构造如下图所示:
可以看到X是这一层残差块的输入,也称作F(x)为残差,x为输入值,F(X)是经过第一层线性变化并激活后的输出,该图表示在残差网络中,第二层进行线性变化之后激活之前,F(x)加入了这一层输入值X,然后再进行激活后输出。在第二层输出值激活前加入X,这条路径称作shortcut连接。
我们发现,假设该层是冗余的,在引入ResNet之前,我们想让该层学习到的参数能够满足h(x)=x,即输入是x,经过该冗余层后,输出仍然为x。但是可以看见,要想学习h(x)=x恒等映射时的这层参数时比较困难的。ResNet想到避免去学习该层恒等映射的参数,使用了如上图的结构,让h(x)=F(x)+x;这里的F(x)我们称作残差项,我们发现,要想让该冗余层能够恒等映射,我们只需要学习F(x)=0。学习F(x)=0比学习h(x)=x要简单,因为一般每层网络中的参数初始化偏向于0,这样在相比于更新该网络层的参数来学习h(x)=x,该冗余层学习F(x)=0的更新参数能够更快收敛,如图所示:
假设该曾网络只经过线性变换,没有bias也没有激活函数。我们发现因为随机初始化权重一般偏向于0,那么经过该网络的输出值为[0.6 0.6],很明显会更接近与[0 0],而不是[2 1],相比与学习h(x)=x,模型要更快到学习F(x)=0。
并且ReLU能够将负数激活为0,过滤了负数的线性变化,也能够更快的使得F(x)=0。这样当网络自己决定哪些网络层为冗余层时,使用ResNet的网络很大程度上解决了学习恒等映射的问题,用学习残差F(x)=0更新该冗余层的参数来代替学习h(x)=x更新冗余层的参数。
这样当网络自行决定了哪些层为冗余层后,通过学习残差F(x)=0来让该层网络恒等映射上一层的输入,使得有了这些冗余层的网络效果与没有这些冗余层的网络效果相同,这样很大程度上解决了网络的退化问题。
我们发现很深的网络层,由于参数初始化一般更靠近0,这样在训练的过程中更新浅层网络的参数时,很容易随着网络的深入而导致梯度消失,浅层的参数无法更新。
可以看到,假设现在需要更新 参数因为随机初始化偏向于0,通过链式求导我们会发现, 相乘会得到更加接近于0的数,那么所求的这个 的梯度就接近于0,也就产生了梯度消失的现象。
ResNet最终更新某一个节点的参数时,由于 ,由于链式求导后的结果如图所示,不管括号内右边部分的求导参数有多小,因为左边的1的存在,并且将原来的链式求导中的连乘变成了连加状态(正是 ),都能保证该节点参数更新不会发生梯度消失或梯度爆炸现象。
这样ResNet在解决了阻碍更深层次网络优化问题的两个重要问题后,ResNet就能训练更深层次几百层乃至几千层的网络并取得更高的精确度了。
这里是应用了ResNet的网络图,这里如果遇到了h(x)=F(x)+x中x的维度与F(x)不同的维度时,我们需要对identity加入Ws来保持Ws*x的维度与F(x)的维度一致。
x与F(x)维度相同时:
x与F(x)维度不同时:
下边是ResNet的网络结构图:
使用1*1卷积减少参数和计算量:
如果用了更深层次的网络时,考虑到计算量,会先用1 * 1的卷积将输入的256维降到64维,然后通过1*1恢复。这样做的目的是减少参数量和计算量。
左图是ResNet34,右图是ResNet50/101/152。这一个模块称作building block,右图称之为bottleneck design。在面对50,101,152层的深层次网络,意味着有很大的计算量,因此这里使用1 * 1卷积先将输入进行降维,然后再经过3 * 3卷积后再用 卷积进行升维。使用1*1卷积的好处是大大降低参数量计算量。
通过上述的学习,你应该知道了,现如今大家普遍认为更好的网络是建立在更宽更深的网络基础上,当你需要设计一个深度网络结构时,你永远不知道最优的网络层次结构是多少层,一旦你设计的很深入了,那势必会有很多冗余层,这些冗余层一旦没有成功学习恒等变换 ,那就会影响网络的预测性能,不会比浅层的网络学习效果好从而产生退化问题。
ResNet的过人之处,是他很大程度上解决了当今深度网络头疼的网络退化问题和梯度消失问题。使用残差网络结构 代替原来的没有shortcut连接的 ,这样更新冗余层的参数时需要学习 比学习 要容易得多。而shortcut连接的结构也保证了反向传播更新参数时,很难有梯度为0的现象发生,不会导致梯度消失。
这样,ResNet的构建,使我们更朝着符合我们的直觉走下去,即越深的网络对于高级抽象特征的提取和网络性能更好,不用在担心随着网络的加深发生退化问题了。
近段时间,准备持续发表一些CNN常见的网络模型讲解。好了,今天的十分钟就带你一起学会ResNet,下次的十分钟我们再见。
C. 神经网络中的前向和后向算法
神经网络中的前向和后向算法
看了一段时间的深度网络模型,也在tf和theano上都跑了一些模型,但是感觉没有潜下去,对很多东西的理解都只停留在“这个是干什么的”层次上面。昨天在和小老师一起看一篇文章的时候,就被问到RNN里面的后向传播算法具体是怎么推。当时心里觉得BP算法其实很熟悉啊,然后在推导的过程中就一脸懵逼了。于是又去网上翻了翻相关内容,自己走了一遍,准备做个笔记,算是个交代。
准备一个神经网络模型,比如:
其中,[i1,i2]
代表输入层的两个结点,[h1,h2]代表隐藏层的两个结点,[o1,o2]为输出。[b1,b2]
为偏置项。连接每个结点之间的边已经在图中标出。
来了解一下前向算法:
前向算法的作用是计算输入层结点对隐藏层结点的影响,也就是说,把网络正向的走一遍:输入层—->隐藏层—->输出层
计算每个结点对其下一层结点的影响。
?? 例如,我们要算结点h1
的值,那么就是:
是一个简单的加权求和。这里稍微说一下,偏置项和权重项的作用是类似的,不同之处在于权重项一般以乘法的形式体现,而偏置项以加法的形式体现。
??而在计算结点o1时,结点h1的输出不能简单的使用neth1的结果,必须要计算激活函数,激活函数,不是说要去激活什么,而是要指“激活的神经元的特征”通过函数保留并映射出来。以sigmoid函数为例,h1的输出:
于是
最后o1的输出结果,也就是整个网络的一个输出值是:
按照上面的步骤计算出out02,则[outo1,outo2]就是整个网络第一次前向运算之后得到的结果。
后向算法:
??在实际情况中,因为是随机给定的权值,很大的可能(几乎是100%)得到的输出与实际结果之间的偏差非常的大,这个时候我们就需要比较我们的输出和实际结果之间的差异,将这个残差返回给整个网络,调整网络中的权重关系。这也是为什么我们在神经网络中需要后向传播的原因。其主要计算步骤如下:
1. 计算总误差
2. 隐藏层的权值更新
在要更新每个边的权重之前,必须要知道这条边对最后输出结果的影响,可以用整体误差对w5求偏导求出:
具体计算的时候,可以采用链式法则展开:
在计算的时候一定要注意每个式子里面哪些自变量是什么,求导千万不要求错了。
??需要讲出来的一个地方是,在计算w1的权重时,Etotal中的两部分都需要对它进行求导,因为这条边在前向传播中对两个残差都有影响
3. 更新权重 这一步里面就没什么东西了,直接根据学习率来更新权重:
至此,一次正向+反向传播过程就到此为止,接下来只需要进行迭代,不断调整边的权重,修正网络的输出和实际结果之间的偏差(也就是training整个网络)。
D. GBDT算法
对于AdaBoost,可以将其视为一个将多个弱分类器线性组合后对数据进行预测的算法,该模型可以表示为:
为基函数(即单个弱分类器), 为基函数的参数(即弱分类器中特征的权重向量), 为基函数的系数(即弱分类器在线性组合时的权重), 就是基函数的线性组合。
给定训练数据和损失函数 的条件下,构建最优加法模型 的问题等价于损失函数最小化:
这个公式展现了AdaBoost算法的核心过程。
我们可以利用前向分布算法来求解上一个式子的最优参数。前向分布算法的核心是 从前向后,每一步计算一个基函数及其系数,逐步逼近优化目标函数式 ,就可以简化优化的复杂度。
M-1个基函数的加法模型为:
M个基函数的加法模型:
由上面两式得:
由这个公式和公式(2)得极小化损失函数:
算法思路如下:
1. 初始化
2. 对m=1,2,...,M:
a. 极小化损失函数: 得到参数
b. 更新:
3. 得到加法模型:
这样,前向分布算法将同时求解从m=1到M所有参数 的优化问题化简为逐次求解各个 的优化问题。
Freidman提出了梯度提升算法,算法的核心是利用损失函数的负梯度将当前模型的值作为回归问题提升树算法中的残差的近似值,去拟合一个回归树。
GBDT的思想就是不断去拟合残差,使残差不断减少。用一个通俗的例子来讲假如有个人30岁,我们首先用20岁去拟合,发现损失有10岁,这时我们用6岁去拟合剩下的损失,发现差距还有4岁,第三轮我们用3岁拟合剩下的差距,差距就只有一岁了。如果我们的迭代轮数还没有完,可以继续迭代下面,每一轮迭代,拟合的岁数误差都会减小。(参考 集成学习之Boosting-gbdt )GBDT中每次迭代构造的Cart树都是用前一轮的残差拟合的。
第t轮第i个样本的损失函数的负梯度表示为:
利用 我们可以拟合一颗CART回归树,得到了第t颗回归树,其对应的叶节点区域 其中J为叶子节点个数。
针对每一个叶子节点里的样本,我们求出使损失函数最小的 输出值 :
这样我们就得到了本轮的决策树拟合函数:
从而本轮最终得到的强学习器的表达式如下:
通过损失函数的负梯度来拟合,是一种通用的拟合损失误差的办法。无论是分类问题还是回归问题,我们都可以通过其损失函数的负梯度的拟合,从而用GBDT来解决我们的分类和回归问题。区别仅仅在于损失函数不同导致的负梯度不同而已。
d. 分位数损失:它对应的是分位数回归的损失函数。
输入: 训练样本
迭代次数(基学习器数量): T
损失函数: L
输出: 强学习器H(x)
算法流程
对于二元GBDT,其对数损失函数在之前已经给出:
其中 此时的负梯度误差为:
对于生成的决策树,各个叶子节点的最佳负梯度拟合值为:
由于这个式子不易优化,一般使用近似值代替:
除了负梯度计算和叶子节点的最佳负梯度拟合的线性搜索,二分类GBDT与GBDT回归算法过程相同。
多分类GBDT的损失函数在之前也已经给出过:
样本属于第k类的概率 的表达式为:
结合上面两个式子可以求出第t轮第i个样本对应类别 l 的负梯度误差为:
可以看出,其实这里的误差就是样本i对应类别l的真实概率和t-1轮预测概率的差值。
对于生成的决策树,各个叶子节点的最佳负梯度拟合值为:
由于上式比较难优化,一般用近似值代替:
除了负梯度计算和叶子节点的最佳负梯度拟合的线性搜索,多分类GBDT与二分类GBDT以及GBDT回归算法过程相同。
为了防止GBDT过拟合,需要对其进行正则化。主要有三种方式:
1. 给每棵树的输出结果乘上一个步长(学习率) a 。之前的弱学习器的迭代式改为:
2. 通过子采样比例进行正则化。GBDT每一轮建树时样本从原始训练集中采用无放回随机抽样的方式产生。若采样比例取1,则采用全部样本进行训练。为了防止过拟合,同时又要防止样本拟合偏差较大(欠拟合),要合理取值,常用 [0.5, 0.8]
3. 对弱学习器(CART)进行正则化剪枝:控制树的最大深度、节点最少样本数、最大叶子节点数、节点分支的最小样本数等。
优点 :
缺点 :
由于弱学习器之间存在依赖关系,难以并行训练数据
boosting框架相关参数 :
弱学习器参数 :
GBDT的适用面非常广,几乎可以用于所有回归问题(线性/非线性),也可以用于分类问题。
E. GBDT —— 梯度提升决策树
GBDT(Gradient Boosting Decision Tree) 又叫 MART(Multiple Additive Regression Tree),是一种迭代的决策树算法,该算法由多棵决策树组成,所有树的结论累加起来做最终答案。它在被提出之初就和SVM一起被认为是泛化能力较强的算法。
GBDT中的树是回归树(不是分类树),GBDT用来做回归预测,调整后也可以用于分类。
GBDT主要由三个概念组成:Regression Decistion Tree(即DT),Gradient Boosting(即GB),Shrinkage (算法的一个重要演进分枝,目前大部分源码都按该版本实现)。搞定这三个概念后就能明白GBDT是如何工作的。
提起决策树(DT, Decision Tree) 绝大部分人首先想到的就是C4.5分类决策树。但如果一开始就把GBDT中的树想成分类树,那就错了。千万不要以为GBDT是很多棵分类树。决策树分为两大类,回归树和分类树。前者用于预测实数值,如明天的温度、用户的年龄、网页的相关程度;后者用于分类标签值,如晴天/阴天/雾/雨、用户性别、网页是否是垃圾页面。这里要强调的是,前者的结果加减是有意义的,如10岁+5岁-3岁=12岁,后者则无意义,如男+男+女=到底是男是女?GBDT的核心在于累加所有树的结果作为最终结果,就像前面对年龄的累加(-3是加负3),而分类树的结果显然是没办法累加的,所以 GBDT中的树都是回归树,不是分类树 ,这点对理解GBDT相当重要(尽管GBDT调整后也可用于分类但不代表GBDT的树是分类树)。
回归树总体流程类似于分类树,区别在于,回归树的每一个节点都会得一个预测值,以年龄为例,该预测值等于属于这个节点的所有人年龄的平均值。分枝时穷举每一个feature的每个阈值找最好的分割点,但衡量最好的标准不再是最大熵,而是最小化平方误差。也就是被预测出错的人数越多,错的越离谱,平方误差就越大,通过最小化平方误差能够找到最可靠的分枝依据。分枝直到每个叶子节点上人的年龄都唯一或者达到预设的终止条件(如叶子个数上限), 若最终叶子节点上人的年龄不唯一,则以该节点上所有人的平均年龄做为该叶子节点的预测年龄。
回归树算法如下图(截图来自《统计学习方法》5.5.1 CART生成):
梯度提升(Gradient boosting)是一种用于回归、分类和排序任务的机器学习技术 [1] ,属于Boosting算法族的一部分。Boosting是一族可将弱学习器提升为强学习器的算法,属于集成学习(ensemble learning)的范畴。Boosting方法基于这样一种思想:对于一个复杂任务来说,将多个专家的判断进行适当的综合所得出的判断,要比其中任何一个专家单独的判断要好。通俗地说,就是“三个臭皮匠顶个诸葛亮”的道理。梯度提升同其他boosting方法一样,通过集成(ensemble)多个弱学习器,通常是决策树,来构建最终的预测模型。
Boosting、bagging和stacking是集成学习的三种主要方法。不同于bagging方法,boosting方法通过分步迭代(stage-wise)的方式来构建模型,在迭代的每一步构建的弱学习器都是为了弥补已有模型的不足。Boosting族算法的着名代表是AdaBoost,AdaBoost算法通过给已有模型预测错误的样本更高的权重,使得先前的学习器做错的训练样本在后续受到更多的关注的方式来弥补已有模型的不足。与AdaBoost算法不同,梯度提升方法在迭代的每一步构建一个能够沿着梯度最陡的方向降低损失(steepest-descent)的学习器来弥补已有模型的不足。经典的AdaBoost算法只能处理采用指数损失函数的二分类学习任务 [2] ,而梯度提升方法通过设置不同的可微损失函数可以处理各类学习任务(多分类、回归、Ranking等),应用范围大大扩展。另一方面,AdaBoost算法对异常点(outlier)比较敏感,而梯度提升算法通过引入bagging思想、加入正则项等方法能够有效地抵御训练数据中的噪音,具有更好的健壮性。这也是为什么梯度提升算法(尤其是采用决策树作为弱学习器的GBDT算法)如此流行的原因,
提升树是迭代多棵回归树来共同决策。当采用平方误差损失函数时,每一棵回归树学习的是之前所有树的结论和残差,拟合得到一个当前的残差回归树,残差的意义如公式:残差 = 真实值 - 预测值 。提升树即是整个迭代过程生成的回归树的累加。 GBDT的核心就在于,每一棵树学的是之前所有树结论和的残差,这个残差就是一个加预测值后能得真实值的累加量。
提升树利用 加法模型和前向分步算法 实现学习的优化过程。当损失函数时平方损失和指数损失函数时,每一步的优化很简单,如平方损失函数学习残差回归树。
提升方法其实是一个比adaboost概念更大的算法,因为adaboost可以表示为boosting的前向分布算法(Forward stagewise additive modeling)的一个特例,boosting最终可以表示为:
其中的w是权重,Φ是弱分类器(回归器)的集合,其实就是一个加法模型(即基函数的线性组合)
前向分布算法 实际上是一个贪心的算法,也就是在每一步求解弱分类器Φ(m)和其参数w(m)的时候不去修改之前已经求好的分类器和参数:
OK,这也就是提升方法(之前向分布算法)的大致结构了,可以看到其中存在变数的部分其实就是极小化损失函数 这关键的一步了,如何选择损失函数决定了算法的最终效果(名字)……这一步你可以看出算法的“趋势”,以后再单独把“趋势”拿出来说吧,因为我感觉理解算法的关键之一就是理解算法公式的“趋势”
不同的损失函数和极小化损失函数方法决定了boosting的最终效果,我们现在来说几个常见的boosting:
广义上来讲,所谓的Gradient Boosting 其实就是在更新的时候选择梯度下降的方向来保证最后的结果最好,一些书上讲的“残差” 方法其实就是L2Boosting吧,因为它所定义的残差其实就是L2Boosting的Derivative,接下来我们着重讲一下弱回归器(不知道叫啥了,自己编的)是决策树的情况,也就是GBDT。
GBDT算法可以看成是由K棵树组成的加法模型:
解这一优化问题,可以用前向分布算法(forward stagewise algorithm)。因为学习的是加法模型,如果能够从前往后,每一步只学习一个基函数及其系数(结构),逐步逼近优化目标函数,那么就可以简化复杂度。这一学习过程称之为Boosting。具体地,我们从一个常量预测开始,每次学习一个新的函数,过程如下:
举个例子,参考自一篇博客, 该博客举出的例子较直观地展现出多棵决策树线性求和过程以及残差的意义。
还是年龄预测,简单起见训练集只有4个人,A,B,C,D,他们的年龄分别是14,16,24,26。其中A、B分别是高一和高三学生;C,D分别是应届毕业生和工作两年的员工。如果是用一棵传统的回归决策树来训练,会得到如下图1所示结果:
现在我们使用GBDT来做这件事,由于数据太少,我们限定叶子节点做多有两个,即每棵树都只有一个分枝,并且限定只学两棵树。我们会得到如下图2所示结果:
在第一棵树分枝和图1一样,由于A,B年龄较为相近,C,D年龄较为相近,他们被分为两拨,每拨用平均年龄作为预测值。此时计算残差 (残差的意思就是: A的预测值 + A的残差 = A的实际值) ,所以A的残差就是16-15=1(注意,A的预测值是指前面所有树累加的和,这里前面只有一棵树所以直接是15,如果还有树则需要都累加起来作为A的预测值)。进而得到A,B,C,D的残差分别为-1,1,-1,1。然后我们拿残差替代A,B,C,D的原值,到第二棵树去学习,如果我们的预测值和它们的残差相等,则只需把第二棵树的结论累加到第一棵树上就能得到真实年龄了。这里的数据显然是我可以做的,第二棵树只有两个值1和-1,直接分成两个节点。此时所有人的残差都是0,即每个人都得到了真实的预测值。
换句话说,现在A,B,C,D的预测值都和真实年龄一致了。Perfect!:
A: 14岁高一学生,购物较少,经常问学长问题;预测年龄A = 15 – 1 = 14
B: 16岁高三学生;购物较少,经常被学弟问问题;预测年龄B = 15 + 1 = 16
C: 24岁应届毕业生;购物较多,经常问师兄问题;预测年龄C = 25 – 1 = 24
D: 26岁工作两年员工;购物较多,经常被师弟问问题;预测年龄D = 25 + 1 = 26
那么哪里体现了Gradient呢?其实回到第一棵树结束时想一想,无论此时的cost function是什么,是均方差还是均差,只要它以误差作为衡量标准,残差向量(-1, 1, -1, 1)都是它的全局最优方向,这就是Gradient。
讲到这里我们已经把GBDT最核心的概念、运算过程讲完了!没错就是这么简单。
该例子很直观的能看到,预测值等于所有树值得累加,如A的预测值 = 树1左节点 值 15 + 树2左节点 -1 = 14。
因此,给定当前模型 fm-1(x),只需要简单的拟合当前模型的残差。现将回归问题的提升树算法叙述如下:
答案是过拟合。过拟合是指为了让训练集精度更高,学到了很多”仅在训练集上成立的规律“,导致换一个数据集当前规律就不适用了。其实只要允许一棵树的叶子节点足够多,训练集总是能训练到100%准确率的(大不了最后一个叶子上只有一个instance)。在训练精度和实际精度(或测试精度)之间,后者才是我们想要真正得到的。
我们发现图1为了达到100%精度使用了3个feature(上网时长、时段、网购金额),其中分枝“上网时长>1.1h” 很显然已经过拟合了,这个数据集上A,B也许恰好A每天上网1.09h, B上网1.05小时,但用上网时间是不是>1.1小时来判断所有人的年龄很显然是有悖常识的;
相对来说图2的boosting虽然用了两棵树 ,但其实只用了2个feature就搞定了,后一个feature是问答比例,显然图2的依据更靠谱。(当然,这里是LZ故意做的数据,所以才能靠谱得如此狗血。实际中靠谱不靠谱总是相对的) Boosting的最大好处在于,每一步的残差计算其实变相地增大了分错instance的权重,而已经分对的instance则都趋向于0。这样后面的树就能越来越专注那些前面被分错的instance。就像我们做互联网,总是先解决60%用户的需求凑合着,再解决35%用户的需求,最后才关注那5%人的需求,这样就能逐渐把产品做好,因为不同类型用户需求可能完全不同,需要分别独立分析。如果反过来做,或者刚上来就一定要做到尽善尽美,往往最终会竹篮打水一场空。
Shrinkage(缩减)的思想认为,每次走一小步逐渐逼近结果的效果,要比每次迈一大步很快逼近结果的方式更容易避免过拟合。即它不完全信任每一个棵残差树,它认为每棵树只学到了真理的一小部分,累加的时候只累加一小部分,通过多学几棵树弥补不足。用方程来看更清晰,即
没用Shrinkage时:(yi表示第i棵树上y的预测值, y(1~i)表示前i棵树y的综合预测值)
y(i+1) = 残差(y1~yi), 其中: 残差(y1~yi) = y真实值 - y(1 ~ i)
y(1 ~ i) = SUM(y1, ..., yi)
Shrinkage不改变第一个方程,只把第二个方程改为:
y(1 ~ i) = y(1 ~ i-1) + step * yi
即Shrinkage仍然以残差作为学习目标,但对于残差学习出来的结果,只累加一小部分(step 残差)逐步逼近目标,step一般都比较小,如0.01~0.001(注意该step非gradient的step),导致各个树的残差是渐变的而不是陡变的。直觉上这也很好理解,不像直接用残差一步修复误差,而是只修复一点点,其实就是把大步切成了很多小步。 本质上,Shrinkage为每棵树设置了一个weight,累加时要乘以这个weight,但和Gradient并没有关系 *。 这个weight就是step。就像Adaboost一样,Shrinkage能减少过拟合发生也是经验证明的,目前还没有看到从理论的证明。
该版本GBDT几乎可用于所有回归问题(线性/非线性),相对logistic regression仅能用于线性回归,GBDT的适用面非常广。亦可用于二分类问题(设定阈值,大于阈值为正例,反之为负例)。
参考资料:
http://blog.csdn.net/w28971023/article/details/8240756
http://blog.csdn.net/dark_scope/article/details/24863289
https://www.jianshu.com/p/005a4e6ac775
https://www.zybuluo.com/yxd/note/611571
F. 残差网络ResNet笔记
作者根据输入将层表示为学习 残差函数 。实验表明,残差网络更容易优化,并且能够通过增加相当的深度来提高准确率。
核心是解决了增加深度带来的副作用(退化问题),这样能够通过单纯地增加网络深度,来提高网络性能。
网络的深度为什么重要?
因为CNN能够提取low/mid/high-level的特征,网络的层数越多,意味着能够提取到不同level的特征越丰富。并且,越深的网络提取的特征越抽象,越具有语义信息。
为什么不能简单地增加网络层数?
怎么解决退化问题?
深度残差网络。如果深层网络的后面那些层是恒等映射,那么模型就退化为一个浅层网络。那现在要解决的就是学习恒等映射函数了。 但是直接让一些层去拟合一个潜在的恒等映射函数H(x) = x,比较困难,这可能就是深层网络难以训练的原因。但是,如果把网络设计为H(x) = F(x) + x,如下图。我们可以转换为学习一个残差函数F(x) = H(x) - x. 只要F(x)=0,就构成了一个恒等映射H(x) = x. 而且,拟合残差肯定更加容易。
其他的参考解释
这种残差学习结构可以通过前向神经网络+shortcut连接实现,如结构图所示。而且shortcut连接相当于简单执行了同等映射,不会产生额外的参数,也不会增加计算复杂度。 而且,整个网络可以依旧通过端到端的反向传播训练。
ImageNet上的实验证明了作者提出的加深的残差网络能够比简单叠加层生产的深度网络更容易优化,而且,因为深度的增加,结果得到了明显提升。另外在CIFAR-10数据集上相似的结果以及一系列大赛的第一名结果表明ResNet是一个通用的方法。
F(x)与x相加就是就是逐元素相加,但是如果两者维度不同,需要给x执行一个线性映射来匹配维度:
用来学习残差的网络层数应当大于1,否则退化为线性。文章实验了layers = 2或3,更多的层也是可行的。
用卷积层进行残差学习: 以上的公式表示为了简化,都是基于全连接层的,实际上当然可以用于卷积层。加法随之变为对应channel间的两个feature map逐元素相加。
key point:
key point:
G. 语言模型介绍
语言模型(LM)是很多自然语言处理(NLP)任务的基础。语言模型是指对于语言序列 ,计算该序列的概率,即 ,这里的语言序列是有序的语言序列,后续计算也会体现这一点。一般我们认为一个正常的语句,它出现的概率是大于非正常的语句。比如有如下三个语句:
那么应当会有 和 ,这是因为 正常词序的语句会比乱序的语句更常见,正常含义的语句会比无意义的语句更常见 。
计算一个语言序列的概率,我们可以使用链式法则去计算
但该计算方法有两个缺陷:
我们能够建立语言模型了,一般的我们在训练集上得到语言模型的参数,在测试集里面来测试模型的性能,那么如何去衡量一个语言模型的好坏呢?比较两个模型A,B好坏,一种外在的评价就是将AB放入具体的任务中,然后分别得到模型的准确率,这种方式当然是最好的方式,但这种方式的缺点是过于耗时,在实际情况中往往需要花费过多时间才能得到结果。另一种方式是使用下面要介绍的困惑度,但注意困惑度并不是上述外在评价的一个好的近似,所以一般使用在试点试验上,所谓试点试验就是一个小规模的初步研究,以评估一些性能。
困惑度的基本评价方式是对测试集赋予高概率值的模型更好,一个句子W的困惑度(PP)定义如下:
S代表sentence,N是句子长度, 是第i个词的概率。第一个词就是 ,而 是<s>,表示句子的起始,是个占位符,事实上,结尾应该也有一个占位符,但这里好像没有写。
这个式子可以这样理解,PPL越小, 则越大,一句我们期望的sentence出现的概率就越高。
为了解决参数空间过大的问题。人们引入了马尔科夫假设:随意一个词出现的概率只与它前面出现的有限的一个或者几个词有关。
如果一个词的出现与它周围的词是独立的,那么我们就称之为unigram,也就是一元语言模型:
如果一个词的出现仅依赖于它前面出现的一个词,那么我们就称之为bigram:
一般来说,N元模型就是假设当前词的出现概率只与它前面的n-1个词有关。而这些概率参数都是可以通过大规模语料库来计算:
在实践中用的最多的就是bigram和trigram了,高于四元的用的非常少,由于训练它须要更庞大的语料,并且数据稀疏严重,时间复杂度高,精度却提高的不多。
那在实际计算中,我们怎么计算一个句子的概率呢?
以一元模型为例,在一元语言模型中,我们的句子概率定义为:
那么这里面的每个因子 该怎么计算呢?
这里使用频率统计的办法,由于一元模型认为每个词是相互独立的,所以统计的时候,只需要统计语料库中每个词出现的频率作为概率就可以了。
这里的计算可以认为是根据极大似然估计得到的,假如词典里有V个词,每个词对应一个概率,考虑到所有词出现的概率和是1,那就有V-1个参数 。假设词典表中第i词在语料库中出现的数目为 ,并且 那么根据极大似然估计就有:
取对数之后:
这个可以用多元函数极值去求解。不过这样进行多元极值求解不容易计算,如果用条件极值会容易计算,用拉格朗日乘数法进行求解。
求解得:
有了每个词的出现概率,带入到式子 中就可以计算出对应句子的概率。
对于二元模型或者多元模型,其计算方式有些区别,因为假设有些不同,假如我们需要计算 的值,那么统计频率的方式是:
那对于bigram中的第一个词的概率,由于他之前没有词汇,那这时候我们一般会认为句子的开头和结尾分别有一个抽象符号<s>和</s>,那么句子就变成了<s>, ,</s>,因此 式可以变为:
其余的n-gram模型也是类似的计算方法,有的地方说的是不要用添加开始和结尾的符号,直接用unigram和bigram的方法去计算 即可,这里暂时以斯坦福的课程为准吧,但是应该对最终影响有限,毕竟本质上是在做最大似然估计。
在上述 计算过程中,由于分子是词对出现的次数,那很有可能在语料库中没有出现这样的词对,这时计算结果就是0,同时也会导致句子的出现概率也为0,那这样有些不合理,即使写错了字,也应当有一定概率出现,所以在计算 式的时候要做一下平滑处理
其原理是保证每一个词对(对于bigram而言)都会出现一次,因此, 式可以修改为:
其中V是词的字典数目,这里分母加1是为了保证概率和为1,即 ,通俗理解为,我们往语料库中加入了 这V个词对,因此其分母语料库统计的数目也要加V。
Add-K平滑就是保证每个词对都出现K次,因此, 式可以修改为:
这里分母加KV和之前的模式是一样的。
这个估计是一个很重要的平滑方式,其原理就是对于没有看见的事件,我们不能认为它的发生概率就是零,因此我们从概率的总量(Probability mass)中,分配一个很小的比例给予这些没有看见的事件,这样一来,看见的那些事件的概率总和就要小于1,因此,需要将所有看见的事件概率小一点。至于小多少,要根据“越是不可信的统计折扣越多”的方法进行。
可以参考: https://zhuanlan.hu.com/p/53636976
神经网络语言模型就是指利用神经网络进行语言建模,当前的一些预训练语言模型就是利用神经网络来建模的。
前向神经网络,又被称为全连接(Fully Connected Neural Network)神经网络,是最早被引入到语言建模中的神经网络结构。前向神经网络一般可表示为:
这里的 是一个resial connect的含义。
其中 ,是权重矩阵, 是输出层的节点数,在语言模型中等于词典的大小, 等于隐藏层大小,为用户自定义。 是输入的特征维度。
当我们用上述前馈神经网络来描述语言模型的时候,我们假设要预测词 ,那我们通常用 这前n-1个词作为输入。一般词的输入先要做一层embedding的映射,将每个词转为一个低维度的向量,这就需要一个look up table,假设字典数目是V,而embedding的长度是m,那么这个look up table就是一个 的矩阵。输入了n-1个词,经过embedding之后,就有n-1个m维度向量,我们将它们拼接起来作为前馈神经网络的输入,这时候前馈神经网络的输入维度 ,最终输出的 作为预测 的得分,然后再接一层softmax得出概率,计算交叉熵损失函数即可训练模型。
下图中的每个子单元是在对语句中的某一个词进行预测。
循环神经网络(Recurrent Neural Networks)是另一种可以用来进行语言模型建模的网络结构,之前提到的前向神经网络语言模型是以前n-1个词作为输入来预测当前词,这种处理方式是解决不了时序问题的,在预测当前词的时候,无法很好的依赖于上下文(主要是上文),而循环神经网络则可以解决上下文依赖问题。
循环神经网络引入了一个中间隐藏层 ,该隐藏层的状态可以沿着时间将信息传给下一次预测。直观来说,就是将第 时刻的隐藏层的状态 作为第 时刻模型预测或者训练的一个输入,这里的时刻也可以叫做时间步。
下图是一张对RNN进行时间铺开的展示图,每个时间单元都将自身的隐藏层作为下一个时间单元的输入,这张图上面并没有画出第一个时间单元接受的隐藏层的输入,事实上,第一个单元也接受了输入,一般是一个初始化的0向量。
上述的结构依然有局限性,就是它只能利用近期的信息去编码我们需要的格式,如果时间步的跨度过大,原先的信息会在传递中逐渐丢失。
假设现在有这样一个任务,考虑到下面这句话“I grew up in France… I speak fluent French.”,现在需要语言模型通过现有以前的文字信息预测该句话的最后一个字。通过以前文字语境可以预测出最后一个字是某种语言,但是要猜测出French,要根据之前的France语境。因为这次的有用信息与需要进行处理信息的地方之间的距离较远,这样容易导致RNN不能学习到有用的信息,最终推导的任务可能失败。
LSTMs也是循环神经网络的一种,它利用了cell状态将长期依赖的信息保留了下来,它也具有这种链式结构,但是它的重复单元不同于标准RNN网络里的单元只有一个网络层,它的内部有四个网络层。LSTMs的结构如下图所示。
LSTM的核心是细胞状态,用贯穿计算单元的水平线表示。这个状态区别于隐藏层的状态,它只是很少的参与信息交换,所以可以保存较远的时间步的信息。我们可以从下图看到,细胞状态在一个时间步里面只参与三次信息交互:两次接受信息,一次输出信息参与计算。这三个操作被称为门,分别称为忘记门、输入门和输出门。
最后回到语言模型上面,使用RNN进行语言模型建模,那么输入的 就是经过embedding的结果,并且最后对于每个 的输出上再接一层全连接层,输出词典数目的维度,最后再加一层softmax就可以得到下一个词输出的概率
上述的都是单向语言模型,但是实际上,我们在t时刻的词的概率不只会依赖于之前的词,有时候还会和后面的词有关,比如说there is a tree,这里的is就是单数形式,依赖于后面的a tree来确定。所以就有了两个单向的LSTM(并不是Bi-LSTM)去更好的进行语言建模。
给定N个token的序列 ,前向语言模型的表示方法为:
同样的,后向语言模型的表示方法为:
前向模型和后向模型都是用同样的方式去预测下一个词,只是方向不同,而且ELMo不光是两个反方向的LSTM模型叠加,还可以是多层的两个反方向LSTM叠加,因此会有多个细胞状态和隐藏层,但其实和单层的是一样的,只是上层的LSTM接受的输入是下层的隐藏层(也可以加上残差连接),两个不同方向的LSTM模型是互不干扰的,他们的联系就只有输入的token的embedding是共用的,以及最后的全连接加softmax是通用的。ELMo训练的时候,比如预测词 ,输入 左边的词汇,经过正向LSTM得到一个 ,同时输入 右边的词汇,经过反向LSTM也得到了一个 ,然后将这两个hidden拼接到一起之后接一个全连接softmax,就可以得到当前输出为词汇表中各词的概率了,同前文中描述的一样,交叉熵损失函数就是当前预测词为实际词的概率,也就是我们要求的语言模型的概率。
最后的极大似然估计则为:
其中token 的embedding表示的参数 以及softmax 层(全连接加softmax转成字典的向量维度)参数 前后向是通用的,LSTM 的参数按照方向取不同值。
传统的语言模型是从左到右或者从右到左的利用上文或者下文去预测当前词,但实际上,当前词出现不只是单单依靠上文或者下文,其实应该是同时依赖于上下文,在ELMo里面,就是用了双向语言模型的结构,但是这种双向语言模型只是两个独立的前向和后向模型合并起来的,并不是一种完美的结合上下文。因此谷歌在Bidirectional Encoder Representation from Transformers一文中,提出了一种Masked Language Model,该语言模型结构是在一个句子中随机挑选一部分词汇,用一个MASK标记替换掉该词汇,然后在模型训练的时候去预测该词汇,完成训练过程
Masked的具体过程是随机选择语料中15%的token,然后再将这些token以80%的概率用[MASK]替换掉,10%的概率用词汇表中的其余词汇替换,还有10%的概率保持不变,然后将这15%的token的位置记录下来。Masked language model需要将包含了MASK的token输入到transformer的encoder的结构里面,encoder会针对每一个输入的token进行self-attention,这样就可以让某个词的信息编码到全局信息。最后根据之前MASK的token位置,取出这些token各自对应的hidden,然后再接一个全连接softmax得到预测值(这里的全连接softmax并不加入到语言模型的词语表征里面,只在训练时候使用),最后再根据实际值去计算mask token的损失函数值。在Bert里面除了mask token的损失值,还有一个next sentence的损失值,对于两个句子组成的句子对,bert在构造样本的时候,会将后一个句子以一定概率替换成其余的句子,并且要记录下构造样本是随机生成的句子对还是真实的句子对,损失值的计算需要用到[CLS]的表征结果,我们对[CLS]的表征结果经过一层全连接softmax,然后去判断这个句子对是随机生成的还是真实的。最后,这两个损失值是直接相加作为最终损失值。
参考链接:
Statistical language model 统计语言模型
深入理解语言模型 Language Model
从经典结构到改进方法,神经网络语言模型综述
深入浅出讲解语言模型
神经网络语言建模系列之一:基础模型
H. Resnet | Block的进化史——未完待续
https://arxiv.org/pdf/1512.03385.pdf
如上述图片所示,Resnet通过提出shortcut连接,解决了深度学习网络在训练过程中随着网络层数越来越多而导致的网络退化问题。这种shortcut的连接有两种形式,一种是完全的等价连接shortcut,另一种作为降采样block的shortcut,使得add操作可以在同等维度上进行降采样shortcut。另外考虑到耗时问题,为了减低更深的resnet网络的耗时,作者提出了一种Bottleneck结构,通过1*1卷积降低作用于3*3卷积的通道数,从而降低计算量和耗时。
对于shortcut的连接有点有两种我比较认可的解释:
1:跳跃连接实现了学习的灵活性,使得网络在训练的过程中可以自由的选择“更多进行卷积与非线性变换”还是“更多的倾向于什么都不做”,或者是两者结合。
2:跳跃连接使得网络刚开始训练时,由于恒等连接shortcut的存在使得网络从物理上变得更浅,网络训练更加容易。
https://arxiv.org/pdf/1603.05027.pdf
希望在训练的过程中,不论是前向还是反向阶段,信号可以直接从一个单元传递到其他任意一个单元,这种方式要比原始的Resnetblcok的性能更要好。
BN_after_addition:BNrelu的结果放在了恒等信号传递的路上,导致优化困难,阻碍了信息的传递。
ReLU_before_addition:将Relu放在信号传递的输出部分,导致最终的输出为非负值,这将限制网络特征的表达范围。
它的BN和ReLU全都放置在权重层的前面。
ReLU-only Pre-activation:ReLU层没有和BN层直连在一起,因此无法共享BN层带来的训练优势。
Pre-activation:这个方法最好。
https://arxiv.org/abs/2004.04989
1:提出了一种基于分段的残差学习网络结构,该方法为信息在网络各层间的传播提供了更好的途径,从而简化了学习过程。
2.更具原始的block和Pre-activation block存在的问题,作者提出了如下拓扑结构,该结构是一种分段的组织结构,细节为:
(1)把网络结构分为三个部分,四个主要stage和一个启动和结束阶段。
(2)四个主要阶段中每个stage都可以包含若干个Blocks,stage1,2,3,4分别有Block的个数为(3,4,6,3)。
(3)每个stage又分为三个部分,一个开始Block,若干个中间Block。比如以resnet50的情况下,有[1,2,4,1]对应stage的中间block。start,middle,end三种blcok的设计如图所示。