深度学习:前馈神经网络

对深度学习(或称神经网络)的探索通常从它在计算机视觉中的应用入手。计算机视觉属于人工智能领域,因深度学习技术而不断革新,并且计算机视觉的基础(光强度)是用实数来表示的,处理实数正是神经网络所擅长的。

以如何识别0到9的手写数字举例。如果从头解决这个问题,首先我们需要制作一个镜头来聚焦光线以成像,然后通过光传感器将光线转换成计算机可“感知”的电子脉冲,最后,由于使用的是数字计算机,需要将图片离散化,也就是说,将颜色和光强度通过二维数组表示出来。幸运的是,我们的在线数据集Mnist(发音为“em-nist”)已经帮我们完成了图片离散化工作。(这里的“nist”是指美国国家标准技术研究所,此数据集由nist提供。)如图1.1所示,每张图片可用28×28的整数数组表示。为了适应页面,已删除左右边界区域。

图1.1 Mnist中离散化的图片数据

图1.1中,0表示白色,255表示黑色,介于两者之间的数字表示灰色。这些数字被称为像素值,其中像素是计算机可处理图片的最小单位。像素所能展示的实际世界的“大小”取决于镜头,以及镜头与物体表面的距离等因素。但在这个例子里,我们不用考虑这些因素。图1.1中对应的黑白图片如图1.2所示。

 

图1.2 图1.1像素得出的黑白图片

仔细观察这张图片,我们会发现可以用一些简单的方法解决本例中的问题。例如,已知图片是数字“7”,则(8,8)处的像素点是暗的。同理,数字“7”的中间部分为白色,比如,(13,13)处的像素值为0。数字“1”则不同,由于数字“1”的标准书写不占用左上角位置,而是在正中间部分,所以这两处的像素值与数字“7”相反。稍加思考,我们就能想出许多启发式规则(大部分情况下是有效的),并利用这些规则编写分类程序。

然而,这不是我们要做的。本书的重点是机器学习,也就是通过给定的样本和对应的正确答案让计算机进行学习。在本例中,我们希望程序通过学习给出的样本和正确答案(或称标签),学会识别28×28像素图片中的数字。在机器学习中,这被称为监督学习问题,准确地说,是全监督学习问题,即每个学习样本都对应有正确答案。在后面的章节中,会出现没有正确答案的情况,如第6章中的半监督学习问题,第7章的无监督学习问题。在这些章节中我们会具体讲解它们的工作机制。

如果忽略处理光线和物体表面的细节问题,就只需解决分类问题,即给定一组输入(通常称为特征),将产生这些输入(或具有这些特征)的实体,识别(或分类)为有限类。在本例中,输入是像素,分为十类。定义有l个输入(像素)的向量为x = (x1, x2,…, xl),正确答案为a。通常输入的是实数,正负均可,在本例中,为正整数。

1.1 感知机

我们从一个更简单的问题入手,创建一个程序来判断图片中的数字是否为0。这是二分类问题,感知机是早期解决二分类问题的机器学习方法之一,如图1.3所示。

 

图1.3 感知机原理图

感知机是神经元的简单计算模型。单个神经元(见图1.4)通常由许多输入(树突)、一个细胞体和一个输出(轴突)组成。因此,感知机也有多个输入和一个输出。一个简单的感知机,在判断28×28像素图片中的数字是否为0时,需要有784个输入和1个输出,每个输入对应一个像素。为简化作图,图1.3中的感知机只有5个输入。

 

图1.4 一个典型的神经元

一个感知机由一个权重向量w = (w1,…, wl )和一个偏置项b组成,其中权重向量的每个权值对应一个输入。wb称为感知机的参数。通常,我们用Φ来表示参数集,ΦiΦΦi是第i个参数。对于感知机,Φ = {w U b}。

感知机利用这些参数计算下列函数。

 

(1.1)

即,将每个感知机输入乘其对应权重值,并加上偏置项,若结果大于0,返回1,否则返回0。感知机是二分类器,所以1表示x属于此类,0则表示不属于。

将长度为l的两个向量的点积定义为式(1.2)。

 \emph{\textbf{x}}\cdot \emph{\textbf{y}}=\sum\limits_{i=1}^{l}{{{x}_{i}}{{y}_{i}}} (1.2)

因此,我们可以将感知机运算函数简化如下:

 

(1.3)

bw·x被称为线性单元,图1.3中,使用∑来标识。当涉及调整参数时,可将偏置项视为w中的一个权重,这个权重的特征值总是1。这样,只需讨论调整w的情况。

我们关注感知机,是因为感知机算法可以根据训练样本找到Φ,而且这种算法简单又鲁棒。我们用上标来区分样本,第k个样本的输入是xk = [x1k,…,xlk],对应的答案是ak。在感知机这种二分类器中,答案是1或0,表示是否属于该类。当分类有m类时,答案将是0到m−1的一个整数。

有时可以将机器学习描述为函数逼近问题。从这个角度来看,单个线性单元感知机定义了一类参数化的函数。而感知机权重的学习,就是挑选出该类中最逼近解的函数,作为“真”函数进行计算,当给定任何一组像素值时,就能正确地判断图片是否为数字0。

在机器学习研究中,假设我们至少有两组,当然最好是三组问题样本。第一组是训练集,它用于调整模型的参数。第二组是开发集(它也称为留出集或验证集),其在改进模型过程中用于测试模型。第三组是测试集,一旦模型固定了,(幸运的话)产生了良好的结果,我们就可以对测试集样本进行评估。测试集是为了防止在验证集上能够运行的程序在没见过的问题上不起作用。这些集合有时被称为语料库,比如“测试语料库”。我们使用的Mnist数据可以在网上获得,训练数据包括60,000幅图片及其对应的正确标签,验证集和测试集各有10,000幅图片和标签。

感知机算法的最大特点是,如果有一组参数值使感知机能够正确分类所有训练集,那么该算法一定会找到这组值。可惜的是对于大多数现实样本来说,并不存在这组参数值。但即使没有这组参数值,仍然有参数值可以使得样本识别正确率很高,就这一点来说,感知机的表现也极其出色。

该算法通过多次迭代训练集来调整参数以增加正确识别的样本数。如果训练时我们遍历了整个训练集发现不需要改变任何参数,那这组参数就是正确的,我们可以停止训练。然而,如果没有这组正确的参数,那么参数会一直改变。为了防止这种情况,我们在N次迭代后停止训练,其中N是程序员设置的系统参数。通常,N随着要学习的参数总数增加而增加。我们要谨慎区分参数Φ和其他与程序关联的参数,它们不属于Φ,例如训练集的迭代次数N,我们称后者为超参数。图1.5给出了该算法的伪代码,注意Δx一般是指“x的变化”。

图1.5中最关键的两行是2(a)i和2(a)ii,其中ak等于1或0,ak=1表示图片属于这一类,等于0则表示不属于。2(a)i行是指如果感知机的输出是正确的标签,就不用进行操作。2(a)ii行则指定了如何改变权重wi,如果我们在每一个参数wi上增加(ak(xk))xik,再次尝试这个样本,感知机误差会减少,甚至得出正确答案。

 

图1.5 感知机算法

我们需要尝试所有可能性,才能了解2(a)ii行的算法是如何增加正确率的。假设训练样本xk属于该类,这意味着它的标签ak = 1。如果我们分类错误,那(x)(感知机对第k个训练样本的输出)一定是0,所以( akxk))= 1,且对于所有的i,Δwi = xik。因为所有像素值都≥0,所以算法会增加权重,下一次运算后xk)会返回更大的值,这减少了误差。(读者练习:在样本实际不在类别里但感知机却认为它属于该类的情况下,使用这个公式达到减少误差目的。)

我们将偏置项b视为一个虚拟特征x0的权重,该特征值恒等于1,上述讨论仍能成立。

让我们举一个小例子。这里我们只查看(并调整)四个像素的权重,分别是像素(7,7)(左上角中心位置)、像素(7,14)(上部中心位置)、像素(14,7)和像素(4,14)。通常方便的做法是对像素值进行归一化,使其介于0和1之间。假设我们的图片中的数字是0,那么a = 1,而这四个位置的像素值分别是0.8、0.9、0.6和0。由于最初所有参数都为0,计算第一幅图片的f(x),运算w·x+b=0,得到f (x) = 0,所以我们的图片被错误分类,− f (x) = 1。因此,对权重w7,7进行运算,结果变为(0 + 0.8×1) = 0.8。同样,接下来的两个权重w7,14和w14,7变为0.9和0.6。而中心像素权重保持为0(因为那里的图像值为0),偏置项变为1.0。需要注意的是,如果我们第二次将同样的图片输入感知机,并使用新的权重,它会被正确分类。

假设下一幅图片不是数字0,而是数字1,且两个中心列的像素的值为1,其他为0。那么bw·x = 1 + 0.8×0+ 0.9×1 + 0.6×0 + 0×1 = 1.9,即f(x)>0,感知机会将样本错误分类为0,因此akf(xk) = 0−1 = −1。我们根据2(a)ii行调整每个权重,因为像素值为0,所以w7,7和w14,7不变,而w7,14现在变成了0.9−0.9×1 = 0(前一个值减去权重乘以当前像素值)。bw4,14的新值留给读者计算。

我们多次在训练集上进行迭代。将训练集过一遍被称为一轮。此外,请注意,如果训练集数据以不同的顺序呈现给程序,我们学习的权重会有所不同,良好的做法是让训练集数据在每轮中随机呈现。在1.6节中我们会再谈到这一点,不过为了刚入门的学生,我们省略了这些细节。

如果我们不是只创建一个感知机,而是为我们想要识别的每个类别都创建一个感知机,这样就将感知机扩展到多类别决策问题。比如,对于最初的0~9数字识别问题,我们可以创建10个感知机,每个数字一个,然后输出感知机预测值最高的类别。在图1.6中,我们展示了三个感知机如何识别三类物体之一的图片。

虽然图1.6中三个感知机看起来关联紧密,但实际上它们是独立的,只共享相同的输入。多类别感知机输出的答案是输出最高值的线性单元的对应数字,所有感知机都是独立于其他感知机接受训练,使用的算法与图1.5所示完全相同。因此,给定图片和标签,我们对10个感知机运行感知机算法步骤2(a)共10次。如果标签是5,但是得出最高值的感知机对应数字6,那么对应数字0到4的感知机不会改变它们的参数(因为它们正确地给出了不是这一类或者是这一类的判断)。对应数字6到9的感知机也是如此。另一方面,由于感知机5和6报告了错误的判断,它们要改变参数。

 

图1.6 用于识别多个类别的多个感知机

1.2 神经网络的交叉熵损失函数

在初期,对神经网络的讨论(以下简称为NN)伴随着类似于图1.6所示的图表,这类图表强调了各个计算元素(线性单元)。如今,我们预计这类元素的数量会很大,所以我们讨论的是以层为单位的计算。层即为一组存储或计算单元,各层并行工作并将值传递给另一层。图1.7所示是图1.6强调了层视图的版本,它显示了输入层到计算层的传递过程。

 

图1.7 神经网络层

神经网络可能有许多层,每个层的输出都是下一层的输入。层的堆叠结构便是“深度学习”中的“深度”由来。

然而,多层堆叠的感知机工作效果不佳,所以我们需要另一种方法学习如何改变权重。在本节中,我们探讨如何使用最简单的网络——前馈神经网络,和相对简单的学习技术——梯度下降,以达到改变权重的目的。一些研究人员将用梯度下降训练的前馈神经网络称为多层感知机。

在我们讨论梯度下降之前,我们首先需要讨论损失函数。损失函数是从模型输出得到输出结果有多“坏”的函数。在学习模型参数时,我们的目标是将损失最小化。如果对于训练样本我们得到了正确的结果,则感知机的损失函数值为零,如果不正确,则为1,这被称为0-1损失。0-1损失的优点让我们有足够的理由使用它,但它也有缺点,它不适用于梯度下降学习,因为梯度下降学习的基本思想是根据以下式子修改参数。

 \Delta {{\varphi }_{i}}=-\mathcal{L}\frac{\partial L}{\partial {{\varphi }_{i}}} 

(1.4)

这里的\mathcal{L}是学习率,它是实数,用来衡量我们改变参数的程度。损失L相对于参数的偏导数是非常重要的,换句话说,如果我们能发现参数是如何影响损失的,就可以改变参数来减少损失(所以\mathcal{L}前有符号–)。在我们的感知机中,或者说在神经网络中,输出是由模型参数Φ所决定的,所以在此类模型中,损失是函数L(Φ)。

说得更形象一点,假设我们的感知机只有两个参数,我们可以想象一个具有轴Φ1和Φ2的欧几里德平面,平面中每个点的上方(或下方)都标有损失函数值。假设参数的当前值分别为1.0和2.2,观察L在点(1,2.2)的行为。当Φ2=2.2时,图1.8的切面显示了作为Φ1函数的虚拟损失的运动轨迹。当Φ1=1时,切线的斜率约为–\frac{\text{1}}{\text{4}}。如果学习率\mathcal{L} = 0.5,那么根据式(1.4),加上(−0.5 ) ×(–\frac{\text{1}}{\text{4}}) =0.125,即,向右移动约0.125个单位,可以减少损失。

 

图1.8 作为1函数的虚拟损失的运动轨迹

式(1.4)有效的前提是,损失必须是参数的可微函数,0-1损失却不是。假设将我们犯错误的数量作为某个参数Φ的函数,开始我们只在一个样本上评估我们的感知机,结果得到错误答案。如果我们继续增加Φ(或者减少Φ),并且重复足够多次,f(x)最终会改变它的值,我们就可以得到正确的结果。所以当我们看函数图像时,会看到一个阶跃函数,但是阶跃函数是不可微的。

当然,还有其他的损失函数。最常用的是交叉熵损失函数,它最接近“标准” 的损失函数。在本节中,我们将解释交叉熵损失函数是什么,以及我们的网络将如何计算这个函数。下一节使用它进行参数学习。

图1.6中的网络输出一个向量,向量中的每个元素值为一个线性单元的输出,我们选择最高输出值对应的类别,接着改变该网络,使得输出的数字是各类的(估计的)概率分布。在本例中,正确分类的概率是随机变量CC=c,其中c∈{0,1,2,…,9}。概率分布是一组总和为1的非负数,目前该网络可以输出数字,但它们一般同时包括正数和负数。幸运的是,softmax函数可以简单地将一组数字转换成概率分布,该函数公式如下:

 \sigma {{(\emph{\textbf{x}})}_{j}}=\frac{{{\text{e}}^{{{x}_{j}}}}}{\sum\limits_{i}{{{\text{e}}^{{{x}_{i}}}}}} 

(1.5)

softmax可以保证返回一个概率分布,因为即使xi是负数,{{e}_{{{x}_{i}}}}也是正数,并且所有值加和为1,因为分母是所有可能值之和。例如,σ([−1,0,1])≈[0.09, 0.244,0.665 ]。要注意一个特殊情况是,神经网络到softmax的输入都为0。当e0= 1,如果有10个选项,所有选项的概率会是\frac{1}。推而广之,如果有n个选项,概率即为\frac{1}{n}

“softmax”之所以得名,是因为它是“max”函数的“软”(soft)版本。max函数的输出完全由最大输入值决定,softmax的输出主要但不完全由最大值决定。很多机器学习函数以“softX”的形式命名,意味着X的输出“被软化”。

图1.9显示了添加了softmax层的网络。左边输入的数字是图片像素值,添加softmax层之后,右边输出的数字是类别概率。离开线性单元进入softmax函数的数字通常称为logit,这是术语,用来指即将使用softmax转化为概率的、没有标准化过的数字(“logit”有多种发音方式,最常用的发音是“LOW-git”)。我们用l来表示多个logit的向量(每个类别对应一个logit),公式如下:

 p({{l}_{i}})=\frac{{{\operatorname{e}}^{{{l}_{i}}}}}{\sum\limits_{j}{{{\operatorname{e}}^{{{l}_{j}}}}}} 

(1.6)

 \propto {{\operatorname{e}}^{{{l}_{i}}}} 

(1.7)

 

图1.9 具有softmax层的简单网络

公式的第二行是指,由于softmax函数的分母是一个标准化常数,以确保这些数字总和为1,所以概率与softmax的分子成比例。

现在我们可以定义交叉熵损失函数X

 X({\pmb{\it\Phi} ,x)=-\ln {{p}_{\it{\pmb{\it\Phi} }}(a_x) (1.8)

样本x的交叉熵损失是x对应标签的概率的负对数。换句话说,我们用softmax计算所有类别的概率,然后找出正确答案,损失是这个数字的概率的负对数。

该公式的合理性如下所述:它朝着正确的方向前进。如果X是一个损失函数,模型越差,函数结果应该越大,模型越好,结果越小。而一个改进过的模型应该为正确答案得到更高的概率,所以我们在前面加上一个减号,这样随着概率的增加,这个数字会变得越来越小。数字的对数随着数字的增加/减小而增加/减小。因此,相较于好参数,X(Φx )输入坏参数得到的结果会更大。

但是为什么要使用对数?一般认为对数可以缩小数字之间的距离,比如,log(10,000)和log(1,000)之间的差异是1。有人认为这是损失函数的一个缺点:它会让糟糕的情况看起来不那么糟糕。对数的这种特征是有误导性的,随着X越来越大,ln()却不会以同样的程度增加,但考虑图1.10中的–ln(X),当X变为0时,对数的变化比X的变化大得多。由于我们处理的是概率,这才是我们要关心的区域。

 

图1.10 –ln(x )图像

为什么这个函数被称为交叉熵损失函数?在信息论中,当概率分布在逼近某个真实分布时,这两个分布的交叉熵用于测量它们之间的差异,交叉熵损失是交叉熵的负值的近似值。这只是比较浅显的解释,因为信息论不是本书重点,所以不再深入讨论。

1.3 导数与随机梯度下降

现在我们有了损失函数,可以用下面的公式来计算它。

X({\pmb{\it\Phi} ,x)=-\ln p(a) 

(1.9)

 p(a)={{\sigma }_{a}}(l)=\frac{{{\text{e}}^{{{l}_{a}}}}}{\sum\limits_{i}{{{\text{e}}^{{{l}_{i}}}}}} 

(1.10)

  {{l}_{j}}={{b}_{j}}+\emph{\textbf{x}}\cdot \emph{\textbf{w}}j 

(1.11)

我们首先根据式(1.11)计算logit l,然后根据式(1.10)将这些logit通过softmax层来计算概率,接着根据式(1.9)计算损失,即正确答案概率的负自然对数。请注意,之前线性单元的权重表示为w,但现在我们有许多这样的单元,所以wj是第j个单元的权重,bj是该单元的偏置项。

这个从输入到损失的计算过程被称为学习算法的前向传递,它计算在权重调整过程即反向传递中将要使用的值。该值可以使用多种方法进行计算,这里我们使用随机梯度下降法。“梯度下降”这个术语源于损失函数的斜率(它的梯度),然后使系统跟随梯度降低其损失(下降)。整个学习方法通常被称为反向传播。

我们先举个最简单的例子,对某个偏置项bj,如何进行梯度估计。从式 (1.9)~式(1.11)中可以看出,通过先后改变lj的值和概率,bj改变了损失。让我们分步做这件事。在本例中,我们只考虑由单个训练样本引起的错误,所以我们将X(Φx )写为X(Φ)。首先,

 \frac{\partial X(\boldsymbol{\it\Phi } )}{\partial {{b}_{j}}}=\frac{\partial {{l}_{j}}}{\partial {{b}_{j}}}\frac{\partial X(\boldsymbol{\it\Phi } )}{\partial {{l}_{j}}}

(1.12)

该公式使用链式法则来表达前述内容——bj的变化会导致X的变化,这是因为bj的变化会引起lj的变化。

现在看式(1.12)右边的一阶偏导数。事实上,它的值恒等于1。

 \frac{\partial {{l}_{j}}}{\partial {{b}_{j}}}=\frac{\partial }{\partial {{b}_{j}}}({{b}_{j}}+\sum\limits_{i}{{{x}_{i}}}{{w}_{i,j}})=1 

(1.13)

其中,wi,j是第j个线性单元的第i个权重。因为在{{b}_{j}}+\sum\limits_{i}{{{x}_{i}}}{{w}_{i,j}}中,作为bj的函数变化的只有bj本身,所以导数是1。

接下来,我们考虑X作为lj的函数是如何变化的。

 \frac{\partial X(\boldsymbol{\it\Phi  })}{\partial {{l}_{j}}}=\frac{\partial {{p}_{a}}}{\partial {{l}_{j}}}\frac{\partial X(\boldsymbol{\it\Phi  })}{\partial {{p}_{a}}}

(1.14)

其中pi是网络分配给i类的概率。这说明,因为X只取决于正确答案的概率,所以lj只通过改变这个概率来影响X。反过来,

 \frac{\partial X({\pmb{\it\Phi}} )}{\partial {{p}_{a}}}=\frac{\partial }{\partial {{p}_{a}}}(-\ln {{p}_{a}})=-\frac{1}{{{p}_{a}}}

(1.15)

(来自基础微积分)。

还剩下一个项有待估值。

 \frac{\partial p_{a}}{\partial l_{j}}=\frac{\partial \sigma_{a}(l)}{\partial l_{j}}=\left{\begin{array}{ll}{\left(1-p_{j}\right) p_{a}} & {a=j} \\{-p_{j} p_{a}} & {a \neq j}\end{array}\right.

(1.16)

式(1.16)的第一个相等表示我们通过计算logit的softmax函数来获得概率,第二个相等来自维基百科。公式推导需要仔细梳理各项,所以在这里我们不做推导,但我们仍可以得出它的合理性。我们来看logit的向量值lj的变化如何影响softmax函数得出的概率,可以查看以下公式。

 {{\sigma }_{a}}(l)=\frac{{{\text{e}}^{{{l}_{a}}}}}{\sum\limits_{i}{{{\text{e}}^{{{l}_{i}}}}}}

分为两种情况。假设logit正在变化的j不等于a,也就是说,假设这是一张数字6的图片,但是我们计算的是logit 8对应的偏置。在这种情况下,式中lj只出现在分母中,导数应该是负的(或0),因为lj越大,pa越小。这就对应式(1.16)中的第二种情况,当然,式(1.16)中会产生一个小于或等于0的数字,因为两个概率相乘不会为负。

另一方面,如果j = a,则式中lj同时出现在分子和分母中。它在分母中的出现会导致结果变小,但是在这种情况下,分子的增加会抵消掉结果的变小。因此,在这种情况下,我们期望一个正导数(或0),这是式(1.16)的第一种情况。

有了这个结果,我们现在可以推导出修正偏置参数bj的公式。将式(1.15)和式(1.16)代入式(1.14),我们可以得到

 \frac{\partial X(\pmb{\it\Phi})}{\partial l_{j}}=-\frac{1}{p_{a}}\left{\begin{array}{ll}{\left(1-p_{j}\right) p_{a}} & {a=j} \\{-p_{j} p_{a}} & {a \neq j}\end{array}\right.

(1.17)

 =\left{\begin{array}{ll}{-\left(1-p_{j}\right)} & {a=j} \\{p_{j}} & {a \neq j}\end{array}\right.

(1.18)

剩下的很简单。我们在式(1.12)中指出

\frac{\partial X({\pmb{\it\Phi}} )}{\partial {{b}_{j}}}=\frac{\partial {{l}_{j}}}{\partial {{b}_{j}}}\frac{\partial X(\pmb{\it\Phi} )}{\partial {{l}_{j}}}

右边第一个导数值为1。因此,损失相对于bj的导数可由式(1.14)算出。最后,使用改变权重的规则式(1.12),我们得到了更新神经网络偏置参数的规则。

 \Delta b_{j}=\mathcal{L}\left{\begin{array}{ll}{\left(1-p_{j}\right)} & {a=j} \\{-p_{j}} & {a \neq j}\end{array}\right.

(1.19)

改变权重参数的公式是式(1.19)的变体。与式(1.12)对应的权重公式为

 \frac{\partial X(\pmb{\it\Phi} )}{\partial {{w}_{i,j}}}=\frac{\partial {{l}_{j}}}{\partial {{w}_{i,j}}}\frac{\partial X(\pmb{\it\Phi} )}{\partial {{l}_{j}}}

(1.20)

首先,请注意最右边的导数与式(1.12)中的导数相同。这意味着,在权重调整阶段,当我们更改偏置时,应当保存结果以复用。右边两个导数的第一个导数估值为

 \frac{\partial {{l}_{j}}}{\partial {{w}_{i,j}}}=\frac{\partial }{\partial {{w}_{i,j}}}({{b}_{j}}+({{w}_{1}}_{,j}{{x}_{1}}+\cdots +{{w}_{i}}_{,j}{{x}_{i}}+\cdots ))={{x}_{i}} 

(1.21)

如果我们牢记偏置仅仅是一个权重,其对应的特征值恒等于1,我们就可以推导出这个公式,运用这个新的“伪权重”就可以立即从式(1.21)推导出式(1.13)。

使用这个结果可以更新权重公式。

 \Delta {{w}_{i,j}}=-\mathcal{L}{{x}_{i}}\frac{\partial X(\pmb{\it\Phi} )}{\partial {{l}_{j}}} 

(1.22)

我们现在已经推导出如何根据一个训练样本调整我们模型的参数。梯度下降算法会让我们遍历所有的训练样本,记录每个样本对参数值修正的建议,但在我们完成遍历所有样本之前,不会实际改变参数。我们通过各个样本得到的修正量加和来修改每个参数。

这个算法的缺点是非常慢,尤其是当训练集特别大的时候。我们通常需要经常调整参数,因为根据特定样本的结果,每个参数有增有减时,它们会以不同的方式相互作用。因此,在实践中,我们几乎不使用梯度下降,而是使用随机梯度下降。随机梯度下降中,每m个样本更新一次参数,然而m个样本比训练集小得多。一个典型的m值是20。这被称为批大小。

通常,批越小,学习率应该设置得越小。因为任何一个样本都会以牺牲其他样本为代价,将权重推向其正确的分类。如果学习率低,不会有太大影响,因为对参数所做的改变相对较小。相反,对于较大批量样本,我们会在m个不同的样本上取平均值,所以参数不会向单个样本的特性过于倾斜,对参数的改变可以更大。

 

图1.11 使用简单的前馈网络进行数字识别的伪代码

1.4 编写程序

我们已经全面了解了神经网络程序的内容。图1.11所示是伪代码,其中第一行内容是我们的首要操作——初始化模型参数。有些情况下我们可以将初始值设为0,就像我们在感知机算法中所做的那样。虽然我们在本例中可以这么设置,但可能不适用于其他情况。一般的做法是随机设置权重接近于0。还可以通过给Python随机数生成器一个种子,这样在调试时,参数可以设置为相同的初始值,就可以得到完全相同的输出。如果没有给种子,Python会使用环境中的数字作为种子,比如时间的最后几位数字。

请注意,在训练的每一次迭代中,我们首先修改参数,然后在验证集上运行模型来查看当前这组参数的表现。当在验证样本运行时,我们不会执行反向训练。如果打算将我们的程序用于某个实际用途(例如,读取邮件上的邮政编码),我们看到的样本并不是我们训练的样本,因此我们想知道程序的实际效果。我们的验证数据就是实际情况的近似。

一些经验可以派上用场。首先,像素值不要太偏离−1到1这个区间。在本例中,因为原始像素值是0到255,我们只需将它们除以255,这个过程叫作数据规范化。虽然不是硬性规定,但是将输入保持在−1到1或者0到1是有意义的,在式(1.22)中,我们可以看到这一点。观察式(1.22)可以发现,调整同一个输入对应的偏置项和权重的公式之间的区别在于后者有乘法项xi,即输入项的值。在上一节我们说过,如果我们假设偏置项只是一个权重项,其输入值总是1,那么用于更新偏置参数的方程就会从式(1.22)中消失。因此,如果我们不修改输入值,并且其中一个像素的值为255,那么我们修改权重值会是修改偏置参数的255倍。这种情况比较古怪,因为我们没有先验理由认定权重值比偏置参数需要更多的修正。

接下来要设定学习率,这是比较复杂的。这里,我们把学习率设定为0.0001。要注意,比起设置过小,设置得过大会产生更加糟糕的结果。如果设置值过大,softmax函数会得出一个数学溢出错误。再看式(1.5),首先应该想到的是分子和分母的指数。如果学习率过大,那么某个logit可能会变大,而e(≈2.7)的指数过高的时候一定会溢出。即使没有错误警告,过高的学习率也会导致程序在学习曲线的无意义区域徘徊。

因此,标准做法是在进行计算时,观察个别样本的损失。首先从第一张训练图片开始。这些数字通过神经网络输出到logit层。所有的权重和偏置都是接近0的数字(比如0.1)。这意味着所有的logit值都非常接近于0,所以所有的概率都非常接近\frac{\text{1}}{\text{10}}(见式(1.5)的讨论)。损失是正确答案概率的负自然对数,即−ln (\frac{\text{1}}{\text{10}}) ≈2.3。我们预计总体趋势是,随着训练的样本增多,单个损失会下降。但是,也有一些图片并不是那么规范,神经网络分类的确定性更低。因此,单个损失既有上升的情况也有下降的情况,这种趋势可能难以辨别。所以我们不是一次打印一个损失,而是将所有损失相加,然后比如每100批打印一次平均值。应该能够观察到平均值明显降低,虽然可能会有抖动。

回到关于学习率和其设置过高的风险讨论,太低的学习率也会降低程序收敛到一组好参数的速度。所以开始时用较低的学习率,而后尝试更大的值通常是最好的做法。

由于很多参数都在同时变化,神经网络算法很难调试。想要完成调试,需要在程序错误出现之前尽可能少地改变一些元素。首先,当我们修改权重时,如果立即再次运行相同的训练样本,损失会更少。如果损失没有减少,可能是有以下两个原因:第一,程序出现错误或学习率设置得太高;第二,不需要改变所有的权重来减少损失,可以只改变其中一个,或者一组。例如,当你第一次运行算法时,只需改变偏置。然而,单层网络中的偏置很大程度上会捕捉到不同的类别以不同的频率出现。这在Mnist数据中并不多见,所以在这种情况下,我们仅通过学习偏置并不会有太大改善。

如果程序运行正常,可以获得约91%或92%的验证数据精度(accuracy)。这个结果并不是很好,但这是一个开始。在后面的章节中,我们将学习如何达到约99%的精度。

简单神经网络有一个好处,那就是有时我们可以直接解释各个参数的值,并判断它们是否合理。在我们讨论图1.1时,像素点(8,8)是暗的,它的像素值为254,这在某种程度上可以判断图片是数字7,而不是数字1,因为数字1通常不占据左上角的空间。我们可以将这一观察转化为对权重矩阵wi,j中值的预测,其中i是像素数,j是答案数。如果像素值是从0到784,那么位置(8,8)的像素值是8×28 + 8 = 232,并且连接像素值和数字7(正确答案)的权重将是w232,7,而连接像素值和数字1的权重是w232,1。也就是说,w232,7应该比w232,1大。我们用低方差随机初始化权重的方法分别运行了几次训练程序,每一次都得到前者为正数(比如0.25),而后者为负数(比如−0.17)。

1.5 神经网络的矩阵表示

线性代数提供了另一种表示神经网络中运算的方法:使用矩阵。矩阵是元素的二维数组。在我们的例子中,这些元素是实数。矩阵的维度分别是行数和列数,所以lm列矩阵如下所示:

\emph{\textbf{X}}=\left(\begin{array}{cccc}{x_{11}} & {x_{1,2}} & {\cdots} & {x_{1, m}} \{x_{2,1}} & {x_{2,2}} & {\cdots} & {x_{2, m}} \\{\vdots} & {} & {} & {\vdots} \\{x_{1,1}} & {x_{l, 2}} & {\cdots} & {x_{l, m}}\end{array}\right)

(1.23)

矩阵的主要运算是加法和乘法。两个矩阵(必须具有相同维度)的相加是每个矩阵元素相加。也就是说,如果YZ两个矩阵相加,即X = Y + Z,那么xi,j = yi,j + zi,j

两个矩阵相乘X = YZ表示维度为×m的矩阵Y和维度为× n的矩阵Z相乘,结果是维度为l×n的矩阵X,其中,

 {{x}_{i,j}}=\sum\limits_{k=1}^{k=m}{{{y}_{i,k}}}{{z}_{k,j}} 

(1.24)

举一个例子,

 \begin{aligned}(12)\left(\begin{array}{c}{123} \{456}\end{array}\right)+(789) &=(91215)+(789) \\&=(162024)\end{aligned}

我们可以使用矩阵乘法和加法的组合来定义线性单元的运算,特别是输入特征是一个1×l的矩阵X。在前述的数字识别问题中,= 784,像素单元对应的权重是W,即wi, j是单元j的第i个权重。所以W的维度是像素数乘数字个数,即784×10。B是长度为10的偏置向量,并且

 \mathbf{\emph{\textbf{L}}}=\emph{\textbf{XW}}+\mathbf{\emph{\textbf{B}}} 

(1.25)

其中L是长度为10的logit向量。确保维度对应是使用该公式的前提。

现在,前馈Mnist模型的损失( L )可以用如下公式表示。

 \Pr (A(x))=\sigma (\mathbf{\emph{\textbf{xW}}}+\mathbf{\emph{\textbf{b}}}) 

(1.26)

 L(x)=-\log (\Pr (A(x)=a)) 

(1.27)

其中,第一个公式给出了可能类别A(x)上的概率分布,第二个公式确定了交叉熵损失。

我们也可以更简洁地表示反向传递。首先,我们引入梯度运算符。

 {{\nabla }_{\mathbf{l}}}X(\pmb{\it\Phi} )=\left( \frac{\partial X(\pmb{\it\Phi} )}{\partial {{l}_{1}}}\cdots \frac{\partial X(\pmb{\it\Phi} )}{\partial {{l}_{m}}} \right)

(1.28)

倒三角形▽xf (x )表示通过对x中的所有值取f的偏导数而得到的向量。之前我们讨论了单个lj的偏导数,这里,我们将所有l的导数定义为单个导数的向量。此外,矩阵的转置是指矩阵行和列间的转换。

 \left(\begin{array}{cccc}{x_{1,1}} & {x_{1,2}} & {\cdots} & {x_{1, m}} \{x_{2,1}} & {x_{2,2}} & {\cdots} & {x_{2, m}} \\{\vdots} & {} & {} & {\vdots} \\{x_{11}} & {x_{1,2}} & {\cdots} & {x_{l, m}}\end{array}\right)^{\rm T}=\left(\begin{array}{cccc}{x_{1,1}} & {x_{2,1}} & {\cdots} & {x_{11}} \\{x_{1,2}} & {x_{2,2}} & {\cdots} & {x_{1,2}} \\{\vdots} & {} & {} & {\vdots} \\{x_{1, m}} & {x_{2, m}} & {\cdots} & {x_{l m}}\end{array}\right)

(1.29)

有了这些,我们可以将式(1.22)改写为

 \text{ }\!\!\Delta\!\!\text{ }\mathbf{\emph{\textbf{W}}}=-\mathcal{L}{{\mathbf{\emph{\textbf{X}}}}^{T}}{{\nabla }_{\mathbf{l}}}X(\pmb{\it\Phi} ) 

(1.30)

根据公式右半部,我们将784×1矩阵乘以1×10矩阵,得到一个784×10的矩阵,这就是784×10权重矩阵W的变化量。

矩阵表示法可以清楚地描述输入层进入线性单元层生成logit,以及损失导数传回参数变化量的运算过程。除此之外,使用矩阵符号还有一个更为实用的原因。当进行大量线性单元运算时,一般线性代数运算,特别是深度学习训练可能非常耗时。而很多问题可以用矩阵符号法表示,许多编程语言都有特殊的包,允许你使用线性代数结构进行编程,并且经过优化的包也比手动编码更有效率。特别是在Python中编程,使用NumPy包及其矩阵运算操作,可以得到一个数量级的加速。

此外,线性代数还可以应用于计算机图形学和游戏程序,这就产生了称为图形处理单元即GPU的专用硬件。GPU相对于CPU较慢,但GPU有大量处理器,以及用于并行线性代数计算的软件。神经网络的一些专用语言(例如Tensorflow)有内置软件,可以感知GPU的可用性,并在不改变代码的情况下使用GPU。这通常又会使速度提升一个数量级。

在本例中,采用矩阵符号还有第三个原因。如果我们并行处理若干训练样本,专用软件包(如NumPy)和硬件(GPU)效率会更高。此外,矩阵符号法符合我们之前提出的思想:在更新模型参数之前处理m个训练样本(批大小)。通常做法是将m个训练样本全都输入矩阵中,一起运行。在式(1.25)中,我们将图片x想象成大小为1×784的矩阵,这是一个训练样本,有784个像素点。现在我们将矩阵维度变为m×784,即使不改变处理方法(必要的更改已经内置到例如NumPy和Tensorflow等软件包和语言中),矩阵仍旧能够工作。让我们来看看为什么。

首先,矩阵乘法XW中的X现在已经从1行变为m行。1行是1×784,那么m行即为m×784。根据线性代数中矩阵乘法的定义,可以看作我们对每一行进行乘法,然后将它们叠在一起,得到m×784矩阵。

在公式中加上偏置项就不像这样可以直接计算。我们说过矩阵加法要求两个矩阵具有相同的维度,但式(1.25)中的两矩阵维度不相等了,XW现在的维度大小是×10,而偏置项B的维度大小为1 ×10,需要进行改变。

NumPy和Tensorflow都有广播机制。当一些矩阵的大小不符合算术运算要求的大小时,可以对矩阵大小进行调整。比如某个矩阵维度为1×n,而我们需要的是m×n维度的矩阵,矩阵会获得由其单行或单列组成的m−1份(虚拟)副本,使维度符合要求。这使得B的维度大小变为×10,这样我们就可以将偏置项加到乘法输出m×10中的所有项上。在数字识别问题中,回忆一下当维度是1×10的时候我们如何加偏置项。每一个数字都可能是正确答案,我们对每个数字的决策增加了偏置项。现在我们也要增加偏置项,但这次是针对所有的决策和m个样本,并行增加偏置项。

1.6 数据独立性

如果独立同分布(iid)假设成立,即我们的数据是独立同分布的,那么神经网络模型会收敛到正确的解。宇宙射线的测量是一个典型的例子,射入的光线和所涉及的过程是随机和不变的。

而我们的数据很少(几乎没有)是独立同分布的——除非国家标准协会提供源源不断的新样本。训练时第一轮的数据是独立同分布的,但从第二轮开始的数据就与第一轮完全相同,独立同分布假设不成立。有时我们的数据从第二个训练样本开始就无法做到独立同分布,这在深度强化学习(第6章)中很常见,因此,在深度强化学习中,网络经常出现不稳定,导致无法收敛到正确的解,有时甚至无法收敛到任何解。我们认为,如果一个较小的数据集以非随机顺序输入数据,就可能产生灾难性的结果。

假设对于每个Mnist图片,我们都添加了第二个形状相同但黑白相反的图片,即,如果原始图片的像素值为v,则反图片的像素值为−v。我们现在在这个新的语料库上训练Mnist感知机,但是使用了不同的输入顺序(我们假设批大小是某个偶数)。在第一个顺序中,每个原始Mnist数字图片后面紧跟着它的相反版本。我们认为(我们通过实验验证了这一点),简单Mnist神经网络不会比随机猜测的结果更好。这似乎是合理的。首先,反向传递修改了原始图片权重,然后处理第二个反图片。由于反图片的输入是原始图片的负数,且其他元素完全相同,所以对反图片权重的修改完全抵消了原始图片权重的修改。因此在训练结束时,所有权重都没有改变。这也意味着没有学习,参数和随机初始化的一样。

另一方面,学习独立处理每个数据集(原始数据集和相反数据集)真的不应该有太多的困难,而且即使是学习合起来的数据集的权重,难度也不应该太大。实际上,简单地随机化输入顺序就可以使性能恢复到接近原始问题的水平。如果有10,000个样本,在这10,000个样本之后,权重已经发生了很大的变化,所以相反的图片并不能完全抵消原来的学习。但如果我们有一个无穷无尽的图片来源,并且抛硬币来随机决定原始图片或反图片输入神经网络的顺序,那么这些抵消就会完全消失。

1.7 参考文献和补充阅读

在本节和后续章节的“参考文献和补充阅读”部分,我会完成以下几点:(a)给学生指定该章节主题的后续阅读材料;(b)阐述该领域的一些重要贡献;(c)引用参考资料。我不能保证所有内容的完整性或客观性,特别针对(b)条目。在准备写这一部分时,我开始阅读神经网络的历史文章,特别是Andrey Kurenkov [Kur15][1]的一篇博客文章,以检查我的记忆是否准确(并丰富其内容)。

早期研究神经网络的一篇重要论文由McCulloch和Pitts [MP43]于1943年撰写,他们提出了本书所说的线性单元作为单个神经元的正式模型。然而,他们没有提出一个可以训练单个或多个神经元完成任务的学习算法,这类学习算法最早由Rosenblatt在其1958年的感知机论文[Ros58]中提出。然而,正如我们在文中指出的,他的算法只适用于单层神经网络。

下一个重大贡献是反向传播的发明,它适用于多层神经网络。当时,许多研究人员都在几年间独立得出了这个方法(只有当最初的论文没有引起足够的注意,其他人尚未发现问题已经解决时,这种情况才会发生)。Rumelhart、Hinton和Williams的论文结束了这一时期,他们明确指出三人的论文是对该发明的重新发现[RHW86]。三人的这篇论文来源于圣地亚哥大学的一个小组,该小组撰写了大量论文,推动了并行分布式处理(PDP)下神经网络的第二次研究热潮。这些论文组成了两卷合集,在神经网络领域颇有影响力[RMG+87]。

至于我是如何学习神经网络的,我在后面的章节中给出了更多的细节。在本章中,我要介绍一篇博客和两本书。Steven Miller [Mil15]的一篇博客用一个很棒的数字例子详细介绍了反向传播的前向传递和反向传递。还有两本我查阅过的神经网络教科书,一本是Ian Goodfellow、Yoshua Bengio和Aaron Courville的《深度学习》[GBC16],第二本是由Aurélien Géron [Gér17]编写的《机器学习实战:基于Scikit-Learn和Tensorflow》。

1.8 习题

练习1.1 思考批大小为1的前馈Mnist程序。假设我们在第一个样本训练前后观察偏置变量。如果它们设置正确(即我们的程序中没有错误),请描述你在偏置值中看到的变化。

练习1.2 假设图片有两个像素,且为二值(像素值为0或1),没有偏置参数,讨论一个二分类问题。(a)当像素值为( 0,1 )且权重为

0.2 -0.3

-0.1 0.4

时,计算前向传递的logit和概率。这里w[i,j]是第i个像素和第j个单元之间连接的权重,例如,w[0,1]在这里等于−0.3。(b)假设正确答案是1(而不是0),学习率为0.1,损失是多少?并计算反向传递时的Δw0,0。

练习1.3 当图片像素值是( 0,0 )时,回答练习1.2的问题。

练习1.4 一个同学问你:“在初等微积分中,我们通过对一个函数求微分,将结果表达式设置为0,并求解方程,找到它的最小值。既然我们的损失函数是可微的,为什么我们不用同样的方法,而使用梯度下降呢?”解释为什么不行。

练习1.5 计算以下内容。

 \left(\begin{array}{ll}{1} & {2} \{3} & {4}\end{array}\right)\left(\begin{array}{ll}{0} & {1} \\{2} & {3}\end{array}\right)+\left(\begin{array}{ll}{4} & {5}\end{array}\right) 

(1.31)

你可以假设有广播机制,这样计算时维度就相符了。

练习1.6 在本章中,我们只讨论了分类问题,对于这类问题,交叉熵是通常会选择的损失函数。在某些问题中我们希望可以用神经网络预测特定的值。例如,许多人想要这样一个程序,给定今天某只股票的价格和世界上其他影响因素,输出明天股票的价格。如果我们训练一个单层神经网络来达到这个目的,通常会使用平方误差损失。

 L(\mathbf{\emph{\textbf{X}}},{\it\pmb{\Phi}} )={{(t-l(\mathbf{\emph{\textbf{X}}},\Phi ))}^{2}} 

(1.32)

其中t是当天的实际价格,l(X,Φ)是单层神经网络Φ = {b,W}的输出。这也被称为二次损失。推导出损失对bi求导的公式。


[1] 说明:本书中的参考文献在正文中出现时,采用作者姓名首字母缩写加年份缩写的形式,如[Kur15]是指Kurenkov 于2015 年发表的文献,[MP43]是指McCulloch 和Pitts 于1943 年发表的文献,[BCP+88]是指Brown、Cocke 和Pietra 等人于1988 年发表的文献,其余类推。

本文摘自刚刚上架的《深度学习导论》

  • 人工智能深度学习经典入门书
  • 美国常青藤名校经典教材,理论与实战结合的良好典范
  • 基于TensorFlow和Python,附带习题和答案

《深度学习导论精装版》讲述了Tensorflow、前馈神经网络、卷积神经网络、词嵌入、循环神经网络、序列到序列学习、深度强化学习、无监督模型等深度学习领域的基本概念和技术,通过一系列的编程任务,向读者介绍了热门的人工智能应用,包括计算机视觉和自然语言处理等。 本书编写简明扼要,理论联系实践,每一章都包括一个编程项目、练习以及进一步阅读的参考资料。本书既可作为高校人工智能教学用书,也可供从业者入门参考。 本书要求读者熟悉线性代数、多元微积分、概率和统计知识,另外需要读者了解Python 编程。