数据科学家分享:AI之主流CNN网络的架构分析

1 绪论

      20世纪60年代,Hubel等人通过对猫视觉皮层细胞的研究,提出了感受野这个概念,到80年代,Fukushima在感受野概念的基础之上提出了神经认知机的概念,可以看作是卷积神经网络的第一个实现网络,神经认知机将一个视觉模式分解成许多子模式(特征),然后进入分层递阶式相连的特征平面进行处理,它试图将视觉系统模型化,使其能够在即使物体有位移或轻微变形的时候,也能完成识别。

     卷积神经网络(Convolutional Neural Networks, CNN)是多层感知机(MLP)的变种。由生物学家休博尔和维瑟尔在早期关于猫视觉皮层的研究发展而来。视觉皮层的细胞存在一个复杂的构造。这些细胞对视觉输入空间的子区域非常敏感,我们称之为感受野,以这种方式平铺覆盖到整个视野区域。这些细胞可以分为两种基本类型,简单细胞和复杂细胞。简单细胞最大程度响应来自感受野范围内的边缘刺激模式。复杂细胞有更大的接受域,它对来自确切位置的刺激具有局部不变性。

     通常神经认知机包含两类神经元,即承担特征提取的采样元和抗变形的卷积元,采样元中涉及两个重要参数,即感受野与阈值参数,前者确定输入连接的数目,后者则控制对特征子模式的反应程度。卷积神经网络可以看作是神经认知机的推广形式,神经认知机是卷积神经网络的一种特例。

     CNN由纽约大学的Yann LeCun于1998年提出。CNN本质上是一个多层感知机,其成功的原因关键在于它所采用的稀疏连接(局部感受)和权值共享的方式,一方面减少了的权值的数量使得网络易于优化,另一方面降低了过拟合的风险。

     CNN具有优点: 良好的容错能力、并行处理能力和自学习能力,可处理环境信息复杂,背景知识不清楚,推理规则不明确情况下的问题,允许样品有较大的缺损、畸变,运行速度快,自适应性能好,具有较高的分辨率。它是通过结构重组和减少权值将特征抽取功能融合进多层感知器,省略识别前复杂的图像特征抽取过程。

1.2价值之一-稀疏连接

     在BP神经网络中,每一层的神经元节点是一个线性一维排列结构,层与层各神经元节点之间是全连接的。卷积神经网络中,层与层之间的神经元节点不再是全连接形式,利用层间局部空间相关性将相邻每一层的神经元节点只与和它相近的上层神经元节点连接,即局部连接。这样大大降低了神经网络架构的参数规模。

1.3价值之二-权重共享

    在卷积神经网络中,卷积层的每一个卷积滤波器重复的作用于整个感受野中,对输入图像进行卷积,卷积结果构成了输入图像的特征图,提取出图像的局部特征。每一个卷积滤波器共享相同的参数,包括相同的权重矩阵和偏置项。共享权重的好处是在对图像进行特征提取时不用考虑局部特征的位置。而且权重共享提供了一种有效的方式,使要学习的卷积神经网络模型参数数量大大降低。

1.4 Softmax回归

它是在逻辑回归的基础上扩张而来,它的目的是为了解决多分类问题。在这类问题中,训练样本的种类一般在两个以上。Softmax回归是有监督学习算法,它也可以与深度学习或无监督学习方法结合使用。

2 基础

2.1 神经元

神经网络由大量的神经元相互连接而成。每个神经元接受线性组合的输入后,最开始只是简单的线性加权,后来给每个神经元加上了非线性的激活函数,从而进行非线性变换后输出。每两个神经元之间的连接代表加权值,称之为权重(weight)。不同的权重和激活函数,则会导致神经网络不同的输出。

神经网络的每个神经元如下

https://img-blog.csdn.net/20160716131107406

基本wx + b的形式,其中

https://img-blog.csdn.net/20160720151554838https://img-blog.csdn.net/20160720151607869表示输入向量

https://img-blog.csdn.net/20160720151620525https://img-blog.csdn.net/20160720151633098为权重,几个输入则意味着有几个权重,即每个输入都被赋予一个权重

b为偏置bias

g(z) 为激活函数

a 为输出

一开始为了简单,人们把激活函数定义成一个线性函数,即对于结果做一个线性变化,比如一个简单的线性激活函数是g(z) = z,输出都是输入的线性变换。后来实际应用中发现,线性激活函数太过局限,于是人们引入了非线性激活函数。

2.2 激活函数

常用的非线性激活函数有sigmoid、tanh、relu等等,前两者sigmoid/tanh比较常见于全连接层,后者relu常见于卷积层。这里先简要介绍下最基础的sigmoid函数,sigmoid的函数表达式如下:

https://img-blog.csdn.net/20160703105637734

其中z是一个线性组合,通过代入很大的正数或很小的负数到g(z)函数中可知,其结果趋近于0或1。

    因此,sigmoid函数g(z)的图形表示如下( 横轴表示定义域z,纵轴表示值域g(z) ):

https://img-blog.csdn.net/20160703105432793

sigmoid函数的功能是相当于把一个实数压缩至0到1之间。当z是非常大的正数时,g(z)会趋近于1,而z是非常小的负数时,则g(z)会趋近于0

2.3 神经网络

    将下图的这种单个神经元

https://img-blog.csdn.net/20160703140734967

    组织在一起,便形成了神经网络。下图便是一个三层神经网络结构

https://img-blog.csdn.net/20160703140745657

上图中最左边的原始输入信息称之为输入层,最右边的神经元称之为输出层(上图中输出层只有一个神经元),中间的叫隐藏层。

  • 输入层(Input layer),众多神经元(Neuron)接受大量非线形输入讯息。输入的讯息称为输入向量。
  • 输出层(Output layer),讯息在神经元链接中传输、分析、权衡,形成输出结果。输出的讯息称为输出向量。
  • 隐藏层(Hidden layer),简称“隐层”,是输入层和输出层之间众多神经元和链接组成的各个层面。如果有多个隐藏层,则意味着多个激活函数。

同时,每一层都可能由单个或多个神经元组成,每一层的输出将会作为下一层的输入数据。比如下图中间隐藏层来说,隐藏层的3个神经元a1、a2、a3皆各自接受来自多个不同权重的输入(因为有x1、x2、x3这三个输入,所以a1 a2 a3都会接受x1 x2 x3各自分别赋予的权重,即几个输入则几个权重),接着,a1、a2、a3又在自身各自不同权重的影响下 成为的输出层的输入,最终由输出层输出最终结果。

https://img-blog.csdn.net/20160703110336151

  • https://img-blog.csdn.net/20160703112204318表示第j层第i个单元的激活函数/神经元
  • https://img-blog.csdn.net/20160703112227254表示从第j层映射到第j+1层的控制函数的权重矩阵 

此外,输入层和隐藏层都存在一个偏置(bias unit),所以上图中也增加了偏置项:x0、a0。针对上图,有如下公式

https://img-blog.csdn.net/20160703112107755

此外,上文中讲的都是一层隐藏层,但实际中也有多层隐藏层的,即输入层和输出层中间夹着数层隐藏层,层和层之间是全连接的结构,同一层的神经元之间没有连接。

https://img-blog.csdn.net/20160703113851013

3 架构

卷积神经网络整体架构:卷积神经网络是一种多层的监督学习神经网络,隐含层的卷积层和池采样层是实现卷积神经网络特征提取功能的核心模块。该网络模型通过采用梯度下降法最小化损失函数对网络中的权重参数逐层反向调节,通过频繁的迭代训练提高网络的精度。卷积神经网络的低隐层是由卷积层和最大池采样层交替组成,高层是全连接层对应传统多层感知器的隐含层和逻辑回归分类器。第一个全连接层的输入是由卷积层和子采样层进行特征提取得到的特征图像。最后一层输出层是一个分类器,可以采用逻辑回归,Softmax回归甚至是支持向量机对输入图像进行分类。

https://img-blog.csdn.net/20160702205047459

4 算法

CNN通过三个方法来实现识别图像的位移、缩放和扭曲不变性,即局域感受野、权值共享和次抽样。局域感受野指的是每一层网络的神经元只与上一层的一个小邻域内的神经单元连接,通过局域感受野,每个神经元可以提取初级的视觉特征,如方向线段,端点和角点等;权值共享使得CNN具有更少的参数,需要相对少的训练数据;次抽样可以减少特征的分辨率,实现对位移、缩放和其它形式扭曲的不变性。卷积层之后通常用一个次抽样层来减少计算时间、建立空间和结构上的不变性。

构造好网络之后,需要对网络进行求解,如果像传统神经网络一样分配参数,则每一个连接都会有未知参数。而CNN采用的是权值共享,这样一来通过一幅特征图上的神经元共享同样的权值就可以大大减少自由参数,这可以用来检测相同的特征在不同角度表示的效果。在网络设计中通常都是抽样层与卷积层交替出现,全连接层的前一层通常为卷积层。在CNN中,权值更新是基于反向传播算法。

CNN在本质上是一种输入到输出的映射,它能够学习大量的输入与输出之间的映射关系,而不需要任何输入和输出之间的精确的数学表达式,只要用已知的模式对卷积网络加以训练,网络就具有输入输出对之间的映射能力。卷积网络执行的是监督训练,所以其样本集是由形如:输入向量,理想输出向量的向量对构成的。所有这些向量对,都应该是来源于网络即将模拟系统的实际“运行”结构,它们可以是从实际运行系统中采集来。在开始训练前,所有的权都应该用一些不同的随机数进行初始化。“小随机数”用来保证网络不会因权值过大而进入饱和状态,从而导致训练失败;“不同”用来保证网络可以正常地学习。实际上,如果用相同的数去初始化权矩阵,则网络无学习能力。

4.1 训练算法

第一阶段,向前传播阶段:

(1)、从样本集中取一个样本,输入网络;

(2)、计算相应的实际输出;在此阶段,信息从输入层经过逐级的变换,传送到输出层。这个过程也是网络在完成训练后正常执行时执行的过程。

第二阶段,向后传播阶段:

(1)、计算实际输出与相应的理想输出的差;

(2)、按极小化误差的方法调整权矩阵。

这两个阶段的工作一般应受到精度要求的控制。

4.2 网络的训练过程

 (1)、选定训练组,从样本集中分别随机地寻求N个样本作为训练组;

 (2)、将各权值、阈值,置成小的接近于0的随机值,并初始化精度控制参数和学习率;

 (3)、从训练组中取一个输入模式加到网络,并给出它的目标输出向量;

 (4)、计算出中间层输出向量,计算出网络的实际输出向量;

 (5)、将输出向量中的元素与目标向量中的元素进行比较,计算出输出误差;对于中间层的隐单元也需要计算出误差;

 (6)、依次计算出各权值的调整量和阈值的调整量;

 (7)、调整权值和调整阈值;

 (8)、当经历M后,判断指标是否满足精度要求,如果不满足,则返回(3),继续迭代;如果满足就进入下一步;

(9)、训练结束,将权值和阈值保存在文件中。这时可以认为各个权值已经达到稳定,分类器已经形成。再一次进行训练,直接从文件导出权值和阈值进行训练,不需要进行初始化

5 计算

5.1 卷积

对图像(不同的数据窗口数据)和滤波矩阵(一组固定的权重:因为每个神经元的多个权重固定,所以又可以看做一个恒定的滤波器filter)做内积(逐个元素相乘再求和)的操作就是所谓的『卷积』操作,也是卷积神经网络的名字来源。

非严格意义上来讲,下图中红框框起来的部分便可以理解为一个滤波器,即带着一组固定权重的神经元。多个滤波器叠加便成了卷积层。在卷积层中每个神经元连接数据窗的权重是固定的,每个神经元只关注一个特性。神经元就是图像处理中的滤波器,比如边缘检测专用的Sobel滤波器,即卷积层的每个滤波器都会有自己所关注一个图像特征,比如垂直边缘,水平边缘,颜色,纹理等等,这些所有神经元加起来就好比就是整张图像的特征提取器集合。

https://img-blog.csdn.net/20160822134955264

举个具体的例子。如下图中,图中左边部分是原始输入数据,图中中间部分是滤波器filter,图中右边是输出的新的二维数据。

https://img-blog.csdn.net/20160702215705128

   5.2图像上的卷积

在下图对应的计算过程中,输入是一定区域大小(width*height)的数据,和滤波器filter(带着一组固定权重的神经元)做内积后等到新的二维数据。

具体来说,左边是图像输入,中间部分就是滤波器filter(带着一组固定权重的神经元),不同的滤波器filter会得到不同的输出数据,比如颜色深浅、轮廓。相当于如果想提取图像的不同特征,则用不同的滤波器filter,提取想要的关于图像的特定信息:颜色深浅或轮廓。

如下图所示

https://img-blog.csdn.net/20160702214116669

在CNN中,滤波器filter(带着一组固定权重的神经元)对局部输入数据进行卷积计算。每计算完一个数据窗口内的局部数据后,数据窗口不断平移滑动,直到计算完所有数据。这个过程中,有这么几个参数: 
  a. 深度depth:神经元个数,决定输出的depth厚度。同时代表滤波器个数。
  b. 步长stride:决定滑动多少步可以到边缘。

c. 填充值zero-padding:在外围边缘补充若干圈0,方便从初始位置以步长为单位可以刚好滑倒末尾位置,通俗地讲就是为了总长能被步长整除。 

   

6 激励

6.1 ReLU激励层

实际梯度下降中,sigmoid容易饱和、造成终止梯度传递,且没有0中心化。可以尝试另外一个激活函数:ReLU,其图形表示如下

https://img-blog.csdn.net/20160703124215945

ReLU的优点是收敛快,求梯度简单。

6.2 池化pool层

池化:即取区域平均或最大,如下图所示https://img-blog.csdn.net/20160703121026432

最大池采样:它是一种非线性降采样方法。在通过卷积获取图像特征之后是利用这些特征进行分类。可以用所有提取到的特征数据进行分类器的训练,但这通常会产生极大的计算量。所以在获取图像的卷积特征后,要通过最大池采样方法对卷积特征进行降维。将卷积特征划分为数个n*n的不相交区域,用这些区域的最大(或平均)特征来表示降维后的卷积特征。这些降维后的特征更容易进行分类。

最大池采样在计算机视觉中的价值体现在两个方面:(1)、它减小了来自上层隐藏层的计算复杂度;(2)、这些池化单元具有平移不变性,即使图像有小的位移,提取到的特征依然会保持不变。由于增强了对位移的鲁棒性,最大池采样方法是一个高效的降低数据维度的采样方法。

7 优化

7.1数据集的大小和分块

数据驱动的模型一般依赖于数据集的大小,CNN和其它经验模型一样,能适用于任意大小的数据集,但用于训练的数据集应该足够大,能够覆盖问题域中所有已知可能出现的问题。设计CNN的时候,数据集中应该包含三个子集:训练集、测试集、验证集。训练集应该包含问题域中的所有数据,并在训练阶段用来调整网络权值。测试集用来在训练过程中测试网络对于训练集中未出现的数据的分类性能。根据网络在测试集上的性能情况,网络的结构可能需要做出调整,或者增加训练循环的次数。验证集中的数据同样应该包含在测试集合训练集中没有出现过的数据,用于在确定网络结构后能够更加好的测试和衡量网络的性能。Looney等人建议,数据集中的65%用于训练,25%用于测试,剩余的10%用于验证。

7.2数据预处理

为了加速训练算法的收敛速度,一般都会采用一些数据预处理技术,这其中包括:去除噪声、输入数据降维、删除无关数据等。数据的平衡化在分类问题中异常重要,一般认为训练集中的数据应该相对于标签类别近似于平均分布,也就是每一个类别标签所对应的数据量在训练集中是基本相等的,以避免网络过于倾向于表现某些分类的特点。为了平衡数据集,应该移除一些过度富余的分类中的数据,并相应的补充一些相对样例稀少的分类中的数据。还有一个办法就是复制一部分这些样例稀少分类中的数据,并在这些输入数据中加入随机噪声。

7.3 数据规则化

将数据规则化到一个统一的区间(如[0,1])中具有很重要的优点:防止数据中存在较大数值的数据造成数值较小的数据对于训练效果减弱甚至无效化。一个常用的方法是将输入和输出数据按比例调整到一个和激活函数(sigmoid函数等)相对应的区间。

7.4网络权值初始化

CNN的初始化主要是初始化卷积层和输出层的卷积核(权重)和偏置。

网络权值初始化就是将网络中的所有连接权值(包括阈值)赋予一个初始值。如果初始权值向量处在误差曲面的一个相对平缓的区域的时候,网络训练的收敛速度可能会异常缓慢。一般情况下,网络的连接权值和阈值被初始化在一个具有0均值的相对小的区间内均匀分布,比如[-0.30, +0.30]这样的区间内。

7.5BP算法的学习速率

如果学习速率n选取的比较大则会在训练过程中较大幅度的调整权值w,从而加快网络训练的速度,但这会造成网络在误差曲面上搜索过程中频繁抖动且有可能使得训练过程不能收敛,而且可能越过一些接近优化w。同样,比较小的学习速率能够稳定的使得网络逼近于全局最优点,但也有可能陷入一些局部最优区域。对于不同的学习速率设定都有各自的优缺点,而且还有一种自适应的学习速率方法,即n随着训练算法的运行过程而自行调整。

7.6收敛条件

有几个条件可以作为停止训练的判定条件,训练误差、误差梯度和交叉验证。一般来说,训练集的误差会随着网络训练的进行而逐步降低。

7.7训练方式

训练样例可以有两种基本的方式提供给网络训练使用,也可以是两者的结合:逐个样例训练(EET)、批量样例训练(BT)。在EET中,先将第一个样例提供给网络,然后开始应用BP算法训练网络,直到训练误差降低到一个可以接受的范围,或者进行了指定步骤的训练次数。然后再将第二个样例提供给网络训练。EET的优点是相对于BT只需要很少的存储空间,并且有更好的随机搜索能力,防止训练过程陷入局部最小区域。EET的缺点是如果网络接收到的第一个样例就是劣质(有可能是噪音数据或者特征不明显)的数据,可能使得网络训练过程朝着全局误差最小化的反方向进行搜索。相对的,BT方法是在所有训练样例都经过网络传播后才更新一次权值,因此每一次学习周期就包含了所有的训练样例数据。BT方法的缺点也很明显,需要大量的存储空间,而且相比EET更容易陷入局部最小区域。而随机训练(ST)则是相对于EET和BT一种折衷的方法,ST和EET一样也是一次只接受一个训练样例,但只进行一次BP算法并更新权值,然后接受下一个样例重复同样的步骤计算并更新权值,并且在接受训练集最后一个样例后,重新回到第一个样例进行计算。ST和EET相比,保留了随机搜索的能力,同时又避免了训练样例中最开始几个样例如果出现劣质数据对训练过程的过度不良影响。\

8 演进

先图解一下CNN的使用,让大家熟悉其中的概念

    特征提取是一个分类器的核心,深度学习的优势就在于它能自动从原始数据提炼出特征,并以层级的逻辑组合这些特征来描述原始样本。我在最后一幅图中用一个简单的例子来说明CNN的层级结构是如何解决图像分类问题的。

    假设我们需要用机器视觉方法对图A(两个三角形构成松树的形状)和图B(两个三角形构成钻石的形状)进行区分。在神经网络方法出现之前,一种比较可行的方法是通过图像处理中的直线检测方法找到图像中所有直线,然后通过直线参数之间的关系来确定如下判断规则:如果下面的三角形尖角朝上,即为松树;如果尖角朝下,即为钻石。经过细致的调参,这个算法应该已经可以完美解决区分图A与图B的问题了。如果现在又来了一副图C(也许是两个三角形水平排列构成小山的形状,也可能根本不包含三角形),需要用之前的算法来同时区分这三幅图片,怎么办?

        好在我们可以用CNN来解决这个问题。首先需要注意,我在这一小节所指“卷积”实际上是滤波操作,因为卷积涉及翻转,不利用直观理解。假设我们训练好的网络有两层隐层,第一层包含两个节点(图中第二列蓝色图形,分别为一条左斜线与一条右斜线),第二层包含四个节点(图中第四列蓝色图形,分别为一条水平线,一条竖直线,一条左斜线与一条右斜线)。图A经过第一隐层,得到图中第三列黑色的图形。黑色的圆点代表原始图像中对某个卷积核激活值高的区域,白色代表激活值低的区域。图A松树左侧的两条斜边经过“左斜线”卷积核计算得到位于图像左侧的两个黑色圆点,其他区域都不符合“左斜线”这个特征,所以输出值全部忽略为0。同时,只有松树右侧的两条斜边会对“右斜线”卷积核产生高激活(得到两个位于右侧的黑色圆点),其他区域产生的激活都为0。同理,图B钻石图像经过“左斜线”与“右斜线”卷积核产生两幅不同的图像(一副在左上和右下有黑点,一副在右上和左下有黑点)。这时,第一层的计算就完成了。

        与一般的CNN模型一样,我们把第一层的结果(图中第三列)输入第二隐层之前要缩小一下图像的尺度。经过缩小之后(你可以眯起眼睛离屏幕稍远些观察),第三列的四个图形分别变成了一条在左侧的竖线,一条在右侧的竖线,一条右斜线和一条左斜线。现在,我们拿第二层的四个卷积核(第四列蓝色图形)来对这四个结果进行卷积再求和。为了简化,如果在图像中存在一个区域使其与某卷积核的激活输出值较高,就将该卷积核的对应输出记为1;如果不存在这样的一个区域即记为0。这样,图中第三列第一个图像对四个卷积核分别产生(0,1,0,0),第二个图像产生(0,1,0,0),所以图A的最终结果是这两个向量的和,为(0,2,0,0)。而图B的结果为(0,0,0,1)+(0,0,1,0)=(0,0,1,1)。虽然图A与图B有相似之处,但经过两次卷积得到的向量是完全不同的,通过这两个向量,我们就能唯一地确定图A与图B。

        如果有新的样本加入,我们只需要改变一下图例中的卷积核数目和形状(或者甚至不对网络做任何修改)也能够轻松地实现分类。当然,CNN方法在实际运用时是不需要人为地设计卷积核的,而是依靠对样本的训练逐渐构造的。

然后,我讲一下过拟合和欠拟合,以及优化方法。当训练结果不好时,可能会有两种结果,欠拟合与过拟合。欠拟合是指模型不足以对训练集产生比较高的分类精度,从误差-迭代曲线上表现为无论是训练期间还是测试期间,误差都比较高。这说明模型对特征的提取不够,不足以用来描述样本间的差异。这时一般需要优化方法来解决这个问题,比如改变激活函数、误差函数,或者换一种梯度下降方法(以及调整梯度方法的参数)。过拟合是指模型对训练集有比较高的分类精度,但对测试集表现不佳,从误差-迭代曲线上表现为在训练期间误差能够收敛到一个较小值,但测试期间误差却比较大。这说明模型过分地依赖训练样本的特征,对没有遇见过新样本不知所措,缺乏泛化能力。这时需要正则化方法来提高模型对一般性样本的适应性,比如Dropout和Batch Normalization。误差不收敛的一个更常见的原因——尤其是在一个新模型刚刚建立时——是梯度消失或梯度爆炸。在网络中缺少比较可靠的正则化技术时,在网络不断迭代训练的过程中(甚至第二次迭代开始)会发现新样本产生的误差梯度在反向传播的过程中越来越小(或越来越大),有时呈现每一两层就减小(或增大)一个数量级。梯度趋向消失时,无论训练多久,会发现最浅层(前一两层)的参数与初始值并没有太大变化,这就使得浅层的存在失去了意义,而且这也会使训练过程变得非常缓慢。梯度爆炸时,仅仅几次迭代之后就会发现某一层所有节点的输出都变成了1(或者十分接近于1),这时网络也就失去了分类的能力。

下面,开始讲主流的网络演进。

 • LeNet,这是最早用于数字识别的CNN
  • AlexNet, 2012 ILSVRC比赛远超第2名的CNN,比 LeNet更深,用多层小卷积层叠加替换单大卷积层。
  • ZF Net, 2013 ILSVRC比赛冠军
  • GoogLeNet, 2014 ILSVRC比赛冠军
  • VGGNet, 2014 ILSVRC比赛中的模型,图像识别略差于GoogLeNet,但是在很多图像转化学习问题(比如object detection)上效果奇好

8.1 LeNet

https://images2017.cnblogs.com/blog/1093303/201802/1093303-20180217131615671-367457714.png

闪光点:定义了CNN的基本组件,是CNN的鼻祖。

LeNet是卷积神经网络的祖师爷LeCun在1998年提出,用于解决手写数字识别的视觉任务。自那时起,CNN的最基本的架构就定下来了:卷积层、池化层、全连接层。如今各大深度学习框架中所使用的LeNet都是简化改进过的LeNet-5(-5表示具有5个层),和原始的LeNet有些许不同,比如把激活函数改为了现在很常用的ReLu。

LeNet-5跟现有的conv->pool->ReLU的套路不同,它使用的方式是conv1->pool->conv2->pool2再接全连接层,但是不变的是,卷积层后紧接池化层的模式依旧不变。

https://images2017.cnblogs.com/blog/1093303/201802/1093303-20180217131630609-291700181.png

以上图为例,对经典的LeNet-5做深入分析:

  1. 首先输入图像是单通道的28*28大小的图像,用矩阵表示就是[1,28,28]
  2. 第一个卷积层conv1所用的卷积核尺寸为5*5,滑动步长为1,卷积核数目为20,那么经过该层后图像尺寸变为24,28-5+1=24,输出矩阵为[20,24,24]。
  3. 第一个池化层pool核尺寸为2*2,步长2,这是没有重叠的max pooling,池化操作后,图像尺寸减半,变为12×12,输出矩阵为[20,12,12]。
  4. 第二个卷积层conv2的卷积核尺寸为5*5,步长1,卷积核数目为50,卷积后图像尺寸变为8,这是因为12-5+1=8,输出矩阵为[50,8,8].
  5. 第二个池化层pool2核尺寸为2*2,步长2,这是没有重叠的max pooling,池化操作后,图像尺寸减半,变为4×4,输出矩阵为[50,4,4]。
  6. pool2后面接全连接层fc1,神经元数目为500,再接relu激活函数。
  7. 再接fc2,神经元个数为10,得到10维的特征向量,用于10个数字的分类训练,送入softmaxt分类,得到分类结果的概率output。

8.2 AlexNet

AlexNet在2012年ImageNet竞赛中以超过第二名10.9个百分点的绝对优势一举夺冠,从此深度学习和卷积神经网络名声鹊起,深度学习的研究如雨后春笋般出现,AlexNet的出现可谓是卷积神经网络的王者归来。

https://images2017.cnblogs.com/blog/1093303/201802/1093303-20180217131643890-1883639712.png

闪光点:

  • 更深的网络
  • 数据增广
  • ReLU
  • dropout
  • LRN

以上图AlexNet架构为例,这个网络前面5层是卷积层,后面三层是全连接层,最终softmax输出是1000类,取其前两层进行详细说明。

  1. AlexNet共包含5层卷积层和三层全连接层,层数比LeNet多了不少,但卷积神经网络总的流程并没有变化,只是在深度上加了不少。
  2. AlexNet针对的是1000类的分类问题,输入图片规定是256×256的三通道彩色图片,为了增强模型的泛化能力,避免过拟合,作者使用了随机裁剪的思路对原来256×256的图像进行随机裁剪,得到尺寸为3×224×224的图像,输入到网络训练。

https://images2017.cnblogs.com/blog/1093303/201802/1093303-20180217131703406-1977094290.png

  1. 因为使用多GPU训练,所以可以看到第一层卷积层后有两个完全一样的分支,以加速训练。
  2. 针对一个分支分析:第一层卷积层conv1的卷积核尺寸为11×11,滑动步长为4,卷积核数目为48。卷积后得到的输出矩阵为[48,55,55]。这里的55是个难以理解的数字,作者也没有对此说明,如果按照正常计算的话(224-11)/4+1 != 55的,所以这里是做了padding再做卷积的,即先padiing图像至227×227,再做卷积(227-11)/4+1 = 55。这些像素层经过relu1单元的处理,生成激活像素层,尺寸仍为2组48×55×55的像素层数据
    。然后经过归一化处理,归一化运算的尺度为5*5。第一卷积层运算结束后形成的像素层的规模为48×27×27。
  3. 输入矩阵是[48,55,55].接着是池化层,做max pooling操作,池化运算的尺度为3*3,运算的步长为2,则池化后图像的尺寸为(55-3)/2+1=27。所以得到的输出矩阵是[48,27,27]。后面层不再重复叙述。

AlexNet用到训练技巧:

  • 数据增广技巧来增加模型泛化能力。
  • 用ReLU代替Sigmoid来加快SGD的收敛速度
  • Dropout:Dropout原理类似于浅层学习算法的中集成算法,该方法通过让全连接层的神经元(该模型在前两个全连接层引入Dropout)以一定的概率失去活性(比如0.5)失活的神经元不再参与前向和反向传播,相当于约有一半的神经元不再起作用。在测试的时候,让所有神经元的输出乘0.5。Dropout的引用,有效缓解了模型的过拟合。
  • Local Responce Normalization:局部响应归一层的基本思路是,假如这是网络的一块,比如是 13×13×256, LRN 要做的就是选取一个位置,比如说这样一个位置,从这个位置穿过整个通道,能得到 256 个数字,并进行归一化。进行局部响应归一化的动机是,对于这张 13×13 的图像中的每个位置来说,我们可能并不需要太多的高激活神经元。但是后来,很多研究者发现 LRN 起不到太大作用,因为并不重要,而且我们现在并不用 LRN 来训练网络。

8.3 VGG-Nets

VGG-Nets是由牛津大学VGG(Visual Geometry Group)提出,是2014年ImageNet竞赛定位任务的第一名和分类任务的第二名的中的基础网络。VGG可以看成是加深版本的AlexNet. 都是conv layer + FC layer,在当时看来这是一个非常深的网络了,因为层数高达十多层,我们从其论文名字就知道了(《Very Deep Convolutional Networks for Large-Scale Visual Recognition》),当然以现在的目光看来VGG真的称不上是一个very deep的网络。

https://images2017.cnblogs.com/blog/1093303/201802/1093303-20180217131736640-1269864740.png

上面一个表格是描述的是VGG-Net的网络结构以及诞生过程。为了解决初始化(权重初始化)等问题,VGG采用的是一种Pre-training的方式,这种方式在经典的神经网络中经常见得到,就是先训练一部分小网络,然后再确保这部分网络稳定之后,再在这基础上逐渐加深。表1从左到右体现的就是这个过程,并且当网络处于D阶段的时候,效果是最优的,因此D阶段的网络也就是VGG-16了!E阶段得到的网络就是VGG-19了!VGG-16的16指的是conv+fc的总层数是16,是不包括max pool的层数!

下面这个图就是VGG-16的网络结构。

https://images2017.cnblogs.com/blog/1093303/201802/1093303-20180217131751843-269987601.png

由上图看出,VGG-16的结构非常整洁,深度较AlexNet深得多,里面包含多个conv->conv->max_pool这类的结构,VGG的卷积层都是same的卷积,即卷积过后的输出图像的尺寸与输入是一致的,它的下采样完全是由max pooling来实现。

VGG网络后接3个全连接层,filter的个数(卷积后的输出通道数)从64开始,然后没接一个pooling后其成倍的增加,128、512,VGG的注意贡献是使用小尺寸的filter,及有规则的卷积-池化操作。

闪光点:

  • 卷积层使用更小的filter尺寸和间隔

与AlexNet相比,可以看出VGG-Nets的卷积核尺寸还是很小的,比如AlexNet第一层的卷积层用到的卷积核尺寸就是11*11,这是一个很大卷积核了。而反观VGG-Nets,用到的卷积核的尺寸无非都是1×1和3×3的小卷积核,可以替代大的filter尺寸。

3×3卷积核的优点:

  • 多个3×3的卷基层比一个大尺寸filter卷基层有更多的非线性,使得判决函数更加具有判决性
  • 多个3×3的卷积层比一个大尺寸的filter有更少的参数,假设卷基层的输入和输出的特征图大小相同为C,那么三个3×3的卷积层参数个数3×(3×3×C×C)=27CC;一个7×7的卷积层参数为49CC;所以可以把三个3×3的filter看成是一个7×7filter的分解(中间层有非线性的分解)

1*1卷积核的优点:

  • 作用是在不影响输入输出维数的情况下,对输入进行线性形变,然后通过Relu进行非线性处理,增加网络的非线性表达能力。

8.4 GoogLeNet

GoogLeNet在2014的ImageNet分类任务上击败了VGG-Nets夺得冠军,其实力肯定是非常深厚的,GoogLeNet跟AlexNet,VGG-Nets这种单纯依靠加深网络结构进而改进网络性能的思路不一样,它另辟幽径,在加深网络的同时(22层),也在网络结构上做了创新,引入Inception结构代替了单纯的卷积+激活的传统操作(这思路最早由Network in Network提出)。GoogLeNet进一步把对卷积神经网络的研究推上新的高度。

https://images2017.cnblogs.com/blog/1093303/201802/1093303-20180217131814499-915840988.png

闪光点:

  • 引入Inception结构
  • 中间层的辅助LOSS单元
  • 后面的全连接层全部替换为简单的全局平均pooling

https://images2017.cnblogs.com/blog/1093303/201802/1093303-20180217131828906-234829229.png

上图结构就是Inception,结构里的卷积stride都是1,另外为了保持特征响应图大小一致,都用了零填充。最后每个卷积层后面都立刻接了个ReLU层。在输出前有个叫concatenate的层,直译的意思是“并置”,即把4组不同类型但大小相同的特征响应图一张张并排叠起来,形成新的特征响应图。Inception结构里主要做了两件事:1. 通过3×3的池化、以及1×1、3×3和5×5这三种不同尺度的卷积核,一共4种方式对输入的特征响应图做了特征提取。2. 为了降低计算量。同时让信息通过更少的连接传递以达到更加稀疏的特性,采用1×1卷积核来实现降维。

这里想再详细谈谈1×1卷积核的作用,它究竟是怎么实现降维的。现在运算如下:下面图1是3×3卷积核的卷积,图2是1×1卷积核的卷积过程。对于单通道输入,1×1的卷积确实不能起到降维作用,但对于多通道输入,就不不同了。假设你有256个特征输入,256个特征输出,同时假设Inception层只执行3×3的卷积。这意味着总共要进行 256×256×3×3的卷积(589000次乘积累加(MAC)运算)。这可能超出了我们的计算预算,比方说,在Google服务器上花0.5毫秒运行该层。作为替代,我们决定减少需要卷积的特征的数量,比如减少到64(256/4)个。在这种情况下,我们首先进行256到64的1×1卷积,然后在所有Inception的分支上进行64次卷积,接着再使用一个64到256的1×1卷积。

  • 256×64×1×1 = 16000
  • 64×64×3×3 = 36000
  • 64×256×1×1 = 16000

现在的计算量大约是70000(即16000+36000+16000),相比之前的约600000,几乎减少了10倍。这就通过小卷积核实现了降维。

现在再考虑一个问题:为什么一定要用1×1卷积核,3×3不也可以吗?考虑[50,200,200]的矩阵输入,我们可以使用20个1×1的卷积核进行卷积,得到输出[20,200,200]。有人问,我用20个3×3的卷积核不是也能得到[20,200,200]的矩阵输出吗,为什么就使用1×1的卷积核?我们计算一下卷积参数就知道了,对于1×1的参数总数:20×200×200×(1×1),对于3×3的参数总数:20×200×200×(3×3),可以看出,使用1×1的参数总数仅为3×3的总数的九分之一!所以我们使用的是1×1卷积核。

https://images2017.cnblogs.com/blog/1093303/201802/1093303-20180217131851234-1368185941.gif
https://images2017.cnblogs.com/blog/1093303/201802/1093303-20180217131904781-1419692084.gif

GoogLeNet网络结构中有3个LOSS单元,这样的网络设计是为了帮助网络的收敛。在中间层加入辅助计算的LOSS单元,目的是计算损失时让低层的特征也有很好的区分能力,从而让网络更好地被训练。在论文中,这两个辅助LOSS单元的计算被乘以0.3,然后和最后的LOSS相加作为最终的损失函数来训练网络。

GoogLeNet还有一个闪光点值得一提,那就是将后面的全连接层全部替换为简单的全局平均pooling,在最后参数会变的更少。而在AlexNet中最后3层的全连接层参数差不多占总参数的90%,使用大网络在宽度和深度允许GoogleNet移除全连接层,但并不会影响到结果的精度,在ImageNet中实现93.3%的精度,而且要比VGG还要快。

8.5 ResNet

2015年何恺明推出的ResNet在ISLVRC和COCO上横扫所有选手,获得冠军。ResNet在网络结构上做了大创新,而不再是简单的堆积层数,ResNet在卷积神经网络的新思路,绝对是深度学习发展历程上里程碑式的事件。

https://images2017.cnblogs.com/blog/1093303/201802/1093303-20180217131926202-233779647.jpg

闪光点:

  • 层数非常深,已经超过百层
  • 引入残差单元来解决退化问题

从前面可以看到,随着网络深度增加,网络的准确度应该同步增加,当然要注意过拟合问题。但是网络深度增加的一个问题在于这些增加的层是参数更新的信号,因为梯度是从后向前传播的,增加网络深度后,比较靠前的层梯度会很小。这意味着这些层基本上学习停滞了,这就是梯度消失问题。深度网络的第二个问题在于训练,当网络更深时意味着参数空间更大,优化问题变得更难,因此简单地去增加网络深度反而出现更高的训练误差,深层网络虽然收敛了,但网络却开始退化了,即增加网络层数却导致更大的误差,比如下图,一个56层的网络的性能却不如20层的性能好,这不是因为过拟合(训练集训练误差依然很高),这就是烦人的退化问题。残差网络ResNet设计一种残差模块让我们可以训练更深的网络。

https://images2017.cnblogs.com/blog/1093303/201802/1093303-20180217131941296-1327847371.png

这里详细分析一下残差单元来理解ResNet的精髓。

从下图可以看出,数据经过了两条路线,一条是常规路线,另一条则是捷径(shortcut),直接实现单位映射的直接连接的路线,这有点类似与电路中的“短路”。通过实验,这种带有shortcut的结构确实可以很好地应对退化问题。我们把网络中的一个模块的输入和输出关系看作是y=H(x),那么直接通过梯度方法求H(x)就会遇到上面提到的退化问题,如果使用了这种带shortcut的结构,那么可变参数部分的优化目标就不再是H(x),若用F(x)来代表需要优化的部分的话,则H(x)=F(x)+x,也就是F(x)=H(x)-x。因为在单位映射的假设中y=x就相当于观测值,所以F(x)就对应着残差,因而叫残差网络。为啥要这样做,因为作者认为学习残差F(X)比直接学习H(X)简单!设想下,现在根据我们只需要去学习输入和输出的差值就可以了,绝对量变为相对量(H(x)-x 就是输出相对于输入变化了多少),优化起来简单很多。

考虑到x的维度与F(X)维度可能不匹配情况,需进行维度匹配。这里论文中采用两种方法解决这一问题(其实是三种,但通过实验发现第三种方法会使performance急剧下降,故不采用):

  • zero_padding:对恒等层进行0填充的方式将维度补充完整。这种方法不会增加额外的参数
  • projection:在恒等层采用1x1的卷积核来增加维度。这种方法会增加额外的参数

https://images2017.cnblogs.com/blog/1093303/201802/1093303-20180217131952952-92773471.png

下图展示了两种形态的残差模块,左图是常规残差模块,有两个3×3卷积核卷积核组成,但是随着网络进一步加深,这种残差结构在实践中并不是十分有效。针对这问题,右图的“瓶颈残差模块”(bottleneck residual block)可以有更好的效果,它依次由1×1、3×3、1×1这三个卷积层堆积而成,这里的1×1的卷积能够起降维或升维的作用,从而令3×3的卷积可以在相对较低维度的输入上进行,以达到提高计算效率的目的。

https://images2017.cnblogs.com/blog/1093303/201802/1093303-20180217132002999-1852938927.png

8.6 DenseNet

自Resnet提出以后,ResNet的变种网络层出不穷,都各有其特点,网络性能也有一定的提升。本文介绍的最后一个网络是CVPR 2017最佳论文DenseNet,论文中提出的DenseNet(Dense Convolutional Network)主要还是和ResNet及Inception网络做对比,思想上有借鉴,但却是全新的结构,网络结构并不复杂,却非常有效,在CIFAR指标上全面超越ResNet。可以说DenseNet吸收了ResNet最精华的部分,并在此上做了更加创新的工作,使得网络性能进一步提升。

闪光点:

  • 密集连接:缓解梯度消失问题,加强特征传播,鼓励特征复用,极大的减少了参数量

DenseNet 是一种具有密集连接的卷积神经网络。在该网络中,任何两层之间都有直接的连接,也就是说,网络每一层的输入都是前面所有层输出的并集,而该层所学习的特征图也会被直接传给其后面所有层作为输入。下图是 DenseNet 的一个dense block示意图,一个block里面的结构如下,与ResNet中的BottleNeck基本一致:BN-ReLU-Conv(1×1)-BN-ReLU-Conv(3×3) ,而一个DenseNet则由多个这种block组成。每个DenseBlock的之间层称为transition layers,由BN−>Conv(1×1)−>averagePooling(2×2)组成

https://images2017.cnblogs.com/blog/1093303/201802/1093303-20180217132019609-1216378928.png

密集连接不会带来冗余吗?不会!密集连接这个词给人的第一感觉就是极大的增加了网络的参数量和计算量。但实际上 DenseNet 比其他网络效率更高,其关键就在于网络每层计算量的减少以及特征的重复利用。DenseNet则是让l层的输入直接影响到之后的所有层,它的输出为:xl=Hl([X0,X1,…,xl−1]),其中[x0,x1,...,xl−1]就是将之前的feature map以通道的维度进行合并。并且由于每一层都包含之前所有层的输出信息,因此其只需要很少的特征图就够了,这也是为什么DneseNet的参数量较其他模型大大减少的原因。这种dense connection相当于每一层都直接连接input和loss,因此就可以减轻梯度消失现象,这样更深网络不是问题

需要明确一点,dense connectivity 仅仅是在一个dense block里的,不同dense block 之间是没有dense connectivity的,比如下图所示。

https://images2017.cnblogs.com/blog/1093303/201802/1093303-20180217132035937-2041404109.png

天底下没有免费的午餐,网络自然也不例外。在同层深度下获得更好的收敛率,自然是有额外代价的。其代价之一,就是其恐怖如斯的内存占用。

用公式讲解区别:

8.7 ResNeXt

闪光点:

  • 基于Resnet与Inception 'Split + Transfrom + Concat'的产物,将卷积核按通道分组,形成32个并行分支,低维度卷积进行特征变换,加法合并。
  • 同参数规模下,增加结构,提高模型表达力(100层ResNeXt = 200层ResNet)
  • 在行业标志性的Imagenet 1k数据集上它取得了比Resnet/Inception/Inception-Resnet系列更佳的效果。
  • ILSVRC-2016竞赛第二

以下为构成ResNeXt网络的基本block单元,32*4d块结构,ResNeXt实际上是将group convolution 引进了ResNet中,以获得更少的参数。

ResNeXt与Resnet基本模块对比

ResNeXt网络的整体结构

正如ResNet是由基本的Residual模块一个个累积起来的一样,ResNeXt网络也是由上图中所描述的模块一个个累积起来的。下面表格当中,我们能看到ResNeXt与ResNet网络的整体结构。

在这里它Follow了之前VGG/ResNet等网络中的一贯做法:一是如果一个block输出同样大小的chnnel size,那么blocks输入、输出有着相同的hyper-parameters(即width和filter sizes);二是若其输出与输入有着不同的大小(如downsampling操作),那么就需要相应地扩大filters的数目。
从上面表格里,亦能看出ResNeXt与ResNet一样都follow这样两条准则以保证每个block的计算量类似,所传递的信息也不会因层数递增而有太多丢失。

ResNeXt网络模块的变形

下面图中显示了三种ResNeXt网络模块的变形。它们在数学计算上是完全等价的,而第三种包含有Group convolution操作的正是最终ResNeXt网络所采用的操作。

ResNeXt网络模块的三种等价形式

ResNeXt网络的Capacity

一般增强一个CNN的表达能力有三种手段:一是增加网络层次即加深网络;二是增加网络模块宽度;三是改善CNN网络结构设计

ResNeXt的做法可归为上面三种方法的第三种。它引入了新的用于构建CNN网络的模块,而此模块又非像过去看到的Inception module那么复杂,它更是提出了一个cardinatity的概念,用于作为模型复杂度的另外一个度量。Cardinatity指的是一个block中所具有的相同分支的数目。

作者进行了一系列对比实验,有力证明在保证相似计算复杂度及模型参数大小的前提下,提升cardinatity比提升height或width可取得更好的模型表达能力。

下图为反映Cardinatity增加对模型性能提升的实验结果。

Cardinatity增加对模型性能提升的影响

实验结果

ImageNet-1K

首先在标准的ImageNet-1K上进行了实验,并与其它state-of-art的模型进行了对比。

ResNeXt与ResNet网络在ImageNet-1K数据集上的性能对比

可以看出ResNeXt网络相对于ResNet网络可在training accuracy/val accuracy上都取得提升,但training accuracy上的提升显得更大。作者分析认为是因为ResNeXt的表达能力更强了,但此类结果改善却并非是因为Regularization的因素导致的。
为了减少可能的overfitting以来有效利用ResNext的表达能力,作者还试着在更大的ImageNet-5K上进行了实验。

下表为ResNeXt与更多CNN网络在ImageNet-1K上的结果比较。

ResNeXt与其它CNN网络在ImageNet-1K上的结果比较

ImageNet-5K

下面的图与表格中反映了ResNeXt与ResNet相比在更大的ImageNet-5K数据集上的优势。

ResNeXt与ResNet网络在ImageNet-5K上的结果比较

9 CNN设计准则

9.1避免信息瓶颈

(1)卷积过程中:空间尺寸H * W逐渐变小,输出通道数C逐渐变多

(2)H * W * C要缓慢变小

9.2通道(卷积核)数量保持在可控范围内

(1)输出通道数量C

(2)输出通道数量K

(3)参数数量

(4)操作数量

从C * K 可以看出卷积核大小对总计算量的影响

9.3 感受野要足够大

(1)卷积是基于局部图片的操作

(2)捕捉大尺寸内容

(3)多个小尺寸卷积核 VS 一个大尺寸卷积核(参数少,计算快,多个非线性激活)

 

9.4 分组策略降低计算量

9.5 低秩分解  降低参数&计算量

10 典型CNN的参数与效果对比

11 ImageNet分类

12 YOLO V3 原理架构:

原理深度解析:yolo系列之yolo v3【深度解析】_yolov3-CSDN博客

YOLOv3借鉴了FPN的思想,从不同尺度提取特征。相比YOLOv2,YOLOv3提取最后3层特征图,不仅在每个特征图上分别独立做预测,同时通过将小特征图上采样到与大的特征图相同大小,然后与大的特征图拼接做进一步预测。用维度聚类的思想聚类出9种尺度的anchor box,将9种尺度的anchor box均匀的分配给3种尺度的特征图,再进行预测目标坐标和目标分类。

darknet-53结构类似于 Resnet的网络结构,用于架构图中的backbone部分,作用是对输入图像进行特征提取。(如下图:全连接加卷积层,共53层)

YOLOv3_TensorFlow版本代码训练流程:

Github地址:GitHub - wizyoung/YOLOv3_TensorFlow: Complete YOLO v3 TensorFlow implementation. Support training on your own dataset.

  1. 环境搭建及模块需求(根据不同项目代码,安装所需模块):
    1. Python version : 2 or 3
    2. Tensorflow >= 1.8.0
    3. opencv-python
    4. Tqdm
  2. 预训练模型转换

下载darknet的预训练 .weights 模型文件,通过执行 python convert_weight.py 转换生成Tensorflow所需要的 .ckpt 模型文件,保存到 ./data/darknet_weights/ 文件夹,作为本项目的预训练模型。

  1. 数据预处理

(1) 可以将 LabelImg标注后的数据处理成标准的voc数据集格式,

通过脚本生成train.txt / val.txt / test.txt 文件,文件内容如下

绿色部分--->图片路径

黄色部分--->真实框的坐标值(x,y,w,h)

紫色部分--->目标类别

  1.  类别名称文件

在 ./data/my_data/ 文件夹下建立 data.names 文件,内容如下:

(3) 通过脚本 get_kmeans.py 进行维度聚类生成数据的9组anchor,保存在 ./data/yolo_anchors.txt文件,内容如下:

  1. 训练前代码中主要参数解释

batch_size                # 每次输入网络进行一次正向传播的图片样本数目

width   height  channels   # 输入图片的长宽和通道数

momentum=0.9            # 动量系数

decay=0.0005             # 权重衰减正则项,防止过拟合

learning_rate=0.001        # 初始学习率

max_batches = 500200     # 训练达到max_batches后停止学习

steps=400000,450000      # 根据batch_num调整学习率

optimizer选择           # SGD、momentum、Adam ......

5、开始训练

CUDA_VISIBLE_DEVICES=GPU_ID  python train.py

  1. 验证

执行 python eval.py 将测试集进行验证,计算得出 mAP 值。

mAP计算(各个目标类别的平均准确率)参考链接:

理解目标检测当中的mAP_map50是什么意思-CSDN博客

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值