参考书籍《深度学习实战》杨云、杜飞著
第1章 深度学习的发展介绍
介绍
python是一种非常简单易学的解释性语言。由于强大的开源库支持(numpy,scipy,matplotlib),其广泛应用于科学计算中。如果你励志成为一名数据科学家或数据“攻城狮”,那python就是你必须学会的工具之一。接下来我们将简短的介绍下python, numpy, matplotlib的使用。本章教程内容主要参考于斯坦福大学cs228课程的python教程,详情可使用该网址查看 https://github.com/kuleshov/cs228-material/blob/master/tutorials/python/cs228-python-tutorial.ipynb
在本章中,你将逐步学习以下内容:
- Python基本使用: 基本数据类型(Containers, Lists, Dictionaries, Sets, Tuples), 函数, 类
- Numpy: 数组, 数组索引, 数据类型,数组运算, 广播
- Matplotlib: Plotting, Subplots, Images
Python基本用法
Python是一种面向对象的解释型高级编程语言。很多时候,由于其代码具有高可读性,且只需要数行代码就可以表达复杂的功能,Python看起来简直和伪代码一样。如下所示,为Python实现经典的快速排序算法例子:
def quicksort(arr):
if len(arr) <= 1:
return arr
pivot = arr[int(len(arr) / 2)]
left = [x for x in arr if x < pivot]
middle = [x for x in arr if x == pivot]
right = [x for x in arr if x > pivot]
return quicksort(left) + middle + quicksort(right)
print(quicksort([3,6,8,10,1,2,1]))
[1, 1, 2, 3, 6, 8, 10]
Python 版本
Python有两个差异较大的版本,分别是2.7+和3.4+。由于在3.0版本和2.7版本语言不兼容,因此2.7下的代码有时候在3.4下是行不通的。本教程中,我们使用的是3.8版本。你可以使用python --version命令。查看你所下载的python版本,确保你的python版本符合我们的教程需求。
基本数据类型
和大多数编程语言一样,Python拥有一系列的基本数据类型,比如整型、浮点型、布尔型和字符串等。这些基本数据类型的使用方式和其他语言的使用方式类似。
整型和浮点型
x = 3
print(x, type(x))
3 <class 'int'>
print(x + 1)
print(x - 1)
print(x * 2)
print(x ** 2)
4
2
6
9
x += 1
print(x)
x *= 2
print(x)
4
8
y = 2.5
print(type(y))
print(y, y + 1, y * 2, y ** 2)
<class 'float'>
2.5 3.5 5.0 6.25
print(y++)
File "C:\Users\23820\AppData\Local\Temp/ipykernel_11896/3264599002.py", line 2
print(y++)
^
SyntaxError: invalid syntax
布尔型
Python实现了所有的布尔逻辑,但使用的是英语单词(“and”,“or”,“not”)等,而不是我们习惯的操作符("&&","||"等)。
t, f = True, False
print(type(t))
<class 'bool'>
逻辑运算:
print(t and f)
print(t or f)
print(not t)
print(t != f)
False
True
False
True
字符串
hello = 'hello'
world = "world"
print(hello, len(hello),world)
hello 5 world
hw = hello + ' ' + world
print(hw)
hello world
hw12 = '%s %s %d' %(hello, world, 12)
print(hw12)
hello world 12
你也可以将字符串当作是一个对象,其有大量的方法。例如:
s = "hello"
print(s.capitalize())
print(s.upper())
print(s.rjust(7))
print(s.center(7))
print(s.replace('l', '(ell)'))
print(' wo rld '.strip())
Hello
HELLO
hello
hello
he(ell)(ell)o
wo rld
更多字符串操作请参考以下网址(https://docs.python.org/2/library/stdtypes.html#string-methods).
容器
Python有以下几种容器类型:列表(lists)、字典(dictionaries)、集合(sets)和元组(tuples)。
列表(Lists)
在Python中,列表相当于数组,但是列表长度可变,且能包含不同类型元素。
xs = [3, 1, 2]
print(xs, xs[2])
print(xs[-1])
[3, 1, 2] 2
2
xs[2] = 'foo'
print(xs)
[3, 1, 'foo']
xs.append('bar')
print(xs)
[3, 1, 'foo', 'bar']
x = xs.pop()
print(x, xs)
bar [3, 1, 'foo']
更多列表操作请参考以下网址(https://docs.python.org/2/tutorial/datastructures.html#more-on-lists).
切片(Slicing)
为了同时获取列表中的多个元素,Python提供了一种简洁的语法去访问子列表,这就是切片。
nums = list(range(5))
print(nums)
print(nums[2:4])
print(nums[2:])
print(nums[:2])
print(nums[:])
print(nums[:-1])
nums[2:4] = ['s','we']
print(nums)
[0, 1, 2, 3, 4]
[2, 3]
[2, 3, 4]
[0, 1]
[0, 1, 2, 3, 4]
[0, 1, 2, 3]
[0, 1, 's', 'we', 4]
循环(Loops)
我们可以这样遍历列表中的每一个元素:
animals = ['cat', 'dog', 'monkey']
for animal in animals:
print(animal)
cat
dog
monkey
如果想要在循环体内访问每个元素的指针,可以使用内置的枚举(enumerate)函数。注意:起始号0
animals = ['cat', 'dog', 'monkey']
for idx, animal in enumerate(animals):
print('#%d: %s' %(idx + 1, animal))
#1: cat
#2: dog
#3: monkey
列表解析(List comprehensions):
在编程的时候,我们常常想要将列表中的每一元素使用特定的表达式进行转换。下面是一个简单例子,将列表中的每个元素转换成它的平方。
nums = [0, 1, 2, 3, 4]
squares = []
for x in nums:
squares.append(x ** 2)
print(squares)
[0, 1, 4, 9, 16]
你可以使用更简单的列表解析(list comprehension):
nums = [0, 1, 2, 3, 4]
squares = [x ** 2 for x in nums]
print(squares)
[0, 1, 4, 9, 16]
列表解析也可以包含条件语句:
nums = [0, 1, 2, 3, 4]
even_squares = [x ** 2 for x in nums if x % 2 == 0]
print(even_squares)
[0, 4, 16]
字典(Dictionaries)
字典用来储存(键, 值)对,这和Java中的Map差不多。你可以这样使用它:
d = {'cat': 'cute', 'dog': 'furry'}
print(d['cat'])
print('cat' in d)
cute
True
d['fish'] = 'wet'
print(d['fish'])
wet
print(d['monkey'])
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_11896/3201959582.py in <module>
----> 1 print(d['monkey']) # KeyError: 'monkey' 键值没有在字典中
KeyError: 'monkey'
print(d.get('monkey', 'N/A'))
print(d.get('fish', 'N/A'))
N/A
wet
del d['fish']
print(d.get('fish', 'N/A'))
N/A
字典的详细用法请参考以下网址:(https://docs.python.org/2/library/stdtypes.html#dict).
迭代字典:
d = {'人': 2, '猫': 4, '蜘蛛': 8}
for animal in d:
legs = d[animal]
print(' %s 有 %d 腿' %(animal, legs))
人 有 2 腿
猫 有 4 腿
蜘蛛 有 8 腿
你也可以使用iteritems方法进行迭代:
d = {'人': 2, '猫': 4, '蜘蛛': 8}
for animal, legs in d.items():
print(' %s 有 %d 腿' %(animal, legs))
人 有 2 腿
猫 有 4 腿
蜘蛛 有 8 腿
字典解析(Dictionary comprehensions): 和列表解析类似,字典解析允许你轻松的构造字典,例如:
nums = [0, 1, 2, 3, 4]
even_num_to_square = {x: x ** 2 for x in nums if x % 2 == 0}
print(even_num_to_square)
{0: 0, 2: 4, 4: 16}
集合(Sets)
集合存放着无序的不同元素,在python中,集合使用花括号表示,如果将一个序列转换为集合,那该序列的重复元素将会被剔除,并且原有的顺序也将被打散。例如:
animals = {'cat', 'dog'}
print('cat' in animals)
print('fish' in animals)
True
False
animals.add('fish')
print('fish' in animals)
print(len(animals) )
True
3
animals.add('cat')
print(len(animals) )
animals.remove('cat')
print(len(animals) )
3
2
集合循环:虽然集合中的循环语法和列表中的一样,但由于集合是无序的,因此访问集合元素的时候,不能做关于顺序的假设:
animals = {'cat', 'dog', 'fish'}
for idx, animal in enumerate(animals):
print('#%d: %s' %(idx + 1, animal))
#1: dog
#2: fish
#3: cat
集合解析(Set comprehensions):和字典,列表一样,你可以很方便地使用集合解析构建集合:
from math import sqrt
print({int(sqrt(x)) for x in range(30)})
{0, 1, 2, 3, 4, 5}
元组(Tuples)
元组是一个(不可改变)有序列表。元组和列表在很多方面都很相似,最大的不同在于,元组可以像字典一样使用键/值对,并且还可以作为集合的元素,而列表不行。例如:
d = {(x, x + 1): x for x in range(10)}
t =(5, 6)
print(type(t))
print(d[t])
print(d[(1, 2)])
<class 'tuple'>
5
1
t[0] = 1
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_11896/2642684736.py in <module>
----> 1 t[0] = 1 # TypeError: 'tuple' object does not support item assignment
TypeError: 'tuple' object does not support item assignment
Functions
Python使用关键词def 来定义函数,例如:
def sign(x):
if x > 0:
return '正'
elif x < 0:
return '负'
else:
return '零'
for x in [-1, 0, 1]:
print(sign(x))
负
零
正
我们也经常使用可选参数来定义函数,例如:
def hello(name, loud=False):
if loud:
print('HELLO, %s' % name.upper())
else:
print('Hello, %s!' % name)
hello('Bob')
hello('Fred', loud=True)
Hello, Bob!
HELLO, FRED
类(Classes)
在python中,定义类的语法很直接:
class Greeter:
def __init__(self, name):
self.name = name
def greet(self, loud=False):
if loud:
print('HELLO, %s!' % self.name.upper())
else:
print('Hello, %s' % self.name)
g = Greeter('Fred')
g.greet()
g.greet(loud=True)
Hello, Fred
HELLO, FRED!
Numpy
Numpy是Python中用于科学计算的核心库。其提供了高性能的多维数组对象,以及相关工具。其用法和MATLAB相似,详情请参考如下网址:(http://wiki.scipy.org/NumPy_for_Matlab_Users)
要使用Numpy,首先要导入numpy 包:
import numpy as np
数组(Arrays)
numpy数组是由相同数据类型组成的网格,其可以通过非负整型的元组访问。数组维度数量也被称为数组的秩或阶(rank),数组的形状是一个由整型数构成的元组,描述数组不同维度上的大小。我们可以从python内嵌的列表中创建数组,然后利用方括号访问其中的元素:
a = np.array([1, 2, 3])
print(type(a), a.shape, a[0], a[1], a[2])
a[0] = 5
print(a)
<class 'numpy.ndarray'> (3,) 1 2 3
[5 2 3]
b = np.array([[1,2,3],[4,5,6]])
print(b)
[[1 2 3]
[4 5 6]]
print(b.shape)
print(b[0, 0], b[0, 1], b[1, 0])
(2, 3)
1 2 4
Numpy同样提供了大量的方法创建数组:
a = np.zeros((2,2))
print(a)
[[0. 0.]
[0. 0.]]
b = np.ones((1,2))
print(b)
[[1. 1.]]
c = np.full((2,2), 7,)
print(c)
[[7 7]
[7 7]]
d = np.eye(3)
print(d)
[[1. 0. 0.]
[0. 1. 0.]
[0. 0. 1.]]
e = np.random.random((3,3))
print(e)
[[0.9626719 0.66113208 0.71213143]
[0.04312188 0.54593384 0.61953381]
[0.03517364 0.11899646 0.29335206]]
数组索引
切片:和Python列表类似,numpy数组可以使用切片语法。因为数组可以是多维的,所以你必须为每个维度指定好切片:
import numpy as np
a = np.array([[1,2,3,4], [5,6,7,8], [9,10,11,12]])
print(a)
b = a[:2, 1:3]
print(b)
[[ 1 2 3 4]
[ 5 6 7 8]
[ 9 10 11 12]]
[[2 3]
[6 7]]
切取的子数组实际上是原数组的一份浅备份,因此修改子数组,原始数组也将受到修改,例如:
print('原始a:',a[0, 1] )
b[0, 0] = 77
print('修改b后的a:',a[0, 1] )
原始a: 2
修改b后的a: 77
你也可以混合整数索引以及切片索引访问数组,但是,这会生成一个秩少于原始数组的子数组。注意:这和MATLAB处理的数组切片有些不同。
a = np.array([[1,2,3,4], [5,6,7,8], [9,10,11,12]])
print(a)
[[ 1 2 3 4]
[ 5 6 7 8]
[ 9 10 11 12]]
Numpy有两种数组切片方式:1.混合整数索引和切片,生成低秩子数组;2.仅使用切片,生成原始数组同秩的子数组。
row_r1 = a[1, :]
row_r2 = a[1:2, :]
row_r3 = a[[1], :]
print('秩为1:',row_r1, row_r1.shape )
print('秩为2:',row_r2, row_r2.shape)
print('秩为2:',row_r3, row_r3.shape)
秩为1: [5 6 7 8] (4,)
秩为2: [[5 6 7 8]] (1, 4)
秩为2: [[5 6 7 8]] (1, 4)
col_r1 = a[:, 1]
col_r2 = a[:, 1:2]
print('秩为1:',col_r1, col_r1.shape)
print('秩为2:')
print(col_r2, col_r2.shape)
秩为1: [ 2 6 10] (3,)
秩为2:
[[ 2]
[ 6]
[10]] (3, 1)
整型数组索引:当我们使用切片索引数组时,得到的总是原数组的子数组。整型数组索引允许我们利用其它数组中的数据构建一个新的数组。例如:
a = np.array([[1,2], [3, 4], [5, 6]])
print(a[[0, 1, 2], [0, 1, 0]])
print(np.array([a[0, 0], a[1, 1], a[2, 0]]))
[1 4 5]
[1 4 5]
print(a[[0, 0], [1, 1]])
print(np.array([a[0, 1], a[0, 1]]))
[2 2]
[2 2]
整型数组索引的一个小技巧是从矩阵的每一行中选择或改变元素:
a = np.array([[1,2,3], [4,5,6], [7,8,9], [10, 11, 12]])
print(a)
b = np.array([0, 2, 0, 1])
print('使用数组[0, 2, 0, 1]索引矩阵a中的每一行')
print(a[np.arange(4), b])
print('使用数组[0, 2, 0, 1]索引矩阵a中的每一行,将其加10')
a[np.arange(4), b] += 10
print(a)
[[ 1 2 3]
[ 4 5 6]
[ 7 8 9]
[10 11 12]]
使用数组[0, 2, 0, 1]索引矩阵a中的每一行
[ 1 6 7 11]
使用数组[0, 2, 0, 1]索引矩阵a中的每一行,将其加10
[[11 2 3]
[ 4 5 16]
[17 8 9]
[10 21 12]]
布尔型数组索引:布尔型索引让你任意挑选数组中的元素,这种类型索引频繁的用于条件语句下的元素选取。例如:
import numpy as np
a = np.array([[1,2], [3, 4], [5, 1]])
print(a)
bool_idx =(a > 2)
print(bool_idx)
[[1 2]
[3 4]
[5 1]]
[[False False]
[ True True]
[ True False]]
print(a[bool_idx])
print(a[a > 2])
[3 4 5]
[3 4 5]
数据类型(Datatypes)
Numpy提供了大量的数据类型去构造数组,Numpy会尝试猜测你创建的数组的数据类型,但构造函数的数组通常也会可选择的显式指明其数据类型。例如:
x = np.array([1, 2])
y = np.array([1.0, 2.0])
z = np.array([1, 2], dtype=np.int64)
print(x.dtype, y.dtype, z.dtype)
int32 float64 int64
详细的数据类型说明,可参考如下网址:(http://docs.scipy.org/doc/numpy/reference/arrays.dtypes.html).
数组数学运算
数组中基本的数学运算操作是按数组元素进行的,并且重载操作以及函数都可以使用:
x = np.array([[1,2],[3,4]], dtype=np.float64)
y = np.array([[5,6],[7,8]], dtype=np.float64)
print(x + y)
print(np.add(x, y))
[[ 6. 8.]
[10. 12.]]
[[ 6. 8.]
[10. 12.]]
print(x - y)
print(np.subtract(x, y))
[[-4. -4.]
[-4. -4.]]
[[-4. -4.]
[-4. -4.]]
print(x * y)
print(np.multiply(x, y))
[[ 5. 12.]
[21. 32.]]
[[ 5. 12.]
[21. 32.]]
print(x / y)
print(np.divide(x, y))
[[0.2 0.33333333]
[0.42857143 0.5 ]]
[[0.2 0.33333333]
[0.42857143 0.5 ]]
print(np.sqrt(x))
[[1. 1.41421356]
[1.73205081 2. ]]
注意:和MATLAB不同,* 在numpy中是按元素乘,而在MATLAB中是矩阵乘。在numpy中我们使用dot函数计算向量内积(点积),矩阵乘矩阵,以及矩阵乘向量等操作。dot可以当作函数在numpy中使用,也可作为数组对象的实例方法:
x = np.array([[1,2],[3,4]])
y = np.array([[5,6],[7,8]])
v = np.array([9,10])
w = np.array([11, 12])
print(v.dot(w))
print(np.dot(v, w))
219
219
print(x.dot(v))
print(np.dot(x, v))
[29 67]
[29 67]
print(x.dot(y))
print(np.dot(x, y))
[[19 22]
[43 50]]
[[19 22]
[43 50]]
Numpy还提供了许多有用的数组计算函数;其中最常用的是 sum 函数:
x = np.array([[1,2],[3,4]])
print(np.sum(x))
print(np.sum(x, axis=0))
print(np.sum(x, axis=1))
10
[4 6]
[3 7]
更多numpy的数学函数参见如下网址:(http://docs.scipy.org/doc/numpy/reference/routines.math.html).
除了使用数组进行数学计算,我们还频繁的使用reshape或者其他方法操纵数组数据。例如:要转置一个矩阵,简单的使用数组对象的T属性即可:
print(x)
print(x.T)
[[1 2]
[3 4]]
[[1 3]
[2 4]]
v = np.array([[1,2,3]])
print(v)
print(v.T)
[[1 2 3]]
[[1]
[2]
[3]]
广播(Broadcasting)
广播提供了强大的机制允许numpy在不同形状的数组中执行数学操作。我们经常会遇到小数组和大数组相乘的情况,比如图片数据矩阵与权重矩阵。使用广播机制将提高你的代码质量以及运算效率。
例如,假设我们想在矩阵的每一行中都加上一个常数向量。我们可以这样做:
x = np.array([[1,2,3], [4,5,6], [7,8,9], [10, 11, 12]])
v = np.array([1, 0, 1])
y = np.empty_like(x)
for i in range(4):
y[i, :] = x[i, :] + v
print(y)
[[ 2 2 4]
[ 5 5 7]
[ 8 8 10]
[11 11 13]]
这样做是有效的,但当矩阵x 特别大时,在Python中计算显式循环就将变得非常缓慢。其实将向量v 加到矩阵x 的每一行相当于将向量v 拷贝多次垂直堆叠成矩阵vv ,然后对矩阵x 与矩阵vv 进行按元素求和。我们也可以这样实现该方法:
vv = np.tile(v,(4, 1))
print(vv)
[[1 0 1]
[1 0 1]
[1 0 1]
[1 0 1]]
y = x + vv
print(y)
[[ 2 2 4]
[ 5 5 7]
[ 8 8 10]
[11 11 13]]
Numpy广播机制允许我们不创建多次向量v备份的情况下执行该计算:
import numpy as np
x = np.array([[1,2,3], [4,5,6], [7,8,9], [10, 11, 12]])
v = np.array([1, 0, 1])
y = x + v
print(y)
[[ 2 2 4]
[ 5 5 7]
[ 8 8 10]
[11 11 13]]
由于广播机制的原因,即使x 的形状为(4, 3) ,v 的形状为(3,) ,表达式y = x + v 依然可以执行;这就好像将v 拷贝重塑为(4, 3) 的矩阵,然后进行按元素相加。 对两个数组使用广播机制要遵守下列规则:
- 如果数组的秩不同,将秩较小的数组进行扩展,直到两个数组的尺寸长度都一样。
- 如果两个数组在某个维度上的长度是一样的,或者其中一个数组在该维度上长度为1,那么我们就说这两个数组在该维度上是相容的。
- 如果两个数组在所有维度上都是相容的,他们就能使用广播。
- 广播之后,两个数组的尺寸将和较大的数组尺寸一样。
- 在任何一个维度上,如果一个数组的长度为1,另一个数组长度大于1,那么在该维度上,就好像是对第一个数组进行了复制。
如果感觉没有解释清楚,详细文档请参考以下网址:(http://docs.scipy.org/doc/numpy/user/basics.broadcasting.html) 或者更具体的解释(http://wiki.scipy.org/EricsBroadcastingDoc).
支持广播机制的函数也被称为通用函数(universal functions)。你可以更具以下网址查看所有的通用函数(http://docs.scipy.org/doc/numpy/reference/ufuncs.html#available-ufuncs).
以下是广播一些应用:
v = np.array([1,2,3])
w = np.array([4,5])
print(np.reshape(v,(3, 1)) * w)
[[ 4 5]
[ 8 10]
[12 15]]
x = np.array([[1,2,3], [4,5,6]])
print(x + v)
[[2 4 6]
[5 7 9]]
print((x.T + w).T)
[[ 5 6 7]
[ 9 10 11]]
print(x + np.reshape(w,(2, 1)))
[[ 5 6 7]
[ 9 10 11]]
print(x * 2)
[[ 2 4 6]
[ 8 10 12]]
广播使你的代码简洁而高效,因此你应该尽可能的使用广播操作。
以上仅仅是一些numpy的重要用法,但其功能远不止这些。详细的文档请参考如下网址:(http://docs.scipy.org/doc/numpy/reference/)
Matplotlib
Matplotlib是一个绘图工具库。下面我们简短的介绍下matplotlib.pyplot 模块,其用法和MATLAB相似。
import matplotlib.pyplot as plt
%matplotlib inline
绘制(Plotting)
matplotlib 最重要的函数就是绘制函数plot,其允许你绘制2D数据。例如:
x = np.arange(0, 3 * np.pi, 0.1)
y = np.sin(x)
plt.plot(x, y)
[<matplotlib.lines.Line2D at 0x20d10089b00>]
? ?
添加标题,说明,坐标轴标记到图表中:
x = np.arange(0, 3 * 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 axis label')
plt.ylabel('y axis label')
plt.title('Sine and Cosine')
plt.legend(['Sine', 'Cosine'])
<matplotlib.legend.Legend at 0x20d13595978>
? ?
子图(Subplots)
你可以使用subplot函数在一副图中绘制不同的子图。例如:
x = np.arange(0, 3 * np.pi, 0.1)
y_sin = np.sin(x)
y_cos = np.cos(x)
plt.subplot(2, 1, 1)
plt.plot(x, y_sin)
plt.title('Sine')
plt.subplot(2, 1, 2)
plt.plot(x, y_cos)
plt.title('Cosine')
plt.show()
? ?
更多内容你可以参考如下网址:(http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.subplot).
import numpy as np
from scipy.misc import imresize
from imageio import imread
import matplotlib.pyplot as plt
img = imread('kitten.jpg')
img_tinted = img * [1, 0.95, 0.6]
plt.subplot(1, 2, 1)
plt.imshow(img)
plt.subplot(1, 2, 2)
plt.imshow(np.uint8(img_tinted))
plt.show()
|