返回介绍

1.4 开始

发布于 2024-01-30 22:34:09 字数 7863 浏览 0 评论 0 收藏 0

如果你已经安装了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 )
矢量量化 / K均值 (cluster.vq )

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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文