1.2 Python入门

简单、高效、易入门的特点,让Python成了最好用的编程语言之一。毫无疑问,Python已经成为人工智能从业者的首选编程语言。本书将介绍和使用的深度学习框架PyTorch、TensorFlow都可以看成用于深度学习的Python库函数。

本节将简单介绍Python的入门知识,包括Python的基本语法和Python常用的库Numpy、Matplotlib等。如果你已经掌握了这些Python入门知识,可以跳过本节,直接阅读后面的章节。

1.2.1 Python简介

Python是什么?跟C/C++、Java一样,Python是一门编程语言。它是著名的“龟叔”Guido van Rossum在1989年圣诞节期间,为了打发无聊的圣诞节而编写的编程语言。虽然Python的诞生距今只有30年,但是它的受欢迎程度却日益高涨,得到广大程序员们的青睐。在2019年3月公布的TIOBE编程语言排行榜上,Python的受欢迎程度已经上升至第3位,仅次于Java和C。图1-4展示了受欢迎程度排名前10的编程语言在2018年和2019年的排名。

图1-4 受欢迎程度排名前10的编程语言在2018年和2019年的排名

为什么Python能获得如此迅速的发展?为什么Python能成为人工智能从业者的首选编程语言?主要原因在于以下四个方面。

(1)Python简单、高效、易入门,它的设计初衷就是优雅、明确、简单。

(2)人工智能的核心计算部分的底层都是由C语言编写的,上层逻辑都是由Python实现。使用Python能极大地提高开发效率,实现快速开发。

(3)Python具有丰富而强大的库,俗称“胶水语言”。

(4)Python应用领域广泛,不仅可以支持航天航空系统开发,还可以支持小游戏开发,几乎无所不能。

总之,Python以其独特而显著的优势当之无愧地成为人工智能从业者的首选编程语言之一。

1.2.2 Python的安装

安装Python时,可以选择单独安装Python,也可以选择安装Python的发行版。安装Python的发行版会方便很多,因为发行版集成了许多Python库,省去了一个个安装的麻烦。因此,本书推荐直接安装Python的发行版。

Anaconda是Python的一个发行版,它把Python数据分析所需要的包都集成在了一起,其中包含了上百个与数据分析相关的开源包,如Numpy、Matplotlib等。Anaconda还包含功能强大的工具——Jupyter Notebook。总之,安装Anaconda可以节省大量下载模块包的时间,操作更加方便。

安装Anaconda非常简单,首先在其官网上下载最新的版本。Anaconda提供了Windows、Mac OS、Linux不同操作系统的安装文件,可根据个人的计算机系统进行选择。Python有Python 2.x和Python 3.x两个版本,安装的时候应选择Python 3.x,按照提示进行安装即可。以Windows操作系统为例,安装完成之后,需要将Anaconda添加到系统的环境变量中。添加环境变量之后,打开命令行窗口,输入python,按回车键就可以启动Python解释器,显示如下:

   >python
   Python 3.7.1 (default, Dec 10 2018, 22:54:23) [MSC v.1915 64 bit (AMD64)] :: Anaconda, Inc. on win32
   Type ″help″, ″copyright″, ″credits″ or ″license″ for more information.
   >>>

现在,Anaconda的安装已经完成了,而且已经启动Python解释器,可以在Python解释器里编写和运行代码。Anaconda的功能非常强大,包含了许多工具和包,便于编写Python程序。这部分内容将在1.3节进行介绍。

1.2.3 Python基础知识

打开Python解释器,当我们在Python解释器中输入一行语句的时候,解释器就会执行该语句并做出回应,这是典型的交互式编程。本小节介绍的所有代码都将在Python解释器中编写。

1.数据类型

在Python中,常用的数据类型包括整数(int)、浮点数(float)、字符串(str)、布尔值(bool)等。Python中,可以使用type()函数来查看数据类型,如下所示:

   >>> type(2)
   <class ′int′>
   >>> type(2.3)
   <class ′float′>
   >>> type(′python′)
   <class ′str′>
   >>> type(True)
   <class ′bool′>
   >>> type(False)
   <class ′bool′>

显然,2是int类型,2.3是float类型,′python′是str类型,True和False是bool类型。

2.数学运算

加、减、乘、除数学运算可以使用下列语句实现:

   >>> 1 + 2
   3
   >>> 1 - 2
   -1
   >>> 1 * 2
   2
   >>> 1 / 2
   0.5
3.变量

变量就是代表(或者引用)某值的名字。变量名必须是大小写英文字母、数字和_的组合,且不能以数字开头。可以对变量赋值,也可以对变量进行计算。

上面的语句中,分别对变量x和变量y赋值,并对两个变量进行计算。其中,“#”是注释的意思。Python是动态语言,所谓动态,是指可以把任意数据类型赋值给变量,同一个变量可以反复赋值,而且可以是不同类型的变量。与动态语言对应的是静态语言。所谓静态,是指在定义变量时必须指定变量类型,如果赋值的时候类型不匹配,就会报错。可见,与静态语言相比,动态语言更灵活。

4.列表

列表是Python内置的一种数据类型。列表是一种有序的集合,可以随时添加和删除其中的元素。同一列表中的元素可以是不同的数据类型。

   >>> a = [1, 2, 3, ′python′]
   >>> print(a)
   [1, 2, 3, ′python′]
   >>> a[0]
   1
   >>> a[-1] = 4   # 最后一个元素赋值4
   >>> print(a)
   [1, 2, 3, 4]

列表中元素的索引是从0开始的,a[0]代表列表a索引为0的元素,即1。索引-1表示列表的倒数第一个元素,以此类推,索引-2表示列表的倒数第二个元素,等等。还可以对列表中的任意元素重新赋值。

可以对列表进行切片操作,使用切片不仅可以访问某个值,还可以访问部分列表。

对列表进行切片操作,需要写成类似a[0:1]的形式。冒号左边是起始索引,包含在切片内;冒号右边是结束索引,不包含在切片内。例如,切片a[0:1]仅包含a[0]元素,不包含a[1]元素。如果冒号左边的起始索引为空,则默认从0开始;如果冒号右边的结束索引为空,则默认到列表的最后一个元素为止。

5.字典

Python中的字典是由多个键及相对应的值构成的对组成。以《新华字典》为例,汉字可以比作键,释义可以比作值。

注意,字典中的键是唯一的,而值并不唯一。

6.if语句

Python中,可以使用if语句进行条件判断。

   >>> busy = True
   >>> if busy:
   ...     print(′He is busy now′)
   ...
   He is busy now

根据Python的缩进规则,如果if语句判断为真,则执行下面缩进的print语句,否则什么也不做。注意,Python使用空白字符表示缩进,每缩进一次,使用4个空白字符。

也可以在if语句之后添加一个else语句,如果if语句判断为假,则不执行if语句的内容,而执行else语句的内容。注意,if语句和else语句后面都需要加上冒号。

   >>> busy = False
   >>> if busy:
   ...     print(′He is busy now′)
   ... else:
   ...     print(′He is not busy now′)
   ...
   He is not busy now
7.for循环

Python中,可以使用for… in…的语句结构进行循环处理。in后面可以是列表,也可以是Python的内置函数range()。range()函数可以创建一个整数列表。

   >>> for i in [0,1,2]:
   ...     print(i)
   ...
   0
   1
   2
   >>> for i in range(3):
   ...     print(i)
   ...
   0
   1
   2
8.函数

Python中,可以使用关键字def来定义函数。

下面定义一个sign函数,根据输入的x的值与0的大小关系,返回negative、zero或positive,代码如下:

函数的形参也可以设置成默认值,例如:

9.类

Python是面向对象的编程语言,面向对象的编程语言中,最重要的概念就是类(class)和实例(instance)。类是抽象的模板,而实例是根据类创建出来的具体的对象。可以在类的内部定义不同的函数,称为类的方法。Python中,使用class关键字来定义类,类的基本模板如下:

   class 类名:
     def _init_(self, 参数, …): # 初始化方法
     ...
     def 方法名1(self, 参数, …): # 方法1
     ...
     def 方法名2(self, 参数, …): # 方法2
     ...

注意,_init_()是一个初始化方法,只在生成类的实例时被调用一次。类的定义中,所有方法的第一个参数都是self,这是Python的特点之一。

下面我们来创建一个简单的类。

我们构造了一个类Greeter,类Greeter生成一个实例g。类Greeter的初始化函数_init_()会接收参数will,然后初始化self.name。

当我们定义一个类的时候,可以从现有的类继承,新的类称为子类(subclass),而被继承的类称为基类(baseclass)、父类或超类(superclass)。继承最大的好处是子类可以获得父类的全部功能,而且子类可以定义新的方法。

下面是一个类继承的简单例子。

1.2.4 NumPy矩阵运算

NumPy是Python的一个外部程序库,支持多维数组与矩阵运算,此外也针对数组运算提供大量的数学函数库。深度学习神经网络模型包含了大量的矩阵相乘运算,如果仅仅使用for循环,运算速度会大大降低,如果使用Python的NumPy进行矩阵运算,可以极大地提高运算效率。

1.导入NumPy

Python中导入NumPy非常简单,使用import命令导入即可,代码如下:

   >>> import numpy as np

import是Python中导入外部库的命令。这条语句表示导入NumPy并命名为np。

2.NumPy数组

NumPy可以使用方法np.array()来生成数组,np.array()接收Python列表作为参数,代码如下:

   >>> a = np.array([1, 2, 3])
   >>> print(a)
   [1 2 3]
   >>> type(a)
   <class ′numpy.ndarray′>
   >>> b = np.array([[1, 2, 3], [4, 5, 6]])
   >>> print(b)
   [[1 2 3]
    [4 5 6]]
   >>> type(b)
   <class ′numpy.ndarray′>

NumPy可以生成任意维度的数组。上面的程序中,a是一维数组,b是二维数组。数学运算中,一般将一维数组称为向量,将二维数组称为矩阵,将三维及三维以上的数组称为张量或多维数组。关于张量,将在第2章和第3章进行介绍。

3.NumPy的数学运算

NumPy中,维度相同的数组可以直接进行对应元素的加、减、乘、除运算。

   >>> x = np.array([1, 2, 3])
   >>> y = np.array([2, 4, 6])
   >>> x + y
   array([3, 6, 9])
   >>> x - y
   array([-1, -2, -3])
   >>> x * y
   array([ 2,  8, 18])
   >>> x / y
   array([0.5, 0.5, 0.5])

Numpy数组可以通过索引或切片来访问和修改,与Python中列表的切片操作一样。

   >>> a = np.array([1, 2, 3, 4])
   >>> a[0]
   1
   >>> a[0:2]
   array([1, 2])
   >>> a[0] = 10
   >>> a
   array([10,  2,  3,  4])
4.NumPy广播机制

如果两个数组的形状不相同,可以使用扩展数组的方法来实现相加、相减、相乘等操作,这种机制叫作广播。广播机制是Python的一个非常重要的功能。

对两个数组使用广播机制应遵守下列规则。

(1)如果两个数组的维度不同,可以使用1来对维度较小的数组进行扩展,直到两个数组的维度和长度都一样。

(2)如果两个数组在某个维度上的长度是一样的,或者其中一个数组在该维度上的长度为1,那么我们就说这两个数组在该维度上是相容的。

(3)如果两个数组在所有维度上都是相容的,就能对这两个数组使用广播机制。

(4)在任何一个维度上,如果一个数组的长度为1,另一个数组的长度大于1,那么在该维度上,就像是对第一个数组进行了复制。这段话比较抽象,不太好理解,下面通过几个例子来解释广播机制到底是如何运行的。

例如,二维数组与标量之间的算术运算其实就用到了最简单的广播机制,代码如下:

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

我们用图解的方法解释二维数组与标量进行算术运算时采用的广播机制,如图1-5所示。

图1-5 二维数组与标量进行算术运算时采用的广播机制

如果是二维数组与一维数组进行算术运算,令维度分别为(3, 2)和(2,),代码如下:

   >>> A = np.array([[1, 2], [3, 4], [5, 6]])
   >>> B = np.array([2, 2])
   >>> A + B
   array([[3, 4],
      [5, 6],
      [7, 8]])

值得一提的是,(3, 2)和(2,)的含义是不一样的。(3, 2)表示二维数组,而(2,)表示一维数组,被称为rank 1 array。一维数组的特点是它的转置还是它本身。在实际应用中,我们要注意这两者的区别。

可以使用下面的语句将一维数组转换为二维数组:

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

我们用图解的方法解释二维数组与一维数组进行算术运算时采用的广播机制,如图1-6所示。

图1-6 二维数组与一维数组进行算术运算时采用的广播机制

如果是两个二维数组进行算术运算,但一个维度的长度为1,令维度分别为(2, 1)和(1, 2),代码如下:

   >>> A = np.array([[1], [2]])
   >>> B = np.array([[2, 2]])
   >>> A + B
   array([[3, 3],
      [4, 4]]])

我们同样用图解的方法解释两个二维数组进行算术运算时采用的广播机制,如图1-7所示。

图1-7 两个二维数组进行算术运算时采用的广播机制

5.NumPy矩阵运算速度

上文介绍,使用NumPy矩阵运算代替for循环可以大大提高运算速度。下面,我们通过一个例子来比较矩阵运算与for循环的时间差异性。

   import numpy as np
   import time
   a = np.random.rand(100000)
   b = np.random.rand(100000)
   c = 0
   tic = time.time()
   for i in range(100000):
     c += a[i]*b[i]
   toc = time.time()
   print(c)
   print(″for loop:″ + str(1000*(toc-tic)) + ″ms″)
   c = 0
   tic = time.time()
   c = np.dot(a,b)
   toc = time.time()
   print(c)
   print(″Vectorized:″ + str(1000*(toc-tic)) + ″ms″)

其中,函数np.random.rand()用来生成标准正态分布的数组,函数np.dot()用来进行矩阵点乘运算。运行上面的代码后,结果显示如下:

   25097.204047472216
   for loop:51.16677284240723ms
   25097.20404747199
   Vectorized:0.9720325469970703ms

每台计算机运行的结果可能稍有不同,因为代码中使用的数组是随机生成的。结果显示,矩阵运算的速度要比for循环快很多,而且数据量越大、矩阵越复杂,速度上的差异就越明显。由此可见,使用Numpy中的矩阵运算将大大提高程序的运算速度。

1.2.5 Matplotlib绘图

Matplotlib是Python的一个强大的绘图库,可以绘制折线图、散点图、柱状图等。在深度学习领域,通常需要绘制大量的图形来实现数据的可视化以进行数据分析。下面,我们就来介绍Matplotlib的基本用法以及绘制图形的方法。

1.绘制函数图形

我们一般使用Matplotlib的pyplot模块来绘图,先来看一个简单的例子。

   # 导入库函数
   import numpy as np
   import matplotlib.pyplot as plt
   # 生成数据
   x = np.arange(0, 4 * np.pi, 0.1)
   y = np.sin(x)
   # 绘图
   plt.plot(x, y)
   plt.show()

上面的代码中,函数np.arange()用来产生0~4π范围内的数,步长为0.1。y是sin(x)函数的值,x和y的数值传给函数plt.plot()用于绘图,最后使用函数plt.show()来显示图形。运行上面的代码,会显示sin()函数的图形,如图1-8所示。

图1-8 sin()函数的图形

如果要绘制的图形稍微复杂一些,例如要绘制两个图形,且要标注x、y坐标和图形的标题等,来看下面这个例子。

   # 导入库函数
   import numpy as np
   import matplotlib.pyplot as plt
   # 生成数据
   x = np.arange(0, 4 * np.pi, 0.1)
   y_sin = np.sin(x)
   y_cos = np.cos(x)
   # 绘图
   plt.plot(x, y_sin)
   plt.plot(x, y_cos, ′--′)
   plt.xlabel(′x′)
   plt.ylabel(′y′)
   plt.title(′sin and cos′)
   plt.legend ([′sin′, ′cos′])
   plt.show()

上面的代码可以实现同时绘制两个图形,一个是sin()函数图形,另一个是cos()函数图形。在绘制cos()函数图形的时候,我们可以改变图形线条的类型,例如可以将图形线条由实线改为虚线。同时,也可以将图的标题、轴标签和每个数据对应的图像名称都标注出来。运行上面的代码,可显示sin()函数和cos()函数的图形,如图1-9所示。

图1-9 sin()函数和cos()函数的图形

2.绘制散点图

某些情况下,我们需要观察数据的分布情况,此时就需要绘制散点图。下面是一个绘制散点图的简单例子。

   import numpy as np
   import matplotlib.pyplot as plt
   # 生成数据
   x = np.random.randn(200)
   y = np.random.randn(200)
   # 绘制散点图
   plt.scatter(x, y)
   plt.show()

函数plt.scatter()用来绘制散点图。执行上面的语句,会生成如图1-10所示的散点图。

图1-10 散点图

如果数据集中包含不同类别的数据,那么绘制散点图的时候,可以根据不同的颜色或者形状进行区分,来看下面这个例子。

   import numpy as np
   import matplotlib.pyplot as plt
   # 生成数据
   x1 = np.random.randn(200)
   y1 = np.random.randn(200)
   x2 = np.random.randn(200) + 3
   y2 = np.random.randn(200) + 3
   # 绘制散点图
   plt.scatter(x1, y1, marker=′o′)
   plt.scatter(x2, y2, marker=′s′)
   plt.show()

函数plt.scatter()中的参数marker用来设置散点图中散点的形状,o代表圆形,s代表方形。执行上面的语句,会生成如图1-11所示的散点图。

图1-11 同时绘制两个散点图

3.显示图片

可以使用Matplotlib的image模块来读取图片文件,并使用pyplot模块里的函数imshow()显示图片。举例如下:

   import numpy as np
   import matplotlib.pyplot as plt
   from matplotlib.image import imread
   # 读取图片文件
   img = imread(′./datasets/ch01/cat.jpg′)
   # 显示图片
   plt.imshow(img)
   plt.show()

使用函数imread()的时候,要注意设置的图片路径是否正确。在本书配套的源代码中,图片python.jpg是存放在datasets文件夹下的ch01文件夹内的。在编写程序的时候,需要根据存放图片的位置设置正确的路径。执行上面的语句,会显示如图1-12所示的图片。

图1-12 显示图片