在机器学习领域,Python语言可以大展身手,因为Python的设计哲学是“优雅、明确、简单”。Python开发者的哲学是“用一种方法,最好是只有一种方法来做一件事”。在设计Python语言时,如果面临多种选择,Python开发者一般会拒绝花俏的语法,而选择明确的没有或者很少有歧义的语法。由于这种设计观念的影响,Python源代码具备更好的可读性,并且能够支撑大规模的软件开发。几乎在任何涉及软件开发的领域都可以看到Python的身影,在机器学习领域它更是威名远扬,大量的优秀机器学习库都是基于Python开发或者提供Python接口的。所以本章重点介绍Python语言在机器学习领域的优势和应用,包括几个重点库:NumPy、SciPy、NTLK、Scikit-Learn的简介、环境依赖以及安装,最后介绍TensorFlow的简介及安装。为后续的学习准备好工具箱。

2.1 Python在机器学习领域的优势

Python在机器学习领域应用广泛(如图2-1所示),我认为主要原因有两个:

● 语法简单,功能强大;

● 生态完整,具备丰富的第三方库,对应的机器学习库非常丰富。

图2-1 主流基于Python的机器学习库

下面将重点介绍四个库。

2.1.1 NumPy

NumPy是Python的一种开源的数值计算扩展。这种工具可用来存储和处理大型矩阵,比Python自身的嵌套列表结构要高效的多。

NumPy包括:

● 一个强大的N维数组对象Array;

● 比较成熟的(广播)函数库;

● 用于整合C/C++和Fortran代码的工具包;

● 实用的线性代数、傅里叶变换和随机数生成函数。

NumPy提供了许多高级的数值编程工具,如:矩阵数据类型、矢量处理,以及精密的运算库,专为进行严格的数字处理。

1.安装方法

    pip install --user numpy

2.用法示例

首先需要创建数组才能对其进行其他操作。

可以通过给array函数传递Python的序列对象创建数组,如果传递的是多层嵌套的序列,将创建多维数组(下例中的变量c):

    >>> a = np.array([1, 2, 3, 4])
    >>> b = np.array((5, 6, 7, 8))
    >>> c = np.array([[1, 2, 3, 4], [4, 5, 6, 7], [7, 8, 9, 10]])
    >>> b
    array([5, 6, 7, 8])
    >>> c
    array([[1, 2, 3, 4],          [4, 5, 6, 7],          [7, 8, 9, 10]])
    >>> c.dtype
    dtype('int32')

数组的大小可以通过其shape属性获得:

    >>> a.shape
    (4, )
    >>> c.shape
    (3, 4)

数组元素的存取方法和Python的标准方法相同:

    >>> a = np.arange(10)
    >>> a[5]     # 用整数作为下标可以获取数组中的某个元素
    5
    >>> a[3∶5]  # 用范围作为下标获取数组的一个切片,包括a[3]不包括a[5]
    array([3, 4])
    >>> a[∶5]   # 省略开始下标,表示从a[0]开始
    array([0, 1, 2, 3, 4])
    >>> a[∶-1]  # 下标可以使用负数,表示从数组后往前数
    array([0, 1, 2, 3, 4, 5, 6, 7, 8])
    >>> a[2∶4] = 100,101     # 下标还可以用来修改元素的值
    >>> a
    array([   0,    1, 100, 101,    4,    5,    6,    7,    8,    9])
    >>> a[1∶-1∶2]   # 范围中的第三个参数表示步长,2表示隔一个元素取一个元素
    array([   1, 101,    5,    7])
    >>> a[∶∶-1] # 省略范围的开始下标和结束下标,步长为-1,整个数组头尾颠倒
    array([   9,    8,    7,    6,    5,    4, 101, 100,    1,    0])
    >>> a[5∶1∶-2] # 步长为负数时,开始下标必须大于结束下标
    array([   5, 101])

和Python的列表序列不同,通过下标范围获取的新的数组是原始数组的一个视图。它与原始数组共享同一块数据空间:

    >>> b = a[3∶7] # 通过下标范围产生一个新的数组b, b和a共享同一块数据空间
    >>> b
    array([101,    4,    5,    6])
    >>> b[2] = -10 # 将b的第2个元素修改为-10
    >>> b
    array([101,    4, -10,    6])
    >>> a # a的第5个元素也被修改为10
    array([   0,    1, 100, 101,    4, -10,    6,    7,    8,    9])

除了使用下标范围存取元素之外,NumPy还提供了两种存取元素的高级方法。

NumPy和MatLab不一样,对于多维数组的运算,缺省情况下并不使用矩阵运算,如果你希望对数组进行矩阵运算的话,可以调用相应的函数。

NumPy库提供了matrix类,使用matrix类创建的是矩阵对象,它们的加减乘除运算缺省采用矩阵方式计算,因此用法和MatLab十分类似。但是由于NumPy中同时存在ndarray和matrix对象,用户很容易将两者弄混。这有违Python的“显式优于隐式”的原则,因此并不推荐在较复杂的程序中使用matrix。下面是使用matrix的一个例子:

    >>> a = np.matrix([[1,2,3], [5,5,6], [7,9,9]])
    >>> a*a**-1
    matrix([[   1.00000000e+00,    1.66533454e-16,   -8.32667268e-17],
[ -2.77555756e-16,    1.00000000e+00,   -2.77555756e-17],
[   1.66533454e-16,    5.55111512e-17,    1.00000000e+00]])

因为a是用matrix创建的矩阵对象,因此乘法和幂运算符都变成了矩阵运算,于是上面计算的是矩阵a和其逆矩阵的乘积,结果是一个单位矩阵。

矩阵的乘积可以使用dot函数进行计算。对于二维数组,它计算的是矩阵乘积,对于一维数组,它计算的是点积。当需要将一维数组当作列矢量或者行矢量进行矩阵运算时,推荐先使用reshape函数将一维数组转换为二维数组:

    >>> a = array([1, 2, 3])
    >>> a.reshape((-1,1))
    array([[1],          [2],          [3]])
    >>> a.reshape((1, -1))
    array([[1, 2, 3]])

除了dot计算乘积之外,NumPy还提供了inner和outer等多种计算乘积的函数。这些函数计算乘积的方式不同,尤其是当处理多维数组的时候,更容易搞混。下面分别介绍这几个函数。

● dot:对于两个一维的数组,计算的是这两个数组对应下标元素的乘积和(数学上称之为“内积”);对于二维数组,计算的是两个数组的矩阵乘积;对于多维数组,它的通用计算公式如下,即结果数组中的每个元素都是——数组a的最后一维上的所有元素与数组b的倒数第二位上的所有元素的乘积和。

    dot(a, b)[i, j, k, m] = sum(a[i, j, ∶] * b[k, ∶, m])

下面以两个三维数组的乘积演示一下dot乘积的计算结果。

首先创建两个三维数组,这两个数组的最后两维满足矩阵乘积的条件:

    >>> a = np.arange(12).reshape(2,3,2)
    >>> b = np.arange(12,24).reshape(2,2,3)
    >>> c = np.dot(a, b)

dot乘积的结果c可以看作是数组a, b的多个子矩阵的乘积:

    >>> np.alltrue( c[0, ∶,0, ∶] == np.dot(a[0], b[0]) )
    True
    >>> np.alltrue( c[1, ∶,0, ∶] == np.dot(a[1], b[0]) )
    True
    >>> np.alltrue( c[0, ∶,1, ∶] == np.dot(a[0], b[1]) )
    True
    >>> np.alltrue( c[1, ∶,1, ∶] == np.dot(a[1], b[1]) )
    True

● inner:和dot乘积一样,对于两个一维数组,计算的是这两个数组对应下标元素的乘积和;对于多维数组,它计算的结果数组中的每个元素都是——数组a和b的最后一维的内积,因此数组a和b的最后一维的长度必须相同。

    inner(a, b)[i, j, k, m] = sum(a[i, j, ∶]*b[k, m, ∶])

下面是inner乘积的演示:

    >>> a = np.arange(12).reshape(2,3,2)
    >>> b = np.arange(12,24).reshape(2,3,2)
    >>> c = np.inner(a, b)
    >>> c.shape (2, 3, 2, 3)
    >>> c[0,0,0,0] == np.inner(a[0,0], b[0,0])
    True
    >>> c[0,1,1,0] == np.inner(a[0,1], b[1,0])
    True
    >>> c[1,2,1,2] == np.inner(a[1,2], b[1,2])
    True

● outer:只按照一维数组进行计算,如果传入参数是多维数组,则先将此数组展平为一维数组,之后再进行运算。outer乘积计算的列向量和行向量的矩阵乘积:

    >>> np.outer([1,2,3], [4,5,6,7])
    array([[ 4,   5,   6,   7],          [ 8, 10, 12, 14],          [12, 15, 18, 21]])

矩阵中更高级的一些运算可以在NumPy的线性代数子库linalg中找到。例如inv函数计算逆矩阵,solve函数可以求解多元一次方程组。下面是solve函数的一个例子:

    >>> a = np.random.rand(10,10)
    >>> b = np.random.rand(10)
    >>> x = np.linalg.solve(a, b)
    >>> np.sum(np.abs(np.dot(a, x) - b))
    3.1433189384699745e-15

solve函数有两个参数a和b。a是一个N×N的二维数组,而b是一个长度为N的一维数组,solve函数找到一个长度为N的一维数组x,使得a和x的矩阵乘积正好等于b,数组x就是多元一次方程组的解。

2.1.2 SciPy

SciPy是一款方便、易于使用、专为科学和工程设计的Python工具包,如图2-2所示。它包括统计、优化、整合、线性代数模块、傅里叶变换、信号和图像处理、常微分方程求解器等等。

图2-2 SciPy主页

安装方法:

    pip install --user numpy scipy matplotlib iPython jupyter pandas sympy nose

2.1.3 NLTK

NLTK在NLP领域中是最常使用的一个Python库,包括图形演示和示例数据,其提供的教程解释了工具包支持的语言处理任务背后的基本概念。

安装程序如下:

    pip install -U nltk

加载数据如下:

    >>> import nltk
    >>> nltk.download()

用法示例如下。

分词与标识:

    >>> import nltk
    >>> sentence = """At eight o'clock on Thursday morning
    ... Arthur didn't feel very good."""
    >>> tokens = nltk.word_tokenize(sentence)
    >>> tokens
    ['At',  'eight',  "o'clock",  'on',  'Thursday',  'morning',  'Arthur',  'did',  "n't",
'feel', 'very', 'good', '.']
    >>> tagged = nltk.pos_tag(tokens)
    >>> tagged[0∶6]
    [('At',  'IN'),  ('eight',  'CD'),  ("o'clock",  'JJ'),  ('on',  'IN'),  ('Thursday',
'NNP'), ('morning', 'NN')]

标识名词实体:

    >>> entities = nltk.chunk.ne_chunk(tagged)
    >>> entities
    Tree('S', [('At', 'IN'), ('eight', 'CD'), ("o'clock", 'JJ'),                  ('on',
'IN'), ('Thursday', 'NNP'), ('morning', 'NN'),
        Tree('PERSON', [('Arthur', 'NNP')]),
            ('did', 'VBD'), ("n't", 'RB'), ('feel', 'VB'),
            ('very', 'RB'), ('good', 'JJ'), ('.', '.')])

展现语法树(如图2-3):

图2-3 展现语法树

    >>> from nltk.corpus import treebank
    >>> t = treebank.parsed_sents('wsj_0001.mrg')[0]
    >>> t.draw()

2.1.4 Scikit-Learn

Scikit-Learn是基于Python的机器学习模块,基于BSD开源许可证。这个项目最早于2007年发起,目前也是由社区自愿者进行维护的。Scikit-Learn官方网站上可以找到相关的Scikit-Learn的资源、模块下载、文档、例程等等。Scikit-Learn的基本功能主要分为6个部分:分类,回归,聚类,数据降维,模型选择,数据预处理。具体可以参考官方网站上的文档,见图2-4。

图2-4 Scikit-Learn主页

依赖的环境:

● Python (>= 2.6 or >= 3.3)

● NumPy (>= 1.6.1)

● SciPy (>= 0.9)

安装方法:

    pip install -U scikit-learn