目录
前言 为什么引入numpy模块
列表类占用的内存数倍于数据本身占用的内存,Python自带的列表类会储存每一个元素的数据信息,数据类型信息,数据大小信息等。这是因为Python语言是一种可以随时改变变量类型的动态类型语言,而C语言和Fortran语言是静态类型语言,静态类型语言一般会在建立变量前先定义变量,并且不可以修改变量的变量类型。总的来说,numpy模块有以下两个优点:
1. 节约内存。numpy模块创建的列表(实际上是一个ndarray对象)中的所有元素将会是同一种变量类型的元素,所以即使创建了一个规模非常大的矩阵,也只会对变量类型声明一次,大大的节约内存空间。
2. 内置函数。numpy中也提供了许多科学计算的函数和常数供用户使用。
第一章 numpy模块介绍
Part1:模块常数
pi | 圆周率 |
---|---|
e | 自然常数 |
int_ | 32bit有符号整型类 |
float64 | Python自带的最高精度的浮点数类 |
complex128 | Python自带的最高精度的复数类 |
__version__ | 模块的版本号 |
Part2:模块函数-创造矩阵
通常在使用模块前需要导入模块,会将numpy设置一个np的别名:
import numpy as np
(一)array()
input: [list]; default: dtype; return: <class ‘ndarray’>.
必须输入一个列表,如果列表中的每个元素都是一个数,那么返回的是一个ndarray类型的向量;如果列表中的每个元素都是同维度的列表(也可以是元组),那么返回的是一个矩阵;如果输入的列表中的列表的每个元素都是同维度的列表(也可以是元组),那么返回的是一个张量。缺省参数dtype可以取为np.int_, np.float64, np.complex128, 使用该缺省参数可以尽可能地避免数据存储出错或者精度不够的现象出现。例如默认的整数类型(占用两个字节,即16bit)只能存储-32767~32768的有符号整数或者是0~65536的无符号整数。
a = np.array([[1, 2], [3, 5]], dtype=np.float64)
print('a = \n', a)
print('a 的数据类型:', type(a))
##### 输出如下 #####
a =
[[1. 2.]
[3. 5.]]
a 的数据类型: <class 'numpy.ndarray'>
(二)zeros()
input: [int] or [tuple]; return: <class ‘ndarray’>.
创造全零矩阵。输入一个正整数或者是一个元组,如果输入的是一个正整数,则会创造一个长度为该正整数的行向量。如果输入的是元组,则要求每个元组中的数都应该是正整数。例如输入(3, 2, 4),则会生成一个行为3,列为2,高为4的张量。
(三)ones()
input: [int] or [tuple]; return: <class ‘ndarray’>.
创造全一矩阵。规则与zeros()函数相同。
(四)random.random()
input: [int] or [tuple]; return: <class ‘ndarray’>.
创造一个随机矩阵,每个元素的值从满足0≤x<1。规则与zeros()函数相同。
(五)arange()
input: [int]; return: <class ‘ndarray’>.
创造一个从0开始,以1为步长的行向量。必须输入一个正整数,该正整数表示生成的行向量的长度。例如输入5,则会生成[0, 1, 2, 3, 4]的行向量。
a = np.random.random((2, 4))
b = np.arange(6)
print('生成的随机矩阵 a =\n', a)
print('序列型行向量 b =', b)
##### 输出如下 #####
生成的随机矩阵 a =
[[0.92728718 0.27852619 0.42024985 0.75553709]
[0.53544035 0.88595461 0.23343352 0.62368876]]
序列型行向量 b = [0 1 2 3 4 5]
(六)zeros_like()
input: [<class ‘ndarray’>]; return: <class ‘ndarray’>.
创造一个与输入矩阵同维度的全零矩阵。输入的是一个ndarray类型的矩阵。
(七)ones_like()
input: [<class ‘ndarray’>]; return: <class ‘ndarray’>.
创造一个与输入矩阵同维度的全一矩阵。输入的是一个ndarray类型的矩阵。
Part3:模块函数-处理矩阵
(一)einsum()
input: [str], [ndarray]; default: [ndarray], [ndarray]; return: <class ‘ndarray’>.
爱因斯坦求和函数,功能强大,使用复杂。输入的第一个参数是一个字符串,表明要进行的操作。第二个参数要求是一个’ndarray’类型的矩阵。第三、四个输入参数为缺省参数,如果不缺省的话,也需要是’ndarray’类型的矩阵。下表是einsum函数的三个例子:
einsum(‘ij -> ji’, Mat) | 对矩阵Mat求转置并返回 |
---|---|
einsum(‘ij -> i’, Mat) | 对矩阵Mat每行求和并返回 |
einsum(‘ij -> j’, Mat) | 对矩阵Mat每列求和并返回 |
(二)np.add.at()
input: [ndarray], [ndarray or list], [ndarray]; return: <class ‘ndarray’>.
累加函数,可以用于单元刚度矩阵组装总刚度矩阵。输入的第一个参数是被加的’ndarray’类型的被加矩阵,在组装过程中,这里放的就是我们的总刚度矩阵。第二个参数要求是一个’ndarray’类型的矩阵或者是列表类,里面存放的是增值索引,显然索引值应该全为整数。第三个输入参数为与增值索引同维度的’ndarray’类型的矩阵或者是列表类,里面存放的是与增值索引相对应的增值。该函数与 被加矩阵[索引] += 值 是有区别的。后者中的增值索引如果有重复的索引,则所有相同索引中的最后的索引会生效,而前者利用累加函数则会将所有的重复索引对应的值累加到被加矩阵该索引处。
a = index = [2, 2, 1]
value = np.array([2.5, 7.5, 3.], dtype=np.float64)
a = np.zeros(6)
print('初始:a =', a)
np.add.at(a, index, value)
print('使用累加函数后:a =', a)
a = np.zeros(6)
a[index] += value
print('使用索引加法赋值后:a =', a)
##### 输出如下 #####
初始:a = [0. 0. 0. 0. 0. 0.]
使用累加函数后:a = [ 0. 3. 10. 0. 0. 0.]
使用索引加法赋值后:a = [0. 3. 7.5 0. 0. 0. ]
第二章 ndarray类
ndarray类是numpy模块中最重要的一个类,几乎所有的操作都是围绕着ndarray展开的。上一节中生成的矩阵实际上都属于ndarray类,而生成的向量,矩阵,张量等称为ndarray对象。为了表述方便,在不引起歧义时,我们下面将ndarray对象称为矩阵对象或是矩阵(也包含张量,向量或者是单个的数)。值得注意的是,这类矩阵在内存中的存储方式是按行存储,意思是每一行的内存位置是相邻的,而Matlab与Fortran中的矩阵是按列存储的,因此在Python中按行遍历的运行速度比按列遍历的运行速度要快(至于快多少与矩阵大小和实际情况有关),而Matlab和Fortran中则尽量按列遍历。下面给出矩阵对象具有的索引,属性和方法。
Part1:索引
索引方法分为四种,分别是逐个索引,切片索引,布尔索引,神奇索引。在Matlab中也有与之相对应的索引方式,最明显的差异有三个:一是numpy矩阵对象的索引使用的是[],而Matlab使用的是();二是在逐个索引方面,numpy矩阵对象的索引通过负整数对矩阵进行倒序索引,而Matlab则通过end关键字完成倒序索引且不允许索引中出现负数;三是Python中的索引均从0开始计数,而Matlab则是从1开始计数。
与Matlab最大的区别就是,当矩阵对象利用索引生成一个新的矩阵时,不会产生大量的内存,因为它只会把索引区域对应的内存位置赋值给了这个新的变量,我们常常将这个变量称之为视图。当我们将视图进行改变,系统会根据其内存位置将储存的值进行改变,即会把最原始的矩阵对象改变。如果我们想要避免这个错误,需要在相应的地方使用.copy()方法,在本节最后我们将介绍视图的一个例子。
(一)逐个索引
向量Vec[i],矩阵Mat[i, j] or Mat[i][j],张量Tensor[i, j, k] or Tensor[i][j][k]
这里的i,j,k分别表示矩阵的行宽高。以向量为例,i = 0时将返回向量的第一个数,i = 2时将返回向量的第三个数,如果索引值大于等于向量长度,则会报错。i = -1时将返回向量的倒数第一个数,i = -4将返回向量的倒数第四个数。
(二)切片索引
格式规范与逐个索引相同。利用(start):(stop)(:step)均可以对行列高进行切片,起始值省略时默认取0,终止值省略时默认遍历到最后一行,步长省略时默认步长为1。start,stop和step均被要求为整数(规则与逐个索引的规则相同),切片i满足:start≤i<stop。我们以矩阵为例,Mat[0:2, :]将会生成一个Mat矩阵前两行的矩阵视图。
(三)布尔索引
Mat[<class ‘ndarray’>]
索引要求是一个矩阵对象,内部的元素均为布尔类型变量,且与Mat的维度相同。这样的索引,会把所有索引值为True的地方取出Mat的值,按行汇总后返回一个行向量视图。最常用的方法是取出矩阵中具有某种特征的所有数,例如取出大于0.5的所有元素:Mat[Mat > .5]。
(四)神奇索引
Mat[<class ‘ndarray’>]
索引要求是一个矩阵对象,内部的元素均为整数类型。例如对于向量Mat[[1, 2, 3]]将生成一个行向量视图,里面依次是Mat的第二个数,第三个数,第四个数。
另外,矩阵对象和Matlab中的矩阵是有所区别的,区别如下:
# Python
a = np.array([(1, 2, 3), (4, 5, 6), (7, 8, 9)])
print(a[[0, 1], [0, 2]]) # 将会输出位于(0, 0)和(1, 2)的两个数
##### 输出如下 #####
[1 6]
% Matlab
a = [1, 2, 3; 4, 5, 6; 7, 8, 9];
a([1, 2], [1, 3]) # 将会输出第一、二行,第一、三列的行列子式
%%%%% 输出如下 %%%%%
ans = 1 3
4 6
Part2:属性
(一).data
内存位置
(二).shape
以元组方式储存的矩阵大小。如果输入的是一个向量,则返回只含有长度一个值的元组。如果输入的是一个矩阵,则返回行数和列数构成的元组。如果输入的是张量,则返回行数,列数和层数构成的元组。
(三).strides
以元组方式储存的内存大小(单位为字节,即8bit)。如果输入的是一个向量,则返回单个数占用的内存大小。如果输入的是一个矩阵,则返回两个数构成的元组,第一个数是每一行占用的内存大小,第二个数是每一数占用的内存大小。如果输入的是一个张量,则返回三个数构成的元组,第一个数是每一层占用的内存大小,第二个数是每一层中,每一行占用的内存大小,第三个数是每一个数占用的内存大小。
附录
Part1:视图
视图是Python语法中的一个基础规则,它不仅仅适用于numpy模块,还适用于数值对象,列表对象,字典对象。其原理是赋值语句的作用不像是Matlab赋值语句那样把值进行了一个“复制粘贴”,而是把内存地址进行了一次“复制粘贴”。Python索引是一个视图,下面以索引为例给出视图的一个例子:
a = np.array([[0, 7], [2, 6]], dtype=np.int_)
b = a[:, 1]
print('变量b的内存地址为:', id(b))
b[:] = [5, 8]
print('变量b的内存地址为:', id(b))
print('通过对视图b的改变,a变为\n', a)
b = [7, 6]
print('因为对b使用了赋值语句,此时b的内存地址为:', id(b))
print('b不再是a的视图,a变为\n', a)
##### 输出如下 #####
变量b的内存地址为: 32354672
变量b的内存地址为: 32354672
通过对视图b的改变,a变为
[[0 5]
[2 8]]
因为对b使用了赋值语句,此时b的内存地址为: 59451912
b不再是a的视图,a变为
[[0 5]
[2 8]]
Part2:ndarray对象的广播
两个矩阵做加法,要求两个矩阵行列数相同,然后对应元素相加。但是对于矩阵对象而言,不会那么严格,例如一个矩阵加上一个数,系统会将这个数临时扩充为与该矩阵同行同列的矩阵,然后再进行对应元素相加的操作。亦或是行向量加上矩阵,这要求行向量的列数等于矩阵的列数,做加法运算时,系统会将该行向量临时扩充为与该矩阵同行数的矩阵。如果是行向量加列向量,系统会将行向量的列数临时扩充为列向量的行数,还会将列向量的行数临时扩充为行向量的列数,例子如下:
a = np.array([1, 1])
b = np.array([[1], [2]])
print('行向量:', a, '\n列向量:\n', b)
print('行向量和列向量之和:\n', a + b)
##### 输出如下 #####
行向量: [1 1]
列向量:
[[1]
[2]]
行向量和列向量之和:
[[2 2]
[3 3]]