生物信息学主要是处理生物数据。AI for Bioinformatics主要是利用AI处理生物数据,从而揭示这些数据中的生物学意义。通常,我们需要做两件重要的事:
(1)获取数据;
(2)将数据读入计算机后对其进行处理。
为了能够完成各种数据操作,我们需要某种方法来存储和操作数据。 如果没有某种方法来存储数据,那么获取数据是没有意义的。PyTorch和TensorFlow这两个主流的AI框架都使用Tensor来完成这个操作。
Tensor是神经网络学习过程中最核心概念之一,需要深刻理解!
2 什么是Tensor
深度学习的基础是神经网络,事实上,神经网络最基本的数据结构就是张量(Tensor),神经网络的输入也是张量。因此,各种深度学习框架都是使用张量,也就是n维数组来存储数据的。无论使用哪个深度学习框架,它的张量类(在MXNet中为ndarray, 在PyTorch和TensorFlow中为Tensor)都与Numpy的ndarray类似。 但深度学习框架又比Numpy的ndarray多一些重要功能:
首先,GPU很好地支持加速计算,而NumPy仅支持CPU计算;
其次,张量类支持自动微分。 这些功能使得张量类更适合深度学习。
如果没有特殊说明,我们后面所说的张量均指的是张量类的实例。可以说,Tensor可以看成是能在GPU中计算的矩阵。
PyTorch和TensorFlow一样,其实就是一个计算工具,借助它们我们能借助计算机完成复杂的计算流程。
2.1 Tensor的含义
我们研究的世界一般用两种数学模型来描述:
经典世界模型——微积分
量子世界模型——线性代数
自然界中的经典运动都是连续的,但是量子运动是不连续的。微积分擅长经典世界的运动规律;线性代数擅长量子世界的运动规律。
1)数据与空间
在一维的世界里,整个空间就是一条直线,假设里面生活了一只虫子,那么虫子永远只知道前后,不知道左右;在二维世界里,空间是一个平面,里面生活的虫子除了前后移动,还可以左右移动,但是在我们三维世界的眼光看来,它们并不知道头顶上有天、脚下有地(上下)——二维世界的生物是不知道什么叫顶天立地的!比如,二维生物看一个球体是一个园。
问题思考:
有一张报纸,上面有一只蚂蚁。我们把蚂蚁君看作是“二维生物”,在二维的纸面上移动。如果要让它从纸的一边(A)爬到另一边(B),则蚂蚁君需要走过整个纸张。
但是我们把这张纸卷起来呢?成为一个圆柱,一个三维空间里的物体;这时蚂蚁君只需要走过接缝的位置,就到达了目的地。(对了!这就是传说中的虫洞)换句话说,把二维空间弯曲,就得到了三维空间。
四维比三维多一维,它是什么?是时间!四维生物可以从现在穿越到过去,也可以穿越到未来,实现了时间上的自由。那么在现实当中我们可以穿越到过去和未来吗?
不能!
因为我们是三维生物,活在三维空间中。二维生物只能看到三维物体的截面一样,我们作为三维生物,也只能看到四维空间的截面,即三维空间,也就是现在的你、我、他;换句话说就是此时此刻的世界。因此,珍惜当下特别重要!
问题:是否存在五维空间?
我不知道也想象不到,正如“夏虫不可语冰”!
在数学上,空间有严格的定义,本质上是指满足一定条件的数据集合。也存在各种各样不同的空间,但最常用也最符合我们理解真实世界直觉的是欧几里得空间(Euclidean Space),简称欧式空间。引入了笛卡尔坐标系(Cartesian Coordinate System),也称直角坐标系。
0维 | 1维 | 2维 | 3维 | 4维 |
点 | 线 | 面 | 体 | 超体 |
静止 | 0维运动 | 1维运动 | 2维运动 | 3维运动 |
1个顶点;既没有空间,也没有时间。 | 2个顶点、1线;1维空间只有长度。 | 4顶点、4线、1面;2维空间有长度和宽度。 | 8顶点、12线、6面、1体;3维度空间有长度、宽度和高度。 | 16顶点、32线、24面、8体、1超体;4维空间除了长、宽、高空间外,还有时间。 |
2)几个相关的概念
标量(Scalar) | 一个数,如1 | 0维的张量 |
向量(Vector) | 一串数(有顺序),如(1,2) | 1维的张量,具有1个轴的张量 |
矩阵(Matrix) | 二维数组,H * W | 2维的张量,具有2个轴的张量 |
张量(Tensor) | H * W *C(*D),用张量来描述任意维度的物体 | n维的张量,具有2个轴以上的张量没有特殊的数学名称 |
- 向量(Vector)
从数据角度看,向量是有序排列的一串数;是1维数组,1维张量。
图例:向量的坐标表示,即把起点放在原点,通过终点的坐标来表示。向量x表示为x(x1,x2)。
对于二维平面来说,我们通常把横轴成为X轴,纵轴称为Y轴,为了不失一般性,我们也可以将它们称为第一轴(记为X1轴)和第二轴(记为X2轴)。对于二维平面上的点来说,其坐标包含2个值,实际上就是对应于X1轴和X2轴上的2个刻度。
超过三维的n维空间我们想象不出来,但是类比可以知道其中的点的坐标一定包含n个值,我们用向量来进行研究。即由n个实数x1,x2,x3,…,xn组成的一个数组x称为向量,写作:
或者
n称为向量的维度,表示向量中元素的个数。
向量默认为列的形式,右上角的撇(’)表示转置(Transpose),也就是旋转为行的形式。
列向量与行向量并没有本质上的不同,只是书写顺序的区别,是我们人为规定的,表示一些特殊的含义:
列向量:对象(object)——坐标、属性
行向量:方法(method)——权重、方向
另一个需要注意的是向量具有大小和方向。
直观上看向量箭头的长度就是这个向量的大小,用|x |表示,复习一下线性代数就可以了。
- 矩阵(Matrix)
矩阵本质上是一个数表。
矩阵就是2维数组,数组的横向排列称为行(row),纵向排列称为列(column)。从向量角度出发,向量就是矩阵,矩阵就是向量!
矩阵是进化了的向量,它是向量的向量,是列向量的行向量/行向量的列向量。
矩阵的表示:
在我读研究生的时候看过一部科幻片《黑客帝国》,其英文原名就叫The Matrix,里面的 “母体”(Matrix)是机器人统治人类的系统,人类的身体泡在营养液里,而脑部与母体系统连接,思维活在这个虚拟的世界而不自知,我第一次看的时候非常震撼。后面再看到《变形金刚》的时候,看到里面有一个“能源宝”(Matrix of Leadership),也翻译为“领导模块”,是汽车人领导人的身份证明和力量来源。在这些电影和电视剧中如此重要的Matrix一词来自母体,是英国数学家James Joseph Sylvester于1850年用它来命名我们今天所熟知的“矩阵”,至此Matrix这个词就深入到数学应用的各个领域。
我突然想到:
老子的道是什么?
道是不是就是矩阵?
是不是可以把道的英文名称直接翻译成Matrix?
道德经的翻译最初是way、virtue、classic、目前主要的英文翻译是tao(音译),德国海德格尔说,“老子的‘道‘’比西方人讲的“理性”、“精神”的意义等更根本”。确实,比起origin,matrix的含义明显更丰富,不仅包含“”先天地生"、“道生万物“”的含义(起源性),同时具备“可为天地母”(母体性,与matrix词源里的子宫、母体意象相关),并且和“周行而不殆、独立而不改”(规律性)高度切合(由于matrix在近现代数学家命名矩阵的新含义,增添这个词汇在规律性上的刻画)
- 张量(Tensor)
张量是向量概念的推广。
Tensor这个单词来源于Tension(物理学中的张力)。在深度学习里,张量可以将向量和矩阵推广到任意维度,张量实际上就是多维度数组,表示为H * W *C(*D)。因此,可以用张量来描述任意维度的物体。
从一般意义上来说,一切皆Tensor!
Tensor可以用来描述机器学习中的样本(如Tensor描述的图像和图等)和模型(主要指的是各种参数可以用Tensor描述)。
如一张彩色图像,有宽度和高度,同时又有R,G,B三个通道。所以一张彩色图像就是三阶张量(通道 * 宽度 *高度)。
再比如高一阶的四阶张量,可以理解为一个批次(批次简单理解为多张图片)的彩色图像。因为在做图像识别过程中,我们训练和推理过程中可以一次推理N张图像,这个N称为批次。即是 批次 * 通道 * 宽度 *高度 的四阶张量。在深度学习中,我们要处理不止一张图片或一篇文档——我们要处理一个集合。我们可能有10,000张图片,这意味着,我们将用到4阶张量。
注意:张量的维度(rank, number of dimensions)指的是张量中用来索引元素的索引个数,0维张量就是标量,因为不需要索引;向量(vector)需要一个索引就可以得到相应元素。
2.2 张量的创建
编程实现:
torch.Tensor是默认的tensor类型torch.FloatTensor的简称,它是一个类,也就是说使用它只能创建torch.FloatTensor类型。
torch.tensor是一个方法,它根据参数data创建Tensor,Tensor类型根据数据进行推断,也就是说当我们没有指定dtype使用它创建的类型和data一致。
torch.Tensor和torch.tensor均可以接收list(列表),tuple(元组),array(numpy 数组),二者除了可能表示的类型不同外,其它是所有都是一样的。
tensor
import torch #首先导入torch。注意: 虽然它被称为PyTorch,但在代码中使用torch
a = torch.tensor((1,2)) #元组
print(a)
print(a.type())
print(a.dim())
print(a.shape)
a = torch.tensor([1,2]) #列表
print(a)
print(a.type())
print(a.dim())
print(a.shape)
import numpy as np
a = torch.tensor(np.array([1,2])) #数组
print(a)
print(a.type())
print(a.dim())
print(a.shape)
运行一下,可以看到它们的输出都是tensor([1,2]),然后类型都市torch.longTensor,dim都是1,表示1维,shape都是torch.Size([2])表示1维中有2个元素。
Tensor
a = torch.Tensor((1,2)) #元组
print(a)
print(a.type())
print(a.dim())
print(a.shape)
a = torch.Tensor([1,2]) #列表
print(a)
print(a.type())
print(a.dim())
print(a.shape)
a = torch.Tensor(np.array([1,2])) #数组
print(a)
print(a.type())
print(a.dim())
print(a.shape)
运行一下,可以看到它的类型是torch.FloatTensor,其它都一样。
torch.torsor和torch.Tensor的不一致性
参数是一个数字的时候:
a = torch.tensor(2) #标量
print(a)
print(a.type())
print(a.dim())
print(a.shape)
a = torch.Tensor(2)
print(a)
print(a.type())
print(a.dim())
print(a.shape)
运行一下!
当参数是一个数字的时候,tensor就创建一个标量,而Tensor认为要创建一个一维张量,里面有2个元素。因此,以下代码一个会正确执行,一个会报错。
a = torch.tensor(2.) #标量
print(a)
print(a.type())
print(a.dim())
print(a.shape)
a = torch.Tensor(2.) #出错
print(a)
print(a.type())
print(a.dim())
print(a.shape)
也就是说,当传递一个数值的时候,Tensor只能接收一个整数,表示一维张量中元素有几个元素。
参数是多个数字:
import torch
import numpy as np
a = torch.Tensor(2,3,4)
print(a)
print(a.type())
print(a.dim())
print(a.shape)
a = torch.tensor(2,3,4) #报错
print(a)
print(a.type())
print(a.dim())
print(a.shape)
当我们传递多个参数的时候,Tensor认为是创建多维度,而tensor会报错,因为tensor的函数为:
torch.tensor(data,dtype=None,device=None,requires_grad=False)
只能接收一个参数data。
创建空的tensor
a=torch.Tensor()
print(a)
a=torch.tensor(())#注意与Tensor的区别
print(a)
几种特殊的Tensor
#几种特殊的Tensor
a = torch.eye(2,2)
print(a)
print(a.type())
b = torch.Tensor(2,3)
b = torch.zeros_like(b)
b = torch.ones_like(b)
print(b)
print(b.type())
# 随机
a = torch.rand(2,2)
print(a)
print(a.type())
# 可按照一定的分布随机化,用于初始化参数的值
a = torch.normal(mean=0.0,std=torch.rand(5))
print(a)
print(a.type())
#也可以对mean进行随机化
a = torch.normal(mean=torch.rand(5),std=torch.rand(5))
print(a)
print(a.type())
# 序列定义Tensor
a = torch.arange(0,10,1)
print(a)
print(a.type())
a = torch.linspace(2,10,3)#拿到等间隔切分的n个数字
print(a)
print(a.type())
print(a.shape) # 可以通过张量的shape属性来访问张量(沿每个轴的长度)的形状
注意:tensor是PyTorch的核心,是其基本操作单位。那torch.tensor和torch.Tensor有什么区别?
更多的张量创建方法请大家根据学习的进展继续研究。
2.3 张量的属性
前面我们实际上都已经应用到了,利用张量的属性来对张量进行描述。
常用的张量属性和方法有:
dim:输出张量的维度;
type:张量的类型;
size、shape:张量的形状;
numel:张量中元素的个数。
import torch
import numpy as np
a = torch.Tensor(2,3,4)
print(a)
print(a.dim())
print(a.type())
print((a.size))
print(a.shape())
print(a.numel())
思考:
既然张量就是一个多维数组,为什么PyTorch不直接用NumPy数组呢?
主要原因:一是一些对张量的操作在GPU上执行非常快;二是在多个设备或机器上可以进行分布式操作;三是可以跟踪创建他们的计算图。
计算图:计算图其实就是描述运算的有向无环图。计算图有两个主要的元素,节点(Node)和边(Edge)。节点表示数据,如向量、矩阵、张量,边表示运算,如加减乘除卷积等。
PyTorch的计算图中有叶子节点的概念,在上图中只有a、b、c是叶子节点。子节点是用户创建的节点,是计算的根基。在函数torch.Tensor中,is_leaf的作用就是指示张量是否为叶子节点。设置叶子节点的概念是很有必要的,因为梯度反向传播之后,非叶子节点的梯度会释放,无法保留数据。叶子节点,起到了减少内存的作用。
3 Tensor的算术运算
3.1 向量的算术运算
我们在学习神经网络的时候,经常用到的一个计算是:向量的内积。
如果两个向量的维度相同(说明处于相同维度空间中),可以进行加法和内积(Inner Product)运算。
向量的内积是两个向量对应元素相乘之后求和,设有两个n维向量:
注意:两个向量内积的结果是一个标量,所以又称为点积(Dot Product)。
向量内积的几何意义:
- 表征或计算两个向量之间的夹角;
- B向量在a向量方向上的投影。
内积是向量代数中的一种基本运算,用于计算两个向量的“距离”。
在深度学习中,特别是自然语言处理过程中,首先是向量化。而内积经常用于度量两个向量间的相似性或相关性。内积越大,两个向量的相似性越高;内积越小,两个向量的相似性越低。当两个向量完全相同时,他们的内积是最大的;反之,当它们完全相反时,内积是最小的。因此,内积可以作为两个向量相似性的度量指标。
图例:当两个向量方向相反时,内积取得最小值;当两个向量方向不平行时,内积取平行时的中间值;当两个向量方向相同时,内积取得最大值。
因此,我们经常使用余弦相似度(Cosine Similarity),即计算两个向量之间的夹角的余弦值,来计算两个的向量相似度。
还有一个计算向量相似性的方法,那就是欧氏距离(L2)。
欧式距离(L2)全称为 Euclidean distance,指欧几里得距离。它计算两个向量点在空间中的直线距离。计算公式如下所示:
其中,a = (a1, a2,..., an) 和 b = (b1, b2,..., bn) 是 n 维空间中的两个点。它是最常用的距离度量。计算所得的值越小,越与搜索值相似。L2在低维空间中表现良好,但是在高维空间中,由于维度灾难的影响,L2的效果会逐渐变差。
在神经网络中,如何高效进行计算和向量化的实现方法是我们需要掌握的最重要知识。也就是说,必须具备向量思维!
向量思维:正如在面向对象编程中我们说“一切都是对象”;在线性代数计算中,其实我们也可以说“一切都是向量”。——这是线性代数的核心思想!
线性代数的计算问题是神经网络的基础。
3.2 矩阵的算术运算
矩阵乘法最早由法国数学家雅克·菲利普·玛丽·比内(Jacques Philippe Marie Binet)于1812年描述,以表示由矩阵表示的线性映射的组合。
矩阵乘法是线性代数的基本工具,是线性代数所有计算应用包括神经网络计算的核心操作。
上图是矩阵乘法的计算方法示意图。矩阵乘法的本质就是把“对象”施加“方法”,成为新发“对象”,表示为:“方法”*“对象”。
记得简单的计算方法就可以了:
- 矩阵相乘的前提:第一个矩阵的列数等于第二个矩阵的行数。
- 乘法:第一行乘以第一列,然后相加。
- 结果矩阵的形状:结果矩阵的行等于第一个矩阵的行数,结果矩阵的列等于第二个矩阵的列数。
再举一个例子,大家计算一下,看看是不是与这个基本的规则相符合?
矩阵的行数就是“矩阵的维度数”——矩阵的元素是列向量,而列向量的维度就是矩阵的行数。
如果两个向量的维度相同(说明处于相同维度空间中),可以进行加法和内积(Inner Product)或称为点积(Dot Product)运算。矩阵(矩阵是进化了的向量,它是向量的向量,是列向量的行向量/行向量的列向量)乘法:A矩阵的行(向量)*B矩阵的列(向量)。
再次强调一下,矩阵乘法的本质:“方法*对象”
编程实现:
c = a + b
c = torch.add(a,b) #减法:"-",torch.sub()等
!a.add(b)
a.add_(b)
# 乘法运算——哈达玛积(element wise,对应元素相乘)
c = a * b
c = torch.mul(a,b) #除法:"/",torch.div()等
a.mul(b)
a.mul_(b)
# 除法运算
c = a / b
c = torch.div(a,b)
a.div(b)
a.div_(b)
# 二维矩阵乘法运算操作包括torch.mm()、torch.matmul()|@
a = torch.ones(2,1)
b = torch.ones(1,2)
print(torch.mm(a,b))
print(torch.matmul(a,b))
print(a @ b )
print(a.matmul(b))
print(a.mm(b))
# 对于高维的Tensor(dim>2),定义其矩阵乘法仅在最后的两个维度上,要求前面的维度必须保持一致,就像举证的索引一样并且运算操作只有torch.matmul()。
a = torch.ones(1,2,3,4)
b = torch.ones(1,2,4,3)
print(a.matmul(b))
print(torch.matmul(a,b))
注意:
- 数学上讲的空间,是元素或对象的集合。有空间就有运动,这些运动我们称之为“变换”或“映射”。
- 线性空间就是向量空间;一维线性空间就是数轴,就是标量空间;二维线性空间就是XY平面,就是二维向量空间。
- 维度思维:“维”这个字,其实就是“网”的意思,而维度,就是“复杂度”的意思。在线性代数里,维度可以代表“空间复杂度”。
矩阵的行数就是“矩阵的维度数”——矩阵的元素是列向量,而列向量的维度就是矩阵的行数。
- 矩阵乘法有什么最基础的意义呢?
上例中,对于B来说,原来的2维矩阵,经过方法A后变成了3维矩阵,所以3*4的意义就是,将原来2维矩阵升维为3维=矩阵。
矩阵乘法完成了“维度的传递”。
- 降维变换
B是2维的,A是4维的,相乘结果得到的C是二维的。降维变换,实际上是高维向低维的投影,信息极可能是损失了的。
“降维攻击”是互联网时代一个很流行的词,其出处来自科幻小说《三体》。书中至少两个地方提到了降维,所以“降维攻击”在现实的使用中也有2层含义:第1种来自小说中歌者文明使用二向箔来打击低端文明的情节,他们可以很轻松地将三维世界二维化,通常用来形容互联网企业从更高的维度来碾压传统行业,特别嚣张。第2种含义来自小说中某些生物主动降维的情节,他们等自己适应低维后再把敌人拖到低维后消灭,比较类似网络上的一个流行说法“把你的智商拉低到同样水平再用丰富的经验打败你”。
降维这样的事情严格来说只存在于科幻的故事里,毕竟在我们三维世界里好像没有见过有谁被降维的情况。但在数据分析领域,这是一项非常有必要的技术。我们知道一个数据矩阵如果包含很多个变量(比如几百个),那意味着研究的对象是一个高维的空间,无论是建模还是理解数据都比较困难,如果有办法把空间降低到低维,同时尽量地减少信息损失,那么将会大大方便于我们进行数据分析。
- 升维变换
B是5维的,A是4维的,相乘结果得到的C是5维的。升维变换,实际上是低维向高维的扩展,信息极可能是增加了的。
- 空间塌陷
用矩阵来表示的空间的基重叠了,二维空间塌陷成了一维空间。——奇异矩阵
Tensor的乘法一样。
后面要学习到的神经网络内积大多数就是矩阵乘法。
4 Tensor的操作
4.1 Tensor的基本操作
- Tensor的维度操作:squezee和unsquezee
squezee就是压缩(维度减少,降维)。
a = torch.IntTensor([ [[1,2,3],[4,5,6]] ] )
#a这个tensor就是[ [[1,2,3],[4,5,6]] ]
#即列表[ ]中只有一个2*3的二维向量,所以三维是1,2,3
#如果列表[ ]中有三个2*3的二维向量,那么三维就是3,2,3
print(a.shape)#输出得到:torch.Size([1,2,3])
#a这个tensor有三个维度,1*2*3,只有某个维度是1,这个维度才能被压缩
b = torch.squeeze(a,0)#表示在第0维上进行压缩
print(b)
#tensor([[1, 2, 3],
# [4, 5, 6]], dtype=torch.int32)
print(b.shape)#输出得到:torch.Size([2, 3])
# 成功的将1*2*3变为2*3,实现了降维
unsqueeze就是扩充(维度增加,升维)。
a=torch.rand(4,1,28,28)
print(a.unsqueeze(0).shape) #torch.Size([1, 4, 1, 28, 28]
reshape/view
reshape/view:调整张量的形状,返回一个新的形状的张量。
相同点:reshape/view均可以调整Tensor的形状。
不同点:
view只能用于内存中连续存储的tensor。如果对tensor做了transpose,permute等操作,
则tensor在内存中会不连续,此时不能调用view函数。此时先调用.contiguous()方法,使
tensor的元素在内存空间中连续,然后调用.view()。
reshape连续与否都能用。
a = torch.rand(4,1,28,28)
print(a.view(4,2,-1).shape)
print(a.reshape(4,-1).shape)
# torch.Size([4, 2, 392])
# torch.Size([4, 784])
- expand
expand:维度扩展
torch.Tensor.expand(*sizes) → Tensor 将现有张量沿着值为1的维度扩展到新的维度。张量可以同时沿着任意一维或多维展开。 如果不想沿着一个特定的维度展开张量,可以设置它的参数值为-1。
参数: sizes(torch.size or int....)--想要扩展的维度。
x = torch.Tensor([3])
print(x.size()) # torch.Size([1])
print(x.expand(3,2))
“””
tensor([[3., 3.],
[3., 3.],
[3., 3.]])
“”“
a = torch.tensor([[[1,2,3],[4,5,6]]])
print(a)
print(a.size()) # torch.Size([1, 2, 3])
print(a.expand(3,2,3)) #只能沿着1的维度扩展到新的维度
注意:
1)只能在第0维扩展一个维数,比如原来是是(1,3,4)-->(2,1,3,4),而在其他维度扩展不可以(1,3,4)==》(1,2,3,4)【错误】
2)如果不增加维数,只是增加维度,要增加的原维度必须是1才可以在该维度增加维度,其他值均不可以。
#1
x = torch.randn(2, 1, 1)#为1可以扩展为3和4
x = x.expand(2, 3, 4)
print('x :', x.size()) # x : torch.Size([2, 3, 4])
#2
#扩展一个新的维度必须在最前面,否则会报错
x = x.expand(2, 3, 4, 6) # 报错
x = x.expand(6, 2, 3, 4)
print('x :', x.size())
# x : torch.Size([6, 2, 3, 4])
#3
#某一个维度为-1表示不改变该维度的大小
x = x.expand(6, -1, -1, -1)
print('x :', x.size())
# x : torch.Size([6, 2, 3, 4])
- transpose/permute
transpose/permute:维度交换。
相同点:均可以进行维度交换。
不同点:transpose 只能交换两个维度; permute可以自由交换位置。
x = torch.rand(5,1,2,1)
print(x.size()) # torch.Size([5, 1, 2, 1])
y = x.transpose(1,2) # 交换1和2维度
print(x.size()) # torch.Size([5, 1, 2, 1])
print(y.size()) # torch.Size([5, 2, 1, 1])
z = x.permute(0,3,1,2) #可以直接变换
print(z.size()) #torch.Size([5, 1, 1, 2])
- 转置操作
a=torch.rand(3, 4)
print(a.t().shape) # torch.Size([4, 3])
- repeat
repeat会重新申请内存空间,repeat()参数表示各个维度指定的重复次数。
a=torch.rand(1,32,1,1)
print(a.repeat(4,32,1,1).shape) #torch.Size([4, 1024, 1, 1])
print(a.repeat(4,1,1,1,1).shape) # torch.Size([4, 1, 32, 1, 1])
- 张量拼接 cat & stack
cat:是在已有的张量上拼接,且不需要张量之间形状相同。
上图示意cat 的作用,原来就是3维的,cat之后仍旧是3维的,而现在咱们是从⒉维变成了3维。
stack: 会生成行的维度,并且要求张量之间形状相同。为了让你加深理解,我们还是结合具体例子来看看。假设我们有两个二维矩阵Tensor,把它们“堆叠”放在一起,构成一个三维的Tensor,如下图:
原来的维度(秩)是2,现在变成了3,变成了一个立体的结构,增加了一个维度。
x = torch.randn(2, 3)
print(x)
y = torch.cat((x, x, x), 0)#从行方向进行拼接
print(y)
y1 = torch.cat((x, x, x), 1)#从列方向进行拼接
print(y1)
"""
下面例子说明torch.cat()与torch.stack()区别。可以看出,stack()是增加新的维度来完成拼接,不改变原维度上的数据大小。
cat()是在现有维度上进行数据的增加(改变了现有维度大小),不增加新的维度。
"""
x = torch.rand(2,3)
y = torch.rand(2,3)
print(x)
print(y)
print(torch.stack((x,y),1))
print(torch.stack((x,y),1).size())
print(torch.cat((x,y),1))
print(torch.cat((x,y),1).size())
- 广播机制
如果一个PyTorch操作支持广播,则其Tensor参数可以自动扩展为相等大小。一般情况是小一点的数组会被broadcast到大一点的,这样才能保持大小一致。
简单来说,Broadcasting可以这样理解:如果你有一个 m × n 的矩阵,让它加减乘除一个 1 × n 的矩阵,它会被复制 m 次,成为一个 m × n 的矩阵,然后再逐元素地进行加减乘除操作。
数组在进行矢量化运算时,要求数组的形状是相等的。当形状不相等的数组执行算术运算的时候,就会出现广播机制,对数组进行扩展,使数组的shape属性值一样。
广播机制的规则:
张量参数可以自动扩展为相同大小,需要满足两个条件:
1)每个张量至少有一个维度
2)满足右对齐
a = torch.rand(2,3)
b = torch.rand(3)
c = a + b
print(a)
print(b)
print(c)
print(c.shape)
4.2 Tensor的其它运算
- .floor()向下取整数
- .ceil()向上取整数
- .round()四舍五入>=0.5向上取整,<0.5向下取整
- .trunc()剪裁,只取整数部分
- .frac()只取小数部分
- %取余
a = torch.rand(2,2)
a = a * 10
print(a)
print(torch.floor(a))
print(torch.ceil(a))
print(torch.round(a))
print(torch.trunc(a))
print(a % 2)
- torch.eq(input, other,out=None) # 按成员进行等式操作,相同返回True
- torch.equal(tensor1,tensor2) # 如果tensor1和tensor2有相同的size和elements,则为True
- torch.ge(input, other,out=None) # input >= other
- torch.gt(input, other,out=None) # input > other
- torch.le(input, other,out=None) # input <= other
- torch.lt(input, other,out=None) # input < other
- torch.ne(input, other,out=None) # input != other
- torch.sort(input, dim=None, descending=False,out=None) # 对目标input进行排序
- torch.topk( input, k, dim=None,largest=True,sorted=True,out=None) # 沿着指定维度返回最大k个数值及其索引值
- torch.kthvalue(input,k,dim=None,out=None) # 沿着指定维度返回第k个最小值及其索引值
- torch.isfinite(tensor)/torch.isinf(tensor)/torch.isnan(tensor) # 返回一个标记元素是否为finite/inf/nan的mask张量
a = torch.rand(2,3)
b = torch.rand(2,3)
print(a)
print(b)
print(torch.eq(a,b))
print(torch.equal(a,b))
print(torch.ge(a,b))
print(torch.gt(a,b))
print(torch.le(a,b))
print(torch.lt(a,b))
print(torch.ne(a,b))
PyTorch简单的编程技巧
- torch.saves(state,dir) #保存/序列化
- torch.load(dir) #加载模型
并行化
- torch.get_num_threads(): # 获得用于并行化CPU操作的OpenMP线程数
- torch.set_num_threas(int): # 设定用于并行化CPU操作的OpenMP线程数
分布式
python在默认情况下只使用一个GPU,在多个GPU的情况下就需要使用pytorch提供的DataParallel
包括:单机多卡、多机多卡
Tensor on GPU
使用方法to()可以将Tensor在CPU和GPU(需要硬件支持)之间相互移动。
if torch.cuda is_available():
device = torch.device("cuda") # GPU
y = torch.ones_like(x,device = device) #直接创建一个在GPU上的Tensor
x = x.to(device) # 等价于.to("cuda")
z = x + y
print(z)
print(z.to("CPU),torch.double) #to()还可以同时更改数据类型
思考一个问题:基因组、蛋白组数据如何转换成Tensor表示?
推荐阅读
- 廖星宇.深度学习入门之PyTorch [M].北京:电子工业出版社,2017
- 孙博. 机器学习中的数学[M].北京:中国水利水电出版社,2019
- 李舰,海恩.统计之美:人工智能时代的科学思维[M].北京:电子工业出版社,2019
- 阿斯顿.张,李沐等.动手学深度学习(PyTorch版)[M].北京:人民邮电出版社,2019