本篇是在学习张量时候的一些总结,可能又不到位的地方,请各位谅解。
在开始张量的学习之前,我们先回顾一下向量及矩阵的一些概念。
1、向量的概念
向量定义为一行或一列数,分别称之为行向量和列向量,如式(1)和(2)所示(这里用T代表转置,意思是行列互换)。组成向量的每个数称为向量的分量。
a = ( a 1 a 2 ⋯ a n ) (1) {a}= \begin{pmatrix} {a_{1}}&{a_{2}}&{\cdots}&{a_{n}} \end{pmatrix} \tag{1} a=(a1a2⋯an)(1)
a T = ( a 1 a 2 ⋮ a n ) (2) a^T= \begin{pmatrix} {a_1}\\ {a_2}\\ {\vdots}\\ {a_n} \end{pmatrix} \tag{2} aT=⎝ ⎛a1a2⋮an⎠ ⎞(2)
2、向量的四则运算
我们定义向量和实数的乘法为向量的每一个分量乘以该实数得到一个新分量。
向量与向量的加减法为向量每个元素一一对应相加减(仅对分量数大小相等的向量有意义)。
向量与向量之间一个重要的运算是 点积 ,或者称之为 内积 ,表现为两个相同大小的向量按分量相乘并且求和。我们把向量与自身内积的平方根称之为向量的长度(或模),两个向量的内积等于向量的模长乘以向量之间夹角的余弦,如式(3)所示。
a b = ∑ i a i b i = ∥ a ∥ ∥ b ∥ c o s θ , ∥ a ∥ = ∑ i a i 2 (3) ab = \sum_{i}a_ib_i = \Vert a \rVert \Vert b \rVert cos \theta ,\Vert a \rVert=\sqrt{\sum_{i}a_i^2} \tag{3} ab=i∑aibi=∥a∥∥b∥cosθ,∥a∥=i∑ai2(3)
1、矩阵的概念
所谓矩阵就是把数按照矩形的形状排列成一个二维的结构,如式(4)所示,这里展示了一个 m x n 的矩阵 a ,与向量一样,矩阵亦可以进行转置,如式(5)所示,一个 m x n 的矩阵 a 的转置是一个 n x m 的矩阵。
a = [ a 11 a 12 ⋯ a 1 n a 21 a 22 ⋯ a 2 n ⋮ ⋮ ⋱ ⋮ a m 1 a m 2 ⋯ a m n ] (4) a = \begin{bmatrix} {a_{11}}&{a_{12}}&{\cdots}&{a_{1n}}\\ {a_{21}}&{a_{22}}&{\cdots}&{a_{2n}}\\ {\vdots}&{\vdots}&{\ddots}&{\vdots}\\ {a_{m1}}&{a_{m2}}&{\cdots}&{a_{mn}}\\ \end{bmatrix} \tag{4} a=⎣ ⎡a11a21⋮am1a12a22⋮am2⋯⋯⋱⋯a1na2n⋮amn⎦ ⎤(4)
a T = [ a 11 a 21 ⋯ a m 1 a 12 a 22 ⋯ a m 2 ⋮ ⋮ ⋱ ⋮ a n 1 a n 2 ⋯ a n m ] (5) a^T = \begin{bmatrix} {a_{11}}&{a_{21}}&{\cdots}&{a_{m1}}\\ {a_{12}}&{a_{22}}&{\cdots}&{a_{m2}}\\ {\vdots}&{\vdots}&{\ddots}&{\vdots}\\ {a_{n1}}&{a_{n2}}&{\cdots}&{a_{nm}}\\ \end{bmatrix} \tag{5} aT=⎣ ⎡a11a12⋮an1a21a22⋮an2⋯⋯⋱⋯am1am2⋮anm⎦ ⎤(5)
2、矩阵的四则运算
矩阵和实数的乘法得到一个新矩阵,该矩阵的每个元素等于原来的矩阵对应位置的元素乘以该矩阵。
矩阵的加减法同样定义为对应元素相加减得到的新矩阵(两个相加减的矩阵行列数必须相等)。
矩阵乘法,定义一个 m x n 的矩阵 c 为一个 m x k 的矩阵 a 和 k x n 的矩阵b的乘积(这里注意矩阵 a 的列数和矩阵 b 的行数必须相等,否则乘法无意义)。矩阵 c 的每个分量表示如式(6)所示。
值得注意的是矩阵的乘法不满足交换律,但满足结合律。
c i j = ∑ i a i k b k j (6) c_{ij} = \sum_i a_{ik} b_{kj} \tag{6} cij=i∑aikbkj(6)
张量在数学上,是一种几何的实体,或者说是广义上的“数量”,是在二维数字的排列之上进一步推广得到。
向量可以看作是一维的张量;矩阵可以看作是二维的张量,是由多个一维的张量的堆叠;三维的张量可以看作数字排列成长方体,三维张量可以看成是多个二维张量的堆叠;更高维的张量也可以此推测得到。
我们可以通过下面这个例子来加强我们的认识:
我们可以把黑白图片看作一个二维矩阵,其两个维度分别为图片的高 h 和图片的宽 w ,而彩色的图片由于有RGB(即红绿蓝)三个通道,可以看作一个三维张量,即 h x w x c,其中 h 是图片的高度,w 是图片的宽度,c 是图片的通道数目(c = 3) ,在深度学习过程中,我们要用到四维张量,增加的一个维数称为迷你批次的大小,可以认为是每次输入深度学习神经网络图片的数目。
张量的创建共有4种方式
如果预先有数据(包括列表和Numpy数组),可以通过这个方法进行转换
import touch
import numpy as np
A = [1,2,3,4]
#转换python列表为pytorch张量
tensor_list = torch.tensor(A)
print(tensor_list)
#转换迭代器为张量
tensor_range = torch.tensor(range(10))
print(tensor_range)
#转换numpy数组为pytorch张量
tensor_numpy = torch.tensor(np.array(A))
print(tensor_numpy)
##注意:pytorch默认的浮点类型为32位单精度,numpy默认的浮点类型为64位双精度
#列表嵌套创建张量
torch.tensor([[1,2,3],[4,5,6]])
##注意:列表的嵌套需要注意子列表的大小要一致,不然会报错
#数据类型的转换
#从torch.float 转换到 torch.int
print(torch.randn(3,3).to(torch.int))
#从torch.int 转换到 torch.float
print(torch.randint(0,5,(3,3)).to(torch.float))
#生成3x3的矩阵,矩阵元素服从[0,1]上的均匀分布
torch.rand(3,3)
#生成2x3x4的张量,张量元素服从标准正态分布
torch.randn(2,3,4)
#生成2x2x2的张量,张量元素全为0
torch.zeros(2,2,2)
#生成1x2x3的张量,张量的元素全为1
torch.ones(1,2,3)
#生成3x3的单位矩阵
torch.eye(3)
#生成0(包含)到10(不含)之间均匀分布整数的3x3矩阵
torch.randint(0,10,(3,3))
# 生成一个随机正态分布的张量t
t = torch.randn(3,3)
#生成一个元素全为0的张量,形状和给定张量t相同
torch.zeros_like(t)
#生成一个元素全为1的张量,形状和给定张量t相同
torch.ones_like(t)
#生成一个元素服从[0,1)上均匀分布的张量,形状和给定张量t相同
torch.rand_like(t)
#生成一个元素服从正态分布的张量,形状和给定张量t相同
torch.randn_like(t)
#根据python列表生成张量,注意这里输出的是单精度浮点数
t.new_tensor([1,2,3])
#生成相同数据类型且元素全为0的张量
t.new_zeros(3,3)
#生成相同数据类型且元素全为1的张量
t.new_ones(3,3)
#产生一个3x4x5的张量
t = torch.randn(3,4,5)
#获取维度的数目
t.ndimension()
#获取该张量的总元素数目
t.nelement()
#获取张量每个维度的大小,调用方法
t.size()
#获取张量每个维度的大小,访问属性
t.shape
#获取张量维度为0的大小,调用方法
t.size(0)
#产生大小为12 的向量
t = torch.randn(12)
#向量改变为3x4的矩阵
t.view(3,4)
#注意,当维度为-1时,pytorch会自动计算该维度的具体值
#view方法不改变底层数据,改变view后张量会改变原来的张量
t.view(3,4)[0,0] = 1.0
#此时输出的向量t与原来的向量t的第一个数据是不同的
#产生一个3x4的张量
t1 = torch.rand(3,4)
#张量的平方根,装量的内部方法
t1.sqrt()
#张量的平方根,函数形式
torch.sqrt(t1)
#平方根原地操作
t1.sqrt_()
#下划线版本会改变原来张量内存地址中的值
#求和,默认对所有元素求和
torch.sum(t1)
#对指定维数的元素求和
torch.sum(t1,0)
#当对多维数进行求和时,对所需求和的维数以列表的形式进行表示
#对所有元素求平均数,也可以用torch.mean(),用法与求和类似
t1.mean()
t1 = torch.rand(2,3)
t2 = torch.rand(2,3)
#张量的四则运算
print(t1.add(t2))
print(t1+t2)
print(t1.sub(t2))
print(t1-t2)
print(t1.mul(t2))
print(t1*t2)
print(t1.div(t2))
print(t1/t2)
查找张量某个维度极值的位置可使用argmax 和 argmin ,查找张量某个维度极值的具体值和位置使用max 和 min 。
排序可使用函数 sort 函数(默认为从小到大的排序,如果要从大到小排序,需要设置参数descending = True),传入所需排序的维度,返回的是排序完的张量,以及对应排序后的元素所在原始张量上的位置。
如果想知道原始张量的元素沿着某个维度排第几位,只需要对相应排序后的元素在张量上的位置进行再次排序,得到的新位置的值即为原始张量沿着该方向进行大小排序后的序号。
t = torch.rand(3,4)
torch.argmax(t,0) #返回的是对应维度极大/极小值对应元素的位置
torch.argmin(t,1)
torch.max(t,-1) #-1表示最后一个维度,返回的是对应维度极大值的值和位置
t.min() #返回全局最小值
t.min(0) #返回对应维度极小值
t.sort(0) #返回对应维度排序之后的元素的值及对应元素原始的位置
矩阵乘法的实现方法:①使用 torch.mm 函数进行;②使用张量内置的 mm 方法;③利用 @ 符号(注:@符号在python 3.5以后才可以作为矩阵乘法的运算)
另一个特殊的矩阵乘法的函数是 bmm 函数,计算迷你批次的数据,迷你批次的数据一般来说第一个维度是(迷你)批次的大小。例一个三维的张量,可以看作是一个迷你批次数量的矩阵叠加在一起。
在这种情况下,如果两个张量做矩阵乘法,一般情况下是沿着(迷你)批次的方向分别对每个矩阵对做乘法,最后把所有的乘积整合在一起。例如大小为 bxmxk 的张量与 bxkxn的张量相乘,那么结果应该是一个 bxmxn 的张量
a = torch.randn(3,4)
b = torch.randn(4,3)
torch.mm(a,b) ,#矩阵乘法,调用函数,返回3x3的矩阵乘积
a.mm(b) #内置方法
a @ b # @运算符号
#bmm函数
a = torch.randn(2,3,4)
b = torch.randn(2,4,3)
c = torch.bmm(a,b) #迷你批次矩阵乘法,返回结果为 2x3x3
print(c)
print(c.shape)
a.bmm(b) #内置方法
a @ b # @运算符号
对于更大维度的张量的乘积,往往要决定各自张量元素乘积的结果需要沿着哪个维度求和,这个操作称为 “缩并” 。
C i j k i ′ j ′ k ′ . . . = ∑ l 1 l 2 . . . l n A i j k . . . m l 1 l 2 . . . l n b i ′ j ′ k ′ . . . m l 1 l 2 . . . l n C_{ijki'j'k'...} = \sum_{l_1 l_2 ... l_n} A_{ijk...m l_1 l_2...l_n}b_{i'j'k'...m l_1 l_2 ... l_n} Cijki′j′k′...=l1l2...ln∑Aijk...ml1l2...lnbi′j′k′...ml1l2...ln
这里记参与运算的两个张量分别为 A 和 B ,输出结果为 C ,这里把对应维度的下标分为三类:
①在 A、B、C 中都出现的,意味着这两个下标对应的一些列元素需要做两两乘积(即张量积);放在低维度来看即张量积为:
C m n p q = A m n b p q C_{mnpq} = A_{mn} b_{pq} Cmnpq=Amnbpq
②在 A、B中出现,但 C 中没有出现,意味着这两个下标对应的一系列元素要做乘积求和(类似于内积);放在低纬度来看相当于矩阵乘法,即为:
C m n = A m k B k n C_{mn} = A_{mk} B_{kn} Cmn=AmkBkn
③在 A、B中出现,C 中只出现一次且这两个指标对应的维度大小相等,意味着这两个维度之间元素按照位置做乘法,放在低纬度来看即为:
C m n = A m n B m n C_{mn} = A_{mn} B_{mn} Cmn=AmnBmn
爱因斯坦求和乘法是为运算更大维度张量的乘积,矩阵乘法和批次乘法都能归结为爱因斯坦求和乘法,爱因斯坦求和乘法的好处在于不用像矩阵乘法那样对矩阵进行变换,它只在乎张量的下标。
a = torch.randn(2,3,4)
b = torch.randn(2,4,3)
#批次乘法的结果
a.bmm(b)
#爱因斯坦求和
torch.einsum("bnk,bkl -> bnl",a,b)
#两者的结果相等
"""
关于torch.einsum函数
在使用时需要传入两个张量的下表对应的形状,以不同的字母区分(字母可以任意选择,只需要服从前面的规则即可),以及最后输出张量的形状,用 -> 符号连接,最后传入两个输入的张量,即可得到输出的结果。
需要注意的是,求和的指标所在的维度的大小要相同,否则会报错
"""
张量的组合和分割函数主要有以下几个函数:torch.stack、torch.cat、torch.split、torch.chunk
t1 = torch.randn(3,4)
t2 = torch.randn(3,4)
t3 = torch.randn(3,4)
t4 = torch.randn(3,2)
#沿着最后一个维度做堆叠,返回大小为3x4x3的张量
print(torch.stack([t1,t2,t3],-1))
torch.stack([t1,t2,t3],-1).shape
#沿着最后一个维度做拼接,返回大小为3x14的张量
print(torch.cat([t1,t2,t3,t4],-1))
torch.cat([t1,t2,t3,t4],-1).shape
t = torch.randn(3,6)
#沿着最后一个维度将张量拆分成3个张量
t.split([1,2,3],-1)
#把张量沿着最后一个维度分割,分割大小为3,输出的张量大小均为3x3
t.split(3,-1)
#把张量沿着最后一个维度分割为三个张量,大小均为3x2
t.chunk(3,-1)
张量维度的扩增(torch.unsqueeze)或压缩(torch.squeeze)与张量的大小等于1的维度有关。对一个张量来说,可以任意添加一个维度,该维度的大小为1,而不改变张量的数据,因为张量的大小等于所有维度大小的乘积,那些为1的维度不改变张量的大小。
#随机生成一个3x4的张量
t = torch.rand(3,4)
t.shape
#扩增最后一个维度,生成3x4x1的张量
t.unsqueeze(-1).shape
#随机生成一个1x3x4x1的张量,有两个维度大小为1
t = torch.rand(1,3,4,1)
t.shape
#压缩所有为大小为1的维度,得到3x4的张量
t.squeeze().shape
张量的扩增有助于实现张量的另外一种可能,即张量的广播。在张量的运算中会碰到一种情况,即两个不同维度张量之间做四则运算,且两个张量的某些维度相等。
而按照张量的四则运算定义,两个不同维度的张量是不能运算的。为了能运算,首先需要unsqueeze 方法来对张量进行维度的扩增。完成扩增维度的两个张量必须能够在维度上对齐,即两个张量之间对应的维度存在两种情况,至少有一个维度大小为1,或者两个维度大小均不为1,但是相等。
t1 = torch.randn(3,4,5)
t2 = torch.randn(3,5)
#将张量2的形状变为3x1x5
t2 = t2.unsqueeze(1)
t2.shape
#广播求和,最后结果为3x4x5的张量
t3 = t1+t2
t3.shape