第3章 线性代数


机器学习、深度学习的基础除了编程语言外,还有一个就是应用数学。它一般包括线性代数、概率与信息论、概率图、数值计算与最优化等。其中线性代数又是基础的基础。线性代数是数学的一个重要分支,广泛应用于科学和工程领域。大数据、人工智能的源数据在模型训练前,都需要转换为向量或矩阵,而这些运算正是线性代数的主要内容。
如在深度学习的图像处理中,如果1张图由28*28像素点构成,那这28*28就是一个矩阵。在深度学习的神经网络中,权重一般都是矩阵,我们经常把权重矩阵W与输入X相乘,输入X一般是向量,这就涉及矩阵与向量相乘的问题。诸如此类,向量或矩阵之间的运算在深度学习中非常普遍,也非常重要。
本章主要介绍如下内容:
标量、向量、矩阵和张量
矩阵和向量运算
特殊矩阵与向量
线性相关性及向量空间

3.1标量、向量、矩阵和张量

在机器学习、深度学习中,首先遇到的就是数据,如果按类别来划分,我们通常会遇到以下4种类型的数据。
1.标量(scalar)
一个标量就是一个单独的数,一般用小写的变量名称表示,如a,x等。
2.向量(vector)
向量就是一列数或一个一维数组,这些数是有序排列的。通过次序中的索引,我们可以确定向量中每个单独的数。通常我们赋予向量粗体的小写变量名称,如x、y等。一个向量一般有很多元素,这些元素如何表示?我们一般通过带脚标的斜体表示,如x_1 表示向量x中的第一个元素,x_2表示第二元素,依次类推。
当需要明确表示向量中的元素时,我们一般将元素排列成一个方括号包围的纵列:

我们可以把向量看作空间中的点,每个元素是不同的坐标轴上的坐标。
向量可以这样表示,那我们如何用编程语言如python来实现呢?如何表示一个向量?如何获取向量中每个元素呢?请看如下实例:

打印结果如下:
5
1 2 4 8

这说明向量元素个数为5,向量中索引一般从0开始,如a[0]表示第一个元素1,a[1]
表示第二个元素2,a[2]表示第三个元素4,依次类推。这是从左到右的排列顺序,如果从右到左,我们可用负数来表示,如a[-1]表示第1个元素(注:从右到左),a[-2]表示第2个元素,依次类推。
3.矩阵(matrix)
矩阵是二维数组,其中的每一个元素被两个索引而非一个所确定。我们通常会赋予矩阵粗体的大写变量名称,比如A。如果一个实数矩阵高度为m,宽度为n,那么我们说A∈R^mxn。
与向量类似,可以通过给定行和列的下标表示矩阵中元素,下标用逗号分隔,如A_1,1表示A左上的元素,A_1,2表示第一行第二列对应的元素,依次类推;这是表示单个元素,如果我们想表示1列或1行,该如何表示呢?我们可以引入冒号":"来表示,如第1行,可用A1,:表示,第2行,用A2,:表示,第1列用A:,1表示,第n列用A:,n表示。
如何用Python来表示或创建矩阵呢?如果希望获取其中某个元素,该如何实现呢?请看如下实例:

打印结果:
[[1 2 3]
[4 5 6]]
6
(2, 3)
1 2 5
[4 5 6]

矩阵可以用嵌套向量生成,和向量一样,在Numpy中,矩阵元素的下标索引也是从0开始的。
4.张量(tensor)
几何代数中定义的张量是向量和矩阵的推广,通俗一点理解的话,我们可以将标量视为零阶张量,向量视为一阶张量,那么矩阵就是二阶张量,三阶的就称为三阶张量,以此类推。在机器学习、深度学习中经常遇到多维矩阵,如一张彩色图片就是一个三阶张量,三个维度分别是图片的高度、宽度和色彩数据。
张量(tensor)也是深度学习框架TensorFlow的重要概念。TensorFlow由tensor(张量)+flow(流)构成。
同样我们可以用Python来生成张量及获取其中某个元素或部分元素,请看实例:

打印结果如下:
[[[ 0 1 2 3]
[ 4 5 6 7]]

[[ 8 9 10 11]
[12 13 14 15]]]
16
(2, 2, 4)
0 1 5
[4 5 6 7]
5.转置(transpose)
转置以主对角线(左上到右下)为轴进行镜像操作,通俗一点来说就是行列互换。将矩

向量可以看作只有一列的矩阵,把(3.1)式中向量x进行转置,得到下式。

用Numpy如何实现张量的转置?很简单,利用张量的T属性即可,示例如下:

打印结果如下:
[[1 2 3]
[4 5 6]]
[[1 4]
[2 5]
[3 6]]

3.2矩阵和向量运算

矩阵加法和乘法是矩阵运算中最常用的操作之一,两个矩阵相加,需要它们的形状相同,进行对应元素的相加,如:C=A+B,其中C_(i,j)=A_(i,j)+B_(i,j)。矩阵也可以和向量相加,只要它们的列数相同,相加的结果是矩阵每行与向量相加,这种隐式地复制向量b到很多位置的方式称为广播(broadcasting),以下我们通过一个代码实例来说明。

打印结果为:
[[11 22 33]
[14 25 36]]

两个矩阵相加,要求它们的形状相同,如果两个矩阵相乘,如A和B相乘,结果为矩阵C,矩阵A和B需要什么条件呢?条件比较简单,只要矩阵A的列数和矩阵B的行数相同即可。如果矩阵A的形状为m×n,矩阵B的形状为n×p,那么矩阵C的形状就是m×p,例如:
C=AB,则它们的具体乘法操作定义为:

矩阵乘积有很多重要性质,如满足分配律A(B+C)=AB+AC 和结合律,A(BC)=(AB)C。大家思考一下是否满足交换律?

3.3特殊矩阵与向量

上一节我们介绍了一般矩阵的运算,实际上在机器学习或深度学习中,我们还经常遇到一些特殊类型的矩阵,如可逆矩阵、对称矩阵、对角矩阵、单位矩阵、正交矩阵等等。这些特殊矩阵有特殊属性,下面我们逐一进行说明。
1.可逆矩阵
先简单介绍一下可逆矩阵,因后续需要用到。在(3.3)式中,假设矩阵W已知,向量b已知,如何求向量x?为求解向量x,我们需要引入一个称为逆矩阵的概念。而为了求逆矩阵,又牵涉到单位矩阵,何为单位矩阵?单位矩阵的结构很简单,就是所有沿主对角线上的元素都是1,而其他位置的元素都是0的方阵(行数等于列数的矩阵),一般记为I_n,如:


对此后续我们有更详细的讨论及代码实现。
2.对角矩阵
对角矩阵只有在主对角线上才有非零元素,其余都是0。从形式上来看,如果A为对角矩阵,当且仅当对所有i≠j,A_(i,j)=0。对角矩阵可以是方阵(行数等于列数)也可以不是方阵,如下矩阵,就是一个对角矩阵。

对角矩阵有非常好的性质,这些性质使很多计算非常高效,在机器学习、深度学习中经常会遇到对角矩阵。
对于对角矩阵为方阵的情况,我们可以把对角矩阵简单记为:

从上面两个式子可以看到对角矩阵的简洁高效。
(3)对称矩阵
对称矩阵,对于任意一个n阶方阵A,若A满足:A=A^T成立,则称方阵A为对称矩阵。
(4)单位向量

3.4线性相关性及向量空间

前面我们介绍了向量、矩阵等概念,接下来我们将介绍向量组、线性组合、线性相关性、秩等重要概念。
由多个同维度的列向量构成的集合称为向量组,矩阵可以看成是由行向量或列向量构成的向量组。
1.线性组合

2.线性相关

秩是一个重要概念,运用非常广泛,实际上矩阵我们可以看成是一个向量组。如果把矩阵看成是由所有行向量构成的向量组,这样矩阵的行秩就等于行向量组的秩;如果把矩阵看成是由所有列向量构成的向量组,这样矩阵的列秩就等于列向量组的秩。矩阵的行秩与列秩相等,因此,把矩阵的行秩和列秩统称为矩阵的秩。

3.5范数

数有大小,向量也有大小,向量的大小我们通过范数(Norm)来衡量。范数在机器学习、深度学习中运用非常广泛,特别在限制模型复杂度、提升模型的泛化能力方面效果不错。p范数的定义如下:


前面主要介绍了利用范数来度量向量的大小,矩阵的大小如何度量呢?我们可以用类似的方法。在深度学习中,常用Frobenius范数来描述,即:

其中θ表示x与y之间的夹角。
以上说了向量一种度量方式,即通过范数来度量向量或矩阵的大小,并有具体公式,在实际编程中如何计算向量的范数呢?这里我们还是以Python为例进行说明。

打印结果如下:
[ 0. 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9]
4.5
1.68819430161
0.9

由此看出利用Python求向量的范数还是很方便的。

3.6特征值分解

许多数学对象可以分解成多个组成部分。特征分解就是使用最广的矩阵分解之一,即将矩阵分解成一组特征向量和特征值。本节讨论的矩阵都是方阵。
我们先介绍特征值、特征向量的概念。
设A是一个n阶方阵,如果存在实数⋋和n维的非零向量x,满足:
Ax=⋋x                                            (3.14)
那么把数⋋称为方阵A的特征值,向量x称为矩阵A对应特征值⋋的特征向量。
假设矩阵A有n个线性无关的特征向量{ν^1,ν^2,⋯,ν^n},它们对应的特征值为{⋋_1,⋋_2,⋯,⋋_n}
把这n个线性无关的特征向量组合成一个新方阵,每一列是一个特征向量。
V=[ν^1,ν^2,⋯,ν^n]
用特征值构成一个n阶对角矩阵,对角线的元素都是特征值。
〖〖diag(λ)=[⋋〗_1,⋋_2,⋯,⋋_n]〗^T
那么,A的特征分解可表示为:
A=Vdiag(λ)V^(-1)                                   (3.15)
注意,并不是所有方阵都能进行特征值分解,一个n阶方阵A能进行特征值分解的充分必要条件是它含有n个线性无关的特征向量。
这里我们介绍了给定一个方阵,如何求该方阵的特征向量和特征值?如何用编程语言实现?这些问题有了Python的帮忙,实现起来都非常简单,具体请看如下示例:

打印结果:
[-0.37228132 5.37228132]
[-0.37228132 5.37228132]
[[-0.82456484 -0.41597356]
[ 0.56576746 -0.90937671]]
【说明】
在numpy.linalg模块中:
eigvals() 计算矩阵的特征值
eig() 返回包含特征值和对应特征向量的元组

3.7奇异值分解

上节我们介绍了方阵的一种分解方式,如果矩阵不是方阵,是否能分解?如果能,该如何分解?这节我们介绍一种一般矩阵的分解方法,称为奇异值分解,这种方法应用非常广泛,如降维、推荐系统、数据压缩等等。
矩阵非常重要,所以其分解方法也非常重要,方法也比较多,除了特征分解法,还有一种分解矩阵的方法,被称为奇异值分解(SVD)。将矩阵分解为奇异向量和奇异值。通过奇异分解,我们会得到一些类似于特征分解的信息。然而,奇异分解有更广泛的应用。
每个实数矩阵都有一个奇异值分解,但不一定都有特征分解。例如,非方阵的矩阵就没有特征分解,这时我们只能使用奇异值分解。
奇异分解与特征分解类似,只不过这回我们将矩阵A分解成三个矩阵的乘积:
A=UDV^T                                      (3.16)
假设A是一个m×n矩阵,那么U是一个m×m矩阵,D是一个m×n矩阵,V是一个n×n矩阵。这些矩阵每一个都拥有特殊的结构,其中U和V都是正交矩阵,D是对角矩阵(注意,D不一定是方阵)。对角矩阵D对角线上的元素被称为矩阵A的奇异值。矩阵U的列向量被称为左奇异向量,矩阵V 的列向量被称右奇异向量。
SVD最有用的一个性质可能是拓展矩阵求逆到非方矩阵上。奇异值分解,看起来很复杂,如果用python来实现,却非常简单,具体请看如下示例:

打印结果:
[ 1.09824632e+01 8.79229347e+00 1.03974857e+00 1.18321522e-15
2.13044868e-32]
[[ 10.98246322 0. 0. ]
[ 0. 8.79229347 0. ]
[ 0. 0. 1.03974857]]

3.8迹运算

迹运算返回的是矩阵对角元素的和:

迹运算在某些场合非常有用。若不使用求和符号,有些矩阵运算很难描述,而通过矩阵乘法和迹运算符号可以清楚地表示。例如,迹运算提供了另一种描述矩阵Frobenius 范数的方式:

对迹运算的表达式,我们可以使用很多等式来表示。例如,迹运算在转置运算下是不变的:

多个矩阵相乘得到的方阵的迹,和将这些矩阵中的最后一个挪到最前面之后相乘的迹是相同的。当然,我们需要考虑挪动之后矩阵乘积依然有定义:
Tr(ABC)=Tr(CAB)=Tr(BCA)
利用Python的Numpy对矩阵求迹同样方便。请看以下示例。

打印结果:
15
15
171
171

3.9实例:Python实现主成分分析

主成分分析(Principal Component Analysis,PCA)是一种统计方法。通过正交变换将一组可能存在相关性的变量转换为一组线性不相关的变量,转换后的这组变量叫主成分。
在许多机器学习、深度学习的应用中,往往需要处理大量样本或大的矩阵,多变量大样本无疑会为研究和应用提供丰富的信息,但也在一定程度上增加了数据采集的工作量。更重要的是在多数情况下,许多变量之间可能存在相关性,从而增加了问题分析的复杂性,同时对分析带来不便。如果分别对每个指标进行分析,分析往往是孤立的,而不是综合的。而盲目减少指标又会损失很多信息,且容易产生错误的结论。
因此需要找到一个合理有效的方法,在减少需要分析的指标或维度的同时,尽量减少原指标所含信息的损失,以达到对所收集数据进行全面分析的目的。由于各变量间存在一定的相关关系,因此有可能用较少的综合指标分别存在变量的各类信息。主成分分析就属于这类降维的方法。
如何实现以上目标呢?这里我们简要说明一下原理,然后使用Python来实现,至于详细的推导过程,大家可参考相关书籍或网上资料。
问题:设在n维空间中有m个样本点:{x^1,x^2,…,x^m},假设m比较大,需要对这些点进行压缩,使其投影到k为空间中,其中k<n,同时使损失的信息最小。
该如何实现呢?以下简要说明一下思路。

要使信息损失最小,一种合理的设想就是重构后的点X^*与原来的数据点之间距离最小,据此,PCA可转换为求带约束的最优化问题:

最后对(3.22)式两端对w求导,并令导数为0,化简后就可得到:
XX^T W=⋋W                                                                                                        (3.23)
由(3.23)式可知,W是由协方差矩阵XX^T的特征向量构成的特征矩阵,利用特征值分解的方法就可求出W。
以下我们Python具体实现一个PCA实例。以iris为数据集,该数据集可以通过load_iris自动下载。
1)iris数据集简介:
Iris数据集是常用的分类实验数据集,由Fisher, 1936收集整理。Iris也称鸢尾花卉数据集。数据集包含150个数据集,分为3类,每类50个数据,每个数据包含4个属性。可通过花萼长度,花萼宽度,花瓣长度,花瓣宽度4个属性预测鸢尾花卉属于(Setosa,Versicolour,Virginica)三类中的哪一类。
2)算法主要步骤为:
(1)对向量X进行去中心化
(2)计算向量X的协方差矩阵,自由度可以选择0或者1
(3)计算协方差矩阵的特征值和特征向量
(4)选取最大的k个特征值及其特征向量
(5)用X与特征向量相乘
3)代码实现

各特征值的贡献率如图3-1所示,可以看出,前2个特征值的方差贡献率超过95%,所以k取2有其合理性。

图3-1 各特征值的贡献率示意图

3.10小结

本章主要介绍线性代数中矩阵及向量有关概念,以及相关规则和运算等。线性代数是机器学习、深度学习的重要基础,与之相当的还有概率与信息论,我们将在下一章介绍。

本章代码下载

第2章 Theano基础


第1章我们介绍了NumPy,它是数据计算的基础,更是深度学习框架的基石。但如果直接使用NumPy计算大数据,其性能已成为一个瓶颈。
随着数据爆炸式增长,尤其是图像数据、音频数据等数据的快速增长,迫切需要突破这个瓶颈。需求就是强大动力!通过大咖们的不懈努力,在很多方面取得可喜进展,如硬件有GPU,软件有Theano、Keras、TensorFlow,算法有卷积神经网络、循环神经网络等等。
Theano是Python的一个库,为开源项目,在2008年,由Yoshua Bengio领导的加拿大蒙特利尔理工学院LISA实验室开发。对于解决大量数据的问题,使用Theano可能获得与手工用C实现差不多的性能。另外通过利用GPU,它能获得比CPU上的快很多数量级的性能。Theano开发者在2010年公布的测试报告中指出:在CPU上执行程序时,Theano程序性能是NumPy的1.8倍,而在GPU上是NumPy的11倍。这还是2010的测试结果,近些年无论是Theano还是GPU,性能都有显著提高。
这里我们把Theano作为基础来讲,除了其性能方面的跨越外,它还是“符合计算图”的开创者,当前很多优秀的开源工具,如TensorFlow、Keras等,都派生于或借鉴了Theano的底层设计。所以了解Theano的使用,将有助于我们更好学习TensorFlow、Keras等其他开源工具。
至于Theano是如何实现性能方面的跨越?如何用“符号计算图”来运算等,本章都将有所涉猎,但限于篇幅无法深入分析,只做一些基础性的介绍。涵盖的主要内容:
 如何安装Theano
 符号变量是什么
 如何设计符号计算图
 函数的功能
 共享变量的妙用

2.1安装

这里主要介绍Linux+Anaconda+theano环境的安装说明,Linux为CentOS或Ubuntu都可以,安装Python、NumPy、SciPy等,建议使用Anaconda来安装,当然也可用pip进行安装。最好使用工具来安装,这样可以避免很多程序依赖的麻烦,而且日后的软件升级维护也很方便。
Theano支持CPU、GPU,如果使用GPU还需要安装其驱动程序如CUDA等,限于篇幅这里只介绍CPU的(TensorFlow将介绍基于GPU的安装),有关GPU的安装,大家可参考:http://www.deeplearning.net/software/theano/install.html
以下为安装主要步骤:
(1)安装anaconda
从anaconda官网:https://www.anaconda.com/download/下载linux环境最新的软件包,Python版本建议选择3系列的,2系列后续将不再维护。下载文件为一个sh程序包:如:
Anaconda3-4.3.1-Linux-x86_64.sh,然后在下载目录下运行如下命令:

bash Anaconda3-4.3.1-Linux-x86_64.sh

安装过程中按enter或y即可,安装完成后,程序提示是否把anaconda的binary加入到.bashrc配置文件中,加入后运行python、ipython时将自动使用新安装的Python环境。
安装完成后,你可用conda list命令查看已安装的库:

conda list

安装成功的话,应该能看到numpy、scipy、matplotlib、conda等库。
(2)安装theano
利用conda 来安装或更新程序

conda install theano

(3)测试
先启动python,然后导入theano模块,如果不报错,说明安装成功。

$ python
Python 3.6.0 |Anaconda custom (64-bit)| (default, Dec 23 2016, 12:22:00)
[GCC 4.4.7 20120313 (Red Hat 4.4.7-1)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import theano
>>>

2.2符号变量

存储数据需要用到各种变量,Theano如何使用变量的呢?Theano用一种变量类型称为符号变量来表示变量,用TensorVariable表示,又称为张量(Tensor),张量是Theano的核心元素(也是TensorFlow的核心元素),它是Theano表达式和运算操作的基本单位。张量可以是标量(scalar)、向量(vector)、矩阵(matrix)等的统称。具体来说,标量就是我们通常看到的0阶的张量,如12,a等,而向量和矩阵分别为1阶张量和2阶的张量。
如果通过这些概念,你还不很清楚,没有关系,可以结合以下一个实例,来直观感受一下。
首先定义三个标量:一个代表输入x、一个代表权重w、一个表示偏移量b,然后计算这些标量运算结果z=x*w+b,Theano代码实现如下:

打印结果
net_input: 6.500000

通过以上实例我们不难看出,Theano本身是一个通用的符号计算框架,与非符号架构的框架不同,它先使用tensor variable初始化变量,然后将复杂的符号表达式编译成为函数模型,最后运行时传入实际数据进行计算。整个过程涉及三个步骤:定义符号变量,编译代码,执行代码。这节主要介绍第一步如何定义符号变量,其他步骤将在后续小节介绍。
如何定义符号变量?或定义符号变量有哪些方式?在Theano中定义符号变量有大致三种:使用内置的变量类型、自定义变量类型、转换其他的变量类型,具体如下:
(1)使用内置的变量类型创建
目前Theano支持7种内置的变量类型,分别是标量(scalar)、向量(vector)、行(row)、列(col)、矩阵(matrix)、tensor3、tensor4等。其中标量是0阶张量,向量为1阶张量,矩阵为二阶张量等,以下为创建内置变量的实例:

其中,name指定变量名字,dtype指变量的数据类型。
(2)自定义变量类型
内置的变量类型只能处理4维及以下的变量,如果需要处理更高维的数据时,我们可以使用Theano的自定义变量类型,具体通过TensorType方法来实现:

其中broadcastable是True或False的布尔类型元组,元组的大小等于变量的维度,如果为True,表示变量在对应维度上的数据可以进行广播,否则数据不能广播。
广播机制(broadcast)是一种重要机制,有了这种机制,就可以方便对不同维的张量进行运算,否则,就要手工把低维数据变成高维,利用广播机制系统自动利用复制等方法把低维数据补齐,numpy也有这种机制。以下我们通过图1-2所示的一个实例来说明广播机制原理:

图2-1 广播机制

图2-1中矩阵与向量相加的具体代码如下:

(3) 将Python类型变量或者Numpy类型变量转化为Theano共享变量
共享变量是Theano实现变量更新的重要机制,后面我们会详细讲解。要创建一个共享变量,只要把一个Python对象或NumPy对象传递给shared函数即可,如下示例:

2.3符号计算图模型

符号变量定义后,需要说明这些变量间的运算关系,如何描述变量间的运算关系? Theano实际采用符号计算图模型来实现。首先创建表达式所需的变量,然后通过操作符(op)把这些变量结合在一起。如图2-1。
Theano处理符号表达式时通过把符号表达式转换为一个计算图(graph)来处理(TensorFlow也使用了这种方法,等到我们介绍TensorFlow时,大家可对比一下),符号计算图的节点有:variable、type、apply和op。
variable节点:即符号的变量节点,符号变量是符号表达式存放信息的数据结构,可以分为输入符号和输出符号。
type节点:当定义了一种具体的变量类型以及变量的数据类型时,Theano为其指定数据存储的限制条件。
apply节点:把某一种类型的符号操作符应用到具体的符号变量中,与variable不同,apply节点无须由用户指定,一个apply节点包括3个字段:op、inputs、outputs。
op节点:即操作符节点,定义了一种符号变量间的运算,如+、-、sum()、tanh()等。
Theano是将符号表达式的计算表示成graphs。这些graphs是由Apply 和 Variable将节点连接而组成,它们分别与函数的应用和数据相连接。 操作由 Op 实例表示,而数据类型由 Type 实例表示。下面这段代码和图2-2,说明了这些代码所构建的结构。借助这个图或许有助于您进一步理解,如何将这些内容拟合在一起:

图2-2 符号计算图
图2-2中箭头表示指向python对象的引用。这里的蓝色盒子是一个 Apply 节点,红色盒子是 Variable 节点,绿色圆圈是Ops,紫色盒子是 Types。
在创建 Variables 之后,应用 Apply Ops得到更多的变量,这些变量仅仅是一个占位符,在function中作为输入。变量指向 Apply 节点的过程是用来表示函数通过它们的owner 域来生成它们 。这些Apply节点是通过它们的inputs和outputs域来得到它们的输入和输出变量。
x 和 y 的owner 域的指向都是None,是因为它们不是另一个计算的结果。如果它们中的一个是另一个计算的结果,那么owner域将会指向另一个蓝色盒。

2.4函数

上节我们介绍了如何把一个符号表达式转化为符号计算图,这节我们介绍函数的功能,函数是Theano的一个核心设计模块,它提供一个接口,把函数计算图编译为可调用的函数对象。前面介绍了如何定义自变量x(不需要赋值),这节介绍如何编写函数方程。
(1)函数定义的格式
theano.function(inputs, outputs, mode=None, updates=None, givens=None, no_default_updates=False, accept_inplace=False, name=None,rebuild_strict=True, allow_input_downcast=None, profile=None, on_unused_input='raise')。
这里参数看起来很多,但常用的一般只用到三个,inputs表示自变量、outputs表示函数的因变量(也就是函数的返回值),还有另外一个比较常用的是updates这个参数,这个一般用于神经网络共享变量参数更新,通常以字典或元组列表的形式指定;givens是一个字典或元组列表,记为[(var1,var2)],表示在每一次函数调用时,在符号计算图中,把符号变量var1节点替换为var2节点,该参数常用来指定训练数据集的batch大小。
我们看一个有多个自变量、同时又有多个因变量的函数定义例子:

打印结果
[array(5.0, dtype=float32), array(6.0, dtype=float32)]
在执行theano.function()时,Theano进行了编译优化,得到一个end-to-end的函数,传入数据调用f(2,3)时,执行的是优化后保存在图结构中的模型,而不是我们写的那行z=x+y,尽管二者结果一样。这样的好处是Theano可以对函数f进行优化,提升速度;坏处是不方便开发和调试,由于实际执行的代码不是我们写的代码,所以无法设置断点进行调试,也无法直接观察执行时中间变量的值。
(2)自动求导
Theano有了符号计算图2-2,自动计算导数就很容易了。tensor.grad()唯一需要做的就是从outputs逆向遍历到输入节点。对于每个Op,它都定义了怎么根据输入计算出偏导数。使用链式法则就可以计算出梯度了。利用Theano求导时非常方便,可以直接利用函数theano.grad(),比如求S函数的导数:

以下代码实现当x=3的时候,求s函数的导数:

打印结果
0.045176658779382706
(3)更新共享变量参数
在深度学习中通常需要迭代多次,每次迭代都需要更新参数。Theano如何更新参数呢?在theano.function函数中,有个非常重要的参数updates,updates是一个包含两个元素的列表或tuple,updates=[old_w,new_w],当函数被调用的时候,这个会用new_w替换old_w,具体看下面这个例子。

打印结果:1、4
在求梯度下降的时候,经常用到updates这个参数。比如updates=[w,w-α*(dT/dw)],其中dT/dw就是我们梯度下降的时候,代价函数对参数w的偏导数,α是学习速率。为便于大家的更全面的了解Theano函数一些使用,下面我们通过一个逻辑回归的完整实例来说明:

2.5条件与循环

编写函数需要经常用到条件或循环,这节我们就简单介绍Theano如何实现条件判断或逻辑循环。 (1)条件判断 Theano是一种符号语言,条件判断不能直接使用Python的if语句。在Theano可以用ifelse 和 Switch来表示判定。这两个判决语句有何区别呢? switch对每个输出变量进行操作,ifelse只对一个满足条件的变量操作,比如: 对语句: switch(cond, ift, iff) 如果满足条件,则switch既执行ift也执行iff。对语句: if cond then ift else iff ifelse只执行ift或者只执行iff 以下通过一个示例进一步说明

打印结果
time spent evaluating both values 0.005268 sec
time spent evaluating one value 0.007501 sec

(2)循环语句
scan是theano中构建循环Graph的方法,scan是个灵活复杂的函数,任何用循环、递归或者跟序列有关的计算,都可以用scan完成。其格式如下:
theano.scan(fn, sequences=None, outputs_info=None, non_sequences=None, n_steps=None, truncate_gradient=-1, go_backwards=False, mode=None, name=None, profile=False, allow_gc=None, strict=False)
参数说明:
fn:函数类型,scan的一步执行。除了outputs_info,fn可以返回sequences变量的更新updates。fn的输入变量顺序为sequences中的变量,outputs_info的变量,non_sequences中的变量。如果使用了taps,则按照taps给fn喂变量。taps的详细介绍会在后面的例子中给出。
sequences:scan进行迭代的变量,scan会在T.arange()生成的list上遍历,例如下面的polynomial 例子。
outputs_info:初始化fn的输出变量,和输出的shape一致。如果初始化值设为None表示这个变量不需要初始值。
non_sequences:fn函数用到的其他变量,迭代过程中不可改变(unchange)。
n_steps:fn的迭代次数。
下面通过一个例子解释scan函数的具体使用方法。
代码实现思路是:先定义函数one_step,它就是scan里的fn,其任务就是计算多项式的一项,scan函数返回的result里会保存多项式每一项的值,然后我们对result求和,就得到了多项式的值。

打印结果
多项式各项的值: [ 2 30 400 6000 50000]
多项式和的值: 56432

2.6共享变量

共享变量(shared variable)是实现机器学习算法参数更新的重要机制。shared函数会返回共享变量。这种变量的值在多个函数可直接共享。可以用符号变量的地方都可以用共享变量。但不同的是,共享变量有一个内部状态的值,这个值可以被多个函数共享。它可以存储在显存中,利用GPU提高性能。我们可以使用get_value和set_value方法来读取或者修改共享变量的值,使用共享变量实现累加操作。

这里state是一个共享 变量,初始化为0,每次调用accumulator(),state都会加上inc。共享变量可以像普通张量一样用于符号表达式,另外,他还有自己的值,可以直接用.get_value()和.set_value()方法来访问和修改。
上述代码引入了函数中updates参数。updates参数是一个list,其中每个元素是一个元组(tuple),这个tuple的第一个元素是一个共享变量,第二个元素是一个新的表达式。updatas中的共享变量会在函数返回后更新自己的值。updates的作用在于执行效率,updates多数时候可以用原地(in-place)算法快速实现,在GPU上,Theano可以更好地控制何时何地给共享变量分配空间,带来性能提升。最常见的神经网络权值更新,一般会用update实现。

2.7小结

Theano基于NumPy,但性能方面又高于NumPy。因Theano采用了张量(Tensor)这个核心元素,在计算方面采用符号计算模型,而且采用共享变量、自动求导、利用GPU等适合于大数据、深度学习的方法,其他很多开发项目也深受这些技术和框架影响。本章主要为后续介绍TensorFlow做个铺垫。

本章代码下载

第1章 NumPy常用操作

NumPy是Python基础,更是数据科学的通用语言,而且与TensorFlow关系密切,所以我们把它列为第一章。
NumPy为何如此重要?实际上Python本身含有列表(list)和数组(array),但对于大数据来说,这些结构有很多不足。因列表的元素可以是任何对象,因此列表中所保存的是对象的指针。这样为了保存一个简单的[1,2,3],都需要有3个指针和三个整数对象。对于数值运算来说这种结构显然比较浪费内存和CPU计算时间。 至于array对象,它直接保存数值,和C语言的一维数组比较类似。但是由于它不支持多维,也没有各种运算函数,因此也不适合做数值运算。
NumPy(Numerical Python 的简称)的诞生弥补了这些不足,NumPy提供了两种基本的对象:ndarray(N-dimensional array object)和 ufunc(universal function object)。ndarray是存储单一数据类型的多维数组,而ufunc则是能够对数组进行处理的函数。
NumPy的主要特点:
 ndarray,快速和节省空间的多维数组,提供数组化的算术运算和高级的广播功能。
 使用标准数学函数对整个数组的数据进行快速运算,而不需要编写循环。
 读取/写入磁盘上的阵列数据和操作存储器映像文件的工具。
 线性代数,随机数生成,和傅里叶变换的能力。
 集成C,C++,Fortran代码的工具。
在使用 NumPy 之前,需要先导入该模块:

本章主要内容如下:
 如何生成NumPy的ndarray的几种方式
 如何存取元素
 如何操作矩阵
 如何合并或拆分数据
 简介NumPy的通用函数
 简介NumPy的广播机制

1.1生成ndarray的几种方式

NumPy封装了一个新的数据类型ndarray(n-dimensional array),一个多维数组对象,该对象封装了许多常用的数学运算函数,方便我们做数据处理,以及数据分析,如何生成ndarray呢?这里我们介绍生成ndarray的几种方式,如从已有数据中创建;利用random创建;创建特殊多维数组;使用arange函数等。
1.从已有数据中创建
直接对python的基础数据类型(如列表、元组等)进行转换来生成ndarray。
(1)将列表变换成ndarray

打印结果
[ 3.14 2.17 0. 1. 2. ]
<class 'numpy.ndarray'>
(2)嵌套列表可以转换成多维ndarray

打印结果
[[ 3.14 2.17 0. 1. 2. ]
[ 1. 2. 3. 4. 5. ]]
<class 'numpy.ndarray'>
如果把(1)和(2)中的列表换成元组也同样适合。
2.利用random模块生成ndarray
在深度学习中,我们经常需要对一些变量进行初始化,适当的初始化能提高模型的性能。通常我们用随机数生成模块random来生成,当然random模块又分为多种函数:
random生成0到1之间的随机数;uniform生成均匀分布随机数;randn生成标准正态的随机数;normal生成正态分布;shuffle随机打乱顺序;seed设置随机数种子等等,以下我们列举几个简单示例。

打印结果
[[ 0.88900951 0.47818541 0.91813526]
[ 0.48329167 0.63730656 0.14301479]
[ 0.9843789 0.99257093 0.24003961]]
<class 'numpy.ndarray'>
生成一个随机种子,对生成的随机数打乱。

打印结果
[[-1.0856306 0.99734545 0.2829785 ]
[-1.50629471 -0.57860025 1.65143654]]
随机打乱后数据
[[-1.50629471 -0.57860025 1.65143654]
[-1.0856306 0.99734545 0.2829785 ]]
<class 'numpy.ndarray'>
3. 创建特定形状的多维数组
数据初始化时,有时需要生成一些特殊矩阵,如0或1的数组或矩阵,这时我们可以利用np.zeros,np.ones,np.diag来实现,以下我们通过几个示例来说明。

我们还可以把生成的数据保存到磁盘,然后从磁盘读取。

4.利用arange函数
arange是numpy模块中的函数,其格式为:arange([start,] stop[, step,], dtype=None)
根据start与stop指定的范围,以及step设定的步长,生成一个 ndarray,其中start缺省值为0,步长step可为小数。

1.2存取元素

上节我们介绍了生成ndarray的几种方法,数据生成后,如何读取我们需要的数据?这节我们介绍几种读取数据的方法。

如果你对上面这些获取方式还不是很清楚,没关系,下面我们通过图形的方式说明如何获取多维数组中的元素,如图1-1所示,左边的为表达式,右边为对应获取元素。

图1-1 获取多维数组中的元素
获取数组中的部分元素除通过指定索引标签外,还可以使用一些函数来实现,如通过random.choice函数可以从指定的样本中进行随机抽取数据。

打印结果:
随机可重复抽取
[[ 7. 22. 19. 21.]
[ 7. 5. 5. 5.]
[ 7. 9. 22. 12.]]
随机但不重复抽取
[[ 21. 9. 15. 4.]
[ 23. 2. 3. 7.]
[ 13. 5. 6. 1.]]
随机但按制度概率抽取
[[ 15. 19. 24. 8.]
[ 5. 22. 5. 14.]
[ 3. 22. 13. 17.]]

1.3矩阵操作

深度学习中经常涉及多维数组或矩阵的运算,正好NumPy模块提供了许多相关的计算方法,下面介绍一些常用的方法。

上面介绍的几种方法是numpy.linalg模块中的函数,numpy.linalg模块中的函数是满足行业标准级的Fortran库,具体请看表1-1。

表1-1 numpy.linalg中常用函数

1.4数据合并与展平

在机器学习或深度学习中,经常遇到需要把多个向量或矩阵按某轴方向进行合并,展平也经常使用,如在卷积或循环神经网络中,在全连接层之前,需要把矩阵展平。这节介绍几种数据合并和展平的方法。
(1)合并一维数组

打印结果:
[1 2 3 4 5 6]
[1 2 3 4 5 6]
(2)多维数组的合并