如果你已经安装了Python(2.7或更高版本),那么还需要安装NumPy和SciPy来处理数据,并需要安装Matplotlib对数据进行可视化。 1.4.1 NumPy、SciPy和Matplotlib简介 在讨论具体的机器学习算法之前,必须说一下如何最好地存储需要处理的数据。这很重要,因为多数高级学习算法,如果运行永远不会结束,对我们毫无用处。这可能仅仅是因为数据访问太慢了,也可能是因为这些数据的表示方式迫使操作系统一直做数据交换。再加上Python是一种解释性语言(尽管是高度优化过的),和C或者Fortran相比,这类语言对很多重数值算法来说运行缓慢。所以或许应该问一问究竟为什么有这么多科学家和公司,甚至在高度计算密集型领域内豪赌Python。 答案就是,在Python中很容易把数值计算任务交给下层的C或Fortran扩展包。这也正是NumPy和SciPy要做的事情(http://scipy.org/install.html)。在NumPy和SciPy这个组合中,NumPy提供了对高度优化的多维数组的支持,而这正是大多数新式算法的基本数据结构。SciPy则通过这些数组提供了一套快速的数值分析方法库。最后,用Python来绘制高品质图形,Matplotlib(http://matplotlib.org/)也许是使用最方便、功能最丰富的程序库了。 1.4.2 安装Python 幸运的是,所有主流操作系统,如Windows、Mac和Linux,都有针对NumPy、SciPy和Matplotlib的安装程序。如果你对安装过程不是很清楚,那么可能就需要安装Enthought Python发行版(https://www.enthought.com/products/epd_free.php)或者Python(x,y)(http://code.google.com/p/ pythonxy/wiki/Downloads),而这些已经包含在之前提到过的程序包里了。 1.4.3 使用NumPy和SciPy智能高效地处理数据 让我们快速浏览一下NumPy的基础示例,然后看看SciPy在NumPy之上提供了哪些东西。在这个过程中,我们将开始使用Matplotlib这个非凡的工具包进行绘图。 你可以在http://www.scipy.org/Tentative_NumPy_Tutorial上找到NumPy所提供的更多有趣示例。 你也会发现由Ivan Idris所著的《Python数据分析基础教程:NumPy学习指南(第2版)》非常有价值。你还可以在http://scipy-lectures.github.com上找到辅导性质的指南,并到http://docs. scipy.org/doc/scipy/reference/tutorial访问SciPy的官方教程。 在本书中,我们使用1.6.2版本的NumPy和0.11.0版本的SciPy。 1.4.4 学习NumPy 让我们引入NumPy,并小试一下。对此,需要打开Python交互界面。 >>> import numpy >>> numpy.version.full_version 1.6.2 由于我们并不想破坏命名空间,所以肯定不能做下面这样的事情: >>> from numpy import * 这个numpy.array数组很可能会遮挡住标准Python中包含的数组模块。相反,我们将会采用下面这种便捷方式: >>> import numpy as np >>> a = np.array([0,1,2,3,4,5]) >>> a array([0, 1, 2, 3, 4, 5]) >>> a.ndim 1 >>> a.shape (6,) 这里只是采用了与在Python中创建列表相类似的方法来创建数组。不过,NumPy数组还包含更多关于数组形状的信息。在这个例子中,它是一个含有5个元素的一维数组。到目前为止,并没有什么令人惊奇的。 现在我们将这个数组转换到一个2D矩阵中: >>> b = a.reshape((3,2)) >>> b array([[0, 1], [2, 3], [4, 5]]) >>> b.ndim 2 >>> b.shape (3, 2) 当我们意识到NumPy包优化到什么程度时,有趣的事情发生了。比如,它在所有可能之处都避免复制操作。 >>> b[1][0]=77 >>> b array([[ 0, 1], [77, 3], [ 4, 5]]) >>> a array([ 0, 1, 77, 3, 4, 5]) 在这个例子中,我们把b的值从2改成77,然后立刻就会发现相同的改动已经反映在a中。当你需要一个真正的副本时,请记住这个。 >>> c = a.reshape((3,2)).copy() >>> c array([[ 0, 1], [77, 3], [ 4, 5]]) >>> c[0][0] = -99 >>> a array([ 0, 1, 77, 3, 4, 5]) >>> c array([[-99, 1], [ 77, 3], [ 4, 5]]) 这里,c和a是完全独立的副本。 NumPy数组还有一大优势,即对数组的操作可以传递到每个元素上。 >>> a*2 array([ 2, 4, 6, 8, 10]) >>> a**2 array([ 1, 4, 9, 16, 25]) Contrast that to ordinary Python lists: >>> [1,2,3,4,5]*2 [1, 2, 3, 4, 5, 1, 2, 3, 4, 5] >>> [1,2,3,4,5]**2 Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unsupported operand type(s) for ** or pow(): 'list' and 'int' 当然,我们在使用NumPy数组的时候会牺牲Python列表所提供的一些敏捷性。像相加、删除这样的简单操作在NumPy数组中会有一点麻烦。幸运的是,这两种方式都可以使用。我们可以根据手头上的任务来选择最适合的那种。 1. 索引 NumPy的部分威力来自于它的通用数组访问方式。 除了正常的列表索引方式,它还允许我们将数组本身当做索引使用。 >>> a[np.array([2,3,4])] array([77, 3, 4]) 除了判断条件可以传递到每个元素这个事实,我们得到了一个非常方便的数据访问方法。 >>> a>4 array([False, False, True, False, False, True], dtype=bool) >>> a[a>4] array([77, 5]) 这还可用于修剪异常值。 >>> a[a>4] = 4 >>> a array([0, 1, 4, 3, 4, 4]) 鉴于这是一个经常碰到的情况,所以这里有一个专门的修剪函数来处理它。如下面的函数调用所示,它将数组值超出某个区间边界的部分修剪掉。 >>> a.clip(0,4) array([0, 1, 4, 3, 4, 4]) 2. 处理不存在的值 当我们预处理刚从文本文件中读出的数据时,NumPy的索引能力就派上用场了。这些数据中很可能包含不合法的值,我们像下面这样用numpy.NAN做标记,来表示它不是真实数值。 c = np.array([1, 2, np.NAN, 3, 4]) # 假设已经从文本文件中读取了数据 >>> c array([ 1., 2., nan, 3., 4.]) >>> np.isnan(c) array([False, False, True, False, False], dtype=bool) >>> c[~np.isnan(c)] array([ 1., 2., 3., 4.]) >>> np.mean(c[~np.isnan(c)]) 2.5 3. 运行时行为比较 让我们比较一下NumPy和标准Python列表的运行时行为。在下面这些代码中,我们将会计算从1到1000的所有数的平方和,并观察这些计算花费了多少时间。为了使评估足够准确,我们重复做了10 000次,并记录下总时间。 import timeit normal_py_sec = timeit.timeit('sum(x*x for x in xrange(1000))', number=10000) naive_np_sec = timeit.timeit('sum(na*na)', setup="import numpy as np; na=np.arange(1000)", number=10000) good_np_sec = timeit.timeit('na.dot(na)', setup="import numpy as np; na=np.arange(1000)", number=10000) print("Normal Python: %f sec"%normal_py_sec) print("Naive NumPy: %f sec"%naive_np_sec) print("Good NumPy: %f sec"%good_np_sec) Normal Python: 1.157467 sec Naive NumPy: 4.061293 sec Good NumPy: 0.033419 sec 我们观察到两个有趣的现象。首先,仅用NumPy作为数据存储(原始NumPy)时,花费的时间竟然是标准Python列表的3.5倍。这让我们感到非常惊奇,因为我们原本以为既然它是C扩展,那肯定要快得多。对此,一个解释是,在Python中访问个体数组元素是相当耗时的。只有当我们在优化后的扩展代码中使用一些算法之后,才能获得速度上的提升。一个巨大的提升是:当使用NumPy的dot()函数之后,可以得到25倍的加速。总而言之,在要实现的算法中,应该时常考虑如何将数组元素的循环处理从Python中移到一些高度优化的NumPy或SciPy扩展函数中。 然而,速度也是有代价的。当使用NumPy数组时,我们不再拥有像Python列表那样基本上可以装下任何数据的不可思议的灵活性。NumPy数组中只有一个数据类型。 >>> a = np.array([1,2,3]) >>> a.dtype dtype('int64') 如果尝试使用不同类型的元素,NumPy会尽量把它们强制转换为最合理的常用数据类型: >>> np.array([1, "stringy"]) array(['1', 'stringy'], dtype='|S8') >>> np.array([1, "stringy", set([1,2,3])]) array([1, stringy, set([1, 2, 3])], dtype=object) 1.4.5 学习SciPy 在NumPy的高效数据结构之上,SciPy提供了基于这些数组的算法级应用。本书中任何一个数值分析方面的重数值算法,你都可以在SciPy中找到相应的支持。无论是矩阵运算、线性代数、最优化方法、聚类、空间运算,还是快速傅里叶变换,都囊括在这个工具包中了。因此在实现数值算法之前先查看一下SciPy模块,是一个好习惯。 为了方便起见,NumPy的全部命名空间都可以通过SciPy访问。因此从现在开始,我们会在SciPy的命名空间中使用NumPy的函数。通过比较这两个基础函数的引用,很容易就可以进行验证,例如: >>> import scipy, numpy >>> scipy.version.full_version 0.11.0 >>> scipy.dot is numpy.dot True 各种各样的算法被分组到下面这个工具包中: SciPy工具包 功 能 cluster 层次聚类(cluster.hierarchy) 矢量量化 / K均值(cluster.vq) constants 物理和数学常量 转换方法 fftpack 离散傅里叶变换算法 integrate 积分例程 interpolate 插值(线性的、三次方的,等等) io 数据输入和输出 linalg 采用优化BLAS和LAPACK库的线性代数函数 maxentropy 最大熵模型的函数 ndimage n维图像工具包 odr 正交距离回归 optimize 最优化(寻找极小值和方程的根) signal 信号处理 (续) SciPy工具包 功 能 sparse 稀疏矩阵 spatial 空间数据结构和算法 special 特殊数学函数如贝塞尔函数(Bessel)或雅可比函数(Jacobian) stats 统计学工具包 其中我们最感兴趣的是scipy.stats、scipy.interpolate、scipy.cluster和scipy. signal。为了简单起见,我们将会简要地探索stats包的一些特性,而其余的则在它们各自出现的章中进行解释。
机器学习系统设计——1.4 开始
书名: 机器学习系统设计
作者:
出版社: 人民邮电出版社
原作名: Building Machine Learning Systems with Python
译者: 刘峰 | Luis Pedro Coelho
出版年: 2014-7-1
页数: 210
定价: CNY 49.00
装帧: 平装
ISBN: 9787115356826