神经网络与深度学习【自用】

  1. 神经网络与深度学习

1.1深度学习概述

深度学习(Deep Learning)就是更复杂的神经网络(Neural Network)

神经网络(Neural Network)

引入神经网络模型的概念:

假如我们要建立房价的预测模型,一共有六个房子。我们已知输入x即每个房子的面积(多少尺或者多少平方米),还知道其对应的输出y即每个房子的价格。根据这些输入输出,我们要建立一个函数模型,来预测房价:y=f(x)。首先,我们将已知的六间房子的价格和面积的关系绘制在二维平面上,如下图所示:

一般地,我们会用一条直线来拟合图中这些离散点,即建立房价与面积的线性模型。但是从实际考虑,我们知道价格永远不会是负数。所以,我们对该直线做一点点修正,让它变成折线的形状,当面积小于某个值时,价格始终为零。如下图蓝色折线所示,就是我们建立的房价预测模型。

其实这个简单的模型(蓝色折线)就可以看成是一个神经网络,而且几乎是一个最简单的神经网络。我们把该房价预测用一个最简单的神经网络模型来表示,如下图所示:

该神经网络的输入x是房屋面积,输出y是房屋价格,中间包含了一个神经元(neuron),即房价预测函数(蓝色折线)该神经元的功能就是实现函数f(x)的功能。

值得一提的是,上图神经元的预测函数(蓝色折线)在神经网络应用中比较常见。我们把这个函数称为ReLU函数,即线性整流函数(Rectified Linear Unit)/ 修正线性单元,形如下图所示:

上面讲的只是由单个神经元(输入x仅仅是房屋面积一个因素)组成的神经网络,而通常一个大型的神经网络往往由许多神经元组成,就像通过乐高积木搭建复杂物体(例如火车)一样。

现在,我们把上面举的房价预测的例子变得复杂一些,而不是仅仅使用房屋面积一个判断因素。例如,除了考虑房屋面积(size)之外,我们还考虑卧室数目(#bedrooms)。这两点实际上与家庭成员的个数(family size)有关。还有,房屋的邮政编码(zip code/postal code),代表了该房屋位置的交通便利性,是否需要步行还是开车?即决定了可步行性(walkability)。另外,还有可能邮政编码和地区财富水平(wealth)共同影响了房屋所在地区的学校质量(school quality)。如下图所示,该神经网络共有三个神经元,分别代表了family size,walkability和school quality。每一个神经元都包含了一个ReLU函数(或者其它非线性函数)。那么,根据这个模型,我们可以根据房屋的面积和卧室个数来估计family size,根据邮政编码来估计walkability,根据邮政编码和财富水平来估计school quality。最后,由family size,walkability和school quality等这些人们比较关心的因素来预测最终的房屋价格。

所以,在这个例子中,x是size,#bedrooms,zip code/postal code和wealth这四个输入;y是房屋的预测价格。这个神经网络模型包含的神经元个数更多一些,相对之前的单个神经元的模型要更加复杂。那么,在建立一个表现良好的神经网络模型之后,在给定输入x时,就能得到比较好的输出y,即房屋的预测价格。

实际上,上面这个例子真正的神经网络模型结构如下所示。它有四个输入,分别是:size,#bedrooms,zip code和wealth。在给定这四个输入后,神经网络所做的就是输出房屋的预测价格y。图中,三个神经元所在的位置称之为中间层或者隐藏层(x所在的称之为输入层,y所在的称之为输出层),每个神经元与所有的输入x都有关联(直线相连)。

这就是基本的神经网络模型结构。在训练的过程中,只要有足够的输入x和输出y,就能训练出较好的神经网络模型,该模型在此类房价预测问题中,能够得到比较准确的结果。

监督式学习在神经网络中的应用(Supervised Learning with Neural Networks)

目前为止,由神经网络模型创造的价值基本上都是基于监督式学习(Supervised Learning)的。监督式学习与非监督式学习本质区别就是是否已知训练样本的输出y。在实际应用中,机器学习解决的大部分问题都属于监督式学习,神经网络模型也大都属于监督式学习。下面我们来看几个监督式学习在神经网络中应用的例子。

首先,第一个例子还是房屋价格预测。根据训练样本的输入x和输出y,训练神经网络模型,预测房价。

第二个例子是线上广告,这是深度学习最广泛、最赚钱的应用之一。其中,输入x是广告和用户个人信息,输出y是用户是否对广告进行点击。神经网络模型经过训练,能够根据广告类型和用户信息对用户的点击行为进行预测,从而向用户提供用户自己可能感兴趣的广告。

第三个例子是电脑视觉(computer vision)。电脑视觉是近些年来越来越火的课题,而电脑视觉发展迅速的原因很大程度上是得益于深度学习。其中,输入x是图片像素值,输出是图片所属的不同类别。

第四个例子是语音识别(speech recognition)。深度学习可以将一段语音信号辨识为相应的文字信息。

第五个例子是智能翻译,例如通过神经网络输入英文,然后直接输出中文。

第六个例子是自动驾驶。通过输入一张图片或者汽车雷达信息,神经网络通过训练来告诉你相应的路况信息并作出相应的决策。

至此,神经网络配合监督式学习,其应用是非常广泛的。

我们应该知道,根据不同的问题和应用场合,应该使用不同类型的神经网络模型。例如上面介绍的几个例子中,对于一般的监督式学习(房价预测和线上广告问题),我们只要使用标准的神经网络模型就可以了。而对于图像识别处理问题,我们则要使用卷积神经网络(Convolution Neural Network),即CNN。而对于处理类似语音这样的序列信号时,则要使用循环神经网络(Recurrent Neural Network),即RNN。还有其它的例如自动驾驶这样的复杂问题则需要更加复杂的混合神经网络模型

CNN和RNN是比较常用的神经网络模型。下图给出了Standard NN,Convolutional NN和Recurrent NN的神经网络结构图。

CNN一般处理图像问题,RNN一般处理语音信号。他们的结构是什么意思?如何实现CNN和RNN的结构?这些问题我们将在以后的课程中来深入分析并解决。

另外,数据类型一般分为两种:Structured Data和Unstructured Data。

简单地说,Structured Data通常指的是有实际意义的数据。例如房价预测中的size,#bedrooms,price等;例如在线广告中的User Age,Ad ID等。这些数据都具有实际的物理意义,比较容易理解。而Unstructured Data通常指的是比较抽象的数据,例如Audio,Image或者Text。以前,计算机对于Unstructured Data比较难以处理,而人类对Unstructured Data却能够处理的比较好,例如我们第一眼很容易就识别出一张图片里是否有猫,但对于计算机来说并不那么简单。现在,值得庆幸的是,由于深度学习和神经网络的发展,计算机在处理Unstructured Data方面效果越来越好,甚至在某些方面优于人类。总的来说,神经网络与深度学习无论对Structured Data还是Unstructured Data都能处理得越来越好,并逐渐创造出巨大的实用价值。我们在之后的学习和实际应用中也将会碰到许多Structured Data和Unstructured Data。

深度学习背后的主要动力(Why is Deep Learning taking off?)

如果说深度学习和神经网络背后的技术思想已经出现数十年了,那么为什么直到现在才开始发挥作用呢?接下来,我们来看一下深度学习背后的主要动力是什么,方便我们更好地理解并使用深度学习来解决更多问题。

深度学习为什么这么强大?下面我们用一张图来说明。如下图所示,横坐标x表示数据量(Amount of data),纵坐标y表示机器学习模型的性能表现(Performance)

上图共有4条曲线。其中,最底下的那条红色曲线代表了传统机器学习算法的表现,例如是SVM,logistic regression,decision tree等。当数据量比较小的时候,传统学习模型的表现是比较好的。但是当数据量很大的时候,其表现很一般,性能基本趋于水平。红色曲线上面的那条黄色曲线代表了规模较小的神经网络模型(Small NN)。它在数据量较大时候的性能优于传统的机器学习算法。黄色曲线上面的蓝色曲线代表了规模中等的神经网络模型(Media NN),它在在数据量更大的时候的表现比Small NN更好。最上面的那条绿色曲线代表更大规模的神经网络(Large NN),即深度学习模型。从图中可以看到,在数据量很大的时候,它的表现仍然是最好的,而且基本上保持了较快上升的趋势。

值得一提的是,近些年来,由于数字计算机的普及,人类进入了大数据时代,每时每分,互联网上的数据是海量的、庞大的。如何对大数据建立稳健准确的学习模型变得尤为重要。传统机器学习算法在数据量较大的时候,性能一般,很难再有提升。然而,深度学习模型由于网络复杂,对大数据的处理和分析非常有效。所以,近些年来,在处理海量数据和建立复杂准确的学习模型方面,深度学习有着非常不错的表现。然而,在数据量不大的时候,例如上图中左边区域,深度学习模型不一定优于传统机器学习算法,性能差异可能并不大。

所以说,现在深度学习如此强大的原因归结为三个因素:

  • Data

  • Computation

  • Algorithms

其中,数据量的几何级数增加,加上GPU出现、计算机运算能力的大大提升,使得深度学习能够应用得更加广泛。另外,算法上的创新和改进让深度学习的性能和速度也大大提升

举个算法改进的例子:

之前神经网络神经元的激活函数Sigmoid函数,后来改成了ReLU函数。之所以这样更改的原因是对于Sigmoid函数,在远离零点的位置,函数曲线非常平缓,其梯度趋于0,所以造成神经网络模型学习速度变得很慢。然而,ReLU函数在x大于零的区域,其梯度始终为1,尽管在x小于零的区域梯度为0,但是在实际应用中采用ReLU函数确实要比Sigmoid函数快很多。

构建一个深度学习的流程是:

首先产生Idea,然后将Idea转化为Code,最后进行Experiment。接着根据结果修改Idea,继续这种Idea->Code->Experiment的循环,直到最终训练得到表现不错的深度学习网络模型。如果计算速度越快,每一步骤耗时越少,那么上述循环越能高效进行。

Summary

本节课的内容比较简单,主要对深度学习进行了简要概述。首先,我们使用房价预测的例子来建立最简单的但个神经元组成的神经网络模型。然后,我们将例子复杂化,建立标准的神经网络模型结构。接着,我们从监督式学习入手,介绍了不同的神经网络类型,包括Standard NN,CNN和RNN。不同的神经网络模型适合处理不同类型的问题。对数据集本身来说,分为Structured Data和Unstructured Data。近些年来,深度学习对Unstructured Data的处理能力大大提高,例如图像处理、语音识别和语言翻译等。最后,我们用一张对比图片解释了深度学习现在飞速发展、功能强大的原因。归纳其原因包含三点:Data,Computation和Algorithms。

1.2神经网络基础之逻辑回归

二分类(Binary Classification)

我们知道逻辑回归模型一般用来解决二分类(Binary Classification)问题。二分类就是输出y只有{0,1}两个离散值(也有{-1,1}的情况)。我们以一个图像识别问题为例,判断图片中是否有猫存在,0代表noncat,1代表cat。主要是通过这个例子简要介绍神经网络模型中一些标准化的、有效率的处理方法和notations。

如上图所示,这是一个典型的二分类问题。一般来说,彩色图片包含RGB三个通道。例如该cat图片的尺寸为(64,64,3)。在神经网络模型中,我们首先要将图片输入x(维度是(64,64,3))转化为一维的特征向量(feature vector)。方法是每个通道一行一行取,再连接起来。由于64x64x3=12288,则转化后的输入特征向量维度为(12288,1)。此特征向量x是列向量,维度一般记为nx。

如果训练样本共有m张图片,那么整个训练样本X组成了矩阵,维度是(nx,m)。注意,这里矩阵X的行nx代表了每个样本x(i)特征个数列m代表了样本个数。这里,Andrew解释了X的维度之所以是(nx,m)而不是(m,nx)的原因是为了之后矩阵运算的方便。算是Andrew给我们的一个小小的经验吧。而所有训练样本的输出Y也组成了一维的行向量,写成矩阵的形式后,它的维度就是(1,m)。

逻辑回归(Logistic Regression)

接下来我们就来介绍如何使用逻辑回归来解决二分类问题。逻辑回归中,预测值h^=P(y=1 | x)表示为1的概率,取值范围在[0,1]之间。这是其与二分类模型不同的地方。使用线性模型,引入参数w和b。权重w的维度是(nx,1),b是一个常数项。这样,逻辑回归的线性预测输出可以写成:

值得注意的是,很多其它机器学习资料中,可能把常数b当做w0处理,并引入x0=1。这样从维度上来看,x和w都会增加一维。但在本课程中,为了简化计算和便于理解,Andrew建议还是使用上式这种形式将w和b分开比较好。

上式的线性输出区间为整个实数范围,而逻辑回归要求输出范围在[0,1]之间,所以还需要对上式的线性函数输出进行处理。方法是引入Sigmoid函数,让输出限定在[0,1]之间。这样,逻辑回归的预测输出就可以完整写成:

Sigmoid函数是一种非线性的S型函数,输出被限定在[0,1]之间,通常被用在神经网络中当作激活函数(Activation function)使用。Sigmoid函数的表达式和曲线如下所示:

从Sigmoid函数曲线可以看出,当z值很大时,函数值趋向于1;当z值很小时,函数值趋向于0。且当z=0时,函数值为0.5。还有一点值得注意的是,Sigmoid函数的一阶导数可以用其自身表示

这样,通过Sigmoid函数,就能够将逻辑回归的输出限定在[0,1]之间了。

逻辑回归代价函数(Logistic Regression Cost Function)

逻辑回归中,w和b都是未知参数,需要反复训练优化得到。因此,我们需要定义一个cost function,包含了参数w和b。通过优化cost function,当cost function取值最小时,得到对应的w和b。

对于m个训练样本,我们通常使用上标来表示对应的样本。例如(x(i),y(i))表示第i个样本。

定义所有m个样本的cost function:

先从单个样本出发,我们希望该样本的预测值y^与真实值越相似越好。我们把单个样本的cost function用Loss function来表示,根据以往经验,如果使用平方错误(squared error)来衡量:

但是,对于逻辑回归,我们一般不使用平方错误来作为Loss function。原因是这种Loss function一般是non-convex非凸优化的。non-convex函数在使用梯度下降算法时,容易得到局部最小值(local minumum),即局部最优化。而我们最优化的目标是计算得到全局最优化(Global optimization)。因此,我们一般选择的Loss function应该是convex的。

Loss function的原则和目的就是要衡量预测输出y^与真实样本输出y的接近程度。平方错误其实也可以,只是它是non-convex的,不利于使用梯度下降算法来进行全局优化。因此,我们可以构建另外一种Loss function,且是convex的,如下所示:

我们来分析一下这个Loss function,它是衡量错误大小的,Loss function越小越好。

因此,这个Loss function能够很好地反映预测输出y^与真实样本输出y的接近程度,越接近的话,其Loss function值越小。而且这个函数是convex的。上面我们只是简要地分析为什么要使用这个Loss function,后面的课程中,我们将详细推导该Loss function是如何得到的。并不是凭空捏造的哦。

上面介绍的Loss function是针对单个样本的。那对于m个样本,我们定义Cost function,Cost function是m个样本的Loss function的平均值,反映了m个样本的预测输出y^与真实样本输出y的平均接近程度。Cost function可表示为:

Cost function已经推导出来了,Cost function是关于待求系数w和b的函数。我们的目标就是迭代计算出最佳的w和b值,最小化Cost function,让Cost function尽可能地接近于零。

其实逻辑回归问题可以看成是一个简单的神经网络,只包含一个神经元。这也是我们这里先介绍逻辑回归的原因。

梯度下降(Gradient Descent)

我们已经掌握了Cost function的表达式,接下来将使用梯度下降(Gradient Descent)算法来计算出合适的w和b值,从而最小化m个训练样本的Cost function,即J(w,b)。

由于J(w,b)是convex function,梯度下降算法是先随机选择一组参数w和b值,然后每次迭代的过程中分别沿着w和b的梯度(偏导数)的反方向前进一小步,不断修正w和b。每次迭代更新w和b后,都能让J(w,b)更接近全局最小值。梯度下降的过程如下图所示。

梯度下降算法每次迭代更新,w和b的修正表达式为:

上式中,α学习因子(learning rate),表示梯度下降的步进长度α越大,w和b每次更新的“步伐”更大一些;α越小,w和b每次更新的“步伐”更小一些。在程序代码中,我们通常

微积分里,df/dx表示对单一变量求导数,∂f/x表示对多个变量中某个变量求偏导数。

梯度下降算法能够保证每次迭代w和b都能向着J(w,b)全局最小化的方向进行。其数学原理主要是运用泰勒一阶展开来证明的,可以参考我的另一篇博客中的Gradient Descent有提到如何推导:台湾大学林轩田机器学习基石课程学习笔记10 – Logistic Regression

Derivatives

这一部分的内容非常简单,Andrew主要是给对微积分、求导数不太清楚的同学介绍的。梯度或者导数一定程度上可以看成是斜率。关于求导数的方法这里就不再赘述了。

More Derivative Examples

Andrew给出了更加复杂的求导数的例子,略。

计算图(Computation graph)

整个神经网络的训练过程实际上包含了两个过程:正向传播(Forward Propagation)反向传播(Back Propagation)正向传播从输入到输出,由神经网络计算得到预测输出的过程;反向传播从输出到输入,对参数w和b计算梯度的过程。下面,我们用计算图(Computation graph)的形式来理解这两个过程。

举个简单的例子,假如Cost function为J(a,b,c)=3(a+bc),包含a,b,c三个变量。我们用u表示bc,v表示a+u,则J=3v。它的计算图可以写成如下图所示:

令a=5,b=3,c=2,则u=bc=6,v=a+u=11,J=3v=33。计算图中,这种从左到右,从输入到输出的过程就对应着神经网络或者逻辑回归中输入与权重经过运算计算得到Cost function的正向过程。

使用计算图求导数(Derivatives with a Computation Graph)

上一部分介绍的是计算图的正向传播(Forward Propagation),下面我们来介绍其反向传播(Back Propagation),即计算输出对输入的偏导数。

还是上个计算图的例子,输入参数有3个,分别是a,b,c。

首先计算J对参数a的偏导数。从计算图上来看,从右到左,J是v的函数,v是a的函数。则利用求导技巧,可以得到:

根据这种思想,然后计算J对参数b的偏导数。从计算图上来看,从右到左,J是v的函数,v是u的函数,u是b的函数。可以推导:

最后计算J对参数c的偏导数。仍从计算图上来看,从右到左,J是v的函数,v是u的函数,u是c的函数。可以推导:

为了统一格式,在程序代码中,我们使用da,db,dc来表示J对参数a,b,c的偏导数。

逻辑回归梯度下降(Logistic Regression Gradient Descent)

现在,我们将对逻辑回归进行梯度计算。对单个样本而言,逻辑回归Loss function表达式如下:

首先,该逻辑回归的正向传播过程非常简单。根据上述公式,例如输入样本x有两个特征(x1,x2),相应的权重w维度也是2,即(w1,w2)。则z=w1x1+w2x2+b,最后的Loss function如下所示:

然后,计算该逻辑回归的反向传播过程,即由Loss function计算参数w和b的偏导数。推导过程如下:

知道了dz之后,就可以直接对w1,w2和b进行求导了。

则梯度下降算法可表示为:

多个样本 Gradient descent on m examples

上一部分讲的是对单个样本求偏导和梯度下降。如果有m个样本,其Cost function表达式如下:

Cost function关于w和b的偏导数可以写成和平均的形式:

这样,每次迭代中w和b的梯度有m个训练样本计算平均值得到。其算法流程图如下所示:

经过每次迭代后,根据梯度下降算法,w和b都进行更新:

这样经过n次迭代后,整个梯度下降算法就完成了。

值得一提的是,在上述的梯度下降算法中,我们是利用for循环对每个样本进行dw1,dw2和db的累加计算最后再求平均数的。在深度学习中,样本数量m通常很大,使用for循环会让神经网络程序运行得很慢。所以,我们应该尽量避免使用for循环操作,而使用矩阵运算,能够大大提高程序运行速度。关于vectorization的内容我们放在下次笔记中再说。

Summary

本节课的内容比较简单,主要介绍了神经网络的基础——逻辑回归。首先,我们介绍了二分类问题,以图片为例,将多维输入x转化为feature vector,输出y只有{0,1}两个离散值。接着,我们介绍了逻辑回归及其对应的Cost function形式。然后,我们介绍了梯度下降算法,并使用计算图的方式来讲述神经网络的正向传播和反向传播两个过程。最后,我们在逻辑回归中使用梯度下降算法,总结出最优化参数w和b的算法流程。

1.3神经网络基础之Python与向量化

上节课我们主要介绍了逻辑回归,以输出概率的形式来处理二分类问题。我们介绍了逻辑回归的Cost function表达式,并使用梯度下降算法来计算最小化Cost function时对应的参数w和b。通过计算图的方式来讲述了神经网络的正向传播和反向传播两个过程。本节课我们将来探讨Python和向量化的相关知识。

向量化Vectorization

深度学习算法中,数据量很大,在程序中应该尽量减少使用loop循环语句,而可以使用向量运算来提高程序运行速度。

向量化(Vectorization)就是利用矩阵运算的思想,大大提高运算速度。

从程序运行结果上来看,该例子使用for循环运行时间是使用向量运算运行时间的约300倍。因此,深度学习算法中,使用向量化矩阵运算的效率要高得多。

为了加快深度学习神经网络运算速度,可以使用比CPU运算能力更强大的GPU。事实上,GPU和CPU都有并行指令(parallelization instructions),称为Single Instruction Multiple Data(SIMD)。SIMD是单指令多数据流,能够复制多个操作数,并把它们打包在大型寄存器的一组指令集。SIMD能够大大提高程序运行速度,例如python的numpy库中的内建函数(built-in function)就是使用了SIMD指令。相比而言,GPU的SIMD要比CPU更强大一些。

Summary

本节课我们主要介绍了神经网络基础——python和向量化。在深度学习程序中,使用向量化和矩阵运算的方法能够大大提高运行速度,节省时间。以逻辑回归为例,我们将其算法流程包括梯度下降转换为向量化的形式。同时,我们也介绍了python的相关编程方法和技巧。


1.4浅层神经网络

上节课我们主要介绍了向量化、矩阵计算的方法和python编程的相关技巧。并以逻辑回归为例,将其算法流程包括梯度下降转换为向量化的形式,从而大大提高了程序运算速度。本节课我们将从浅层神经网络入手,开始真正的神经网络模型的学习。

神经网络概述(Neural Networks Overvie)

首先,我们从整体结构上来大致看一下神经网络模型。

使用计算图方式介绍逻辑回归梯度下降算法的正向传播和反向传播两个过程。

如下图所示。神经网络的结构与逻辑回归类似,只是神经网络的层数比逻辑回归多一层,多出来的中间那层称为隐藏层或中间层。这样从计算上来说,神经网络的正向传播和反向传播过程只是比逻辑回归多了一次重复的计算。

正向传播过程分成两层:

1)第一层是输入层到隐藏层,用上标[1]来表示:

2)第二层是隐藏层到输出层,用上标[2]来表示:

在写法上值得注意的是,方括号上标[i]表示当前所处的层数;圆括号上标(i)表示第i个样本。

同样,反向传播过程也分成两层:

第一层是输出层到隐藏层,第二层是隐藏层到输入层。其细节部分我们之后再来讨论。

神经网络表示(Neural Network Representation)

下面我们以图示的方式来介绍单隐藏层的神经网络结构。如下图所示,单隐藏层神经网络就是典型的浅层(shallow)神经网络。

结构上,从左到右,可以分成三层:输入层(Input layer)隐藏层(Hidden layer)输出层(Output layer)。输入层和输出层,顾名思义,对应着训练样本的输入和输出,很好理解。隐藏层是抽象的非线性的中间层,这也是其被命名为隐藏层的原因。

在写法上,我们通常把输入矩阵X记为a[0],把隐藏层输出记为a[1],上标从0开始。用下标表示第几个神经元,注意下标从1开始。例如a[1]1表示隐藏层第1个神经元,a[1]2表示隐藏层第2个神经元,,等等。这样,隐藏层有4个神经元就可以将其输出a[1]写成矩阵的形式:

最后,相应的输出层记为a[2],即y^。这种单隐藏层神经网络也被称为两层神经网络(2 layer NN)。之所以叫两层神经网络是因为,通常我们只会计算隐藏层输出和输出层的输出,输入层是不用计算的。这也是我们把输入层层数上标记为0的原因(a[0])。

关于隐藏层对应的权重W[1]和常数项b[1],W[1]的维度是(4,3)。这里的4对应着隐藏层神经元个数,3对应着输入层x特征向量包含元素个数。常数项b[1]的维度是(4,1),这里的4同样对应着隐藏层神经元个数。关于输出层对应的权重W[2]和常数项b[2],W[2]的维度是(1,4),这里的1对应着输出层神经元个数,4对应着输出层神经元个数。常数项b[2]的维度是(1,1),因为输出只有一个神经元。总结一下,第i层的权重W[i]维度的行等于i层神经元的个数,列等于i-1层神经元的个数第i层常数项b[i]维度的行等于i层神经元的个数,列始终为1

神经网络计算(Computing a Neural Network's Output)

接下来我们开始详细推导神经网络的计算过程。回顾一下,我们前面讲过两层神经网络可以看成是逻辑回归再重复计算一次。如下图所示,逻辑回归的正向计算可以分解成计算z和a的两部分

对于两层神经网络,从输入层到隐藏层对应一次逻辑回归运算;从隐藏层到输出层对应一次逻辑回归运算。每层计算时,要注意对应的上标和下标,一般我们记上标方括号表示layer层下标表示第几个神经元。例如ai[l]表示第l层的第i个神经元。注意,i从1开始,l从0开始。

下面,我们将从输入层到输出层的计算公式列出来:

然后,从隐藏层到输出层的计算公式为:

其中a[1]为:

上述每个节点的计算都对应着一次逻辑运算的过程,分别由计算z和a两部分组成。

为了提高程序运算速度,我们引入向量化和矩阵运算的思想,将上述表达式转换成矩阵运算的形式:

之前也介绍过,这里顺便提一下,W[1]的维度是(4,3),b[1]的维度是(4,1),W[2]的维度是(1,4),b[2]的维度是(1,1)。这点需要特别注意。

多样本的向量化(Vectorizing across multiple examples)

上一部分我们只是介绍了单个样本的神经网络正向传播矩阵运算过程。而对于m个训练样本,我们也可以使用矩阵相乘的形式来提高计算效率。而且它的形式与上一部分单个样本的矩阵运算十分相似,比较简单。

之前我们也介绍过,在书写标记上用上标(i)表示第i个样本,例如x(i),z(i),a[2](i)。对于每个样本i,可以使用for循环来求解其正向输出:

不使用for循环,利用矩阵运算的思想,输入矩阵X的维度为(nx,m)。这样,我们可以把上面的for循环写成矩阵运算的形式:

其中,Z[1]的维度是(4,m),4是隐藏层神经元的个数;A[1]的维度与Z[1]相同;Z[2]和A[2]的维度均为(1,m)。对上面这四个矩阵来说,均可以这样来理解:行表示神经元个数,列表示样本数目m。

Explanation for Vectorized Implementation

这部分Andrew用图示的方式解释了m个样本的神经网络矩阵运算过程。其实内容比较简单,只要记住上述四个矩阵的行表示神经元个数,列表示样本数目m就行了。

值得注意的是输入矩阵X也可以写成A[0]。

激活函数(Activation functions)

神经网络隐藏层和输出层都需要激活函数(activation function),在之前的课程中我们都默认使用Sigmoid函数σ(x)作为激活函数。其实,还有其它激活函数可供使用,不同的激活函数有各自的优点。下面我们就来介绍几个不同的激活函数g(x)。

  • sigmoid函数

  • tanh函数

  • ReLU函数

  • Leaky ReLU函数

如上图所示,不同激活函数形状不同,a的取值范围也有差异。如何选择合适的激活函数呢?

首先我们来比较sigmoid函数tanh函数

对于隐藏层的激活函数,一般来说,tanh函数要比sigmoid函数表现更好一些。因为tanh函数的取值范围在[-1,+1]之间,隐藏层的输出被限定在[-1,+1]之间,可以看成是在0值附近分布,均值为0。这样从隐藏层到输出层,数据起到了归一化(均值为0)的效果。因此,隐藏层的激活函数,tanh比sigmoid更好一些。

而对于输出层的激活函数,因为二分类问题的输出取值为{0,+1},所以一般会选择sigmoid作为激活函数。

观察sigmoid函数和tanh函数,我们发现有这样一个问题,就是当|z|很大的时候,激活函数的斜率(梯度)很小。因此,在这个区域内,梯度下降算法会运行得比较慢。在实际应用中,应尽量避免使z落在这个区域,使|z|尽可能限定在零值附近,从而提高梯度下降算法运算速度

为了弥补sigmoid函数和tanh函数的这个缺陷,就出现了ReLU激活函数。ReLU激活函数在z大于零时梯度始终为1;在z小于零时梯度始终为0;z等于零时的梯度可以当成1也可以当成0,实际应用中并不影响。对于隐藏层,选择ReLU作为激活函数能够保证z大于零时梯度始终为1,从而提高神经网络梯度下降算法运算速度。但当z<0时,存在梯度为0的缺点,实际应用中,这个缺点影响不是很大。为了弥补这个缺点,出现了Leaky ReLU激活函数,能够保证z<0时梯度不为0。

最后总结一下,如果是分类问题输出层的激活函数一般会选择sigmoid函数。但是隐藏层的激活函数通常不会选择sigmoid函数,tanh函数的表现会比sigmoid函数好一些。实际应用中,通常会会选择使用ReLU或者Leaky ReLU函数,保证梯度下降速度不会太小。其实,具体选择哪个函数作为激活函数没有一个固定的准确的答案,应该要根据具体实际问题进行验证(validation)。

非线性激活函数(Why do you need non-linear activation functions)

我们知道上一部分讲的四种激活函数都是非线性(non-linear)的。那是否可以使用线性激活函数呢?答案是不行!下面我们就来进行简要的解释和说明。

假设所有的激活函数都是线性的,为了简化计算,我们直接令激活函数g(z)=z,即a=z。那么,浅层神经网络的各层输出为:

我们对上式中a[2]进行化简计算:

经过推导我们发现a[2]仍是输入变量x的线性组合。这表明,使用神经网络与直接使用线性模型的效果并没有什么两样。即便是包含多层隐藏层的神经网络,如果使用线性函数作为激活函数,最终的输出仍然是输入x的线性模型。这样的话神经网络就没有任何作用了。因此,隐藏层的激活函数必须要是非线性的。

另外,如果所有的隐藏层全部使用线性激活函数,只有输出层使用非线性激活函数,那么整个神经网络的结构就类似于一个简单的逻辑回归模型,而失去了神经网络模型本身的优势和价值。

值得一提的是,如果是预测问题而不是分类问题,输出y是连续的情况下,输出层的激活函数可以使用线性函数。如果输出y恒为正值,则也可以使用ReLU激活函数,具体情况,具体分析。

激活函数的导数:梯度(Derivatives of activation functions)

在梯度下降反向计算过程中少不了计算激活函数的导数即梯度

我们先来看一下sigmoid函数的导数:

对于tanh函数的导数:

对于ReLU函数的导数:

对于Leaky ReLU函数:

在神经网络中进行梯度计算(Gradient descent for neural networks)

在神经网络中如何进行梯度计算

该神经网络正向传播过程为:

反向传播是计算导数(梯度)的过程,这里先列出来Cost function对各个参数的梯度:

反向传播的具体推导过程我们下一部分再进行详细说明。

反向传播:Backpropagation intuition(optional)

我们仍然使用计算图的方式来推导神经网络反向传播过程。记得之前介绍逻辑回归时,我们就引入了计算图来推导正向传播和反向传播,其过程如下图所示:

由于多了一个隐藏层,神经网络的计算图要比逻辑回归的复杂一些,如下图所示。对于单个训练样本,正向过程很容易,反向过程可以根据梯度计算方法逐一推导

总结一下,浅层神经网络(包含一个隐藏层),m个训练样本的正向传播过程和反向传播过程分别包含了6个表达式,其向量化矩阵形式如下图所示:

初始化(Random Initialization)

神经网络模型中的参数权重W是不能全部初始化为零的,接下来我们分析一下原因。

举个简单的例子,一个浅层神经网络包含两个输入,隐藏层包含两个神经元。如果权重和都初始化为零,即:

这样使得隐藏层第一个神经元的输出等于第二个神经元的输出,即。经过推导得到,以及。因此,这样的结果是隐藏层两个神经元对应的权重行向量和每次迭代更新都会得到完全相同的结果,始终等于,完全对称。这样隐藏层设置多个神经元就没有任何意义了。值得一提的是,参数b可以全部初始化为零,并不会影响神经网络训练效果。

我们把这种权重W全部初始化为零带来的问题称为symmetry breaking problem(对称破坏问题)。解决方法也很简单,就是将W进行随机初始化(b可初始化为零)。python里可以使用如下语句进行W和b的初始化:

这里我们将和乘以0.01的目的是尽量使得权重W初始化比较小的值。之所以让W比较小,是因为如果使用sigmoid函数或者tanh函数作为激活函数的话,W比较小,得到的|z|也比较小(靠近零点),而零点区域的梯度比较大,这样能大大提高梯度下降算法的更新速度,尽快找到全局最优解。如果W较大,得到的|z|也比较大,附近曲线平缓,梯度较小,训练过程会慢很多。

当然,如果激活函数是ReLU或者Leaky ReLU函数,则不需要考虑这个问题。但是,如果输出层是sigmoid函数,则对应的权重W最好初始化到比较小的值。

Summary

本节课主要介绍了浅层神经网络。首先简单概述了神经网络的结构:包括输入层,隐藏层和输出层。然后,我们以计算图的方式推导了神经网络的正向输出,并以向量化的形式归纳出来。接着,介绍了不同的激活函数并做了比较,实际应用中根据不同需要选择合适的激活函数。激活函数必须是非线性的,不然神经网络模型起不了任何作用。然后重点介绍了神经网络的反向传播过程以及各个参数的导数推导,并以矩阵形式表示出来。最后介绍权重随机初始化的重要性,必须对权重W进行随机初始化操作。

1.5深层神经网络

上节课我们主要介绍了浅层神经网络。首先介绍神经网络的基本结构,包括输入层,隐藏层和输出层。然后以简单的2 layer NN为例,详细推导了其正向传播过程和反向传播过程,使用梯度下降的方法优化神经网络参数。同时,我们还介绍了不同的激活函数,比较各自优缺点,讨论了激活函数必须是非线性的原因。最后介绍了神经网络参数随机初始化的必要性,特别是权重W,不同神经元的W不能初始化为同一零值。本节课是对上节课的延伸和扩展,讨论更深层的神经网络。

深层神经网络(Deep L-layer neural network)

深层神经网络其实就是包含更多的隐藏层神经网络。如下图所示,分别列举了逻辑回归、1个隐藏层的神经网络、2个隐藏层的神经网络和5个隐藏层的神经网络它们的模型结构。

命名规则上,一般只参考隐藏层个数和输出层。例如,上图中的逻辑回归又叫1 layer NN,1个隐藏层的神经网络叫做2 layer NN,2个隐藏层的神经网络叫做3 layer NN,以此类推。如果是L-layer NN,则包含了L-1个隐藏层最后的L层是输出层

下面以一个4层神经网络为例来介绍关于神经网络的一些标记写法。如下图所示,首先,总层数用L表示,L=4。输入层是第0层,输出层是第L层。表示第层包含的单元个数,。这个模型中,,表示三个输入特征。,,,。第层的激活函数输出用表示,。表示第层的权重,用于计算。另外,我们把输入x记为,把输出层记为。

注意,和中的上标都是从1开始的,。

正向传播过程(Forward Propagation in a Deep Network)

接下来,我们来推导一下深层神经网络的正向传播过程。仍以上面讲过的4层神经网络为例

对于单个样本

第1层:

第2层:

第3层:

第4层:

如果有m个训练样本,其向量化矩阵形式为:

第1层:

第2层:

第3层:

第4层:

综上所述,对于第l层,其正向传播过程的和可以表示为:

Getting your matrix dimensions right

Why deep representations?

我们都知道神经网络能处理很多问题,而且效果显著。其强大能力主要源自神经网络足够“深”,也就是说网络层数越多,神经网络就更加复杂和深入,学习也更加准确。接下来,我们从几个例子入手,看一下为什么深度网络能够如此强大。

先来看人脸识别的例子,如下图所示。经过训练,神经网络第一层所做的事就是从原始图片中提取出人脸的轮廓与边缘,即边缘检测。这样每个神经元得到的是一些边缘信息。神经网络第二层所做的事情就是将前一层的边缘进行组合,组合成人脸一些局部特征,比如眼睛、鼻子、嘴巴等。再往后面,就将这些局部特征组合起来,融合成人脸的模样。可以看出,随着层数由浅到深,神经网络提取的特征也是从边缘到局部特征到整体,由简单到复杂。可见,如果隐藏层足够多,那么能够提取的特征就越丰富、越复杂,模型的准确率就会越高。

语音识别模型也是这个道理。浅层的神经元能够检测一些简单的音调,然后较深的神经元能够检测出基本的音素,更深的神经元就能够检测出单词信息。如果网络够深,还能对短语、句子进行检测。记住一点,神经网络从左到右,神经元提取的特征从简单到复杂。特征复杂度与神经网络层数成正相关。特征越来越复杂,功能也越来越强大。

除了从提取特征复杂度的角度来说明深层网络的优势之外,深层网络还有另外一个优点,就是能够减少神经元个数,从而减少计算量。例如下面这个例子,使用电路理论,计算逻辑输出:

其中,表示异或操作。对于这个逻辑运算,如果使用深度网络,深度网络的结构是每层将前一层的两两单元进行异或,最后到一个输出,如下图左边所示。这样,整个深度网络的层数是,不包含输入层。总共使用的神经元个数为:

可见,输入个数是n,这种深层网络所需的神经元个数仅仅是n-1个

如果不用深层网络,仅仅使用单个隐藏层,那么需要的神经元个数将是指数级别那么大。Andrew指出,由于包含了所有的逻辑位(0和1),则需要个神经元。这里笔者推导的是个神经元,为啥是请哪位高手解释下。

比较下来,处理同一逻辑问题,深层网络所需的神经元个数比浅层网络要少很多。这也是深层神经网络的优点之一。

尽管深度学习有着非常显著的优势,Andrew还是建议对实际问题进行建模时,尽量先选择层数少的神经网络模型,这也符合奥卡姆剃刀定律(Occam’s Razor)。对于比较复杂的问题,再使用较深的神经网络模型。

流程块图:Building blocks of deep neural networks

用流程块图来解释神经网络正向传播和反向传播过程

神经网络中的参数Parameters vs 超参数Hyperparameters

参数(parameters):我们熟悉的和

超参数(hyperparameters):例如学习速率,训练迭代次数N,神经网络层数L,各层神经元个数,激活函数等。之所以叫做超参数的原因是它们决定了参数和的值。在后面的第二门课我们还将学习其它的超参数,这里先不讨论。

如何设置最优的超参数是一个比较困难的、需要经验知识的问题。通常的做法是选择超参数一定范围内的值,分别代入神经网络进行训练,测试cost function,随着迭代次数增加的变化,根据结果选择cost function最小时对应的超参数值。这类似于validation的方法。

神经网络与人脑机制(What does this have to do with the brain?)

那么,神经网络跟人脑机制到底有什么联系呢?究竟有多少的相似程度?神经网络实际上可以分成两个部分:正向传播过程和反向传播过程。神经网络的每个神经元采用激活函数的方式,类似于感知机模型。这种模型与人脑神经元是类似的,可以说是一种非常简化的人脑神经元模型。

如下图所示,人脑神经元可分为树突、细胞体、轴突三部分。树突接收外界电刺激信号(类比神经网络中神经元输入),传递给细胞体进行处理(类比神经网络中神经元激活函数运算),最后由轴突传递给下一个神经元(类比神经网络中神经元输出)。

人脑神经元的结构和处理方式要复杂的多,神经网络模型只是非常简化的模型。人脑如何进行学习?是否也是通过反向传播和梯度下降算法现在还不清楚,可能会更加复杂。这是值得生物学家探索的事情。也许发现重要的新的人脑学习机制后,让我们的神经网络模型抛弃反向传播和梯度下降算法,能够实现更加准确和强大的神经网络模型!

Summary

本节课主要介绍了深层神经网络,是上一节浅层神经网络的拓展和归纳。首先,我们介绍了建立神经网络模型一些常用的标准的标记符号。然后,用流程块图的方式详细推导正向传播过程和反向传播过程的输入输出和参数表达式。我们也从提取特征复杂性和计算量的角度分别解释了深层神经网络为什么优于浅层神经网络。接着,我们介绍了超参数的概念,解释了超参数与参数的区别。最后,我们将神经网络与人脑做了类别,人工神经网络是简化的人脑模型。

  1. 优化深度神经网络

2.1深度学习的实用层面

  1. Train/Dev/Test sets

选择最佳的训练集(Training sets)、验证集(Development sets)、测试集(Test sets)对神经网络的性能影响非常重要。

除此之外,在构建一个神经网络的时候,我们需要设置许多参数,例如:神经网络的层数、每个隐藏层包含的神经元个数、学习因子(学习速率)、激活函数的选择等等。

实际上很难在第一次设置的时候就选择到这些最佳的参数,而是需要通过不断地迭代更新来获得

这个循环迭代的过程是这样的:

我们先有个想法Idea,先选择初始的参数值,构建神经网络模型结构;

然后通过代码Code的形式,实现这个神经网络;

最后,通过实验Experiment验证这些参数对应的神经网络的表现性能。

根据验证结果,我们对参数进行适当的调整优化,再进行下一次的Idea->Code->Experiment循环。通过很多次的循环,不断调整参数,选定最佳的参数值,从而让神经网络性能最优化。

深度学习已经应用于许多领域中,比如NLP,CV,Speech Recognition等等。通常来说,最适合某个领域的深度学习网络往往不能直接应用在其它问题上。解决不同问题的最佳选择是根据样本数量、输入特征数量和电脑配置信息(GPU或者CPU)等,来选择最合适的模型。即使是最有经验的深度学习专家也很难第一次就找到最合适的参数。因此,应用深度学习是一个反复迭代的过程,需要通过反复多次的循环训练得到最优化参数。决定整个训练过程快慢的关键在于单次循环所花费的时间,单次循环越快,训练过程越快。而设置合适的Train/Dev/Test sets数量,能有效提高训练效率。

一般地,我们将所有的样本数据分成三个部分:Train/Dev/Test sets。

Train sets用来训练你的算法模型;

Dev sets用来验证不同算法的表现情况,从中选择最好的算法模型;

Test sets用来测试最好算法的实际表现,作为该算法的无偏估计。

之前人们通常设置Train sets和Test sets的数量比例为70%和30%。如果有Dev sets,则设置比例为60%、20%、20%,分别对应Train/Dev/Test sets。这种比例分配在样本数量不是很大的情况下,例如100,1000,10000,是比较科学的。但是如果数据量很大的时候,例如100万,这种比例分配就不太合适了。科学的做法是要将Dev sets和Test sets的比例设置得很低。因为Dev sets的目标是用来比较验证不同算法的优劣,从而选择更好的算法模型就行了。因此,通常不需要所有样本的20%这么多的数据来进行验证。对于100万的样本,往往只需要10000个样本来做验证就够了。Test sets也是一样,目标是测试已选算法的实际表现,无偏估计。对于100万的样本,往往也只需要10000个样本就够了。因此,对于大数据样本,Train/Dev/Test sets的比例通常可以设置为98%/1%/1%,或者99%/0.5%/0.5%。样本数据量越大,相应的Dev/Test sets的比例可以设置的越低一些。

现代深度学习还有个重要的问题就是训练样本和测试样本分布上不匹配,意思是训练样本和测试样本来自于不同的分布。举个例子,假设你开发一个手机app,可以让用户上传图片,然后app识别出猫的图片。在app识别算法中,你的训练样本可能来自网络下载,而你的验证和测试样本可能来自不同用户的上传。从网络下载的图片一般像素较高而且比较正规,而用户上传的图片往往像素不稳定,且图片质量不一。因此,训练样本和验证/测试样本可能来自不同的分布。解决这一问题的比较科学的办法是尽量保证Dev sets和Test sets来自于同一分布。值得一提的是,训练样本非常重要,通常我们可以将现有的训练样本做一些处理,例如图片的翻转、假如随机噪声等,来扩大训练样本的数量,从而让该模型更加强大。即使Train sets和Dev/Test sets不来自同一分布,使用这些技巧也能提高模型性能。

最后提一点的是如果没有Test sets也是没有问题的。Test sets的目标主要是进行无偏估计。我们可以通过Train sets训练不同的算法模型,然后分别在Dev sets上进行验证,根据结果选择最好的算法模型。这样也是可以的,不需要再进行无偏估计了。如果只有Train sets和Dev sets,通常也有人把这里的Dev sets称为Test sets,我们要注意加以区别。

  1. 偏差(Bias)和方差(Variance)

偏差(Bias)和方差(Variance)是机器学习领域非常重要的两个概念和需要解决的问题。在传统的机器学习算法中,Bias和Variance是对立的,分别对应着欠拟合和过拟合,我们常常需要在Bias和Variance之间进行权衡。而在深度学习中,我们可以同时减小Bias和Variance,构建最佳神经网络模型。

如下图所示,显示了二维平面上,high bias,just right,high variance的例子。

可见,high bias对应着欠拟合,而high variance对应着过拟合

上图这个例子中输入特征是二维的,high bias和high variance可以直接从图中分类线看出来。而对于输入特征是高维的情况,如何来判断是否出现了high bias或者high variance呢?

例如猫识别问题,输入是一幅图像,其特征维度很大。这种情况下,我们可以通过两个数值Train set error和Dev set error来理解bias和variance。

假设Train set error为1%,而Dev set error为11%,即该算法模型对训练样本的识别很好,但是对验证集的识别却不太好。这说明了该模型对训练样本可能存在过拟合,模型泛化能力不强,导致验证集识别率低。这恰恰是high variance的表现。

假设Train set error为15%,而Dev set error为16%,虽然二者error接近,即该算法模型对训练样本和验证集的识别都不是太好。这说明了该模型对训练样本存在欠拟合。这恰恰是high bias的表现

假设Train set error为15%,而Dev set error为30%,说明了该模型既存在high bias也存在high variance(深度学习中最坏的情况)。

再假设Train set error为0.5%,而Dev set error为1%,即low bias和low variance,是最好的情况。值得一提的是,以上的这些假设都是建立在base error是0的基础上,即人类都能正确识别所有猫类图片。base error不同,相应的Train set error和Dev set error会有所变化,但没有相对变化。

一般来说,Train set error体现了是否出现bias,Dev set error体现了是否出现variance(正确地说,应该是Dev set error与Train set error的相对差值)。

我们已经通过二维平面展示了high bias或者high variance的模型,下图展示了high bias and high variance的模型:

模型既存在high bias也存在high variance,可以理解成某段区域是欠拟合的,某段区域是过拟合的。

  1. Basic Recipe for Machine Learning

机器学习中基本的一个诀窍就是避免出现high bias和high variance。

首先,减少high bias的方法通常是增加神经网络的隐藏层个数、神经元个数,训练时间延长,选择其它更复杂的NN模型等。在base error不高的情况下,一般都能通过这些方式有效降低和避免high bias,至少在训练集上表现良好。

其次,减少high variance的方法通常是增加训练样本数据,进行正则化Regularization,选择其他更复杂的NN模型等

这里有几点需要注意的。第一,解决high bias和high variance的方法是不同的。实际应用中通过Train set error和Dev set error判断是否出现了high bias或者high variance,然后再选择针对性的方法解决问题。

第二,Bias和Variance的折中tradeoff。传统机器学习算法中,Bias和Variance通常是对立的,减小Bias会增加Variance,减小Variance会增加Bias。而在现在的深度学习中,通过使用更复杂的神经网络和海量的训练样本,一般能够同时有效减小Bias和Variance。这也是深度学习之所以如此强大的原因之一。

  1. 正则化:Regularization

如果出现了过拟合,即high variance,则需要采用正则化regularization来解决。虽然扩大训练样本数量也是减小high variance的一种方法,但是通常获得更多训练样本的成本太高,比较困难。所以,更可行有效的办法就是使用regularization。

  1. 正则化有效避免高方差,防止过拟合:Why regularization reduces overfitting

为什么正则化能够有效避免high variance,防止过拟合呢?

该神经网络模型中的某些神经元实际的作用很小,可以忽略。从效果上来看,其实是将某些神经元给忽略掉了。这样原本过于复杂的神经网络模型就变得不那么复杂了,而变得非常简单化了。如下图所示,整个简化的神经网络模型变成了一个逻辑回归模型。问题就从high variance变成了high bias了。

  1. Dropout Regularization

除了L2 regularization之外,还有另外一种防止过拟合的有效方法:Dropout。

Dropout是指在深度学习网络的训练过程中,对于每层的神经元,按照一定的概率将其暂时从网络中丢弃。也就是说,每次训练时,每一层都有部分神经元不工作,起到简化复杂网络模型的效果,从而避免发生过拟合。

  1. Understanding Dropout

Dropout通过每次迭代训练时,随机选择不同的神经元,相当于每次都在不同的神经网络上进行训练,类似机器学习中Bagging的方法(三个臭皮匠,赛过诸葛亮),能够防止过拟合。

除此之外,还可以从权重w的角度来解释为什么dropout能够有效防止过拟合。对于某个神经元来说,某次训练时,它的某些输入在dropout的作用被过滤了。而在下一次训练时,又有不同的某些输入被过滤。经过多次训练后,某些输入被过滤,某些输入被保留。这样,该神经元就不会受某个输入非常大的影响,影响被均匀化了。也就是说,对应的权重w不会很大。这从从效果上来说,与L2 regularization是类似的,都是对权重w进行“惩罚”,减小了w的值。

总结一下,对于同一组训练数据,利用不同的神经网络训练之后,求其输出的平均值可以减少overfitting。Dropout就是利用这个原理,每次丢掉一定数量的隐藏层神经元,相当于在不同的神经网络上进行训练,这样就减少了神经元之间的依赖性,即每个神经元不能依赖于某几个其他的神经元(指层与层之间相连接的神经元),使神经网络更加能学习到与其他神经元之间的,更加健壮robust的特征。

  1. 其他减少过拟合的方法:Other regularization methods

除了L2 regularization和dropout regularization之外,还有其它减少过拟合的方法。

一种方法是增加训练样本数量。但是通常成本较高,难以获得额外的训练样本。但是,我们可以对已有的训练样本进行一些处理来“制造”出更多的样本,称为data augmentation

例如图片识别问题中,可以对已有的图片进行水平翻转、垂直翻转、任意角度旋转、缩放或扩大等等。如下图所示,这些处理都能“制造”出新的训练样本。虽然这些是基于原有样本的,但是对增大训练样本数量还是有很有帮助的,不需要增加额外成本,却能起到防止过拟合的效果。

在数字识别中,也可以将原有的数字图片进行任意旋转或者扭曲,或者增加一些noise,如下图所示:

还有另外一种防止过拟合的方法:early stopping。一个神经网络模型随着迭代训练次数增加,train set error一般是单调减小的,而dev set error 先减小,之后又增大。也就是说训练次数过多时,模型会对训练样本拟合的越来越好,但是对验证集拟合效果逐渐变差,即发生了过拟合。因此,迭代训练次数不是越多越好,可以通过train set error和dev set error随着迭代次数的变化趋势,选择合适的迭代次数,即early stopping。

然而,Early stopping有其自身缺点。通常来说,机器学习训练模型有两个目标:一是优化cost function,尽量减小J;二是防止过拟合。这两个目标彼此对立的,即减小J的同时可能会造成过拟合,反之亦然。我们把这二者之间的关系称为正交化orthogonalization。该节课开始部分就讲过,在深度学习中,我们可以同时减小Bias和Variance,构建最佳神经网络模型。但是,Early stopping的做法通过减少得带训练次数来防止过拟合,这样J就不会足够小。也就是说,early stopping将上述两个目标融合在一起,同时优化,但可能没有“分而治之”的效果好。

与early stopping相比,L2 regularization可以实现“分而治之”的效果:迭代训练足够多,减小J,而且也能有效防止过拟合。而L2 regularization的缺点之一是最优的正则化参数λ的选择比较复杂。对这一点来说,early stopping比较简单。总的来说,L2 regularization更加常用一些。

  1. 标准化输入(归一化):Normalizing inputs

在训练神经网络时,标准化输入可以提高训练的速度。标准化输入就是对训练数据集进行归一化的操作,即将原始数据减去其均值μ后,再除以其方差σ2

以二维平面为例,下图展示了其归一化过程:

值得注意的是,由于训练集进行了标准化处理,那么对于测试集或在实际应用时,应该使用同样的μσ2

对其进行标准化处理。这样保证了训练集合测试集的标准化操作一致。

之所以要对输入进行标准化操作,主要是为了让所有输入归一化同样的尺度上,方便进行梯度下降算法时能够更快更准确地找到全局最优解。假如输入特征是二维的,且x1的范围是[1,1000],x2的范围是[0,1]。如果不进行标准化处理,x1与x2之间分布极不平衡,训练得到的w1和w2也会在数量级上差别很大。这样导致的结果是cost function与w和b的关系可能是一个非常细长的椭圆形碗。对其进行梯度下降算法时,由于w1和w2数值差异很大,只能选择很小的学习因子α,来避免J发生振荡。一旦α较大,必然发生振荡,J不再单调下降。如下左图所示。

然而,如果进行了标准化操作,x1与x2分布均匀,w1和w2数值差别不大,得到的cost function与w和b的关系是类似圆形碗。对其进行梯度下降算法时,α可以选择相对大一些,且J一般不会发生振荡,保证了J是单调下降的。如下右图所示。

另外一种情况,如果输入特征之间的范围本来就比较接近,那么不进行标准化操作也是没有太大影响的。但是,标准化处理在大多数场合下还是值得推荐的。

  1. 梯度消失和梯度爆炸:Vanishing and Exploding gradients

在神经网络尤其是深度神经网络中存在可能存在这样一个问题:梯度消失和梯度爆炸。意思是当训练一个层数非常多的神经网络时,计算得到的梯度可能非常小或非常大,甚至是指数级别的减小或增大。这样会让训练过程变得非常困难

举个例子来说明,假设一个多层的每层只包含两个神经元的深度神经网络模型,如下图所示:

为了简化复杂度,便于分析,我们令各层的激活函数为线性函数,即g(Z)=Z。且忽略各层常数项b的影响,令b全部为零。那么,该网络的预测输出Y^为:

如果各层权重W[l]的元素都稍大于1,例如1.5,则预测输出Y^将正比于1.5L。L越大,Y^越大,且呈指数型增长。我们称之为数值爆炸。相反,如果各层权重W[l]的元素都稍小于1,例如0.5,则预测输出Y^将正比于0.5L。网络层数L越多,Y^呈指数型减小。我们称之为数值消失。

也就是说,如果各层权重W[l]都大于1或者都小于1,那么各层激活函数的输出将随着层数l的增加,呈指数型增大或减小。当层数很大时,出现数值爆炸或消失。同样,这种情况也会引起梯度呈现同样的指数型增大或减小的变化。L非常大时,例如L=150,则梯度会非常大或非常小,引起每次更新的步进长度过大或者过小,这让训练过程十分困难。

  1. 初始化权重:Weight Initialization for Deep Networks

下面介绍如何改善Vanishing and Exploding gradients这类问题,方法是对权重w进行一些初始化处理。

至于选择哪种初始化方法因人而异,可以根据不同的激活函数选择不同方法。另外,我们可以对这些初始化方法中设置某些参数,作为超参数,通过验证集进行验证,得到最优参数,来优化神经网络。

  1. 梯度的数值逼近Numerical approximation of gradients

Back Propagation神经网络有一项重要的测试是梯度检查(gradient checking)。其目的是检查验证反向传播过程中梯度下降算法是否正确。该小节将先介绍如何近似求出梯度值。

利用微分思想,函数f在点θ处的梯度可以表示成:

其中,ε>0,且足够小。

  1. 梯度检查:Gradient checking

介绍完如何近似求出梯度值后,我们将介绍如何进行梯度检查,来验证训练过程中是否出现bugs。

14. Gradient Checking Implementation Notes

在进行梯度检查的过程中有几点需要注意的地方:

不要在整个训练过程中都进行梯度检查,仅仅作为debug使用。

如果梯度检查出现错误,找到对应出错的梯度,检查其推导是否出现错误。

注意不要忽略正则化项,计算近似梯度的时候要包括进去。

梯度检查时关闭dropout,检查完毕后再打开dropout。

随机初始化时运行梯度检查,经过一些训练后再进行梯度检查(不常用)

2.2 优化算法

上节课我们主要介绍了如何建立一个实用的深度学习神经网络。包括Train/Dev/Test sets的比例选择,Bias和Variance的概念和区别:Bias对应欠拟合,Variance对应过拟合。接着,我们介绍了防止过拟合的两种方法:L2 regularization和Dropout。然后,介绍了如何进行规范化输入,以加快梯度下降速度和精度。然后,我们介绍了梯度消失和梯度爆炸的概念和危害,并提出了如何使用梯度初始化来降低这种风险。最后,我们介绍了梯度检查,来验证梯度下降算法是否正确。本节课,我们将继续讨论深度神经网络中的一些优化算法,通过使用这些技巧和方法来提高神经网络的训练速度和精度

  1. 小批量梯度下降法:Mini-batch gradient descent

之前我们介绍的神经网络训练过程是对所有m个样本,称为batch,通过向量化计算方式,同时进行的。如果m很大,例如达到百万数量级,训练速度往往会很慢,因为每次迭代都要对所有样本进行进行求和运算和矩阵运算。我们将这种梯度下降算法称为Batch Gradient Descent

为了解决这一问题,我们可以把m个训练样本分成若干个子集,称为mini-batches,这样每个子集包含的数据量就小了,例如只有1000,然后每次在单一子集上进行神经网络训练,速度就会大大提高。这种梯度下降算法叫做Mini-batch Gradient Descent

假设总的训练样本个数m=5000000,其维度为。将其分成5000个子集,每个mini-batch含有1000个样本。我们将每个mini-batch记为,其维度为。相应的每个mini-batch的输出记为,其维度为,且。

这里顺便总结一下我们遇到的神经网络中几类字母的上标含义:

:第i个样本

:神经网络第层网络的线性输出

:第t组mini-batch

Mini-batches Gradient Descent的实现过程是:

先将总的训练样本分成T个子集(mini-batches)

然后对每个mini-batch进行神经网络训练,包括Forward Propagation,Compute Cost Function,Backward Propagation,

循环至T个mini-batch都训练完毕

经过T次循环之后,所有m个训练样本都进行了梯度下降计算。这个过程,我们称之为经历了一个epoch。对于Batch Gradient Descent而言,一个epoch只进行一次梯度下降算法;而Mini-Batches Gradient Descent,一个epoch会进行T次梯度下降算法。

值得一提的是,对于Mini-Batches Gradient Descent,可以进行多次epoch训练。而且,每次epoch,最好是将总体训练数据重新打乱、重新分成T组mini-batches,这样有利于训练出最佳的神经网络模型。

  1. 理解mini-batch梯度下降算法:Understanding mini-batch gradient descent

Batch gradient descent和Mini-batch gradient descent的cost曲线如下图所示:

对于一般的神经网络模型,使用Batch gradient descent,随着迭代次数增加,cost是不断减小的。然而,使用Mini-batch gradient descent,随着在不同的mini-batch上迭代训练,其cost不是单调下降,而是受类似noise的影响,出现振荡。但整体的趋势是下降的,最终也能得到较低的cost值。

之所以出现细微振荡的原因是不同的mini-batch之间是有差异的。例如可能第一个子集是好的子集,而第二个子集包含了一些噪声noise。出现细微振荡是正常的。

如何选择每个mini-batch的大小,即包含的样本个数呢?有两个极端:

如果mini-batch size=m,即为Batch gradient descent,只包含一个子集为;

如果mini-batch size=1,即为Stachastic gradient descent,每个样本就是一个子集,共有m个子集。

我们来比较一下Batch gradient descent和Stachastic gradient descent的梯度下降曲线。如下图所示,蓝色的线代表Batch gradient descent,紫色的线代表Stachastic gradient descent。Batch gradient descent会比较平稳地接近全局最小值,但是因为使用了所有m个样本,每次前进的速度有些慢Stachastic gradient descent每次前进速度很快,但是路线曲折,有较大的振荡,最终会在最小值附近来回波动,难以真正达到最小值处。而且在数值处理上就不能使用向量化的方法来提高运算速度。

实际使用中,mini-batch size不能设置得太大(Batch gradient descent),也不能设置得太小(Stachastic gradient descent)。这样,相当于结合了Batch gradient descent和Stachastic gradient descent各自的优点,既能使用向量化优化算法,又能较快速地找到最小值。mini-batch gradient descent的梯度下降曲线如下图绿色所示,每次前进速度较快,且振荡较小,基本能接近全局最小值。

一般来说,如果总体样本数量m不太大时,例如,建议直接使用Batch gradient descent。如果总体样本数量m很大时,建议将样本分成许多mini-batches。推荐常用的mini-batch size为64,128,256,512。这些都是2的幂。之所以这样设置的原因是计算机存储数据一般是2的幂,这样设置可以提高运算速度

  1. 指数加权平均:Exponentially weighted averages

该部分我们将介绍指数加权平均(Exponentially weighted averages)的概念。

看上去,温度数据似乎有noise,而且抖动较大。如果我们希望看到半年内气温的整体变化趋势,可以通过移动平均(moving average)的方法来对每天气温进行平滑处理。

  1. Understanding exponetially weighted averages

  1. 偏移校正:Bias correction in exponentially weighted average

上文中提到当时,指数加权平均结果如下图绿色曲线所示。但是实际上,真实曲线如紫色曲线所示。

我们注意到,紫色曲线与绿色曲线的区别是,紫色曲线开始的时候相对较低一些。这是因为开始时我们设置,所以初始值会相对小一些,直到后面受前面的影响渐渐变小,趋于正常。

修正这种问题的方法是进行偏移校正(bias correction),即在每次计算完后,对进行下式处理:

值得一提的是,机器学习中,偏移校正并不是必须的。因为,在迭代一次次数后(t较大),受初始值影响微乎其微,紫色曲线与绿色曲线基本重合。所以,一般可以忽略初始迭代过程,等到一定迭代之后再取值,这样就不需要进行偏移校正了。

  1. 动量梯度下降算法:Gradient descent with momentum

该部分将介绍动量梯度下降算法,其速度要比传统的梯度下降算法快很多。做法是在每次训练时,对梯度进行指数加权平均处理,然后用得到的梯度值更新权重W和常数项b。下面介绍具体的实现过程。

  1. RMSprop

下面简单解释一下RMSprop算法的原理,仍然以下图为例,为了便于分析,令水平方向为W的方向,垂直方向为b的方向。

从图中可以看出,梯度下降(蓝色折线)在垂直方向(b)上振荡较大,在水平方向(W)上振荡较小,表示在b方向上梯度较大,即较大,而在W方向上梯度较小,即较小。因此,上述表达式中较大,而较小。在更新W和b的表达式中,变化值较大,而较小。也就使得W变化得多一些,b变化得少一些。即加快了W方向的速度,减小了b方向的速度,减小振荡,实现快速梯度下降算法,其梯度下降过程如绿色折线所示。总得来说,就是如果哪个方向振荡大,就减小该方向的更新速度,从而减小振荡

还有一点需要注意的是为了避免RMSprop算法中分母为零,通常可以在分母增加一个极小的常数:

  1. Adam optimization algorithm

Adam(Adaptive Moment Estimation)算法结合了动量梯度下降算法和RMSprop算法

  1. 减小学习因子:Learning rate decay

减小学习因子也能有效提高神经网络训练速度,这种方法被称为learning rate decay。

Learning rate decay就是随着迭代次数增加,学习因子逐渐减小。下面用图示的方式来解释这样做的好处。下图中,蓝色折线表示使用恒定的学习因子,由于每次训练相同,步进长度不变,在接近最优值处的振荡也大,在最优值附近较大范围内振荡,与最优值距离就比较远。绿色折线表示使用不断减小的,随着训练次数增加,逐渐减小,步进长度减小,使得能够在最优值处较小范围内微弱振荡,不断逼近最优值。相比较恒定的来说,learning rate decay更接近最优值。

  1. 局部最优解:The problem of local optima

在使用梯度下降算法不断减小cost function时,可能会得到局部最优解(local optima)而不是全局最优解(global optima)。之前我们对局部最优解的理解是形如碗状的凹槽,如下图左边所示。但是在神经网络中,local optima的概念发生了变化。准确地来说,大部分梯度为零的“最优点”并不是这些凹槽处,而是形如右边所示的马鞍状,称为saddle point。也就是说,梯度为零并不能保证都是convex(极小值),也有可能是concave(极大值)。特别是在神经网络中参数很多的情况下,所有参数梯度为零的点很可能都是右边所示的马鞍状的saddle point,而不是左边那样的local optimum。

类似马鞍状的plateaus会降低神经网络学习速度。Plateaus是梯度接近于零的平缓区域,如下图所示。在plateaus上梯度很小,前进缓慢,到达saddle point需要很长时间。到达saddle point后,由于随机扰动,梯度一般能够沿着图中绿色箭头,离开saddle point,继续前进,只是在plateaus上花费了太多时间。

总的来说,关于local optima,有两点总结:

  • 只要选择合理的强大的神经网络,一般不太可能陷入local optima

  • Plateaus可能会使梯度下降变慢,降低学习速度

值得一提的是,上文介绍的动量梯度下降,RMSprop,Adam算法都能有效解决plateaus下降过慢的问题,大大提高神经网络的学习速度。

2.3超参数调试、Batch正则化和编程框架

上节课我们主要介绍了深度神经网络的优化算法。包括对原始数据集进行分割,使用mini-batch gradient descent。然后介绍了指数加权平均(Exponentially weighted averages)的概念以及偏移校正(bias correction)方法。

接着,我们着重介绍了三种常用的加速神经网络学习速度的三种算法:动量梯度下降、RMSprop和Adam算法。其中,Adam结合了动量梯度下降和RMSprop各自的优点,实际应用中表现更好。然后,我们介绍了另外一种提高学习速度的方法:learning rate decay,通过不断减小学习因子,减小步进长度,来减小梯度振荡。最后,我们对深度学习中local optima的概念作了更深入的解释。本节课,我们将重点介绍三个方面的内容:超参数调试、Batch正则化和深度学习编程框架。

  1. Tuning Process

深度神经网络需要调试的超参数(Hyperparameters)较多,包括:

  • α:学习因子

  • β:动量梯度下降因子

  • β1,β2,ε:Adam算法参数

  • #layers:神经网络层数

  • #hidden units:各隐藏层神经元个数

  • learning rate decay:学习因子下降参数

  • mini-batch size:批量训练样本包含的样本个数

超参数之间也有重要性差异。通常来说,学习因子α是最重要的超参数,也是需要重点调试的超参数。动量梯度下降因子β、各隐藏层神经元个数#hidden units和mini-batch size的重要性仅次于α。然后就是神经网络层数#layers和学习因子下降参数learning rate decay。最后,Adam算法的三个参数β1,β2,ε一般常设置为0.9,0.999和10−8,不需要反复调试。当然,这里超参数重要性的排名并不是绝对的,具体情况,具体分析。

如何选择和调试超参数?传统的机器学习中,我们对每个参数等距离选取任意个数的点,然后,分别使用不同点对应的参数组合进行训练,最后根据验证集上的表现好坏,来选定最佳的参数。例如有两个待调试的参数,分别在每个参数上选取5个点,这样构成了5x5=25中参数组合,如下图所示:

这种做法在参数比较少的时候效果较好。但是在深度神经网络模型中,我们一般不采用这种均匀间隔取点的方法,比较好的做法是使用随机选择。也就是说,对于上面这个例子,我们随机选择25个点,作为待调试的超参数,如下图所示:

随机化选择参数的目的是为了尽可能地得到更多种参数组合。还是上面的例子,如果使用均匀采样的话,每个参数只有5种情况;而使用随机采样的话,每个参数有25种可能的情况,因此更有可能得到最佳的参数组合。

这种做法带来的另外一个好处就是对重要性不同的参数之间的选择效果更好。假设hyperparameter1为α,hyperparameter2为ε,显然二者的重要性是不一样的。如果使用第一种均匀采样的方法,ε的影响很小,相当于只选择了5个α值。而如果使用第二种随机采样的方法,ε和α都有可能选择25种不同值。这大大增加了α调试的个数,更有可能选择到最优值。其实,在实际应用中完全不知道哪个参数更加重要的情况下,随机采样的方式能有效解决这一问题,但是均匀采样做不到这点。

在经过随机采样之后,我们可能得到某些区域模型的表现较好。然而,为了得到更精确的最佳参数,我们应该继续对选定的区域进行由粗到细的采样(coarse to fine sampling scheme)。也就是放大表现较好的区域,再对此区域做更密集的随机采样。例如,对下图中右下角的方形区域再做25点的随机采样,以获得最佳参数。

  1. 尺度均匀采样:Using an appropriate scale to pick hyperparameters

上一部分讲的调试参数使用随机采样,对于某些超参数是可以进行尺度均匀采样的,但是某些超参数需要选择不同的合适尺度进行随机采样。

什么意思呢?例如对于超参数#layers和#hidden units,都是正整数,是可以进行均匀随机采样的,即超参数每次变化的尺度都是一致的(如每次变化为1,犹如一个刻度尺一样,刻度是均匀的)。

但是,对于某些超参数,可能需要非均匀随机采样(即非均匀刻度尺)。例如超参数α,待调范围是[0.0001, 1]。如果使用均匀随机采样,那么有90%的采样点分布在[0.1, 1]之间,只有10%分布在[0.0001, 0.1]之间。这在实际应用中是不太好的,因为最佳的α值可能主要分布在[0.0001, 0.1]之间,而[0.1, 1]范围内α值效果并不好。因此我们更关注的是区间[0.0001, 0.1],应该在这个区间内细分更多刻度。

通常的做法是将linear scale转换为log scale,将均匀尺度转化为非均匀尺度,然后再在log scale下进行均匀采样。这样,[0.0001, 0.001],[0.001, 0.01],[0.01, 0.1],[0.1, 1]各个区间内随机采样的超参数个数基本一致,也就扩大了之前[0.0001, 0.1]区间内采样值个数。

  1. Hyperparameters tuning in practice: Pandas vs. Caviar

  1. Normalizing activations in a network

Sergey Ioffe和Christian Szegedy两位学者提出了Batch Normalization方法。Batch Normalization不仅可以让调试超参数更加简单,而且可以让神经网络模型更加“健壮”。也就是说较好模型可接受的超参数范围更大一些,包容性更强,使得更容易去训练一个深度神经网络。接下来,我们就来介绍什么是Batch Normalization,以及它是如何工作的。

之前,我们在Coursera吴恩达《优化深度神经网络》课程笔记(1)– 深度学习的实用层面中提到过在训练神经网络时,标准化输入可以提高训练的速度。方法是对训练数据集进行归一化的操作,即将原始数据减去其均值μ后,再除以其方差σ2。但是标准化输入只是对输入进行了处理,那么对于神经网络,又该如何对各隐藏层的输入进行标准化处理呢?

其实在神经网络中,第l层隐藏层的输入就是第l−1层隐藏层的输出A[l−1]。对A[l−1]进行标准化处理,从原理上来说可以提高W[l]和b[l]的训练速度和准确度。这种对各隐藏层的标准化处理就是Batch Normalization。值得注意的是,实际应用中,一般是对Z[l−1]进行标准化处理而不是A[l−1],其实差别不是很大。Batch Normalization对第l层隐藏层的输入Z[l−1]做如下标准化处理,忽略上标[l−1]:

  1. Fitting Batch Norm into a neural network

  1. Why does Batch Norm work?

我们可以把输入特征做均值为0,方差为1的规范化处理,来加快学习速度。而Batch Norm也是对隐藏层各神经元的输入做类似的规范化处理。总的来说,Batch Norm不仅能够提高神经网络训练速度,而且能让神经网络的权重W的更新更加“稳健”,尤其在深层神经网络中更加明显。比如神经网络很后面的W对前面的W包容性更强,即前面的W的变化对后面W造成的影响很小,整体网络更加健壮。

举个例子来说明,假如用一个浅层神经网络(类似逻辑回归)来训练识别猫的模型。如下图所示,提供的所有猫的训练样本都是黑猫。然后,用这个训练得到的模型来对各种颜色的猫样本进行测试,测试的结果可能并不好。其原因是训练样本不具有一般性(即不是所有的猫都是黑猫),这种训练样本(黑猫)和测试样本(猫)分布的变化称之为covariate shift。

对于这种情况,如果实际应用的样本与训练样本分布不同,即发生了covariate shift,则一般是要对模型重新进行训练的。在神经网络,尤其是深度神经网络中,covariate shift会导致模型预测效果变差,重新训练的模型各隐藏层的W[l]和B[l]均产生偏移、变化。而Batch Norm的作用恰恰是减小covariate shift的影响,让模型变得更加健壮,鲁棒性更强。Batch Norm减少了各层W[l]、B[l]之间的耦合性,让各层更加独立,实现自我训练学习的效果。也就是说,如果输入发生covariate shift,那么因为Batch Norm的作用,对个隐藏层输出Z[l]进行均值和方差的归一化处理,W[l]和B[l]更加稳定,使得原来的模型也有不错的表现。针对上面这个黑猫的例子,如果我们使用深层神经网络,使用Batch Norm,那么该模型对花猫的识别能力应该也是不错的。

从另一个方面来说,Batch Norm也起到轻微的正则化(regularization)效果。具体表现在:

  • 每个mini-batch都进行均值为0,方差为1的归一化操作

  • 每个mini-batch中,对各个隐藏层的Z[l]

  • 添加了随机噪声,效果类似于Dropout

  • mini-batch越小,正则化效果越明显

但是,Batch Norm的正则化效果比较微弱,正则化也不是Batch Norm的主要功能。

  1. Batch Norm at test time

  1. Softmax回归模型:Softmax Regression

目前我们介绍的都是二分类问题,神经网络输出层只有一个神经元,表示预测输出y^是正类的概率P(y=1|x),y^>0.5则判断为正类,y^<0.5则判断为负类。

对于多分类问题,用C表示种类个数,神经网络中输出层就有C个神经元,即n[L]=C。其中,每个神经元的输出依次对应属于该类的概率,即P(y=c|x)。为了处理多分类问题,我们一般使用Softmax回归模型。Softmax回归模型输出层的激活函数如下所示:

9. Training a softmax classifier

10. 深度学习框架:Deep learning frameworks

深度学习框架有很多,例如:

Caffe/Caffe2

CNTK

DL4J

Keras

Lasagne

mxnet

PaddlePaddle

TensorFlow

Theano

Torch

一般选择深度学习框架的基本准则是:

Ease of programming(development and deployment)

Running speed

Truly open(open source with good governance)

实际应用中,我们应该根据自己的需求选择最合适的深度学习框架。

11. TensorFlow

这里简单介绍一下最近几年比较火的一个深度学习框架:TensorFlow。

TensorFlow的最大优点就是采用数据流图(data flow graphs)来进行数值运算。图中的节点(Nodes)表示数学操作,图中的线(edges)则表示在节点间相互联系的多维数据数组,即张量(tensor)。而且它灵活的架构让你可以在多种平台上展开计算,例如台式计算机中的一个或多个CPU(或GPU),服务器,移动设备等等。

关于TensorFlow更多的原理和编程技巧这里就不在赘述了,感兴趣的朋友可以关注更详细的TensorFlow相关文档。

  1. 构建机器学习项目

3.1 机器学习策略

  1. Why ML Strategy

当我们最初得到一个深度神经网络模型时,我们可能希望从很多方面来对它进行优化,例如:

Collect more data

Collect more diverse training set

Train algorithm longer with gradient descent

Try Adam instead of gradient descent

Try bigger network

Try smaller network

Try dropout

Add L2 regularization

Network architecture: Activation functions, #hidden units…

可选择的方法很多,也很复杂、繁琐。盲目选择、尝试不仅耗费时间而且可能收效甚微。因此,使用快速、有效的策略来优化机器学习模型是非常必要的。

  1. 正交化方法:Orthogonalization

机器学习中有许多参数、超参数需要调试。通过每次只调试一个参数,保持其它参数不变,而得到的模型某一性能改变是一种最常用的调参策略,我们称之为正交化方法(Orthogonalization)

Orthogonalization的核心在于每次调试一个参数只会影响模型的某一个性能。例如老式电视机旋钮,每个旋钮就对应一个功能,调整旋钮会调整对应的功能,而不会影响其它功能。也就是说彼此旋钮之间是互不影响的,是正交的,这也是Orthogonalization名称的由来。这种方法能够让我们更快更有效地进行机器学习模型的调试和优化。

对应到机器学习监督式学习模型中,可以大致分成四个独立的“功能”,每个“功能”对应一些可调节的唯一的旋钮。四个“功能”如下:

Fit training set well on cost function

Fit dev set well on cost function

Fit test set well on cost function

Performs well in real world

其中,第一条优化训练集可以通过使用更复杂NN(神经网络),使用Adam等优化算法来实现;

第二条优化验证集可以通过正则化,采用更多训练样本来实现;

第三条优化测试集可以通过使用更多的验证集样本来实现;

第四条提升实际应用模型可以通过更换验证集,使用新的cost function来实现。

概括来说,每一种“功能”对应不同的调节方法。而这些调节方法(旋钮)只会对应一个“功能”,是正交的。

顺便提一下,early stopping在模型功能调试中并不推荐使用。因为early stopping在提升验证集性能的同时降低了训练集的性能。也就是说early stopping同时影响两个“功能”,不具有独立性、正交性。

  1. 单值评价指标:Single number evaluation metric

构建、优化机器学习模型时,单值评价指标非常必要。有了量化的单值评价指标后,我们就能根据这一指标比较不同超参数对应的模型的优劣,从而选择最优的那个模型

举个例子,比如有A和B两个模型,它们的准确率(Precision)和召回率(Recall)分别如下:

  1. 优化指标、满意指标:Satisficing and Optimizing metic

有时候,要把所有的性能指标都综合在一起,构成单值评价指标是比较困难的。解决办法是,我们可以把某些性能作为优化指标(Optimizing metic),寻求最优化值;而某些性能作为满意指标(Satisficing metic),只要满足阈值就行了

举个猫类识别的例子,有A,B,C三个模型,各个模型的Accuracy和Running time如下表中所示:

Accuracy和Running time这两个性能不太合适综合成单值评价指标。因此,我们可以将Accuracy作为优化指标(Optimizing metic),将Running time作为满意指标(Satisficing metic)。也就是说,给Running time设定一个阈值,在其满足阈值的情况下,选择Accuracy最大的模型。如果设定Running time必须在100ms以内,那么很明显,模型C不满足阈值条件,首先剔除;模型B相比较模型A而言,Accuracy更高,性能更好。

概括来说,性能指标(Optimizing metic)是需要优化的,越优越好;而满意指标(Satisficing metic)只要满足设定的阈值就好了。

  1. Train/dev/test distributions

Train/dev/test sets如何设置对机器学习的模型训练非常重要,合理设置能够大大提高模型训练效率和模型质量。

原则上应该尽量保证dev sets和test sets来源于同一分布且都反映实际样本的情况。如果dev sets和test sets不来自同一分布,那么我们从dev sets上选择的“最佳”模型往往不能够在test sets上表现得很好。这就好比我们在dev sets上找到最接近一个靶的靶心的箭,但是我们test sets提供的靶心却远远偏离dev sets上的靶心,结果这支肯定无法射中test sets上的靶心位置。

  1. Size of the dev and test sets

在之前的课程中我们已经介绍过,当样本数量不多(小于一万)的时候,通常将Train/dev/test sets的比例设为60%/20%/20%,在没有dev sets的情况下,Train/test sets的比例设为70%/30%。当样本数量很大(百万级别)的时候,通常将相应的比例设为98%/1%/1%或者99%/1%。

dev sets数量设置,应该遵循的准则是通过dev sets能够检测不同算法或模型的区别,以便选择出更好的模型

test sets数量设置,应该遵循的准则是通过test sets能够反映出模型在实际中的表现

实际应用中,可能只有train/dev sets,而没有test sets。这种情况也是允许的,只要算法模型没有对dev sets过拟合。但是,条件允许的话,最好是有test sets,实现无偏估计。

7. When to change dev/test sets and metrics

8. Why human-level performance

9. Avoidable bias

10. Understanding human-level performance

3.2

7. Transfer learning

深度学习非常强大的一个功能之一就是有时候你可以将已经训练好的模型的一部分知识(网络结构)直接应用到另一个类似模型中去。比如我们已经训练好一个猫类识别的神经网络模型,那么我们可以直接把该模型中的一部分网络结构应用到使用X光片预测疾病的模型中去。这种学习方法被称为迁移学习(Transfer Learning)。

如果我们已经有一个训练好的神经网络,用来做图像识别。现在,我们想要构建另外一个通过X光片进行诊断的模型。迁移学习的做法是无需重新构建新的模型,而是利用之前的神经网络模型,只改变样本输入、输出以及输出层的权重系数。也就是说对新的样本(X,Y),重新训练输出层权重系数,而其它层所有的权重系数保持不变。

迁移学习,重新训练权重系数,如果需要构建新模型的样本数量较少,那么可以像刚才所说的,只训练输出层的权重系数,保持其它层所有的权重系数不变。这种做法相对来说比较简单。如果样本数量足够多,那么也可以只保留网络结构,重新训练所有层的权重系数。这种做法使得模型更加精确,因为毕竟样本对模型的影响最大。选择哪种方法通常由数据量决定。

顺便提一下,如果重新训练所有权重系数,初始由之前的模型训练得到,这一过程称为pre-training。之后,不断调试、优化的过程称为fine-tuning。pre-training和fine-tuning分别对应上图中的黑色箭头和红色箭头。

迁移学习之所以能这么做的原因是,神经网络浅层部分能够检测出许多图片固有特征,例如图像边缘、曲线等。使用之前训练好的神经网络部分结果有助于我们更快更准确地提取X光片特征。二者处理的都是图片,而图片处理是有相同的地方,第一个训练好的神经网络已经帮我们实现如何提取图片有用特征了。 因此,即便是即将训练的第二个神经网络样本数目少,仍然可以根据第一个神经网络结构和权重系数得到健壮性好的模型。

迁移学习可以保留原神经网络的一部分,再添加新的网络层。具体问题,具体分析,可以去掉输出层后再增加额外一些神经层。

总体来说,迁移学习的应用场合主要包括三点:

Task A and B have the same input x.

You have a lot more data for Task A than Task B.

Low level features from A could be helpful for learning B.

8. Multi-task learning

顾名思义,多任务学习(multi-task learning)就是构建神经网络同时执行多个任务。这跟二元分类或者多元分类都不同,多任务学习类似将多个神经网络融合在一起,用一个网络模型来实现多种分类效果。如果有C个,那么输出y的维度是。例如汽车自动驾驶中,需要实现的多任务为行人、车辆、交通标志和信号灯。如果检测出汽车和交通标志,则y为:

  1. 卷积神经网络CNN

4.1卷积神经网络基础

  1. 机器视觉:Computer Vision

机器视觉(Computer Vision)是深度学习应用的主要方向之一。

一般的CV问题包括以下三类:

  • Image Classification

  • Object detection

  • Neural Style Transfer(神经风格转换)

使用传统神经网络处理机器视觉的一个主要问题是输入层维度很大。例如一张64x64x3的图片,神经网络输入层的维度为12288。如果图片尺寸较大,例如一张1000x1000x3的图片,神经网络输入层的维度将达到3百万,使得网络权重W非常庞大。

这样会造成两个后果,一是神经网络结构复杂,数据量相对不够,容易出现过拟合;二是所需内存、计算量较大。

解决这一问题的方法就是使用卷积神经网络(CNN)

  1. 检测图片边缘:Edge Detection Example

对于CV问题,我们在之前的笔记中介绍过,神经网络由浅层到深层,分别可以检测出图片的边缘特征 、局部特征(例如眼睛、鼻子等)、整体面部轮廓

这一小节我们将介绍如何检测图片的边缘。

最常检测的图片边缘有两类:一是垂直边缘(vertical edges),二是水平边缘(horizontal edges)。

图片的边缘检测可以通过与相应滤波器进行卷积来实现。

以垂直边缘检测为例,原始图片尺寸为6x6,滤波器filter尺寸为3x3,卷积后的图片尺寸为4x4,得到结果如下:

上图只显示了卷积后的第一个值和最后一个值。

顺便提一下, * 表示卷积操作。python中,卷积用conv_forward()表示;tensorflow中,卷积用tf.nn.conv2d()表示;keras中,卷积用Conv2D()表示。

Vertical edge detection能够检测图片的垂直方向边缘。下图对应一个垂直边缘检测的例子:

  1. More Edge Detection

图片边缘有两种渐变方式,一种是由明变暗,另一种是由暗变明。以垂直边缘检测为例,下图展示了两种方式的区别。实际应用中,这两种渐变方式并不影响边缘检测结果,可以对输出图片取绝对值操作,得到同样的结果。

垂直边缘检测和水平边缘检测的滤波器算子如下所示:

下图展示一个水平边缘检测的例子:

除了上面提到的这种简单的Vertical、Horizontal滤波器之外,还有其它常用的filters,例如Sobel filter和Scharr filter。这两种滤波器的特点是增加图片中心区域的权重

上图展示的是垂直边缘检测算子,水平边缘检测算子只需将上图顺时针翻转90度即可。

在深度学习中,如果我们想检测图片的各种边缘特征,而不仅限于垂直边缘和水平边缘,那么filter的数值一般需要通过模型训练得到,类似于标准神经网络中的权重W一样,由梯度下降算法反复迭代求得。CNN的主要目的就是计算出这些filter的数值。确定得到了这些filter后,CNN浅层网络也就实现了对图片所有边缘特征的检测

4. Padding解决卷积后图片缩小的问题

按照我们上面讲的图片卷积,如果原始图片尺寸为n*n,filter尺寸为f*f,则卷积后的图片尺寸为(n-f+1)*(n-f+1),注意f一般为奇数。这样会带来两个问题:

  • 卷积运算后,输出图片尺寸缩小

  • 原始图片边缘信息对输出贡献得少,输出图片丢失边缘信息

为了解决图片缩小的问题,可以使用padding方法,即把原始图片尺寸进行扩展,扩展区域补零,用p来表示每个方向扩展的宽度。

经过padding之后,原始图片尺寸为(n+2p)*(n+2p),filter尺寸为f*f,则卷积后的图片尺寸为(n+2p-f+1)*(n+2p-f+1)。若要保证卷积前后图片尺寸不变,则p应满足:

没有padding操作,p=0,我们称之为“Valid convolutions”;有padding操作,p=(f−1)/2,我们称之为“Same convolutions”

5. 步进长度:Strided Convolutions

Stride表示filter在原图片中水平方向和垂直方向每次的步进长度。

之前我们默认stride=1。若stride=2,则表示filter每次步进长度为2,即隔一点移动一次。

我们用s表示stride长度,p表示padding长度,如果原始图片尺寸为n x n,filter尺寸为f x f,则卷积后的图片尺寸为:

上式中, ⌊ ⋯ ⌋表示向下取整。

值得一提的是,相关系数(cross-correlations)卷积(convolutions)之间是有区别的。实际上,真正的卷积运算会先将filter绕其中心旋转180度,然后再将旋转后的filter在原始图片上进行滑动计算。filter旋转如下所示:

比较而言,相关系数的计算过程则不会对filter进行旋转,而是直接在原始图片上进行滑动计算

其实,目前为止我们介绍的CNN卷积实际上计算的是相关系数,而不是数学意义上的卷积。但是,为了简化计算,我们一般把CNN中的这种“相关系数”就称作卷积运算。之所以可以这么等效,是因为滤波器算子一般是水平或垂直对称的,180度旋转影响不大;而且最终滤波器算子需要通过CNN网络梯度下降算法计算得到,旋转部分可以看作是包含在CNN模型算法中。总的来说,忽略旋转运算可以大大提高CNN网络运算速度,而且不影响模型性能。

卷积运算服从结合律:(A∗B)∗C=A∗(B∗C)

6. 卷积层:Convolutions Over Volume

对于3通道的RGB图片,其对应的滤波器算子同样也是3通道的。例如一个图片是6 x 6 x 3,分别表示图片的高度(height)、宽度(weight)和通道(#channel)。

3通道图片的卷积运算与单通道图片的卷积运算基本一致。过程是将每个单通道(R,G,B)与对应的filter进行卷积运算求和,然后再将3通道的和相加,得到输出图片的一个像素值

不同通道的滤波算子可以不相同。例如R通道filter实现垂直边缘检测,G和B通道不进行边缘检测,全部置零,或者将R,G,B三通道filter全部设置为水平边缘检测。

滤波器组:

为了进行多个卷积运算,实现更多边缘检测,可以增加更多的滤波器组。例如设置第一个滤波器组实现垂直边缘检测,第二个滤波器组实现水平边缘检测。这样,不同滤波器组卷积得到不同的输出,个数由滤波器组决定。

若输入图片的尺寸为n x n x n,filter尺寸为f x f x n,则卷积后的图片尺寸为(n-f+1) x (n-f+1) x n。其中,nc为图片通道数目,nc′为滤波器组个数。

7. 单层卷积网络:One Layer of a Convolutional Network

卷积神经网络的单层结构如下所示:

相比之前的卷积过程,CNN的单层结构多了激活函数ReLU偏移量b。整个过程与标准的神经网络单层结构非常类似:

卷积运算对应着上式中的乘积运算,滤波器组数值对应着权重 W[l],所选的激活函数为ReLU。

我们来计算一下上图中参数的数目:每个滤波器组有3x3x3=27个参数,还有1个偏移量b,则每个滤波器组有27+1=28个参数,两个滤波器组总共包含28x2=56个参数。我们发现,选定滤波器组后,参数数目与输入图片尺寸无关。所以,就不存在由于图片尺寸过大,造成参数过多的情况。例如一张1000x1000x3的图片,标准神经网络输入层的维度将达到3百万,而在CNN中,参数数目只由滤波器组决定,数目相对来说要少得多,这是CNN的优势之一。

最后,我们总结一下CNN单层结构的所有标记符号,设层数为 l 。

8. 简单的CNN模型:Simple Convolutional Network Example

下面介绍一个简单的CNN网络模型:

CNN有三种类型的layer:

  • Convolution层(CONV):卷积层

  • Pooling层(POOL):池化层

  • Fully connected层(FC):全连接层

CONV最为常见也最重要,关于POOL和FC我们之后再介绍

9.池化层:Pooling Layers

Pooling layers是CNN中用来减小尺寸,提高运算速度的,同样能减小noise影响,让各特征更具有健壮性

Pooling layers的做法比convolution layers简单许多,没有卷积运算,仅仅是在滤波器算子滑动区域内取最大值,即max pooling,这是最常用的做法。注意,超参数p很少在pooling layers中使用。

Max pooling的好处是只保留区域内的最大值(特征),忽略其它值,降低noise影响,提高模型健壮性。而且,max pooling需要的超参数仅为滤波器尺寸f和滤波器步进长度s,没有其他参数需要模型训练得到,计算量很小。

如果是多个通道,那么就每个通道单独进行max pooling操作

除了max pooling之外,还有一种做法:average pooling。顾名思义,average pooling就是在滤波器算子滑动区域计算平均值。

实际应用中,max pooling比average pooling更为常用。

10.CNN Example

下面介绍一个简单的数字识别的CNN例子:

图中,CON层后面紧接一个POOL层,CONV1和POOL1构成第一层,CONV2和POOL2构成第二层。特别注意的是FC3和FC4为全连接层FC,它跟标准的神经网络结构一致。最后的输出层(softmax)由10个神经元构成。

整个网络各层的尺寸和参数如下表格所示:

11. Why Convolutions

相比标准神经网络,CNN的优势之一就是参数数目要少得多。参数数目少的原因有两个:

1、参数共享:一个特征检测器(例如垂直边缘检测)对图片某块区域有用,同时也可能作用在图片其它区域。

2、连接的稀疏性:因为滤波器算子尺寸限制,每一层的每个输出只与输入部分区域内有关。

除此之外,由于CNN参数数目较小,所需的训练样本就相对较少,从而一定程度上不容易发生过拟合现象。而且,CNN比较擅长捕捉区域位置偏移。也就是说CNN进行物体检测时,不太受物体所处图片位置的影响,增加检测的准确性和系统的健壮性。

4.2 经典的深度卷积模型

  1. 典型的CNN模型

  • LeNet-5

  • AlexNet

  • VGG

除了这些性能良好的CNN模型之外,我们还会介绍Residual Network(ResNet)。其特点是可以构建很深很深的神经网络(目前最深的好像有152层)。

另外,还会介绍Inception Neural Network。接下来,我们将一一讲解。

  1. LeNet-5

LeNet-5模型是Yann LeCun教授于1998年提出来的,它是第一个成功应用于数字识别问题的卷积神经网络。在MNIST数据中,它的准确率达到大约99.2%。

典型的LeNet-5结构包含CONV layer,POOL layer和FC layer,顺序一般是CONV layer->POOL layer->CONV layer->POOL layer->FC layer->FC layer->OUTPUT layer,即y^。

下图所示的是一个数字识别的LeNet-5的模型结构:

该LeNet模型总共包含了大约6万个参数。值得一提的是,当时Yann LeCun提出的LeNet-5模型池化层使用的是average pool,而且各层激活函数一般是Sigmoid和tanh。现在,我们可以根据需要,做出改进,使用max pool和激活函数ReLU。

  1. AlexNet

AlexNet模型是由Alex Krizhevsky、Ilya Sutskever和Geoffrey Hinton共同提出的,其结构如下所示:

AlexNet模型与LeNet-5模型类似,只是要复杂一些,总共包含了大约6千万个参数。同样可以根据实际情况使用激活函数ReLU。原作者还提到了一种优化技巧,叫做Local Response Normalization(LRN)。 而在实际应用中,LRN的效果并不突出。

  1. VGG

VGG-16模型更加复杂一些,一般情况下,其CONV layer和POOL layer设置如下:

  • CONV = 3x3 filters, s = 1, same

  • MAX-POOL = 2x2, s = 2

VGG-16结构如下所示:

VGG-16的参数多达1亿3千万。

  1. ResNets

我们知道,如果神经网络层数越多,网络越深,源于梯度消失和梯度爆炸的影响,整个模型难以训练成功。解决的方法之一是人为地让神经网络某些层跳过下一层神经元的连接,隔层相连,弱化每层之间的强联系。这种神经网络被称为Residual Networks(ResNets)

Residual Networks由许多隔层相连的神经元子模块组成,我们称之为Residual block。单个Residual block的结构如下图所示:

该模型由Kaiming He, Xiangyu Zhang, Shaoqing Ren和Jian Sun共同提出。由多个Residual block组成的神经网络就是Residual Network。实验表明,这种模型结构对于训练非常深的神经网络,效果很好。另外,为了便于区分,我们把非Residual Networks称为Plain Network

Residual Network的结构如上图所示。

与Plain Network相比,Residual Network能够训练更深层的神经网络,有效避免发生发生梯度消失和梯度爆炸。从下面两张图的对比中可以看出,随着神经网络层数增加,Plain Network实际性能会变差,training error甚至会变大。然而,Residual Network的训练效果却很好,training error一直呈下降趋势。

  1. Why ResNets Work

下面用个例子来解释为什么ResNets能够训练更深层的神经网络。

下图所示的是CNN中ResNets的结构:

ResNets同类型层之间,例如CONV layers,大多使用same类型,保持维度相同。如果是不同类型层之间的连接,例如CONV layer与POOL layer之间,如果维度不同,则引入矩阵Ws

  1. 1x1卷积网络:Networks in Networks and 1x1 Convolutions

Min Lin, Qiang Chen等人提出了一种新的CNN结构,即1x1 Convolutions,也称Networks in Networks。这种结构的特点是滤波器算子filter的维度为1x1。对于单个filter,1x1的维度,意味着卷积操作等同于乘积操作。

那么,对于多个filters,1x1 Convolutions的作用实际上类似全连接层的神经网络结构。效果等同于Plain Network中a[l]到a[l+1]的过程。这点还是比较好理解的。

1x1 Convolutions可以用来缩减输入图片的通道数目。方法如下图所示:

  1. Inception Network Motivation

之前我们介绍的CNN单层的滤波算子filter尺寸是固定的,1x1或者3x3等。而Inception Network在单层网络上可以使用多个不同尺寸的filters,进行same convolutions,把各filter下得到的输出拼接起来。除此之外,还可以将CONV layer与POOL layer混合,同时实现各种效果。但是要注意使用same pool。

Inception Network由Christian Szegedy, Wei Liu等人提出。与其它只选择单一尺寸和功能的filter不同,Inception Network使用不同尺寸的filters并将CONV和POOL混合起来,将所有功能输出组合拼接,再由神经网络本身去学习参数并选择最好的模块。

Inception Network在提升性能的同时,会带来计算量大的问题。例如下面这个例子:

此CONV layer需要的计算量为:28x28x32x5x5x192=120m,其中m表示百万单位。可以看出但这一层的计算量都是很大的。为此,我们可以引入1x1 Convolutions来减少其计算量,结构如下图所示:

通常我们把该1x1 Convolution称为“瓶颈层”(bottleneck layer)。引入bottleneck layer之后,总共需要的计算量为:28x28x16x192+28x28x32x5x5x16=12.4m。明显地,虽然多引入了1x1 Convolution层,但是总共的计算量减少了近90%,效果还是非常明显的。由此可见,1x1 Convolutions还可以有效减少CONV layer的计算量。

  1. Inception Network

上一节我们使用1x1 Convolution来减少Inception Network计算量大的问题。引入1x1 Convolution后的Inception module如下图所示:

多个Inception modules组成Inception Network,效果如下图所示:

上述Inception Network除了由许多Inception modules组成之外,值得一提的是网络中间隐藏层也可以作为输出层Softmax,有利于防止发生过拟合。

  1. Using Open-Source Implementation

本节主要介绍GitHub的使用,GitHub是一个面向开源及私有软件项目的托管平台,上面包含有许多优秀的CNN开源项目。关于GitHub具体内容不再介绍,有兴趣的小伙伴自行查阅。

  1. Transfer Learning

有关Transfer Learning的相关内容,我们在 Coursera吴恩达《构建机器学习项目》课程笔记(2)– 机器学习策略(下)中已经详细介绍过,这里就不再赘述了。

12. 数据增强:Data Augmentation

常用的Data Augmentation方法是对已有的样本集进行镜像Mirroring和随机裁剪Random Cropping

另一种Data Augmentation的方法是color shifting。color shifting就是对图片的RGB通道数值进行随意增加或者减少,改变图片色调

除了随意改变RGB通道数值外,还可以更有针对性地对图片的RGB通道进行PCA color augmentation,也就是对图片颜色进行主成分分析,对主要的通道颜色进行增加或减少,可以采用高斯扰动做法。这样也能增加有效的样本数量。具体的PCA color augmentation做法可以查阅AlexNet的相关论文。

在构建大型神经网络的时候,data augmentation和training可以由两个不同的线程来进行。

13.计算机视觉现状:State of Computer Vision

神经网络需要数据,不同的网络模型所需的数据量是不同的。Object dection,Image recognition,Speech recognition所需的数据量依次增加。

一般来说,如果data较少,那么就需要更多的hand-engineering,对已有data进行处理,比如上一节介绍的data augmentation。模型算法也会相对要复杂一些。

如果data很多,可以构建深层神经网络,不需要太多的hand-engineering,模型算法也就相对简单一些。

值得一提的是hand-engineering是一项非常重要也比较困难的工作。很多时候,hand-engineering对模型训练效果影响很大,特别是在数据量不多的情况下。

在模型研究或者竞赛方面,有一些方法能够有助于提升神经网络模型的性能:

  • Ensembling: Train several networks independently and average their outputs.

  • Multi-crop at test time: Run classifier on multiple versions of test images and average results.

但是由于这两种方法计算成本较大,一般不适用于实际项目开发。

最后,我们还要灵活使用开源代码:

  • Use archittectures of networks published in the literature

  • Use open source implementations if possible

  • Use pretrained models and fine-tune on your dataset

4.3 目标检测

  1. 目标定位:Object Localization

前两节课程中,我们介绍的是利用CNN模型进行图像分类。除此之外,本周课程将继续深入介绍目标定位和目标检测(包含多目标检测)。

标准的CNN分类模型我们已经很熟悉了,如下所示:

原始图片经过CONV卷积层后,Softmax层输出4 x 1向量,分别是:

注意,class label也可能是概率。上述四个向量分别对应pedestrain,car,motorcycle和background四类。

对于目标定位和目标检测问题,其模型如下所示:

原始图片经过CONV卷积层后,Softmax层输出8 x 1向量。除了包含上述一般CNN分类3 x 1向量(class label)之外,还包含了(bx, by),表示目标中心位置坐标;还包含了bh和bw,表示目标所在矩形区域的高和宽;还包含了Pc,表示矩形区域是目标的概率,数值在0~1之间,且越大概率越大。一般设定图片左上角为原点(0, 0),右下角为(1, 1)。在模型训练时,bx、by、bh、bw都由人为确定其数值。例如上图中,可得bx=0.5,by=0.7,bh=0.3,bw=0.4。

输出label可表示为:

Pc=1: Pc=0:

若Pc=0,表示没有检测到目标,则输出label后面的7个参数都可以忽略。

对于损失函数Loss function,若使用平方误差形式,有两种情况:

  • Pc=1,即y1=1:

  • Pc=0,即y1=0:

当然,除了使用平方误差之外,还可以逻辑回归损失函数,类标签也可以通过softmax输出。比较而言,平方误差已经能够取得比较好的效果。

  1. 特征点检测:Landmark Detection

除了使用矩形区域检测目标类别和位置外,我们还可以仅对目标的关键特征点坐标进行定位,这些关键点被称为landmarks。

例如人脸识别,可以对人脸部分特征点坐标进行定位检测,并标记出来,如下图所示:

该网络模型共检测人脸上64处特征点,加上是否为face的标志位,输出label共有64x2+1=129个值。通过检测人脸特征点可以进行情绪分类与判断,或者应用于AR领域等等。

除了人脸特征点检测之外,还可以检测人体姿势动作,如下图所示:

  1. 滑动窗算法

目标检测的一种简单方法是滑动窗算法。

首先:在训练样本集上搜集相应的各种目标图片和非目标图片。注意训练集图片尺寸较小,尽量仅包含相应目标,如下图所示:

然后,使用这些训练集构建CNN模型,使得模型有较高的识别率

最后,在测试图片上,选择大小适宜的窗口、合适的步进长度,进行从左到右、从上倒下的滑动。每个窗口区域都送入之前构建好的CNN模型进行识别判断。若判断有目标,则此窗口即为目标区域;若判断没有目标,则此窗口为非目标区域。

滑动窗算法的优点是原理简单,且不需要人为选定目标区域(检测出目标的滑动窗即为目标区域)。但是其缺点也很明显,首先滑动窗的大小步进长度都需要人为直观设定。滑动窗过小或过大,步进长度过大均会降低目标检测正确率。而且,每次滑动窗区域都要进行一次CNN网络计算,如果滑动窗和步进长度较小,整个目标检测的算法运行时间会很长。所以,滑动窗算法虽然简单,但是性能不佳,不够快,不够灵活。

  1. 卷积实现滑动窗口:Convolutional Implementation of Sliding Windows

滑动窗算法可以使用卷积方式实现,以提高运行速度,节约重复运算成本。

首先,单个滑动窗口区域进入CNN网络模型时,包含全连接层。

那么滑动窗口算法卷积实现的第一步就是将全连接层转变成为卷积层,如下图所示:

全连接层转变成卷积层的操作很简单,只需要使用与上层尺寸一致的滤波算子进行卷积运算即可。最终得到的输出层维度是1x1x4,代表4类输出值。

单个窗口区域卷积网络结构建立完毕之后,对于待检测图片,即可使用该网络参数和结构进行运算。例如16 x 16 x 3的图片,步进长度为2,CNN网络得到的输出层为2 x 2 x 4。其中,2 x 2表示共有4个窗口结果。对于更复杂的28 x 28 x3的图片,CNN网络得到的输出层为8 x 8 x 4,共64个窗口结果。

之前的滑动窗算法需要反复进行CNN正向计算,例如16 x 16 x 3的图片需进行4次,28 x 28 x3的图片需进行64次。而利用卷积操作代替滑动窗算法,则不管原始图片有多大,只需要进行一次CNN正向计算,因为其中共享了很多重复计算部分,这大大节约了运算成本。值得一提的是,窗口步进长度与选择的MAX POOL大小有关。如果需要步进长度为4,只需设置MAX POOL为4 x 4即可。

  1. Bounding Box Predictions

滑动窗口算法有时会出现滑动窗不能完全涵盖目标的问题,如下图蓝色窗口所示。

YOLO(You Only Look Once)算法可以解决这类问题,生成更加准确的目标区域(如上图红色窗口)。

YOLO算法首先将原始图片分割成nxn网格,每个网格代表一块区域。为简化说明,下图中将图片分成3x3网格。

然后,利用上一节卷积形式实现滑动窗口算法的思想,对该原始图片构建CNN网络,得到的的输出层维度为3 x 3 x 8。其中,3 x 3对应9个网格,每个网格的输出包含8个元素:

如果目标中心坐标不在当前网格内,则当前网格Pc=0;相反,则当前网格Pc=1(即只看中心坐标是否在当前网格内)。判断有目标的网格中,限定了目标区域。值得注意的是,当前网格左上角坐标设定为(0, 0),右下角坐标设定为(1, 1),范围限定在[0,1]之间,但是可以大于1。因为目标可能超出该网格,横跨多个区域,如上图所示。目标占几个网格没有关系,目标中心坐标必然在一个网格之内。

划分的网格可以更密一些。网格越小,则多个目标的中心坐标被划分到一个网格内的概率就越小,这恰恰是我们希望看到的。

检测每个grid cell时,若检测到目标,则会将其标出来(图中的框)。这就是bounding box。

  1. 交并比:Intersection Over Union

IoU,即交集与并集之比,可以用来评价目标检测区域的准确性。

如上图所示,红色方框为真实目标区域,蓝色方框为检测目标区域。两块区域的交集为绿色部分,并集为紫色部分。蓝色方框与红色方框的接近程度可以用IoU比值来定义:

IoU可以表示任意两块区域的接近程度。IoU值介于0~1之间,且越接近1表示两块区域越接近。

  1. 非极大值抑制: Non-max Suppression

YOLO算法中,可能会出现多个网格都检测出到同一目标的情况,例如几个相邻网格都判断出同一目标的中心坐标在其内。

上图中,三个绿色网格和三个红色网格分别检测的都是同一目标。那如何判断哪个网格最为准确呢?方法是使用非最大值抑制算法。

非最大值抑制(Non-max Suppression)做法很简单,图示每个网格的Pc值可以求出,Pc值反映了该网格包含目标中心坐标的可信度。首先选取Pc最大值对应的网格和区域,然后计算该区域与所有其它区域的IoU,剔除掉IoU大于阈值(例如0.5)的所有网格及区域。这样就能保证同一目标只有一个网格与之对应,且该网格Pc最大,最可信。接着,再从剩下的网格中选取Pc最大的网格,重复上一步的操作。最后,就能使得每个目标都仅由一个网格和区域对应。如下图所示:

总结一下非最大值抑制算法的流程:

1. 剔除Pc值小于某阈值(例如0.6)的所有网格;

2. 选取Pc值最大的网格,利用IoU,摒弃与该网格交叠较大的网格;

3. 对剩下的网格,重复步骤2。

  1. 锚框:Anchor Boxes

到目前为止,我们介绍的都是一个网格至多只能检测一个目标。那对于多个目标重叠的情况,例如一个人站在一辆车前面,该如何使用YOLO算法进行检测呢?方法是使用不同形状的Anchor Boxes

如下图所示,同一网格出现了两个目标:人和车。为了同时检测两个目标,我们可以设置两个Anchor Boxes,Anchor box 1检测人,Anchor box 2检测车。也就是说,每个网格多加了一层输出。原来的输出维度是 3x3x8,现在是3x3x2x8(也可以写成3x3x16的形式)。这里的2表示有两个Anchor Boxes,用来在一个网格中同时检测多个目标。每个Anchor box都有一个Pc值,若两个Pc值均大于某阈值,则检测到了两个目标

在使用YOLO算法时,只需对每个Anchor box使用上一节的非最大值抑制即可。Anchor Boxes之间并行实现。

顺便提一下,Anchor Boxes形状的选择可以通过人为选取,也可以使用其他机器学习算法,例如k聚类算法对待检测的所有目标进行形状分类,选择主要形状作为Anchor Boxes。

9. YOLO Algorithm

这一节主要介绍YOLO算法的流程,算是对前几节内容的回顾。网络结构如下图所示,包含了两个Anchor Boxes。

1. For each grid call, get 2 predicted bounding boxes.

对于每个网格grid cell,预测B个bounding box,v1中B=2

2. Get rid of low probability predictions.

剔除pc值低的点

3. For each class (pedestrian, car, motorcycle) use non-max suppression to generate final predictions.

对于每一类,使用非极大值抑制来得到最终的预测结果

10. 候选区域:Region Proposals

之前介绍的滑动窗算法会对原始图片的每个区域都进行扫描,即使是一些空白的或明显没有目标的区域,例如下图所示。这样会降低算法运行效率,耗费时间。

为了解决这一问题,尽量避免对无用区域的扫描,可以使用Region Proposals的方法。具体做法是先对原始图片进行分割算法处理,然后只对分割后的图片中的块进行目标检测。

Region Proposals共有三种方法:

  • R-CNN: 滑动窗的形式,一次只对单个区域块进行目标检测,运算速度慢。

  • Fast R-CNN: 利用卷积实现滑动窗算法,类似第4节做法。

  • Faster R-CNN: 利用卷积对图片进行分割,进一步提高运行速度。

比较而言,Faster R-CNN的运行速度还是比YOLO慢一些。

4.4 人脸识别与神经风格迁移

  1. 人脸识别:What is face recognition

首先简单介绍一下人脸验证(face verification)和人脸识别(face recognition)的区别。

  • 人脸验证:输入一张人脸图片,验证输出与模板是否为同一人,即一对一问题。

  • 人脸识别:输入一张人脸图片,验证输出是否为K个模板中的某一个,即一对多问题。

一般地,人脸识别比人脸验证更难一些。因为假设人脸验证系统的错误率是1%,那么在人脸识别中,输出分别与K个模板都进行比较,则相应的错误率就会增加,约K%。模板个数越多,错误率越大一些。

  1. One Shot Learning

One-shot learning就是说数据库中每个人的训练样本只包含一张照片,然后训练一个CNN模型来进行人脸识别。若数据库有K个人,则CNN模型输出softmax层就是K维的。

但是One-shot learning的性能并不好,其包含了两个缺点:

  • 每个人只有一张图片,训练样本少,构建的CNN网络不够健壮

  • 若数据库增加另一个人,输出层softmax的维度就要发生变化,相当于要重新构建CNN网络,使模型计算量大大增加,不够灵活

为了解决One-shot learning的问题,我们先来介绍相似函数(similarity function)。相似函数表示两张图片的相似程度,用d(img1,img2)来表示。若d(img1,img2)较小,则表示两张图片相似;若d(img1,img2)较大,则表示两张图片不是同一个人。相似函数可以在人脸验证中使用:

  • : 一样

  • : 不一样

对于人脸识别问题,则只需计算测试图片与数据库中K个目标的相似函数,取其中d(img1,img2)最小的目标为匹配对象。若所有的d(img1,img2)都很大,则表示数据库没有这个人。

  1. Siamese Network

若一张图片经过一般的CNN网络(包括CONV层、POOL层、FC层),最终得到全连接层FC,该FC层可以看成是原始图片的编码encoding,表征了原始图片的关键特征。这个网络结构我们称之为Siamese network。也就是说每张图片经过Siamese network后,由FC层每个神经元来表征。

建立Siamese network后,两张图片和的相似度函数可由各自FC层与之差的范数来表示:

值得一提的是,不同图片的CNN网络所有结构和参数都是一样的。我们的目标就是利用梯度下降算法,不断调整网络参数,使得属于同一人的图片之间很小,而不同人的图片之间很大。

  • 若,是同一个人,则较小

  • 若,不是同一个人,则较大

具体网络构建和训练参数方法我们下一节再详细介绍。

  1. 三元组损失:Triplet Loss

构建人脸识别的CNN模型,需要定义合适的损失函数,这里我们将引入Triplet Loss。

Triplet Loss需要每个样本包含三张图片:靶目标(Anchor)、正例(Positive)、反例(Negative),这就是triplet名称的由来。顾名思义,靶目标和正例是同一人,靶目标和反例不是同一人。Anchor和Positive组成一类样本,Anchor和Negative组成另外一类样本。

我们希望上一小节构建的CNN网络输出编码接近,即尽可能小,而尽可能大,数学上满足:

根据上面的不等式,如果所有的图片都是零向量,即,那么上述不等式也满足。但是这对我们进行人脸识别没有任何作用,是不希望看到的。我们希望得到远小于。所以,我们添加一个超参数,且,对上述不等式做出如下修改:

顺便提一下,这里的也被称为边界margin,类似与支持向量机中的margin。举个例子,若,,则。

接下来,我们根据A,P,N三张图片,就可以定义Loss function为:

相应地,对于m组训练样本,cost function为:

关于训练样本,必须保证同一人包含多张照片,否则无法使用这种方法。例如10k张照片包含1k个不同的人脸,则平均一个人包含10张照片。这个训练样本是满足要求的。

然后,就可以使用梯度下降算法,不断训练优化CNN网络参数,让J不断减小接近0。

同一组训练样本,A,P,N的选择尽可能不要使用随机选取方法。因为随机选择的A与P一般比较接近,A与N相差也较大,毕竟是两个不同人脸。这样的话,也许模型不需要经过复杂训练就能实现这种明显识别,但是抓不住关键区别。所以,最好的做法是人为选择A与P相差较大(例如换发型,留胡须等),A与N相差较小(例如发型一致,肤色一致等)。这种人为地增加难度和混淆度会让模型本身去寻找学习不同人脸之间关键的差异,“尽力”让更小,让更大,即让模型性能更好。

下面给出一些A,P,N的例子:

值得一提的是,现在许多商业公司构建的大型人脸识别模型都需要百万级别甚至上亿的训练样本。如此之大的训练样本我们一般很难获取。但是一些公司将他们训练的人脸识别模型发布在了网上,可供我们使用。

  1. Face Verification and Binary Classification

除了构造triplet loss来解决人脸识别问题之外,还可以使用二分类结构。做法是将两个siamese网络组合在一起,将各自的编码层输出经过一个逻辑输出单元,该神经元使用sigmoid函数,输出1则表示识别为同一人,输出0则表示识别为不同人。结构如下:

每组训练样本包含两张图片,每个siamese网络结构和参数完全相同。这样就把人脸识别问题转化成了一个二分类问题。引入逻辑输出层参数w和b,输出表达式为:

其中参数和都是通过梯度下降算法迭代训练得到。

的另外一种表达式为:

上式被称为方公式,也叫方相似度。

在训练好网络之后,进行人脸识别的常规方法是测试图片与模板分别进行网络计算,编码层输出比较,计算逻辑输出单元。为了减少计算量,可以使用预计算的方式在训练时就将数据库每个模板的编码层输出保存下来。因为编码层输出比原始图片数据量少很多,所以无须保存模板图片,只要保存每个模板的即可,节约存储空间。而且,测试过程中,无须计算模板的siamese网络,只要计算测试图片的siamese网络,得到的直接与存储的模板进行下一步的逻辑输出单元计算即可,计算时间减小了接近一半。这种方法也可以应用在上一节的triplet loss网络中。

  1. 神经风格迁移:What is neural style transfer

神经风格迁移是CNN模型一个非常有趣的应用。它可以实现将一张图片的风格“迁移”到另外一张图片中,生成具有其特色的图片。比如我们可以将毕加索的绘画风格迁移到我们自己做的图中,生成类似的“大师作品”,很酷不是吗?

下面列出几个神经风格迁移的例子:

一般用C表示内容图片,S表示风格图片,G表示生成的图片。

  1. What are deep ConvNets learning

在进行神经风格迁移之前,我们先来从可视化的角度看一下卷积神经网络每一层到底是什么样子?它们各自学习了哪些东西。

典型的CNN网络如下所示:

首先来看第一层隐藏层

遍历所有训练样本,找出让该层激活函数输出最大的9块图像区域;然后再找出该层的其它单元(不同的滤波器通道)激活函数输出最大的9块图像区域;最后共找9次,得到9 x 9的图像如下所示,其中每个3 x 3区域表示一个运算单元。

可以看出,第一层隐藏层一般检测的是原始图像的边缘和颜色阴影等简单信息。

继续看CNN的更深隐藏层,随着层数的增加,捕捉的区域更大,特征更加复杂,从边缘到纹理再到具体物体。

8. Cost Function

9. Content Cost Function

10. Style Cost Function

什么是图片的风格?利用CNN网络模型,图片的风格可以定义成第层隐藏层不同通道间激活函数的乘积(相关性)。

例如我们选取第层隐藏层,其各通道使用不同颜色标注,如下图所示。因为每个通道提取图片的特征不同,比如1通道(红色)提取的是图片的垂直纹理特征,2通道(黄色)提取的是图片的橙色背景特征。那么计算这两个通道的相关性大小,相关性越大,表示原始图片及既包含了垂直纹理也包含了该橙色背景;相关性越小,表示原始图片并没有同时包含这两个特征。也就是说,计算不同通道的相关性,反映了原始图片特征间的相互关系,从某种程度上刻画了图片的“风格”。

接下来我们就可以定义图片的风格矩阵(style matrix)为:

11. 1D and 3D Generalizations

  1. 序列模型 

5.1循环神经网络RNN

  1. 序列模型:Why sequence models

序列模型能够应用在许多领域,例如:

  • 语音识别

  • 音乐发生器

  • 情感分类

  • DNA序列分析

  • 机器翻译

  • 视频动作识别

  • 命名实体识别

这些序列模型基本都属于监督式学习,输入x和输出y不一定都是序列模型。如果都是序列模型的话,模型长度不一定完全一致。

  1. 符号:Notation

以命名实体识别为例,介绍序列模型的命名规则。

  1. 循环神经网络:Recurrent Neural Network Model

对于序列模型,如果使用标准的神经网络,其模型结构如下:

使用标准的神经网络模型存在两个问题:

1、不同样本的输入序列长度或输出序列长度不同,即,,造成模型难以统一。解决办法之一是设定一个最大序列长度,对每个输入和输出序列补零并统一到最大长度。但是这种做法实际效果并不理想。

2、主要问题:这种标准神经网络结构无法共享序列不同之间的特征。例如,如果某个即“Harry”是人名成分,那么句子其它位置出现了“Harry”,也很可能也是人名。这是共享特征的结果,如同CNN网络特点一样。但是,上图所示的网络不具备共享特征的能力。值得一提的是,共享特征还有助于减少神经网络中的参数数量,一定程度上减小了模型的计算复杂度。例如上图所示的标准神经网络,假设每个扩展到最大序列长度为100,且词汇表长度为10000,则输入层就已经包含了100 x 10000个神经元了,权重参数很多,运算量将是庞大的。

标准的神经网络不适合解决序列模型问题,而循环神经网络(RNN)是专门用来解决序列模型问题的。RNN模型结构如下:

序列模型从左到右,依次传递,此例中,。到之间是隐藏神经元。会传入到第个元素中,作为输入。其中,一般为零向量。

RNN模型包含三类权重系数,分别是,,。且不同元素之间同一位置共享同一权重系数。

  1. 正向传播(Forward Propagation)

RNN的正向传播(Forward Propagation)过程为:

其中,表示激活函数,不同的问题需要使用不同的激活函数。

为了简化表达式,可以对项进行整合:

则正向传播可表示为:

值得一提的是,以上所述的RNN为单向RNN,即按照从左到右顺序,单向进行,只与左边的元素有关。但是,有时候也可能与右边元素有关。例如下面两个句子中,单凭前三个单词,无法确定“Teddy”是否为人名,必须根据右边单词进行判断。

He said, “Teddy Roosevelt was a great President.”

He said, “Teddy bears are on sale!”

因此,有另外一种RNN结构是双向RNN,简称为BRNN。与左右元素均有关系,我们之后再详细介绍。

  1. Backpropagation through time

针对上面识别人名的例子,经过RNN正向传播,单个元素的Loss function为:

该样本所有元素的Loss function为:

然后,反向传播(Backpropagation)过程就是从右到左分别计算对参数,,,的偏导数。思路与做法与标准的神经网络是一样的。一般可以通过成熟的深度学习框架自动求导,例如PyTorch、Tensorflow等。这种从右到左的求导过程被称为Backpropagation through time。

  1. Different types of RNNs

以上介绍的例子中,。但是在很多RNN模型中,是不等于的。例如第1节介绍的许多模型都是。根据与的关系,RNN模型包含以下几个类型:

Many to many:

Many to many:

Many to one:

One to many:

One to one:

不同类型相应的示例结构如下:

  1. Language model and sequence generation

语言模型是自然语言处理(NLP)中最基本和最重要的任务之一。使用RNN能够很好地建立需要的不同语言风格的语言模型。

什么是语言模型呢?举个例子,在语音识别中,某句语音有两种翻译:

The apple and pair salad.

The apple and pear salad.

很明显,第二句话更有可能是正确的翻译。语言模型实际上会计算出这两句话各自的出现概率。比如第一句话概率为,第二句话概率为。也就是说,利用语言模型得到各自语句的概率,选择概率最大的语句作为正确的翻译。概率计算的表达式为:

如何使用RNN构建语言模型?首先,我们需要一个足够大的训练集,训练集由大量的单词语句语料库(corpus)构成。然后,对corpus的每句话进行切分词(tokenize)。做法就跟第2节介绍的一样,建立vocabulary,对每个单词进行one-hot编码。例如下面这句话:

The Egyptian Mau is a bread of cat.

One-hot编码已经介绍过了,不再赘述。还需注意的是,每句话结束末尾,需要加上< EOS >作为语句结束符。另外,若语句中有词汇表中没有的单词,用< UNK >表示。假设单词“Mau”不在词汇表中,则上面这句话可表示为:

The Egyptian < UNK > is a bread of cat. < EOS >

准备好训练集并对语料库进行切分词等处理之后,接下来构建相应的RNN模型。

语言模型的RNN结构如上图所示,和均为零向量。Softmax输出层表示出现该语句第一个单词的概率,softmax输出层表示在第一个单词基础上出现第二个单词的概率,即条件概率,以此类推,最后是出现< EOS >的条件概率。

单个元素的softmax loss function为:

该样本所有元素的Loss function为:

对语料库的每条语句进行RNN模型训练,最终得到的模型可以根据给出语句的前几个单词预测其余部分,将语句补充完整。例如给出“Cats average 15”,RNN模型可能预测完整的语句是“Cats average 15 hours of sleep a day.”。

最后补充一点,整个语句出现的概率等于语句中所有元素出现的条件概率乘积。例如某个语句包含,则整个语句出现的概率为:

  1. Sampling novel sequences

  1. Vanisging gradients with RNNs

  1. Gated Recurrent Unit(GRU)

  1. Long Short Term Memory(LSTM)

  1. Bidirectional RNN

  1. Deep RNNs

  • 2
    点赞
  • 62
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值