深度学习简介
你可能已经接触过编程,并开发过一两款程序。同时你可能读过关于深度学习或者机器学习的铺天盖地的报道,尽管很多时候它们被赋予了更广义的名字:人工智能。实际上,或者说幸运的是,大部分程序并不需要深度学习或者是更广义上的人工智能技术。例如,如果我们要为一台微波炉编写一个用户界面,只需要一点儿工夫我们便能设计出十几个按钮以及一系列能精确描述微波炉在各种情况下的表现的规则。再比如,假设我们要编写一个电子邮件客户端。这样的程序比微波炉要复杂一些,但我们还是可以沉下心来一步一步思考:客户端的用户界面将需要几个输入框来接受收件人、主题、邮件正文等,程序将监听键盘输入并写入一个缓冲区,然后将它们显示在相应的输入框中。当用户点击“发送”按钮时,我们需要检查收件人邮箱地址的格式是否正确,并检查邮件主题是否为空,或在主题为空时警告用户,而后用相应的协议传送邮件。
值得注意的是,在以上两个例子中,我们都不需要收集真实世界中的数据,也不需要系统地提取这些数据的特征。只要有充足的时间,我们的常识与编程技巧已经足够让我们完成任务。
与此同时,我们很容易就能找到一些连世界上最好的程序员也无法仅用编程技巧解决的简单问题。例如,假设我们想要编写一个判定一张图像中有没有猫的程序。这件事听起来好像很简单,对不对?程序只需要对每张输入图像输出“真”(表示有猫)或者“假”(表示无猫)即可。但令人惊讶的是,即使是世界上最优秀的计算机科学家和程序员也不懂如何编写这样的程序。
我们该从哪里入手呢?我们先进一步简化这个问题:若假设所有图像的高和宽都是同样的400像素大小,一个像素由红绿蓝三个值构成,那么一张图像就由近50万个数值表示。那么哪些数值隐藏着我们需要的信息呢?是所有数值的平均数,还是四个角的数值,抑或是图像中的某一个特别的点?事实上,要想解读图像中的内容,需要寻找仅仅在结合成千上万的数值时才会出现的特征,如边缘、质地、形状、眼睛、鼻子等,最终才能判断图像中是否有猫。
一种解决以上问题的思路是逆向思考。与其设计一个解决问题的程序,不如从最终的需求入手来寻找一个解决方案。事实上,这也是目前的机器学习和深度学习应用共同的核心思想:我们可以称其为“用数据编程”。与其枯坐在房间里思考怎么设计一个识别猫的程序,不如利用人类肉眼在图像中识别猫的能力。我们可以收集一些已知包含猫与不包含猫的真实图像,然后我们的目标就转化成如何从这些图像入手得到一个可以推断出图像中是否有猫的函数。这个函数的形式通常通过我们的知识来针对特定问题选定。例如,我们使用一个二次函数来判断图像中是否有猫,但是像二次函数系数值这样的函数参数的具体值则是通过数据来确定。
通俗来说,机器学习是一门讨论各式各样的适用于不同问题的函数形式,以及如何使用数据来有效地获取函数参数具体值的学科。深度学习是指机器学习中的一类函数,它们的形式通常为多层神经网络。近年来,仰仗着大数据集和强大的硬件,深度学习已逐渐成为处理图像、文本语料和声音信号等复杂高维度数据的主要方法。
我们现在正处于一个程序设计得到深度学习的帮助越来越多的时代。这可以说是计算机科学历史上的一个分水岭。举个例子,深度学习已经在你的手机里:拼写校正、语音识别、认出社交媒体照片里的好友们等。得益于优秀的算法、快速而廉价的算力、前所未有的大量数据以及强大的软件工具,如今大多数软件工程师都有能力建立复杂的模型来解决十年前连最优秀的科学家都觉得棘手的问题。
本书希望能帮助读者进入深度学习的浪潮中。我们希望结合数学、代码和样例让深度学习变得触手可及。本书不要求具有高深的数学或编程背景,我们将随着章节的发展逐一解释所需要的知识。更值得一提的是,本书的每一节都是一个可以独立运行的Jupyter记事本。读者可以从网上获得这些记事本,并且可以在个人电脑或云端服务器上执行它们。这样读者就可以随意改动书中的代码并得到及时反馈。我们希望本书能帮助和启发新一代的程序员、创业者、统计学家、生物学家,以及所有对深度学习感兴趣的人。
起源
虽然深度学习似乎是最近几年刚兴起的名词,但它所基于的神经网络模型和用数据编程的核心思想已经被研究了数百年。自古以来,人类就一直渴望能从数据中分析出预知未来的窍门。实际上,数据分析正是大部分自然科学的本质,我们希望从日常的观测中提取规则,并找寻不确定性。
早在17世纪,雅各比·伯努利(1655–1705)提出了描述只有两种结果的随机过程(如抛掷一枚硬币)的伯努利分布。大约一个世纪之后,卡尔·弗里德里希·高斯(1777–1855)发明了今日仍广泛用在从保险计算到医学诊断等领域的最小二乘法。概率论、统计学和模式识别等工具帮助自然科学的实验学家们从数据回归到自然定律,从而发现了如欧姆定律(描述电阻两端电压和流经电阻电流关系的定律)这类可以用线性模型完美表达的一系列自然法则。
即使是在中世纪,数学家也热衷于利用统计学来做出估计。例如,在雅各比·科贝尔(1460–1533)的几何书中记载了使用16名男子的平均脚长来估计男子的平均脚长。
如图1.1所示,在这个研究中,16位成年男子被要求在离开教堂时站成一排并把脚贴在一起,而后他们脚的总长度除以16得到了一个估计:这个数字大约相当于今日的一英尺。这个算法之后又被改进,以应对特异形状的脚:最长和最短的脚不计入,只对剩余的脚长取平均值,即裁剪平均值的雏形。
现代统计学在20世纪的真正起飞要归功于数据的收集和发布。统计学巨匠之一罗纳德·费雪(1890–1962)对统计学理论和统计学在基因学中的应用功不可没。他发明的许多算法和公式,例如线性判别分析和费雪信息,仍经常被使用。即使是他在1936年发布的Iris数据集,仍然偶尔被用于演示机器学习算法。
克劳德·香农(1916–2001)的信息论以及阿兰·图灵 (1912–1954)的计算理论也对机器学习有深远影响。图灵在他著名的论文《计算机器与智能》中提出了“机器可以思考吗?”这样一个问题 [1]。在他描述的“图灵测试”中,如果一个人在使用文本交互时不能区分他的对话对象到底是人类还是机器的话,那么即可认为这台机器是有智能的。时至今日,智能机器的发展可谓日新月异。
另一个对深度学习有重大影响的领域是神经科学与心理学。既然人类显然能够展现出智能,那么对于解释并逆向工程人类智能机理的探究也在情理之中。最早的算法之一是由唐纳德·赫布(1904–1985)正式提出的。在他开创性的著作《行为的组织》中,他提出神经是通过正向强化来学习的,即赫布理论 [2]。赫布理论是感知机学习算法的原型,并成为支撑今日深度学习的随机梯度下降算法的基石:强化合意的行为、惩罚不合意的行为,最终获得优良的神经网络参数。
来源于生物学的灵感是神经网络名字的由来。这类研究者可以追溯到一个多世纪前的亚历山大·贝恩(1818–1903)和查尔斯·斯科特·谢灵顿(1857–1952)。研究者们尝试组建模仿神经元互动的计算电路。随着时间发展,神经网络的生物学解释被稀释,但仍保留了这个名字。时至今日,绝大多数神经网络都包含以下的核心原则。
- 交替使用线性处理单元与非线性处理单元,它们经常被称为“层”。
- 使用链式法则(即反向传播)来更新网络的参数。
在最初的快速发展之后,自约1995年起至2005年,大部分机器学习研究者的视线从神经网络上移开了。这是由于多种原因。首先,训练神经网络需要极强的计算力。尽管20世纪末内存已经足够,计算力却不够充足。其次,当时使用的数据集也相对小得多。费雪在1936年发布的的Iris数据集仅有150个样本,并被广泛用于测试算法的性能。具有6万个样本的MNIST数据集在当时已经被认为是非常庞大了,尽管它如今已被认为是典型的简单数据集。由于数据和计算力的稀缺,从经验上来说,如核方法、决策树和概率图模型等统计工具更优。它们不像神经网络一样需要长时间的训练,并且在强大的理论保证下提供可以预测的结果。
发展
互联网的崛起、价廉物美的传感器和低价的存储器令我们越来越容易获取大量数据。加之便宜的计算力,尤其是原本为电脑游戏设计的GPU的出现,上文描述的情况改变了许多。一瞬间,原本被认为不可能的算法和模型变得触手可及。这样的发展趋势从如下表格中可见一斑。
年代 | 数据样本个数 | 内存 | 每秒浮点计算数 |
---|---|---|---|
1970 | 100(Iris) | 1 KB | 100 K(Intel 8080) |
1980 | 1 K(波士顿房价) | 100 KB | 1 M(Intel 80186) |
1990 | 10 K(手写字符识别) | 10 MB | 10 M(Intel 80486) |
2000 | 10 M(网页) | 100 MB | 1 G(Intel Core) |
2010 | 10 G(广告) | 1 GB | 1 T(NVIDIA C2050) |
2020 | 1 T(社交网络) | 100 GB | 1 P(NVIDIA DGX-2) |
很显然,存储容量没能跟上数据量增长的步伐。与此同时,计算力的增长又盖过了数据量的增长。这样的趋势使得统计模型可以在优化参数上投入更多的计算力,但同时需要提高存储的利用效率,例如使用非线性处理单元。这也相应导致了机器学习和统计学的最优选择从广义线性模型及核方法变化为深度多层神经网络。这样的变化正是诸如多层感知机、卷积神经网络、长短期记忆循环神经网络和Q学习等深度学习的支柱模型在过去10年从坐了数十年的冷板凳上站起来被“重新发现”的原因。
近年来在统计模型、应用和算法上的进展常被拿来与寒武纪大爆发(历史上物种数量大爆发的一个时期)做比较。但这些进展不仅仅是因为可用资源变多了而让我们得以用新瓶装旧酒。下面的列表仅仅涵盖了近十年来深度学习长足发展的部分原因。
-
优秀的容量控制方法,如丢弃法,使大型网络的训练不再受制于过拟合(大型神经网络学会记忆大部分训练数据的行为) [3]。这是靠在整个网络中注入噪声而达到的,如训练时随机将权重替换为随机的数字 [4]。
-
注意力机制解决了另一个困扰统计学超过一个世纪的问题:如何在不增加参数的情况下扩展一个系统的记忆容量和复杂度。注意力机制使用了一个可学习的指针结构来构建出一个精妙的解决方法 [5]。也就是说,与其在像机器翻译这样的任务中记忆整个句子,不如记忆指向翻译的中间状态的指针。由于生成译文前不需要再存储整句原文的信息,这样的结构使准确翻译长句变得可能。
-
记忆网络 [6]和神经编码器—解释器 [7]这样的多阶设计使得针对推理过程的迭代建模方法变得可能。这些模型允许重复修改深度网络的内部状态,这样就能模拟出推理链条上的各个步骤,就好像处理器在计算过程中修改内存一样。
-
另一个重大发展是生成对抗网络的发明 [8]。传统上,用在概率分布估计和生成模型上的统计方法更多地关注于找寻正确的概率分布,以及正确的采样算法。生成对抗网络的关键创新在于将采样部分替换成了任意的含有可微分参数的算法。这些参数将被训练到使辨别器不能再分辨真实的和生成的样本。生成对抗网络可使用任意算法来生成输出的这一特性为许多技巧打开了新的大门。例如生成奔跑的斑马 [9]和生成名流的照片 [10] 都是生成对抗网络发展的见证。
-
许多情况下单个GPU已经不能满足在大型数据集上进行训练的需要。过去10年内我们构建分布式并行训练算法的能力已经有了极大的提升。设计可扩展算法的最大瓶颈在于深度学习优化算法的核心:随机梯度下降需要相对更小的批量。与此同时,更小的批量也会降低GPU的效率。如果使用1,024个GPU,每个GPU的批量大小为32个样本,那么单步训练的批量大小将是32,000个以上。近年来李沐 [11]、Yang You等人 [12]以及Xianyan Jia等人 [13]的工作将批量大小增至多达64,000个样例,并把在ImageNet数据集上训练ResNet-50模型的时间降到了7分钟。与之对比,最初的训练时间需要以天来计算。
-
并行计算的能力也为至少在可以采用模拟情况下的强化学习的发展贡献了力量。并行计算帮助计算机在围棋、雅达利游戏、星际争霸和物理模拟上达到了超过人类的水准。
-
深度学习框架也在传播深度学习思想的过程中扮演了重要角色。Caffe、 Torch和Theano这样的第一代框架使建模变得更简单。许多开创性的论文都用到了这些框架。如今它们已经被TensorFlow(经常是以高层API Keras的形式被使用)、CNTK、 Caffe 2 和Apache MXNet所取代。第三代,即命令式深度学习框架,是由用类似NumPy的语法来定义模型的 Chainer所开创的。这样的思想后来被 PyTorch和MXNet的Gluon API 采用,后者也正是本书用来教学深度学习的工具。
系统研究者负责构建更好的工具,统计学家建立更好的模型。这样的分工使工作大大简化。举例来说,在2014年时,训练一个逻辑回归模型曾是卡内基梅隆大学布置给机器学习方向的新入学博士生的作业问题。时至今日,这个问题只需要少于10行的代码便可以完成,普通的程序员都可以做到。
成功案例
长期以来机器学习总能完成其他方法难以完成的目标。例如,自20世纪90年代起,邮件的分拣就开始使用光学字符识别。实际上这正是知名的MNIST和USPS手写数字数据集的来源。机器学习也是电子支付系统的支柱,可以用于读取银行支票、进行授信评分以及防止金融欺诈。机器学习算法在网络上被用来提供搜索结果、个性化推荐和网页排序。虽然长期处于公众视野之外,但是机器学习已经渗透到了我们工作和生活的方方面面。直到近年来,在此前认为无法被解决的问题以及直接关系到消费者的问题上取得突破性进展后,机器学习才逐渐变成公众的焦点。这些进展基本归功于深度学习。
-
苹果公司的Siri、亚马逊的Alexa和谷歌助手一类的智能助手能以可观的准确率回答口头提出的问题,甚至包括从简单的开关灯具(对残疾群体帮助很大)到提供语音对话帮助。智能助手的出现或许可以作为人工智能开始影响我们生活的标志。
-
智能助手的关键是需要能够精确识别语音,而这类系统在某些应用上的精确度已经渐渐增长到可以与人类比肩 [14]。
-
物体识别也经历了漫长的发展过程。在2010年从图像中识别出物体的类别仍是一个相当有挑战性的任务。当年日本电气、伊利诺伊大学香槟分校和罗格斯大学团队在ImageNet基准测试上取得了28%的前五错误率 [15]。到2017年,这个数字降低到了2.25% [16]。研究人员在鸟类识别和皮肤癌诊断上,也取得了同样惊世骇俗的成绩。
-
游戏曾被认为是人类智能最后的堡垒。自使用时间差分强化学习玩双陆棋的TD-Gammon开始,算法和算力的发展催生了一系列在游戏上使用的新算法。与双陆棋不同,国际象棋有更复杂的状态空间和更多的可选动作。“深蓝”用大量的并行、专用硬件和游戏树的高效搜索打败了加里·卡斯帕罗夫 [17]。围棋因其庞大的状态空间被认为是更难的游戏,AlphaGo在2016年用结合深度学习与蒙特卡洛树采样的方法达到了人类水准 [18]。对德州扑克游戏而言,除了巨大的状态空间之外,更大的挑战是游戏的信息并不完全可见,例如看不到对手的牌。而“冷扑大师”用高效的策略体系超越了人类玩家的表现 [19]。以上的例子都体现出了先进的算法是人工智能在游戏上的表现提升的重要原因。
-
机器学习进步的另一个标志是自动驾驶汽车的发展。尽管距离完全的自主驾驶还有很长的路要走,但诸如Tesla、NVIDIA、 MobilEye和Waymo这样的公司发布的具有部分自主驾驶功能的产品展示出了这个领域巨大的进步。完全自主驾驶的难点在于它需要将感知、思考和规则整合在同一个系统中。目前,深度学习主要被应用在计算机视觉的部分,剩余的部分还是需要工程师们的大量调试。
以上列出的仅仅是近年来深度学习所取得的成果的冰山一角。机器人学、物流管理、计算生物学、粒子物理学和天文学近年来的发展也有一部分要归功于深度学习。可以看到,深度学习已经逐渐演变成一个工程师和科学家皆可使用的普适工具。
特点
在描述深度学习的特点之前,我们先回顾并概括一下机器学习和深度学习的关系。机器学习研究如何使计算机系统利用经验改善性能。它是人工智能领域的分支,也是实现人工智能的一种手段。在机器学习的众多研究方向中,表征学习关注如何自动找出表示数据的合适方式,以便更好地将输入变换为正确的输出,而本书要重点探讨的深度学习是具有多级表示的表征学习方法。在每一级(从原始数据开始),深度学习通过简单的函数将该级的表示变换为更高级的表示。因此,深度学习模型也可以看作是由许多简单函数复合而成的函数。当这些复合的函数足够多时,深度学习模型就可以表达非常复杂的变换。
深度学习可以逐级表示越来越抽象的概念或模式。以图像为例,它的输入是一堆原始像素值。深度学习模型中,图像可以逐级表示为特定位置和角度的边缘、由边缘组合得出的花纹、由多种花纹进一步汇合得到的特定部位的模式等。最终,模型能够较容易根据更高级的表示完成给定的任务,如识别图像中的物体。值得一提的是,作为表征学习的一种,深度学习将自动找出每一级表示数据的合适方式。
因此,深度学习的一个外在特点是端到端的训练。也就是说,并不是将单独调试的部分拼凑起来组成一个系统,而是将整个系统组建好之后一起训练。比如说,计算机视觉科学家之前曾一度将特征抽取与机器学习模型的构建分开处理,像是Canny边缘探测 [20] 和SIFT特征提取 [21] 曾占据统治性地位达10年以上,但这也就是人类能找到的最好方法了。当深度学习进入这个领域后,这些特征提取方法就被性能更强的自动优化的逐级过滤器替代了。
相似地,在自然语言处理领域,词袋模型多年来都被认为是不二之选 [22]。词袋模型是将一个句子映射到一个词频向量的模型,但这样的做法完全忽视了单词的排列顺序或者句中的标点符号。不幸的是,我们也没有能力来手工抽取更好的特征。但是自动化的算法反而可以从所有可能的特征中搜寻最好的那个,这也带来了极大的进步。例如,语义相关的词嵌入能够在向量空间中完成如下推理:“柏林 - 德国 + 中国 = 北京”。可以看出,这些都是端到端训练整个系统带来的效果。
除端到端的训练以外,我们也正在经历从含参数统计模型转向完全无参数的模型。当数据非常稀缺时,我们需要通过简化对现实的假设来得到实用的模型。当数据充足时,我们就可以用能更好地拟合现实的无参数模型来替代这些含参数模型。这也使我们可以得到更精确的模型,尽管需要牺牲一些可解释性。
相对其它经典的机器学习方法而言,深度学习的不同在于:对非最优解的包容、对非凸非线性优化的使用,以及勇于尝试没有被证明过的方法。这种在处理统计问题上的新经验主义吸引了大量人才的涌入,使得大量实际问题有了更好的解决方案。尽管大部分情况下需要为深度学习修改甚至重新发明已经存在数十年的工具,但是这绝对是一件非常有意义并令人兴奋的事。
最后,深度学习社区长期以来以在学术界和企业之间分享工具而自豪,并开源了许多优秀的软件库、统计模型和预训练网络。正是本着开放开源的精神,本书的内容和基于它的教学视频可以自由下载和随意分享。我们致力于为所有人降低学习深度学习的门槛,并希望大家从中获益。
小结
- 机器学习研究如何使计算机系统利用经验改善性能。它是人工智能领域的分支,也是实现人工智能的一种手段。
- 作为机器学习的一类,表征学习关注如何自动找出表示数据的合适方式。
- 深度学习是具有多级表示的表征学习方法。它可以逐级表示越来越抽象的概念或模式。
- 深度学习所基于的神经网络模型和用数据编程的核心思想实际上已经被研究了数百年。
- 深度学习已经逐渐演变成一个工程师和科学家皆可使用的普适工具。
练习
- 你现在正在编写的代码有没有可以被“学习”的部分,也就是说,是否有可以被机器学习改进的部分?
- 你在生活中有没有这样的场景:虽有许多展示如何解决问题的样例,但缺少自动解决问题的算法?它们也许是深度学习的最好猎物。
- 如果把人工智能的发展看作是新一次工业革命,那么深度学习和数据的关系是否像是蒸汽机与煤炭的关系呢?为什么?
- 端到端的训练方法还可以用在哪里?物理学,工程学还是经济学?
- 为什么应该让深度网络模仿人脑结构?为什么不该让深度网络模仿人脑结构?
参考文献
[1] Machinery, C. (1950). Computing machinery and intelligence-AM Turing. Mind, 59(236), 433.
[2] Hebb, D. O. (1949). The organization of behavior; a neuropsycholocigal theory. A Wiley Book in Clinical Psychology., 62-78.
[3] Srivastava, N., Hinton, G., Krizhevsky, A., Sutskever, I., & Salakhutdinov, R. (2014). Dropout: a simple way to prevent neural networks from overfitting. The Journal of Machine Learning Research, 15(1), 1929-1958.
[4] Bishop, C. M. (1995). Training with noise is equivalent to Tikhonov regularization. Neural computation, 7(1), 108-116.
[5] Bahdanau, D., Cho, K., & Bengio, Y. (2014). Neural machine translation by jointly learning to align and translate. arXiv preprint arXiv:1409.0473.
[6] Sukhbaatar, S., Weston, J., & Fergus, R. (2015). End-to-end memory networks. In Advances in neural information processing systems (pp. 2440-2448).
[7] Reed, S., & De Freitas, N. (2015). Neural programmer-interpreters. arXiv preprint arXiv:1511.06279.
[8] Goodfellow, I., Pouget-Abadie, J., Mirza, M., Xu, B., Warde-Farley, D., Ozair, S., … & Bengio, Y. (2014). Generative adversarial nets. In Advances in neural information processing systems (pp. 2672-2680).
[9] Zhu, J. Y., Park, T., Isola, P., & Efros, A. A. (2017). Unpaired image-to-image translation using cycle-consistent adversarial networks. arXiv preprint.
[10] Karras, T., Aila, T., Laine, S., & Lehtinen, J. (2017). Progressive growing of gans for improved quality, stability, and variation. arXiv preprint arXiv:1710.10196.
[11] Li, M. (2017). Scaling Distributed Machine Learning with System and Algorithm Co-design (Doctoral dissertation, PhD thesis, Intel).
[12] You, Y., Gitman, I., & Ginsburg, B. Large batch training of convolutional networks. ArXiv e-prints.
[13] Jia, X., Song, S., He, W., Wang, Y., Rong, H., Zhou, F., … & Chen, T. (2018). Highly Scalable Deep Learning Training System with Mixed-Precision: Training ImageNet in Four Minutes. arXiv preprint arXiv:1807.11205.
[14] Xiong, W., Droppo, J., Huang, X., Seide, F., Seltzer, M., Stolcke, A., … & Zweig, G. (2017, March). The Microsoft 2016 conversational speech recognition system. In Acoustics, Speech and Signal Processing (ICASSP), 2017 IEEE International Conference on (pp. 5255-5259). IEEE.
[15] Lin, Y., Lv, F., Zhu, S., Yang, M., Cour, T., Yu, K., … & Huang, T. (2010). Imagenet classification: fast descriptor coding and large-scale svm training. Large scale visual recognition challenge.
[16] Hu, J., Shen, L., & Sun, G. (2017). Squeeze-and-excitation networks. arXiv preprint arXiv:1709.01507, 7.
[17] Campbell, M., Hoane Jr, A. J., & Hsu, F. H. (2002). Deep blue. Artificial intelligence, 134(1-2), 57-83.
[18] Silver, D., Huang, A., Maddison, C. J., Guez, A., Sifre, L., Van Den Driessche, G., … & Dieleman, S. (2016). Mastering the game of Go with deep neural networks and tree search. nature, 529(7587), 484.
[19] Brown, N., & Sandholm, T. (2017, August). Libratus: The superhuman ai for no-limit poker. In Proceedings of the Twenty-Sixth International Joint Conference on Artificial Intelligence.
[20] Canny, J. (1986). A computational approach to edge detection. IEEE Transactions on pattern analysis and machine intelligence, (6), 679-698.
[21] Lowe, D. G. (2004). Distinctive image features from scale-invariant keypoints. International journal of computer vision, 60(2), 91-110.
[22] Salton, G., & McGill, M. J. (1986). Introduction to modern information retrieval.
注:本节与原书基本相同,为了完整性而搬运过来,原书传送门