在前面的章节中,我们介绍了神经元模型,它计算输入和权重的点积后对其使用非线性函数,以及分层结构的神经网络。总之,这些选择定义了“得分函数”的新形式,我们已经扩展了简单线性映射的形式。 具体而言,神经网络执行一系列线性映射并交织着非线性函数。 在本节中,我们将讨论关于数据预处理,权重初始化和损失函数的选择。
有三种常见的数据预处理的方式,我们假设数据矩阵 X X 的大小为 [N×D] [ N × D ] ( N N 是数据的数量, D D 是它们的维数)。
是最常见的预处理形式。这个方法减数据中每个单独特征的平均值,它的几何解释是能够将数据的中心向原点靠近。在numpy中,这个操作可以这样实现:X -= np.mean(X,axis = 0)
。特别是处理图像数据时,为了方便,从所有像素中减去单个值是很常用的预处理方式(例如,X -= np.mean(X))
,或者也能在三个颜色通道上分别处理。
是指规范化数据分布范围,使其具有大致相同的规模。有两种常见的方法来实现这种处理。一是将每个维度除以它的标准偏差(当数据均值为0时):(X /= np.std(X,axis = 0))
。另一种方式是将每个维度规范化,使每个维度最小值和最大值分别为-1和1。只有不同的输入特征具有不同的尺度时(或单位),应用这种预处理才有意义,但它们应该对学习算法来说基本上同样重要。在处理图像的情况,像素的尺寸已经大致相等(范围从0到255),因此不必严格执行这个预处理步骤。
是预处理的另一种形式。在这个过程中,数据首先如上所述居中处理。然后,我们可以计算协方差矩阵,告诉我们关于数据的相关性:
#大小为[N×D]的输入数据矩阵X
X - = np.mean(X,axis = 0)#零中心数据(重要)
cov = np.dot(X.T,X)/ X.shape [0]#获得数据协方差矩阵
数据协方差矩阵的(i,j)元素包含数据的第i维和第j维之间的协方差,这个矩阵的对角线包含了数据的方差。此外,协方差矩阵是对称的和半正定的。我们可以计算数据协方差矩阵的SVD分解:U,S,V = np.linalg.svd(cov)
。U的列是特征向量,S是奇异值的一维数组。为去除数据的相关性,我们将原始数据(以零为中心)投影到特征向量上:Xrot = np.dot(X,U)#解除关联数据
。
注意U的列是一组正交向量(范数为1,并且彼此正交),所以它们可以被看作是基向量。因此投影相当于数据的旋转,使得特征向量作为新的坐标轴。如果我们要计算Xrot
的协方差矩阵,我们会发现它是对角的。 np.linalg.svd
的很棒的一个特性是在其返回值U中,特征向量列按其特征值排序。我们可以通过只使用前几个特征向量来减少数据的维度,并丢弃数据方差很小的维度。这有时也被称为主成分分析(PCA)降维:
Xrot_reduced = np.dot(X,U [:,:100])#Xrot_reduced变成[N×100]
在这个操作之后,我们可以将大小为[N×D]
的原始数据集缩小到[N×100]
的大小,得到包含最多方差的数据的100个维度。通常情况下,您可以通过PCA简化数据集,从而节省训练线性分类器或神经网络的空间和时间。
在实践中可能用上的最后一个转换方法是白化。白化操作将基于特征向量的数据除以每个维度的特征值以规范化。这种变换的几何解释是,如果输入数据是一个服从高斯分布的多重变量,那么白化数据将数据转化为具有零均值和单位协方差矩阵的高斯变量。以下代码可以实现白化:
#白化数据:
#除以特征值(其是奇异值的平方根)
Xwhite = Xrot / np.sqrt(S + 1e-5)
警告:放大噪音。请注意,我们添加1e-5(或一个小常数)以防止被零除。这种转变的一个弱点是,它可能会放大数据中的噪音,因为它将所有维度(包括大部分是噪音的不相关维度)延伸到输入中具有相同大小。实际上,这可以通过更强的平滑(即增大1e-5的值)来缓解。
我们也可以尝试用CIFAR-10的图像对这些转换进行可视化。 CIFAR-10的训练集大小为50,000 x 3072,其中每个图像都被拉伸成3072维的行向量。 然后,我们可以计算[3072 x 3072]协方差矩阵并计算其SVD分解(操作相对费时)。 计算出的特征向量看起来像什么? 可视化可能有所帮助:
U.transpose()[:144 ,:]
来实现,然后将生成的3072个数字可视化为图像。您可以看到图像稍微模糊,反映了顶部特征向量捕捉较低频率的事实。但是,大部分信息仍然保留。右图:“白化”的可视化表示,144个维度的每个维度的变化都被压缩至相等长度。通过乘以
U.transpose()[:144,:]
,将白化的144个数字旋转回图像像素。较低的频率(占绝大多数的变化)变得可以忽略不计,而较高的频率(其原先相对较小的变化)则被放大了。
关于预处理的重要一点是任何预处理统计数据(例如数据平均值)只能在训练数据上计算,然后应用到验证/测试数据。计算平均值并从整个数据集中的每个图像中减去平均值,然后将数据分割成train / val/test分割是错误的。平均值只能在训练数据上计算,然后从所有数据(train / val / test)中减去相同的平均值。
我们学会如何构建神经网络架构,以及如何预处理数据。在我们开始训练网络之前,我们必须初始化它的参数。
让我们从不应该做的事开始。请注意,我们不知道在训练好的网络中每个权重的最终值应该是多少,但通过正确的数据规范化,大约一半的权重是正数一半是负数的假设是合理的。一个合理的观点可能是将所有初始权重设置为零,我们认为这是期望中的“最佳猜测”。这是错误的,因为如果网络中的每个神经元计算出相同的输出,那么它们也将在反向传播期间计算相同的梯度并且进行完全一样的参数更新。换句话说,如果它们的权重被初始化为相同,那么神经元之间就不能打破参数的对称性。
我们仍然希望权重非常接近零,但正如我们上面所论述的那样,不能是相同的零。作为一种解决方案,通常将神经元的权重初始化为小数值,并参照这样做打破对称性。这个想法是,神经元在开始时都是随机且独特的,所以他们将计算不一样的权重更新,并将自己作为为完整网络的不同部分。一个权重矩阵的实现可能看起来像 W=0.01∗np.random.randn(D,H) W = 0.01 ∗ n p . r a n d o m . r a n d n ( D , H ) ,其中randn是来自零均值、方差为1的高斯分布取样。通过这个公式,每个神经元的权向量被初始化为从多维高斯向量采样的一个随机向量,所以神经元指向输入空间的随机方向。取样也可以是平均分布的,但这对实践中的最终表现影响相对较小。
警告:数字越小不一定越好。例如,具有非常小权重的神经网络层将在反向传播期间在其数据上计算非常小的梯度(因为该梯度与权重的值成比例)。这会大大减少通过网络后向传播时“梯度信号”,并且可能成为深度网络的关注点(译注???)。
上述方法的一个问题是,随机初始化神经元输出的方差会随着输入数量增长的增长。事实证明,我们可以将每个神经元输出的方差标准化为1,方法是将其权重向量乘以输入数量的平方根。也就是说,推荐的启发式方法是将每个神经元的权向量初始化为:w = np.random.randn(n)/ sqrt(n)
,其中n是其输入的数量。这确保了网络中的所有神经元最初具有大致相同的输出分布,经验证明这能提高模型的收敛速度。
推导如下:考虑权重w和输入x之间的内积 s=Σniwixi s = Σ i n w i x i ,s是非线性激活之前的神经元的原始激活值。我们可以得到s的方差:
在推导的前两个步骤中我们使用了方差的性质。第三步中,我们假定输入和权重的均值为0,所以 E[xi]=E[wi]=0 E [ x i ] = E [ w i ] = 0 。需要注意的是,通常实际不是这种情况:例如,ReLU单元具有正的平均值。在最后一步,我们假设所有wi,xi都是独立分布的。从这个推导我们可以看出,如果我们想要s具有与其输入x相同的方差,那么在初始化期间我们应该确保每个权重的方差w是 1/n 1 / n 。由于 Var(aX)=a2Var(X) V a r ( a X ) = a 2 V a r ( X ) ,对于一个随机变量X和一个标量a,这意味着我们用 a=1/n‾√ a = 1 / n 缩放变量,就能使其方差为1 / N。因此,我们初始化w = np.random.randn(n)/ sqrt(n)
。
在Glorot等人的“Understanding the difficulty of training deep feedforward neural networks”中进行了类似的分析。在文章中,作者建议初始化形式 Var(w)=2/(nin+nout) V a r ( w ) = 2 / ( n i n + n o u t ) ,其中 nin,nout n i n , n o u t 是前一层和下一层中的单元数。这是基于对反向传播梯度的妥协和等效分析。近期关于此主题的论文有“Delving Deep into Rectifiers: Surpassing Human-Level Performance on ImageNet Classification ”,文章推导出专门针对ReLU神经元的初始化,得出网络中神经元的方差应为 2.0/n 2.0 / n 。这给出了初始化 w = np.random.randn(n)* sqrt(2.0 / n)
,这是现在推荐在采用ReLU神经元的神经网络的使用的初始化方法。
解决未校准方差问题的另一种方法是将所有权重矩阵设置为零,但是为了打破对称性,每个神经元随机连接(和上文类似,权重从高斯分布中抽样)到它下面的固定数量的神经元。连接的神经元数典型的取值为小于10。
将偏差初始化为零是可能的也是常见的,因为打破不对称是由权重中的小随机数完成的。对于ReLU非线性,有些人喜欢对所有偏差初始化为一小的常数值,例如0.01,因为这可以确保所有ReLU单位在开始时激活并因此获得并传播一些梯度。然而,目前尚不清楚这是否能够提供一致的改进(实际上有些结果似乎表明这种情况表现较差),仅使用0偏差初始化更为常见。
在实践中,目前的建议是使用ReLU单位并使用w = np.random.randn(n)* sqrt(2.0 / n)
,如He等人所述。
Ioffe和Szegedy最近开发的称为批量标准化(Batch Normalization)的技术通过在训练开始时强制整个网络中的参数使用单位高斯分布来初始化神经网络,从而减少了许多麻烦。仔细观察发现这是有可能的,因为标准化是一个简单可微的操作。在实现中,应用这种技术通常相当于在完全连接的层(或卷积层,我们很快就会看到)之后,在非线性之前插入 BatchNorm B a t c h N o r m 层。我们不在这里展开这种技术,它在相关文章中有很好的描述。但请注意,在神经网络中使用批量标准化已经成为一种非常普遍的做法。在实践中,使用批量标准化的网络比不好的初始化的具有更好的鲁棒性。此外,批量标准化可以解释为在网络的每一层进行预处理,但是以可微的方式集成到网络本身中。
有几种方法可以控制神经网络防止过拟合的能力:
也许是正则化最常见的形式。它可以通过直接在目标中惩罚所有参数的平方来实现。也就是说,对于网络中的每个权重 w w ,我们将 12λw2 1 2 λ w 2 添加到目标中,其中λ是正则化强度。通常在前面看到 12 1 2 的因子,因为这样做的话,参数w的这个项的梯度就是λw而不是2λw。 L2规则化的直观解释是严重惩罚峰值大的权重,而偏向于比较均匀的权重。正如我们在线性分类部分所讨论的那样,由于权重和输入之间的乘法交互作用,L2正则化具有很好的特性,即鼓励网络对所有输入都使用一些,而不是过度使用某些输入。最后,请注意,在梯度下降参数更新时,使用L2正则化最终意味着每个权重都线性衰减至0: W+=−λ∗W W + = − λ ∗ W 。
是另一种相对常见的正则化形式,其中对于每个权重w,我们将λ|w|添加到目标中。也可以将L1正则化与L2正则化结合起来:λ1|w|+λ2w2(这被称为Elastic net regularization)。 L1正则化具有令人感兴趣的性质,它导致权重向量在优化期间变稀疏(即非常接近零)。换句话说,具有L1正则化的神经元最终只使用其最重要输入的稀疏子集,并且对“噪声”输入几乎不变。相比之下,来自L2正则化的最终权重向量通常是弥散的,权重值较小。在实践中,如果你不关心特征的选择,那么L2正则化可以比L1提供更好的性能。
正则化的另一种相对常见的形式,它对每个神经元的权重执行绝对上限,并使用投影梯度下降来实现这种约束。在实践中,这相应于正常执行参数更新,然后强制每个神经元的权向量 ||w||2<c | | w | | 2 < c 。 c的典型值约为3或4,有人在使用这种形式的正则化时称其是有效的。其吸引人的特性之一是,即使学习率设置得太高,网络也不会“爆炸”,因为更新总是有界的。
是Srivastava等人最近引入的非常简单有效的正则化技术。:Dropout: A Simple Way to Prevent Neural Networks from Overfitting(pdf)。训练时,Dropout让神经元以概率p(超参数)为活动的,否则将其设置为0来实现的,如图3所示。
示例中的3层神经网络Dropout的普通实现如下:
"""Dropout: 不推荐的实现 """
p = 0.5 # 丢弃神经元的概率
def train_step(X):
""" X contains the data """
# 前向传播
H1 = np.maximum(0, np.dot(W1, X) + b1)
U1 = np.random.rand(*H1.shape) < p # first dropout mask
H1 *= U1 # drop!
H2 = np.maximum(0, np.dot(W2, H1) + b2)
U2 = np.random.rand(*H2.shape) < p # second dropout mask
H2 *= U2 # drop!
out = np.dot(W3, H2) + b3
# backward pass: compute gradients... (not shown)
# perform parameter update... (not shown)
def predict(X):
# ensembled forward pass
H1 = np.maximum(0, np.dot(W1, X) + b1) * p # NOTE: scale the activations
H2 = np.maximum(0, np.dot(W2, H1) + b2) * p # NOTE: scale the activations
out = np.dot(W3, H2) + b3
在train_step函数中,在第一个隐藏层和第二个隐藏层上执行了两次Dropout操作。我们也可以在输入层执行Dropout操作,这样就需要给输入X创建一个二进制掩码。反向传播则必须考虑生成的掩码U1,U2。
至关重要的是,在predict函数中,我们不使用Dropout,但是我们对两个隐藏层得出输出都要执行一个p的缩放。这一点很重要,因为在测试时所有神经元都被激活,但是我们希望测试时神经元的输出与训练时期的输出期望相同。例如,在 p=0.5 p = 0.5 的情况下,神经元在测试时必须将他们的输出减半,使得其输出期望与训练时相同。考虑神经元x的输出(在Dropout之前),执行Dropout时,因为神经元的输出将以概率1-p被设置为零,因此这个神经元的期望输出将变成 px+(1−p)0 p x + ( 1 − p ) 0 。在测试时,当我们保持神经元始终处于活动状态时,我们必须调整 x→px x → p x 以保持相同的期望输出。可以看出,在测试时执行这种衰减类似于遍历所有可能的子网络(指数级),并计算集成模型的预测结果。
这个方法的缺点在于我们必须在测试时间通过p来缩放激活值。由于测试时对性能要求很高,因此最好使用反向Dropout(inverted Dropout),它在训练时执行缩放,而在测试时间不做任何缩放。此外,当你调整在哪些神经元中应用Dropout时,预测代码无需更改。反向Dropout的实现如下:
"""
Inverted Dropout: Recommended implementation example.
We drop and scale at train time and don't do anything at test time.
"""
p = 0.5 # probability of keeping a unit active. higher = less dropout
def train_step(X):
# forward pass for example 3-layer neural network
H1 = np.maximum(0, np.dot(W1, X) + b1)
U1 = (np.random.rand(*H1.shape) < p) / p # first dropout mask. Notice /p!
H1 *= U1 # drop!
H2 = np.maximum(0, np.dot(W2, H1) + b2)
U2 = (np.random.rand(*H2.shape) < p) / p # second dropout mask. Notice /p!
H2 *= U2 # drop!
out = np.dot(W3, H2) + b3
# backward pass: compute gradients... (not shown)
# perform parameter update... (not shown)
def predict(X):
# ensembled forward pass
H1 = np.maximum(0, np.dot(W1, X) + b1) # no scaling necessary
H2 = np.maximum(0, np.dot(W2, H1) + b2)
out = np.dot(W3, H2) + b3
在引入Dropout的概念后,人们进行了大量的研究,试图了解它在实践中的如此有效的原因,以及它与其他正则化技术的关系。有兴趣的读者可以进一步阅读:
Srivastava等人的Dropout论文。
“Dropout Training as Adaptive Regularization :“研究表明,将输入数据根据Fisher information matrix 的估计缩放后,Dropout一阶等价于 L2 L 2 正则化”。
前向传播的噪音。更广泛的来说,Dropout属于在前向传播中引入随机行为的一种方法。理论分析中,噪音的作用会减小(比如Dropout测试时乘以p),通过采样,随机执行多个前向传播然后对它们的结果进行平均,同样也减小了噪音。在这个方向上的其他研究有DropConnect,文中将一个随机权重的子集在正向传递期间设置为0。剧透一下,卷积神经网络还利用了stochastic pooling, fractional pooling, and data augmentation等方法。稍后我们将详细介绍这些方法。
偏差正则化。正如我们在“线性分类”部分中已经提到的那样,偏差参数正则化并不常见,因为它们不和数据通过乘法产生相互作用。但是,在实际应用中(并且具有适当的数据预处理),正则化偏差很少会导致性能显著变差。这很可能是因为与所有权重相比,偏差项的数量非常少,所以如果分类器需要正则化偏差来获得更优的数据损失,那么对偏差正则化也是能够接受的。
逐层正则化。对不同的层采用不同的正则化强度(除了输出层)不太常见。关于这个想法的结果的文献也不多。
在实践中:使用交叉验证的全局L2正则化是最常见的。将其与在所有层之后应用Dropout相结合也很常见。 p = 0.5是一个合理的默认值,但可以根据交叉验证进行调整。
我们已经讨论了正则化损失,这可以当作对模型复杂性的一种惩罚。目标函数的第二部分是数据损失(data loss),数据损失在监督学习问题中用来衡量预测结果(例如分类中的类别分数)和样本实际标签之间的一致性。数据损失取为每个样本数据损失的平均值。也就是说, L=1NΣNiLi L = 1 N Σ i N L i 其中N是训练数据的数量。让我们缩写 f=f(xi;W) f = f ( x i ; W ) 作为神经网络中输出层的激活函数。在实践中可能需要解决几种类型的问题:
是我们迄今为止详细讨论过的问题。在这里,我们假设每个样本都有一个样本数据集和一个正确的标签。在这种情况下,最常见的两种损失函数之一是SVM:
上述两种损失都假定只有一个正确的标签 yi y i 。但是如果 yi y i 是一个二元向量,那么每个样本都可能有或没有某一特定属性,而且这些属性不是唯一的。例如,Instagram上的图片可以被认为是来自所有主题标签中的特定标签子集,而图像可能包含多个标签。在这种情况下,一个好方法是为每个属性单独构建一个二元分类器。例如,每个类别的二元分类器将独立采用以下形式:
这种损失的替代方法是独立训练每个属性的逻辑回归分类器。二元逻辑回归分类器只有两个类(0,1),并计算类1的概率为:
是预测实值的任务,例如房屋价格或图像中某物的长度。对于这项任务,计算预测值与真实值之间的损失是很常见的,L2范数或L1范数也同样常见。 计算单个样本损失的L2范数的平方将有如下的形式:
注意事项:要注意,L2损失比Softmax等更加稳定的损失更难优化。直观地说,它需要网络为每个输入(及其增量)准确输出一个正确的值。请注意,Softmax并非如此,每个分数的精确值并不重要:只要它们的大小是匹配的就行(例如,二分类问题中,评分[1,2]与评分[0.1,0.2]是一样的)。此外,L2损失鲁棒性较弱,因为异常值可能会产生很大的梯度。当面临回归问题时,首先考虑是不是绝对不能将问题转化为分类问题。例如,如果您要预测某个产品的星级评分,那么使用5个独立分类器评估1-5星而非回归可能会更好。分类有很多额外的好处,它可以给你一个回归输出的分布,而不仅仅是单一的输出,而不输出其置信程度。如果您确定分类是不合适的,请使用L2,但要小心:L2更脆弱,并且在网络中使用Dropout(特别是在L2之前的层中)并不是一个好主意。
结构化损失是指标签可以是任意结构(如图形,树或其他复杂对象)的情况。通常还假定结构的空间非常大,并且不易枚举。结构化支持向量机损失背后的基本思想是要求在正确的结构 yi y i 和得分最高的不正确结构之间有一个间隔。这个问题一般无法作为梯度下降的简单无约束优化问题来解决。相反,通常需要设计特殊求解方法,以便可以利用结构空间的特定条件。我们简要地提一下这个问题,但这些细节超出了课堂范围。
综上所述:
用标准差为 2N‾‾√ 2 N 的高斯分布来初始化权重,其中n是神经元输入的数量。 在numpy中: w = np.random.randn(n)* sqrt(2.0 / n)
。
使用L2正则化和Dropout(反转版本)