深度学习算法与实践

978-7-115-50047-2
作者: 于子叶
译者:
编辑: 陈聪聪

图书目录:

详情

本书旨在为读者建立完整的深度学习知识体系。全书内容包含3个部分,第一部分为与深度学习相关的数学基础;第二部分为深度学习的算法基础以及相关实现;第三部分为深度学习的实际应用。通过阅读本书,读者可加深对深度学习算法的理解,并将其应用到实际工作中。 本书适用于对深度学习感兴趣并希望从事相关工作的读者,也可作为高校相关专业的教学参考书。

图书摘要

版权信息

书名:深度学习算法与实践

ISBN:978-7-115-50047-2

本书由人民邮电出版社发行数字版。版权所有,侵权必究。

您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。

我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。

如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。

著    于子叶

责任编辑 陈聪聪

人民邮电出版社出版发行  北京市丰台区成寿寺路11号

邮编 100164  电子邮件 315@ptpress.com.cn

网址 http://www.ptpress.com.cn

读者服务热线:(010)81055410

反盗版热线:(010)81055315


本书旨在为读者建立完整的深度学习知识体系。全书内容包含3个部分,第一部分为与深度学习相关的数学基础;第二部分为深度学习的算法基础以及相关实现;第三部分为深度学习的实际应用。通过阅读本书,读者可加深对深度学习算法的理解,并将其应用到实际工作中。

本书适用于对深度学习感兴趣并希望从事相关工作的读者,也可作为高校相关专业的教学参考书。


机器学习算法在现代社会生活的很多方面表现出了强大的生命力:从金融分析、气象预报和互联网内容挖掘,到与普通人相关的智能客服、智能手机等。广泛的行业应用是机器学习算法保持生命力的根基。而深度学习算法在一定程度上弱化了对专业领域知识的依赖,使深度学习算法本身可以从数据中学习所需的特征。因此,深度模型又被称为通用机器学习算法。

目前,深度学习在计算机视觉、自然语言处理与自然科学领域均展开了大规模的应用。一方面,深度学习有着比其他机器学习算法更加优秀的普适性;另一方面,这种普适性是以更高的时间与空间复杂度为代价的。作为一名算法工程师,需要做的不仅仅是关注算法的可行性,还需要关注算法本身的复杂度。从这个角度来讲,深度学习算法也许并不是最合适的,但应该是最容易(从理论上来讲)实现的,算法的普适性使深度学习算法具有了比其他机器学习算法更加宽广的想象空间。而这也对相关从业者提出了更高的要求,因为需要了解多个学科的内容。目前国内外已有诸多与机器学习、深度学习相关的著作,如《深度学习》(人民邮电出版社引进出版)对深度学习理论进行了较为深入的讲解。但初学者缺乏一个合适的实现过程与理论描述方式。深度学习内容涉及的统计和几何内容是相对复杂的知识体系。初步学习可能仅了解库函数的使用,如TensorFlow和PyTorch,就可以进行一些工作了。但是要进行深入学习,还是需要更加坚实的基础,这几乎是不可或缺的。

本书主要内容分为3个部分。

第一部分为深度学习的数学基础(第1~4章)。这部分内容包括空间几何与线性代数、概率与统计、函数建模与优化、机器学习库的使用。其中,前两个是相对独立的,因此读者可以根据自己的基础进行选择性阅读;建模与优化综合了线性代数、概率与统计的内容。

第二部分为深度学习基本组件(第5~9章)。这部分内容包括深度学习模型与全连接网络、卷积神经网络、循环神经网络基础、循环神经网络扩展以及深度学习优化。这些基本组件均配有与训练预测过程基本实现。读者可以脱离机器学习库实现深度学习算法,这是理想的学习结果,但并非理想的学习过程。作为初学者应当在实践中逐步深入地进行学习。

第三部分为深度学习中常见的应用场景以及相关模型(第10~12章)。这部分内容包括图像处理(物体检测、人脸识别)、自然语言处理(语音识别、自然语言翻译、语音生成)和非监督学习(对抗生成网络、图像去噪、增强学习)。

作为入门图书,本书会从简单的函数入手描述深度学习(这是编程所必需的),同时介绍深度学习的基本元素与实现(如卷积神经网络、循环神经网络)。至于更复杂的理论,仅进行预测过程公式的说明与机器学习库版本的实现(如注意力机制)。训练过程可能需要借助TensorFlow来完成,但这不代表其本身与TensorFlow是绑定的关系。希望看完本书的读者能够抽出时间来进行更系统的学习。比如从空间几何、统计理论开始学习,但作为初学者,不建议过分纠结基础。如果要真正精通机器学习问题,时间和精力也是必须付出的成本。

本书力求以统一的数学语言详细而完整地描述深度学习理论,内容上侧重于与应用相关的重点算法与理论,特别是算法预测过程。每一章的写作过程尽量避免使用图形,取而代之的是详细的公式描述,这使读者初读本书时可能有些许障碍。但习惯了公式之后就会发现,图形会使我们的理解出现偏差,同时无法进行实现,而公式则没有这个问题。每一章都尽量对理论有完整的公式推演过程,供读者熟悉理论知识。同时,在章节最后会对公式进行实现,以帮助读者在进行理论学习的同时通过实践快速掌握算法。

在学习过程中一些读者经常会陷入几个误区。

第一个误区是对所有算法均试图以图形方式去理解或者寻找一种形象的方式去解释。这种方式并不可取,因为初学者应当把精力放于公式之上,这对于算法实现和定量思维方式的建立都是十分重要的。

第二个误区是试图理解每一个公式,使学习过程无法继续进行。初学者应当先建立知识框架,也就是了解每一种算法之间的关系与内在设计思路,在此之上再进行对公式的理解。

第三个误区是太过强调机器学习库。机器学习库本身仅是辅助,更多的工作需要依赖于对算法的理解。在深入理解之后就会发现,几乎所有的机器学习库,比如PyTorch、Caffe等,均是十分相近的。

本书希望读者将精力集中于几个部分。在数学基础部分希望读者将主要精力放在理解什么是机器学习以及矩阵相关的概念;在深度神经网络部分希望读者更加关注于建模思路;深度学习建模思路是相近的,理解它有助于快速迁移至其他项目任务之中。

感谢家人在本书编写期间给予我的支持和鼓励,是家人让我能够无后顾之忧地进行图书的写作。感谢本书的编辑为图书的顺利出版所做的工作。本人水平毕竟有限,书中纰漏之处在所难免,诚请读者提出意见或建议,以便修订并使之更加完善。


本书由异步社区出品,社区(https://www.epubit.com/)为您提供相关资源和后续服务。

本书提供如下资源:

本书配套资源请到异步社区本书购买页下载。

要获得以上配套资源,请在异步社区本书页面中单击,跳转到下载界面,按提示进行操作即可。注意:为保证购书读者的权益,该操作会给出相关提示,要求输入提取码进行验证。

作者和编辑尽最大努力来确保书中内容的准确性,但难免会存在疏漏。欢迎您将发现的问题反馈给我们,帮助我们提升图书的质量。

当您发现错误时,请登录异步社区,按书名搜索,进入本书页面,单击“提交勘误”,输入勘误信息,单击“提交”按钮即可。本书的作者和编辑会对您提交的勘误进行审核,确认并接受后,您将获赠异步社区的100积分。积分可用于在异步社区兑换优惠券、样书或奖品。

我们的联系邮箱是contact@epubit.com.cn。

如果您对本书有任何疑问或建议,请您发邮件给我们,并请在邮件标题中注明本书书名,以便我们更高效地做出反馈。

如果您有兴趣出版图书、录制教学视频,或者参与图书翻译、技术审校等工作,可以发邮件给我们;有意出版图书的作者也可以到异步社区在线提交投稿(直接访问www.epubit.com/selfpublish/submission即可)。

如果您所在的学校、培训机构或企业想批量购买本书或异步社区出版的其他图书,也可以发邮件给我们。

如果您在网上发现有针对异步社区出品图书的各种形式的盗版行为,包括对图书全部或部分内容的非授权传播,请您将怀疑有侵权行为的链接发邮件给我们。您的这一举动是对作者权益的保护,也是我们持续为您提供有价值的内容的动力之源。

“异步社区”是人民邮电出版社旗下IT专业图书社区,致力于出版精品IT技术图书和相关学习产品,为作译者提供优质出版服务。异步社区创办于2015年8月,提供大量精品IT技术图书和电子书,以及高品质技术文章和视频课程。更多详情请访问异步社区官网https://www.epubit.com。

“异步图书”是由异步社区编辑团队策划出版的精品IT专业图书的品牌,依托于人民邮电出版社的计算机图书出版积累和专业编辑团队,相关图书在封面上印有异步图书的LOGO。异步图书的出版领域包括软件开发、大数据、AI、测试、前端、网络技术等。

异步社区

微信服务号


图2.4 数据的不同方差图示

图2.5 不同分布数据的线性相关性

图2.11 空间中的两类数据点

图2.12 高斯环境下朴素贝叶斯分类器的结果

图3.5 不同学习率在寻找函数最小值时的迭代过程

图5.4 加入非线性特征之后的分类结果(取值为取得黄色点概率)

图5.5 多层神经网络分类

图5.6 深度神经网络分类结果

图5.7 参数过多所形成的分类图像

图5.8 增加训练集数量

图5.9 加入正则化项后的分类图像

图6.1 一维连续型数据

图6.2 乱序数据

图6.3 图形和红、黄、蓝3个颜色通道图像

图10.2 隐藏向量散点图

图10.3 使用9个数字(不包含数字9)训练所得向量

图10.5 CenterLoss加入后的分类图像

图10.6 人脸向量距离统计图像

图11.11 Attention得分信息



机器学习中的很多概念是直接脱胎于现代几何的,如空间、向量和流形等。这些概念与统计学一起构成了整个机器学习的数学基础。对于学习而言,不必深入地理解度量、张量等内容就可以掌握深度学习,但是与解析几何相关的(严格来说,几何中的概念是与空间相关的,而代数则不是)线性代数却是机器学习所必需的基础。线性代数广泛地应用于工程与科学之中,是目前几乎所有理工学科的基础,同时也是深度学习非常重要的基础。掌握好线性代数中的一些基本概念,对于希望从事与机器学习相关工作的人员来说是必要的。

本章将对空间几何的一些基本概念进行解释,并将其与线性代数中的基本概念进行简单映射,这种映射对于今后的学习是有益的。之后对矩阵概念和算法进行详细阐述,帮助对线性代数不甚了解的读者进行学习。如果读者已经熟悉线性代数的相关概念,那么可以略过本章或仅了解一些几何概念。

本章会略去一些对数学证明很重要但在机器学习中很少用到的概念。很多深度学习内容实际上并无完善的理论约束,这也是很多研究人员努力的方向。如果希望从事相关研究工作,则需要进行更加深入、完整的学习。

本节将对空间几何中的相关概念进行阐述。这种阐述是基于“直觉”的,也就是帮助读者建立空间与函数的知识体系并将其与机器学习联系起来。这并不是严格的数学证明过程。对于学习而言,这种“直觉”是必要的。让理论符合我们的“直觉”,是更进一步学习的基础。

首先介绍的概念是空间函数,在此之前我们需要了解什么是空间笛卡儿坐标。这里将空间的每一个点(使用等表示)进行编号,用一组实数与之相对应,此时称这组实数为坐标。当且仅当两个坐标值相等时代表的才是同一个点。也就是说,当空间点与坐标为一一对应的关系时,这种坐标称为笛卡儿坐标,记为称为此空间的维数。对于读者来讲,比较容易理解的是二维、三维空间,如图1.1所示。

图1.1 空间以及其中的坐标点

在空间区域,可以定义一个函数,这构成了维空间中的函数。如果空间坐标满足:连接空间中任意两点,直线的长度平方公式如下。

  (1.1)

如式 (1.1) 所示,称这种坐标系为欧氏空间。欧氏空间是一种简单的空间形式,从零点开始指向空间中某一点的坐标称为坐标向量,如图1.2所示。坐标向量在欧氏空间中是可以直接相加的。

图1.2 二维欧氏空间中的坐标向量

欧氏空间中的坐标向量是可以直接进行代数运算的。例如,图1.2中展示的坐标向量的关系如下。

  (1.2)

在此二维空间所有的坐标向量中,有两个特殊的向量:,其长度均为1,并且互相正交。此二维空间中所有的坐标向量都可以由这两个向量通过某种代数运算得到,因此这两个向量称为坐标基向量,向量可表示为式 (1.3)。

  (1.3)

如果已知维空间中的向量,那么

  (1.4)

称为欧几里得内积,在机器学习中也称为向量乘积。如果两个向量内积为0,则称两个向量正交。假设在空间中定义了关于坐标的某种函数约束

  (1.5)

则定义了一个维空间中的超曲面。对于三维空间而言,式 (1.5) 代表了一个二维曲面。通常,维空间中的维曲面可以由式(1.6)给出。

  (1.6)

由于多维空间情况不太好理解,因此以三维空间中的一维曲线为例进行说明。三维空间中一维曲线需要使用两个方程约束,也就是只有一个自由度,这个自由度可以用变量表示。此时三维空间中的一维曲线的另外一种表示形式为式 (1.7)。

  (1.7)

将式(1.7)所表示的关系绘制为空间中的曲线,如图1.3所示。

图1.3 三维空间中的曲线和速度向量

图1.3展示了式 (1.7) 所描述的三维空间中的一维曲线。这里有了向量的另外一种定义—速度,其形式如式 (1.8)。

  (1.8)

速度表示了空间曲线的变化情况。

 

注意:这里的速度也是一个向量,但是与前面提到的坐标向量不同。速度代表了空间曲面的特征,而坐标向量是带有位置信息的,之后我们所讨论的坐标变换也是对于位置向量而言的。在机器学习问题中,我们通常不太纠结于两种向量的区别,因为它们均是在欧氏空间之中。

 

式(1.7)描述了空间曲线。空间几何体在空间中都具有一定的连续性,这种空间上的连续性使我们可以从曲线拟合的角度看待机器学习问题。对于一般的机器学习问题而言,空间曲线的数学形式是未知的。这就需要通过数据点去预测曲线形状,这些数据点是对未知曲线的采样,也就是从曲线上选择的一些数据点。很多时候这些数据点是带噪声的。恢复未知曲线最简单的方式是“插值法”。这里插值指的是通过已知空间曲线的若干点取值来预测其他数据点位置。

图1.4是插值法示意图,我们只知道空间中的点。如果需要计算在空间中某一点处所对应的取值,则从函数角度来说,就是获取空间曲面的近似值。

  (1.9)

图1.4 插值法示意图

这是一种朴素的机器学习过程,整个过程我们只是用直线来连接空间中的数据点。为解释这个问题,我们列举一个更常见的多维空间的问题,对于手写数字而言,其是一个28×28大小的灰度图。从空间几何角度来说,是一个28×28=784维空间,每个像素点为一个坐标,不同值代表不同灰阶,0~1代表从黑到白。将整个图像沿竖直方向移动,移动量为,此时可以将每个像素点都看成是移动量的函数。由此构建了一个在784维空间中的一维曲面,这个高维空间中的曲面是难以观测的,因此使用主成分分析方法(PCA方法)对高维空间进行降维,从而将其可视化,如图1.5所示。

图1.5 784维空间一维曲线

图1.5展示了多维空间中的一维曲线,这是PCA方法降维之后可视化的结果。可以看到,图像在竖直方向产生微小改变后,多维曲线依然具有一定的连续性。这使我们可以不必精确地用函数来描述变化过程,而只需要使用几个点就可以大概地描述整个图像移动的过程。机器学习问题本身的泛化性能也来源于此。后面会对PCA方法进行讲解。

如果在空间中包含两种坐标系,而坐标之间符合函数关系,则这种函数关系称为空间变换。

  (1.10)

简单的空间变换形式为空间的旋转、平移和拉伸。这种变换称为仿射变换,如图1.6所示。

  (1.11)

图1.6 二维空间的仿射变换(旋转)

为了书写方便,这里将式 (1.11) 表示的代数计算写成矩阵形式。

  (1.12)

这里是二维矩阵。矩阵是深度学习中的基本概念,深度学习的建模以及优化过程都是围绕矩阵进行的。在这里矩阵指的是将一系列的代数运算按照某种方式组织起来。矩阵概念映射到计算机科学中就是数组。

  (1.13)

在式 (1.13) 中,代表列的实数矩阵。矩阵的表示方式有或者。对于我们所接触的机器学习问题而言,矩阵内的数值通常为实数。为了说明问题,我们列举一个比较简单的例子,如图1.7所示。

从图1.7可以看到,仿射变换本身包含了图像的旋转与拉伸,但是仿射变换在原始坐标中的直线在变换坐标中依然是直线。仿射变换是一种线性变换。仿射变换可以用于对图像进行简单处理,如图像的拉伸、平移和旋转等,如图1.8所示。

图1.7 二维空间的仿射变换

图1.8 对图像进行仿射变换

对于欧氏空间中的某一坐标向量,在坐标系中坐标分别为,其可以写为基向量相加的形式。

  (1.14)

因此,向量在坐标变换中有相似的形式。

  (1.15)

如果存在一个特征向量在线性变换过程中仅是长度方向发生变化,且符合形式

  (1.16)

则称为特征向量,为特征值。

 

张量与矩阵的区别

张量本身的定义是在空间中使空间中的某种度量(如长度)不变而成产生的量,因此其产生与几何相关。而矩阵本身则仅代表一种代数运算的组合形式。两者的联系点在于函数和几何空间的映射。

这里列举几种常见的张量(向量)。

速度向量:

梯度向量:

在仿射变换中定义的就是矩阵,它是独立于空间张量存在的。一维矩阵就是一个机器学习中常说的向量。

列向量:

行向量:

 

1.1节已经对矩阵进行了简单阐述,说明矩阵本身也带有一定的几何意义(空间线性变换)。本节将对一些常见的矩阵运算进行叙述。在阐述相关概念时也会使用图像的方式,因为这会涉及一些几何的概念。

实际上前面已经提到了矩阵和向量的乘法运算,这里再对矩阵相乘的概念进行重述。矩阵相乘是基本且常用的运算之一。这里定义矩阵和矩阵相乘得到矩阵。在定义乘法运算的过程中,需要使的列数与的行数相等。将乘法运算写为如下形式。

  (1.17)

式 (1.17) 展示了两种矩阵乘法的书写习惯,前一种是线性代数里常用的矩阵乘法书写方式,后一种在张量分析中常用,代表向量的点乘运算。式 (1.18) 为写成分量的形式。

  (1.18)

这里有两点需要解释,有时会用字母加下标的方式来表示矩阵元素。而矩阵相乘的过程中,在一部分文献中会写成约定求和的方式,即省略求和符号而用相同的指标代表求和。对于矩阵的乘法来说,还有其他的乘法形式,如矩阵的哈达玛积(Hadamard Product),就是矩阵的对应元素相乘,其形式如下。

  (1.19)

这里需要注意的是,式 (1.19) 中相同指标并不代表求和,而仅是元素相乘。与之相类似的是矩阵的加法运算,其代表着矩阵对应元素相加。

  (1.20)

矩阵运算本身也有着类似于数字运算的法则。

(1)分配率:

(2)结合律:

(3)交换律:矩阵运算无交换律。

回顾如下一种简单的等式。

  (1.21)

这是一种简单的表示形式,它代表之间存在某种关系。如果将看成二维空间中的坐标,那么式 (1.21) 则代表了空间中的某一条直线。写成矩阵的乘法与加法,则形式如下。

  (1.22)

式 (1.22) 实际上代表对矩阵进行线性变换后得到的过程。因此矩阵的线性变换实际上就是对式 (1.21) 的扩展。这代表之间存在某种简单的关系。取的某一列向量,则公式如下。

  (1.23)

这代表着对向量进行线性变换。在给出式 (1.22) 的过程中,我们需要解释一个细节,就是矩阵的分块运算。对于矩阵的乘法及加法运算,都可以分解为对子矩阵进行相乘运算。例如将式 (1.22) 中矩阵的每一列看作一个子矩阵(向量),那么可以写成分块形式。式 (1.23) 中就来自于

   (1.24)

均写成类似的形式,那么的乘法可以写成如下形式。

  (1.25)

这就是矩阵的分块运算。当然,分块运算还有其他划分形式,读者可参考线性代数的相关内容。

如果令,那么式 (1.23) 就变成了如下形式。

  (1.26)

式 (1.26) 是一个标准的线性方程组。从矩阵分块运算的角度来看,将个未知数的组方程写成了式 (1.23) 所示的紧凑形式。矩阵可以简化公式的书写。假设矩阵是列的,则严格来说还需要

(1)如果,那么代表未知数个数与方程个数是相等的,这是一个适定方程。

(2)如果,那么代表未知数个数大于方程个数,这是一个欠定方程。

(3)如果,那么代表未知数个数小于方程个数,这是一个超定方程。

这就有了3种典型问题。对于适定问题,如果矩阵行列式不等于0,那么方程有唯一解(空间中的一个点);对于欠定方程,方程具有无穷多个解(一个空间曲面);对于超定方程,仅有近似解。机器学习问题应当都是超定问题,也就是方程个数是多于未知数个数的。但是也有些情况例外,比如深度学习模型,未知数个数可能是大于方程个数的。

现在列举一个简单的例子。假设在二维空间中有 共4个点,求解这4个点所在的直线。如果直线方程为,那么将4个数据点代入后会得到4个方程,而未知数有两个,因此这就是一个典型的超定问题。此时,对于取得任何值都无法很好地描述通过4个点的直线。但若取,此时虽然无法精确地描述的关系,但是通过这种方式可以得到与数据点相比十分接近,因此得到了近似意义(最小二乘)上的解。这是一个非常典型的机器学习问题。从这个例子可以看到,实际上机器学习就是一个从数据中寻找规律的过程。而假设数据符合直线分布就是我们给定的模型,求解给定模型参数的过程称为优化。这里不需要读者对机器学习问题进行更多的思考,我们在之后还会进行更详细的阐释。这里只是说明机器学习问题大部分情况下是一个超定问题,但由于可训练参数(也就是未知数)较多,在训练样本(每个训练数据都是一个方程)不足的情况下深度学习模型可能并非超定问题,此时会面临过拟合风险,因此对于机器学习尤其是深度学习需要海量(数量远超未知数的个数,未知数也就是可训练参数的个数)的样本才能学习到有价值的知识。

上面提到空间中某一坐标向量可以写成多个向量相加的形式。

  (1.27)

对于一组不全为0的向量而言,如果其中的任意一个向量都不能由其他向量以式 (1.27) 的方式表示,那就代表这组向量线性无关或这组向量是线性独立的。

线性独立的概念很重要。如果几个向量线性不独立,即某个向量可以用其他向量表示,那么这个向量就没有存储的必要。举个简单的例子。

  (1.28)

式 (1.28) 代表向量是线性相关的,也就是说,我们仅需存储3个向量其中的两个就可以恢复第3个向量。这种恢复是无损的,是信息压缩最原始的思想。这里加强约束,式(1.27) 中等式右边各个向量之间的关系如下。

  (1.29)

式 (1.29) 中描述的向量是互相正交的关系,并且是单位向量。

  (1.30)

 

单位向量:长度为1的向量。

向量正交:两个向量内积为0。

坐标基向量是最简单的单位向量。

 

因此,实际上式 (1.27) 就是对坐标向量进行的坐标基展开,这是在空间中所用到的概念。当然,并不是所有坐标基向量都是正交的,同样也未必是单位向量。

对于一组矩阵的向量 来说,其中的每个向量都可以用其他多个向量以加权求和的方式表示。

  (1.31)

其中,代表第个单位向量的第个元素。同样地,代表第个向量的第个元素。此时式 (1.31) 实际上可以表示为矩阵相乘的形式。

  (1.32)

式 (1.32) 中由向量组成的矩阵可以分解为两个矩阵的乘积表示。如果,也就是说,我们可以用少于个数字来表示向量,这是一个标准的数据压缩过程。此时,可以代表矩阵的特征,如果要恢复的话,还需要保存。但是机器学习中通常只需即可,因为其带有的信息。

从前面的内容可以知道,式 (1.32) 是对矩阵进行的线性变换,这个变换的目的在于信息压缩。这个过程中需要的是求解矩阵。如果,则信息压缩方式可以写为如下形式。

  (1.33)

称为变换矩阵。这是通过矩阵的线性变换来完成数据压缩的过程。

特征值分解是最简单的一种矩阵分解形式,也是矩阵算法中最常用的。特征值分解是对方阵而言的。下面将某个矩阵分解成3个矩阵相乘的形式。

  (1.34)

这是一个矩阵相乘的逆运算,也是一个典型的欠定问题,因为矩阵分解并不是唯一的。为了解决这种非唯一性问题,我们对分解后的矩阵加入约束条件。第一个约束就是特征值分解中矩阵是正交矩阵。

  (1.35)

此时,式 (1.33) 中的变换矩阵即为。另外一个约束就是对角矩阵,对角线上的元素称为特征值。中的向量则称为特征向量。

对于特征值分解而言,其本身具有明确的几何意义。如果将矩阵当作1.1.2节中的仿射变换矩阵,那么前面提到的坐标与矩阵相乘实际上代表了对空间的旋转拉伸变换。由此仿射变换本身可以分解为旋转与拉伸。因此式 (1.34) 中所得到的矩阵,代表了对空间的旋转变换,则代表了对空间的拉伸变换。在此,以二维情况进行简单阐述,如图1.9所示。

图1.9 仿射变换图示

作为矩阵的分解算法,特征值分解最主要的缺陷在于它只能应用于方阵。非方阵情况下的矩阵分解算法,比较有代表性的是奇异值分解(SVD)。

  (1.36)

SVD的求解过程可以用特征值分解进行,这就需要将矩阵转换为方阵。

  (1.37)

进行特征值分解,利用对应元素相等可以得到如下关系。

  (1.38)

根据式 (1.36) 可以得到的值如下。

  (1.39)

由此3个矩阵已经完全确定。因此,有人说矩阵的特征值分解是 SVD 的基础。同时可以看到,矩阵在变换为矩阵的过程中,相当于对矩阵进行一次线性变换。

对于SVD分解而言,有一个非常大的问题就是约束过于严格,如矩阵为正交矩阵,这就导致在计算的过程中,为了满足分解条件,信息压缩的质量可能会降低。因此,产生了另外一个更加宽泛的约束方式。

  (1.40)

假设条件足够稀疏,此时就称为字典。在这种情况下弱化了正交性假设,所得到的信息压缩效果会更加出色。

在TensorFlow 中,最简单的矩阵乘法运算可以写成如代码清单1.1所示的形式。

代码清单1.1 矩阵点乘1

# 矩阵乘法
# 引入库
import tensorflow as tf
# 定义常量全为1的矩阵,矩阵为4*4的矩阵
a1 = tf.ones([4, 4])
a2 = tf.ones([4, 4])
# 矩阵乘法
a1_dot_a2 = tf.matmul(a1, a2)
# 定义会话用于执行计算
sess = tf.Session()
# 执行并输出
print(sess.run(a1_dot_a2))

这是通过常量进行定义的,一般的函数库是按顺序执行的,如NumPy。而TensorFlow在运行过程中,一直都只是在描述计算,直到定义了会话并运行之后才真正开始计算。这样一来,我们就可以用速度较慢的Python来描绘计算过程,而需要快速计算时,利用会话(Session)可以实现Python所描述的计算。Python中用得最多的是变量,将上述矩阵运算使用TensorFlow可以写成代码清单1.2的形式。

代码清单1.2 矩阵点乘2

# 矩阵乘法
# 引入库
import tensorflow as tf
# 定义变量,并将其初始值赋为1
a 1 = tf.Variable(tf.ones([4, 4]))
# 推荐使用get_varialbe函数
a2 = tf.get_variable("a2", [4, 4])
# 矩阵乘法
a1_dot_a2 = tf.matmul(a1, a2)
# 变量需要初始化
init = tf.global_variables_initializer()
sess = tf.Session()
sess.run(init)
print(sess.run(a1_dot_a2))

变量是在计算过程中可以不断变化调整的量,这对机器学习来说是比较重要的(后面会提到)。这里需要注意的是,使用变量时需要对其进行初始化,否则会出现错误。

上面提到我们用Python来描绘计算过程,称为计算图。在描述之后,我们很难对变量进行外部输入。因此这里引入了placeholder用于从外部接收数据,如代码清单1.3所示。

代码清单1.3 矩阵乘法3

# 矩阵乘法
# 引入库
import tensorflow as tf
import numpy as np 
# 变量推荐使用get_variable函数
a1 = tf.get_variable("a1", [4, 4])
# 定义 placeholder 用于从外部接收数据
a2 = tf.placeholder(dtype=tf.float32, shape=[4, 4])
# 矩阵乘法
a1_dot_a2 = tf.matmul(a1, a2)
# 变量需要初始化
init = tf.global_variables_initializer()
sess = tf.Session()
sess.run(init)
# 需要为 placeholder 提供数据
print(sess.run(a1_dot_a2, feed_dict={a2:np.ones([4,4])}))

想象一下,在运行过程中只需不断地从外部输入c2,就可以持续地输出a1_dot_a2。这个过程对于机器学习输入样本来说,非常简便。

 

注意格式检查。在计算过程中,float 64与float 32放在一起使用会产生错误。

 

本例中我们将对图像进行仿射变换,这里用到的库函数为OpenCV。当然,这不是必需的,读者可以根据仿射变换的描述来完成计算,但后续处理中需要对图像进行插值。为了程序简洁,我们仅以OpenCV进行举例。其中使用的图像如图1.10所示。

图1.10 原始图像

这里将图像顺时针旋转45°,见代码清单1.4。

代码清单1.4 图像旋转代码

import numpy as np 
import cv2
# 图像读入
image = cv2.imread("figure/demo.jpg")
# 定义仿射变换矩阵A旋转拉伸变换,b平移变换
pi2 = np.pi / 4
Ab = np.array([[np.cos(pi2), np.sin(pi2), 0], 
              [-np.sin(pi2), np.cos(pi2), 0]],dtype=np.float32)
# 对图像进行仿射变换
image_trans = cv2.warpAffine(image, Ab, (image.shape[1], image.shape[0]))
# 图像输出
cv2.imwrite("figure/demo_t.jpg", image_trans)

最终输出图像如图1.11所示。

图1.11 图像顺时针旋转45°

注意,这里在进行图像旋转的过程中坐标原点在图像右上角。旋转后图像空白部分填充为0。因此,可以看到图1.11所示的很多位置显示为黑色。对于图像的拉伸与平移问题,我们仅需对变换矩阵进行如下更改。

# 图像横向拉伸3倍
Ab = np.array([[3, 0, 0], 
              [0,13, 0]], dtype=np.float32)
# 图像纵向拉伸3倍
Ab = np.array([[1, 0, 0], 
              [0, 3, 0]], dtype=np.float32)
# 图像向左移动10个单位像素
Ab = np.array([[1, 0, 10], 
               [0, 1, 0]], dtype=np.float32)
# 图像向下移动10个单位像素
Ab = np.array([[1, 0, 0], 
              [0, 1, 10]], dtype=np.float32)

拉伸后的图像如图1.12所示。

图1.12 对图像进行横向(左图)与纵向(右图)拉伸

由此可见,仿射变换本身是由几何所产生的概念,其广泛应用于图像处理、机器学习等相关工作之中。

本示例使用的原始图像如图1.13所示。

图1.13 数据压缩示例所使用的原始图像

为了保存图像,使用的矩阵格式为三维矩阵,为了方便SVD处理,将图像进行灰度化。灰度化的方式为对每个颜色通道进行加权求和。图像SVD分解的完整代码如代码清单1.5所示。

代码清单1.5 图像SVD分解代码

import matplotlib.image as mpimg # mpimg 用于读取图像
import matplotlib.pyplot as plt # 用于绘图
import tensorflow as tf # 用于矩阵运算SVD分解
import numpy as np #用于矩阵运算
# 设置中文显示
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False 
# 读取图像
img = mpimg.imread('img/timg.jpg')
# 归一化
img = img / 255
# 灰度化
a1, a2, a3 = 0.2989, 0.5870, 0.1140
img_gray = img[:,:,0]*a1+img[:,:,1]*a2+img[:,:,2]*a3
H, W = np.shape(img_gray)

# 定义TensorFlow中的常量矩阵
img_tf = tf.constant(img_gray)
# 对矩阵进行SVD分解
img_svd = tf.linalg.svd(img_tf, full_matrices=True)
# 在TensorFlow中需要Session执行
sess = tf.Session()
# 真正执行SVD分解
A, M, N = sess.run(img_svd)
print(np.shape(A), np.shape(M), np.shape(N))
# 将A转换为对角矩阵
A = np.diag(A)
A = np.pad(A, ((0, 0), (0, W-H)), mode='constant', constant_values=(0, 0))
# 绘图
plt.subplot(221)
plt.title("原始图像")
plt.imshow(img_gray, cmap=plt.get_cmap("gray"))
plt.subplot(222)
plt.title("无损恢复图像")
plt.imshow(M.dot(A).dot(N.T), cmap=plt.get_cmap("gray"))
plt.subplot(223)
plt.title("保留前200个向量")
num = 200
# 仅取前200个向量恢复图像
plt.imshow(M[:,:num].dot(A[:num,:num]).dot(N[:, :num].T), cmap=plt.get_cmap("gray"))
plt.subplot(224)
plt.title("保留前20个向量")
num = 20
plt.imshow(M[:,:num].dot(A[:num,:num]).dot(N[:, :num].T), cmap=plt.get_cmap("gray"))
plt.show()

对于程序而言,需要说明的有以下两点。

(1)TensorFlow使用Python语言描述数据经过的计算过程,我们在描述过程中实际并未执行,真正的执行过程需要使用Session来执行。

(2)执行完成后的结果为NumPy矩阵,可以直接使用与NumPy相关的函数进行运算,这个过程是实时的。

最终得到结果如图1.14所示。

图1.14 进行图像压缩与恢复示意

可以看到,即使使用一部分向量依然可以恢复原始图像,但是图像质量损失,这个过程不是无损的。下面计算压缩比,如表1.1所示。

表1.1 图像压缩比

图像处理过程

保存需要浮点数

压缩后的浮点数个数/原始图像浮点数个数

原始图像

867×1 300=1 127 100

100%

保留前200个特征值

(867+1 300)×200+200=433 600

38.5%

保留前20个特征值

(867+1 300)×20+20=43 360

3.85%

可以看到,即使仅使用原始图像3.85%的浮点数,依然可以部分恢复原始图像。这是图像等连续型数据的特点。从另一个角度来说,这是数据本身具有一定的稀疏性,可以由少量特征恢复原始数据。

本章内容源自于《现代几何学》与《线性代数》,有兴趣的读者可以自行阅读。对于本章内容,读者应着重理解矩阵的相关概念以及运算。对于与几何学结合的内容,酌情了解即可。很多线性代数的概念是脱胎于几何的,因此对于单独学习线性代数的读者而言很多概念可能会感觉无法理解,这不适合理论体系的建立。前面说到,所谓“学会”就是将公式理论融入我们直觉思考的过程,这里的直觉指的是记忆并接受。如果直觉上就对一些概念有所抵触,那么今后的公式推演就可能会成为我们的一个薄弱点。

然而,公式推演过程并非是机器学习的全部,这里更希望读者在遇到理解上的问题时,不要纠结于具体细节,而是继续阅读,在阅读完整本图书后再去理解之前不懂的内容。这是一种比较推荐的学习方式。


相关图书

ChatGPT原理与应用开发
ChatGPT原理与应用开发
深度学习的数学——使用Python语言
深度学习的数学——使用Python语言
深度学习:从基础到实践(上、下册)
深度学习:从基础到实践(上、下册)
动手学深度学习(PyTorch版)
动手学深度学习(PyTorch版)
深度学习与医学图像处理
深度学习与医学图像处理
深度强化学习实战:用OpenAI Gym构建智能体
深度强化学习实战:用OpenAI Gym构建智能体

相关文章

相关课程