1.4 开始
如果你已经安装了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 tordinary 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 ) |
constants | 物理和数学常量 |
fftpack | 离散傅里叶变换算法 |
integrate | 积分例程 |
interpolate | 插值(线性的,三次方的,等等) |
i | 数据输入和输出 |
linalg | 采用优化BLAS和LAPACK库的线性代数函数 |
maxentropy | 最大熵模型的函数 |
ndimage | n 维图像工具包 |
odr | 正交距离回归 |
optimize | 最优化(寻找极小值和方程的根) |
signal | 信号处理 |
sparse | 稀疏矩阵 |
spatial | 空间数据结构和算法 |
special | 特殊数学函数如贝塞尔函数(Bessel)或雅可比函数(Jacobian) |
stats | 统计学工具包 |
其中我们最感兴趣的是scipy.stats 、scipy.interpolate 、scipy.cluster 和scipy.signal 。为了简单起见,我们将会简要地探索stats 包的一些特性,而其余的则在它们各自出现的章中进行解释。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论