一、学习任务
第一章:预备知识:Python基础,Numpy基础,练习
二、学习笔记
理论内容:
1.列表推导式与条件赋值
这部分的要点是关于列式推导式的学习。它是推导式中其中一种。
Python 推导式是从一个数据序列构建另一个新的数据序列的结构体。支持各种数据结构的推导式:
- 列表(list)推导式
- 字典(dict)推导式
- 集合(set)推导式
- 元组(tuple)推导式
列表推导式格式为:
[表达式 for 变量 in 列表] [out_exp_res for out_exp in input_list] 或者 [表达式 for 变量 in 列表 if 条件] [out_exp_res for out_exp in input_list if condition]
- out_exp_res:列表生成元素表达式,可以是有返回值的函数。
- for out_exp in input_list:迭代 input_list 将 out_exp 传入到 out_exp_res 表达式中。
- if condition:条件语句,可以过滤列表中不符合条件的值。
2.匿名函数与map方法
当我们在传入函数时,有些时候,不需要显式地定义函数,直接传入匿名函数更方便。
在Python中,对匿名函数提供了有限支持。还是以map()
函数为例,计算f(x)=x2时,除了定义一个f(x)
的函数外,还可以直接传入匿名函数:
>>> list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
[1, 4, 9, 16, 25, 36, 49, 64, 81]
通过对比可以看出,匿名函数lambda x: x * x
实际上就是:
def f(x):
return x * x
关键字lambda
表示匿名函数,冒号前面的x
表示函数参数。
匿名函数有个限制,就是只能有一个表达式,不用写return
,返回值就是该表达式的结果。
用匿名函数有个好处,因为函数没有名字,不必担心函数名冲突。此外,匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数:
>>> f = lambda x: x * x
>>> f
<function <lambda> at 0x101c6ef28>
>>> f(5)
25
同样,也可以把匿名函数作为返回值返回,比如:
def build(x, y):
return lambda: x * x + y * y
3.zip对象与enumerate方法
zip()
是Python的一个内建函数,用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表。
如果各个迭代器的元素个数不一致,则返回列表长度与最短的对象相同,利用 * 号操作符,可以将元组解压为列表。
zip 语法:
zip([iterable, ...])
参数说明:
- iterabl -- 一个或多个迭代器;
返回元组列表。
为了深入了解,实践一个例子,如下:
4.np数组的构造
这一部分教程已经写的非常通俗易懂了。最一般的方法是通过array
来构造。还有一些特殊数组的生成方式。比如:等差序列:np.linspace
, np.arange,
特殊矩阵:zeros
, eye
, full,
随机矩阵:np.random.以及讲解的
np数组的变形与合并,np数组的切片与索引很容易理解。
5.广播机制
这一部分很重要。本学习只讨论了不超过两维的数组广播机制。所以详细记录。
Broadcasting描述了 numpy 如何在算术运算期间处理具有不同形状的数组。受某些约束的影响,较小的数组在较大的数组上“广播”,以便它们具有兼容的形状。广播提供了一种矢量化数组操作的方法,以便在C而不是Python中进行循环。它可以在不制作不必要的数据副本的情况下实现这一点,通常导致高效的算法实现。然而,有些情况下广播是一个坏主意,因为它会导致内存使用效率低下,从而减慢计算速度。
在两个数组上运行时,NumPy会逐元素地比较它们的形状。它从尾随尺寸开始,并向前发展。两个尺寸兼容时
- 他们是平等的,或者
- 其中一个是1
如果不满足这些条件,则抛出 ValueError: operands could not be broadcast together
异常,指示数组具有不兼容的形状。结果数组的大小是沿输入的每个轴不是1的大小。
数组不需要具有相同 数量 的维度。例如,如果您有一个256x256x3
RGB值数组,并且希望将图像中的每种颜色缩放不同的值,则可以将图像乘以具有3个值的一维数组。根据广播规则排列这些数组的尾轴的大小,表明它们是兼容的:
Image (3d array): 256 x 256 x 3
Scale (1d array): 3
Result (3d array): 256 x 256 x 3
当比较的任何一个尺寸为1时,使用另一个尺寸。换句话说,尺寸为1的尺寸被拉伸或“复制”以匹配另一个尺寸。
在以下示例中,A
和B
数组都具有长度为1的轴,在广播操作期间会扩展为更大的大小:
A (4d array): 8 x 1 x 6 x 1
B (3d array): 7 x 1 x 5
Result (4d array): 8 x 7 x 6 x 5
以下是一些例子:
A (2d array): 5 x 4
B (1d array): 1
Result (2d array): 5 x 4
A (2d array): 5 x 4
B (1d array): 4
Result (2d array): 5 x 4
A (3d array): 15 x 3 x 5
B (3d array): 15 x 1 x 5
Result (3d array): 15 x 3 x 5
A (3d array): 15 x 3 x 5
B (2d array): 3 x 5
Result (3d array): 15 x 3 x 5
A (3d array): 15 x 3 x 5
B (2d array): 3 x 1
Result (3d array): 15 x 3 x 5
以下是不广播的形状示例:
A (1d array): 3
B (1d array): 4 # trailing dimensions do not match
A (2d array): 2 x 1
B (3d array): 8 x 4 x 3 # second from last dimensions mismatched
实践中广播的一个例子:
>>> x = np.arange(4)
>>> xx = x.reshape(4,1)
>>> y = np.ones(5)
>>> z = np.ones((3,4))
>>> x.shape
(4,)
>>> y.shape
(5,)
>>> x + y
ValueError: operands could not be broadcast together with shapes (4,) (5,)
>>> xx.shape
(4, 1)
>>> y.shape
(5,)
>>> (xx + y).shape
(4, 5)
>>> xx + y
array([[ 1., 1., 1., 1., 1.],
[ 2., 2., 2., 2., 2.],
[ 3., 3., 3., 3., 3.],
[ 4., 4., 4., 4., 4.]])
>>> x.shape
(4,)
>>> z.shape
(3, 4)
>>> (x + z).shape
(3, 4)
>>> x + z
array([[ 1., 2., 3., 4.],
[ 1., 2., 3., 4.],
[ 1., 2., 3., 4.]])
广播提供了一种方便的方式来获取两个数组的外积(或任何其他外部操作)。以下示例显示了两个1-d数组的外积操作:
>>> a = np.array([0.0, 10.0, 20.0, 30.0])
>>> b = np.array([1.0, 2.0, 3.0])
>>> a[:, np.newaxis] + b
array([[ 1., 2., 3.],
[ 11., 12., 13.],
[ 21., 22., 23.],
[ 31., 32., 33.]])
这里 newaxis
索引操作符插入一个新轴 a
,使其成为一个二维 4x1
数组。将 4x1
数组与形状为 (3,)
的 b
组合,产生一个4x3
数组。
通常只在对多个数组进行对应元素操作形状不同时,才会发生广播。
那什么是对应元素进行操作呢?比如:
a = np.array([1,2,3])
b = np.array([2,2,2])
a*b # a和b对应元素相乘
# a*b的结果是: [1*2,2*2,3*2]
'''
np.dot(a,b) # 这就不是对应元素操作,这是矩阵相乘。
# np.dot(a,b)的结果是a,b的点积。
'''
什么叫做形状不同呢?
a = np.array([1,2,3])
b = 2
a*b #a是1维向量,b是标量,这就是形状不同
# 结果也是:[1*2,2*2, 3*2]
'''
这是因为发生了广播。b被填充为[2,2,2]
然后a*b的效果变成了,[1,2,3]*[2,2,2]
'''
前面的两个例子输入不同但运行结果相同的原因就是发生的广播(broadcast)。
可以广播的几种情况:
1. 两个数组各维度大小从后往前比对均一致
举个例子:
A = np.zeros((2,5,3,4))
B = np.zeros((3,4))
print((A+B).shape) # 输出 (2, 5, 3, 4)
A = np.zeros((4))
B = np.zeros((3,4))
print((A+B).shape) # 输出(3,4)
举个反例:
A = np.zeros((2,5,3,4))
B = np.zeros((3,3))
print((A+B).shape)
报错:
ValueError: operands could not be broadcast together with shapes (2,5,3,4) (3,3)
为啥呢?因为最后一维的大小A是4,B是3,不一致。
2.两个数组存在一些维度大小不相等时,有一个数组的该不相等维度大小为1.
这是对上面那条规则的补充,虽然存在多个维大小不一致,但是只要不相等的那些维有一个数组的该大小是1就可以。
举个例子:
A = np.zeros((2,5,3,4))
B = np.zeros((3,1))
print((A+B).shape) # 输出:(2, 5, 3, 4)
A = np.zeros((2,5,3,4))
B = np.zeros((2,1,1,4))
print((A+B).shape) # 输出:(2, 5, 3, 4)
A = np.zeros((1))
B = np.zeros((3,4))
print((A+B).shape) # 输出(3,4)
# 下面是报错案例
A = np.zeros((2,5,3,4))
B = np.zeros((2,4,1,4))
print((A+B).shape)
ValueError: operands could not be broadcast together with shapes (2,5,3,4) (2,4,1,4)
为啥报错?因为A和B的第2维不相等。并且都不等于1.
练习:
Ex1:利用列表推导式写矩阵乘法
一般的矩阵乘法根据公式,可以由三重循环写出,请将其改写为列表推导式的形式。
这个题目将中间的三层 for循环用列表推导式的形式表示出来。
res = [[sum([M1[i][k] * M2[k][j] for k in range(M1.shape[1])]) for j in range(M2.shape[1])] for i in range(M1.shape[0])]
A = np.arange(1,10).reshape(3,-1) B = A*(1/A).sum(1).reshape(-1,1) B
Ex5:连续整数的最大长度
输入一个整数的Numpy
数组,返回其中严格递增连续整数子数组的最大长度,正向是指递增方向。例如,输入[1,2,5,6,7],[5,6,7]为具有最大长度的连续整数子数组,因此输出3;输入[3,2,1,2,3,4,6],[1,2,3,4]为具有最大长度的连续整数子数组,因此输出4。请充分利用Numpy
的内置函数完成。(提示:考虑使用nonzero, diff
函数)
f = lambda x:np.diff(np.nonzero(np.r_[1,np.diff(x)!=1,1])).max() f([1,2,5,6,7]) f([3,2,1,2,3,4,6])