% !Mode:: "TeX:UTF-8" % !TEX encoding = UTF-8 Unicode %---------------------------------------------------------------------------------------- % 机器翻译:统计建模与深度学习方法 % Machine Translation: Statistical Modeling and Deep Learning Methods % % Copyright 2020 % 肖桐(xiaotong@mail.neu.edu.cn) 朱靖波 (zhujingbo@mail.neu.edu.cn) %---------------------------------------------------------------------------------------- \part{神经机器翻译} %---------------------------------------------------------------------------------------- % CONFIGURATIONS %---------------------------------------------------------------------------------------- \renewcommand\figurename{图}% Figure => 图 \renewcommand\tablename{表}% Table => 表 \definecolor{ublue}{rgb}{0.152,0.250,0.545} % Color of deep blue \definecolor{ugreen}{rgb}{0,0.5,0} % Color of deep green \chapterimage{../Figures/fig-NEU-6.jpg} % Chapter heading image %---------------------------------------------------------------------------------------- % CHAPTER 9 %---------------------------------------------------------------------------------------- \chapter{人工神经网络和神经语言建模} \parinterval {\small\sffamily\bfseries{人工神经网络}}\index{人工神经网络}(Artificial Neural Networks)\index{Artificial Neural Networks}或{\small\sffamily\bfseries{神经网络}}\index{神经网络}(Neural Networks)\index{Neural Networks}是描述客观世界的一种数学模型。这种模型和生物学上的神经系统在行为上有一些相似之处,但是人们更多的是把它作为一种计算工具,而非一个生物学模型。近些年,随着机器学习领域的快速发展,人工神经网络被大量使用在对图像和自然语言的处理上。特别是,研究人员发现深层神经网络可以被成功训练后,学术界也逐渐形成了一种新的机器学习范式\ \dash \ {\small\bfnew{深度学习}}\index{深度学习}(Deep Learning\index{Deep Learning})。可以说,深度学习是近几年最受瞩目的研究领域之一,其应用也十分广泛。比如,图像识别的很多重要进展都来自深度学习模型的使用。包括机器翻译在内的很多自然语言处理任务中,深度学习也已经成为了一种标准模型。基于深度学习的表示学习方法也为自然语言处理开辟了新的思路。 \parinterval 本章将对深度学习的概念和技术进行介绍,目的是为本书后面神经机器翻译的内容进行铺垫。此外,本章也会对深度学习在语言建模方面的应用进行介绍,以便读者可以初步了解如何使用深度学习方法描述自然语言处理问题。 %---------------------------------------------------------------------------------------- % NEW SECTION %---------------------------------------------------------------------------------------- \section{深度学习与人工神经网络} \parinterval 深度学习是机器学习研究中一个非常重要的分支,其概念来源于对人工神经网络的研究:通过人工神经元之间的连接建立一种数学模型,使计算机可以像人一样进行分析、学习和推理。 \parinterval 近几年来,随着深度学习技术的广泛传播与使用,“人工智能”这个名词在有些场合下甚至与“深度学习”划上了等号。这种理解非常片面,比较准确地说,“深度学习”是实现“人工智能”的一种技术手段。但从这种现象中,深度学习的火爆情况可见一斑。深度学习的技术浪潮以惊人的速度席卷世界,也改变了很多领域的现状,在数据挖掘、自然语言处理、语音识别、图像识别等各个领域随处可见深度学习的身影。自然语言处理领域中,深度学习在很多任务中已经取得令人震撼的效果。特别是,基于深度学习的表示学习方法已经成为自然语言处理的新范式,在机器翻译任务中更是衍生出了“神经机器翻译”这样全新的模型。 %---------------------------------------------------------------------------------------- % NEW SUB-SECTION %---------------------------------------------------------------------------------------- \subsection{发展简史} \parinterval 神经网络最早出现在控制论中,随后更多地在连接主义中被提及。神经网络被提出的初衷并不是做一个简单的计算模型,而是希望将神经网络应用到一些自动控制相关的场景中。然而随着神经网络技术的持续发展,神经网络方法已经被广泛应用到各行各业的研究和实践工作中。 \parinterval 人工神经网络诞生至今,经历了多次高潮和低谷,这是任何一种技术都无法绕开的命运。然而,好的技术和方法终究不会被埋没,直到今天,神经网络和深度学习迎来了最好的时代。 %---------------------------------------------------------------------------------------- % NEW SUBSUB-SECTION %---------------------------------------------------------------------------------------- \subsubsection{1. 早期的人工神经网络和第一次寒冬} \parinterval 最初,神经网络设计的初衷是用计算模型来模拟生物大脑中神经元的运行机理,这种想法哪怕是现在看来也是十分超前的。例如,目前很多机构关注的概念\ \dash \ “类脑计算”就是希望研究人脑的运行机制及相关的计算机实现方法。然而模拟大脑这件事并没有想象中的那么简单,众所周知,生物学中对人脑机制的研究是十分困难的。因此,神经网络技术一直在摸索着前行,发展到现在,其计算过程与人脑的运行机制已经大相径庭。 \parinterval 人工神经网络的第一个发展阶段是在二十世纪40年代到70年代,这个时期的人工神经网络还停留在利用线性模型模拟生物神经元的阶段。虽然,线性模型在现在看来可能比较“简陋”,但是这类模型对后来的随机梯度下降等经典方法产生了深远影响。不过,显而易见的是,这种结构也存在着非常明显的缺陷,单层结构限制了它的学习能力,使它无法描述非线性问题,如著名的异或函数(XOR)学习问题。此后,神经网络的研究陷入了很长一段时间的低迷期。 %---------------------------------------------------------------------------------------- % NEW SUBSUB-SECTION %---------------------------------------------------------------------------------------- \subsubsection{2. 神经网络的第二次高潮和第二次寒冬} \parinterval 虽然第一代神经网络受到了打击,但是在20世纪80年代,第二代人工神经网络开始萌发新的生机。在这个发展阶段,生物属性已经不再是神经网络的唯一灵感来源,在{\small\bfnew{连接主义}}\index{连接主义}(Connectionism)\index{Connectionism}和分布式表示两种思潮的影响下,神经网络方法再次走入了人们的视线。 \vspace{0.3em} \parinterval 1)符号主义与连接主义 \vspace{0.3em} \parinterval 人工智能领域始终存在着符号主义和连接主义之争。早期的人工智能研究在认知学中被称为{\small\bfnew{符号主义}}\index{符号主义}(Symbolicism)\index{Symbolicism},符号主义认为人工智能源于数理逻辑,希望将世界万物的所有运转方式归纳成像文法一样符合逻辑规律的推导过程。符号主义的支持者们坚信基于物理符号系统(即符号操作系统)假设和有限合理性原理,就能通过逻辑推理来模拟智能。但被他们忽略的一点是,模拟智能的推理过程需要大量的先验知识支持,哪怕是在现代,生物学界也很难准确解释大脑中神经元的工作原理,因此也很难用符号系统刻画人脑逻辑。另一方面,连接主义则侧重于利用人工神经网络中神经元的连接去探索并模拟输入与输出之间存在的某种关系,这个过程不需要任何先验知识,其核心思想是“大量简单的计算单元连接到一起可以实现智能行为”,这种思想也推动了反向传播等多种神经网络方法的应用,并发展了包括长短时记忆模型在内的经典建模方法。2019年3月27日,ACM 正式宣布将图灵奖授予 Yoshua Bengio, Geoffrey Hinton 和 Yann LeCun,以表彰他们提出的概念和工作使得深度学习神经网络有了重大突破,这三位获奖人均是人工智能连接主义学派的主要代表,从这件事中也可以看出连接主义对当代人工智能和深度学习的巨大影响。 \vspace{0.3em} \parinterval 2)分布式表示 \vspace{0.3em} \parinterval 分布式表示的主要思想是“一个复杂系统的任何部分的输入都应该是多个特征共同表示的结果”,这种思想在自然语言处理领域的影响尤其深刻,它改变了刻画语言世界的角度,将语言文字从离散空间映射到多维连续空间。例如,在现实世界中,“张三”这个代号就代表着一个人。如果想要知道这个人亲属都有谁,因为有“如果A和B姓氏相同且在同一个家谱中,那么A和B是本家”这个先验知识在,在知道代号“张三”的情况下,可以得知“张三”的亲属是谁。但是如果不依靠这个先验知识,就无法得知“张三”的亲属是谁。但在分布式表示中,可以用一个实数向量,如$ (0.1,0.3,0.4) $来表示“张三”这个人,这个人的所有特征信息都包含在这个实数向量中,通过在向量空间中的一些操作(如计算距离等),哪怕没有任何先验知识的存在,也完全可以找到这个人的所有亲属。在自然语言处理中,一个单词也用一个实数向量(词向量或词嵌入)表示,通过这种方式将语义空间重新刻画,将这个离散空间转化成了一个连续空间,这时单词就不再是一个简单的词条,而是由成百上千个特征共同描述出来的,其中每个特征分别代表这个词的某个“ 方面”。 \parinterval 随着第二代人工神经网络的“脱胎换骨”,学者们又对神经网络方法燃起了希望之火,这也导致有些时候过分夸大了神经网络的能力。20世纪90年代后期,由于在语音识别、自然语言处理等应用中,人们对神经网络方法期望过高,但是结果并没有达到预期,这也让很多人丧失了对神经网络方法的信任。相反,核方法、图模型等机器学习方法取得了很好的效果,这导致神经网络研究又一次进入低谷。 %---------------------------------------------------------------------------------------- % NEW SUBSUB-SECTION %---------------------------------------------------------------------------------------- \subsubsection{3. 深度学习和神经网络方法的崛起} \parinterval 21世纪初,随着深度学习浪潮席卷世界,人工神经网络又一次出现在人们的视野中。深度学习的流行源于2006年Hinton等人成功训练了一个深度信念网络(Deep Belief Network),在深度神经网络方法完全不受重视的情况下,大家突然发现深度神经网络完全是一个魔鬼般的存在,可以解决很多当时其他方法无法解决的问题。神经网络方法终于在一次又一次的被否定后,迎来了它的春天。随之针对神经网络和深度学习的一系列研究前赴后继地展开了,延续至今。 \parinterval 回过头来看,现代深度学习的成功主要有三方面的原因: \begin{itemize} \vspace{0.5em} \item 第一,模型和算法的不断完善和改进。这是现代深度学习能够获得成功的最主要原因; \vspace{0.5em} \item 第二,并行计算能力的提升使大规模的实践成为了可能。早期的计算机设备根本无法支撑深度神经网络训练所需要的计算量,导致实践变得十分困难。而设备的进步、计算能力的提升则彻底改变了这种窘境; \vspace{0.5em} \item 第三,以Geoffrey Hinton等人为代表的学者的坚持和持续努力。 \vspace{0.5em} \end{itemize} \parinterval 另外,从应用的角度来看,数据量的快速提升和模型容量的增加也为深度学习的成功提供了条件,数据量的增加使得深度学习有了用武之地,例如,2000年以来,无论在学术研究还是在工业实践中,双语数据的使用数量都在逐年上升(如图\ref{fig:9-1}所示)。现在的深度学习模型参数量都十分巨大,因此需要大规模数据才能保证模型学习的充分性,而大数据时代的到来为训练这样的模型提供了数据基础。 %---------------------------------------------------------------------- \begin{figure}[htp] \centering \input{./Chapter9/Figures/figure-the-amount-of-data-in-a-bilingual-corpus} \caption{机器翻译系统所使用的双语数据量变化趋势} \label{fig:9-1} \end{figure} %---------------------------------------------------------------------- %---------------------------------------------------------------------------------------- % NEW SUB-SECTION %---------------------------------------------------------------------------------------- \subsection{为什么需要深度学习} \parinterval 深度神经网络提供了一种简单的学习机制,即直接学习输入与输出的关系,通常把这种机制称为{\small\bfnew{端到端学习}}\index{端到端学习}(End-to-End Learning)\index{End-to-End Learning}。与传统方法不同,端到端学习并不需要人工定义特征或者进行过多的先验性假设,所有的学习过程都是由一个模型完成。从外面看这个模型只是建立了一种输入到输出的映射,而这种映射具体是如何形成的完全由模型的结构和参数决定。这样做的最大好处是,模型可以更加“自由”的进行学习。此外,端到端学习也引发了一个新的思考\ \dash \ 如何表示问题?这也就是所谓的{\small\bfnew{表示学习}}\index{表示学习}(Representation Learning)\index{Representation Learning}问题。在深度学习时代,问题输入和输出的表示已经不再是人类通过简单地总结得到的规律,而是可以让计算机自己进行描述的一种可计算“量”,比如一个实数向量。由于这种表示可以被自动学习,因此也大大促进了计算机对语言文字等复杂现象的处理能力。 %---------------------------------------------------------------------------------------- % NEW SUBSUB-SECTION %---------------------------------------------------------------------------------------- \subsubsection{1. 端到端学习和表示学习} \parinterval 端到端学习使机器学习不再依赖传统的特征工程方法,因此也不需要繁琐的数据预处理、特征选择、降维等过程,而是直接利用人工神经网络自动从输入数据中提取、组合更复杂的特征,大大提升了模型能力和工程效率。以图\ref{fig:9-2}中的图像分类为例,在传统方法中,图像分类需要很多阶段的处理。首先,需要提取一些手工设计的图像特征,在将其降维之后,需要利用SVM等分类算法对其进行分类。与这种多阶段的流水线似的处理流程相比,端到端深度学习只训练一个神经网络,输入就是图片的像素表示,输出直接是分类类别。 %---------------------------------------------- \begin{figure}[htp] \centering \input{./Chapter9/Figures/figure-compare} \caption{特征工程{\small\sffamily\bfseries{vs}}端到端学习} \label{fig:9-2} \end{figure} %---------------------------------------------- \parinterval 传统的机器学习需要人工定义特征,这个过程往往需要对问题的隐含假设。这种方法存在三方面的问题: \begin{itemize} \vspace{0.5em} \item 特征的构造需要耗费大量的时间和精力。在传统机器学习的特征工程方法中,特征提取都是基于人力完成的,该过程往往依赖于大量的先验假设,会导致相关系统的研发周期也大大增加; \vspace{0.5em} \item 最终的系统性能强弱非常依赖特征的选择。有一句话在业界广泛流传:“数据和特征决定了机器学习的上限”,但是人的智力和认知是有限的,因此人工设计的特征的准确性和覆盖度会存在瓶颈; \vspace{0.5em} \item 通用性差。针对不同的任务,传统机器学习的特征工程方法需要选择出不同的特征,在某个任务上表现很好的特征在其他任务上可能没有效果。 \vspace{0.5em} \end{itemize} \parinterval 端到端学习将人们从大量的特征提取工作之中解放出来,可以不需要太多人的先验知识。从某种意义上讲,对问题的特征提取完全是自动完成的,这也意味着即使系统开发者不是该任务的“专家”也可以完成相关系统的开发。此外,端到端学习实际上也隐含了一种新的对问题的表示形式\ $\dash$分布式表示。 在这种框架下,模型的输入可以被描述为分布式的实数向量,这样模型可以有更多的维度描述一个事物,同时避免传统符号系统对客观事物离散化的刻画。比如,在自然语言处理中,表示学习重新定义了什么是词,什么是句子。在本章后面的内容中也会看到,表示学习可以让计算机对语言文字的描述更加准确和充分。 %---------------------------------------------------------------------------------------- % NEW SUBSUB-SECTION %---------------------------------------------------------------------------------------- \subsubsection{2. 深度学习的效果} \parinterval 相比于传统的基于特征工程的方法,基于深度学习的模型更加方便、通用,在系统性能上也普遍更优。这里以语言建模任务为例。语言建模的目的是开发一个模型来描述词串出现的可能性(见{\chaptertwo})。这个任务已经有着很长时间的历史。表\ref{tab:9-1}给出了不同方法在常用的PTB数据集上的困惑度结果 \footnote{困惑度越低表明语言建模的效果越好。} 。传统的$ n$-gram语言模型由于面临维度灾难和数据稀疏问题,最终语言模型的性能并不是很好。而在深度学习模型中,通过引入循环神经网络等结构,所得到的语言模型可以更好地描述序列生成的问题。而最新的基于Transformer架构的语言模型将PPL从最初的178.0 下降到了惊人的35.7。可见深度学习为这个任务带来的进步是巨大的。 %---------------------------------------------------------------------------------------------------- \begin{table}[htp] \centering \caption{不同方法在PTB语言建模任务上的困惑度(PPL)} \label{tab:9-1} \small \begin{tabular}{l | l l l} \rule{0pt}{15pt} 模型 & 作者 & 年份 & PPL \\ \hline \rule{0pt}{15pt} 3-gram LM\upcite{brown1992class} & Brown et al. & 1992 & 178.0 \\ \rule{0pt}{15pt} Feed-forward Neural LM\upcite{bengio2003a} & Bengio et al. & 2003 & 162.2 \\ \rule{0pt}{15pt} Recurrent NN-based LM\upcite{mikolov2010recurrent} & Mikolov et al. & 2010 & 124.7 \\ \rule{0pt}{15pt} Recurrent NN-LDA\upcite{mikolov2012context} & Mikolov et al. & 2012 & 92.0 \\ \rule{0pt}{15pt} LSTM \upcite{zaremba2014recurrent}& Zaremba et al. & 2014 & 78.4 \\ \rule{0pt}{15pt} RHN\upcite{zilly2016recurrent} & Zilly et al. & 2016 & 65.4 \\ \rule{0pt}{15pt} AWD-LSTM\upcite{merity2017regularizing} & Merity et al. & 2018 & 58.8 \\ \rule{0pt}{15pt} GPT-2 (Transformer)\upcite{radford2019language} & Radford et al. & 2019 & 35.7 \\ \end{tabular} \end{table} %---------------------------------------------------------------------------------------------------- %---------------------------------------------------------------------------------------- % NEW SECTION %---------------------------------------------------------------------------------------- \sectionnewpage \section{神经网络基础} \parinterval 神经网络是一种由大量的节点(或称神经元)之间相互连接构成的计算模型。那么什么是神经元?神经元之间又是如何连接的?神经网络的数学描述又是什么样的?这一节将围绕这些问题系统地对神经网络的基础知识进行介绍。 %---------------------------------------------------------------------------------------- % NEW SUB-SECTION %---------------------------------------------------------------------------------------- \subsection{线性代数基础} \label{sec:9.2.1} \parinterval 线性代数作为一个数学分支,广泛应用于科学和工程中,神经网络的数学描述中也大量使用了线性代数工具。因此,这里对线性代数的一些概念进行简要介绍,以方便后续对神经网络进行数学描述。 %---------------------------------------------------------------------------------------- % NEW SUBSUB-SECTION %---------------------------------------------------------------------------------------- \subsubsection{1. 标量、向量和矩阵} \vspace{-0.5em} \parinterval {\small\sffamily\bfseries{标量}}\index{标量}(Scalar)\index{Scalar}:标量亦称“无向量”,是一种只具有数值大小而没有方向的量,通俗地说,一个标量就是一个单独的数,这里特指实数\footnote{严格意义上,标量可以是复数等其他形式。这里为了方便讨论,仅以实数为对象。}。比如,对于$ a=5 $,$ a $就是一个标量。 \parinterval {\small\sffamily\bfseries{向量}}\index{向量}(Vector)\index{Vector}:向量是由一组实数组成的有序数组。与标量不同,向量既有大小也有方向。可以把向量看作空间中的点,每个元素是不同坐标轴上的坐标。公式\eqref{eq:9-1}和公式\eqref{eq:9-2}分别展示了一个行向量和一个列向量: \begin{eqnarray} \mathbi{a} &=& \begin{pmatrix} 1 & 2 & 5 & 7 \end{pmatrix}\label{eq:9-1}\\ \nonumber \\ \mathbi{a}^{\textrm T} &=& \begin{pmatrix} &1& \\ &2&\\ &5& \\ &7&\end{pmatrix} \label{eq:9-2} \end{eqnarray} \parinterval 本章默认使用行向量,如$ \mathbi{a}=(a_1, a_2, a_3) $,$ \mathbi{a} $对应的列向量记为$ \mathbi{a}^{\textrm T} $。 \parinterval {\small\sffamily\bfseries{矩阵}}\index{矩阵}(Matrix)\index{Matrix}:矩阵是一个按照长方阵列排列的实数集合,最早来自于方程组的系数及常数所构成的方阵。在计算机领域,通常将矩阵看作二维数组。这里用符号$ \mathbi{A}$表示一个矩阵,如果该矩阵有$ m $行$ n $列,那么有$\mathbi{A}\in {\mathbb R}^{m\times n} $。矩阵中的每个元素都被一个行索引和一个列索引所确定,例如,$ a_{ij} $表示第$ i $行、第$ j $列的矩阵元素。如下,下式中的$ \mathbi{A} $定义了一个2行2列的矩阵。 \begin{eqnarray} \mathbi{A}& = & \begin{pmatrix} a_{11} & a_{12}\\ a_{21} & a_{22} \end{pmatrix} \nonumber \\ & = & \begin{pmatrix} 1 & 2\\ 3 & 4 \end{pmatrix} \label{eq:9-3} \end{eqnarray} %---------------------------------------------------------------------------------------- % NEW SUBSUB-SECTION %---------------------------------------------------------------------------------------- \subsubsection{2. 矩阵的转置} \parinterval {\small\sffamily\bfseries{转置}}\index{转置}(Transpose)\index{Transpose}是矩阵的重要操作之一。矩阵的转置可以看作是将矩阵以对角线为镜像进行翻转:假设$\mathbi{A}$为$ m $行$ n $列的矩阵,第$ i $行、第$ j $ 列的元素是$ a_{ij} $,即:$\mathbi{A}={(a_{ij})}_{m\times n} $,把$ m\times n $矩阵$\mathbi{A}$的行换成同序数的列得到一个$ n\times m $矩阵,则得到$ \mathbi{A}$的转置矩阵,记为${\mathbi{A}}^{\textrm T} $,且${\mathbi{A}}^{\textrm T}={(a_{ji})}_{n\times m} $。例如,对于下式中的矩阵, \begin{eqnarray} \mathbi{A} & = & \begin{pmatrix} 1 & 3 & 2 & 6\\5 & 4 & 8 & 2\end{pmatrix} \label{eq:9-100} \end{eqnarray} \noindent 它转置的结果如下: \begin{eqnarray} {\mathbi{A}}^{\textrm T} & = &\begin{pmatrix} 1 & 5\\3 & 4\\2 & 8\\6 & 2\end{pmatrix} \label{eq:9-101} \end{eqnarray} \parinterval 向量可以看作只有一行(列)的矩阵。对应地,向量的转置可以看作是只有一列(行)的矩阵。标量可以看作是只有一个元素的矩阵。因此,标量的转置等于它本身,即$ a^{\textrm T}=a $。 %---------------------------------------------------------------------------------------- % NEW SUBSUB-SECTION %---------------------------------------------------------------------------------------- \subsubsection{3. 矩阵加法和数乘} \parinterval 矩阵加法又被称作{\small\sffamily\bfseries{按元素加法}}\index{按元素加法}(Element-wise Addition)\index{Element-wise Addition}。它是指两个矩阵把其相对应元素加在一起的运算,通常的矩阵加法被定义在两个形状相同的矩阵上。两个$ m\times n $矩阵$ \mathbi{A}$和$ \mathbi{B} $的和,标记为$ \mathbi{A} + \mathbi{B}$,它也是个$ m\times n $矩阵,其内的各元素为其相对应元素相加后的值,即如果矩阵$ {\mathbi{C}}= {\mathbi{A}} + {\mathbi{B}} $,则$ c_{ij} = a_{ij} + b_{ij} $。下式展示了矩阵之间进行加法的计算过程。 \begin{eqnarray} \begin{pmatrix} 1 & 3\\ 1 & 0\\ 1 & 2 \end{pmatrix}\;\;+\;\;\begin{pmatrix} 0 & 0\\ 7 & 5\\ 2 & 1 \end{pmatrix}&=&\begin{pmatrix} 1+0 & 3+0\\ 1+7 & 0+5\\ 1+2 & 2+1 \end{pmatrix}\;\;=\;\;\begin{pmatrix} 1 & 3\\ 8 & 5\\ 3 & 3 \end{pmatrix} \label{eq:9-4} \end{eqnarray} \parinterval 矩阵加法满足以下运算规律: \begin{itemize} \vspace{0.5em} \item 交换律:$ {\mathbi{A}}+{\mathbi{B}} = {\mathbi{B}} +{\mathbi{A}} $。 \vspace{0.5em} \item 结合律:$ ({\mathbi{A}}+{\mathbi{B}})+{\mathbi{C}}= {\mathbi{A}}+({\mathbi{B}}+{\mathbi{C}}) $。 \vspace{0.5em} \item $ {\mathbi{A}}+{\mathbi{0}}={\mathbi{A}} $,其中$ {\mathbi{0}} $指的是零矩阵,即元素皆为0的矩阵。 \vspace{0.5em} \item $ {\mathbi{A}}+(-{\mathbi{A}})={\mathbi{0}} $,其中$ -{\mathbi{A}} $是矩阵$ {\mathbi{A}} $的负矩阵,即将矩阵$ {\mathbi{A}} $的每个元素取负得到的矩阵。 \vspace{0.5em} \end{itemize} \parinterval 矩阵的{\small\bfnew{数乘}}\index{数乘}(Scalar Multiplication)\index{Scalar Multiplication}是指标量(实数)与矩阵的乘法运算,计算过程是将标量与矩阵的每个元素相乘,最终得到与原矩阵形状相同的矩阵。例如,矩阵$ {\mathbi{A}}={(a_{ij})}_{m\times n} $与标量$ k $进行数乘运算,其结果矩阵$ {\mathbi{B}}={(ka_{ij})}_{m\times n} $,即$ k{(a_{ij})}_{m\times n}={(ka_{ij})}_{m\times n} $。公式\eqref{eq:9-102}和\eqref{eq:9-5}展示了矩阵数乘的计算过程: \begin{eqnarray} {\mathbi{A}} & = & \begin{pmatrix} 3 & 2 & 7\\ 5 & 8 & 1 \end{pmatrix} \label{eq:9-102} \\ \nonumber \\ 2{\mathbi{A}} & = & \begin{pmatrix} 6 & 4 & 14\\ 10 & 16 & 2 \end{pmatrix} \label{eq:9-5} \end{eqnarray} \parinterval 矩阵的数乘满足以下运算规律,其中$ k $和$ l $是实数,$ {\mathbi{A}} $和$ {\mathbi{B}} $是形状相同的矩阵: \begin{itemize} \vspace{0.5em} \item 右分配律:$ k({\mathbi{A}}+{\mathbi{B}})=k{\mathbi{A}}+k{\mathbi{B}} $。 \vspace{0.5em} \item 左分配律:$ (k+l){\mathbi{A}}=k{\mathbi{A}}+l{\mathbi{A}}$。 \vspace{0.5em} \item 结合律:$ (kl){\mathbi{A}}=k(l{\mathbi{A}}) $。 %\vspace{0.5em} \end{itemize} %---------------------------------------------------------------------------------------- % NEW SUBSUB-SECTION %---------------------------------------------------------------------------------------- \subsubsection{4. 矩阵乘法和矩阵点乘} \parinterval 矩阵乘法是矩阵运算中最重要的操作之一,为了与矩阵点乘区分,通常也把矩阵乘法叫做矩阵叉乘。假设$ {\mathbi{A}} $为$ m\times p $的矩阵,$ {\mathbi{B}} $为$ p\times n $的矩阵,对$ {\mathbi{A}}$和$ {\mathbi{B}} $作矩阵乘法的结果是一个$ m\times n $的矩阵$ {\mathbi{C}} $,其中矩阵$ {\mathbi{C}} $中第$ i $行、第$ j $列的元素可以表示为: \begin{eqnarray} {({\mathbi{A}}{\mathbi{B}})}_{ij} &=& \sum_{k=1}^p a_{ik}b_{kj} \label{eq:9-6} \end{eqnarray} \parinterval 只有当第一个矩阵的列数与第二个矩阵的行数相等时,两个矩阵才可以作矩阵乘法。公式\eqref{eq:9-7}展示了矩阵乘法的运算过程,若${\mathbi{A}}=\begin{pmatrix}a_{11} & a_{12}& a_{13}\\a_{21} & a_{22} & a_{23}\end{pmatrix}$,${\mathbi{B}}=\begin{pmatrix}b_{11} & b_{12}\\b_{21} & b_{22}\\b_{31} & b_{32}\end{pmatrix} $,则有: \vspace{-0.5em} \begin{eqnarray} {\mathbi{C}} & = & {\mathbi{A}}{\mathbi{B}} \nonumber \\ & = & \begin{pmatrix} a_{11}b_{11}+a_{12}b_{21}+a_{13}b_{31} & a_{11}b_{12}+a_{12}b_{22}+a_{13}b_{32}\\ a_{21}b_{11}+a_{22}b_{21}+a_{23}b_{31} & a_{21}b_{12}+a_{22}b_{22}+a_{23}b_{32} \end{pmatrix} \label{eq:9-7} \end{eqnarray} \parinterval 矩阵乘法满足以下运算规律: \begin{itemize} \vspace{0.5em} \item 结合律:若${\mathbi{A}}\in {\mathbb R}^{m\times n} $,$ {\mathbi{B}}\in {\mathbb R}^{n\times p} $,${\mathbi{C}}\in {\mathbb R}^{p\times q} $,则$ ({\mathbi{A}}{\mathbi{B}}){\mathbi{C}}={\mathbi{A}}({\mathbi{B}}{\mathbi{C}}) $。 \vspace{0.5em} \item 左分配律:若$ {\mathbi{A}}\in {\mathbb R}^{m\times n} $,$ {\mathbi{B}}\in {\mathbb R}^{m\times n} $,$ {\mathbi{C}}\in {\mathbb R}^{n\times p} $,则$ ({\mathbi{A}}+{\mathbi{B}}){\mathbi{C}}={\mathbi{A}}{\mathbi{C}}+{\mathbi{B}}{\mathbi{C}} $。 \vspace{0.5em} \item 右分配律:若$ {\mathbi{A}}\in {\mathbb R}^{m\times n} $,$ {\mathbi{B}}\in {\mathbb R}^{n\times p} $,$ {\mathbi{C}}\in {\mathbb R}^{n\times p} $,则$ {\mathbi{A}}({\mathbi{B}}+{\mathbi{C}})={\mathbi{A}}{\mathbi{B}}+{\mathbi{A}}{\mathbi{C}} $。 \vspace{0.5em} \end{itemize} \begin{spacing}{1.4} \parinterval 可以将线性方程组用矩阵乘法表示,如对于线性方程组$ \begin{cases} 5x_1+2x_2=y_1\\3x_1+x_2=y_2\end{cases} $,可以表示为$ {\mathbi{A}}{\mathbi{x}}^{\textrm T}={\mathbi{y}}^{\textrm T}$,其中$ {\mathbi{A}} = \begin{pmatrix} 5 & 2\\3 & 1\end{pmatrix} $,$ {\mathbi{x}}^{\textrm T} = \begin{pmatrix} x_1\\x_2\end{pmatrix} $,$ {\mathbi{y}}^{\textrm T} = \begin{pmatrix} y_1\\y_2\end{pmatrix} $。 \end{spacing} \parinterval 矩阵的点乘就是两个形状相同的矩阵各个对应元素相乘,矩阵点乘也被称为{\small\bfnew{按元素乘积}}\index{按元素乘积}(Element-wise Product)\index{Element-wise Product}或Hadamard乘积,记为${\mathbi{A}}\odot {\mathbi{B}}$。例如,对于公式\eqref{eq:9-103}和公式\eqref{eq:9-104}所示的两个矩阵, \begin{eqnarray} {\mathbi{A}} &=& \begin{pmatrix} 1 & 0\\ -1 & 3 \end{pmatrix} \label{eq:9-103} \\ \nonumber \\ {\mathbi{B}} &=& \begin{pmatrix} 3 & 1\\ 2 & 1 \end{pmatrix} \label{eq:9-104} \end{eqnarray} \parinterval 矩阵点乘的计算方式如下: \begin{eqnarray} {\mathbi{C}} & = & {\mathbi{A}}\odot {\mathbi{B}} \nonumber \\ & = & \begin{pmatrix} 1\times 3 & 0\times1\\ -1\times2 & 3\times1 \end{pmatrix} \label{eq:9-8} \end{eqnarray} %---------------------------------------------------------------------------------------- % NEW SUBSUB-SECTION %---------------------------------------------------------------------------------------- \subsubsection{5. 线性映射} \parinterval {\small\sffamily\bfseries{线性映射}}\index{线性映射}( Linear Mapping)\index{Linear Mapping}或{\small\sffamily\bfseries{线性变换}}\index{线性变换}(Linear Transformation)\index{Linear Transformation}是一个向量空间V到另一个向量空间W的映射函数$ f:v\rightarrow w$,且该映射函数保持加法运算和数量乘法运算,即对于空间V中任何两个向量$ {\mathbi{u}} $ 和$ {\mathbi{v}} $以及任何标量$ c $,始终符合公式\eqref{eq:9-9}和公式\eqref{eq:9-10}: \begin{eqnarray} f({\mathbi{u}}+{\mathbi{v}})&=&f({\mathbi{u}})+f({\mathbi{v}})\label{eq:9-9}\\ f(c{\mathbi{v}})&=&cf({\mathbi{v}}) \label{eq:9-10} \end{eqnarray} \parinterval 利用矩阵$ {\mathbi{A}}\in {\mathbb R}^{m\times n} $,可以实现两个有限维欧氏空间的映射函数$f:{\mathbb R}^n\rightarrow {\mathbb R}^m$。例如$ n $维列向量$ {\mathbi{x}} ^{\textrm T}$与$ m\times n $的矩阵$ {\mathbi{A}} $,向量$ {\mathbi{x}} ^{\textrm T}$左乘矩阵$ {\mathbi{A}} $,可将向量$ {\mathbi{x}} ^{\textrm T}$映射为$ m $列向量。公式\eqref{eq:9-11}\eqref{eq:9-12}\eqref{eq:9-13}展示了一个具体的例子, \begin{eqnarray} {\mathbi{x}}^{\textrm{T}} & = & {\begin{pmatrix} x_1\\ x_2 \\ \dots \\ x_n \end{pmatrix}} \label{eq:9-11} \end{eqnarray} \begin{eqnarray} {\mathbi{A}}&=& \begin{pmatrix} a_{11} & a_{12} & \dots & a_{1n}\\ a_{21} & \dots & \dots & \dots \\ \dots & \dots & \dots & \dots \\ a_{m1} & \dots & \dots & a_{mn} \end{pmatrix} \label{eq:9-12} \end{eqnarray} \parinterval 可以得到: \begin{eqnarray} {\mathbi{y}}^{\textrm{T}}& = &{\mathbi{A}}{\mathbi{x}}^{\textrm{T}} \nonumber \\ & = & \begin{pmatrix} a_{11}x_{1}+a_{12}x_{2}+\dots+a_{1n}x_{n}\\ a_{21}x_{1}+a_{22}x_{2}+\dots+a_{2n}x_{n}\\ \vdots \\ a_{m1}x_{1}+a_{m2}x_{2}+\dots+a_{mn}x_{n} \label{eq:9-13}\end{pmatrix} \end{eqnarray} \parinterval 上例中矩阵$ {\mathbi{A}} $定义了一个从$ {\mathbb R}^n $到$ {\mathbb R}^m $的线性映射:向量$ {\mathbi{x}}^{\textrm{T}}\in {\mathbb R}^n $和$ {\mathbi{y}}^{\textrm{T}}\in {\mathbb R}^m $分别为两个空间中的列向量,即大小为$ n\times 1 $ 和$ m\times 1 $ 的矩阵。 %---------------------------------------------------------------------------------------- % NEW SUBSUB-SECTION %---------------------------------------------------------------------------------------- \subsubsection{6. 范数} \parinterval 工程领域,经常会使用被称为{\small\bfnew{范数}}\index{范数}(Norm)\index{Norm}的函数衡量向量大小,范数为向量空间内的所有向量赋予非零的正长度或大小。对于一个$n$维向量$ {\mathbi{x}} $,一个常见的范数函数为$ l_p $ 范数,通常表示为$ {\Vert{\mathbi{x}}\Vert}_p $ ,其中$p\ge 0$,是一个标量形式的参数。常用的$ p $的取值有$ 1 $、$ 2 $、$ \infty $等。范数的计算方式如下: \begin{eqnarray} l_p({\mathbi{x}}) & = & {\Vert{\mathbi{x}}\Vert}_p \nonumber \\ & = & {\left (\sum_{i=1}^{n}{{\vert x_{i}\vert}^p}\right )}^{\frac{1}{p}} \label{eq:9-14} \end{eqnarray} \parinterval $ l_1 $范数为向量的各个元素的绝对值之和: \begin{eqnarray} {\Vert{\mathbi{x}}\Vert}_1&=&\sum_{i=1}^{n}{\vert x_{i}\vert} \label{eq:9-15} \end{eqnarray} \parinterval $ l_2 $范数为向量的各个元素平方和的二分之一次方: \begin{eqnarray} {\Vert{\mathbi{x}}\Vert}_2&=&\sqrt{\sum_{i=1}^{n}{{x_{i}}^2}} \nonumber \\ &=&\sqrt{{\mathbi{x}}^{\textrm T}{\mathbi{x}}} \label{eq:9-16} \end{eqnarray} \parinterval $ l_2 $范数被称为{\small\bfnew{欧几里得范数}}\index{欧几里得范数}(Euclidean Norm)\index{Euclidean Norm}。从几何角度,向量也可以表示为从原点出发的一个带箭头的有向线段,其$ l_2 $范数为线段的长度,也常被称为向量的模。$ l_2 $ 范数在机器学习中非常常用。向量$ {\mathbi{x}} $的$ l_2 $范数经常简化表示为$ \Vert{\mathbi{x}}\Vert $,可以通过点积$ {\mathbi{x}}^{\textrm T}{\mathbi{x}} $进行计算。 \parinterval $ l_{\infty} $范数为向量的各个元素的最大绝对值: \begin{eqnarray} {\Vert{\mathbi{x}}\Vert}_{\infty}&=&{\textrm{max}}\{x_1,x_2,\dots,x_n\} \label{eq:9-17} \end{eqnarray} \parinterval 广义上讲,范数是将向量映射到非负值的函数,其作用是衡量向量$ {\mathbi{x}} $到坐标原点的距离。更严格的说,范数并不拘于$ l_p $范数,任何一个同时满足下列性质的函数都可以作为范数: \begin{itemize} \vspace{0.5em} \item 若$ f({\mathbi{x}})=0 $,则$ {\mathbi{x}}={\mathbi{0}} $。 \vspace{0.5em} \item 三角不等式:$ f({\mathbi{x}}+{\mathbi{y}})\leqslant f({\mathbi{x}})+f({\mathbi{y}}) $。 \vspace{0.5em} \item 任意实数$ \alpha $,$ f(\alpha {\mathbi{x}})=\vert \alpha \vert f({\mathbi{x}}) $。 \vspace{0.5em} \end{itemize} \parinterval 在深度学习中,有时候希望衡量矩阵的大小,这时可以考虑使用 {\small\bfnew{Frobenius 范数}}\index{Frobenius 范数}(Frobenius Norm)\index{Frobenius Norm},其计算方式如下: \begin{eqnarray} {\Vert{\mathbi{A}}\Vert}_F&=&\sqrt{\sum_{i,j} a_{i,j}^2} \label{eq:9-18} \end{eqnarray} %---------------------------------------------------------------------------------------- % NEW SUB-SECTION %---------------------------------------------------------------------------------------- \subsection{人工神经元和感知机} \parinterval 生物学中,神经元是神经系统的基本组成单元。同样,人工神经元是人工神经网络的基本单元。在人们的想象中,人工神经元应该与生物神经元类似。但事实上,二者在形态上是有明显差别的。如图\ref{fig:9-4} 是一个典型的人工神经元,其本质是一个形似$ y=f({\mathbi{x}}\cdot {\mathbi{w}}+b) $的函数。显而易见,一个神经元主要由$ {\mathbi{x}} $,$ {\mathbi{w}} $,$ b $,$ f $四个部分构成。其中$ {\mathbi{x}} $是一个形如$ (x_1,x_2,\dots,x_n) $ 的实数向量,在一个神经元中担任“输入”的角色。$ {\mathbi{w}} $通常被理解为神经元连接的{\small\sffamily\bfseries{权重}}\index{权重}(Weight)\index{Weight}(对于一个人工神经元,权重是一个向量,表示为$ {\mathbi{w}} $;对于由多个神经元组成的神经网络,权重是一个矩阵,表示为$ {\mathbi{W}} $),其中的每一个元素都对应着一个输入和一个输出,代表着“某输入对某输出的贡献程度”。$ b $被称作偏置(对于一个人工神经元,偏置是一个实数,表示为$b$;对于神经网络中的某一层,偏置是一个向量,表示为${\mathbi{b}}$)。$ f $被称作激活函数,用于对输入向量各项加权和后进行某种变换。可见,一个人工神经元的功能是将输入向量与权重矩阵右乘(做内积)后,加上偏置量,经过一个激活函数得到一个标量结果。 %---------------------------------------------- \begin{figure}[htp] \centering \input{./Chapter9/Figures/figure-artificial-neuron} \caption{人工神经元} \label{fig:9-4} \end{figure} %---------------------------------------------- %---------------------------------------------------------------------------------------- % NEW SUBSUB-SECTION %---------------------------------------------------------------------------------------- \vspace{-0.5em} \subsubsection{1. 感知机\ \dash \ 最简单的人工神经元模型} \vspace{0.5em} \parinterval 感知机是人工神经元的一种实例,在上世纪50-60年代被提出后,对神经网络研究产生了深远的影响。感知机模型如图\ref {fig:9-5}所示,其输入是一个$n$维二值向量$ {\mathbi{x}}=(x_1,x_2,\dots,x_n) $,其中$ x_i=0 $或$ 1 $。权重${\mathbi{w}}=(w_1,w_2,\dots,w_n) $,每个输入变量对应一个权重$ w_i $。偏置$ b $是一个实数变量($ -\sigma $)。输出也是一个二值结果,即$ y=0 $或$ 1 $。$ y $值的判定由输入的加权和是否大于(或小于)一个阈值$ \sigma $决定: \begin{eqnarray} y&=&\begin{cases} 0 & \sum_{i}{x_i\cdot w_i}-\sigma <0\\1 & \sum_{i}{x_i\cdot w_i}-\sigma \geqslant 0\end{cases} \label{eq:9-19} \end{eqnarray} %---------------------------------------------- \begin{figure}[htp] \centering \input{./Chapter9/Figures/figure-perceptron-mode} \caption{感知机模型} \label{fig:9-5} \end{figure} %------------------------------------------- \parinterval 感知机可以做一些简单的决策。举一个非常简单的例子,有一场音乐会,你正在纠结是否去参加,有三个因素会影响你的决定: \begin{itemize} \vspace{0.5em} \item $ x_1 $:剧场是否离你足够近(是,则$ x_1=1 $;否则$ x_1=0 $); \vspace{0.5em} \item $ x_2 $:票价是否低于300元(是,则$ x_2=1 $;否则$ x_2=0 $); \vspace{0.5em} \item $ x_3 $:女朋友是否喜欢音乐会(是,则$ x_3=1 $;否则$ x_3=0 $)。 \vspace{0.5em} \end{itemize} \parinterval 在这种情况下应该如何做出决定呢?比如,女朋友很希望和你一起去看音乐会,但是剧场很远而且票价500元,如果这些因素对你都是同等重要的(即$ w_1=w_2=w_3 $,假设这三个权重都设置为1)那么会得到一个综合得分: \begin{eqnarray} x_1\cdot w_1+x_2\cdot w_2+x_3\cdot w_3 & = & 0\cdot 1+0\cdot 1+1\cdot 1 \nonumber \\ & = & 1 \label{eq:9-20} \end{eqnarray} \parinterval 如果你不是十分纠结的人,能够接受不完美的事情,你可能会把$ \sigma $设置为1,于是$ \sum{w_i\cdot x_i}-\sigma \ge 0 $,那么你会去音乐会。可以看出,上面的例子的本质就是一个如图\ref{fig:9-6}所示的感知机: %---------------------------------------------- \begin{figure}[htp] \centering \input{./Chapter9/Figures/figure-perceptron-to-predict-1} \caption{预测是否去剧场的感知机(权重相同)} \label{fig:9-6} \end{figure} %------------------------------------------- %---------------------------------------------------------------------------------------- % NEW SUBSUB-SECTION %---------------------------------------------------------------------------------------- \vspace{-1em} \subsubsection{2. 神经元内部权重} \parinterval 在上面的例子中,连接权重代表着每个输入因素对最终输出结果的重要程度,为了得到令人满意的决策,需要不断调整权重。如果你是守财奴,则会对票价看得更重一些,这样你会用不均匀的权重计算每个因素的影响,比如:$ w_1=0.5 $,$ w_2=2 $,$ w_3=0.5 $,此时感知机模型如图\ref{fig:9-7}所示。在这种情况下,女友很希望和你一起去看音乐会,但是剧场很远而且票价500元,会导致你不去看音乐会,该决策过程如下: \begin{eqnarray} \sum_{i}{x_i\cdot w_i} & = & 0\cdot 0.5+0\cdot 2+1\cdot 0.5 \nonumber \\ & = & 0.5 \nonumber \\ & < & \sigma = 1 \label{eq:9-21} \end{eqnarray} %---------------------------------------------- \begin{figure}[htp] \centering \input{./Chapter9/Figures/figure-perceptron-to-predict-2} \caption{预测是否去剧场的感知机(权重不同)} \label{fig:9-7} \end{figure} %------------------------------------------- \parinterval 当然,结果是女友对这个决定非常不满意。 %---------------------------------------------------------------------------------------- % NEW SUBSUB-SECTION %---------------------------------------------------------------------------------------- \subsubsection{3. 神经元的输入\ \dash \ 离散 vs 连续} \parinterval 在受到了女友“批评教育”之后,你意识到决策考虑的因素(即输入)不应该只是非0即1,而应该把“程度”考虑进来,于是你改变了三个输入的形式: \parinterval $ x_1 $:10/距离(km) \parinterval $ x_2 $:150/票价(元) \parinterval $ x_3 $:女朋友是否喜欢 \parinterval 在新修改的模型中,$ x_0 $和$ x_1 $变成了连续变量,$ x_2 $仍然是离散变量,如图\ref{fig:9-8}所示。 %---------------------------------------------- \begin{figure}[htp] \centering \input{./Chapter9/Figures/figure-different-forms-of-neuronal-input} \caption{神经元输入的不同形式} \label{fig:9-8} \end{figure} %------------------------------------------- \parinterval 使用修改后的模型做决策:女朋友很希望和你一起,但是剧场有20km远而且票价有500元。于是有$ x_1=10/20 $,$ x_2=150/500 $,$ x_3=1 $。此时决策过程如下: \begin{eqnarray} \sum_{i}{x_i\cdot w_i} & = & 0.5\cdot 0.5+0.3\cdot 2+1\cdot 0.5 \nonumber \\ & = & 1.35 \nonumber \\ & > & \sigma =1 \label{eq:9-22} \end{eqnarray} \parinterval 虽然剧场很远,价格有点贵,但是女友很满意,你还是很高兴。 %---------------------------------------------------------------------------------------- % NEW SUBSUB-SECTION %---------------------------------------------------------------------------------------- \subsubsection{4. 神经元内部的参数学习} \parinterval 一次成功的音乐会之后,你似乎掌握了一个真理:其他什么都不重要,女友的喜好最重要,所以你又将决策模型的权重做出了调整:最简单的方式就是$ w_1=w_2=0 $,同时令$ w_3>0 $,相当于只考虑$ x_3 $的影响而忽略其他因素,于是你得到了如图\ref {fig:9-9}所示的决策模型: %---------------------------------------------- \begin{figure}[htp] \centering \input{./Chapter9/Figures/figure-perceptron-to-predict-3} \caption{预测是否去剧场的决策模型(只考虑女友喜好)} \label{fig:9-9} \end{figure} %------------------------------------------- \parinterval 很快又有了一场音乐会,距你1000公里,票价3000元,当然女友是一直喜欢音乐会的。根据新的决策模型,你义无反顾地选择去看音乐会。然而,女友又不高兴了,喜欢浪漫的女友觉得去看这场音乐会太奢侈了。在这几次看音乐会的经历中,你发现每个因素的权重需要准确地设置才能达到最好的决策效果。 \parinterval 那么如何确定最好的权重的?方法其实很简单,不断地尝试,根据结果不断地调整权重。在经过成百上千次的尝试之后,终于找到了一组合适的权重,使每次决策的正确率都很高。上面这个过程就类似于参数训练的过程,利用大量的数据来模拟成百上千次的尝试,根据输出的结果来不断地调整权重。 \parinterval 可以看到,在“是否参加音乐会”这个实际问题中,主要涉及到三方面的问题: \begin{itemize} \vspace{0.5em} \item 对问题建模,即定义输入$ \{x_i\} $的形式; \vspace{0.5em} \item 设计有效的决策模型,即定义$ y $; \vspace{0.5em} \item 得到模型参数(如权重$ \{w_i\} $)的最优值。 \vspace{0.5em} \end{itemize} \parinterval 上面的例子对这三个问题都简要地做出了回答。下面的内容将继续对它们进行详细阐述。 %---------------------------------------------------------------------------------------- % NEW SUB-SECTION %---------------------------------------------------------------------------------------- \subsection{多层神经网络} \parinterval 感知机是一种最简单的单层神经网络。一个很自然的问题是:能否把多个这样的网络叠加在一起,获得建模更复杂问题的能力?如果可以,那么在多层神经网络的每一层,神经元之间是怎么组织、工作的呢?单层网络又是通过什么方式构造成多层的呢? %---------------------------------------------------------------------------------------- % NEW SUBSUB-SECTION %---------------------------------------------------------------------------------------- \subsubsection{1. 线性变换和激活函数} \parinterval 为了建立多层神经网络,首先需要把前面提到的简单的神经元进行扩展,把多个神经元组成一“层”神经元。比如,很多实际问题需要同时有多个输出,这时可以把多个相同的神经元并列起来,每个神经元都会有一个单独的输出,这就构成一“层”,形成了单层神经网络。单层神经网络中的每一个神经元都对应着一组权重和一个输出,可以把单层神经网络中的不同输出看作一个事物不同角度的描述。 \parinterval 举个简单的例子,预报天气时,往往需要预测温度、湿度和风力,这就意味着如果使用单层神经网络进行预测,需要设置3个神经元。如图\ref{fig:9-10}所示,此时权重矩阵如下: \begin{eqnarray} {\mathbi{W}}&=&\begin{pmatrix} w_{11} & w_{12} & w_{13}\\ w_{21} & w_{22} & w_{23}\end{pmatrix} \label{eq:9-105} \end{eqnarray} %---------------------------------------------- \begin{figure}[htp] \centering \input{./Chapter9/Figures/figure-corresponence-between-matrix-element-and-output} \caption{权重矩阵中的元素与输出的对应关系} \label{fig:9-10} \end{figure} %------------------------------------------- \noindent 它的第一列元素$ \begin{pmatrix} w_{11}\\ w_{21}\end{pmatrix} $是输入相对第一个输出$ y_1 $ 的权重,参数向量$ {\mathbi{b}}=(b_1,b_2,b_3) $的第一个元素$ b_1 $是对应于第一个输出$ y_1 $ 的偏置量;类似的,可以得到$ y_2 $和$ y_3 $。预测天气的单层模型如图\ref{fig:9-11}所示(在本例中,假设输入$ {\mathbi{x}}=(x_1,x_2) $)。 %---------------------------------------------- \begin{figure}[htp] \centering \input{./Chapter9/Figures/figure-single-layer-of-neural-network-for-weather-prediction} \caption{预测天气的单层神经网络} \label{fig:9-11} \end{figure} %------------------------------------------- \parinterval 在神经网络中,对于输入向量$ {\mathbi{x}}\in {\mathbb R}^m $,一层神经网络首先将其经过线性变换映射到$ {\mathbb R}^n $,再经过激活函数变成${\mathbi{y}}\in {\mathbb R}^n $。还是上面天气预测的例子,每个神经元获得相同的输入,权重矩阵$ {\mathbi{W}} $是一个$ 2\times 3 $矩阵,矩阵中每个元素$ w_{ij} $代表第$ j $个神经元中$ x_{i} $对应的权重值,假设编号为1的神经元负责预测温度,则$ w_{i1} $的含义为预测温度时输入$ x_{i} $对其影响程度。此外所有神经元的偏置$ b_{1} $,$ b_{2} $,$ b_{3} $组成了最终的偏置向量$ {\mathbi{b}}$。在该例中则有,权重矩阵$ {\mathbi{W}}=\begin{pmatrix} w_{11} & w_{12} & w_{13}\\ w_{21} & w_{22} & w_{23}\end{pmatrix} $,偏置向量$ {\mathbi{b}}=(b_1,b_2,b_3) $。 \parinterval 那么,线性变换的本质是什么? \begin{itemize} \vspace{0.5em} \item 从代数角度看,对于线性空间$ \textrm V $,任意$ {\mathbi{a}}$,${\mathbi{a}}\in {\textrm V} $和数域中的任意$ \alpha $,线性变换$ T(\cdot) $需满足:$ T({\mathbi{a}}+{\mathbi{b}})=T({\mathbi{a}})+T({\mathbi{b}}) $,且$ T(\alpha {\mathbi{a}})=\alpha T({\mathbi{a}}) $; \vspace{0.5em} \item 从几何角度看,公式中的${\mathbi{x}}\cdot {\mathbi{W}}+{\mathbi{b}}$将${\mathbi{x}}$右乘${\mathbi{W}}$相当于对$ {\mathbi{x}} $进行旋转变换。例如,对三个点$ (0,0) $,$ (0,1) $,$ (1,0) $及其围成的矩形区域右乘如下矩阵: \begin{eqnarray} {\mathbi{W}}&=&\begin{pmatrix} 1 & 0 & 0\\ 0 & -1 & 0\\ 0 & 0 & 1\end{pmatrix} \label{eq:9-106} \end{eqnarray} 这样,矩形区域由第一象限旋转90度到了第四象限,如图\ref{fig:9-13}第一步所示。公式$ {\mathbi{x}}\cdot {\mathbi{W}}+{\mathbi{b}}$中的公式中的${\mathbi{b}}$相当于对其进行平移变换。其过程如图\ref{fig:9-13} 第二步所示,偏置矩阵$ {\mathbi{b}}=\begin{pmatrix} 0.5 & 0 & 0\\ 0 & 0 & 0\\ 0 & 0 & 0\end{pmatrix} $将矩形区域沿$x$轴向右平移了一段距离。 \vspace{0.5em} \end{itemize} %---------------------------------------------- \begin{figure}[htp] \centering \input{./Chapter9/Figures/figure-translation} \caption{线性变换示意图} \label{fig:9-13} \end{figure} %------------------------------------------- \parinterval 也就是说,线性变换提供了对输入数据进行空间中旋转、平移的能力。当然,线性变换也适用于更加复杂的情况,这也为神经网络提供了拟合不同函数的能力。比如,可以利用线性变换将三维图形投影到二维平面上,或者将二维平面上的图形映射到三维空间。如图\ref{fig:9-14}所示,通过一个简单的线性变换,可以将三维图形投影到二维平面上。 \vspace{-0.5em} %---------------------------------------------- \begin{figure}[htp] \centering \input{./Chapter9/Figures/figure-linear-transformation} \caption{线性变换3维$ \rightarrow $2维数学示意} \label{fig:9-14} \end{figure} %------------------------------------------- \vspace{-0.5em} \parinterval 那激活函数又是什么?一个神经元在接收到经过线性变换的结果后,通过激活函数的处理,得到最终的输出$ y $。激活函数的目的是解决实际问题中的非线性变换,线性变换只能拟合直线,而激活函数的加入,使神经网络具有了拟合曲线的能力。 特别是在实际问题中,很多现象都无法用简单的线性关系描述,这时可以使用非线性激活函数来描述更加复杂的问题。常见的非线性激活函数有Sigmoid、ReLU、Tanh等。图\ref{fig:9-15}中列举了几种激活函数的形式。 %---------------------------------------------- \begin{figure}[htp] \centering \input{./Chapter9/Figures/figure-activate} \caption{几种常见的激活函数} \label{fig:9-15} \end{figure} %------------------------------------------- %---------------------------------------------------------------------------------------- % NEW SUBSUB-SECTION %---------------------------------------------------------------------------------------- \vspace{-0.5em} \subsubsection{2. 单层神经网络$\rightarrow$多层神经网络} \parinterval 单层神经网络由线性变换和激活函数两部分构成,但在实际问题中,单层网络并不能很好地拟合复杂函数。因此很自然地想到将单层网络扩展到多层神经网络,即深层神经网络。将一层神经网络的最终输出向量作为另一层神经网络的输入向量,通过这种方式可以将多个单层神经网络连接在一起。 %---------------------------------------------- \begin{figure}[htp] \centering \input{./Chapter9/Figures/figure-four-layers-of-neural-network} \caption{三层神经网络} \label{fig:9-17} \end{figure} %------------------------------------------- \parinterval 在多层神经网络中,通常包括输入层、输出层和至少一个隐藏层。图\ref{fig:9-17}展示了一个三层神经网络,包括输入层\footnote{由于输入层不存在神经元,因此在计算神经网络层数时不将其包括在内。}、输出层和两个隐藏层。\\ %---------------------------------------------------------------------------------------- % NEW SUB-SECTION %---------------------------------------------------------------------------------------- \subsection{函数拟合能力} \parinterval 神经网络方法之所以受到青睐一方面是由于它提供了端到端学习的模式,另一方面是由于它强大的函数拟合能力。理论上说,神经网络可以拟合任何形状的函数。下面就来看一下为什么神经网络会有这样的能力。 \parinterval 众所周知,单层神经网络无法解决线性不可分问题,比如经典的异或问题。但是具有一个隐藏层的两层神经网络在理论上就可以拟合所有的函数了。接下来我们分析一下为什么仅仅是多了一层,神经网络就能变得如此强大。对于二维空间(平面),“拟合”是指:把平面上一系列的点,用一条光滑的曲线连接起来,并用函数来表示这条拟合的曲线。这个概念可以推广到更高维空间上。在用神经网络解决问题时,可以通过拟合训练数据中的“ 数据点”来获得输入与输出之间的函数关系,并利用其对未知数据做出判断。可以假设输入与输出之间存在一种函数关系,而神经网络的“拟合”是要尽可能地逼近原函数输出值,与原函数输出值越逼近,则意味着拟合得越好。 \parinterval 如图\ref{fig:9-18}是一个以Sigmoid作为隐藏层激活函数的两层神经网络。通过调整参数$ {\mathbi{W}}^{[1]}=(w_{11},w_{12}) $,$ {\mathbi{b}}=(b_1,b_2) $和$ {\mathbi{W}}^{[2]}={(w'_{11},w'_{21})}^{\textrm{T}} $的值,可以不断地改变目标函数的形状。 %---------------------------------------------- \begin{figure}[htp] \centering \input{./Chapter9/Figures/figure-two-layer-neural-network} \caption{以Sigmoid作为隐藏层激活函数的两层神经网络} \label{fig:9-18} \end{figure} %------------------------------------------- \parinterval 设置$ w'_{11}=1 $,$ w_{11}=1 $,$ b_1=0 $,其他参数设置为0。可以得到如图\ref{fig:9-19}(a)所示的目标函数,此时目标函数还是比较平缓的。通过调大$ w_{11} $,可以将图\ref{fig:9-19}(a) 中函数的坡度调得更陡:当$ w_{11}=10 $时,如图\ref{fig:9-19}(b)所示,目标函数的坡度与图\ref{fig:9-19}(a)相比变得更陡了;当$ w_{11}=100 $时,如图\ref{fig:9-19}(c)所示,目标函数的坡度变得更陡、更尖锐,已经逼近一个阶梯函数。 %---------------------------------------------- \begin{figure}[htp] \centering \input{./Chapter9/Figures/figure-weight} \caption{通过调整权重$ w_{11} $改变目标函数平滑程度} \label{fig:9-19} \end {figure} %------------------------------------------- \parinterval 设置$ w'_{11}=1 $,$ w_{11}=100 $,$ b_1=0 $,其他参数设置为0。可以得到如图\ref{fig:9-20}(a)所示的目标函数,此时目标函数是一个阶梯函数,其“阶梯”恰好与y轴重合。通过改变$ b_1 $,可以将整个函数沿x轴向左右平移:当$ b_1=-2 $时,如图\ref{fig:9-20}(b)所示,与图\ref{fig:9-20}(a)相比目标函数的形状没有发生改变,但其位置沿x轴向右平移;当$ b_1=-4 $时,如图\ref{fig:9-20}(c)所示,目标函数的位置继续沿x轴向右平移。 %---------------------------------------------- \begin{figure}[htp] \centering \input{./Chapter9/Figures/figure-bias} \caption{通过调整偏置量$ b_1 $改变目标函数位置} \label{fig:9-20} \end {figure} %------------------------------------------- \parinterval 设置$ w'_{11}=1 $,$ w_{11}=100 $,$ b_1=-4 $,其他参数设置为0。可以得到如图\ref{fig:9-21}\\(a)所示的目标函数,此时目标函数是一个阶梯函数,该阶梯函数取得最大值的分段处为$ y=1 $。 通过改变$ w'_{11} $,可以将目标函数“拉高”或是“压扁”。如图\ref{fig:9-21}(b)和(c)所示,目标函数变得 “扁”了。最终,该阶梯函数取得最大值的分段处约为$ y=0.7 $。 %---------------------------------------------- \begin{figure}[htp] \centering \input{./Chapter9/Figures/figure-w1} \caption{通过改变权重$ w'_{11} $将目标函数“拉高”或“压扁”} \label{fig:9-21} \end {figure} %------------------------------------------- \parinterval 设置$ w'_{11}=0.7 $,$ w_{11}=100 $,$ b_1=-4 $,其他参数设置为0。可以得到如图\ref{fig:9-22}(a)所示的目标函数,此时目标函数是一个阶梯函数。若是将其他参数设置为$ w'_{21}=0.7 $,$ w'_{11}=100 $,$ b_2=16 $,由图\ref{fig:9-22}(b)可以看出,原来目标函数的“阶梯”由一级变成了两级,由此可以推测,将第二组参数进行设置,可以使目标函数分段数增多;若将第二组参数中的$ w'_{21} $由原来的$ 0.7 $设置为$ -0.7 $,可得到如图\ref{fig:9-22}(c)所示的目标函数,与图\ref{fig:9-22}(b)相比,原目标函数的“第二级阶梯”向下翻转,由此可见${\mathbi{W}}^{[2]}$的符号决定了目标函数的翻转方向。 %---------------------------------------------- \begin{figure}[htp] \centering \input{./Chapter9/Figures/figure-w2} \caption{通过设置第二组参数($b_2$和$w'_{21}$)将目标函数分段数增加} \label{fig:9-22} \end {figure} %------------------------------------------- \parinterval 由上面的内容,已经看到通过设置神经元中的参数将目标函数的形状做各种变换,但是看起来目标函数的类型还是比较单一的。而在实际问题中,输入与输出之间的函数关系甚至复杂到无法人为构造或是书写,神经网络又是如何拟合这种复杂的函数关系的呢? %---------------------------------------------- \begin{figure}[htp] \centering \input{./Chapter9/Figures/figure-piecewise} \caption{将目标函数作分段处理} \label{fig:9-23} \end {figure} %------------------------------------------- \parinterval 以如图\ref{fig:9-23}(a)所示的目标函数为例,为了拟合该函数,可以将其看成分成无数小段的分段函数,如图\ref{fig:9-23}(b)所示。 \parinterval 如图\ref{fig:9-24}(a)所示,上例中两层神经网络的函数便可以拟合出目标函数的一小段。为了使两层神经网络可以拟合出目标函数更多的一小段,需要增加隐层神经元的个数。如图\ref{fig:9-24}(b),将原本的两层神经网络神经元个数增多一倍,由2个神经元扩展到4个神经元,其函数的分段数也增加一倍,而此时的函数恰好可以拟合目标函数中的两个小段。以此类推,理论上,该两层神经网络便可以通过不断地增加隐层神经元数量去拟合任意函数。 %---------------------------------------------- \begin{figure}[htp] \centering \input{./Chapter9/Figures/figure-fit} \caption{扩展隐层神经元个数去拟合目标函数更多的“一小段”} \label{fig:9-24} \end {figure} %------------------------------------------- \parinterval 两层神经元的神经网络在理论上可以拟合所有函数了,但是在实际问题中所使用的神经网络都远远超过了两层,这也是对深度学习这个概念中“深度”的一种体现。使用深层神经网络主要有以下几方面的原因: \begin{itemize} \vspace{0.5em} \item 使用较浅的神经网络去拟合一个比较复杂的函数关系,需要数量极其庞大的神经元和参数,训练难度大。在上面的例子中可以看出,两层神经元仅仅拟合目标函数的两小段,其隐层就需要4个神经元。从另一个角度说,加深网络也可能会达到与宽网络(更多神经元)类似的效果。 \vspace{0.5em} \item 更多层的网络可以提供更多的线性变换和激活函数,对输入的抽象程度更好,因而可以更好的表示数据的特征。 \vspace{0.5em} \end{itemize} \parinterval 在本书后面的内容中还会看到,深层网络在机器翻译中可以带来明显的性能提升。 %---------------------------------------------------------------------------------------- % NEW SECTION %---------------------------------------------------------------------------------------- \sectionnewpage \section{神经网络的张量实现} \parinterval 在神经网络内部,输入经过若干次变换,最终得到输出的结果。这个过程类似于一种逐层的数据“流动”。不禁会产生这样的疑问:在神经网络中,数据是以哪种形式“流动”的?如何去编程实现这种数据“流动”呢? \parinterval 为了解决上面的问题,本节将介绍人工神经网络更加通用的描述形式 \ \dash \ 张量计算。随后也会看到,使用基于张量的数学工具,可以方便的搭建神经网络。 %---------------------------------------------------------------------------------------- % NEW SUB-SECTION %---------------------------------------------------------------------------------------- \subsection{ 张量及其计算} %---------------------------------------------------------------------------------------- % NEW SUBSUB-SECTION %---------------------------------------------------------------------------------------- \subsubsection{1. 张量} \parinterval 对于神经网络中的某层神经元${\mathbi{y}}=f({\mathbi{x}}\cdot {\mathbi{W}}+{\mathbi{b}}) $,其中$ {\mathbi{W}} $是权重矩阵,例如$ \begin{pmatrix} 1 & 2\\ 3 & 4\end{pmatrix} $,${\mathbi{b}} $ 是偏置向量,例如$ (1,3) $。在这里,输入$ {\mathbi{x}} $和输出$ {\mathbi{y}} $,可以不是简单的向量或是矩阵形式,而是深度学习中更加通用的数学量\ \dash \ {\small\bfnew{张量}}\index{张量}(Tensor)\index{Tensor},比如公式\eqref{eq:9-107}中的几种情况都可以看作是深度学习中定义数据的张量: \begin{eqnarray} {\mathbi{x}}&=&\begin{pmatrix} -1 & 3\end{pmatrix}\qquad {\mathbi{x}}\;\;=\;\;\begin{pmatrix} -1 & 3\\ 0.2 & 2\end{pmatrix}\qquad {\mathbi{x}}\;\;=\;\;\begin{pmatrix}{\begin{pmatrix} -1 & 3\\ 0.2 & 2\end{pmatrix}}\\{\begin{pmatrix} -1 & 3\\ 0.2 & 2\end{pmatrix}}\end{pmatrix} \label{eq:9-107} \end{eqnarray} \parinterval 简单来说,张量是一种通用的工具,用于描述由多个数据构成的量。比如,输入的量有三个维度在变化,用矩阵不容易描述,但是用张量却很容易。 \parinterval 从计算机实现的角度来看,现在所有深度学习框架都把张量定义为“多维数组”。张量有一个非常重要的属性\ \dash \ {\small\bfnew{阶}}\index{阶}(Rank)\index{Rank}。可以将多维数组中“维”的属性与张量的“阶”的属性作类比,这两个属性都表示多维数组(张量)有多少个独立的方向。例如,3是一个标量,相当于一个0维数组或0阶张量;$ {(\begin{array}{cccc} 2 & -3 & 0.8 & 0.2\end{array})}^{\textrm T} $ 是一个向量,相当于一个1维数组或1阶张量;$ \begin{pmatrix} -1 & 3 & 7\\ 0.2 & 2 & 9\end{pmatrix} $是一个矩阵,相当于一个2维数组或2阶张量;如图\ref{fig:9-25}所示,这是一个3 维数组或3阶张量,其中,每个$4 \times 4$的方形代表一个2阶张量,这样的方形有4个,最终形成3阶张量。 %---------------------------------------------- \begin{figure}[htp] \centering \input{./Chapter9/Figures/figure-tensor-sample} \caption{3阶张量示例($4 \times 4 \times 4$)} \label{fig:9-25} \end{figure} %------------------------------------------- \parinterval 虽然这里所使用的张量是出于编程实现的视角,但是数学中张量有严格的定义。从数学上看“张量并不是向量和矩阵的简单扩展,多维数组也并不是张量所必须的表达形式”。从某种意义上说,矩阵才是张量的扩展。当然,这个逻辑可能和人们在深度学习中的认知是不一致的。但是,本书仍然遵循深度学习中常用的概念,把张量理解为多维数组。在保证数学表达的简洁性的同时,使程序实现接口更加统一。 %---------------------------------------------------------------------------------------- % NEW SUBSUB-SECTION %---------------------------------------------------------------------------------------- \subsubsection{2. 张量的矩阵乘法} \parinterval 对于一个单层神经网络,$ {\mathbi{y}}=f({\mathbi{x}}\cdot{\mathbi{W}}+{\mathbi{b}}) $中的${\mathbi{x}}\cdot {\mathbi{W}} $表示对输入${\mathbi{x}} $进行线性变换,其中${\mathbi{x}}$是输入张量,$ {\mathbi{W}}$是权重矩阵。$ {\mathbi{x}}\cdot {\mathbi{W}} $表示的是矩阵乘法,需要注意的是这里是矩阵乘法而不是张量乘法。 \parinterval 张量乘以矩阵是怎样计算呢?可以先回忆一下\ref{sec:9.2.1}节的线性代数的知识。假设$ {\mathbi{A}} $为$ m\times p $的矩阵,$ {\mathbi{B}} $为$ p\times n $的矩阵,对${\mathbi{A}} $ 和${\mathbi{B}}$ 作矩阵乘积的结果是一个$ m\times n $的矩阵${\mathbi{C}}$,其中矩阵${\mathbi{C}}$中第$ i $行、第$ j $列的元素可以表示为: \begin{eqnarray} {({\mathbi{A}}{\mathbi{B}})}_{ij}&=&\sum_{k=1}^{p}{a_{ik}b_{kj}} \label{eq:9-24} \end{eqnarray} \noindent 例如$ {\mathbi{A}}= \begin{pmatrix} a_{11} & a_{12} & a_{13}\\a_{21} & a_{22} & a_{23}\end{pmatrix} $,$ {\mathbi{B}}= \begin{pmatrix} b_{11} & b_{12}\\b_{21} & b_{22}\\b_{31} & b_{32}\end{pmatrix} $,两矩阵做乘法运算的过程如下: \begin{eqnarray} {\mathbi{C}} & = & {\mathbi{A}}{\mathbi{B}} \nonumber \\ & = & \begin{pmatrix} a_{11}b_{11}+a_{12}b_{21}+a_{13}b_{31} & a_{11}b_{12}+a_{12}b_{22}+a_{13}b_{32}\\a_{21}b_{11}+a_{22}b_{21}+a_{23}b_{31} & a_{21}b_{12}+a_{22}b_{22}+a_{23}b_{32}\end{pmatrix} \label{eq:9-108} \end{eqnarray} \parinterval 将矩阵乘法扩展到高阶张量中:一个张量${\mathbi{x}}$若要与矩阵$ {\mathbi{W}}$做矩阵乘法,则$ {\mathbi{x}} $的最后一维度需要与${\mathbi{W}}$的行数大小相等,即:若张量${\mathbi{x}} $的形状为$ \cdot \times n $,${\mathbi{W}} $须为$ n\times \cdot $的矩阵。下式是一个例子: \begin{eqnarray} {\mathbi{x}}(1:4,1:4,{\red{1:4}})\;\;\times\;\; {{\mathbi{W}}({\red{1:4}},1:2)}&=&{\mathbi{s}}(1:4,1:4,1:2) \label{eq:9-25} \end{eqnarray} \noindent 其中,张量${\mathbi{x}} $沿第1阶所在的方向与矩阵$ {\mathbi{W}}$进行矩阵运算(张量${\mathbi{x}}$第1阶的每个维度都可以看做一个$ 4\times 4 $的矩阵)。图\ref{fig:9-27}演示了这个计算过程。张量${\mathbi{x}}$中编号为\ding{172}的子张量(可看作矩阵)与矩阵${\mathbi{W}}$进行矩阵乘法,其结果对应张量$ {\mathbi{s}}$中编号为\ding{172}的子张量。这个过程会循环四次,因为有四个这样的矩阵(子张量)。最终,图\ref{fig:9-27}给出了结果张量的形式($ 4 \times 4 \times 2 $)。 %---------------------------------------------- \begin{figure}[htp] \centering \input{./Chapter9/Figures/figure-tensor-mul} \caption{张量与矩阵的矩阵乘法} \label{fig:9-27} \end {figure} %------------------------------------------- %---------------------------------------------------------------------------------------- % NEW SUBSUB-SECTION %---------------------------------------------------------------------------------------- \subsubsection{3. 张量的单元操作} \vspace{0.5em} \parinterval 对于神经网络中的某层神经元$ {\mathbi{y}}=f({\mathbi{x}}\cdot {\mathbi{W}}+{\mathbi{b}}) $,也包含有其他张量单元操作:1)加法:$ {\mathbi{s}}+{\mathbi{b}}$,其中张量$ {\mathbi{s}}={\mathbi{x}}\cdot {\mathbi{W}} $;2)激活函数:$ f(\cdot) $。具体来说: \begin{itemize} \vspace{0.5em} \item $ {\mathbi{s}}+{\mathbi{b}} $中的单元加就是对张量中的每个位置都进行加法。在上例中$ {\mathbi{s}} $是形状为$ (1:4,1:4,1:2) $的3阶张量,而$ {\mathbi{b}}$是含有4个元素的向量,在形状不同的情况下是怎样进行单元加的呢?在这里需要引入{\small\sffamily\bfseries{广播机制}}\index{广播机制}(Broadcast Mechanism\index{Broadcast Mechanism}):如果两个数组的后缘维度(即从末尾开始算起的维度)的轴长度相符或其中一方的长度为1,则认为它们是广播兼容的。广播会在缺失或长度为1的维度上进行,它是深度学习框架中常用的计算方式。来看一个具体的例子,如图\ref{fig:9-28}所示,$ {\mathbi{s}} $是一个$ 2\times 4 $的矩阵而$ {\mathbi{b}} $是一个长度为4的向量,这两者进行单元加运算时,广播机制会将$ {\mathbi{b}} $沿第一个维度复制后,再与$ {\mathbi{s}} $做加法运算。 %---------------------------------------------- \begin{figure}[htp] \centering \input{./Chapter9/Figures/figure-broadcast} \caption{广播机制} \label{fig:9-28} \end {figure} %------------------------------------------- \vspace{0.5em} \item 除了单位加之外,张量之间也可以使用减法操作、乘法操作。此外也可以对张量作激活操作,这里将其称作为函数的{\small\bfnew{向量化}}\index{向量化}(Vectorization)\index{Vectorization}。例如,对向量(1阶张量)作ReLU激活,ReLU激活函数表达式如下: \begin{eqnarray} f(x)&=&\begin{cases} 0 & x\le 0 \\x & x>0\end{cases} \label{eq:9-26} \end{eqnarray} \vspace{-0.5em} 例如$ {\textrm{ReLU}}\left( \begin{pmatrix} 2\\-.3\end{pmatrix}\right)=\begin{pmatrix} 2\\0\end{pmatrix} $。 \vspace{0.5em} \end{itemize} %---------------------------------------------------------------------------------------- % NEW SUB-SECTION %---------------------------------------------------------------------------------------- \subsection{张量的物理存储形式} \parinterval 在深度学习世界中,张量就是多维数组。因此,张量的物理存储方式也与多维数组相同。如下就是一些实例: \begin{itemize} \vspace{0.5em} \item 张量$ {\mathbi{t}}(1:3) $表示一个含有三个元素的向量(1阶张量),其物理存储如图\ref{fig:9-29}(a)所示。 \vspace{0.5em} \item 张量$ {\mathbi{t}}(1:2,1:3) $表示一个$ 2\times 3 $的矩阵(2阶张量),其物理存储如图\ref{fig:9-29}(b)所示。 \vspace{0.5em} \item 张量${\mathbi{t}}(1:2,1:2,1:3) $表示一个大小$ 2\times 2\times 3 $的3阶张量,其物理存储如图\ref{fig:9-29}(c)所示。 \vspace{0.5em} \end{itemize} %---------------------------------------------- \begin{figure}[htp] \centering \input{./Chapter9/Figures/figure-save} \caption{不同阶的张量的物理存储方式} \label{fig:9-29} \end{figure} %------------------------------------------- \parinterval 高阶张量的物理存储方式与多维数组在C++、Python中的物理存储方式相同。 %---------------------------------------------------------------------------------------- % NEW SUB-SECTION %---------------------------------------------------------------------------------------- \subsection{张量的实现手段} \parinterval 实现神经网络的开源系统有很多,比如,使用经典的Python工具包Numpy。也可以使用成熟的深度学习框架,比如,Tensorflow和Pytorch就是非常受欢迎的深度学习工具包,除此之外还有很多其他优秀的框架:CNTK、MXNet、PaddlePaddle、\\Keras、Chainer、dl4j、NiuTensor等。开发者可以根据自身的喜好和开发项目的要求选择所采用的框架。 \parinterval 这里以NiuTensor为例对张量计算库进行简单介绍。这类库需要提供张量计算接口,如张量的声明、定义和张量的各种代数运算,各种单元算子,如$ + $、$ - $、$ \ast $、$ / $、Log (取对数)、Exp (指数运算)、Power(幂方运算)、Absolute(绝对值)等,还有Sigmoid、Softmax等激活函数。除了上述单元算子外,张量计算库还支持张量之间的高阶运算,其中最常用的是矩阵乘法。表\ref{tab:9-2} 展示了一些常用的函数。 %-------------------------------------------------------------------- \begin{table}[htp] \centering \caption{NiuTensor支持的部分函数} \label{tab:9-2} \small \begin{tabular}{l | l} \rule{0pt}{15pt} 函数 & 描述 \\ \hline \rule{0pt}{15pt} \texttt{a.Reshape(o,s)} & 把张量$ {\mathbi{a}} $变换成阶为o、形状为s的张量 \\ \rule{0pt}{15pt} \texttt{a.Get(pos)} & 取张量$ {\mathbi{a}} $中位置为pos的元素 \\ \rule{0pt}{15pt} \texttt{a.Set(v,pos)} & 把张量$ {\mathbi{a}} $中位置为pos的元素值设为v \\ \rule{0pt}{15pt} \texttt{a.Dump(file)} & 把张量$ {\mathbi{a}} $存到file中,file为文件句柄 \\ \rule{0pt}{15pt} \texttt{a.Read(file)} & 从file中读取张量$ {\mathbi{a}} $,file为文件句柄 \\ \rule{0pt}{15pt} \texttt{Power(a,p)} & 计算指数$ a^p $ \\ \rule{0pt}{15pt} \texttt{Linear(a,s,b)} & 计算${\mathbi{a}}\ast s+b $,s和b都是一个实数 \\ \rule{0pt}{15pt} \texttt{CopyValue(a)} & 构建张量$ {\mathbi{a}} $的一个拷贝 \\ \rule{0pt}{15pt} \texttt{ReduceMax(a,d)} & 对张量$ {\mathbi{a}} $沿着方向d进行规约,得到最大值 \\ \rule{0pt}{15pt} \texttt{ReduceSum(a,d)} & 对张量$ {\mathbi{a}} $沿着方向d进行规约,得到和 \\ \rule{0pt}{15pt} \texttt{Concatenate(a,b,d)} & 把两个张量$ {\mathbi{a}} $和$ {\mathbi{b}} $沿d方向级联 \\ \rule{0pt}{15pt} \texttt{Merge(a,d)} & 对张量$ {\mathbi{a}} $沿d方向合并 \\ \rule{0pt}{15pt} \texttt{Split(a,d,n)} & 对张量$ {\mathbi{a}} $沿d方向分裂成n份 \\ \rule{0pt}{15pt} \texttt{Sigmoid(a)} & 对张量${\mathbi{a}}$进行Sigmoid变换 \\ \rule{0pt}{15pt} \texttt{Softmax(a)} & 对张量$ {\mathbi{a}} $进行Softmax变换,沿最后一个方向 \\ \rule{0pt}{15pt} \texttt{HardTanh(a)} & 对张量$ {\mathbi{a}} $进行HardTanh变换(双曲正切的近似) \\ \rule{0pt}{15pt} \texttt{Rectify(a)} & 对张量$ {\mathbi{a}} $进行ReLU变换 \\ \end{tabular} \end{table} %-------------------------------------------------------------------- %---------------------------------------------------------------------------------------- % NEW SUB-SECTION %---------------------------------------------------------------------------------------- \subsection{前向传播与计算图} \parinterval 有了张量这个工具,可以很容易地实现任意的神经网络。反过来,神经网络都可以被看作是张量的函数。一种经典的神经网络计算模型是:给定输入张量,通过各个神经网络层所对应的张量计算之后,最后得到输出张量。这个过程也被称作{\small\sffamily\bfseries{前向传播}}\index{前向传播}(Forward Propagation\index{Forward Propagation}),它常常被应用在使用神经网络对新的样本进行推断中。 \parinterval 来看一个具体的例子,图\ref{fig:9-37}展示了一个根据天气情况判断穿衣指数(穿衣指数是人们穿衣薄厚的依据)的过程,将当天的天空状况、低空气温、水平气压作为输入,通过一层神经元在输入数据中提取温度、风速两方面的特征,并根据这两方面的特征判断穿衣指数。需要注意的是,在实际的神经网络中,并不能准确地知道神经元究竟可以提取到哪方面的特征,以上表述是为了让读者更好地理解神经网络的建模过程和前向传播过程。这里将上述过程建模为如图\ref{fig:9-37}所示的两层神经网络。 %---------------------------------------------- \begin{figure}[htp] \centering \input{./Chapter9/Figures/figure-weather} \caption{判断穿衣指数问题的神经网络过程} \label{fig:9-37} \end{figure} %------------------------------------------- \parinterval 它可以被描述为公式\eqref{eq:9-27},其中隐藏层的激活函数是Tanh函数,输出层的激活函数是Sigmoid函数,${\mathbi{W}}^{[1]}$和${\mathbi{b}}^{[1]}$分别表示第一层的权重矩阵和偏置,${\mathbi{W}}^{[2]}$和$b^{[2]}$分别表示第二层的权重矩阵和偏置\footnote{注意这里${\mathbi{b}}^{[1]}$是向量而$b^{[2]}$是标量,因而前者加粗后者未加粗}: \begin{eqnarray} y&=&{\textrm{Sigmoid}}({\textrm{Tanh}}({\mathbi{x}}\cdot {\mathbi{W}}^{[1]}+{\mathbi{b}}^{[1]})\cdot {\mathbi{W}}^{[2]}+ b^{[2]} ) \label{eq:9-27} \end{eqnarray} \parinterval 前向计算实现如图\ref{fig:9-38}所示,图中对各张量和其他参数的形状做了详细说明。输入$ {\mathbi{x}}=(x_1,x_2,x_3) $是一个$1\times 3$的张量,其三个维度分别对应天空状况、低空气温、水平气压三个方面的数据。输入数据经过隐藏层的线性变换$ {\mathbi{x}}\cdot {\mathbi{W}}^{[1]}+{\mathbi{b}}^{[1]}$和Tanh函数的激活,得到新的张量$ {\mathbi{a}}=(a_1,a_2) $,其中$a_1$,$a_2$分别对应着从输入数据中提取出的温度和风速两方面特征;神经网络在获取到天气情况的特征$ {\mathbi{a}}$后,继续对其进行线性变换${\mathbi{a}}\cdot {\mathbi{W}}^{[2]}+ b^{[2]} $和Sigmoid函数的激活操作,得到神经网络的最终输出$ y $,即神经网络此时预测的穿衣指数。 %---------------------------------------------- \begin{figure}[htp] \centering \input{./Chapter9/Figures/figure-weather-forward} \caption{前向计算示例(计算图)} \label{fig:9-38} \end{figure} %------------------------------------------- \parinterval 图\ref{fig:9-38}实际上是神经网络的一种{\small\bfnew{计算图}}\index{计算图}(Computation Graph)\index{Computation Graph}表示。现在很多深度学习框架都是把神经网络转化为计算图,这样可以把复杂的运算分解为简单的运算,称为{\small\bfnew{算子}}\index{算子}(Calculus\index{Calculus})。通过对计算图中节点的遍历,可以方便地完成神经网络的计算。比如,可以对图中节点进行拓扑排序(由输入到输出),之后依次访问每个节点,同时完成相应的计算,这也就实现了一个前向计算的过程。 \parinterval 使用计算图的另一个优点在于,这种方式易于参数梯度的计算。在后面的内容中会看到,计算神经网络中参数的梯度是模型训练的重要步骤。在计算图中,可以使用{\small\bfnew{反向传播}}\index{反向传播} (Backward Propagation\index{Backward Propagation})的方式逐层计算不同节点上的梯度信息。在\ref{sec9:para-training} 节会看到使用计算图这种结构可以非常方便、高效地计算反向传播中所需的梯度信息。 %---------------------------------------------------------------------------------------- % NEW SECTION %---------------------------------------------------------------------------------------- \sectionnewpage \section{神经网络的参数训练} \parinterval 简单来说,神经网络可以被看作是由变量和函数组成的表达式,例如:$ {\mathbi{y}}={\mathbi{x}}+{\mathbi{b}} $、$ {\mathbi{y}}={\textrm{ReLU}}({\mathbi{x}}\cdot {\mathbi{W}}+{\mathbi{b}}) $、$ {\mathbi{y}}={\textrm{Sigmoid}}({\textrm{ReLU}}({\mathbi{x}}\cdot {\mathbi{W}}^{[1]}+{\mathbi{b}}^{[1]})\cdot {\mathbi{W}}^{[2]}+{\mathbi{b}}^{[2]}) $等等,其中的$ {\mathbi{x}} $和$ {\mathbi{y}} $作为输入和输出向量, ${\mathbi{W}}$、${\mathbi{b}}$等其他变量作为{\small\sffamily\bfseries{模型参数}}\index{模型参数}(Model Parameters)\index{Model Parameters}。确定了函数表达式和模型参数,也就确定了神经网络模型。通常,表达式的形式需要系统开发者设计,而模型参数的数量有时会非常巨大,因此需要自动学习,这个过程也被称为模型学习或训练。为了实现这个目标,通常会准备一定量的带有标准答案的数据,称之为有标注数据。这些数据会用于对模型参数的学习,这也对应了统计模型中的参数估计过程。在机器学习中,一般把这种使用有标注数据进行统计模型参数训练的过程称为{\small\sffamily\bfseries{有指导的训练}}\index{有指导的训练}或{\small\sffamily\bfseries{有监督的训练}}\index{有监督的训练}(Supervised Training)\index{Supervised Training}。在本章中,如果没有特殊说明,模型训练都是指有监督的训练。那么神经网络内部是怎样利用有标注数据对参数进行训练的呢?\\ \\ \vspace{-2.5em} \parinterval 为了回答这个问题,可以把模型参数的学习过程看作是一个优化问题,即找到一组参数,使得模型达到某种最优的状态。这个问题又可以被转化为两个新的问题: \begin{itemize} \vspace{0.5em} \item 优化的目标是什么? \vspace{0.5em} \item 如何调整参数以达到优化目标? \vspace{0.5em} \end{itemize} \parinterval 下面会围绕这两个问题对神经网络的参数学习方法展开介绍。 %---------------------------------------------------------------------------------------- % NEW SUB-SECTION %---------------------------------------------------------------------------------------- \subsection{损失函数} \parinterval 在神经网络的有监督学习中,训练模型的数据是由输入和正确答案所组成的样本构成的。假设有多个输入样本$ \{{\mathbi{x}}_1,{\mathbi{x}}_2,\dots,{\mathbi{x}}_n\} $,每一个$ {\mathbi{x}}_i $都对应一个正确答案$ \widetilde{\mathbi{y}}_i $,$ \{{\mathbi{x}}_i,\widetilde{\mathbi{y}}_i\} $就构成一个优化神经网络的{\small\sffamily\bfseries{训练数据集合}}\index{训练数据集合}(Training Data Set)\index{Training Data Set}。对于一个神经网络模型${\mathbi{y}}=f({\mathbi{x}}) $,每个$ {\mathbi{x}}_i $也会有一个输出$ {\mathbi{y}}_i $。如果可以度量正确答案$ \widetilde{\mathbi{y}}_i $和神经网络输出$ {\mathbi{y}}_i $之间的偏差,进而通过调整网络参数减小这种偏差,就可以得到更好的模型。 %---------------------------------------------- \begin{figure}[htp] \centering \input{./Chapter9/Figures/figure-absolute-loss} \caption{正确答案与神经网络输出之间的偏差} \label{fig:9-42} \end{figure} %------------------------------------------- \parinterval 通常,可以通过设计{\small\sffamily\bfseries{损失函数}}\index{损失函数}(Loss Function)\index{Loss Function}来度量正确答案$ \widetilde{\mathbi{y}}_i $和神经网络输出$ {\mathbi{y}}_i $之间的偏差。而这个损失函数往往充当训练的{\small\sffamily\bfseries{目标函数}}\index{目标函数}(Objective Function)\index{Objective Function},神经网络训练就是通过不断调整神经网络内部的参数而使损失函数最小化。图\ref{fig:9-42}展示了一个绝对值损失函数的实例。 \parinterval 这里用$ Loss(\widetilde{\mathbi{y}}_i,{\mathbi{y}}_i) $表示网络输出$ {\mathbi{y}}_i $相对于答案$ \widetilde{\mathbi{y}}_i $的损失,简记为$ L $。表\ref{tab:9-3}是几种常见损失函数的定义。需要注意的是,没有一种损失函数可以适用于所有的问题。损失函数的选择取决于许多因素,包括:数据中是否有离群点、模型结构的选择、是否易于找到函数的导数以及预测结果的置信度等。对于相同的神经网络,不同的损失函数会对训练得到的模型产生不同的影响。对于新的问题,如果无法找到已有的、适合于该问题的损失函数,研究人员也可以自定义损失函数。因此设计新的损失函数也是神经网络中有趣的研究方向。 %-------------------------------------------------------------------- \begin{table}[htp] \centering \caption{常见的损失函数} \label{tab:9-3} \small \begin{tabular}{l | l l} \rule{0pt}{15pt} 名称 & 定义 & 应用 \\ \hline \rule{0pt}{15pt} 0-1损失 & $ L=\begin{cases} 0 & \widetilde{\mathbi{y}}_i={\mathbi{y}}_i \\1 & \widetilde{\mathbi{y}}_i\not ={\mathbi{y}}_i\end{cases} $ & 感知机 \\ \rule{0pt}{15pt} Hinge损失 & $ L={\textrm {max}}(0,1-\widetilde{\mathbi{y}}_i\cdot {\mathbi{y}}_i) $ & SVM \\ \rule{0pt}{15pt} 绝对值损失 & $ L=\vert \widetilde{\mathbi{y}}_i-{\mathbi{y}}_i\vert $ & 回归 \\ \rule{0pt}{15pt} Logistic损失 & $ L={\textrm{log}}(1+\widetilde{\mathbi{y}}_i\cdot {\mathbi{y}}_i) $ & 回归 \\ \rule{0pt}{15pt} 平方损失 & $ L={(\widetilde{\mathbi{y}}_i-{\mathbi{y}}_i)}^2 $ & 回归 \\ \rule{0pt}{15pt} 指数损失 & $ L={\textrm{exp}}(-\widetilde{\mathbi{y}}_i\cdot {\mathbi{y}}_i) $ & AdaBoost \\ \rule{0pt}{15pt} 交叉熵损失 & $ L=-\sum_{k}{{\mathbi{y}}_{i}[k]}{\textrm {log}} {\widetilde{\mathbi{y}}_{i}[k]} $ & 多分类 \\ \rule{0pt}{15pt} & 其中,${\mathbi{y}}_{i}[k]$ 表示 ${\mathbi{y}}_i$的第$k$维 \end{tabular} \end{table} %-------------------------------------------------------------------- \parinterval 在实际系统开发中,损失函数中除了损失项(即用来度量正确答案$ \widetilde{\mathbi{y}}_i $和神经网络输出$ {\mathbi{y}}_i $之间的偏差的部分)之外,还可以包括正则项,比如L1正则和L2正则。设置正则项本质上是要加入一些偏置,使模型在优化的过程中偏向某个方向多一些。关于正则项的内容将在\ref{sec:9.4.5}节介绍。 %---------------------------------------------------------------------------------------- % NEW SUB-SECTION %---------------------------------------------------------------------------------------- \subsection{基于梯度的参数优化}\label{sec9:para-training} \parinterval 对于第$ i $个样本$ ({\mathbi{x}}_i,\widetilde{\mathbi{y}}_i) $,把损失函数$ L(\widetilde{\mathbi{y}}_i,{\mathbi{y}}_i) $看作是参数$ \bm \theta $的函数\footnote{为了简化描述,可以用$ \bm{\theta} $表示神经网络中的所有参数,包括各层的权重矩阵${\mathbi{W}}^{[1]}\dots{\mathbi{W}}^{[n]}$和偏置向量${\mathbi{b}}^{[1]}\dots{\mathbi{b}}^{[n]}$等。},因为输出$ {\mathbi{y}}_i $是由输入$ {\mathbi{x}}_i $和模型参数$ \bm \theta $决定,因此也把损失函数写为$ L({\mathbi{x}}_i,\widetilde{\mathbi{y}}_i;{\bm \theta}) $。下式描述了参数学习的过程: \begin{eqnarray} \widehat{\bm\theta}&=&\mathop{\arg\min}_{\bm \theta}\frac{1}{n}\sum_{i=1}^{n}{L({\mathbi{x}}_i,\widetilde{\mathbi{y}}_i;{\bm \theta})} \label{eq:9-28} \end{eqnarray} \noindent 其中,$ \widehat{\bm \theta} $表示在训练数据上使损失的平均值达到最小的参数,$n$为训练数据总量。$ \frac{1}{n}\sum \limits_{i=1}^{n}{L({\mathbi{x}}_i,\widetilde{\mathbi{y}}_i;{\bm \theta})} $也被称作{\small\sffamily\bfseries{代价函数}}\index{代价函数}(Cost Function)\index{Cost Function},它是损失函数均值期望的估计,记为$ J({\bm \theta}) $。 \parinterval 参数优化的核心问题是:找到使代价函数$ J({\bm\theta}) $达到最小的$ \bm \theta $。然而$ J({\bm\theta}) $可能会包含大量的参数,比如,基于神经网络的机器翻译模型的参数量可能会超过一亿个。这时不可能用手动方法进行调参。为了实现高效的参数优化,比较常用的手段是使用{\small\bfnew{梯度下降方法}}\index{梯度下降方法}(The Gradient Descent Method)\index{The Gradient Descent Method}。 %---------------------------------------------------------------------------------------- % NEW SUBSUB-SECTION %---------------------------------------------------------------------------------------- \subsubsection{1. 梯度下降} \parinterval 梯度下降法是一种常用的优化方法,非常适用于目标函数可微分的问题。它的基本思想是:给定函数上的第一个点,找到使函数值变化最大的方向,然后前进一“步”,这样模型就可以朝着更大(或更小)的函数值以最快的速度移动\footnote{梯度下降的一种实现是{\scriptsize\bfnew{最速下降}}(Steepest Descent)。该方法的每一步移动都选取合适的步长,进而使目标函数能得到最大程度的增长(或下降)。}。具体来说,梯度下降通过迭代更新参数$ {\bm \theta} $,不断沿着梯度的反方向让参数$ \bm \theta $朝着损失函数更小的方向移动:如果$ J({\bm \theta}) $对$ \bm \theta $可微分,则$ \frac{\partial J({\bm \theta})}{\partial {\bm \theta}} $将指向$ J({\bm \theta}) $在$ {\bm \theta} $处变化最大的方向,这里将其称之为梯度方向。${\bm \theta}$沿着梯度方向更新,新的${\bm \theta}$可以使函数更接近极值,其过程如图\ref{fig:9-43}所示\footnote{图中的${\bm \theta}^{[1]}$和${\bm \theta}^{[2]}$分别是参数$\bm \theta$的不同变化方向}。 %---------------------------------------------- \begin{figure}[htp] \centering \input{./Chapter9/Figures/figure-gradient-descent} \caption{函数上一个点沿着不同方向移动的示例} \label{fig:9-43} \end{figure} %------------------------------------------- \parinterval 应用梯度下降算法时,首先需要初始化参数${\bm \theta}$。一般情况下深度学习中的参数应该初始化为一个不太大的随机数。一旦初始化${\bm \theta}$后,就开始对模型进行不断的更新,{\small\sffamily\bfseries{参数更新的规则}}\index{参数更新的规则}(Update Rule)\index{Update Rule}如下: \begin{eqnarray} {\bm \theta}_{t+1}&=&{\bm \theta}_{t}-\alpha \cdot \frac{\partial J({\bm \theta})}{\partial {\bm \theta}} \label{eq:9-29} \end{eqnarray} \noindent 其中$t $表示更新的步数,$ \alpha $是一个超参数,被称作{\small\sffamily\bfseries{学习率}}\index{学习率}(Learning Rate)\index{Learning Rate},表示更新步幅的大小。$ \alpha $的设置需要根据任务进行调整。 \parinterval 从优化的角度看,梯度下降是一种典型的 {\small\bfnew{基于梯度的方法}}\index{基于梯度的方法}(The Gradient-based Method)\index{The Gradient-based Method},属于基于一阶导数的方法。其他类似的方法还有牛顿法、共轭方向法、拟牛顿法等。在具体实现时,公式\eqref{eq:9-29}可以有以下不同的形式。\\ %---------------------------------------------------------------------------------------- % %---------------------------------------------------------------------------------------- \vspace{0.5em} \noindent {\small\sffamily\bfseries{1)批量梯度下降\index{批量梯度下降}(Batch Gradient Descent)\index{Batch Gradient Descent}}} \vspace{0.5em} \parinterval 批量梯度下降是梯度下降方法中最原始的形式,这种梯度下降方法在每一次迭代时使用所有的样本进行参数更新。参数优化的目标函数如下: \begin{eqnarray} J({\bm \theta})&=&\frac{1}{n}\sum_{i=1}^{n}{L({\mathbi{x}}_i,\widetilde{\mathbi{y}}_i;{\bm \theta})} \label{eq:9-30} \end{eqnarray} \parinterval 公式\eqref{eq:9-30}是公式\eqref{eq:9-29}的严格实现,也就是将全部训练样本的平均损失作为目标函数。由全数据集确定的方向能够更好地代表样本总体,从而朝着模型在数据上整体优化所在的方向更新参数。 \parinterval 不过,这种方法的缺点也十分明显,因为要在全部训练数据上最小化损失,每一次参数更新都需要计算在所有样本上的损失。在使用海量数据进行训练的情况下,这种计算是非常消耗时间的。当训练数据规模很大时,很少使用这种方法。 %---------------------------------------------------------------------------------------- % %---------------------------------------------------------------------------------------- \vspace{0.5em} \noindent {\small\sffamily\bfseries{2)随机梯度下降\index{随机梯度下降}(Stochastic Gradient Descent)\index{Stochastic Gradient Descent}}} \vspace{0.5em} \parinterval 随机梯度下降(简称SGD)不同于批量梯度下降,每次迭代只使用一个样本对参数进行更新。SGD的目标函数如下: \begin{eqnarray} J({\bm \theta})&=&L({\mathbi{x}}_i,\widetilde{\mathbi{y}}_i;{\bm \theta}) \label{eq:9-31} \end{eqnarray} \noindent 由于每次只随机选取一个样本$({\mathbi{x}}_i,\widetilde{\mathbi{y}}_i)$进行优化,这样更新的计算代价低,参数更新的速度大大加快,而且也适用于利用少量样本进行在线学习的情况\footnote{比如,训练数据不是一次给定的,而是随着模型的使用不断追加的。这时,需要不断地用新的训练样本更新模型,这种模式也被称作{\scriptsize\bfnew{在线学习}}(Online Learning)。}。 \parinterval 因为随机梯度下降算法每次优化的只是某一个样本上的损失,所以它的问题也非常明显:单个样本上的损失无法代表在全部样本上的损失,因此参数更新的效率低,方法收敛速度极慢。即使在目标函数为强凸函数的情况下,SGD仍旧无法做到线性收敛。 %---------------------------------------------------------------------------------------- % %---------------------------------------------------------------------------------------- \vspace{0.5em} \noindent {\small\sffamily\bfseries{3)小批量梯度下降\index{小批量梯度下降}(Mini-batch Gradient Descent)\index{Mini-batch Gradient Descent}}} \vspace{0.5em} \parinterval 为了综合批量梯度下降和随机梯度下降的优缺点,在实际应用中一般采用这两个算法的折中\ \dash \ 小批量梯度下降。其思想是:每次迭代计算一小部分训练数据的损失函数,并对参数进行更新。这一小部分数据被称为一个批次(mini-batch或者batch)。小批量梯度下降的参数优化的目标函数如下: \begin{eqnarray} J({\bm \theta})&=&\frac{1}{m}\sum_{i=j}^{j+m-1}{L({\mathbi{x}}_i,\widetilde{\mathbi{y}}_i;{\bm \theta})} \label{eq:9-32} \end{eqnarray} \noindent 其中,$ m $表示一个批次中的样本的数量,$ j $表示这个批次在全体训练数据的起始位置。这种方法可以更充分的利用GPU设备,因为批次中的样本可以一起计算。而且每次使用多个样本可以大大减小使模型收敛所需要的参数更新次数。但是需要注意的是批次大小的选择对模型的最终性能是存在一定影响的。 %---------------------------------------------------------------------------------------- % NEW SUBSUB-SECTION %---------------------------------------------------------------------------------------- \subsubsection{2. 梯度获取} \parinterval 梯度下降算法的一个核心是要得到目标函数相对于参数的梯度。下面将介绍三种常见的求梯度方法:数值微分、符号微分和自动微分,深度学习实现过程中多是采用自动微分方法计算梯度\upcite{baydin2017automatic}。 %---------------------------------------------------------------------------------------- % %---------------------------------------------------------------------------------------- \vspace{0.5em} \noindent {\small\sffamily\bfseries{1)数值微分\index{数值微分}(Numerical Differentiation)\index{Numerical Differentiation}}} \vspace{0.5em} \parinterval 数学上,梯度的求解其实就是求函数偏导的问题。导数是用极限来定义的,如公式\eqref{eq:9-33}所示: \begin{eqnarray} \frac{\partial L({\bm \theta})}{\partial {\bm \theta}}&=&\lim\limits_{\Delta {\bm \theta} \to 0}\frac{L({\bm \theta}+\Delta {\bm \theta})-L({\bm \theta}-\Delta {\bm \theta})}{2\Delta {\bm \theta}} \label{eq:9-33} \end{eqnarray} \noindent 其中,${\bm \theta}$表示参数的一个很小的变化值。公式\eqref{eq:9-33}也被称作导数的双边定义。如果一个函数是初等函数,可以用求导法则来求得其导函数。如果不知道函数导数的解析式,则必须利用数值方法来求解该函数在某个点上的导数,这种方法就是数值微分。 \parinterval 数值微分根据导数的原始定义完成,根据公式可知,要得到损失函数在某个参数状态$ {\bm \theta} $下的梯度,可以将$ {\bm \theta} $增大或减小一点($ \Delta{\bm \theta}$),例如,取$ \vert{\Delta {\bm \theta}}\vert=0.0001 $,之后观测损失函数的变化与$ \Delta {\bm \theta} $的比值。$ \Delta {\bm \theta}$的取值越小计算的结果越接近导数的真实值,但是对计算的精度要求越高。 \parinterval 这种求梯度的方法很简单,但是计算量很大,求解速度非常慢,而且这种方法会造成{\small\sffamily\bfseries{截断误差}}\index{截断误差}(Truncation Error)\index{Truncation Error}和{\small\sffamily\bfseries{舍入误差}}\index{舍入误差}(Round-off Error)\index{Round-off Error}。在网络比较复杂、参数量稍微有点大的模型上一般不会使用这种方法。 \parinterval 截断误差和舍入误差是如何造成的呢?数值微分方法求梯度时,需用极限或无穷过程来求得。然而计算机需要将求解过程化为一系列有限的算术运算和逻辑运算。这样就要对某种无穷过程进行“截断”,即仅保留无穷过程的前段有限序列而舍弃它的后段。这就带来截断误差;舍入误差,是指运算得到的近似值和精确值之间的差异。由于数值微分方法计算复杂函数的梯度问题时,经过无数次的近似,每一次近似都产生了舍入误差,在这样的情况下,误差会随着运算次数增加而积累得很大,最终得出没有意义的运算结果。实际上,截断误差和舍入误差在训练复杂神经网络中,特别是使用低精度计算时,也会出现,因此是实际系统研发中需要注意的问题。 \parinterval 尽管数值微分不适用于大模型中的梯度求解,但是由于其非常简单,因此经常被用于检验其他梯度计算方法的正确性。比如在实现反向传播的时候(详见\ref{sec:9.4.6}节),可以检验求导是否正确(Gradient Check),这个过程就是利用数值微分实现的。\\ \\ %---------------------------------------------------------------------------------------- % %---------------------------------------------------------------------------------------- \noindent {\small\sffamily\bfseries{2)符号微分\index{符号微分}(Symbolic Differentiation)\index{Symbolic Differentiation}}} \vspace{0.5em} \parinterval 顾名思义,符号微分就是通过建立符号表达式求解微分的方法:借助符号表达式和求导公式,推导出目标函数关于自变量的微分表达式,最后再带入具体数值得到微分结果。例如,对于表达式$ L({\bm \theta})={\mathbi{x}}\cdot {\bm \theta}+2{\bm \theta}^2 $,可以手动推导出微分表达式$ \frac{\partial L({\bm \theta})}{\partial {\bm \theta}}=\mathbi{x}+4{\bm \theta} $,最后将具体数值$ \mathbi{x} = {(\begin{array}{cc} 2 & -3\end{array})} $和$ {\bm \theta} = {(\begin{array}{cc} -1 & 1\end{array})} $带入后,得到微分结果$\frac{\partial L({\bm \theta})}{\partial {\bm \theta}}= {(\begin{array}{cc} 2 & -3\end{array})}+4{(\begin{array}{cc} -1 & 1\end{array})}= {(\begin{array}{cc} -2 & 1\end{array})}$。 \parinterval 使用这种求梯度的方法,要求必须将目标函数转化成一种完整的数学表达式,这个过程中存在{\small\bfnew{表达式膨胀}}\index{表达式膨胀}(Expression Swell)\index{Expression Swell}的问题,很容易导致符号微分求解的表达式急速“膨胀”,大大增加系统存储和处理表达式的负担。关于这个问题的一个实例请看表\ref{tab:9-4}。在深层的神经网络中,神经元数量和参数量极大,损失函数的表达式会非常冗长,不易存储和管理,而且,仅仅写出损失函数的微分表达式就是一个很庞大的工作量。从另一方面来说,这里真正需要的是微分的结果值,而不是微分表达式,推导微分表达式仅仅是求解过程中的中间产物。 %-------------------------------------------------------------------- \begin{table}[htp] \centering \caption{符号微分的表达式随函数的规模增加而膨胀} \label{tab:9-4} \small \begin{tabular}{l | l l} \rule{0pt}{18pt} 函数 & 微分表达式 & 化简的微分表达式 \\ \hline \rule{0pt}{18pt} $x$ & $1$ & $1$ \\ \rule{0pt}{18pt} $x\cdot(x+1)$ & $(x+1)+x$ & $2x + 1$ \\ \rule{0pt}{18pt} $x\cdot(x+1)\cdot$ & $(x+1)\cdot(x^2+x+1)$ & $4x^3+6x^2$ \\ $(x^2+x+1)$ & $+x\cdot(x^2+x+1)$ & $+4x+1$ \\ & $+x\cdot(x+1)\cdot(2x+1)$ & \\ \rule{0pt}{18pt} $(x^2+x)\cdot$ & $(2x+1)\cdot(x^2+x+1)\cdot$ & $8x^7+28x^6$ \\ $(x^2+x+1)\cdot$ & $(x^4+2x^3+2x^2+x+1)$ & $+48x^5+50x^4$ \\ $(x^4+2x^3$ & $+(2x+1)\cdot(x^2+x)\cdot$ & $+36x^3+18x^2$ \\ $+2x^2+x+1)$ & \ \ $(x^4+2x^3+2x^2+x+1)$ & $+6x+1$ \\ & $+(x^2+x)\cdot(x^2+x+1)\cdot$ & \\ & \ \ $(4x^3+6x^2+4x+1)$ & \\ \end{tabular} \end{table} %-------------------------------------------------------------------- %---------------------------------------------------------------------------------------- % %---------------------------------------------------------------------------------------- \vspace{0.5em} \noindent {\small\sffamily\bfseries{3)自动微分\index{自动微分}(Automatic Differentiation)\index{Automatic Differentiation}}} \vspace{0.5em} \parinterval 自动微分是一种介于数值微分和符号微分的方法:将符号微分应用于最基本的算子,如常数、幂函数、指数函数、对数函数、三角函数等,然后代入数值,保留中间结果,最后再应用于整个函数。通过这种方式,将复杂的微分变成了简单的步骤,这些步骤完全自动化,而且容易进行存储和计算。 \parinterval 由于它只对基本函数或常数运用符号微分法则,所以它非常适合嵌入编程语言的循环条件等结构中,形成一种程序化的微分过程。在具体实现时,自动微分往往被当做是一种基于图的计算,相关的理论和技术方法相对成熟,因此是深度学习中使用最广泛的一种方法。不同于一般的编程模式,图计算先生成计算图,然后按照计算图执行计算过程。 \parinterval 自动微分可以用一种{\small\sffamily\bfseries{反向模式}}\index{反向模式}(Reverse Mode\index{Reverse Mode}/Backward Mode\index{Backward Mode})即反向传播思想进行描述\upcite{baydin2017automatic}。令${\mathbi{h}}_i$是神经网络的计算图中第$i$个节点的输出。反向模式的自动微分是要计算: \begin{eqnarray} \bar{{\mathbi{h}}_i} &=& \frac{\partial L}{\partial {\mathbi{h}}_i} \label{eq:reverse-mode-v} \end{eqnarray} \noindent 这里,$\bar{{\mathbi{h}}_i}$表示损失函数$L$相对于${\mathbi{h}}_i$的梯度信息,它会被保存在节点$i$处。为了计算$\bar{{\mathbi{h}}_i}$,需要从网络的输出反向计算每一个节点处的梯度。具体实现时,这个过程由一个包括前向计算和反向计算的两阶段方法实现。 \parinterval 首先,从神经网络的输入,逐层计算每层网络的输出值。如图\ref{fig:9-44}所示,第$ i $ 层的输出$ {\mathbi{h}}_i $ 作为第$ i+1 $ 层的输入,数据流在神经网络内部逐层传递。 %---------------------------------------------- \begin{figure}[htp] \centering \input{./Chapter9/Figures/figure-forward-propagation} \caption{前向计算示意图} \label{fig:9-44} \end{figure} %------------------------------------------- \parinterval 前向计算实际上就是网络构建的过程,所有的计算都会被转化为计算图上的节点,前向计算和反向计算都依赖计算图来完成。构建计算图有以下两种实现方式: \begin{itemize} \vspace{0.5em} \item 动态图:前向计算与计算图的搭建同时进行,函数表达式写完即能得到前向计算的结果,有着灵活、易于调试的优点。 \vspace{0.5em} \item 静态图:先搭建计算图,后执行运算,函数表达式完成后,并不能得到前向计算结果,需要显性调用一个Forward函数。但是计算图可以进行深度优化,执行效率较高。 \vspace{0.5em} \end{itemize} \parinterval 对于反向计算的实现,一般从神经网络的输出开始,逆向逐层计算每层网络输入所对应的微分结果。如图\ref{fig:9-45}所示,在第$ i $层计算此处的梯度$ \frac{\partial L}{\partial {\mathbi{h}}_i} $,并将微分值向前一层传递,根据链式法则继续计算梯度。 %---------------------------------------------- \begin{figure}[htp] \centering \input{./Chapter9/Figures/figure-back-propagation} \caption{反向计算示意图} \label{fig:9-45} \end{figure} %------------------------------------------- \parinterval 反向计算也是深度学习中反向传播方法的基础。其实现的内部细节将在\ref{sec:9.4.6}节详细阐述,所以在这里不再赘述。 %---------------------------------------------------------------------------------------- % NEW SUBSUB-SECTION %---------------------------------------------------------------------------------------- \subsubsection{3. 基于梯度的方法的变种和改进}\label{sec:9.4.2.3} \parinterval 参数优化通常基于梯度下降算法,即在每个更新步骤$ t $,沿梯度反方向更新参数,该过程如下: \begin{eqnarray} {\bm \theta}_{t+1}&=&{\bm \theta}_{t}-\alpha \cdot \frac{\partial J({\bm \theta}_t)}{\partial {\bm \theta}_t} \label{eq:9-200} \end{eqnarray} \noindent 其中,$ \alpha $是一个超参数,表示更新步幅的大小,称作学习率。当然,这是一种最基本的梯度下降方法。如果函数的形状非均向,比如呈延伸状,搜索最优点的路径就会非常低效,因为这时梯度的方向并没有指向最小值的方向,并且随着参数的更新,梯度方向往往呈锯齿状,这将是一条相当低效的路径;此外这种梯度下降算法并不是总能到达最优点,而是在其附近徘徊;还有一个最令人苦恼的问题\ \dash \ 设置学习率,如果学习率设置的比较小,会导致训练收敛速度慢,如果学习率设置的比较大,会导致训练过程中因为优化幅度过大而频频跳过最优点。我们希望网络在优化的时候损失函数有一个很好的收敛速度同时又不至于摆动幅度太大。 \parinterval 针对以上问题,很多学者尝试对梯度下降方法做出改进,如Momentum\upcite{qian1999momentum}, AdaGrad\upcite{duchi2011adaptive}, Adadelta\upcite{Zeiler2012ADADELTAAA}, RMSProp\upcite{tieleman2012rmsprop}, Adam\upcite{kingma2014adam}, AdaMax\upcite{kingma2014adam}, Nadam\upcite{Dozat2016IncorporatingNM}, AMSGrad\upcite{Reddi2018OnTC}等等,在这里将介绍Momentum、AdaGrad、RMSProp、Adam这4 种方法。 %---------------------------------------------------------------------------------------- % %---------------------------------------------------------------------------------------- \vspace{0.5em} \noindent {\small\sffamily\bfseries{1)Momentum \index{Momentum}}} \vspace{0.5em} \parinterval Momentum梯度下降算法的参数更新方式如公式\eqref{eq:9-34}和\eqref{eq:9-35}所示\footnote{在梯度下降算法的几种改进方法的公式中,其更新对象是某个具体参数而非参数矩阵,因此不再使用加粗样式。}: \begin{eqnarray} v_t&=&\beta v_{t-1}+(1-\beta)\frac{\partial J}{\partial \theta_t} \label{eq:9-34}\\ \theta_{t+1}&=&\theta_t-\alpha v_t \label{eq:9-35} \end{eqnarray} \parinterval 该算法引入了一个“动量”的理念\upcite{qian1999momentum},它是基于梯度的移动指数加权平均。公式中的$ {v}_t $是损失函数在前$ t-1 $次更新中累积的梯度动量,$ \beta $是梯度累积的一个指数,这里一般设置值为0.9。所以Momentum梯度下降算法的主要思想就是对网络的参数进行平滑处理,让梯度的摆动幅度变得更小。 \parinterval 这里的“梯度”不再只是现在的损失函数的梯度,而是之前的梯度的加权和。在原始的梯度下降算法中,如果在某个参数状态下,梯度方向变化特别大,甚至与上一次参数更新中梯度方向成90度夹角,下一次参数更新中梯度方向可能又是一次90度的改变,这时参数优化路径将会成“锯齿”状(如图\ref{fig:9-46}所示),优化效率极慢。而Momentum梯度下降算法不会让梯度发生90度的变化,而是让梯度慢慢发生改变:如果当前的梯度方向与之前的梯度方向相同,在原梯度方向上加速更新参数;如果当前的梯度方向与之前的梯度方向相反,并不会产生一个急转弯,而是尽量把优化路径平滑地进行改变。这样做的优点也非常明显,一方面杜绝了“锯齿”状优化路径的出现,另一方面将优化幅度变得更加平滑,不会导致频频跳过最优点。 %---------------------------------------------- \begin{figure}[htp] \centering \input{./Chapter9/Figures/figure-sawtooth} \caption{Momentum梯度下降 vs 普通梯度下降} \label{fig:9-46} \end{figure} %------------------------------------------- %---------------------------------------------------------------------------------------- % %---------------------------------------------------------------------------------------- \vspace{0.5em} \noindent {\small\sffamily\bfseries{2)AdaGrad \index{AdaGrad}}} \vspace{0.5em} \parinterval 在神经网络的学习中,学习率的设置很重要。学习率过小, 会导致学习花费过多时间;反过来,学习率过大,则会导致学习发散,甚至造成模型的“跑偏”。在深度学习实现过程中,有一种被称为学习率{\small\bfnew{衰减}}\index{衰减}(Decay)\index{Decay}的方法,即最初设置较大的学习率,随着学习的进行,使学习率逐渐减小,这种方法相当于将“全体”参数的学习率值一起降低。AdaGrad梯度下降算法进一步发展了这个思想\upcite{duchi2011adaptive}。 \parinterval AdaGrad会为参数的每个元素适当地调整学习率,与此同时进行学习。其参数更新方式如公式\eqref{eq:9-36}和\eqref{eq:9-37}所示: \begin{eqnarray} z_t&=&z_{t-1}+\frac{\partial J}{\partial {\theta}_t} \cdot \frac{\partial J}{\partial {\theta}_t} \label{eq:9-36}\\ {\theta}_{t+1}&=&{\theta}_t-\eta \frac{1}{\sqrt{z_t}}\cdot \frac{\partial J}{\partial {\theta}_t} \label{eq:9-37} \end{eqnarray} \parinterval 这里新出现了变量$ z $,它保存了以前的所有梯度值的平方和。如公式\eqref{eq:9-37}所示,在更新参数时,通过除以${\sqrt{z_t}} $ ,就可以调整学习的尺度。这意味着,变动较大(被大幅度更新)的参数的学习率将变小。也就是说,可以按参数的元素进行学习率衰减,使变动大的参数的学习率逐渐减小。 %---------------------------------------------------------------------------------------- % %---------------------------------------------------------------------------------------- \vspace{0.5em} \noindent {\small\sffamily\bfseries{3)RMSProp \index{RMSProp}}} \vspace{0.5em} \parinterval RMSProp算法是一种自适应学习率的方法\upcite{tieleman2012rmsprop},它是对AdaGrad算法的一种改进,可以避免AdaGrad算法中学习率不断单调下降以至于过早衰减的缺点。 \parinterval RMSProp 算法沿袭了Momentum梯度下降算法中指数加权平均的思路,不过Momentum算法中加权平均的对象是梯度(即$ \frac{\partial J}{\partial {\theta}}$),而RMSProp 算法加权平均的对象是梯度的平方(即$ \frac{\partial J}{\partial {\theta}} \cdot \frac{\partial J}{\partial {\theta}} $)。RMSProp 算法的参数更新方式如公式\eqref{eq:9-38}和\eqref{eq:9-39}所示: \begin{eqnarray} z_t&=&\gamma z_{t-1}+(1-\gamma) \frac{\partial J}{\partial {\theta}_t} \cdot \frac{\partial J}{\partial {\theta}_t} \label{eq:9-38}\\ {\theta}_{t+1}&=&{\theta}_t-\frac{\eta}{\sqrt{z_t+\epsilon}}\cdot \frac{\partial J}{\partial {\theta}_t} \label{eq:9-39} \end{eqnarray} \parinterval 公式中的$ \epsilon $是为了维持数值稳定性而添加的常数,一般可设为 $ 10^{-8} $。和 AdaGrad 的想法类似,模型参数中每个元素都拥有各自的学习率。 \parinterval RMSProp与AdaGrad相比,学习率的分母部分(即两种梯度下降算法迭代公式中的$ z $)的计算由累积方式变成了指数衰减移动平均。于是,每个参数的学习率并不是呈衰减趋势,而是既可以变小也可以变大,从而避免AdaGrad算法中学习率不断单调下降以至于过早衰减的缺点。 %---------------------------------------------------------------------------------------- % %---------------------------------------------------------------------------------------- \vspace{0.5em} \noindent {\small\sffamily\bfseries{4)Adam \index{Adam} }} \vspace{0.5em} \parinterval Adam梯度下降算法是在RMSProp算法的基础上进行改进的,可以将其看成是带有动量项的RMSProp算法\upcite{kingma2014adam}。该算法在自然语言处理领域非常流行。Adam 算法的参数更新方式如公式\eqref{eq:9-40}\eqref{eq:9-41}\eqref{eq:9-42}所示: \begin{eqnarray} v_t&=&\beta v_{t-1}+(1-\beta)\frac{\partial J}{\partial {\theta}_t} \label{eq:9-40}\\ z_t&=&\gamma z_{t-1}+(1-\gamma) \frac{\partial J}{\partial {\theta}_t} \cdot \frac{\partial J}{\partial {\theta}_t} \label{eq:9-41}\\ {\theta}_{t+1}&=&{\theta}_t-\frac{\eta}{\sqrt{z_t+\epsilon}} v_t \label{eq:9-42} \end{eqnarray} \noindent 可以看到Adam 算法相当于在RMSProp算法中引入了Momentum算法中的动量项,这样做使得Adam算法兼具了Momentum算法和RMSProp算法的优点:既能使梯度更为“平滑”地更新,同时可以为神经网络中的每个参数设置不同的学习率。 \parinterval 需要注意的是包括Adam在内的很多参数更新算法中的学习率都需要人为设置。而且模型学习的效果与学习率的设置关系极大,甚至在研发实际系统时工程师需要进行大量的实验,才能得到最佳的模型。 %---------------------------------------------------------------------------------------- % NEW SUB-SECTION %---------------------------------------------------------------------------------------- \subsection{参数更新的并行化策略} \parinterval 当神经网络较为复杂时,模型训练还是需要几天甚至几周的时间。如果希望尽可能缩短一次学习所需的时间,最直接的想法就是把不同的训练样本分配给多个GPU 或CPU,然后在这些设备上同时进行训练,即实现并行化训练。这种方法也被称作{\small\sffamily\bfseries{数据并行}}\index{数据并行}。具体实现时,有两种常用的并行化策略:(参数)同步更新和(参数)异步更新。 \begin{itemize} \vspace{0.5em} \item {\small\sffamily\bfseries{同步更新}}\index{同步更新}(Synchronous Update)\index{Synchronous Update}是指所有计算设备完成计算后,统一汇总并更新参数。当所有设备的反向传播算法完成之后同步更新参数,不会出现单个设备单独对参数进行更新的情况。这种方法效果稳定,但是效率比较低,在同步更新时,每一次参数更新都需要所有设备统一开始、统一结束,如果设备的运行速度不一致,那么每一次参数更新都需要等待最慢的设备结束才能开始。 \vspace{0.5em} \item {\small\sffamily\bfseries{异步更新}}\index{异步更新}(Asynchronous Update)\index{Asynchronous Update}是指每个计算设备可以随时更新参数。不同设备可以随时读取参数的最新值,然后根据当前参数值和分配的训练样本,各自执行反向传播过程并独立更新参数。由于设备间不需要相互等待,这种方法并行度高。但是不同设备读取参数的时间可能不同,会造成不同设备上的参数不同步,导致这种方法不十分稳定,有可能无法达到较好的训练结果。 \vspace{0.5em} \end{itemize} \parinterval 图\ref{fig:9-47}对比了同步更新和异步更新的区别,在这个例子中,使用4台设备对一个两层神经网络中的参数进行更新,其中使用了一个{\small\bfnew{参数服务器}}\index{参数服务器}(Parameter Server\index{Parameter Server})来保存最新的参数,不同设备(图中的G1、G2、G3)可以通过同步或者异步的方式访问参数服务器。图中的$ {\bm \theta}_o $和$ {\bm \theta}_h $分别代表输出层和隐藏层的全部参数,操作Push($\cdot$) 表示设备向参数服务器传送梯度,操作Fetch($\cdot$)表示参数服务器向设备传送更新后的参数。 \parinterval 此外,在使用多个设备进行并行训练的时候,由于设备间带宽的限制,大量的数据传输会有较高的延时。对于复杂神经网络来说,设备间参数和梯度传递的时间消耗也会成为一个不得不考虑的因素。有时候,设备间数据传输的时间甚至比模型计算的时间都长,大大降低了并行度\upcite{xiao2017fast}。对于这种问题,可以考虑对数据进行压缩或者减少传输的次数来缓解问题。 %---------------------------------------------- \begin{figure}[htp] \centering \input{./Chapter9/Figures/figure-parallel} \caption{同步更新与异步更新对比} \label{fig:9-47} \end {figure} %------------------------------------------- %---------------------------------------------------------------------------------------- % NEW SUB-SECTION %---------------------------------------------------------------------------------------- \subsection{梯度消失、梯度爆炸和稳定性训练}\label{sec:9.4.4} \parinterval 深度学习中随着神经网络层数的增加,导数可能会出现指数级的下降或者指数级的增加,这种现象分别称为{\small\sffamily\bfseries{梯度消失}}\index{梯度消失}(Gradient Vanishing)\index{Gradient Vanishing}和{\small\sffamily\bfseries{梯度爆炸}}\index{梯度爆炸}(Gradient Explosion)\index{Gradient Explosion}。出现这两种现象的本质原因是反向传播过程中链式法则导致梯度矩阵的多次相乘。这类问题很容易导致训练的不稳定。 %---------------------------------------------------------------------------------------- % NEW SUBSUB-SECTION %---------------------------------------------------------------------------------------- \subsubsection{1. 易于优化的激活函数} \parinterval 网络训练过程中,如果每层网络的梯度都小于1,各层梯度的偏导数会与后面层传递而来的梯度相乘得到本层的梯度,并向前一层传递。该过程循环进行,最后导致梯度指数级地减小,这就产生了梯度消失现象。这种情况会导致神经网络层数较浅的部分梯度接近0。一般来说,产生很小梯度的原因是使用了类似于Sigmoid这样的激活函数,当输入的值过大或者过小的时候这类函数曲线会趋于直线,梯度近似为零。针对这个问题,主要的解决办法是使用更加易于优化的激活函数,比如,使用ReLU代替Sigmoid和Tanh作为激活函数。 %---------------------------------------------------------------------------------------- % NEW SUBSUB-SECTION %---------------------------------------------------------------------------------------- \subsubsection{2. 梯度裁剪} \parinterval 网络训练过程中,如果参数的初始值过大,而且每层网络的梯度都大于1,反向传播过程中,各层梯度的偏导数都会比较大,会导致梯度指数级地增长直至超出浮点数表示的范围,这就产生了梯度爆炸现象。如果发生这种情况,模型中离输入近的部分比离输入远的部分参数更新得更快,使网络变得非常不稳定。在极端情况下,模型的参数值变得非常大,甚至于溢出。针对梯度爆炸的问题,常用的解决办法为{\small\sffamily\bfseries{梯度裁剪}}\index{梯度裁剪}(Gradient Clipping)\index{Gradient Clipping}。 \parinterval 梯度裁剪的思想是设置一个梯度剪切阈值。在更新梯度的时候,如果梯度超过这个阈值,就将其强制限制在这个范围之内。假设梯度为${\mathbi{g}}$,梯度剪切阈值为$\sigma $,梯度裁剪过程可描述为下式: \begin{eqnarray} {\mathbi{g}}&=&{\textrm{min}}(\frac{\sigma}{\Vert {\mathbi{g}}\Vert},1){\mathbi{g}} \label{eq:9-43} \end{eqnarray} \noindent 其中,$ \Vert \cdot \Vert $表示$ l_2 $范数。梯度裁剪经常被使用在层数较多的模型中,如循环神经网络。 %---------------------------------------------------------------------------------------- % NEW SUBSUB-SECTION %---------------------------------------------------------------------------------------- \subsubsection{3. 稳定性训练} \parinterval 为了使神经网络模型训练更加稳定,通常还会考虑其他策略。 \begin{itemize} \item {\small\bfnew{批量标准化}}\index{批量标准化}(Batch Normalization)\index{Batch Normalization}。批量标准化,顾名思义,是以进行学习时的小批量样本为单位进行标准化\upcite{ioffe2015batch}。具体而言,就是对神经网络隐层输出的每一个维度,沿着批次的方向进行均值为0、方差为1的标准化。在深层神经网络中,每一层网络都可以使用批量标准化操作。这样使神经网络任意一层的输入不至于过大或过小,从而防止隐层中异常值导致模型状态的巨大改变。 \item {\small\bfnew{层标准化}}\index{层标准化}(Layer Normalization)\index{Layer Normalization}。类似的,层标准化更多是针对自然语言处理这种序列处理任务\upcite{Ba2016LayerN},它和批量标准化的原理是一样的,只是标准化操作是在序列上同一层网络的输出结果上进行的,也就是标准化操作沿着序列方向进行。这种方法可以很好的避免序列上不同位置神经网络输出结果的不可比性。同时由于标准化后所有的结果都转化到一个可比的范围,使得隐层状态可以在不同层之间进行自由组合。 \item {\small\bfnew{残差网络}}\index{残差网络}(Residual Networks)\index{Residual Networks}。最初,残差网络是为了解决神经网络持续加深时的模型退化问题\upcite{DBLP:journals/corr/HeZRS15},但是残差结构对解决梯度消失和梯度爆炸问题也有所帮助。有了残差结构,可以很轻松的构建几十甚至上百层的神经网络,而不用担心层数过深造成的梯度消失问题。残差网络的结构如图\ref{fig:9-51}所示。图\ref{fig:9-51}中右侧的曲线叫做{\small\bfnew{跳接}}\index{跳接}(Skip Connection)\index{Skip Connection},通过跳接在激活函数前,将上一层(或几层)之前的输出与本层计算的输出相加,将求和的结果输入到激活函数中作为本层的输出。假设残差结构的输入为$ {\mathbi{x}}_l $,输出为$ {\mathbi{x}}_{l+1} $,则有 \begin{eqnarray} {\mathbi{x}}_{l+1}&=&F({\mathbi{x}}_l)+{\mathbi{x}}_l \label{eq:9-44} \end{eqnarray} %---------------------------------------------- \begin{figure}[htp] \centering \input{./Chapter9/Figures/figure-residual-structure} \caption{残差网络的结构} \label{fig:9-51} \end{figure} %------------------------------------------- 相比较于简单的多层堆叠的结构,残差网络提供了跨层连接结构。这种结构在反向传播中有很大的好处,比如,对于一个训练样本,损失函数为$L$,$ \mathbi x_l $处的梯度可以进行如下计算: \begin{eqnarray} \frac{\partial L}{\partial {\mathbi{x}}_l}&=&\frac{\partial L}{\partial {\mathbi{x}}_{l+1}} \cdot \frac{\partial {\mathbi{x}}_{l+1}}{\partial {\mathbi{x}}_l}\nonumber\\ &=&\frac{\partial L}{\partial {\mathbi{x}}_{l+1}} \cdot \left(1+\frac{\partial F({\mathbi{x}}_l)}{\partial {\mathbi{x}}_l}\right)\nonumber\\ &=&\frac{\partial L}{\partial {\mathbi{x}}_{l+1}}+\frac{\partial L}{\partial {\mathbi{x}}_{l+1}} \cdot \frac{\partial F({\mathbi{x}}_l)}{\partial {\mathbi{x}}_l} \label{eq:9-45} \end{eqnarray} 由上式可知,残差网络可以将后一层的梯度$ \frac{\partial L}{\partial {\mathbi{x}}_{l+1}} $不经过任何乘法项直接传递到$ \frac{\partial L}{\partial {\mathbi{x}}_l} $,从而缓解了梯度经过每一层后多次累乘造成的梯度消失问题。在{\chaptertwelve}中还会看到,在机器翻译中残差结构可以和层标准化一起使用,而且这种组合可以取得很好的效果。 \end{itemize} %---------------------------------------------------------------------------------------- % NEW SUB-SECTION %---------------------------------------------------------------------------------------- \subsection{过拟合}\label{sec:9.4.5} \parinterval 理想中,我们总是希望尽可能地拟合输入和输出之间的函数关系,即让模型尽量模拟训练数据的中由输入预测答案的行为。然而,在实际应用中,模型在训练数据上的表现不一定代表了其在未见数据上的表现。如果模型训练过程中过度拟合训练数据,最终可能无法对未见数据做出准确的判断,这种现象叫做{\small\sffamily\bfseries{过拟合}}\index{过拟合}(Overfitting)\index{Overfitting}。随着模型复杂度增加,特别在神经网络变得更深、更宽时,过拟合问题会表现得更为突出。如果训练数据量较小,而模型又很复杂,可以“完美”地拟合这些数据,这时过拟合也很容易发生。所以在模型训练时,往往不希望去完美拟合训练数据中的每一个样本。 \parinterval {\small\sffamily\bfseries{正则化}}\index{正则化}(Regularization)\index{Regularization}是常见的缓解过拟合问题的手段,通过在损失函数中加上用来刻画模型复杂程度的正则项来惩罚过度复杂的模型,从而避免神经网络过度学习造成过拟合。引入正则化处理之后目标函数变为$ J({\bm \theta})+\lambda R({\bm \theta}) $,其中$ J({\bm \theta}) $是原来的代价函数,$ R({\bm \theta}) $即为正则项,$ \lambda $用来调节正则项对结果影响的程度。 \parinterval 过拟合的模型通常会表现为部分非零参数过多或者参数的值过大。这种参数产生的原因在于模型需要复杂的参数才能匹配样本中的个别现象甚至噪声。基于此,常见的正则化方法有L1正则化和L2正则化,其命名方式是由$ R({\bm \theta}) $的计算形式来决定的。在L1正则化中,$ R({\bm \theta}) $即为参数$ {\bm \theta} $的$ l_1 $范数,即$ R({\bm \theta}) ={\Vert {\bm \theta}\Vert}_1=\sum\limits_{i=1}^{n}{\vert \theta_i\vert} $;在L2正则化中,$ R(\bm \theta) $即为参数${\bm \theta} $的$ l_2 $范数的平方,即$ R(\bm \theta) =({\Vert {\bm \theta}\Vert}_2)^2=\sum\limits_{i=1}^{n}{\theta_i^2} $。L1正则化中的正则项衡量了模型权数中的绝对值大小,倾向于生成值为0的参数,从而让参数变得更加稀疏;而L2正则化由于平方的加入,当参数中的某一项小到一定程度,比如0.001的时候,参数的平方结果已经可以忽略不计了,因此L2正则化会倾向生成很小的参数,在这种情况下,即便训练数据中含有少量随机噪音,模型也不太容易通过增加个别参数的值来对噪声进行过度拟合,即提高了模型的抗扰动能力。 \parinterval 此外,在{\chaptertwelve}即将介绍的Dropout和标签平滑方法也可以被看作是一种正则化操作。它们都可以提高模型在未见数据上的泛化能力。 %---------------------------------------------------------------------------------------- % NEW SUB-SECTION %---------------------------------------------------------------------------------------- \subsection{反向传播}\label{sec:9.4.6} \parinterval 为了获取梯度,最常用的做法是使用自动微分技术,通常通过反向传播来实现。该方法分为两个计算过程:前向计算和反向计算。前向计算的目的是从输入开始,逐层计算,得到网络的输出,并记录计算图中每个节点的局部输出。反向计算过程从输出端反向计算梯度,这个过程可以被看作是一种梯度的“传播”,最终计算图中所有节点都会得到相应的梯度结果。 \parinterval 这里,首先对反向传播算法中涉及到的符号进行统一说明。图\ref{fig:9-52}是一个多层神经网络,其中层$ k-1 $、层$ k $、层$ k+1 $均为神经网络中的隐藏层,层$ K $为神经网络中的输出层。为了化简问题,这里每层网络没有使用偏置项。 %---------------------------------------------- \begin{figure}[htp] \centering \input{./Chapter9/Figures/figure-multilayer-neural-network-example} \caption{多层神经网络实例} \label{fig:9-52} \end{figure} %------------------------------------------- \parinterval 下面是一些符号的定义: \begin{itemize} \vspace{0.5em} \item $ h_i^k $:第$ k $层第$ i $个神经元的输出; \vspace{0.5em} \item $ {\mathbi{h}}^k $:第$ k $层的输出。若第$ k $层有$ n $个神经元,则: \begin{eqnarray} {\mathbi{h}}^k&=&(h_1^k,h_2^k,\dots,h_n^k) \end{eqnarray} \vspace{0.5em} \item $ w_{j,i}^k $:第$ k-1 $层神经元$ j $与第$ k $层神经元$ i $的连接权重; \vspace{0.5em} \item $ {\mathbi{W}}^k $:第$ k-1 $层与第$ k $层的连接权重。若第$ k-1 $层有$ m $个神经元,第$ k $层有$ n $个神经元,则: \begin{eqnarray} {\mathbi{W}}^k &=& \begin{pmatrix} w_{1,1}^k & w_{1,2}^k & \dots & w_{1,n}^k\\w_{2,1}^k & \dots & \dots & \dots\\ \dots & \dots & \dots & \dots\\w_{m,1}^k & \dots & \dots & w_{m,n}^k\end{pmatrix} \end{eqnarray} \vspace{0.5em} \item $ {\mathbi{h}}^K $:整个网络的输出; \vspace{0.5em} \item $ {\mathbi{s}}^k $:第$ k $层的线性变换结果,其计算方式如下: \begin{eqnarray} {\mathbi{s}}^k & = & {\mathbi{h}}^{k-1}{\mathbi{W}}^k \nonumber \\ & = & \sum{h_j^{k-1}w_{j,i}^k} \label{eq:9-109} \end{eqnarray} \vspace{0.5em} \item $ f^k $:第$ k $层的激活函数,$ {\mathbi{h}}^k=f^k({\mathbi{s}}^k)$。 \vspace{0.5em} \end{itemize} \parinterval 于是,在神经网络的第$ k $层,前向计算过程可以描述为: \begin{eqnarray} {\mathbi{h}}^k & = & f^k({\mathbi{s}}^k) \nonumber \nonumber \\ & = & f^k({\mathbi{h}}^{k-1}{\mathbi{W}}^k) \label{eq:9-46} \end{eqnarray} %---------------------------------------------------------------------------------------- % NEW SUBSUB-SECTION %---------------------------------------------------------------------------------------- \subsubsection{1. 输出层的反向传播} \parinterval 反向传播是由输出层开始计算梯度,之后逆向传播到每一层网络,直至到达输入层。这里首先讨论输出层的反向传播机制。输出层(即第$ K $层)可以被描述为公式\eqref{eq:9-47}\eqref{eq:9-48}: \begin{eqnarray} {\mathbi{h}}^K&=&f^K({\mathbi{s}}^K)\label{eq:9-47}\\ {\mathbi{s}}^K&=&{\mathbi{h}}^{K-1}{\mathbi{W}}^K \label{eq:9-48} \end{eqnarray} \noindent 也就是,输出层(第$ K $层)的输入$ {\mathbi{h}}^{K-1} $先经过线性变换右乘${\mathbi{W}}^K $转换为中间状态$ {\mathbi{s}}^K $,之后${\mathbi{s}}^K $再经过激活函数$ f^K(\cdot) $变为$ {\mathbi{h}}^K $,$ {\mathbi{h}}^K $即为第$ K $层(输出层)的输出。最后,$ {\mathbi{h}}^K $和标准答案一起计算得到损失函数的值\footnote{在反向传播算法部分我们以某一个训练样本为例进行讲解,因而这里不再计算代价函数$J$,而是损失函数$L$。},记为$ L $。以上过程如图\ref{fig:9-53}所示。这里将输出层的前向计算过程细化为两个阶段:线性变换阶段和激活函数+损失函数阶段。 %---------------------------------------------- \begin{figure}[htp] \centering \input{./Chapter9/Figures/figure-forward-propagation-output} \caption{输出层的前向计算过程} \label{fig:9-53} \end{figure} %------------------------------------------- \parinterval 在前向过程中,计算次序为${\mathbi{h}}^{K-1}\rightarrow {\mathbi{s}}^K\rightarrow {\mathbi{h}}^K \rightarrow L $。而反向计算中节点访问的次序与之相反: \begin{itemize} \vspace{0.5em} \item 第一步,获取$ \frac{\partial L}{\partial {\mathbi{h}}^K} $,即计算损失函数$ L $关于网络输出结果$ {\mathbi{h}}^K $的梯度,并将梯度向前传递; \vspace{0.5em} \item 第二步,获取$ \frac{\partial L}{\partial {\mathbi{s}}^K} $,即计算损失函数$ L $关于中间状态$ {\mathbi{s}}^K $的梯度,并将梯度向前传递; \vspace{0.5em} \item 第三步,获取$ \frac{\partial L}{\partial {\mathbi{h}}^{K-1}} $和$ \frac{\partial L}{\partial{\mathbi{W}}^K} $,即计算损失函数$ L $关于第$ K-1 $层输出结果$ {\mathbi{h}}^{K-1} $的梯度,并将梯度向前传递;同时计算损失函数$ L $关于第$K$层参数${\mathbi{W}}^K $的梯度,并用于参数更新。 \vspace{0.5em} \end{itemize} \parinterval 对于前两个步骤,如图\ref{fig:9-54}所示: %---------------------------------------------- \begin{figure}[htp] \centering \input{./Chapter9/Figures/figure-back-propagation-output1} \caption{从损失到中间状态的反向传播(输出层)} \label{fig:9-54} \end{figure} %------------------------------------------- \parinterval 在第一阶段,计算的目标是得到损失函数$ L $关于第$ K $层中间状态$ {\mathbi{s}}^K $的梯度,这里令$ {\bm \pi}^K= \frac{\partial L}{\partial{\mathbi{s}}^K} $,利用链式法则有: \begin{eqnarray} {\bm \pi}^K&=& \frac{\partial L}{\partial {\mathbi{s}}^K}\nonumber\\ &=&\frac{\partial L}{\partial {\mathbi{h}}^K}\cdot \frac{\partial {\mathbi{h}}^K}{\partial {\mathbi{s}}^K}\nonumber\\ &=&\frac{\partial L}{\partial {\mathbi{h}}^K}\cdot \frac{\partial f^K({\mathbi{s}}^K)}{\partial {\mathbi{s}}^K} \label{eq:9-49} \end{eqnarray} \noindent 其中: \begin{spacing}{1.6} \begin{itemize} \vspace{0.5em} \item $ \frac{\partial L}{\partial {\mathbi{h}}^K} $表示损失函数$ L $相对网络输出$ {\mathbi{h}}^K $的梯度。比如,对于平方损失$ L=\frac{1}{2}{\Vert \widetilde {\mathbi{y}}-{\mathbi{h}}^K\Vert}^2 $,有$ \frac{\partial L}{\partial {\mathbi{h}}^K}= \widetilde{\mathbi{y}} -{\mathbi{h}}^K $。计算结束后,将$ \frac{\partial L}{\partial {\mathbi{h}}^K} $向前传递。 \vspace{0.5em} \item $ \frac{\partial f^T({\mathbi{s}}^K)}{\partial {\mathbi{s}}^K} $表示激活函数相对于其输入$ {\mathbi{s}}^K $的梯度。比如,对于Sigmoid函数$ f({\mathbi{s}})=\frac{1}{1+{\textrm e}^{- {\mathbi{s}}}}$,有$ \frac{\partial f({\mathbi{s}})}{\partial {\mathbi{s}}}=f({\mathbi{s}}) (1-f({\mathbi{s}}))$ \vspace{0.5em} \end{itemize} \end{spacing} \parinterval 这个过程可以得到$ {\mathbi{s}}^K $节点处的梯度$ {\bm \pi}^K= \frac{\partial L}{\partial {\mathbi{s}}^K} $,在后续的过程中可以直接使用其作为前一层提供的梯度计算结果,而不需要从$ {\mathbi{h}}^K $节点处重新计算。这也体现了自动微分与符号微分的差别,对于计算图的每一个阶段,并不需要得到完成的微分表达式,而是通过前一层提供的梯度,直接计算当前的梯度即可,这样避免了大量的重复计算。 \parinterval 在得到$ {\bm \pi}^K= \frac{\partial L}{\partial {\mathbi{s}}^K} $之后,下一步的目标是:1)计算损失函数$ L $相对于第$ K-1 $层与输出层之间连接权重$ {\mathbi{W}}^K $的梯度;2)计算损失函数$ L $相对于神经网络网络第$ K-1 $层输出结果$ {\mathbi{h}}^{K-1} $的梯度。这部分内容如图\ref{fig:9-55}所示。 %---------------------------------------------- \begin{figure}[htp] \centering \input{./Chapter9/Figures/figure-back-propagation-output2} \caption{从中间状态到输入的反向传播(输出层)} \label{fig:9-55} \end{figure} %------------------------------------------- \noindent 具体来说: \begin{itemize} \vspace{0.5em} \item 计算$ \frac{\partial L}{\partial {\mathbi{W}}^K} $ :由于$ {\mathbi{s}}^K={\mathbi{h}}^{K-1}{\mathbi{W}}^K $,且损失函数$ L $关于$ {\mathbi{s}}^K $的梯度$ {\bm \pi}^K= \frac{\partial L}{\partial {\mathbi{s}}^K} $已经得到,于是有 \begin{eqnarray} \frac{\partial L}{\partial {\mathbi{W}}^K}&=&{\left[{\mathbi{h}}^{K-1}\right]}^{\textrm T} {\bm \pi}^K \label{eq:9-50} \end{eqnarray} 其中${[\cdot]}^{\textrm T}$表示转置操作\footnote{如果$ {\mathbi{h}}^{K-1} $是一个向量,$ {\left[{\mathbi{h}}^{K-1}\right]}^{\textrm T} $表示向量的转置,比如,行向量变成列向量;如果$ {\mathbi{h}}^{K-1} $ 是一个高阶张量,$ {\left[{\mathbi{h}}^{K-1}\right]}^{\textrm T} $表示沿着张量最后两个方向的转置。}。 \vspace{0.5em} \item 计算$ \frac{\partial L}{\partial{\mathbi{h}}^{K-1}} $ :与求解$ \frac{\partial L}{\partial {\mathbi{W}}^K} $类似,可以得到 \begin{eqnarray} \frac{\partial L}{\partial {\mathbi{h}}^{K-1}}&=&{\bm \pi}^K{\left[{\mathbi{W}}^K\right]}^{\textrm T} \label{eq:9-51} \end{eqnarray} 梯度$ \frac{\partial L}{\partial {\mathbi{h}}^{K-1}} $ 需要继续向前一层传递,用于计算网络中间层的梯度。$ \frac{\partial L}{\partial {\mathbi{W}}^K} $会作为参数$ {\mathbi{W}}^K $的梯度计算结果,用于模型参数的更新\footnote{$ {\mathbi{W}}^K $可能会在同一个网络中被多次使用(类似于网络不同部分共享同一个参数),这时需要累加相关计算节点处得到的$ \frac{\partial L}{\partial {\mathbi{W}}^K} $。}。 \vspace{0.5em} \end{itemize} %---------------------------------------------------------------------------------------- % NEW SUBSUB-SECTION %---------------------------------------------------------------------------------------- \subsubsection{2. 隐藏层的反向传播} \parinterval 对于第$ k $个隐藏层,有: \begin{eqnarray} {\mathbi{h}}^k&=&f^k({\mathbi{s}}^k) \label{eq:9-52}\\ {\mathbi{s}}^k&=&{\mathbi{h}}^{k-1}{\mathbi{W}}^k \label{eq:9-53} \end{eqnarray} \noindent 其中,$ {\mathbi{h}}^k $、$ {\mathbi{s}}^k $、$ {\mathbi{h}}^{k-1} $、${\mathbi{W}}^k $和分别表示隐藏层的输出、中间状态、隐藏层的输入和参数矩阵。隐藏层的前向计算过程如图\ref{fig:9-56}所示,第$ k-1 $ 层神经元的输出$ {\mathbi{h}}^{k-1} $经过线性变换和激活函数后,将计算结果$ {\mathbi{h}}^k $向后一层传递。 %---------------------------------------------- \begin{figure}[htp] \centering \input{./Chapter9/Figures/figure-forward-propagation-hid} \caption{隐藏层的前向计算过程} \label{fig:9-56} \end{figure} %------------------------------------------- \parinterval 与输出层类似,隐藏层的反向传播也是逐层逆向计算。 \begin{itemize} \vspace{0.5em} \item 第一步,获取$\frac{\partial L}{\partial {\mathbi{s}}^k} $,即计算损失函数$ L $关于第$ k $层中间状态$ {\mathbi{s}}^k$的梯度,并将梯度向前传递; \vspace{0.5em} \item 第二步,获取$\frac{\partial L}{\partial {\mathbi{h}}^{k-1}} $和$\frac{\partial L}{\partial {\mathbi{W}}^k} $,即计算损失函数$ L $关于第$ k-1 $层输出结果$ {\mathbi{h}}^{k-1} $的梯度,并将梯度向前传递。同时计算损失函数$ L $关于参数$ {\mathbi{W}}^k $的梯度,并用于参数更新。 \vspace{0.5em} \end{itemize} \parinterval 这两步和输出层的反向传播十分类似。可以利用链式法则得到: \begin{eqnarray} \frac{\partial L}{\partial {\mathbi{s}}^k}&=&\frac{\partial L}{\partial {\mathbi{h}}^k}\cdot \frac{\partial {\mathbi{h}}^k}{\partial {\mathbi{s}}^k}\nonumber\\ &=&\frac{\partial L}{\partial {\mathbi{h}}^k}\cdot \frac{\partial f^k({\mathbi{s}}^k)}{\partial {\mathbi{s}}^k} \label{eq:9-54} \end{eqnarray} \noindent 其中$ \frac{\partial L}{\partial {\mathbi{h}}^k} $表示损失函数$ L $相对该隐藏层输出${\mathbi{h}}^k $的梯度。进一步,由于$ {\mathbi{s}}^k ={\mathbi{h}}^{k-1}{\mathbi{W}}^k$,可以得到 \begin{eqnarray} \frac{\partial L}{\partial {\mathbi{W}}^k}&=&{\left[{\mathbi{h}}^{k-1}\right]}^{\textrm T}\cdot \frac{\partial L}{\partial {\mathbi{s}}^k}\label{eq:9-55}\\ \nonumber\\ \frac{\partial L}{\partial {\mathbi{h}}^{k-1}}&= &\frac{\partial L}{\partial {\mathbi{s}}^k} \cdot {\left[{\mathbi{W}}^k\right]}^{\textrm T} \label{eq:9-56} \end{eqnarray} \parinterval $ \frac{\partial L}{\partial {\mathbi{h}}^{k-1}} $需要继续向第$ k-1 $隐藏层传递。$ \frac{\partial L}{\partial {\mathbi{W}}^k} $会作为参数的梯度用于参数更新。图\ref{fig:9-57}展示了隐藏层反向传播的计算过程。 %---------------------------------------------- \begin{figure}[htp] \centering \input{./Chapter9/Figures/figure-back-propagation-hid} \caption{隐藏层的反向传播} \label{fig:9-57} \end{figure} %------------------------------------------- \parinterval 综合输出层和隐藏层的反向传播方法,可以得到神经网络中任意位置和任意参数的梯度信息。只需要根据网络的拓扑结构,逆向访问每一个节点,并执行上述反向计算过程。 %---------------------------------------------------------------------------------------- % NEW SECTION %---------------------------------------------------------------------------------------- \sectionnewpage \section{神经语言模型}\label{sec9:nlm} \parinterval 神经网络提供了一种工具,只要将问题的输入和输出定义好,就可以学习输入和输出之间的对应关系。显然,很多自然语言处理任务都可以用神经网络进行实现。比如,在机器翻译中,可以把输入的源语言句子和输出的目标语言句子用神经网络建模;在文本分类中,可以把输入的文本内容和输出的类别标签进行神经网络建模,等等。 \parinterval 为了更好地理解神经网络和深度学习在自然语言处理中的应用。这里介绍一种基于神经网络的语言建模方法\ \dash \ {\small\sffamily\bfseries{神经语言模型}}\index{神经语言模型}(Neural Language Model)\index{Neural Language Model}。可以说,神经语言模型是深度学习时代下自然语言处理的标志性成果,它所涉及的许多概念至今仍是研究的热点,比如:词嵌入、表示学习、预训练等。此外,神经语言模型也为机器翻译的建模提供了很好的思路。从某种意义上说,机器翻译的深度学习建模的很多灵感均来自神经语言模型,二者在一定程度上是统一的。 %---------------------------------------------------------------------------------------- % NEW SUB-SECTION %---------------------------------------------------------------------------------------- \subsection{基于前馈神经网络的语言模型} \parinterval 回顾一下{\chaptertwo}的内容,语言建模的问题被定义为:对于一个词序列$ w_1w_2\dots w_m$,如何计算该词序列的可能性?词序列出现的概率可以通过链式法则得到: \begin{eqnarray} \funp{P}(w_1w_2\dots w_m)&=&\funp{P}(w_1)\funp{P}(w_2|w_1)\funp{P}(w_3|w_1w_2)\dots \funp{P}(w_m|w_1\dots w_{m-1}) \label{eq:9-57} \end{eqnarray} \parinterval 由于$ \funp{P}(w_m|w_1\dots w_{m-1}) $需要建模$ m-1 $个词构成的历史信息,这个模型仍然很复杂。于是就有了基于局部历史的$n$-gram语言模型: \begin{eqnarray} \funp{P}(w_m|w_1\dots w_{m-1})&=&\funp{P}(w_m|w_{m-n+1}\dots w_{m-1}) \label{eq:9-58} \end{eqnarray} \noindent 其中,$\funp{P}(w_m|w_{m-n+1}\dots w_{m-1}) $可以通过相对频次估计进行计算,如公式\eqref{eq:9-110}所示,其中$ {\textrm{count}}(\cdot) $表示在训练数据上的频次: \begin{eqnarray} \funp{P}(w_m|w_{m-n+1}\dots w_{m-1})&=&\frac{{\textrm{count}}(w_{m-n+1}\dots w_m)}{{\textrm{count}}(w_{m-n+1}\dots w_{m-1})} \label{eq:9-110} \end{eqnarray} \noindent 这里,$ w_{m-n+1}\dots w_m $也被称作$n$-gram,即$ n $元语法单元。$n$-gram语言模型是一种典型的基于离散表示的模型。在这个模型中,所有的词都被看作是离散的符号。因此,不同单词之间是“完全”不同的。另一方面,语言现象是十分多样的,即使在很大的语料库上也无法得到所有$n$-gram的准确统计。甚至很多$n$-gram在训练数据中从未出现过。由于不同$n$-gram 间没有建立直接的联系, $n$-gram 语言模型往往面临数据稀疏的问题。比如,虽然在训练数据中见过“景色”这个词,但是测试数据中却出现了“风景”这个词,恰巧“风景”在训练数据中没有出现过。即使“风景”和“景色”表达的是相同的意思,$n$-gram语言模型仍然会把“风景”看作未登录词,赋予一个很低的概率值。 \parinterval 上面这个问题的本质是$n$-gram语言模型对词使用了离散化表示,即每个单词都孤立的对应词表中的一个索引,词与词之间在语义上没有任何“重叠”。神经语言模型重新定义了这个问题。这里并不需要显性地通过统计离散的$n$-gram的频度,而是直接设计一个神经网络模型$ g(\cdot)$来估计单词生成的概率,如下所示: \begin{eqnarray} \funp{P}(w_m|w_1\dots w_{m-1})&=&g(w_1\dots w_m) \label{eq:9-59} \end{eqnarray} \vspace{-0.4em} \parinterval $ g(w_1\dots w_m) $实际上是一个多层神经网络。与$n$-gram语言模型不同的是$ g(w_1\dots \\ w_m) $并不包含对$ w_1\dots w_m $的任何假设,比如,在神经网络模型中,单词不再是离散的符号,而是连续空间上的点。这样两个单词之间也不再是简单的非0即1的关系,而是具有可计算的距离。此外,由于没有对$ w_1\dots w_m $进行任何结构性的假设,神经语言模型对问题进行端到端学习。通过设计不同的神经网络$ g(\cdot)$,可以从不同的角度“定义”序列的表示问题。当然,这么说可能还有一些抽象,下面就一起看看神经语言模型究竟是什么样子的。 %---------------------------------------------------------------------------------------- % NEW SUBSUB-SECTION %---------------------------------------------------------------------------------------- \subsubsection{1. 模型结构} \parinterval 最具代表性的神经语言模型是{\small\sffamily\bfseries{前馈神经网络语言模型}}\index{前馈神经网络语言模型}(Feed-forward Neural Network Language Model,FNNLM\index{FNNLM})。这种语言模型的目标是用神经网络计算$ \funp{P}(w_m|w_{m-n+1}\dots w_{m-1}) $,之后将多个$n$-gram的概率相乘得到整个序列的概率\upcite{bengio2003a}。 %---------------------------------------------- \begin{figure}[htp] \centering \input{./Chapter9/Figures/figure-4-gram} \caption{4-gram前馈神经网络语言架构} \label{fig:9-60} \end{figure} %------------------------------------------- \parinterval 为了有一个直观的认识,这里以4-gram的FNNLM为例,即根据前三个单词$ w_{i-3} $、 $ w_{i-2} $ 、$ w_{i-1} $预测当前单词$ w_i $的概率。模型结构如图\ref{fig:9-60}所示。从结构上看,FNNLM是一个典型的多层神经网络结构。主要有三层: \begin{itemize} \vspace{0.3em} \item 输入层(词的分布式表示层),即把输入的离散的单词变为分布式表示对应的实数向量; \vspace{0.3em} \item 隐藏层,即将得到的词的分布式表示进行线性和非线性变换; \vspace{0.3em} \item 输出层(Softmax层),根据隐藏层的输出预测单词的概率分布。 \vspace{0.3em} \end{itemize} \parinterval 这三层堆叠在一起构成了整个网络,而且也可以加入从词的分布式表示直接到输出层的连接(红色虚线箭头)。 %---------------------------------------------------------------------------------------- % NEW SUBSUB-SECTION %---------------------------------------------------------------------------------------- \subsubsection{2. 输入层} \parinterval $ {\mathbi{o}}_{i-3} $、 $ {\mathbi{o}}_{i-2} $ 、$ {\mathbi{o}}_{i-1} $为该语言模型的输入(绿色方框),输入为每个词(如上文的$ w_{i-1}$、$ w_{i-2}$等)的One-hot向量表示(维度大小与词表大小一致),每个One-hot向量仅一维为1,其余为0,比如:$ (0,0,1,\dots,0) $ 表示词表中第三个单词。之后把One-hot向量乘以一个矩阵$ \mathbi{C} $得到单词的分布式表示(紫色方框)。令$ {\mathbi{o}}_i $为第$ i $个词的One-hot表示,$ {\mathbi{e}}_i $为第$ i $个词的分布式表示,则分布式表示$ {\mathbi{e}}_i $的计算方式如下: \begin{eqnarray} {\mathbi{e}}_i&=&{\mathbi{o}}_i\cdot{\mathbi{C}} \label{eq:9-60} \end{eqnarray} \noindent 这里的$ \mathbi{C} $可以被理解为一个查询表,根据$ {\mathbi{o}}_i $中为1的那一维,在$ \mathbi{C}$中索引到相应的行进行输出(结果是一个行向量)。通常,把${\mathbi{e}}_i$这种单词的实数向量表示称为词嵌入,把$ \mathbi{C} $称为词嵌入矩阵。 %---------------------------------------------------------------------------------------- % NEW SUBSUB-SECTION %---------------------------------------------------------------------------------------- \subsubsection{3. 隐藏层和输出层} \parinterval 把得到的$ {\mathbi{e}}_0 $、$ {\mathbi{e}}_1 $、$ {\mathbi{e}}_2 $三个向量级联在一起,经过两层网络,最后通过Softmax函数(橙色方框)得到输出,具体过程为: \begin{eqnarray} {\mathbi{y}}&=&{\textrm{Softmax}}({\mathbi{h}}_0{\mathbi{U}})\label{eq:9-61}\\ {\mathbi{h}}_0&=&{\textrm{Tanh}}([{\mathbi{e}}_{i-3},{\mathbi{e}}_{i-2},{\mathbi{e}}_{i-1}]{\mathbi{H}}+{\mathbi{d}}) \label{eq:9-62} \end{eqnarray} \noindent 这里,输出$ {\mathbi{y}}$是词表$V$上的一个分布,来表示$\funp{P}(w_i|w_{i-1},w_{i-2},w_{i-3}) $。$ {\mathbi{U}}$、${\mathbi{H}}$和${\mathbi{d}}$是模型的参数。这样,对于给定的单词$w_i$可以用$y_i$得到其概率,其中$y_i$表示向量${\mathbi{y}}$的第$i$维。 \parinterval Softmax($\cdot$)的作用是根据输入的$|V|$维向量(即${\mathbi{h}}_0{\mathbi{U}}$),得到一个$|V|$维的分布。令${\bm \tau}$表示Softmax($\cdot$)的输入向量,Softmax函数可以被定义为: \begin{eqnarray} \textrm{Softmax}(\tau_i)&=&\frac{\textrm{exp}(\tau_i)} {\sum_{i'=1}^{|V|} \textrm{exp}(\tau_{i'})} \label{eq:9-120} \end{eqnarray} \noindent 这里,exp($\cdot$)表示指数函数。Softmax函数是一个典型的归一化函数,它可以将输入的向量的每一维都转化为0-1之间的数,同时保证所有维的和等于1。Softmax的另一个优点是,它本身(对于输出的每一维)都是可微的(如图\ref{fig:softmax}所示),因此可以直接使用基于梯度的方法进行优化。实际上,Softmax经常被用于分类任务。也可以把机器翻译中目标语单词的生成看作一个分类问题,它的类别数是|$V$|。 %---------------------------------------------- \begin{figure}[htp] \centering \input{./Chapter9/Figures/figure-softmax} \caption{ Softmax函数(一维)所对应的曲线} \label{fig:softmax} \end{figure} %---------------------------------------------- %---------------------------------------------------------------------------------------- % NEW SUBSUB-SECTION %---------------------------------------------------------------------------------------- \subsubsection{4. 连续空间表示能力} \parinterval 值得注意的是,在FNNLM中,单词已经不再是一个孤立的符号串,而是被表示为一个实数向量。这样,两个单词之间可以通过向量计算某种相似度或距离。这导致相似的单词会具有相似的分布,进而缓解$n$-gram语言模型的问题\ \dash \ 明明意思很相近的两个词但是概率估计的结果差异性却很大。 \parinterval 在FNNLM中,所有的参数、输入、输出都是连续变量,因此FNNLM也是一个典型的连续空间模型。通过使用交叉熵等损失函数,可以很容易地对FNNLM进行优化。比如,可以使用梯度下降方法对FNNLM的模型参数进行训练。 \parinterval 虽然FNNLM形式简单,却为处理自然语言提供了一个全新的视角。首先,该模型重新定义了“词是什么”\ \dash \ 它并非词典的一项,而是可以用一个连续实数向量进行表示的可计算的“量”。此外,由于$n$-gram不再是离散的符号序列,模型不需要记录$n$-gram,所以很好的缓解了上面所提到的数据稀疏问题,模型体积也大大减小。 \parinterval 当然,FNNLM也引发后人的许多思考,比如:神经网络每一层都学到了什么?是词法、句法,还是一些其他知识?如何理解词的分布式表示?等等。在随后的内容中也会看到,随着近几年深度学习和自然语言处理的发展,部分问题已经得到了很好的解答,但是仍有许多问题需要进一步探索。 %---------------------------------------------------------------------------------------- % NEW SUB-SECTION %---------------------------------------------------------------------------------------- \subsection{对于长序列的建模} \parinterval FNNLM固然有效,但是和传统的$n$-gram语言模型一样需要依赖有限上下文假设,也就是$ w_i $的生成概率只依赖于之前的$ n-1 $个单词。很自然的一个想法是引入更大范围的历史信息,这样可以捕捉单词间的长距离依赖。 %---------------------------------------------------------------------------------------- % NEW SUBSUB-SECTION %---------------------------------------------------------------------------------------- \subsubsection{1. 基于循环神经网络的语言模型} \parinterval 对于长距离依赖问题,可以通过{\small\sffamily\bfseries{循环神经网络}}\index{循环神经网络}(Recurrent Neural Network\index{Recurrent Neural Network},或RNN)进行求解。通过引入循环单元这种特殊的结构,循环神经网络可以对任意长度的历史进行建模,因此在一定程度上解决了传统$n$-gram语言模型有限历史的问题。正是基于这个优点,{\small\sffamily\bfseries{循环神经网络语言模型}}\index{循环神经网络语言模型}(RNNLM)\index{RNNLM}应运而生\upcite{mikolov2010recurrent}。 \parinterval 在循环神经网络中,输入和输出都是一个序列,分别记为$ ({\mathbi{x}}_1,\dots,{\mathbi{x}}_m) $和$ ({\mathbi{y}}_1,\dots,\\ {\mathbi{y}}_m) $。它们都可以被看作是时序序列,其中每个时刻$ t $都对应一个输入$ {\mathbi{x}}_t $和输出$ {\mathbi{y}}_t $。循环神经网络的核心是{\small\sffamily\bfseries{循环单元}}\index{循环单元}(RNN Cell)\index{RNN Cell},它读入前一个时刻循环单元的输出和当前时刻的输入,生成当前时刻循环单元的输出。图\ref{fig:9-62}展示了一个简单的循环单元结构,对于时刻$ t $,循环单元的输出被定义为: \begin{eqnarray} {\mathbi{h}}_t&=&{\textrm{Tanh}}({\mathbi{x}}_t{\mathbi{U}}+{\mathbi{h}}_{t-1}{\mathbi{W}}) \label{eq:9-63} \end{eqnarray} \noindent 其中,$ {\mathbi{h}}_t $表示$ t $时刻循环单元的输出,$ {\mathbi{h}}_{t-1} $表示$ t-1 $时刻循环单元的输出,${\mathbi{U}}$和${\mathbi{W}}$是模型的参数。可以看出,循环单元的结构其实很简单,只是一个对$ {\mathbi{h}}_{t-1} $和$ {\mathbi{x}}_t $的线性变换再加上一个Tanh函数。通过读入上一时刻的输出,当前时刻可以访问以前的历史信息。这个过程可以循环执行,这样就完成了对所有历史信息的建模。$ {\mathbi{h}}_t $可以被看作是序列在$ t $时刻的一种表示,也可以被看作是网络的一个隐藏层。进一步,$ {\mathbi{h}}_t $可以被送入输出层,得到$ t $时刻的输出: \begin{eqnarray} {\mathbi{y}}_t&=&{\textrm{Softmax}}({\mathbi{h}}_t{\mathbi{V}}) \label{eq:9-64} \end{eqnarray} \noindent 其中,${\mathbi{V}}$是输出层的模型参数。 \parinterval 图\ref{fig:9-62}展示了一个基于循环神经网络的语言模型结构。首先,所有输入的单词会被转换成分布式表示(红色部分),这个过程和FNNLM是一样的。之后,该模型堆叠了两层循环神经网络(绿色部分)。最后通过Softmax层(紫色部分)得到每个时刻的预测结果$ {\mathbi{y}}_t=\funp{P}(w_t|w_1\dots w_{t-1}) $。 %---------------------------------------------- \begin{figure}[htp] \centering \input{./Chapter9/Figures/figure-rnn-lm} \caption{基于循环神经网络的语言模型结构} \label{fig:9-62} \end{figure} %------------------------------------------- \parinterval RNNLM体现了一种“记忆”的能力。对于每一个时刻,循环单元都会保留一部分“以前”的信息,并加入“现在”的信息。从这个角度说,RNNLM本质上是一种记忆模型。在简单的循环单元结构的基础上,也有很多改进工作,如LSTM、GRU等模型,这部分内容将会在{\chapterten}进行介绍。 %---------------------------------------------------------------------------------------- % NEW SUBSUB-SECTION %---------------------------------------------------------------------------------------- \subsubsection{2. 其他类型的语言模型}\label{sec:9.5.2.2} \parinterval 通过引入记忆历史的能力,RNNLM缓解了$n$-gram模型中有限上下文的局限性,但依旧存在一些问题。随着序列变长,不同单词之间信息传递路径变长,信息传递的效率变低。对于长序列,很难通过很多次的循环单元操作保留很长的历史信息。过长的序列还容易引起梯度消失和梯度爆炸问题(详见\ref{sec:9.4.4}节),增加模型训练的难度。 \parinterval 针对这个问题,一种解决方法是使用卷积神经网络\upcite{Pham2016ConvolutionalNN}。卷积神经网络的特点是可以对一定窗口大小内的连续单词进行统一建模,这样非常易于捕捉窗口内单词之间的依赖,同时对它们进行整体的表示。进一步,卷积操作可以被多次叠加使用,通过更多层的卷积神经网络可以捕捉更大范围的依赖关系。关于卷积神经网络及其在机器翻译中的应用,{\chaptereleven}会有详细论述。 \parinterval 此外,研究者也提出了另一种新的结构$\ \dash \ ${\small\bfnew{自注意力机制}}\index{自注意力机制}(Self-attention Mechanism)\index{Self-attention Mechanism}。自注意力是一种特殊的神经网络结构,它可以对序列上任意两个词的相互作用直接进行建模,这样也就避免了循环神经网络中随着距离变长信息传递步骤增多的缺陷。在自然语言处理领域,自注意力机制被成功地应用在机器翻译任务上,著名的Transformer模型\upcite{vaswani2017attention}就是基于该原理工作的。{\chaptertwelve}会系统地介绍自注意力机制和Transformer模型。 %---------------------------------------------------------------------------------------- % NEW SUB-SECTION %---------------------------------------------------------------------------------------- \subsection{单词表示模型} \parinterval 在神经语言建模中,每个单词都会被表示为一个实数向量。这对应了一种单词的表示模型。下面就来看看传统的单词表示模型和这种基于实数向量的单词表示模型有何不同。 %---------------------------------------------------------------------------------------- % NEW SUBSUB-SECTION %---------------------------------------------------------------------------------------- \subsubsection{1. One-hot编码} \parinterval {\small\sffamily\bfseries{One-hot编码}}\index{One-hot编码}(也称{\small\sffamily\bfseries{独热编码}}\index{独热编码})是传统的单词表示方法。One-hot编码把单词表示为词汇表大小的0-1向量,其中只有该词所对应的那一项是1,而其余所有项都是0。举个简单的例子,假如有一个词典,里面包含10k个单词,并进行编号。那么每个单词都可以表示为一个10k维的One-hot向量,它仅在对应编号那个维度为1,其他维度都为0,如图\ref{fig:9-64}所示。 %---------------------------------------------- \begin{figure}[htp] \centering \input{./Chapter9/Figures/figure-one-hot} \caption{单词的One-hot表示 } \label{fig:9-64} \end{figure} %------------------------------------------- \parinterval One-hot编码的优点是形式简单、易于计算,而且这种表示与词典具有很好的对应关系,因此每个编码都可以进行解释。但是,One-hot编码把单词都看作是相互正交的向量。这导致所有单词之间没有任何的相关性。只要是不同的单词,在One-hot编码下都是完全不同的东西。比如,大家可能会期望诸如“桌子”和“椅子”之类的词具有一些相似性,但是One-hot编码把它们看作相似度为0的两个单词。 %---------------------------------------------------------------------------------------- % NEW SUBSUB-SECTION %---------------------------------------------------------------------------------------- \subsubsection{2. 分布式表示} \parinterval 神经语言模型中使用的是一种分布式表示。在神经语言模型里,每个单词不再是完全正交的0-1向量,而是在多维实数空间中的一个点,具体表现为一个实数向量。很多时候,也会把单词的这种分布式表示叫做词嵌入。 \parinterval 单词的分布式表示可以被看作是欧式空间中的一个点,因此单词之间的关系也可以通过空间的几何性质进行刻画。如图\ref{fig:9-65}所示,可以在一个512维空间上表示不同的单词。在这种表示下,“桌子”与“椅子”之间是具有一定的联系的。 %---------------------------------------------- \begin{figure}[htp] \centering \input{./Chapter9/Figures/figure-embedding} \caption{单词的分布式表示(词嵌入) } \label{fig:9-65} \end{figure} %------------------------------------------- \parinterval 那么,分布式表示中每个维度的含义是什么?可以把每一维度都理解为一种属性,比如一个人的身高、体重等。但是,神经网络模型更多的是把每个维度看作是单词的一种抽象“刻画”,是一种统计意义上的“语义”,而非简单的人工归纳的事物的一个个属性。使用这种连续空间的表示的好处在于,表示的内容(实数向量)可以进行计算和学习,因此可以通过模型训练得到更适用于自然语言处理的单词表示结果。 \parinterval 为了方便理解,看一个简单的例子。假如现在有个“预测下一个单词”的任务:有这样一个句子“屋里 要 摆放 一个 \rule[-3pt]{1cm}{0.05em}”,其中下划线的部分表示需要预测的下一个单词。如果模型在训练数据中看到过类似于“摆放 一个 桌子”这样的片段,那么就可以很自信的预测出“桌子”。另一方面,很容易知道,实际上与“桌子”相近的单词,如“椅子”,也是可以预测的单词的。但是,“椅子”恰巧没有出现在训练数据中,这时如果用One-hot编码来表示单词,显然无法把“椅子”填到下划线处;而如果使用单词的分布式表示,很容易就知道 “桌子”与“椅子”是相似的,因此预测“ 椅子”在一定程度上也是合理的。 \begin{example} 屋里 要 摆放 一个 \_\_\_\_\_ \hspace{0.5em} \quad \quad 预测下个词 \hspace{2em} 屋里 要 摆放 一个{ \red{桌子}} \hspace{3.2em}见过 \hspace{2em} 屋里 要 摆放 一个{ \blue{椅子}} \hspace{3.2em}没见过,但是仍然是合理预测 \end{example} \parinterval 关于单词的分布式表示还有一个经典的例子:通过词嵌入可以得到如下关系:$\textrm{“国王”}=\textrm{“女王”}-\textrm{“女人”} +\textrm{“男人”}$。从这个例子可以看出,词嵌入也具有一些代数性质,比如,词的分布式表示可以通过加、减等代数运算相互转换。图\ref{fig:9-66}展示了词嵌入在一个二维平面上的投影,不难发现,含义相近的单词分布比较临近。 %---------------------------------------------- \begin{figure}[htp] \centering \includegraphics[width=6cm]{./Chapter9/Figures/figure-word-graph.jpg} \caption{分布式表示的可视化} \label{fig:9-66} \end{figure} %------------------------------------------- \parinterval 语言模型的词嵌入是通过词嵌入矩阵进行存储的,矩阵中的每一行对应了一个词的分布式表示结果。图\ref{fig:9-67}展示了一个词嵌入矩阵的实例。 %---------------------------------------------- \begin{figure}[htp] \centering \input{./Chapter9/Figures/figure-embedding-matrix} \caption{词嵌入矩阵${\mathbi{C}}$} \label{fig:9-67} \end{figure} %------------------------------------------- \parinterval 通常,有两种方法得到词嵌入矩阵。一种方法是把词嵌入作为语言模型的一部分进行训练,不过由于语言模型往往较复杂,这种方法非常耗时;另一种方法使用更加轻便的外部训练方法,如word2vec\upcite{mikolov2013distributed}、Glove\upcite{DBLP:conf/emnlp/PenningtonSM14}等。由于这些方法的效率较高,因此可以使用更大规模的数据得到更好的词嵌入结果。 %---------------------------------------------------------------------------------------- % NEW SUB-SECTION %---------------------------------------------------------------------------------------- \subsection{句子表示模型} \parinterval 目前,词嵌入已经成为诸多自然语言处理系统的标配,也衍生出很多有趣的研究法方向。但是,冷静地看,词嵌入依旧存在一些问题:每个词都对应唯一的向量表示,那么对于一词多义现象,词义需要通过上下文进行区分,这时使用简单的词嵌入式是无法处理的。有一个著名的例子: \begin{example} Aaron is an employee of {\red{\underline{apple}}}. \hspace{2em} He finally ate the {\red{\underline{apple}}}. \end{example} \parinterval 这两句中“apple”的语义显然是不同的,第一句中的上下文“Jobs”和“CEO”可以帮助我们判断“apple”是一个公司名字,而不是水果。但是词嵌入只有一个结果,因此无法区分这两种情况。这个例子给我们一个启发:在一个句子中,不能孤立的看待单词,应同时考虑其上下文的信息。也就是需要一个能包含句子中上下文信息的表示模型。 \parinterval 回忆一下神经语言模型的结构,它需要在每个位置预测单词生成的概率。这个概率是由若干层神经网络进行计算后,通过输出层得到的。实际上,在送入输出层之前,系统已经得到了这个位置的一个向量(隐藏层的输出),因此可以把它看作是含有一部分上下文信息的表示结果。 \parinterval 以RNNLM为例,图\ref{fig:9-68}展示了一个由四个词组成的句子,这里使用了一个两层循环神经网络对其进行建模。可以看到,对于第三个位置,RNNLM已经积累了从第1个单词到第3个单词的信息,因此可以看作是单词1-3(“乔布斯\ 就职\ 于”)的一种表示;另一方面,第4个单词的词嵌入可以看作是“苹果”自身的表示。这样,可以把第3个位置RNNLM的输出和第4个位置的词嵌入进行合并,就得到了第4个位置上含有上下文信息的表示结果。从另一个角度说,这里得到了“苹果”的一种新的表示,它不仅包含苹果这个词自身的信息,也包含它前文的信息。 %---------------------------------------------- \begin{figure}[htp] \centering \input{./Chapter9/Figures/figure-rnn-model} \caption{基于RNN的表示模型(词+上下文)} \label{fig:9-68} \end{figure} %------------------------------------------- \parinterval 在自然语言处理中,{\small\sffamily\bfseries{句子表示模型}}\index{句子表示模型}是指把输入的句子进行分布式表示。不过表示的形式不一定是一个单独的向量。现在广泛使用的句子表示模型可以被描述为:给定一个输入的句子$ \{ w_1,\dots ,w_m\} $,得到一个表示序列$ \{{\mathbi{h}}_1,\dots ,{\mathbi{h}}_m\} $,其中$ {\mathbi{h}}_i $是句子在第$ i $个位置的表示结果。$ \{ {\mathbi{h}}_1,\dots ,{\mathbi{h}}_m\} $就被看作是{\small\sffamily\bfseries{句子的表示}}\index{句子的表示},它可以被送入下游模块。比如,在机器翻译任务中,可以用这种模型表示源语言句子,然后通过这种表示结果进行目标语译文的生成;在序列标注(如词性标注)任务中,可以对输入的句子进行表示,然后在这个表示之上构建标签预测模块。很多自然语言处理任务都可以用句子表示模型进行建模,因此句子的表示模型也是应用最广泛的深度学习模型之一。而学习这种表示的过程也被称作表示学习。 \parinterval 句子表示模型有两种训练方法。最简单的方法是把它作为目标系统中的一个模块进行训练,比如把句子表示模型作为机器翻译系统的一部分。也就是,并不单独训练句子表示模型,而是把它作为一个内部模块放到其他系统中。另一种方法是把句子表示作为独立的模块,用外部系统进行训练,之后把训练好的表示模型放入目标系统中,再进行微调。这种方法构成了一种新的范式:预训练+微调(pre-training + fine-tuning)。图\ref{fig:9-69}对比了这两种不同的方法。 %---------------------------------------------- \begin{figure}[htp] \centering \input{./Chapter9/Figures/figure-model-training} \caption{表示模型的训练方法(与目标任务联合训练 vs 用外部任务预训练)} \label{fig:9-69} \end{figure} %------------------------------------------- \parinterval 目前,句子表示模型的预训练方法在多项自然语言处理任务上取得了很好的效果。预训练模型也成为了当今自然语言处理中的热点方向,相关系统也在很多评测任务上刷榜。不过,上面介绍的模型是一种最简单句子表示模型,在{\chaptersixteen}会对一些前沿的预训练方法和句子表示模型进行介绍。 %---------------------------------------------------------------------------------------- % NEW SECTION %---------------------------------------------------------------------------------------- \sectionnewpage \section{小结及拓展阅读} \parinterval 神经网络为解决自然语言处理问题提供了全新的思路。而所谓深度学习也是建立在多层神经网络结构之上的一系列模型和方法。本章从神经网络的基本概念到其在语言建模中的应用进行了概述。由于篇幅所限,这里无法覆盖所有神经网络和深度学习的相关内容,感兴趣的读者可以进一步阅读\textit{Neural Network Methods in Natural Language Processing}\upcite{goldberg2017neural}和\textit{Deep Learning}\upcite{Goodfellow-et-al-2016}。此外,也有一些研究方向值得关注: \begin{itemize} \vspace{0.5em} \item 端到端学习是神经网络方法的特点之一。这样,系统开发者不需要设计输入和输出的隐含结构,甚至连特征工程都不再需要。但是,另一方面,由于这种端到端学习完全由神经网络自行完成,整个学习过程没有人的先验知识做指导,导致学习的结构和参数很难进行解释。针对这个问题也有很多研究者进行{\small\sffamily\bfseries{可解释机器学习}}\index{可解释机器学习}(Explainable Machine Learning)\index{Explainable Machine Learning}的研究\upcite{moraffah2020causal,Kovalerchuk2020SurveyOE,DoshiVelez2017TowardsAR}。对于自然语言处理,方法的可解释性是十分必要的。从另一个角度说,如何使用先验知识改善端到端学习也是很多人关注的方向\upcite{arthur2016incorporating,zhang-etal-2017-prior},比如,如何使用句法知识改善自然语言处理模型\upcite{stahlberg2016syntactically,currey2019incorporating,Yang2017TowardsBH,marevcek2018extracting,blevins2018deep}。 \vspace{0.5em} \item 为了进一步提高神经语言模型性能,除了改进模型,还可以在模型中引入新的结构或是其他有效信息,该领域也有很多典型工作值得关注。例如在神经语言模型中引入除了词嵌入以外的单词特征,如语言特征(形态、语法、语义特征等)\upcite{Wu2012FactoredLM,Adel2015SyntacticAS}、上下文信息\upcite{mikolov2012context,Wang2015LargerContextLM}、知识图谱等外部知识\upcite{Ahn2016ANK};或是在神经语言模型中引入字符级信息,将其作为字符特征单独\upcite{Kim2016CharacterAwareNL,Hwang2017CharacterlevelLM}或与单词特征一起\upcite{Onoe2016GatedWR,Verwimp2017CharacterWordLL}送入模型中;在神经语言模型中引入双向模型也是一种十分有效的尝试,在单词预测时可以同时利用来自过去和未来的文本信息\upcite{Graves2013HybridSR,bahdanau2014neural,Peters2018DeepCW}。 \vspace{0.5em} \item 词嵌入是自然语言处理近些年的重要进展。所谓“嵌入”是一类方法,理论上,把一个事物进行分布式表示的过程都可以被看作是广义上的“嵌入”。基于这种思想的表示学习也成为了自然语言处理中的前沿方法。比如,如何对树结构,甚至图结构进行分布式表示成为了分析自然语言的重要方法\upcite{DBLP:journals/corr/abs-1809-01854,Yin2018StructVAETL,Aharoni2017TowardsSN,Bastings2017GraphCE,KoncelKedziorski2019TextGF}。此外,除了语言建模,还有很多方式可以进行词嵌入的学习,比如,SENNA\upcite{2011Natural}、word2vec\upcite{DBLP:journals/corr/abs-1301-3781,mikolov2013distributed}、Glove\upcite{DBLP:conf/emnlp/PenningtonSM14}、CoVe\upcite{mccann2017learned} 等。 \vspace{0.5em} \end{itemize}