Python与机器学习

Published: by Creative Commons Licence

  • Tags:

Python入门

Python是啥

Python(英国发音:/ˈpaɪθən/ 美国发音:/ˈpaɪθɑːn/),是一种广泛使用的高级编程语言,属于通用型编程语言,由吉多·范罗苏姆创造,第一版发布于1991年。可以视之为一种改良(加入一些其他编程语言的优点,如面向对象)的LISP。作为一种解释型语言,Python的设计哲学强调代码的可读性和简洁的语法(尤其是使用空格缩进划分代码块,而非使用大括号或者关键词)。相比于C++或Java,Python让开发者能够用更少的代码表达想法。不管是小型还是大型程序,该语言都试图让程序的结构清晰明了。与SchemeRubyPerlTcl等动态类型编程语言一样,Python拥有动态类型系统垃圾回收功能,能够自动管理内存使用,并且支持多种编程范式,包括面向对象、命令式、函数式和过程式编程。其本身拥有一个巨大而广泛的标准库。[1]

安装

Python的安装非常简单,在官网找到对应你系统的版本,下载安装即可。

pip

pip是python的包管理工具。使用方法非常简单

$ pip install [package1 <package2> <package3> ...]

上面就是安装多个库的语法。

如果你处于内网无法访问外部网络,你可以借助代理。

$ pip install --proxy=<代理机IP>:<代理端口> [package1 <package2> <package3> ...]

使用交互解释器

在控制台输出python即可打开默认的python交互解释器(在此之前不要忘了把python所在位置加到到PATH环境变量中去)。

$ python
Python 3.7.0 (v3.7.0:1bf9cc5093, Jun 27 2018, 04:59:51) [MSC v.1914 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> "Hello, World!"
'Hello, World!'
>>> exit()

使用IPython

python交互解释器缺乏语法提示和高亮,用着应该是有点不爽的。如果你真这样认为,那你应该尝试一下IPython。IPython是python的替代品,支持更多的特性,其中就包括语法提示和高亮。

安装IPython非常简单,用之前提到的pip就可以了。

$ pip install ipython --proxy=localhost:1080
Collecting ipython
......
Installing collected packages: ipython
Successfully installed ipython-7.3.0

之后照例输入ipython,开始你的ipython之旅。

$ ipython
Python 3.7.0 (v3.7.0:1bf9cc5093, Jun 27 2018, 04:59:51) [MSC v.1914 64 bit (AMD64)]
Type 'copyright', 'credits' or 'license' for more information
IPython 7.3.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: s = "Hello world"

In [2]: s?
Type:        str
String form: Hello world
Length:      11
Docstring:
str(object='') -> str
str(bytes_or_buffer[, encoding[, errors]]) -> str

Create a new string object from the given object. If encoding or
errors is specified, then the object must expose a data buffer
that will be decoded using the given encoding and error handler.
Otherwise, returns the result of object.__str__() (if defined)
or repr(object).
encoding defaults to sys.getdefaultencoding().
errors defaults to 'strict'.

利用tab键还可以获得语法提示哦。

来个IDE,VSCode

显然一直用解释器还是很麻烦,写个函数,不小心一处写错,需要整个函数重写。如果是写在文件中的话,就不会有这些问题了。

你可以试试VSCode这款IDE,装上Python插件后,会带给你非常好的体验。这里不细说。

名为pandas的库

pandas是一个开源,基于BSD许可证的python库,提供了高性能且易于使用的数据结果和数据分析工具。

首先你需要用pip安装pandas。

$ pip install pandas

DataFrame和Series

这节大家可以直接参考pandas介绍

名为numpy的库

Numpy是python用于科学计算的基础库,它包含下列部分:

  • 一个强力的N维数组
  • 复杂的广播函数
  • 用于整合C/C++和Fortran代码的工具
  • 有用的线性函数,傅里叶变换,以及随机数

安装非常简单,使用pip即可。

$ pip install numpy

概念

Numpy的重要对象是多维齐次数组。它是一个元素表(通常元素指的是数值),元素拥有相同的类型,由整数定位。在Numpy中,维度被称为axes。

比如,三维空间的点坐标[1,2,1]有一个axes。这个axis包含三个元素,因此我们称的长度为3。下面这个数组有2个axes,第一个axis的长度为2,第二个axis的长度为3。

[[1, 0, 0],
 [0, 1, 2]]

NumPy的数组类称为ndarray,它也被称为array,但是numpy.array与标准python库类array.array不同,后者仅处理1维数组并仅提供非常少的功能。ndarray提供了更多的重要属性。

  • ndarray.ndim,数组的axes数目。
  • ndarray.shape,数组的维度,是一个整数数组,第i个下标记录的是数组第i+1维的长度。比如n*m的矩阵的shape为(n,m)。
  • ndarray.size,数组中的元素总数,这等价于shape所有元素的乘积。
  • ndarray.dtype,用于描述数组中存储的元素类型的对象。
  • ndarray.itemsize,数组中每个元素的大小(以字节为单位)
  • ndarray.data,保存数组中的实际元素。通常我们不会直接使用这个属性,我们通过使用索引访问实际元素。

一个例子:

>>> import numpy as np
>>> a = np.arange(15).reshape(3, 5)
>>> a
array([[ 0,  1,  2,  3,  4],
       [ 5,  6,  7,  8,  9],
       [10, 11, 12, 13, 14]])
>>> a.shape
(3, 5)
>>> a.ndim
2
>>> a.dtype.name
'int64'
>>> a.itemsize
8
>>> a.size
15
>>> type(a)
<type 'numpy.ndarray'>
>>> b = np.array([6, 7, 8])
>>> b
array([6, 7, 8])
>>> type(b)
<type 'numpy.ndarray'>

创建数组

这里有好几种创建数组的方式。

比如你可以通过一个常规Python列表或元组来创建数组。结果数组的类型由序列中元素的类型推导得到。

>>> import numpy as np
>>> a = np.array([2,3,4])
>>> a
array([2, 3, 4])
>>> a.dtype
dtype('int64')
>>> b = np.array([1.2, 3.5, 5.1])
>>> b.dtype
dtype('float64')

数组会自动将序列的序列转换为二维数组,将序列的序列的序列转换为三维数组。

>>> b = np.array([(1.5,2,3), (4,5,6)])
>>> b
array([[ 1.5,  2. ,  3. ],
       [ 4. ,  5. ,  6. ]])

数组的类型也可以在创建时显式指定。

>>> c = np.array( [ [1,2], [3,4] ], dtype=complex )
>>> c
array([[ 1.+0.j,  2.+0.j],
       [ 3.+0.j,  4.+0.j]])

通常,数组中的元素开始时是未知的,但是大小却是已知的。因此,NumPy提供了多种函数,用来创建固定大小的数组,这可以尽可能减少数组增长操作。

zeros函数创建一个由0填充数组,而函数ones创建一个以1填充数组,而函数empty创建一个由随机数填充的数组,这段数组中的值由内存初始值决定。默认情况下,创建的dtype是float64。

>>> np.zeros( (3,4) )
array([[ 0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.]])
>>> np.ones( (2,3,4), dtype=np.int16 )                # dtype can also be specified
array([[[ 1, 1, 1, 1],
        [ 1, 1, 1, 1],
        [ 1, 1, 1, 1]],
       [[ 1, 1, 1, 1],
        [ 1, 1, 1, 1],
        [ 1, 1, 1, 1]]], dtype=int16)
>>> np.empty( (2,3) )                                 # uninitialized, output may vary
array([[  3.73603959e-262,   6.02658058e-154,   6.55490914e-260],
       [  5.30498948e-313,   3.14673309e-307,   1.00000000e+000]])

要创建数值序列,NumPy提供了一个类似于range的函数,返回数组而非list。

>>> np.arange( 10, 30, 5 )
array([10, 15, 20, 25])
>>> np.arange( 0, 2, 0.3 )                 # it accepts float arguments
array([ 0. ,  0.3,  0.6,  0.9,  1.2,  1.5,  1.8])

当arange使用了浮点数参数,这时通常无法预测将得到的元素,由于有穷的浮点数精度。因此,最好使用linspace并提供实际步数,而非步长。

>>> from numpy import pi
>>> np.linspace( 0, 2, 9 )                 # 9 numbers from 0 to 2
array([ 0.  ,  0.25,  0.5 ,  0.75,  1.  ,  1.25,  1.5 ,  1.75,  2.  ])
>>> x = np.linspace( 0, 2*pi, 100 )        # useful to evaluate function at lots of points
>>> f = np.sin(x)

打印数组

当你打印一个数组,NumPy以相似的嵌套方法进行显示.

>>> a = np.arange(6)                         # 1d array
>>> print(a)
[0 1 2 3 4 5]
>>>
>>> b = np.arange(12).reshape(4,3)           # 2d array
>>> print(b)
[[ 0  1  2]
 [ 3  4  5]
 [ 6  7  8]
 [ 9 10 11]]
>>>
>>> c = np.arange(24).reshape(2,3,4)         # 3d array
>>> print(c)
[[[ 0  1  2  3]
  [ 4  5  6  7]
  [ 8  9 10 11]]
 [[12 13 14 15]
  [16 17 18 19]
  [20 21 22 23]]]

但是如果数组过大,NumPy会自动跳过数组的中间部分,并仅打印数组的角落。

>>> print(np.arange(10000))
[   0    1    2 ..., 9997 9998 9999]
>>>
>>> print(np.arange(10000).reshape(100,100))
[[   0    1    2 ...,   97   98   99]
 [ 100  101  102 ...,  197  198  199]
 [ 200  201  202 ...,  297  298  299]
 ...,
 [9700 9701 9702 ..., 9797 9798 9799]
 [9800 9801 9802 ..., 9897 9898 9899]
 [9900 9901 9902 ..., 9997 9998 9999]]

要让NumPy强制打印整个数组,你可以通过改变显示选项。

>>> np.set_printoptions(threshold=np.nan)

基础操作

数组上进行算术操作会逐元素操作,一个新的数组被创建并填充结果。

>>> a = np.array( [20,30,40,50] )
>>> b = np.arange( 4 )
>>> b
array([0, 1, 2, 3])
>>> c = a-b
>>> c
array([20, 29, 38, 47])
>>> b**2
array([0, 1, 4, 9])
>>> 10*np.sin(a)
array([ 9.12945251, -9.88031624,  7.4511316 , -2.62374854])
>>> a<35
array([ True, True, False, False])

不同于其他矩阵语言,乘法运算符*会对NumPy数组逐元素操作。要执行矩阵乘法,可以使用@操作符(python>=3.5)或者dot函数。

>>> A = np.array( [[1,1],
...             [0,1]] )
>>> B = np.array( [[2,0],
...             [3,4]] )
>>> A * B                       # elementwise product
array([[2, 0],
       [0, 4]])
>>> A @ B                       # matrix product
array([[5, 4],
       [3, 4]])
>>> A.dot(B)                    # another matrix product
array([[5, 4],
       [3, 4]])

诸如像+=和*=的运算符,运算的结果会存储在左边的操作数,而非创建一个新数组。

>>> a = np.ones((2,3), dtype=int)
>>> b = np.random.random((2,3))
>>> a *= 3
>>> a
array([[3, 3, 3],
       [3, 3, 3]])
>>> b += a
>>> b
array([[ 3.417022  ,  3.72032449,  3.00011437],
       [ 3.30233257,  3.14675589,  3.09233859]])
>>> a += b                  # b is not automatically converted to integer type
Traceback (most recent call last):
  ...
TypeError: Cannot cast ufunc add output from dtype('float64') to dtype('int64') with casting rule 'same_kind'

当在两个类型的数组之间执行运算,结果数组会转换为更宽泛的类型保存二者的运算结果。

>>> a = np.ones(3, dtype=np.int32)
>>> b = np.linspace(0,pi,3)
>>> b.dtype.name
'float64'
>>> c = a+b
>>> c
array([ 1.        ,  2.57079633,  4.14159265])
>>> c.dtype.name
'float64'
>>> d = np.exp(c*1j)
>>> d
array([ 0.54030231+0.84147098j, -0.84147098+0.54030231j,
       -0.54030231-0.84147098j])
>>> d.dtype.name
'complex128'

NumPy还提供了很多一元运算符,比如计算数组的总和等。

>>> a = np.random.random((2,3))
>>> a
array([[ 0.18626021,  0.34556073,  0.39676747],
       [ 0.53881673,  0.41919451,  0.6852195 ]])
>>> a.sum()
2.5718191614547998
>>> a.min()
0.1862602113776709
>>> a.max()
0.6852195003967595

通常,这些运算会将数组视作一个大的数值列表,而不管外形,通过指定axis参数,你可以要求NumPy为沿着某个特定的axis来执行运算。

>>> b = np.arange(12).reshape(3,4)
>>> b
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])
>>>
>>> b.sum(axis=0)                            # sum of each column
array([12, 15, 18, 21])
>>>
>>> b.min(axis=1)                            # min of each row
array([0, 4, 8])
>>>
>>> b.cumsum(axis=1)                         # cumulative sum along each row
array([[ 0,  1,  3,  6],
       [ 4,  9, 15, 22],
       [ 8, 17, 27, 38]])

NumPy还提供了类似的数学函数,比如sin,cos和exp。在NumPy中,这些函数称为全局函数。这些数学函数会逐元素计算,并将结果记录在另外的一个数组中。

>>> B = np.arange(3)
>>> B
array([0, 1, 2])
>>> np.exp(B)
array([ 1.        ,  2.71828183,  7.3890561 ])
>>> np.sqrt(B)
array([ 0.        ,  1.        ,  1.41421356])
>>> C = np.array([2., -1., 4.])
>>> np.add(B, C)
array([ 2.,  0.,  6.])

一维数组可以被索引,切片和遍历,和列表以及其它Python序列类似。

>>> a = np.arange(10)**3
>>> a
array([  0,   1,   8,  27,  64, 125, 216, 343, 512, 729])
>>> a[2]
8
>>> a[2:5]
array([ 8, 27, 64])
>>> a[:6:2] = -1000    # equivalent to a[0:6:2] = -1000; from start to position 6, exclusive, set every 2nd element to -1000
>>> a
array([-1000,     1, -1000,    27, -1000,   125,   216,   343,   512,   729])
>>> a[ : :-1]                                 # reversed a
array([  729,   512,   343,   216,   125, -1000,    27, -1000,     1, -1000])
>>> for i in a:
...     print(i**(1/3.))
...
nan
1.0
nan
3.0
nan
5.0
6.0
7.0
8.0
9.0

多维数组可以为每个axis分配一个下标。这些下表以元组的方式给出。

>>> def f(x,y):
...     return 10*x+y
...
>>> b = np.fromfunction(f,(5,4),dtype=int)
>>> b
array([[ 0,  1,  2,  3],
       [10, 11, 12, 13],
       [20, 21, 22, 23],
       [30, 31, 32, 33],
       [40, 41, 42, 43]])
>>> b[2,3]
23
>>> b[0:5, 1]                       # each row in the second column of b
array([ 1, 11, 21, 31, 41])
>>> b[ : ,1]                        # equivalent to the previous example
array([ 1, 11, 21, 31, 41])
>>> b[1:3, : ]                      # each column in the second and third row of b
array([[10, 11, 12, 13],
       [20, 21, 22, 23]])

改变形状

一个数组的形状为每个维度的元素数目。

>>> a = np.floor(10*np.random.random((3,4)))
>>> a
array([[ 2.,  8.,  0.,  6.],
       [ 4.,  5.,  1.,  1.],
       [ 8.,  9.,  3.,  6.]])
>>> a.shape
(3, 4)

可以通过多种命令改变数组的形状。

>>> a.ravel()  # returns the array, flattened
array([ 2.,  8.,  0.,  6.,  4.,  5.,  1.,  1.,  8.,  9.,  3.,  6.])
>>> a.reshape(6,2)  # returns the array with a modified shape
array([[ 2.,  8.],
       [ 0.,  6.],
       [ 4.,  5.],
       [ 1.,  1.],
       [ 8.,  9.],
       [ 3.,  6.]])
>>> a.T  # returns the array, transposed
array([[ 2.,  4.,  8.],
       [ 8.,  5.,  9.],
       [ 0.,  1.,  3.],
       [ 6.,  1.,  6.]])
>>> a.T.shape
(4, 3)
>>> a.shape
(3, 4)

reshape函数返回一个新的数组而不会改变原数组,而resize方法会改变数组本身。

>>> a
array([[ 2.,  8.,  0.,  6.],
       [ 4.,  5.,  1.,  1.],
       [ 8.,  9.,  3.,  6.]])
>>> a.resize((2,6))
>>> a
array([[ 2.,  8.,  0.,  6.,  4.,  5.],
       [ 1.,  1.,  8.,  9.,  3.,  6.]])

如果在reshape时,某个维度的值为-1,那么这个维度会被自动计算。

使用hsplit可以按照水平axis划分数组。

>>> a = np.floor(10*np.random.random((2,12)))
>>> a
array([[ 9.,  5.,  6.,  3.,  6.,  8.,  0.,  7.,  9.,  7.,  2.,  7.],
       [ 1.,  4.,  9.,  2.,  2.,  1.,  0.,  6.,  2.,  2.,  4.,  0.]])
>>> np.hsplit(a,3)   # Split a into 3
[array([[ 9.,  5.,  6.,  3.],
       [ 1.,  4.,  9.,  2.]]), array([[ 6.,  8.,  0.,  7.],
       [ 2.,  1.,  0.,  6.]]), array([[ 9.,  7.,  2.,  7.],
       [ 2.,  2.,  4.,  0.]])]
>>> np.hsplit(a,(3,4))   # Split a after the third and the fourth column
[array([[ 9.,  5.,  6.],
       [ 1.,  4.,  9.]]), array([[ 3.],
       [ 2.]]), array([[ 6.,  8.,  0.,  7.,  9.,  7.,  2.,  7.],
       [ 2.,  1.,  0.,  6.,  2.,  2.,  4.,  0.]])]

同样还有vsplit可以切分垂直axis。而使用array_split运行你以任意axis切分数组。

广播规则

广播允许全局函数以一种有意义的方式处理不同形状的输入。

广播的第一个规则是如果所有输入数组拥有不同的维度,1会追加到形状的前面直到直到两个数组拥有相同的维度。比如[1,2]可以被扩展为[[1,2]]。

广播的第二个规则是输出数组的某个维度长度是所有输入数组在该维度的长度的最大值。

名为matplotlib的库

matplotlib是一个Python的2D绘图库,可以产出发行级别质量的图片,并允许以丰富的格式进行存储。

先安装一波。

$ pip install matplotlib

基础概念

matplotlib有大量的代码库,这可能会使得新手望而生畏。但是,只要拥有相当简单的概念框架和少数重要知识点,大部分的matplotlib用例可以被轻易理解。

绘图需要在多个层次发起动作,从最底层(为2D数组增加轮廓)到最明确的(将屏幕着成红色)。一个绘图库的目的就是将帮助你以尽可能简单的方式展示自己的数据,并提供必要的控制。即在大部分时间内使用相对高级的命令,但是在需要的时候依旧可以切换使用低级命令。

因此,matplotlib中的一切都以层次的方式组织。层次的顶部是matplotlib的“状态机环境”,由matplotlib.pyplot模块提供。在这个层次,提供了诸如增加绘图元件(直线,图像,文本等等)到当前图像的特定Axes的简单函数。

下面一个层次,是面向对象接口的第一个层次,在这个层次pyplot仅提供 了一些崭新的函数,比如创建图像。在这个层次,用户使用pyplot创建图像,并通过这些图像创建一个或多个Axes对象。在之后的绘制操作将使用这些Axes对象。

要获得更多的控制——这对于将matplotlib嵌套到GUI应用这样的用例是很基础的事情——pyplot层次可以完全抛弃,留下一个纯粹的面向对象的接口的方式。

import matplotlib.pyplot as plt
import numpy as np

上面图记录了所有的Axes对象,一些特殊的文本(标题,图像说明等),以及画布(不要过分担心画布,由于实际执行绘制的对象,以为你产出图像,因此非常重要,但是对于用户来说是或多或少不可见的)。一张图可以有任意数量的Axes,但是至少应该有一个。

用pyplot创建一副新图的最简单的方式:

fig = plt.figure()  # an empty figure with no axes
fig.suptitle('No axes on this figure')  # Add a title so we know which it is

fig, ax_lst = plt.subplots(2, 2)  # a figure with a 2x2 grid of Axes

  • Axes,这就是我们认为的一幅图。一幅图可以有多个Axes,但是一个Axes仅属于一幅图。Axes可以包含两个(在3D情况下为三个)Axis对象,用于管理数据限制。每个Axes都有一个标题。
  • Axis,这些是类似于线条的对象,它们负责设置图像限制并生成标记(Axis上的标记)和标记的标签(标记在标记上的字符串)。标记的位置由一个Locator对象决定,而标签由Formatter进行格式化。正确的Locator和Formatter的组合会提供对标记位置和标签非常良好的控制。
  • Artist,你在图上能看到的任何东西都是一个artist(包括图像、Axes和Axis对象)。包括文本对象,Line2D对象,集合对象,Patch对象…当图像被显示时,所有上面的artist都会被绘制到画布(canvas)上。大部分artists都与Axes紧密关联,这样的artist不能在多个Axes间共享,或者在之间移动。

所有的绘图函数都将np.array或np.ma.masked_array作为输入。

MAtplotlib,pyplot和pylab:它们之间的关系

Matplotlib是整个包;matplotlib.pyplot是matplotlib的一个模块,而pylab是一个随着matplotlib一起安装的模块。

Pyplot为底层面向对象绘图库提供了一个状态机接口。状态机会自动创建图像和axes来实现目标图像。比如

x = np.linspace(0, 2, 100)

plt.plot(x, x, label='linear')
plt.plot(x, x**2, label='quadratic')
plt.plot(x, x**3, label='cubic')

plt.xlabel('x label')
plt.ylabel('y label')

plt.title("Simple Plot")

plt.legend()

plt.show()

对plt.plot的第一次调用会自动创建必要图像和axes来实现目标图像。后续对plt.plot的调用会重用当前的axes,并每次增加一条线。设置标题,图例和axis标签,这些图元也会自动复用当前axes并设置标题,创建图例并对axis上标签。

pylab是一个便捷的模块,用于批量导入在一个命名空间下导入matplotlib.pyplot和numpy。pylab不被推荐使用,使用pyplot代替它。

对于非交互式的绘图,推荐使用pyplot来创建图像并用OO接口来绘图。

代码风格

当查看这篇文档和例子时,你会发现不同的代码风格和使用模式。这些风格都是完全有效的并有各自的优缺点。几乎所有的例子都能被转换为另外一种风格并实现相同的结果。唯一的警告就注意不要在你自己的代码中混淆不同的风格。

在不同的风格中,有两种是官方支持的。因此,这里有推荐的方式使用matplotlib。

对于pyplot风格,在你的脚本的顶部的导入语句应该如下:

import matplotlib.pyplot as plt
import numpy as np

接下来一次调用,比如,np.arange,np.zeros, np.pi等,使用pyplot接口来创建图像,接下来使用对象方法。

x = np.arange(0, 10, 0.2)
y = np.sin(x)
fig, ax = plt.subplots()
ax.plot(x, y)
plt.show()

所有为什么所有额外的键入使用MATLAB风格(依赖于全局状态和一个平面命名空间)?对于像上面这么简单的例子,唯一的优势就是理论性:文字的风格更加明显,对于东西的来源和发生的事情更加清晰。对于更复杂的应用,明显和清晰变得更加有价值,并且更丰富和完整的面向对象接口会使得程序变得容易编写和维护。

往往一个人会发现自己不断在构建相同的图像,但是使用不同的数据集。因此导致需要写一个特殊的函数来执行绘图。推荐的函数签名如下:

def my_plotter(ax, data1, data2, param_dict):
    """
    A helper function to make a graph

    Parameters
    ----------
    ax : Axes
        The axes to draw to

    data1 : array
       The x data

    data2 : array
       The y data

    param_dict : dict
       Dictionary of kwargs to pass to ax.plot

    Returns
    -------
    out : list
        list of artists added
    """
    out = ax.plot(data1, data2, **param_dict)
    return out

# which you would then use as:

data1, data2, data3, data4 = np.random.randn(4, 100)
fig, ax = plt.subplots(1, 1)
my_plotter(ax, data1, data2, {'marker': 'x'})

如果你拥有两个子图:

fig, (ax1, ax2) = plt.subplots(1, 2)
my_plotter(ax1, data1, data2, {'marker': 'x'})
my_plotter(ax2, data3, data4, {'marker': 'o'})

再一次,对于这些简单的例子,好像有点牛刀小用,但是一旦图像变得稍微复杂一些,你将收到回报。

什么是后台

matplotlib旨在提供不同的用例和输出格式。一些人在python shell交互式使用matplotlib,并在键入命令时弹出窗口。一些人运行Jupyter笔记本,并且为数据分析绘制内联图像。其他人将matplotlib嵌入到图像用户接口,比如wxpython或pygtk来构建丰富的应用。一些人在批处理脚本中使用matplotlib,来从实时从数值中生成附录图像,其他人运行web服务器来动态生成图像。

要支持上述所有的用例,matplotlib可以生成不同的输出,这些能力每一个都被称为一个后端;而前端这是用户面对的代码,而后端则在场景后完成了所有生成图像必须的硬性工作。这里有两类后端:用户接口后端(也称为交互式后端)以及创建图像文件的硬拷贝后端(也称为非交互式后端)。

有四种方法可以配置你的后端。如果他们彼此矛盾,可以使用最后描述的方法。

  1. 使用你的matplotlibrc文件中的backend参数。

    backend : WXAgg   # use wxpython with antigrain (agg) rendering
    
  2. 设置MPLBACKEND环境变量。

    $ export MPLBACKEND=module://my_backend
    
  3. 如果你的脚本依赖于特定的后端,你可以使用use函数。

    import matplotlib
    matplotlib.use('PS')   # generate postscript output by default
    

    如果你使用use函数,这一定要在导入matplotlib.pyplot之前调用,之后的调用将不会生效。

什么是交互模式

一个交互式后台能允许在屏幕上绘制。是否再屏幕上绘制,何时绘制取决于你实际调用的函数,以及一个状态变量,决定是否matplotlib处于交互模式。默认的布尔值配置在matplotlibrc文件中。你也可以通过matplotlib.interactive()函数启用,并通过matplotlib.is_interactive()获取值。你也可以通过matplotlib.pyplot.ion()方法启用交互模式,而用matplotlib.pyplot.ioff()禁用交互模式。

通过python shell执行下面命令。

import matplotlib.pyplot as plt
plt.ion()
plt.plot([1.6, 2.7])

假如你运行1.0.1或更高版本的matplotlib,并且你安装了一个交互式的后台并默认选择,你应该能看到一个图表,并且你的命令行并没有阻塞,你可以自由键入新的命令。

plt.title("interactive test")
plt.xlabel("index")

并且你会发现图像随着你输入命令被实时更新。获取一个axes对象的引用,并且调用它的方法。

ax = plt.gca()
ax.plot([3.1, 2.2])

如果你使用特定的后端,或者老版本的matplotlib,你可能不能看到实时的变化。这时候你需要显式调用draw函数强制刷新。

plt.draw()

接下来演示非交互模式。

import matplotlib.pyplot as plt
plt.ioff()
plt.plot([1.6, 2.7])

可以看到什么都没有出现在屏幕上(除非你用的是macosx,它是不协调的)。要显示图表,你需要做下面工作:

plt.show()

之后你就能看到图像了,但是你的命令行工具变的阻塞了。show方法是一个阻塞方法,直到你手动关闭图像窗体后才会离开。

强制使用阻塞函数的好处是什么?假设你需要用一段脚本将一个文件中的内容绘制到屏幕上。你会希望能看到最终的图像后才结束脚本。如果没有类似的阻塞命令,脚本会直接结束,导致图像窗口一闪而过。除此之外,非交互模式会将绘制操作延迟到show方法的调用,这比每次加入新特性时重绘整个图像要高效得多。

在1.0之前的版本,show在一个脚本中最多调用一次。但是在1.0.1和之后的版本,这个约束被移除了,因此你可以调用任意多次的脚本。

在交互模式,pyplot函数会自动将图像绘制到窗口上。但是如果你使用对象的方法而非pyplot中的函数,那么你需要手动调用draw函数来刷新图像。

性能

当通过交互模式探索数据或以编程的方式保存大量图像,渲染性能可能成为你整个流水线的一个痛点。Matplotlib提供了多种方式来大幅减少渲染所需时间,但是代驾是生成图像的质量要轻微受损。可以用来减少渲染时间的可用方法取决于创建的图像类别。

简化线条

对于包含线条的图像,渲染性能可以通过matplotlibrc中的path.simplify和path.simplify_threshold参数来控制,前者指定是否要简化线条,后者决定线条的简化程度(越大意味着渲染越快,但是图像越粗糙)。

下面的脚本会以无简化的方式展示数据,之后以简化的方式展示相同数据。

import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl

# Setup, and create the data to plot
y = np.random.rand(100000)
y[50000:] *= 2
y[np.logspace(1, np.log10(50000), 400).astype(int)] = -1
mpl.rcParams['path.simplify'] = True

mpl.rcParams['path.simplify_threshold'] = 0.0
plt.plot(y)
plt.show()

mpl.rcParams['path.simplify_threshold'] = 1.0
plt.plot(y)
plt.show()

Matplotlib目前保守地将simplify_threshold设置为1/9。线条简化的是同构迭代地合并线段到一个向量中,直到下一个线段的垂直距离超过path.simplify_threshold参数为止。

简化标记

标记也可以被检查,虽然没有线段一样好的效果。标记简化仅对Line2D对象可用,在构建Line2D对象的时候,可以使用markevery参数来进行简化。

plt.plot(x, y, markevery=10)

markevery参数允许原始的二次抽样,或者参数在均匀的空间进行采样。

使用fast风格

可以使用快速风格来自动设置简化和分块参数为可行值来加速大数据的展示。使用非常简单:

import matplotlib.style as mplstyle
mplstyle.use('fast')

你可以一次性使用多个风格,但是不要忘了要把fast放在最末,以防被覆盖。

mplstyle.use(['dark_background', 'ggplot', 'fast'])

什么是pyplot

matplotlib.pyplot是命令风格函数的集合,是的matplotlib能表现的像MATLAB一样。每个pyplot函数会对图像做出一些改变。创建一个图像,在图像上创建一些绘制区域,在绘制区域画一些线条,为图像装饰上表情,等。matplotlib.pyplot的多种状态在函数调用之间被保留,所以它会记录当前图像和绘制区域,而绘制函数直接关联当前axes。

import matplotlib.pyplot as plt
plt.plot([1, 2, 3, 4])
plt.ylabel('some numbers')
plt.show()

你可能会好奇为什么线段x轴范围为0~3,而y轴范围为1~4。如果你仅为plot提供一个数组,matplotlib会假设它是一个y值序列,并自动用序列下表作为x值,而python以0起始,因此导致了差异。

plot是一个多用途的命令,并且可以接受任意数目的参数,比如,要绘制x->y的映射,你可以使用下面命令。

plt.plot([1, 2, 3, 4], [1, 4, 9, 16])

格式化plot风格

除了x,y参数外,这里还有第三个参数——格式字符串,指定图像的颜色和线条的形状。格式字符串中的字母和符号均来自MATLAB,并且你将要联结颜色字符串和线条风格字符串。默认的字符串是b-,表示蓝色实心线,因此要展示红色圆圈,你应该发布下面命令:

plt.plot([1, 2, 3, 4], [1, 4, 9, 16], 'ro')
plt.axis([0, 6, 0, 20])
plt.show()

要查看所有的可用线条风格和格式字符串,可以参考plot()文档。上面的axis()命令取一个参数列表[xmin,xmax,ymin,ymax]以指定视窗。

如果matplotlib只能处理列表,那么matplotlib对于数值处理就是相当无用的。通常,你会选择使用numpy数组。实际上,所有的序列在内部都会被转换成numpy的数组。下面的例子会使用一条命令以不同的风格绘制多条线条。

import numpy as np

# evenly sampled time at 200ms intervals
t = np.arange(0., 5., 0.2)

# red dashes, blue squares and green triangles
plt.plot(t, t, 'r--', t, t**2, 'bs', t, t**3, 'g^')
plt.show()

可能你的数据通过字典的方式获取。比如numpy.recarray或pandas.DataFrame。

通过关键字绘制

Matplotlib允许同构data关键字传递这样的对象。如果你提供了,那么你可以存储在data对象中的键值对来绘制图像。

data = {'a': np.arange(50),
        'c': np.random.randint(0, 50, 50),
        'd': np.random.randn(50)}
data['b'] = data['a'] + 10 * np.random.randn(50)
data['d'] = np.abs(data['d']) * 100

plt.scatter('a', 'b', c='c', s='d', data=data)
plt.xlabel('entry a')
plt.ylabel('entry b')
plt.show()

绘制分类变量

也可以创建分类变量,matplotlib允许你向许多绘制函数传递分类变量。比如:

names = ['group_a', 'group_b', 'group_c']
values = [1, 10, 100]

plt.figure(1, figsize=(9, 3))

plt.subplot(131)
plt.bar(names, values)
plt.subplot(132)
plt.scatter(names, values)
plt.subplot(133)
plt.plot(names, values)
plt.suptitle('Categorical Plotting')
plt.show()

控制线条属性

线条有许多你可以设置的属性:线宽,抗锯齿等,可以参考matplotlib.lines.Line2D。这里有许多可以设置线条属性的方式。

  • 使用关键字

    plt.plot(x, y, linewidth=2.0)
    
  • 使用Line2D对象的setter方法。

    line, = plt.plot(x, y, '-')
    line.set_antialiased(False) # turn off antialising
    
  • 使用setp函数设置多个属性。

    lines = plt.plot(x1, y1, x2, y2)
    # use keyword args
    plt.setp(lines, color='r', linewidth=2.0)
    # or MATLAB style string value pairs
    plt.setp(lines, 'color', 'r', 'linewidth', 2.0)
    

    要获取线段所有可设置的属性,可以仅为setp传递线段,而不传递属性。

    In [69]: lines = plt.plot([1, 2, 3])
      
    In [70]: plt.setp(lines)
      alpha: float
      animated: [True | False]
      antialiased or aa: [True | False]
      ...snip
    

在多图和多Axes上工作

MATLAB和pyplot,有当前图和当前Axes的概念。所有绘制命令都作用在当前axes之上。函数gca()返回当前axes(一个matplotlib.axes.Axes实例),gcf()返回当前图像(matplotlib.figure.Figure实例)。通常你不需要关注这些细节,因为所有的任务都是在幕后完成的。下面的脚本创建两个子图。

  def f(t):
      return np.exp(-t) * np.cos(2*np.pi*t)
  
  t1 = np.arange(0.0, 5.0, 0.1)
  t2 = np.arange(0.0, 5.0, 0.02)
  
  plt.figure(1)
  plt.subplot(211)
  plt.plot(t1, f(t1), 'bo', t2, f(t2), 'k')
  
  plt.subplot(212)
  plt.plot(t2, np.cos(2*np.pi*t2), 'r--')
  plt.show()

上面的figure()命令是可选的,因为figure(1)会被自动创建,就像如果你不指定任何axes,subplot(111)会被默认创建一样。而subplot()命令指定了行数,列数,一个索引index,index范围是[1,行数*列数],左上角的子图为1,向右递增。

你可以创建任意数量的子图和axes,如果你想手动放置axes,也就是不放在矩形表格中,那就使用axes()命令,这个命令允许你显式指定axes的位置,比如axes([left, bottom, width, height]),所有数都表示比例,取0到1之间。

利用figure()命令你可以创建多个图像,每个图像有一个自己的id,id按照创建的顺序递增。当然,每个图像都可以包含任意多的axes和子图。

  import matplotlib.pyplot as plt
  plt.figure(1)                # the first figure
  plt.subplot(211)             # the first subplot in the first figure
  plt.plot([1, 2, 3])
  plt.subplot(212)             # the second subplot in the first figure
  plt.plot([4, 5, 6])
  
  
  plt.figure(2)                # a second figure
  plt.plot([4, 5, 6])          # creates a subplot(111) by default
  
  plt.figure(1)                # figure 1 current; subplot(212) still current
  plt.subplot(211)             # make subplot(211) in figure1 current
  plt.title('Easy as 1, 2, 3') # subplot 211 title

你可以通过clf()命令清理当前图像,利用cla()清理当前axes。如果状态在幕后维护让你感到恼怒,不要失去信心,这仅仅只是在面向对象接口上的一层薄薄的带状态的包装,你可以使用面向对象接口。

如果你在创建大量的图像,你需要意识到,一个图像占用的内存直到对该图像显式调用close()命令后才会被完全释放。删除所有对图像的引用,关闭显示图像的窗口是不够的,因为pyplot内部持有对图像的引用,且尽在close()后才释放。

#### 用于文本

text()命令可以用于向图像任意位置添加文本,xlabel()、ylabel()和title()命令向特定的位置添加文本。

  mu, sigma = 100, 15
  x = mu + sigma * np.random.randn(10000)
  
  # the histogram of the data
  n, bins, patches = plt.hist(x, 50, density=1, facecolor='g', alpha=0.75)
  
  
  plt.xlabel('Smarts')
  plt.ylabel('Probability')
  plt.title('Histogram of IQ')
  plt.text(60, .025, r'$\mu=100,\ \sigma=15$')
  plt.axis([40, 160, 0, 0.03])
  plt.grid(True)
  plt.show()

所有的text()命令都会返回一个matplotlib.text.Text实例,就像线段一样,你可以通过setp()函数来设置它的属性。

#### 在文本中使用数学表达式

matplotlib的任意文本表达式中均可以包含TeX语法,比如要表示σi=15,你可以通过美元符号包围TeX表达式。

  plt.title(r'$\sigma_i=15$')

字符串之前的r很重要,它标志这个字符串是一个个裸字符串,这意味着里面的反斜线不作为转义字符。matplotlib有一个内建的TeX表达式解析器,以及布局引擎,并使用自己的数学字体。因此你可以跨平台使用数学表达式而不需要安装额外的TeX。对于那些已经安装了LaTeX和dvipng的用户,你也可以使用LaTeX来格式化你的文本并且直接输出到你的图像上。

#### 注解文本

text()命令的基础用处就是在Axes的任意位置放置一段文本。文本的一个通常的用处就是注解图像的一些特性,而annotate()方法提供了有用的功能使得注解变得简单。一个注解中,要开率两个点:注解的坐标,文本展示的位置。

  ax = plt.subplot(111)
  
  t = np.arange(0.0, 5.0, 0.01)
  s = np.cos(2*np.pi*t)
  line, = plt.plot(t, s, lw=2)
  
  plt.annotate('local max', xy=(2, 1), xytext=(3, 1.5),
               arrowprops=dict(facecolor='black', shrink=0.05),
               )
  
  plt.ylim(-2, 2)
  plt.show()

在这个基础案例中,xy和xytext位置都是数据坐标,这里还有大量其他坐标系统可以选择。

对数和其它非线性Axes

matplotlib.pyplot不仅支持线性的axis刻度,也支持对数的和分对数的刻度。当数据跨越多个量级是非常通用的。要改变一个坐标轴的刻度非常简单:plt.xscale('log')

下面的例子会绘制四个子图,它们拥有相同的数据,但是不同的精度。

from matplotlib.ticker import NullFormatter  # useful for `logit` scale

# Fixing random state for reproducibility
np.random.seed(19680801)

# make up some data in the interval ]0, 1[
y = np.random.normal(loc=0.5, scale=0.4, size=1000)
y = y[(y > 0) & (y < 1)]
y.sort()
x = np.arange(len(y))

# plot with various axes scales
plt.figure(1)

# linear
plt.subplot(221)
plt.plot(x, y)
plt.yscale('linear')
plt.title('linear')
plt.grid(True)


# log
plt.subplot(222)
plt.plot(x, y)
plt.yscale('log')
plt.title('log')
plt.grid(True)


# symmetric log
plt.subplot(223)
plt.plot(x, y - y.mean())
plt.yscale('symlog', linthreshy=0.01)
plt.title('symlog')
plt.grid(True)

# logit
plt.subplot(224)
plt.plot(x, y)
plt.yscale('logit')
plt.title('logit')
plt.grid(True)
# Format the minor tick labels of the y-axis into empty strings with
# `NullFormatter`, to avoid cumbering the axis with too many labels.
plt.gca().yaxis.set_minor_formatter(NullFormatter())
# Adjust the subplot layout, because the logit one may take more space
# than usual, due to y-tick labels like "1 - 10^{-3}"
plt.subplots_adjust(top=0.92, bottom=0.08, left=0.10, right=0.95, hspace=0.25,
                    wspace=0.35)

plt.show()

机器学习入门

机器学习系统通过学习如何组合输入信息来对从未见过的数据做出有用的预测。

术语

  • 模型

    模型是我们机器学习训练的结果,我们通过已知的数据训练模型,用模型预测未知的数据。

    模型分为回归和分类两类模型:

    • 回归模型用于预测连续值,比如价格
    • 分类模型用于预测离散值,比如性别
  • 标签

    标签是模型的输出,记作y。

  • 特征

    特征是模型的输入,记作x。特征可能不仅一个,因此x是一个向量。

  • 样本

    样本是包含特征的数据。样板分为有标签样本和无标签样本,有标签样本同时包含特征和标签,而无标签样本仅包含特征。

Tensorflow的输入函数

import tensorflow as tf
import numpy as np 
import matplotlib.pyplot as plt 
import pandas as pd
from tensorflow.python.data import Dataset
import sklearn.metrics as metrics
import math

def my_input_fn(features, targets, batch_size=1, shuffle=True, num_epochs=None):
    """Trains a linear regression model of one feature.
  
    Args:
      features: pandas DataFrame of features
      targets: pandas DataFrame of targets
      batch_size: Size of batches to be passed to the model
      shuffle: True or False. Whether to shuffle the data.
      num_epochs: Number of epochs for which data should be repeated. None = repeat indefinitely
    Returns:
      Tuple of (features, labels) for next data batch
    """
    
    # Convert pandas data into a dict of np arrays.
    features = {key:np.array(value) for key,value in dict(features).items()}                                           
 
    # Construct a dataset, and configure batching/repeating.
    ds = Dataset.from_tensor_slices((features,targets)) # warning: 2GB limit
    ds = ds.batch(batch_size).repeat(num_epochs)
    
    # Shuffle the data, if specified.
    if shuffle:
      ds = ds.shuffle(buffer_size=10000)
    
    # Return the next batch of data.
    features, labels = ds.make_one_shot_iterator().get_next()
    return features, labels

y=ax+b

假设我们要预测一个函数y=ax+b。

下面我们预测y=2x+b。

import tensorflow as tf
import numpy as np 
import matplotlib.pyplot as plt 
import pandas as pd
from tensorflow.python.data import Dataset
import sklearn.metrics as metrics
import math

x = np.arange(100, dtype=np.float)
y = x * 2 + 1
frame = pd.DataFrame()
frame['x'] = x
frame['y'] = y

features = frame[['x']]
targets = frame['y']

feature_columns = [tf.feature_column.numeric_column('x')]
my_optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.01)
my_optimizer = tf.contrib.estimator.clip_gradients_by_norm(my_optimizer, 5.0)
classifier = tf.estimator.LinearRegressor(feature_columns=feature_columns, optimizer=my_optimizer)
input_fn = lambda: my_input_fn(features, targets, batch_size = 5, shuffle = True)
classifier.train(input_fn, steps=1000)

predict_input = lambda: my_input_fn(features, targets, shuffle=False, batch_size=1, num_epochs=1)
prediction = classifier.predict(predict_input)
predicted_y = [item['predictions'][0] for item in prediction]
loss = math.sqrt(metrics.mean_squared_error(y, predicted_y))

print("The loss is", loss)
a = classifier.get_variable_value('linear/linear_model/x/weights')
b = classifier.get_variable_value('linear/linear_model/bias_weights')
print("y = %fx + %f" % (a, b))

plt.figure(1)
plt.subplot(1, 1, 1)
plt.tight_layout()
plt.plot(x, y, label='y')
plt.plot(x, predicted_y, label='predicted_y')
plt.xlabel('x')
plt.ylabel('y')
plt.title('prediction')
plt.legend()

执行上面命令,可以看到不错的预测效果。

如果将steps增大到1000,误差将会变得非常小。

y=a0*x0+a1*x1+b

import tensorflow as tf
import numpy as np 
import matplotlib.pyplot as plt 
import pandas as pd
from tensorflow.python.data import Dataset
import sklearn.metrics as metrics
import math

x0 = np.random.rand(100)
x1 = np.random.rand(100)
y = 1 * x0 + 2 * x1 + 3
frame = pd.DataFrame()
frame['x0'] = x0
frame['x1'] = x1
frame['y'] = y

features_name = ['x0', 'x1']
features = frame[features_name]
targets = frame['y']

feature_columns = [tf.feature_column.numeric_column(name) for name in features]
my_optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.01)
my_optimizer = tf.contrib.estimator.clip_gradients_by_norm(my_optimizer, 5.0)
classifier = tf.estimator.LinearRegressor(feature_columns=feature_columns, optimizer=my_optimizer)
input_fn = lambda: my_input_fn(features, targets, batch_size = 5, shuffle = True)
classifier.train(input_fn, steps=1000)

predict_input = lambda: my_input_fn(features, targets, shuffle=False, batch_size=1, num_epochs=1)
prediction = classifier.predict(predict_input)
predicted_y = [item['predictions'][0] for item in prediction]
loss = math.sqrt(metrics.mean_squared_error(y, predicted_y))

print("The loss is", loss)
a0 = classifier.get_variable_value('linear/linear_model/x0/weights')
a1 = classifier.get_variable_value('linear/linear_model/x1/weights')
b = classifier.get_variable_value('linear/linear_model/bias_weights')
print("y = %fx0 + %fx1 + %f" % (a0, a1, b))

y=a0*x0*x1

import tensorflow as tf
import numpy as np 
import matplotlib.pyplot as plt 
import pandas as pd
from tensorflow.python.data import Dataset
import sklearn.metrics as metrics
import math

x0 = np.random.rand(100) * 2 - 1
x1 = np.random.rand(100) * 2 - 1
y = x0 * x1
frame = pd.DataFrame()
frame['x0'] = x0
frame['x1'] = x1
frame['y'] = y
frame['x2'] = frame['x0'] * frame['x1']

features_name = ['x0', 'x1', 'x2']
features = frame[features_name]
targets = frame['y']

feature_columns = [tf.feature_column.numeric_column(name) for name in features]
my_optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.01)
my_optimizer = tf.contrib.estimator.clip_gradients_by_norm(my_optimizer, 5.0)
classifier = tf.estimator.LinearRegressor(feature_columns=feature_columns, optimizer=my_optimizer)
input_fn = lambda: my_input_fn(features, targets, batch_size = 5, shuffle = True)
classifier.train(input_fn, steps=1000)

predict_input = lambda: my_input_fn(features, targets, shuffle=False, batch_size=1, num_epochs=1)
prediction = classifier.predict(predict_input)
predicted_y = [item['predictions'][0] for item in prediction]
loss = math.sqrt(metrics.mean_squared_error(y, predicted_y))

print("The loss is", loss)
a0 = classifier.get_variable_value('linear/linear_model/x0/weights')
a1 = classifier.get_variable_value('linear/linear_model/x1/weights')
a2 = classifier.get_variable_value('linear/linear_model/x2/weights')
b = classifier.get_variable_value('linear/linear_model/bias_weights')
print("y = %fx0 + %fx1 + %f*x1*x2 + %f" % (a0, a1, a2, b))

引用

[1] https://zh.wikipedia.org/wiki/Python

参考