第2章
图像识别前置技术
本章将主要讲解目前主流的深度学习平台、如何搭建本书推荐的开发环境以及图像识别的前置技术Numpy。图像处理的大部分场景都需要将图像转换成向量(或者矩阵)以便于进行后续的图像识别处理。Numpy包中提供了非常好的矩阵运算,因此,学习并掌握Numpy,在后续的图像识别学习中会起到重要作用。
本章的要点具体如下。
- 深度学习平台概述。
- 搭建图像识别开发环境。
- Numpy的使用详解。
2.1 深度学习框架
近几年,深度学习技术的大爆炸式发展,除了理论方面的突破外,还有基础架构的突破,这些都奠定了深度学习繁荣发展的基础。本节将对其中涌现出的几个著名的深度学习平台进行简要介绍。
2.1.1 Theano
Theano是在BSD许可证下发布的一个开源项目,是由LISA集团(现MILA)在加拿大魁北克的蒙特利尔大学开发的,其是以一位希腊数学家的名字命名的。
Theano是一个Python库,可用于定义、优化和计算数学表达式,特别是多维数组(numpy.ndarray)。它的诞生是为了执行深度学习中的大规模神经网络算法,从本质上而言,Theano可以被理解为一个数学表达式的编译器:用符号式语言定义程序员所需的结果,并且Theano可以高效地运行于GPU或CPU中。
在过去很长一段时间内,Theano是深度学习开发与研究的行业标准。而且,由于出身学界,它最初是为学术研究而设计的,这也导致深度学习领域的许多学者至今仍在使用Theano。但随着Tensorflow在Google的支持下强势崛起,Theano日渐式微,使用Theano的人也越来越少。这个转变的标志性事件是:创始者之一的Ian Goodfellow放弃Theano转而去Google开发Tensorflow了。
尽管Theano已退出历史舞台,但作为Python的第一个深度学习框架,它很好地完成了自己的使命,为深度学习研究人员的早期拓荒提供了极大的帮助,同时也为之后的深度学习框架的开发奠定了基本的设计方向:以计算图为框架的核心,采用GPU加速计算。
总结:对于深度学习新手,可以使用Theano来练手;但对于职业开发者,建议使用Tensorflow。
2.1.2 Tensorflow
2015年11月10日,Google宣布推出全新的机器学习开源工具Tensorflow。Tensorflow最初是由Google机器智能研究部门的Google Brain团队开发,基于Google 2011年开发的深度学习基础架构DistBelief构建起来的。Tensorflow是广泛使用的实现机器学习以及其他涉及大量数学运算的算法库之一。Google几乎在所有应用程序中都使用Tensorflow来实现机器学习。例如,如果你使用过Google照片或Google语音搜索,那么你就间接使用了Tensorflow模型。它们在大型Google硬件集群上工作,在感知任务方面,功能非常强大。
Tensorflow在很大程度上可以看作是Theano的后继者,不仅因为它们有很大一批共同的开发者,而且它们还拥有相近的设计理念:它们都是基于计算图实现自动微分系统。Tensorflow使用数据流图进行数值计算,图中的节点代表数学运算,图中的边则代表在这些节点之间传递的多维数组(tensor)。
Tensorflow编程接口支持Python和C++。随着1.0版本的公布,Java、Go、R和Haskell API的alpha版本也得到支持。此外,Tensorflow还可在Google Cloud和AWS中运行。Tensorflow还支持Windows 7、Windows 10和Windows Server 2016。由于Tensorflow使用C++ Eigen库,所以库可在ARM架构上进行编译和优化。这也就意味着用户可以在各种服务器和移动设备上部署自己的训练模型,而无须执行单独的模型解码器或者加载Python解释器。
作为当前最流行的深度学习框架,Tensorflow获得了极大成功,但在学习过程中读者需要注意下面这些问题。
- 由于Tensorflow的接口一直处于快速迭代之中,并且版本之间存在不兼容的问题,因此开发和调试过程中可能会出现一些问题(许多开源代码无法在新版的Tensorflow上运行)。
- 想要学习Tensorflow底层运行机制的读者需要做好准备,Tensorflow在GitHub代码仓库的总代码量超过100万行,系统设计比较复杂,因此这将是一个漫长的过程。
- 代码层面,对于同一个功能,Tensorflow提供了多种实现,这些实现良莠不齐,使用中还存在细微的区别,请读者注意,避免入坑。另外,Tensorflow还创造了图、会话、命名空间、PlaceHolder等诸多抽象概念,对普通用户来说可能会难以理解。
总结:凭借着Google强大的推广能力,Tensorflow已经成为当今最为热门的深度学习框架,虽不完美但是最流行,目前,各公司使用的框架也不统一,读者有必要多学习几个流行框架以作为知识储备,Tensorflow无疑是一个不错的选择。
项目地址:https://github.com/tensorflow/tensorflow 。
2.1.3 MXNet
MXNet是亚马逊(Amazon)的李沐带队开发的深度学习框架。它拥有类似于Theano和Tensorflow的数据流图,为多GPU架构提供了良好的配置,有着类似于Lasagne和Blocks的更高级别的模型构建块,并且可以在你想象的任何硬件上运行(包括手机)。对Python的支持只是其功能的冰山一角,MXNet同样提供了对R、Julia、C++、Scala、Matlab、Golang和Java的接口。
MXNet以其超强的分布式支持,明显的内存、显存优化为人所称道。同样的模型,MXNet往往占用更小的内存和显存,并且在分布式环境下,MXNet展现出了明显优于其他框架的扩展性能。
MXNet的缺点是推广不给力及接口文档不够完善。MXNet长期处于快速迭代的过程中,其文档却长时间未更新,这就导致新手用户难以掌握MXNet,老用户则需要常常查阅源码才能真正理解MXNet接口的用法。
总结:MXNet文档比较混乱导致其不太适合新手入门,但其分布性能强大,语言支持比较多,比较适合在云平台使用。
项目主页:https://mxnet.incubator.apache.org/ 。
2.1.4 Keras
Keras是一个高层神经网络API,由纯Python语言编写而成,并使用Tensorflow、Theano及CNTK作为后端。Keras为支持快速实验而生,能够将想法迅速转换为结果。Keras应该是深度学习框架之中最容易上手的一个,它提供了一致而简洁的API,能够极大地减少一般应用下用户的工作量,避免用户重复造*,而且Keras支持无缝CPU和GPU的相互转换。
为了屏蔽后端的差异性,Keras做了层层封装,导致用户在新增操作或是获取底层的数据信息时过于困难。同时,过度封装也使得Keras的程序过于缓慢,许多bug都隐藏于封装之中。另外就是学习Keras十分容易,但是很快就会遇到瓶颈,因为它缺少灵活性。另外,在使用Keras的大多数时间里,用户主要是在调用接口,很难真正学习到深度学习的内容。
总结:Keras比较适合作为练习使用的深度学习框架,但是因为其过度的封装导致并不适合新手学习(无法理解深度学习的真正内涵),故不推荐。
项目主页:https://keras.io 。
2.1.5 PyTorch
PyTorch是一个Python优先的深度学习框架,能够在强大的GPU加速的基础上实现张量和动态神经网络。
PyTorch是一个Python软件包,其提供了两种高层面的功能,具体如下。
1)使用强大的GPU加速的Tensor计算(类似于Numpy)。
2)构建基于tape的autograd系统的深度神经网络。
3)活跃的社区:PyTorch提供了完整的文档,循序渐进的指南,作者亲自维护论坛以供用户交流和求教问题。Facebook人工智能研究院(FAIR)对PyTorch提供了强力支持,作为当今排名前三的深度学习研究机构,FAIR的支持足以确保PyTorch获得持续的开发更新,而不至于像许多由个人开发的框架那样昙花一现。
如有需要,你也可以复用你喜欢的Python软件包(如Numpy、scipy和Cython)来扩展PyTorch。
相对于Tensorflow,PyTorch的一大优点是,它的图是动态的,而Tensorflow等都是静态图,不利于扩展。同时,PyTorch非常简洁,方便使用。本书将选取PyTorch作为图像识别的主要实现框架。
总结:如果说TensorFlow的设计是“Make It Complicated”,Keras的设计是“Make It Complicated And Hide It”,那么PyTorch的设计真正做到了“Keep it Simple,Stupid”。
项目地址:http://pytorch.org/ 。
2.1.6 Caffe
Caffe是基于C++语言编写的深度学习框架,作者是中国人贾杨清。它开放源码(具有Licensed BSD),提供了命令行,以及Matlab和Python接口,清晰、可读性强、容易上手。
Caffe是早期深度学习研究者使用的框架,由于很多研究人员在上面进行开发和优化,因此其现今也是流行的框架之一。Caffe也存在不支持多机、跨平台、可扩展性差等问题。初学者使用Caffe时还需要注意下面这些问题。
1)Caffe的安装过程需要大量的依赖库,因此会涉及很多安装版本问题,初学者不易上手。
2)当用户想要实现一个新的层时,需要用C++实现它的前向传播和反向传播代码,而如果想要新层运行在GPU之上,则需要同时使用CUDA实现这一层的前向传播和反向传播。
Caffe2出自Facebook人工智能实验室与应用机器学习团队,但贾杨清仍是主要贡献者之一。Caffe2在工程上做了很多优化,比如运行速度、跨平台、可扩展性等,它可以看作是Caffe更细粒度的重构,但在设计上,其实Caffe2与TensorFlow更像。目前代码已开源。
总结:至今工业界和学界仍有很多人在使用Caffe,而Caffe2的出现为我们提供了更多的选择。
项目地址:Caffe:http://caffe.berkeleyvision.org/
Caffe2:https://caffe2.ai/
2.2 搭建图像识别开发环境
本节将带领读者一步一步安装开发环境,安装环境主要是由Anaconda与PyTorch组成。
2.2.1 Anaconda
要想使用PyTorch,首先需要安装Python。Python可以在https://www.python.org 上下载,当需要某个软件包时可单独进行下载并安装。本书推荐读者使用Anaconda,Anaconda是一个用于科学计算的Python发行版,支持Linux、Mac、Windows系统,能让你在数据科学的工作中轻松安装经常使用的程序包。Anaconda的下载地址:https://www.anaconda.com/distribution/#download-section 。
在介绍Anaconda之前首先提一下Conda(2.2.2节会详细介绍)。Conda是一个工具,也是一个可执行命令,其核心功能是包的管理与环境管理,它支持多种语言,因此用其来管理Python包也是绰绰有余的。这里注意区分一下conda和pip,pip可以在任何环境中安装Python包,而conda则可以在conda环境中安装任何语言包。因为Anaconda中集合了conda,因此可以直接使用conda进行包和环境的管理。
- 包管理:不同的包在安装和使用的过程中都会遇到版本匹配和兼容性等问题,在实际工程中经常会使用大量的第三方安装包,若人工手动进行匹配是非常耗时耗力的事情,因此包管理是非常重要的内容。
- 环境管理:用户可以使用conda来创建虚拟环境,其可以很方便地解决多版本Python并存、切换等问题。
本书在第2~7章的代码运行环境为macOS,下载的Anaconda对应的Python版本为3.7,如图2-1a所示;第8~12章的代码运行环境为Linux,下载的Anaconda对应的Python版本为2.7,如图2-1b所示。下载Anaconda之后,Windows和MacOS用户按照默认提示进行图形化安装即可,Linux用户可用命令行sh Anaconda2-x.x.x-Linux-x86_64.sh进行安装(由于Anaconda一直在更新,因此读者使用的版本号可能与书中的版本不一致,但问题不大)。
Mac上安装完Anaconda之后,在应用程序界面里就能看到Anaconda Navigator的图标,点击运行之后就能看到如图2-2所示的界面,然后选Notebook,点击“Launch”按钮,浏览器中会出现如图2-3所示的画面。Windows可以从“开始”菜单中找到Anaconda,然后点击Jupyter Notebook运行。
展开右上角菜单New,选择Python3,即可新建一个编写代码的页面,然后在网页窗口中的“In”区域输入“1+1”,最后按“Shift”+“Enter”键,我们会看到Out区域的显示为2,这说明我们的Anaconda环境部署成功了,如图2-4所示。
JupyterNotebook提供的功能之一就是可以使我们多次编辑Cell(代码单元格),在实际开发当中,为了得到最好的效果,我们往往会对测试数据(文本)使用不同的技术进行解析与探索,因此Cell的迭代分析数据功能变得特别有用。
延伸学习:
本节主要介绍了Anaconda的基本概念和使用方法,如果读者需要对Anaconda中的组件JupyterNotebook进行更深入的了解,可以访问官方文档(https://jupyter.readthedocs.io/en/latest/install.html )。
2.2.2 conda
由于在后续的学习过程中,我们将多次用到conda,因此本书单独组织一个小节来介绍它。
1.包的安装和管理
conda对包的管理都是通过命令行来实现的(Windows用户可以参考面向Windows的命令提示符教程),若想要安装包,那么在终端中输入conda install package_name即可。例如,要安装Numpy,输入如下代码:
conda install numpy
你可以同时安装多个包。类似conda install numpy scipy pandas的命令会同时安装所有这些包。你还可以通过添加版本号(例如,conda install numpy=1.10)来指定所需的包版本。
conda还会自动为你安装依赖项。例如,scipy依赖于Numpy,如果你只安装scipy(conda install scipy),则conda还会安装Numpy(如果尚未安装的话)。
conda的大多数命令都是很直观的。要卸载包,请使用conda remove package_name;要更新包,请使用conda update package_name。如果想更新环境中的所有包(这样做常常很有用),请使用conda update --all。最后,要想列出已安装的包,请使用前面提过的conda list。
如果不知道要找的包的确切名称,可以尝试使用conda search search_term进行搜索。例如,我想安装Beautiful Soup,但我不清楚包的具体名称,可以尝试执行conda search beautifulsoup,结果如图2-5所示。
提示:conda将几乎所有的工具,包括第三方包都当作package对待,因此conda可以打破包管理与环境管理的约束,从而能够更高效地安装各种版本的Python以及各种package,并且切换起来也很方便。
2.环境管理
除了管理包之外,conda还是虚拟环境管理器。环境能让你分隔用于不同项目的包。在实际工作中常常需要使用依赖于某个库的不同版本的代码,例如,你的代码可能使用了Numpy中的新功能,或者使用了已删除的旧功能。实际上,不可能同时安装两个Numpy版本。你要做的就是,为每个Numpy版本创建一个环境,然后在对应的环境中工作。这里再补充一下,每一个环境都是相互独立、互不干预的。
我们在上文中提到过不同的章节需要不同的运行环境,下面举例说明:
#创建第2~7章代码运行的环境:
conda create -n basic_env python=3.7 #创建一个名为basic_env的环境
source activate basic_env #激活这个环境—Linux和macOS代码
activate basic_env #激活这个环境—Windows代码
#创建第8~12章代码运行的环境:
conda create -n imgrecognition_env python=3.7
#创建一个名为imgrecognition _env的环境
source activate imgrecognition _env #激活这个环境—Linux和macOS代码
activate imgrecognition_env #激活这个环境—Windows代码
2.2.3 Pytorch的下载与安装
安装完Anaconda环境之后,我们已经有了Python的运行环境以及基础的数学计算库了,接下来,我们开始学习如何安装PyTorch。首先,进入PyTorch的官方网站(https://pytorch.org ),如图2-6所示。
按照系统提示,我们可以使用系统推荐的命令进行安装。值得注意的是,如果你的电脑没有支持的显卡进行GPU加速,那么CUDA这个选项就选择None。本书使用的电脑并没有GPU加速的显卡,所以只介绍了非GPU的安装过程。
2.3 Numpy使用详解
Numpy(Numerical Python的简称)是高性能科学计算和数据分析的基础包,其提供了矩阵运算的功能。Numpy提供的主要功能具体如下。
- ndarray—一个具有向量算术运算和复杂广播能力的多维数组对象。
- 用于对数组数据进行快速运算的标准数学函数。
- 用于读写磁盘数据的工具以及用于操作内存映射文件的工具。
- 非常有用的线性代数,傅里叶变换和随机数操作。
- 用于集成C /C++和Fortran代码的工具。
除了明显的科学计算用途之外,Numpy还可以用作通用数据的高效多维容器,定义任意的数据类型。这些都使得Numpy能够无缝、快速地与各种数据库集成。
提示:这里提到的“广播”可以这么理解:当两个维度不同的数组(array)运算的时候,可以将低维的数组复制成高维数组参与运算(因为Numpy运算的时候需要结构相同)。
在学习图像识别的过程中,需要将图片转换为矩阵。即将对图片的处理简化为向量空间中的向量运算。基于向量运算,我们就可以实现图像的识别。
2.3.1 创建数组
现在就来关注下Numpy中的一些核心知识点。在Numpy中,最核心的数据结构是ndarray, ndarray代表的是多维数组,数组指的是数据的集合。为了方便理解,我们下面列举一个小例子。
一个班级里学生的学号可以通过一维数组来表示,数组名为a,数组a中存储的是数值类型的数据,分别是1,2,3,4。
其中,a[0]代表的是第一个学生的学号1,a[1]代表的是第二个学生的学号2,以此类推。
一个班级里学生的学号和姓名,可以用二维数组来表示,数组名为b。
类似的,其中b[0,0]代表的就是1(学号),b[0,1]代表的就是Tim(学号为1的学生的名字),以此类推b[1,0]代表的是2(学号)等。
借用线性代数的说法,一维数组通常称为向量(vector),二维数组通常称为矩阵(matrix)。
当我们安装完Anaconda之后,默认情况下Numpy已经在库中了,所以不需要额外安装。下面我们来写一些语句简单测试下Numpy库。
1)在Anaconda的Notebook里输入import numpy as np之后,通过键盘按住Shift+Enter执行,如果没有报错,则说明Numpy已被正常引入,如图2-7所示。
稍微解释下这条语句:通过import关键字将Numpy库引入,然后通过as为其取一个别名np,别名的作用是为了便于后续引用。
2)Numpy中的array()可以直接导入向量,代码如下:
vector = np.array([1,2,3,4])
3)numpy.array()方法也可以导入矩阵,代码如下:
matrix = np.array([[1,'Tim'],[2,'Joey'],[3,'Johnny'],[4,'Frank']])
轮到你来:
首先定义一个向量,然后分配一个变量名vector;定义一个矩阵,然后分配给变量matrix;最后通过Python中的print方法在Notebook中打印出结果。
2.3.2 创建Numpy数组
我们可以通过创建Python列表(list)的方式来创建Numpy矩阵,比如输入nparray = np.array([i for i in range(10)]),可以看到返回的结果是array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9])。同样,也可以通过Python列表的方式来修改值,比如输入nparray[0] = 10,再来观察nparray的向量内容就会发现返回的结果是array([ 10, 1, 2, 3, 4, 5, 6, 7, 8, 9])。
Numpy数组还封装了其他方法来创建矩阵。首先,我们介绍第一个方法np.zeros(从命名规则来看,这个方法就是用来创建数值都为0的向量),比如,我们输入:
a = np.zeros(10)
可以看到结果为:
array([ 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])
从上述结果可以看出,每一个0后面都有一个小数点,调用a.dtype会发现我们创建的这个向量的类型为dtype(‘float64’)。值得注意的是:在大部分图像识别算法开发中,我们使用的都是float64这个类型。如果希望在创建Numpy矩阵的时候强制规定一种类型,那么我们可以使用以下代码:
np.zeros(10,dtype=int)
这样,返回的结果在矩阵中的数据就都是整型0了。介绍完使用zeros方法创建向量之后,再来看看如何创建一个多维矩阵。我们可以使用传入元组的方式,代码如下:
np.zeros(shape=(3,4)) #代表创建的是三行四列的矩阵并且其数据类型为float64
返回的结果为:
array([[ 0., 0., 0., 0.],
[ 0., 0., 0., 0.],
[ 0., 0., 0., 0.]])
与np.zeros方法相似的还有np.ones方法,顾名思义,np.ones方法创建的矩阵的数值都为1。我们来举个例子:
np.ones((3,4))
返回的结果如下:
array([[ 1., 1., 1., 1.],
[ 1., 1., 1., 1.],
[ 1., 1., 1., 1.]])
读者可能会比较好奇,既然我们可以创建数值全为0的矩阵,也可以创建数值全为1的矩阵,那么Numpy是否提供了一个方法可以让我们自己指定值呢?答案是肯定的,这个方法就是np.full方法,我们来看一个例子,代码如下:
np.full((3,5),121) #这个方法的意思是我们创建了一个三行五列的矩阵,默认值为121
返回的结果是:
array([[121, 121, 121, 121, 121],
[121, 121, 121, 121, 121],
[121, 121, 121, 121, 121]])
我们也可以使用np.arange方法来创建Numpy的矩阵。示例代码如下:
np.arange(0,20,2) #arange接收三个参数,与Python中的range方法相似,arange也是前闭后开的方法,第一个参数为向量的第一个值0,第二个参数为最后一个值20,因为是后开所以取的是18,第三个参数为步长,默认为1,本例中设置为2,所以最后一个值是18。
返回的结果是:
array([ 0, 2, 4, 6, 8, 10, 12, 14, 16, 18])
我们可以使用np.linspace方法(前闭后闭)来对Numpy矩阵进行等分,比如将0~10等分为5份的代码如下:
np.linspace(0,10,5)
返回的结果是:
array([ 0. , 2.5, 5. , 7.5, 10. ])
下面通过几个例子再来看看在Numpy矩阵中如何生成随机数矩阵。
1)生成一个长度为10的向量,里面每一个数值都是介于0~10之间的整数,代码如下:
import numpy as np
np.random.randint(0,10,10)
2)如果不确定每个参数代表的意思,则加上参数名size,代码如下:
np.random.randint(0,5,size=5) #注意是前闭后开,永远取不到5
3)我们也可以生成一个三行五列的整数矩阵,代码如下
np.random.randint(4,9,size=(3,5))
4)seed的作用:如果不希望每次生成的随机数都不固定,那么我们可以使用np.random.seed(1),随机种子使用数字1记录,这以后只要是用随机种子1生成的随机数就都是固定的。
5)我们也可以生成介于0~1之间的浮点数的向量或者矩阵,代码如下:
np.random.random(10) #生成0~1之间的浮点数,向量的长度为10
np.random.random((2,4)) #生成0~1之间的浮点数,二行四列的矩阵
6)np.random.normal()表示的是一个正态分布,normal在这里是正态的意思。numpy.random.normal(loc=0,scale=1,size=shape)的意义如下。
- 参数loc(float):正态分布的均值,对应这个分布的中心。loc=0说明这是一个以Y轴为对称轴的正态分布。
- 参数scale(float):正态分布的标准差,对应分布的宽度,scale越大,正态分布的曲线越矮胖,scale越小,曲线越高瘦。
- 参数size(int或者整数元组):输出的值赋在shape里,默认为None。
2.3.3 获取Numpy属性
首先,我们通过Numpy中的一个方法arange(n),生成0到n-1的数组。比如,我们输入np.arange(15),可以看到返回的结果是array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14])。
然后,再通过Numpy中的reshape(row,column)方法,自动构架一个多行多列的array对象。
比如,我们输入:
a = np.arange(15).reshape(3,5) #代表3行5列
可以看到结果:
array([[ 0, 1, 2, 3, 4],
[ 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14]])
有了基本数据之后,我们就可以通过Numpy提供的shape属性获取Numpy数组的行数与列数,示例代码如下:
print(a.shape)
可以看到返回的结果是一个元组(tuple),第一个3代表的是3行,第二个5代表的是5列:
(3, 5)
轮到你来:
使用arange和reshape方法自定义一个Numpy数组,最后通过Python中的print方法打印出数组的shape值(返回的应该是一个元组类型)。
我们可以通过.ndim来获取Numpy数组的维度,示例代码如下:
importnumpy as np
x = np.arange(15)
print(x.ndim) #输出x向量的维度,这时能看到的维度是1维
X = x.reshape(3,5) #将x向量转为三行五列的二维矩阵
Print(X.ndim) #输出X矩阵的维度,这时能看到的维度是2维
reshape方法的特别用法
如果只关心需要多少行或者多少列,其他由计算机自己来算,那么这个时候我们可以使用如下方法:
x.reshape(15,-1) #我关心的是我只要15行,列由计算机自己来算
x.reshape(-1,15) #我关心的是我只要15列,行由计算机自己来算
2.3.4 Numpy数组索引
Numpy支持类似list的定位操作,示例代码如下:
import numpy as np
matrix = np.array([[1,2,3],[20,30,40]])
print(matrix[0,1])
得到的结果是2。
上述代码中的matrix[0,1],0代表的是行,在Numpy中,0代表起始的第一个,所以取的是第1行,之后的1代表的是列,所以取的是第2列。那么,最后的输出结果是取第一行第二列,也就是2这个值了。
2.3.5 切片
Numpy支持类似list的切片操作,示例代码如下:
import numpy as np
matrix = np.array([
[5, 10, 15],
[20, 25, 30],
[35, 40, 45]
])
print(matrix[:,1])
print(matrix[:,0:2])
print(matrix[1:3,:])
print(matrix[1:3,0:2])
上述的print(matrix[:,1])语法代表选择所有的行,而且列的索引是1的数据,因此返回的结果是10,25,40。
print(matrix[:,0:2])代表的是选取所有的行,而且列的索引是0和1的数据。
print(matrix[1:3,:])代表的是选取所有的列,而且行的索引值是1和2的数据。
print(matrix[1:3,0:2])代表的是选取行的索引是1和2,而且列的索引是0和1的所有数据。
2.3.6 Numpy中的矩阵运算
矩阵运算(加、减、乘、除),在本书中将严格按照数学公式来进行演示,即两个矩阵的基本运算必须具有相同的行数与列数。本例只演示两个矩阵相减的操作,其他的操作读者可以自行测试。示例代码如下:
import numpy as np
myones = np.ones([3,3])
myeye = np.eye(3) #生成一个对角线的值为1,其余值都为0的三行三列矩阵
print(myeye)
print(myones-myeye)
输出结果如下:
[[ 1. 0. 0.]
[ 0. 1. 0.]
[ 0. 0. 1.]]
[[ 0. 1. 1.]
[ 1. 0. 1.]
[ 1. 1. 0.]]
提示:numpy.eye(N, M=None, k=0, dtype=)中第一个参数输出矩阵(行数=列数),第三个参数默认情况下输出的是对角线的值全为1,其余值全为0。
除此之外,Numpy还预置了很多函数,使用这些函数可以作用于矩阵中的每个元素。下面我们来看下表2-1。
(1)矩阵之间的点乘
矩阵真正的乘法必须满足第一个矩阵的列数等于第二个矩阵的行数,矩阵乘法的函数为dot。示例代码如下:
import numpy as np
mymatrix = np.array([[1,2,3],[4,5,6]])
a = np.array([[1,2],[3,4],[5,6]])
print(mymatrix.shape[1] == a.shape[0])
print(mymatrix.dot(a))
其输出结果如下:
[[22 28]
[49 64]]
上述示例代码的原理是将mymatrix的第一行[1,2,3]与a矩阵的第一列[1,3,5]相乘然后相加,接着将mymatrix的第一行[1,2,3]与a矩阵的第二列[2,4,6]相乘然后相加,以此类推。
(2)矩阵的转置
矩阵的转置是指将原来矩阵中的行变为列。示例代码如下:
import numpy as np
a = np.array([[1,2,3],[4,5,6]])
print(a.T)
输出结果如下:
[[1 4]
[2 5]
[3 6]]
(3)矩阵的逆
需要首先导入numpy.linalg,再用linalg的inv函数来求逆,矩阵求逆的条件是矩阵的行数和列数必须是相同的。示例代码如下:
import numpy as np
import numpy.linalg as lg
A = np.array([[0,1],[2,3]])
invA = lg.inv(A)
print(invA)
print(A.dot(invA))
输出结果如下:
[[-1.5 0.5]
[ 1. 0. ]]
逆矩阵就是,原矩阵A.dot(invA)以及逆矩阵invA.dot(A)的结果都为单位矩阵。并不是所有的矩阵都有逆矩阵。
2.3.7 数据类型转换
Numpy ndarray数据类型可以通过参数dtype进行设定,而且还可以使用参数astype来转换类型,在处理文件时该参数会很实用。注意,astype调用会返回一个新的数组,也就是原始数据的备份。
比如,将String转换成float。示例代码如下:
vector = numpy.array(["1", "2", "3"])
vector = vector.astype(float)
注意:在上述例子中,如果字符串中包含非数字类型,那么从string转换成float就会报错。
2.3.8 Numpy的统计计算方法
NumPy内置了很多计算方法,其中最重要的统计方法及说明具体如下。
- sum():计算矩阵元素的和;矩阵的计算结果为一个一维数组,需要指定行或者列。
- mean():计算矩阵元素的平均值;矩阵的计算结果为一个一维数组,需要指定行或者列。
- max():计算矩阵元素的最大值;矩阵的计算结果为一个一维数组,需要指定行或者列。
- mean():计算矩阵元素的平均值。
- median():计算矩阵元素的中位数。
需要注意的是,用于这些统计方法的数值类型必须是int或者float。
数组示例代码如下:
vector = numpy.array([5, 10, 15, 20])
vector.sum()
得到的结果是50
矩阵示例代码如下:
matrix=
array([[ 5, 10, 15],
[20, 10, 30],
[35, 40, 45]])
matrix.sum(axis=1)
array([ 30, 60, 120])
matrix.sum(axis=0)
array([60, 60, 90])
如上述例子所示,axis = 1计算的是行的和,结果以列的形式展示。axis = 0计算的是列的和,结果以行的形式展示。
延伸学习:
官方推荐教程(https://docs.scipy.org/doc/numpy-dev/user/quickstart.html )是不错的入门选择。
2.3.9 Numpy中的arg运算
argmax函数就是用来求一个array中最大值的下标。简单来说,就是最大的数所对应的索引(位置)是多少。示例代码如下:
index2 = np.argmax([1,2,6,3,2]) #返回的是2
argmin函数可用于求一个array中最小值的下标,用法与argmax类似。示例代码如下:
index2 = np.argmin([1,2,6,3,2]) #返回的是0
下面我们来探索下Numpy矩阵的排序和如何使用索引,示例代码如下:
import numpy as np
x = np.arange(15)
print(x) # array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14])
np.random.shuffle(x) #随机打乱
print(x) # array([ 8, 13, 12, 3, 9, 2, 10, 0, 11, 5, 14, 7, 1, 4, 6])
sx = np.argsort(x) #从小到大排序,返回索引值
print(sx) # [ 7 12 5 3 13 9 14 11 0 4 6 8 2 1 10]
这里简单解释一下,第一个元素7代表的是x向量中的0的索引地址,第二个元素12代表的是x向量中的1的索引地址,其他元素以此类推。
2.3.10 FancyIndexing
要索引向量中的一个值是比较容易的,比如通过x[0]来取值。但是,如果想要更复杂地取数,比如,需要返回第3个、第5个以及第8个元素时,应该怎么办?示例代码如下:
import numpy as np
x = np.arange(15)
ind = [3,5,8]
print(x[ind]) #使用fancyindexing就可以解决这个问题
我们也可以从一维向量中构成新的二维矩阵,示例代码如下:
import numpy as np
x = np.arange(15)
np.random.shuffle(x)
ind = np.array([[0,2],[1,3]]) #第一行需要取x向量中索引为0的元素,以及索引为2的元素,
第二行需要取x向量中索引为1的元素以及索引为3的元素
print(x)
print(x[ind])
我们来看下输出结果很容易就能明白了:
[ 3 2 7 12 9 13 11 14 10 5 4 1 6 8 0]
[[ 3 7]
[ 2 12]]
对于二维矩阵,我们使用fancyindexing取数也是比较容易的,示例代码如下:
import numpy as np
x = np.arange(16)
X = x.reshape(4,-1)
row = np.array([0,1,2])
col = np.array([1,2,3])
print(X[row,col]) #相当于取三个点,分别是(0,1),(1,2),(2,3)
print(X[1:3,col]) #相当于取第2、3行,以及需要的列
2.3.11 Numpy数组比较
Numpy有一个强大的功能是数组或矩阵的比较,数据比较之后会产生boolean值。示例代码如下:
import numpy as np
matrix = np.array([
[5, 10, 15],
[20, 25, 30],
[35, 40, 45]
])
m = (matrix == 25)
print(m)
我们看到输出的结果如下:
[[False False False]
[False True False]
[False False False]]
下面再来看一个比较复杂的例子,示例代码如下:
import numpy as np
matrix = np.array([
[5, 10, 15],
[20, 25, 30],
[35, 40, 45]
])
second_column_25 = (matrix[:,1] == 25)
print(second_column_25)
print(matrix[second_column_25, :])
上述代码中,print(second_column_25)输出的是[False, True False],首先matrix[:,1]代表的是所有的行,以及索引为1的列,即[10,25,40],最后与25进行比较,得到的就是[False, True, False]。print(matrix[second_column_25, :])代表的是返回true值的那一行数据,即 [20, 25, 30]。
注意:上述的示例是单个条件,Numpy也允许我们使用条件符来拼接多个条件,其中“&”代表的是“且”,“|”代表的是“或”。比如,vector=np.array([5,10,11,12]), equal_to_five_and_ten = (vector == 5) & (vector == 10)返回的都是false,如果是equal_to_five_or_ten = (vector == 5) | (vector == 10),则返回的是[True,True,False,False]。
比较之后,我们就可以通过np.count_nonzero(x<=3)来计算小于等于3的元素个数了,1代表True,0代表False。也可以通过np.any(x == 0),只要x中有一个元素等于0就返回True。np.all(x>0)则需要所有的元素都大于0才返回True。这一点可以帮助我们判断x里的数据是否满足一定的条件。
2.4 本章小结
工欲善其事,必先利其器。本章主要讲述了让图像识别工作变得更高效的一些“利器”,如使用Anaconda快速构建开发环境,以及如何使用Numpy进行科学计算等。需要提醒读者的是,应重点关注Numpy,因为在一些具体任务上,在开始时通常都需要将图片存储于Numpy矩阵中以进行相应的计算,比如后续章节讲到的KNN算法,就需要通过Numpy的科学运算来计算每张测试样本图与训练图之间的距离。此外,由于篇幅限制,无法逐一对诸如Pandas、Matplotlib等常用的Python库进行介绍,希望读者自行查找相关资料。另外,还有一点值得注意的是,在入门图像识别之前,读者需有一定的Python基础。