购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

第1章
NumPy基础

为何第1章介绍NumPy基础?在机器学习和深度学习中,图像、声音、文本等首先要实现数字化。那么如何实现数字化?数字化后如何处理?这些都涉及NumPy。NumPy是数据科学的通用语言,而且与TensorFlow关系非常密切,是科学计算、深度学习的基石。TensorFlow中的重要概念—张量(Tensor)与NumPy非常相似,二者可以非常方便地进行转换。掌握NumPy是学好TensorFlow的重要基础,故我们把它列为全书第1章。

基于NumPy的运算有哪些优势?实际上Python本身包含列表(list)和数组(array)结构,但对于大数据来说,这些结构有很多不足。因为列表的元素可以是任何对象,因此列表中保存的是对象的指针。例如为了保存一个简单的[1, 2, 3]列表,我们需要有3个指针和3个整数对象。对于数值运算来说,这种结构显然比较浪费内存和CPU等宝贵资源。至于数组对象,它直接保存数值,与C语言的一维数组比较类似,但是它不支持多维,定义的内置函数也不多,因此也不适合做数值运算。

NumPy(Numerical Python的简称)的诞生弥补了这些不足,它提供了两种基本的对象:ndarray(n-dimensional array object,多维数组对象)和ufunc(universal function object,全局函数对象)。ndarray是存储单一数据类型的多维数组,而ufunc则为数组处理提供了丰富的函数。

NumPy的主要特点总结如下。

1)提供ndarray这种既快速又节省空间的多维数组,提供数组化的算术运算和高级的广播功能。

2)使用标准数学函数对整个数组的数据进行快速运算,而不需要通过编写循环语句实现。

3)可作为读取/写入磁盘上的阵列数据和操作存储器映像文件的工具。

4)拥有线性代数、随机数生成和傅里叶变换的能力。

5)提供集成C、C++、Fortran代码的工具。

本章主要内容如下:

❑把图像数字化

❑存取元素

❑NumPy的算术运算

❑数据变形

❑通用函数

❑广播机制

❑用NumPy实现回归实例

1.1 把图像数字化

NumPy是Python的第三方库,若要使用它,需要先导入。

导入NumPy后,可通过输入np.+Tab键查看可使用的函数。如果你对其中一些函数的使用方法不是很清楚,还可以通过运行“对应函数+?”命令的方式,看到使用函数的帮助信息。

例如,输入np.,然后按Tab键,将出现如图1-1所示界面。

运行如下命令,可查看函数abs的详细帮助信息。

NumPy不但强大,而且非常友好。接下来我将介绍NumPy的一些常用方法,尤其是与机器学习、深度学习相关的一些内容。

图1-1 输入np.并按Tab键

前面提到,NumPy封装了一个新的多维数组对象ndarray。该对象封装了许多常用的数学运算函数,以便我们做数据处理、数据分析等。如何生成ndarray呢?这里介绍几种生成ndarray的方式,如从已有数据中生成,利用random模块生成,使用arange、Linspace函数生成等。

机器学习中的图像、自然语言、语音等在输入模型之前都需要数字化。这里我们用cv2把一个汽车图像转换为NumPy多维数组,然后查看该多维数组的基本属性,具体代码如下:

运行结果如下,效果如图1-2所示。

图1-2 把汽车图像转换为NumPy数组

1.1.1 数组属性

在NumPy中,维度被称为轴,比如我们把汽车图像转换为一个NumPy 3维数组,这个数组中有3个轴,长度分别为675、1200、3。

NumPy的ndarray对象有3个重要的属性,分析如下。

❑ndarray.ndim:数组的维度(轴的个数)。

❑ndarray.shape:数组的维度,它的值是一个整数元祖。元祖的值代表其所对应的轴的长度。比如对于2维数组,它用来表达这是几行几列的矩阵,值为( x , y ),其中 x 代表这个数组有几行, y 代表有几列。

❑ndarray.dtype:描述数组中元素的类型。

比如上面提到的img数组:

为更好地理解ndarray对象的3个重要属性,我们对1维数组、2维数组、3维数组进行可视化,如图1-3所示。

1.1.2 从已有数据中生成数组

直接对Python的基础数据类型(如列表、元组等)进行转换来生成数组。

1)将列表转换成数组。

图1-3 多维数组的可视化表示

2)将嵌套列表转换成多维数组。

如果把上面示例中的列表换成元组,上述的转换方法也同样适用。

1.1.3 利用random模块生成数组

在深度学习中,我们经常需要对一些参数进行初始化。为了更有效地训练模型,提高模型的性能,有些初始化还需要满足一定条件,如满足正态分布或均匀分布等。这里我们先来看看np.random模块常用函数,如表1-1所示。

表1-1 np.random模块常用函数

下面我们来看看一些函数的具体使用方法:

运行结果如下:

用以上方法生成的随机数是无法重现的,比如调用两次np.random.randn(3, 3),输出同样结果的概率极低。如果我们想要多次生成同一份数据,应该怎么办呢?可以使用np.random.seed函数设置种子。设置一个种子,然后调用随机函数产生一个数组,如果想要再次得到一个一模一样的数组,只要再次设置同样的种子就可以。

运行结果如下:

1.1.4 利用arange、linspace函数生成数组

有时,我们希望用到具有特定规律的一组数据,这时可以使用NumPy提供的arange、linspace函数来生成数组。

arange是numpy模块中的函数,其格式为:

其中start与stop用于指定范围,step用于设定步长,生成1个ndarray。start默认为0,step可为小数。Python的内置函数range的功能与此类似。

linspace也是numpy模块中常用的函数,其格式为:

它可以根据输入的指定数据范围以及等份数量,自动生成一个线性等分向量,其中endpoint(包含终点)默认为True,等分数量num默认为50。如果将retstep设置为True,则会返回一个带步长的数组。

值得一提的是,这里并没有像我们预期的那样生成0.1, 0.2, …, 1.0这样步长为0.1的数组,这是因为linspace必定会包含数据起点和终点,那么其步长为(1-0)/9=0.11111111。如果需要产生0.1, 0.2,…, 1.0这样的数据,只需要将数据起点0修改为0.1即可。

除了上面介绍的arange和linspace函数,NumPy还提供了logspace函数,该函数的使用方法与linspace的使用方法一样,读者不妨自己动手试一下。

1.2 存取元素

上节我们介绍了生成数组的几种方法。在数组生成后,应该如何读取所需的数据呢?本节就来介绍几种常用的方法。

如果你对上面这些获取方式还不是很清楚,没关系,下面我们通过图形的方式进一步说明,如图1-4所示,左边为表达式,右边为表达式获取的元素。注意,不同的边界表示不同的表达式。

图1-4 获取多维数组中的元素

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

运行结果如下:

1.3 NumPy的算术运算

机器学习和深度学习中涉及大量的数组或矩阵运算,本节我们将重点介绍两种常用的运算。一种是对应元素相乘,又称为逐元乘法,运算符为np.multiply()或*。另一种是点积或内积运算,运算符为np.dot()。

1.3.1 对应元素相乘

对应元素相乘是计算两个矩阵中对应元素的乘积。np.multiply函数用于数组或矩阵对应元素相乘,输出与相乘数组或矩阵的大小一致,格式如下:

其中x1、x2之间的对应元素相乘遵守广播规则,NumPy的广播规则将在1.6节介绍。下面我们通过一些示例来进一步说明。

矩阵 A B 的对应元素相乘,可以直观地用图1-5表示。

NumPy数组不仅可以与数组进行对应元素相乘,还可以与单一数值(或称为标量)进行运算。运算时,NumPy数组的每个元素与标量进行运算,其间会用到广播机制。例如:

图1-5 对应元素相乘示意图

运行结果如下:

由此可见,数组通过一些激活函数的运算后,输出与输入形状一致。

运行结果如下:

1.3.2 点积运算

点积运算又称为内积,在NumPy中用np.dot表示,其一般格式为:

以下通过一个示例来说明点积运算的具体使用方法及注意事项。

运行结果如下:

以上运算可以用图1-6表示。

如图1-6所示,矩阵X1与矩阵X2进行点积运算,其中X1和X2对应维度(即X1的第2个维度与X2的第1个维度)的元素个数必须保持一致。此外,矩阵X3的形状是由矩阵X1的行数与矩阵X2的列数确定的。

图1-6 矩阵的点积运算示意图,对应维度的元素个数需要保持一致

点积运算在神经网络中的使用非常频繁,如图1-7所示的神经网络,其输入 I 与权重矩阵 W 之间的运算就是点积运算。

图1-7 点积运算可视化示意图

1.4 数据变形

在机器学习以及深度学习的任务中,我们通常需要将处理好的数据以模型能接收的格式发送给模型,然后由模型通过一系列运算,最终返回一个处理结果。然而,由于不同模型所接收的输入格式不一样,我们往往需要先对其进行一系列变形和运算,从而将数据处理成符合模型要求的格式。最常见的是矩阵或者数组的运算,如我们经常会需要把多个向量或矩阵按某轴方向合并或展平(如在卷积或循环神经网络中,在全连接层之前,我们需要把矩阵展平)。下面介绍几种常用的数据变形方法。

1.4.1 更改数组的形状

修改指定数组的形状是NumPy中最常见的操作之一,表1-2列出了NumPy中改变向量形状的一些常用函数。

表1-2 NumPy中改变向量形状的一些常用函数

下面我们来看一些示例。

1. reshape

使用reshape函数修改向量维度。

输出结果如下:

值得注意的是,reshape函数支持只指定行数(或列数),其他值设置为-1即可。不过所指定的行数或列数一定要能被整除,例如,将上面的代码修改为arr.reshape(3,-1)时将报错(10不能被3整除)。

2. resize

使用resize函数修改向量维度。

输出结果如下:

3. .T

使用.T函数对向量进行转置。

输出结果如下:

4. ravel

ravel函数接收一个根据C语言格式(即按行优先排序)或者Fortran语言格式(即按列优先排序)来进行展平的参数,默认为按行优先排序。

输出结果如下:

5. flatten(order='C')

把矩阵转换为向量,展平方式默认是按行优先排序(即参数order='C'),这种需求经常出现在卷积神经网络与全连接层之间。

输出结果如下:

flatten函数经常用于神经网络中,一般我们在把2维、3维等数组转换为1维数组时会用到flatten,如图1-8所示。

6. squeeze

这是一个主要用来降维的函数,可以把矩阵中含1的维度去掉。

图1-8 含flatten运算的神经网络示意图

7. transpose

对高维矩阵进行轴对换,多用于深度学习中,比如把表示图像颜色的RGB顺序改为GBR。

1.4.2 合并数组

合并数组也是最常见的操作之一,表1-3列举了几种常用的NumPy数组合并方法。

表1-3 NumPy数组合并方法

说明

1)append、concatenate以及stack函数都有一个axis参数,用于控制数组合并是按行还是按列优先排序。

2)对于append和concatenate函数,待合并的数组必须有相同的行数或列数(满足一个即可)。

3)对于stack、hstack、dstack函数,待合并的数组必须具有相同的形状(shape)。

下面从表1-3中选择一些常用函数进行说明。

1. append

合并一维数组:

合并多维数组:

输出结果如下:

2. concatenate

沿指定轴连接数组或矩阵:

输出结果如下:

3. stack

沿指定轴堆叠数组或矩阵:

输出结果如下:

4. zip

zip是Python的一个内置函数,多用于张量运算中。

运行结果如下:

再来看一个示例:

运行结果如下:

1.5 通用函数

前文提到,NumPy提供了两种基本的对象,即ndarray和ufunc对象。前面我们介绍了ndarray,本节将介绍ufunc。ufunc是一种能对数组的每个元素进行操作的函数,许多ufunc函数都是用C语言实现的,因此计算速度非常快。此外,它们比math模块中的函数更灵活。math模块中函数的输入一般是标量,但NumPy中函数的输入可以是向量或矩阵,而利用向量或矩阵可以避免使用循环语句,这点在机器学习、深度学习中非常重要。表1-4为几个NumPy中常用的通用函数。

表1-4 几个NumPy中常用的通用函数

说明 sum函数中涉及一个有关轴的参数(即axis),该参数的具体含义可参考图1-9。

图1-9 可视化参数axis的具体含义

1)math与numpy中函数的性能比较:

打印结果如下:

由此可见,numpy.sin比math.sin快近10倍。

2)循环与向量运算的比较。充分使用Python的numpy库中的内置函数,实现计算的向量化,可大大提高运行速度。numpy库中的内置函数使用SIMD指令实现向量化,这要比使用循环的速度快得多。如果使用GPU,其性能将更强大,不过NumPy不支持GPU。

输出结果如下:

从运行结果上来看,使用for循环的运行时间大约是使用向量运算的400倍。因此,在深度学习算法中,我们一般使用向量化矩阵运算。

1.6 广播机制

NumPy的通用函数(ufunc)中要求输入的数组shape是一致的,当数组的shape不一致时,则会用到广播机制。不过,调整数组使得shape一样时需满足一定规则,否则将出错。广播机制中的这些规则可归结为以下四条。

1)让所有输入数组都向其中shape最长的数组看齐,shape中不足的部分都通过在前面加1补齐;如对于数组a(2×3×2)和数组b(3×2),则b向a看齐,在b的前面加1,变为1×3×2。

2)输出数组的shape是输入数组shape的各个轴上的最大值。

3)如果输入数组的某个轴和输出数组的对应轴的长度相同或者长度为1时,则可以调整,否则将出错。

4)当输入数组的某个轴的长度为1时,沿着此轴运算时都用(或复制)此轴上的第一组值。

广播机制在整个NumPy中用于决定如何处理形状迥异的数组,涉及的算术运算包括+、-、*、/。这些规则虽然很严谨,但不直观。下面我们结合图形与代码做进一步说明。

目的: A + B 。其中 A 为4×1矩阵, B 为一维向量(3,)。要相加,需要做如下处理。

1)根据规则1, B 需要向 A 看齐,把 B 变为(1, 3)。

2)根据规则2,输出的结果为各个轴上的最大值,即输出结果应该为(4, 3)矩阵。那么 A 如何由(4, 1)变为(4, 3)矩阵? B 如何由(1, 3)变为(4, 3)矩阵?

3)根据规则4,用此轴上的第一组值(主要区分是哪个轴)进行复制即可。(但在实际处理中不是真正复制,而是采用其他对象,如ogrid对象,进行网格处理,否则太耗内存。)如图1-10所示。

图1-10 NumPy广播机制示意图

具体实现如下:

运行结果如下:

1.7 用NumPy实现回归实例

前面我们介绍了NumPy的属性及各种操作,对如何使用NumPy数组有了一定认识。下面我们将分别用NumPy、TensorFlow实现同一个机器学习任务,比较它们之间的异同及优缺点,从而加深对TensorFlow的理解。

我们用最原始的NumPy实现有关回归的一个机器学习任务。这种方法的代码可能有点多,但每一步都是透明的,有利于理解每一步的工作原理。主要步骤分析如下。

首先,给出一个数组 x ,然后基于表达式 y =3 x 2 +2,加上一些噪声数据,到达另一组数据 y

然后,构建一个机器学习模型,学习表达式 y = wx 2 + b 的两个参数 w b 。数组 x y 的数据为训练数据。

最后,采用梯度下降法,通过多次迭代,学习后得出 w b 的值。

以下为具体实现步骤。

1)导入需要的库。

2)生成输入数据x及目标数据y。

设置随机数种子,生成同一份数据,以便用多种方法进行比较。

3)查看x、y的分布情况。

运行结果如图1-11所示。

图1-11 NumPy实现的源数据

4)初始化权重参数。

5)训练模型。

定义损失函数,假设批量大小为100:

对损失函数求导:

利用梯度下降法学习参数,学习率为lr。

用代码实现上面这些表达式:

6)可视化结果。

运行结果如下,效果如图1-12所示。

图1-12 可视化NumPy学习结果

从结果看来,学习效果还是比较理想的。

1.8 小结

本章主要介绍了NumPy的使用。机器学习、深度学习涉及很多向量与向量、向量与矩阵、矩阵与矩阵的运算,这些运算都离不开NumPy。NumPy为各种运算提供了各种高效方法,也为后面介绍TensorFlow中的张量运算奠定了基础。 zfYU7AABZBb5+Ooa9sePjyn/qPHu0K1mL2/mjg9bpKSQKt1AJSZX1Wcbdk6qtVZc

点击中间区域
呼出菜单
上一章
目录
下一章
×