pytorch-1-4 张量的基础知识和运算

Lesson 1. 张量(Tensor)的创建和常用方法

  • 首次使用,先导入PyTorch包
    import torch
  • 查看版本号
    torch.version

一、张量(Tensor)的基本创建及其类型

1.张量(Tensor)函数创建方法

  张量的最基本创建方法和NumPy中创建Array的格式一致,都是创建函数(序列)的格式:张量创建函数:torch.tensor()

# 通过列表创建张量
t = torch.tensor([1, 2])
t

# 通过元组创建张量
torch.tensor((1, 2))

import numpy as np
a = np.array((1, 2))
a
# 通过数组创建张量
t1 = torch.tensor(a)
t1

2.张量的类型

  张量和数组类似,都拥有dtype方法,可返回张量类型。

# 数组类型
a.dtype
t.dtype
t1.dtype

  在这里,我们发现,整数型的数组默认创建int32(整型)类型,而张量则默认创建int64(长整型)类型。

np.array([1.1, 2.2]).dtype
torch.tensor(np.array([1.1, 2.2])).dtype
torch.tensor([1.11, 2.2]).dtype

  相对的,创建浮点型数组时,张量默认是float32(单精度浮点型),而Array则是默认float64(双精度浮点型)。
  当然,除了数值型张量,常用的常量类型还有布尔型张量,也就是构成张量的各元素都是布尔类型的张量。

t2 = torch.tensor([True, False])
t2
t2.dtype

  和数组不同,对于张量而言,数值型和布尔型张量就是最常用的两种张量类型,相关类型总结如下。

PyTorch中Tensor类型

数据类型dtype
32bit浮点数torch.float32或torch.float
64bit浮点数torch.float64或torch.double
16bit浮点数torch.float16或torch.half
8bit无符号整数torch.unit8
8bit有符号整数torch.int8
16bit有符号整数torch.int16或torch.short
16bit有符号整数torch.int16或torch.short
32bit有符号整数torch.int32或torch.int
64bit有符号整数torch.int64或torch.long
布尔型torch.bool
复数型torch.complex64

此外,我们还可以通过dtype参数,在创建张量过程中设置输出结果。

# 创建int16整型张量
torch.tensor([1.1, 2.7], dtype = torch.int16)

当然,在PyTorch中也支持复数类型对象创建

a = torch.tensor(1 + 2j)           # 1是实部、2是虚部
a

3.张量类型的转化

  • 张量类型的隐式转化
      和NumPy中array相同,当张量各元素属于不同类型时,系统会自动进行隐式转化。
# 浮点型和整数型的隐式转化
torch.tensor([1.1, 2]).dtype 
# 布尔型和数值型的隐式转化
torch.tensor([True, 2.0])
  • 张量类型的转化方法
  •   当然,我们还可以使用.float()、.int()等方法对张量类型进行转化。
t
# 转化为默认浮点型(32位)
t.float()
# 转化为双精度浮点型
t.double()
t.dtype
# 转化为16位整数
t.short()

Point:

  • 当在torch函数中使用dtype参数时候,需要输入torch.float表示精度;
  • 在使用方法进行类型转化时,方法名称则是double。(虽然torch.float和double都表示双精度浮点型。)

二、张量的维度与形变

  张量作为一组数的结构化表示,也同样拥有维度的概念。简答理解,向量就是一维的数组,而矩阵则是二维的数组,以此类推,在张量中,我们还可以定义更高维度的数组。当然,张量的高维数组和NumPy中高维Array概念类似。

1.创建高维张量

  • 用简单序列创建一维数组
      包含“简单”元素的序列可创建一维数组。
t1 = torch.tensor([1, 2])
t1
# 使用ndim属性查看张量的维度
t1.ndim
# 使用shape查看形状
t1.shape
# 和size函数相同
t1.size()

:和NumPy不同,PyTorch中size方法返回结果和shape属性返回结果一致。

此外,还需要注意有两个常用的函数/方法,用来查看张量的形状。

# 返回拥有几个(N-1)维元素
len(t1)
# 返回总共拥有几个数
t1.numel()

**注:**一维张量len和numel返回结果相同,但更高维度张量则不然

  • 用“序列”的“序列”创建二维数组
      以此类推,我们还可以用形状相同的序列组成一个新的序列,进而将其转化为二维张量。
# 用list创建二维数组
t2 = torch.tensor([[1, 2], [3, 4]])
t2
t2.ndim
t2.shape             
t2.size()
len(t2)

**理解:**此处len函数返回结果代表t2由两个1维张量构成

t2.numel()

**理解:**此处numel方法返回结果代表t2由总共由4个数构成

  • “零”维张量
      在PyTorch中,还有一类特殊的张量,被称为零维张量。该类型张量只包含一个元素,但又不是单独一个数。
t = torch.tensor(1)
t
t.ndim
t.shape
t.numel()

理解零维张量:
  目前,我们可将零维张量视为拥有张量属性的单独一个数。(例如,张量可以存在GPU上,但Python原生的数值型对象不行,但零维张量可以,尽管是零维。)从学术名称来说,Python中单独一个数是scalars(标量),而零维的张量则是tensor。

  • 高维张量
      一般来说,三维及三维以上的张量,我们就将其称为高维张量。当然,在高维张量中,最常见的还是三维张量。我们可以将其理解为二维数组或者矩阵的集合。
a1 = np.array([[1, 2, 2], [3, 4, 4]])
a1
a2 = np.array([[5, 6, 6], [7, 8, 8]])
a2

# 由两个形状相同的二维数组创建一个三维的张量
t3 = torch.tensor([a1, a2])
t3
t3.ndim
t3.shape          # 包含两个,两行三列的矩阵的张量。
len(t3)
t3.numel()

当然,N维张量的创建方法,我们可以先创建M个N-1维的数组,然后将其拼成一个N维的张量。关于更高维度的张量,我们将在后续遇到时再进行讲解。在张量的学习过程中,三维张量就已经足够。

2.张量的形变

  张量作为数字的结构化集合,其结构也是可以根据实际需求灵活调整的。

2.1 flatten拉平:将任意维度张量转化为一维张量
t2
t2.flatten()

按行排列,拉平。

t3
t3.flatten()

注:如果将零维张量使用flatten,则会将其转化为一维张量。

t
t.flatten()
t.flatten().ndim
2.2 reshape方法:任意变形
t1
# 转化为两行、一列的向量
t1.reshape(2, 1)

注意,reshape过程中维度的变化:reshape转化后的维度由该方法输入的参数“个数”决定

  • 转化后生成一维张量
t1.reshape(2)
t1.reshape(2).ndim
# 注,另一种表达形式
t1.reshape(2, )
  • 转化后生成二维张量
t1.reshape(1, 2)        # 生成包含一个两个元素的二维张量
t1.reshape(1, 2).ndim
  • 转化后生成三维张量
t1.reshape(1, 1, 2)        
t1.reshape(1, 2, 1)
# 注意转化过程维度的变化
t1.reshape(1, 2, 1).ndim

Q1:如何利用reshape方法,将t3拉平?

二、特殊张量的创建方法

  在很多数值科学计算的过程中,都会创建一些特殊取值的张量,用于模拟特殊取值的矩阵,如全0矩阵、对角矩阵等。因此,PyTorch中也存在很多创建特殊张量的函数。

1.特殊取值的张量创建方法

  • 全0张量
    torch.zeros([2, 3]) # 创建全是0的,两行、三列的张量(矩阵)

注:由于zeros就已经确定了张量元素取值,因此该函数传入的参数实际上是决定了张量的形状

  • 全1张量
torch.ones([2, 3])

张量和列表、数组之间的转化

  • 单位矩阵
torch.eye(5)
  • 对角矩阵
    略有特殊的是,在PyTorch中,需要利用一维张量去创建对角矩阵。
t1
torch.diag(t1)
torch.diag([1, 2])             # 不能使用list直接创建对角矩阵
  • rand:服从0-1均匀分布的张量
torch.rand(2, 3)
  • randn:服从标准正态分布的张量
torch.randn(2, 3)
  • normal:服从指定正态分布的张量
torch.normal(2, 3, size = (2, 2))            # 均值为2,标准差为3的张量
  • randint:整数随机采样结果
torch.randint(1, 10, [2, 4])                 # 在1-10之间随机抽取整数,组成两行四列的矩阵
  • arange/linspace:生成数列
torch.arange(5)                              # 和range相同
torch.arange(1, 5, 0.5)                      # 从1到5(左闭右开),每隔0.5取值一个
torch.linspace(1, 5, 3)                      # 从1到5(左右都包含),等距取三个数
  • empty:生成未初始化的指定形状矩阵
torch.empty(2, 3)
  • full:根据指定形状,填充指定数值
torch.full([2, 4], 2)      

2.创建指定形状的数组

  当然,我们还能根据指定对象的形状进行数值填充,只需要在上述函数后面加上_like即可。

t1
t2
torch.full_like(t1, 2)             # 根据t1形状,填充数值2
torch.randint_like(t2, 1, 10)
torch.zeros_like(t1)

Point:

  • 更多_like函数,可查阅帮助文档;
  • 需要注意一点的是,_like类型转化需要注意转化前后数据类型一致的问题;
torch.randn_like(t1)                  # t1是整数,而转化后将变为浮点数,此时代码将报错
t10 = torch.tensor([1.1, 2.2])        # 重新生成一个新的浮点型张量
t10
torch.randn_like(t10)                 # 即可执行相应的填充转化

三、张量(Tensor)和其他相关类型之间的转化方法

  张量、数组和列表是较为相似的三种类型对象,在实际操作过程中,经常会涉及三种对象的相互转化。在此前张量的创建过程中,我们看到torch.tensor函数可以直接将数组或者列表转化为张量,而我们也可以将张量转化为数组或者列表。另外,前文介绍了0维张量的概念,此处也将进一步给出零维张量和数值对象的转化方法。

  • .numpy方法:张量转化为数组
t1
t1.numpy()
# 当然,也可以通过np.array函数直接转化为array
np.array(t1)
  • .tolist方法:张量转化为列表
t1.tolist()
  • list函数:张量转化为列表
list(t1)

需要注意的是,此时转化的列表是由一个个零维张量构成的列表,而非张量的数值组成的列表。

  • .item()方法:转化为数值
    在很多情况下,我们需要将最终计算的结果张量转化为单独的数值进行输出,此时需要使用.item方法来执行。
n = torch.tensor(1)
n
n.item()

四、张量的深拷贝

  Python中其他对象类型一样,等号赋值操作实际上是浅拷贝,需要进行深拷贝,则需要使用clone方法

t1
t11 = t1                          # t11是t1的浅拷贝       
t11
t1[1]                             
t1[1] = 10                        # t1修改
t1
t11                               # t11会同步修改

此处t1和t11二者指向相同的对象。而要使得t11不随t1对象改变而改变,则需要对t11进行深拷贝,从而使得t11单独拥有一份对象。

t11 = t1.clone()
t1
t11
t1[0]
t1[0] = 100
t1
t11

本节练习答案:

A1
t3
t3.reshape(t3.numel())

Lesson 2.张量的索引、分片、合并以及维度调整

  张量作为有序的序列,也是具备数值索引的功能,并且基本索引方法和Python原生的列表、NumPy中的数组基本一致,当然,所有不同的是,PyTorch中还定义了一种采用函数来进行索引的方式。
  而作为PyTorch中基本数据类型,张量即具备了列表、数组的基本功能,同时还充当着向量、矩阵、甚至是数据框等重要数据结构,因此PyTorch中也设置了非常完备的张量合并与变换的操作。

import torch
import numpy as np

一、张量的符号索引

  张量也是有序序列,我们可以根据每个元素在系统内的顺序“编号”,来找出特定的元素,也就是索引。

1.一维张量索引

  一维张量的索引过程和Python原生对象类型的索引一致,基本格式遵循[start: end: step],索引的基本要点回顾如下。

t1 = torch.arange(1, 11)
t1
  • 从左到右,从零开始
t1[0]
t1[0].item()

注:张量索引出来的结果还是零维张量, 而不是单独的数。要转化成单独的数,需要使用item()方法。

  • 冒号分隔,表示对某个区域进行索引,也就是所谓的切片
    t1[1: 8] # 索引其中2-9号元素,并且左包含右不包含
  • 第二个冒号,表示索引的间隔
t1[1: 8: 2]          # 索引其中2-9号元素,左包含右不包含,且隔两个数取一个
  • 冒号前后没有值,表示索引这个区域
t1[1: : 2]           # 从第二个元素开始索引,一直到结尾,并且每隔两个数取一个
t1[: 8: 2]           # 从第一个元素开始索引到第9个元素(不包含),并且每隔两个数取一个

在张量的索引中,step位必须大于0

t1[9: 1: -1]

2.二维张量索引

  二维张量的索引逻辑和一维张量的索引逻辑基本相同,需要用逗号进行分隔,分别表示对哪个一维张量进行索引、以及具体的一维张量的索引。

t2 = torch.arange(1, 10).reshape(3, 3)
t2
t2[0, 1]                  # 表示索引第一行、第二个(第二列的)元素
t2[0, ::2]                # 表示索引第一行、每隔两个元素取一个
t2[0, [0, 2]]             # 索引结果同上
t2[::2, ::2]              # 表示每隔两行取一行、并且每一行中每隔两个元素取一个
t2[[0, 2], 1]             # 索引第一行、第三行、第二列的元素

理解:对二维张量来说,基本可以视为是对矩阵的索引,并且行、列的索引遵照相同的索引规范,并用逗号进行分隔。

3.三维张量的索引

  在二维张量索引的基础上,三维张量拥有三个索引的维度。我们将三维张量视作矩阵组成的序列,则在实际索引过程中拥有三个维度,分别是索引矩阵、索引矩阵的行、索引矩阵的列。

t3 = torch.arange(1, 28).reshape(3, 3, 3)
t3
t3.shape
t3[1, 1, 1]          # 索引第二个矩阵中,第二行、第二个元素
t3[1, ::2, ::2]      # 索引第二个矩阵,行和列都是每隔两个取一个
t3[:: 2, :: 2, :: 2]      # 每隔两个取一个矩阵,对于每个矩阵来说,行和列都是每隔两个取一个

理解:更为本质的角度去理解高维张量的索引,其实就是围绕张量的“形状”进行索引

t3.shape
t3[1, 1, 1]             # 与shape一一对应

二、张量的函数索引

  在PyTorch中,我们还可以使用index_select函数,通过指定index来对张量进行索引。

t1 = torch.arange(12)
t1
t1.ndim
indices = torch.tensor([1, 2])
indices
t1[1: 3]
t1[[1, 2]]
torch.index_select(t1, 0, indices)

在index_select函数中,第二个参数实际上代表的是索引的维度。对于t1这个一维向量来说,由于只有一个维度,因此第二个参数取值为0,就代表在第一个维度上进行索引

t2 = torch.arange(12).reshape(4, 3)
t2
t2.shape
indices
torch.index_select(t2, 0, indices)      # 行维度索引
torch.index_select(t2, 1, indices)      # 列维度索引

dim参数取值为0,代表在shape的第0个维度上索引
dim参数取值为1,代表在shape的第1个维度上索引

三、tensor.view()方法

  在正式介绍张量的切分方法之前,需要首先介绍PyTorch中的.view()方法。该方法会返回一个类似视图的结果,该结果和原张量对象共享一块数据存储空间,并且通过.view()方法,还可以改变对象结构,生成一个不同结构,但共享一个存储空间的张量。当然,共享一个存储空间,也就代表二者是“浅拷贝”的关系,修改其中一个,另一个也会同步进行更改。

t = torch.arange(6).reshape(2, 3)
t
te = t.view(3, 2)              # 构建一个数据相同,但形状不同的“视图”
te
t
t[0] = 1                       # 对t进行修改
t
te                             # te同步变化
tr = t.view(1, 2, 3)           # 维度也可以修改
tr

“视图”的作用就是节省空间,而值得注意的是,在接下来介绍的很多切分张量的方法中,返回结果都是“视图”,而不是新生成一个对象。

四、张量的分片函数

1.分块:chunk函数

  chunk函数能够按照某维度,对张量进行均匀切分,并且返回结果是原张量的视图。 torch.chunk(input, chunks, dim) ,chunks表示均分几份。

t2 = torch.arange(12).reshape(4, 3)
t2
tc = torch.chunk(t2, 4, dim=0)           # 在第0个维度上(按行),进行四等分
tc

注意:chunk返回结果是一个视图,不是新生成了一个对象

tc[0]
tc[0][0]
tc[0][0][0] = 1            # 修改tc中的值
tc
t2                         # 原张量也会对应发生变化

当原张量不能均分时,chunk不会报错,但会返回其他均分的结果

torch.chunk(t2, 3, dim=0)            # 次一级均分结果
len(torch.chunk(t2, 3, dim=0))
torch.chunk(t2, 5, dim=0)            # 次一级均分结果

2.拆分:split函数

  split既能进行均分,也能进行自定义切分。当然,需要注意的是,和chunk函数一样,split返回结果也是view。

t2 = torch.arange(12).reshape(4, 3)
t2
torch.split(t2, 2, 0)           # 第二个参数只输入一个数值时表示均分,第三个参数表示切分的维度
torch.split(t2, [1, 3], 0)      # 第二个参数输入一个序列时,表示按照序列数值进行比例切分,也就是1/3分

注意,当第二个参数位输入一个序列时,序列的各数值的和必须等于对应维度下形状分量的取值。例如,上述代码中,是按照第一个维度进行切分,而t2总共有4行,因此序列的求和必须等于4,也就是1+3=4,而序列中每个分量的取值,则代表切块大小。

torch.split(t2, [1, 1, 1, 1], 0)  
torch.split(t2, [1, 1, 2], 0) 
ts = torch.split(t2, [1, 2], 1) 
ts
ts[0][0]
ts[0][0] = 1                 # view进行修改
ts
t2                           # 原对象同步改变

tensor的split方法和array的split方法有很大的区别,array的split方法是根据索引进行切分。

五、张量的合并操作

  张量的合并操作类似与列表的追加元素,可以拼接、也可以堆叠。

1.拼接:cat函数

PyTorch中,可以使用cat函数实现张量的拼接。

a = torch.zeros(2, 3)
a
b = torch.ones(2, 3)
b
c = torch.zeros(3, 3)
c
torch.cat([a, b])                  # 按照行进行拼接,dim默认取值为0
torch.cat([a, b], 1)               # 按照列进行拼接
torch.cat([a, c], 1)               # 形状不匹配时将报错

注意理解,拼接的本质是实现元素的堆积,也就是构成a、b两个二维张量的各一维张量的堆积,最终还是构成二维向量。

2.堆叠:stack函数

  和拼接不同,堆叠不是将元素拆分重装,而是简单的将各参与堆叠的对象分装到一个更高维度的张量里。

a
b
torch.stack([a, b])                 # 堆叠之后,生成一个三维张量
torch.stack([a, b]).shape
torch.cat([a, b])

注意对比二者区别,拼接之后维度不变,堆叠之后维度升高。拼接是把一个个元素单独提取出来之后再放到二维张量中,而堆叠则是直接将两个二维张量封装到一个三维张量中,因此,堆叠的要求更高,参与堆叠的张量必须形状完全相同。

a
c
torch.cat([a, c])                # 横向拼接时,对行数没有一致性要求
torch.stack([a, c])               # 维度不匹配时也会报错

六、张量维度变换

  此前我们介绍过,通过reshape方法,能够灵活调整张量的形状。而在实际操作张量进行计算时,往往需要另外进行降维和升维的操作,当我们需要除去不必要的维度时,可以使用squeeze函数,而需要手动升维时,则可采用unsqueeze函数。

a = torch.arange(4)
a
a2 = a.reshape(1, 4)
a2
torch.squeeze(a2)
torch.squeeze(a2).ndim

1.squeeze函数:删除不必要的维度

t = torch.zeros(1, 1, 3, 1)
t
t.shape

torch.squeeze(t)
torch.squeeze(t).shape	# 转化后生成了一个一维张量
t1 = torch.zeros(1, 1, 3, 2, 1, 2)
t1.shape
torch.squeeze(t1)
torch.squeeze(t1).shape	# 转化后生成 (3, 2, 2) 张量

简单理解,squeeze就相当于删除了shape中的1

2.unsqeeze函数:手动升维

t = torch.zeros(2, 2, 2, 2)
t.shape
torch.unsqueeze(t, dim = 0)              # 在第1个维度索引上升高1个维度
torch.unsqueeze(t, dim = 0).shape
torch.unsqueeze(t, dim = 2).shape         # 在第3个维度索引上升高1个维度
torch.unsqueeze(t, dim = 4).shape          # 在第5个维度索引上升高1个维度

结果:

torch.Size([2, 2, 2, 2])
torch.Size([1, 2, 2, 2, 2])
torch.Size([2, 2, 1, 2, 2])
torch.Size([2, 2, 2, 2, 1])

Lesson 3.张量的广播和科学运算

  • 数学运算与算子
      作为PyTorch中执行深度学习的基本数据类型,张量(Tensor)也拥有非常多的数学运算函数和方法,以及对应的一系列计算规则。在PyTorch中,能够作用与Tensor的运算,被统一称作为算子。并且相比于NumPy,PyTorch给出了更加规范的算子(运算)的分类,从而方便用户在不同场景下调用不同类型的算子(运算)。
  • 数学运算的分类

PyToch总共为Tensor设计了六大类数学运算,分别是:

  • 1.逐点运算(Pointwise Ops):指的是针对Tensor中每个元素执行的相同运算操作;

  • 2.规约运算(Reduction Ops):指的是对于某一张量进行操作得出某种总结值;

  • 3.比较运算(Comparison Ops):指的是对多个张量进行比较运算的相关方法;

  • 4.谱运算(Spectral Ops):指的是涉及信号处理傅里叶变化的操作;

  • 5.BLAS和LAPACK运算:指的是基础线性代数程序集(Basic Linear Algeria Subprograms)和线性代数包(Linear Algeria Package)中定义的、主要用于线性代数科学计算的函数和方法;

  • 6.其他运算(Other Ops):其他未被归类的数学运算。

  由于谱运算(Spectral Ops)前期不会涉及,而要理解傅里叶变换本身需要更多额外的数学基础,而很多其他运算,我们在前面介绍张量的基本方法时已经介绍,因此接下来将主要围绕逐点运算、规约运算、比较运算和线性代数运算四块进行讲解,而线性代数部分由于涉及到大量的数学内容,因此将放在Lesson 4中单独进行讲解。

关于数学运算的另一种分类方法,是根据运算使用场景进行分类,如基础数学运算、数理统计运算等。由于PyTorch官网是按照六类算子进行的分类,因此本节将结合两种分类方法进行讲解。

一、张量的广播(Broadcast)特性

  在具体介绍张量的运算操作之前,我们先要了解张量的运算规则,其中最重要的一点,就是张量具备和NumPy相同的广播特性,也就是允许不同形状的张量之间进行计算。

1.相同形状的张量计算

  根据官网说法,“same shapes are always broadcastable”,相同形状数组总是可以进行广播计算。这里简单强调一下,虽然我们往往觉得不同形状之间的张量计算才是应用到广播特性,但其实相同形状的张量计算,尽管是对应位置元素进行计算,但本质上也是应用到了广播特性。

t1 = torch.arange(3)
t1
t1 + t1                      # 对应位置元素依次相加

思考:如果是两个list相加,是什么结果?

la = [1,2]
lb = [3,4]
la + lb

2.不同形状的张量计算

  广播的特性是在不同形状的张量进行计算时,一个或多个张量通过隐式转化,转化成相同形状的两个张量,从而完成计算的特性。但并非任何两个不同形状的张量都可以通过广播特性进行计算,因此,我们需要了解广播的基本规则及其核心依据。

2.1 标量和任意形状的张量

  标量可以和任意形状的张量进行计算,计算过程就是标量和张量的每一个元素进行计算。

t1 = torch.arange(3)
t1 + 1                      # 1是标量,可以看成是零维

t1 + torch.tensor(1)        # 二维加零维
t2 = torch.zeros(3, 4)
t2.shape
t2 + 1
2.2 相同维度、不同形状的张量之间计算
  • 二维张量的广播
(torch.ones(1, 4) + torch.zeros(3, 4) ).shape
(torch.ones(3, 1) + torch.zeros(3, 4)).shape 
torch.zeros(3, 4) + torch.ones(2, 4)    # 第一个维度取值不同,且取值均不为1,因此无法广播
(torch.arange(3).reshape(3, 1) + torch.arange(3).reshape(1, 3)).shape   # 对应维度取值不同,但为1,可以广播

二者计算过程如下:
4

  • 三维张量的广播
(torch.zeros(3, 4, 5) + torch.ones(3, 4, 1)).shape
(torch.ones(3, 1, 5) + torch.zeros(3, 4, 5)).shape
(torch.zeros(3, 4, 5) + torch.ones(1, 1, 5)).shape
2.3 不同维度的张量计算过程中广播

  在理解相同维度、不同形状的张量广播之后,对于不同维度的张量之间的广播其实就会容易很多,因为对于不同维度的张量,我们首先可以将低维的张量升维,然后依据相同维度不同形状的张量广播规则进行广播。而低维向量的升维也非常简单,只需将更高维度方向的形状填充为1即可,例如:

(torch.arange(4).reshape(1, 1, 2, 2) + torch.zeros(3, 2, 2)).shape
(torch.arange(2).reshape(2, 1) + torch.zeros(3, 2, 3)).shape

结果:

torch.Size([1, 3, 2, 2])
torch.Size([3, 2, 3])

二、逐点运算(Pointwise Ops)

  PyTorch中逐点运算大部分都是可以针对Tensor中每个元素都进行的数学科学运算,并且都是较为通用的数学科学运算,和NumPy中针对Array的科学运算类似。在PyTorch中文文档中有全部运算符的相关介绍,此处仅针对常用计算函数进行介绍。
  逐点运算主要包括数学基本运算、数值调整运算和数据科学运算三块,相关函数如下:

1. 基本数学运算

Tensor基本数学运算

函数描述
torch.add(t1,t2 )t1、t2两个张量逐个元素相加,等效于t1+t2
torch.subtract(t1,t2)t1、t2两个张量逐个元素相减,等效于t1-t2
torch.multiply(t1,t2)t1、t2两个张量逐个元素相乘,等效于t1*t2
torch.divide(t1,t2)t1、t2两个张量逐个元素相除,等效于t1/t2
t1 = torch.tensor([1, 2])
t1
t2 = torch.tensor([3, 4])
t2
torch.add(t1, t2)
t1 + t2
t1 / t2

2. 数值调整函数

Tensor数值调整函数

函数描述
torch.abs(t)返回绝对值
torch.ceil(t)向上取整
torch.floor(t)向下取整
torch.round(t)四舍五入取整
torch.neg(t)返回相反的数
t = torch.randn(5)
t
torch.round(t)
torch.abs(t)
torch.neg(t)

:虽然此类型函数是数值调整函数,但并不会对原对象进行调整,而是输出新的结果。

t                # t本身并未发生变化

而若要对原对象本身进行修改,则可考虑使用方法_()的表达形式,对对象本身进行修改。此时方法就是上述同名函数。

t.abs_()
t
t.neg_()
t

除了上述数值调整函数有对应的同名方法外,本节介绍的许多科学计算都有同名方法。

t.exp_()
t

3. 常用科学计算

Tensor常用科学计算

在这里插入图片描述

  • tensor的大多数科学计算只能作用于tensor对象
# 计算2的2次方
torch.pow(2, 2)     # 无法计算
torch.pow(torch.tensor(2), 2)

理解:相比于Python原生数据类型,张量是一类更加特殊的对象,例如张量可以指定运行在CPU或者GPU上,因此很多张量的科学计算函数都不允许张量和Python原生的数值型对象混合使用。

  • tensor的大多数科学运算具有一定的静态性
      所谓静态性,指的是对输入的张量类型有明确的要求,例如部分函数只能输入浮点型张量,而不能输入整型张量。
t = torch.arange(1, 4)
t.dtype
torch.exp(t)

需要注意的是,虽然Python是动态编译的编程语言,但在PyTorch中,由于会涉及GPU计算,因此很多时候元素类型不会在实际执行函数计算时进行调整。此处的科学运算大多数都要求对象类型是浮点型,我们需要提前进行类型转化。

t1 = t.float()
t1
torch.exp(t1)
torch.expm1(t1)

注,此处返回结果是 e t − 1 e^{t} - 1 et1,在数值科学计算中,expm1函数和log1p函数是一对对应的函数关系,后面再介绍log1p的时候会讲解这对函数的实际作用。

torch.exp2(t1)
torch.pow(t, 2)

注意区分2的t次方和t的2次方

torch.square(t)
torch.sqrt(t1)
torch.pow(t1, 0.5)

开根号也就相当于0.5次幂

torch.log10(t1)
torch.log(t1)
torch.log2(t1)

同时,我们也可简单回顾幂运算和对数运算之间的关系

torch.exp(torch.log(t1))
torch.exp2(torch.log2(t1))
a = torch.tensor(-1).float()
a
torch.exp2(torch.log2(a))
  • 排序运算:sort
      在PyTorch中,sort排序函数将同时返回排序结果和对应的索引值的排列。
t = torch.tensor([1.0, 3.0, 2.0])
t
# 升序排列
torch.sort(t)
# 降序排列
torch.sort(t, descending=True)

  • 二维张量的排序
    和上述过程类似,在进行排序过程中,二维张量也可以按行或者按列进行排序
t22 = torch.randn(3, 4)             # 创建二维随机数张量
t22
# 默认情况下,是按照行进行升序排序
torch.sort(t22)
# 修改dim和descending参数,使得按列进行降序排序
torch.sort(t22, dim = 1, descending=True)

三、规约运算

  规约运算指的是针对某张量进行某种总结,最后得出一个具体总结值的函数。此类函数主要包含了数据科学领域内的诸多统计分析函数,如均值、极值、方差、中位数函数等等。

1. 统计分析函数

Tensor统计分析函数

函数描述
torch.mean(t)返回张量均值
torch.var(t)返回张量方差
torch.std(t)返回张量标准差
torch.var_mean(t)返回张量方差和均值
torch.std_mean(t)返回张量标准差和均值
torch.max(t)返回张量最大值
torch.argmax(t)返回张量最大值索引
torch.min(t)返回张量最小值
torch.argmin(t)返回张量最小值索引
torch.median(t)返回张量中位数
torch.sum(t)返回张量求和结果
torch.logsumexp(t)返回张量各元素求和结果,适用于数据量较小的情况
torch.prod(t)返回张量累乘结果
torch.dist(t1, t2)计算两个张量的闵式距离,可使用不同范式
torch.topk(t)返回t中最大的k个值对应的指标
# 生成浮点型张量
t = torch.arange(10).float()
t
# 计算均值
torch.mean(t)
# 计算标准差、均值
torch.std_mean(t)
# 计算最大值
torch.max(t)
# 返回最大值的索引
torch.argmax(t)
# 计算中位数
torch.median(t)
# 求和
torch.sum(t)
# 求积
torch.prod(t)
torch.prod(torch.tensor([1, 2, 3]))
t
# 返回t中最大的k个值对应的指标
torch.topk(t, 2)

2. dist计算距离

  dist函数可计算闵式距离(闵可夫斯基距离),通过输入不同的p值,可以计算多种类型的距离,如欧式距离、街道距离等。闵可夫斯基距离公式如下:

$ D(x,y) = (\sum^{n}_{u=1}|x_u-y_u|^{p})^{1/p}$
t1 = torch.tensor([1.0, 2])
t2 = torch.tensor([3.0, 4])
torch.dist(t1, t2, 2)  # p取值为2时,计算欧式距离
torch.dist(t1, t2, 1)  # p取值为1时,计算街道距离

3. 规约运算的维度

  由于规约运算是一个序列返回一个结果,因此若是针对高维张量,则可指定某维度进行计算。

  • 二维张量
# 创建一个3*3的二维张量
t2 = torch.arange(12).float().reshape(3, 4)
t2
t2.shape
# 按照第一个维度求和(每次计算三个)、按列求和
torch.sum(t2, dim = 0)		
torch.sum(t2, dim = 0).shape
# 按照第二个维度求和(每次计算四个)、按行求和
torch.sum(t2, dim = 1)
torch.sum(t2, dim = 1).shape

结果:

tensor([[ 0.,  1.,  2.,  3.],
        [ 4.,  5.,  6.,  7.],
        [ 8.,  9., 10., 11.]])
torch.Size([3, 4])
# dim = 0
tensor([12., 15., 18., 21.])
torch.Size([4])		
# dim = 1
tensor([ 6., 22., 38.])
torch.Size([3])
  • 三维张量
# 创建一个2*3*4的三维张量
t3 = torch.arange(24).float().reshape(2, 3, 4)
t3
t3.shape
torch.sum(t3, dim = 0)
torch.sum(t3, dim = 0).shape
torch.sum(t3, dim = 1)
torch.sum(t3, dim = 1).shape
torch.sum(t3, dim = 2)
torch.sum(t3, dim = 2).shape

结果:

tensor([[[ 0.,  1.,  2.,  3.],
         [ 4.,  5.,  6.,  7.],
         [ 8.,  9., 10., 11.]],
         
        [[12., 13., 14., 15.],
         [16., 17., 18., 19.],
         [20., 21., 22., 23.]]])
torch.Size([2, 3, 4])
# dim = 0
tensor([[12., 14., 16., 18.],
        [20., 22., 24., 26.],
        [28., 30., 32., 34.]])
torch.Size([3, 4])
# dim = 1
tensor([[12., 15., 18., 21.],
        [48., 51., 54., 57.]])
torch.Size([2, 4])
# dim = 2
tensor([[ 6., 22., 38.],
        [54., 70., 86.]])
torch.Size([2, 3])  

理解:dim参数和shape返回结果一一对应。

四、比较运算

  比较运算是一类较为简单的运算类型,和Python原生的布尔运算类似,常用于不同张量之间的逻辑运算,最终返回逻辑运算结果(逻辑类型张量)。基本比较运算函数如下所示:

Tensor比较运算函数

函数描述
torch.eq(t1, t2)比较t1、t2各元素是否相等,等效==
torch.equal(t1, t2)判断两个张量是否是相同的张量
torch.gt(t1, t2)比较t1各元素是否大于t2各元素,等效>
torch.lt(t1, t2)比较t1各元素是否小于t2各元素,等效<
torch.ge(t1, t2)比较t1各元素是否大于或等于t2各元素,等效>=
torch.le(t1, t2)比较t1各元素是否小于等于t2各元素,等效<=
torch.ne(t1, t2)比较t1、t2各元素是否不相同,等效!=
t1 = torch.tensor([1.0, 3, 4])
t2 = torch.tensor([1.0, 2, 5])
t1 == t2
torch.equal(t1, t2)          # 判断t1、t2是否是相同的张量
torch.eq(t1, t2)
t1 > t2
t1 >= t2

Lesson 4.张量的线性代数运算

  也就是PyTorch中BLAS和LAPACK模块的相关运算。
  PyTorch中并未设置单独的矩阵对象类型,因此PyTorch中,二维张量就相当于矩阵对象,并且拥有一系列线性代数相关函数和方法。
  在实际机器学习和深度学习建模过程中,矩阵或者高维张量都是基本对象类型,而矩阵所涉及到的线性代数理论也是深度学习用户必备的基本数学基础。因此,本节在介绍张量的线性代数运算时,也会回顾基本的矩阵运算,及其基本线性代数的数学理论基础,以期在强化张量的线性代数运算过程中,也进一步夯实同学的线性代数数学基础。
  另外,在实际的深度学习建模过程中,往往会涉及矩阵的集合,也就是三维甚至是四维张量的计算,因此在部分场景中,我们也将把二维张量计算拓展到更高维的张量计算。

import torch
import numpy as np

一、BLAS和LAPACK概述

  BLAS(Basic Linear Algeria Subprograms)和LAPACK(Linear Algeria Package)模块提供了完整的线性代数基本方法,由于涉及到函数种类较多,因此此处对其进行简单分类,具体包括:

  • 矩阵的形变及特殊矩阵的构造方法:包括矩阵的转置、对角矩阵的创建、单位矩阵的创建、上/下三角矩阵的创建等;
  • 矩阵的基本运算:包括矩阵乘法、向量内积、矩阵和向量的乘法等,当然,此处还包含了高维张量的基本运算,将着重探讨矩阵的基本运算拓展至三维张量中的基本方法;
  • 矩阵的线性代数运算:包括矩阵的迹、矩阵的秩、逆矩阵的求解、伴随矩阵和广义逆矩阵等;
  • 矩阵分解运算:特征分解、奇异值分解和SVD分解等。

相关内容如果涉及数学基础,将在讲解过程中逐步补充。

二、矩阵的形变及特殊矩阵构造方法

  矩阵的形变方法其实也就是二维张量的形变方法,在此基础上本节将补充转置的基本方法。另外,在实际线性代数运算过程中,经常涉及一些特殊矩阵,如单位矩阵、对角矩阵等,相关创建方法如下:

Tensor矩阵运算

函数描述
torch.t(t)t转置
torch.eye(n)创建包含n个分量的单位矩阵
torch.diag(t1)以t1中各元素,创建对角矩阵
torch.triu(t)取矩阵t中的上三角矩阵
torch.tril(t)取矩阵t中的下三角矩阵
# 创建一个2*3的矩阵
t1 = torch.arange(1, 7).reshape(2, 3).float()
t1
# 转置
torch.t(t1)
t1.t()
torch.eye(3)
t = torch.arange(5)
t
torch.diag(t)
# 对角线向上偏移一位
torch.diag(t, 1)
# 对角线向下偏移一位
torch.diag(t, -1)
t1 = torch.arange(9).reshape(3, 3)
t1
# 取上三角矩阵
torch.triu(t1)
# 上三角矩阵向左下偏移一位
torch.triu(t1, -1)
# 上三角矩阵向右上偏移一位
torch.triu(t1, 1)
# 下三角矩阵
torch.tril(t1)

三、矩阵的基本运算

  矩阵不同于普通的二维数组,其具备一定的线性代数含义,而这些特殊的性质,其实就主要体现在矩阵的基本运算上。课程中常见的矩阵基本运算如下所示:

矩阵的基本运算

函数描述
torch.dot(t1, t2)计算t1、t2张量内积
torch.mm(t1, t2)矩阵乘法
torch.mv(t1, t2)矩阵乘向量
torch.bmm(t1, t2)批量矩阵乘法
torch.addmm(t, t1, t2)矩阵相乘后相加
torch.addbmm(t, t1, t2)批量矩阵相乘后相加
  • dot\vdot:点积计算
    注意,在PyTorch中,dot和vdot只能作用于一维张量,且对于数值型对象,二者计算结果并没有区别,两种函数只在进行复数运算时会有区别。更多复数运算的规则,我们将在涉及复数运算的场景中再进行详细说明。
t = torch.arange(1, 4)
t
torch.dot(t, t)
torch.vdot(t, t)

torch.dot(t1, t1)  # 不能进行除了一维张量以外的计算
  • mm:矩阵乘法
      再PyTorch中,矩阵乘法其实是一个函数簇,除了矩阵乘法以外,还有批量矩阵乘法、矩阵相乘相加、批量矩阵相乘相加等等函数。
t1 = torch.arange(1, 7).reshape(2, 3)
t1
t2 = torch.arange(1, 10).reshape(3, 3)
t2

t1 * t1			# 对应位置元素相乘

torch.mm(t1, t2)		# 矩阵乘法

矩阵乘法执行过程如下所示:
5

  • mv:矩阵和向量相乘
      PyTorch中提供了一类非常特殊的矩阵和向量相乘的函数,矩阵和向量相乘的过程我们可以看成是先将向量转化为列向量然后再相乘。
met = torch.arange(1, 7).reshape(2, 3)
met
vec = torch.arange(1, 4)
vec

在实际执行向量和矩阵相乘的过程中,需要矩阵的列数和向量的元素个数相同

torch.mv(met, vec)		# (2,3) * (3)
vec.reshape(3, 1)             # 转化为列向量

torch.mm(met, vec.reshape(3, 1))
torch.mm(met, vec.reshape(3, 1)).flatten()		# 降为1维

理解:mv函数本质上提供了一种二维张量和一维张量相乘的方法,在线性代数运算过程中,有很多矩阵乘向量的场景,典型的如线性回归的求解过程,通常情况下我们需要将向量转化为列向量(或者某些编程语言就默认向量都是列向量)然后进行计算,但PyTorch中单独设置了一个矩阵和向量相乘的方法,从而简化了行/列向量的理解过程和将向量转化为列向量的转化过程。

  • bmm:批量矩阵相乘
      所谓批量矩阵相乘,指的是三维张量的矩阵乘法。根据此前对张量结构的理解,我们知道,三维张量就是一个包含了多个相同形状的矩阵的集合。例如,一个(3, 2, 2)的张量,本质上就是一个包含了3个2*2矩阵的张量。而三维张量的矩阵相乘,则是三维张量内部各对应位置的矩阵相乘。由于张量的运算往往涉及二维及以上,因此批量矩阵相乘也有非常多的应用场景。
t3 = torch.arange(1, 13).reshape(3, 2, 2)
t3
t4 = torch.arange(1, 19).reshape(3, 2, 3)
t4
torch.bmm(t3, t4)
torch.bmm(t3, t4).shape  	# torch.Size([3, 2, 3])

Point:

  • 三维张量包含的矩阵个数需要相同;
  • 每个内部矩阵,需要满足矩阵乘法的条件,也就是左乘矩阵的行数要等于右乘矩阵的列数。
  • addmm:矩阵相乘后相加

addmm函数结构:addmm(input, mat1, mat2, beta=1, alpha=1)
输出结果:beta * input + alpha * (mat1 * mat2)

t1
t2
t = torch.arange(3)
t
torch.mm(t1, t2)                    	# 矩阵乘法
torch.addmm(t, t1, t2)              # 先乘法后相加   
torch.addmm(t, t1, t2, beta = 0, alpha = 10)
  • addbmm:批量矩阵相乘后相加
      和addmm类似,都是先乘后加,并且可以设置权重。不同的是addbmm是批量矩阵相乘,并且,在相加的过程中也是矩阵相加,而非向量加矩阵。
t = torch.arange(6).reshape(2, 3)
t
t3
t4
torch.bmm(t3, t4)
torch.addbmm(t, t3, t4)

**注:**addbmm会在原来三维张量基础之上,对其内部矩阵进行求和

四、矩阵的线性代数运算

  如果说矩阵的基本运算是矩阵基本性质,那么矩阵的线性代数运算,则是我们利用矩阵数据类型在求解实际问题过程中经常涉及到的线性代数方法,具体相关函数如下:

矩阵的线性代数运算

函数描述
torch.trace(A)矩阵的迹
matrix_rank(A)矩阵的秩
torch.det(A)计算矩阵A的行列式
torch.inverse(A)矩阵求逆
torch.lstsq(A,B)最小二乘法

同时,由于线性代数所涉及的数学基础知识较多,从实际应用的角度出发,我们将有所侧重的介绍实际应用过程中需要掌握的相关内容,并通过本节末尾的实际案例,来加深线性代数相关内容的理解。

1.矩阵的迹(trace)

  矩阵的迹的运算相对简单,就是矩阵对角线元素之和,在PyTorch中,可以使用trace函数进行计算。

A = torch.tensor([[1, 2], [4, 5]]).float()  
A
torch.trace(A)

当然,对于矩阵的迹来说,计算过程不需要是方阵

B = torch.arange(1, 7).reshape(2, 3)
B
torch.trace(B)

2.矩阵的秩(rank)

  矩阵的秩(rank),是指矩阵中行或列的极大线性无关数,且矩阵中行、列极大无关数总是相同的,任何矩阵的秩都是唯一值,满秩指的是方阵(行数和列数相同的矩阵)中行数、列数和秩相同,满秩矩阵有线性唯一解等重要特性,而其他矩阵也能通过求解秩来降维,同时,秩也是奇异值分解等运算中涉及到的重要概念。

  • matrix_rank计算矩阵的秩
A = torch.arange(1, 5).reshape(2, 2).float()
A
torch.matrix_rank(A)

B = torch.tensor([[1, 2], [2, 4]]).float()
B
torch.matrix_rank(B)

对于矩阵B来说,第一列和第二列明显线性相关,最大线性无关组只有1组,因此矩阵的秩计算结果为1

3.矩阵的行列式(det)

  所谓行列式,我们可以简单将其理解为矩阵的一个基本性质或者属性,通过行列式的计算,我们能够知道矩阵是否可逆,从而可以进一步求解矩阵所对应的线性方程。当然,更加专业的解释,行列式的作为一个基本数学工具,实际上就是矩阵进行线性变换的伸缩因子。
对于任何一个n维方正,行列式计算过程如下:
7
更为简单的情况,如果对于一个2*2的矩阵,行列式的计算就是主对角线元素之积减去另外两个元素之积

A = torch.tensor([[1, 2], [4, 5]]).float()     # 秩的计算要求浮点型张量
A
torch.det(A)
B
torch.det(B)

A的行列式计算过程如下:
6
对于行列式的计算,要求二维张量必须是方正,也就是行列数必须一致。

B = torch.arange(1, 7).reshape(2, 3)
B
torch.det(B)		# 无法计算

3.线性方程组的矩阵表达形式

  在正式进入到更进一步矩阵运算的讨论之前,我们需要对矩阵建立一个更加形象化的理解。通常来说,我们会把高维空间中的一个个数看成是向量,而由这些向量组成的数组看成是一个矩阵。例如:(1,2),(3,4)是二维空间中的两个点,矩阵A就代表这两个点所组成的矩阵。

A = torch.arange(1, 5).reshape(2, 2).float()
A

import matplotlib as mpl
import matplotlib.pyplot as plt

# 绘制点图查看两个点的位置
plt.plot(A[:,0], A[:, 1], 'o')

在这里插入图片描述

如果更进一步,我们希望在二维空间中找到一条直线,来拟合这两个点,也就是所谓的构建一个线性回归模型,我们可以设置线性回归方程如下:

$ y = ax + b $
带入(1,2)和(3,4)两个点之后,我们还可以进一步将表达式改写成矩阵表示形式,改写过程如下 8 而用矩阵表示线性方程组,则是矩阵的另一种常用用途,接下来,我们就可以通过上述矩阵方程组来求解系数向量x。

  首先一个基本思路是,如果有个和A矩阵相关的另一个矩阵,假设为 A − 1 A^{-1} A1,可以使得二者相乘之后等于1,也就是 A ∗ A − 1 = 1 A * A^{-1} = 1 AA1=1,那么在方程组左右两边同时左乘该矩阵,等式右边的计算结果 A − 1 ∗ B A^{-1} * B A1B就将是x系数向量的取值。而此处的 A − 1 A^{-1} A1就是所谓的A的逆矩阵。

逆矩阵定义:

$ 如果存在两个矩阵A、B,并在矩阵乘法运算下,A * B = E(单位矩阵),则我们称A、B互为逆矩阵$

  
在上述线性方程组求解场景中,我们已经初步看到了逆矩阵的用途,而一般来说,我们往往会通过伴随矩阵来进行逆矩阵的求解。由于伴随矩阵本身并无其他核心用途,且PyTorch中也未给出伴随矩阵的计算函数(目前),因此我们直接调用inverse函数来进行逆矩阵的计算。

当然,并非所有矩阵都有逆矩阵,对于一个矩阵来说,首先必须是方正,其次矩阵的秩不能为零,满足两个条件才能求解逆矩阵。

  • inverse函数:求解逆矩阵
    首先,根据上述矩阵表达式,从新定义A和B
A = torch.tensor([[1.0, 1], [3, 1]])
A
B = torch.tensor([2.0, 4])
B
# 然后使用inverse函数进行逆矩阵求解
torch.inverse(A)
# 简单试探逆矩阵的基本特性
torch.mm(torch.inverse(A), A)
torch.mm(A, torch.inverse(A))

torch.mv(torch.inverse(A), B)	# 得到线性方程的系数 a,b
# 结果:tensor([1.0000, 1.0000])

然后在方程组左右两边同时左乘 A − 1 A^{-1} A1,求解x

$A^{-1} * A * x= A^{-1} * B $
$ E * x = A^{-1} * B $
$ x = A^{-1} * B$

最终得到线性方程为:y = ax + b

$ y = x + 1$

当然,上述计算过程只是一个简化的线性方程组求解系数的过程,同时也是一个简单的一元线性方程拟合数据的过程,关于常用求解线性方程组系数的最小二乘法,可以先阅读本节末尾的选读内容,更多线性回归相关内容,我们将在下周进行详细讲解。

五、矩阵的分解

  矩阵的分解也是矩阵运算中的常规计算,矩阵分解也有很多种类,常见的例如QR分解、LU分解、特征分解、SVD分解等等等等,虽然大多数情况下,矩阵分解都是在形式上将矩阵拆分成几种特殊矩阵的乘积,但本质上,矩阵的分解是去探索矩阵更深层次的一些属性。本节将主要围绕特征分解和SVD分解展开讲解,更多矩阵分解的运算,我们将在后续课程中逐渐进行介绍。值得一提的是,此前的逆矩阵,其实也可以将其看成是一种矩阵分解的方式,分解之后的等式如下:

$A = A * A^{-1} * A $
而大多数情况下,矩阵分解都是分解成形如下述形式
$ A = VUD$

1. 特征分解

特征分解中,矩阵分解形式为:

$ A = Q\Lambda Q^{-1}$
其中,Q和$Q^{-1}$互为逆矩阵,并且Q的列就是A的特征值所对应的特征向量,而$\Lambda$为矩阵A的特征值按照降序排列组成的对角矩阵。
  • torch.eig函数:特征分解
A = torch.arange(1, 10).reshape(3, 3).float()
A
torch.eig(A, eigenvectors=True)                 # 注,此处需要输入参数为True才会返回矩阵的特征向量

输出结果中,eigenvalues表示特征值向量,即A矩阵分解后的Λ矩阵的对角线元素值,并按照又大到小依次排列,eigenvectors表示A矩阵分解后的Q矩阵,此处需要理解特征值,所谓特征值,可简单理解为对应列在矩阵中的信息权重,如果该列能够简单线性变换来表示其他列,则说明该列信息权重较大,反之则较小。特征值概念和秩的概念有点类似,但不完全相同,矩阵的秩表示矩阵列向量的最大线性无关数,而特征值的大小则表示某列向量能多大程度解读矩阵列向量的变异度,即所包含信息量,秩和特征值关系可用下面这个例子来进行解读。

B = torch.tensor([1, 2, 2, 4]).reshape(2, 2).float()
B
torch.matrix_rank(B)
torch.eig(B)          # 返回结果中只有一个特征
C = torch.tensor([[1, 2, 3], [2, 4, 6], [3, 6, 9]]).float()
C
torch.eig(C)                # 只有一个特征的有效值

特征值一般用于表示矩阵对应线性方程组解空间以及数据降维,当然,由于特征分解只能作用于方阵,而大多数实际情况下矩阵行列数未必相等,此时要进行类似的操作就需要采用和特征值分解思想类似的奇异值分解(SVD)。

2.奇异值分解(SVD)

  奇异值分解(SVD)来源于代数学中的矩阵分解问题,对于一个方阵来说,我们可以利用矩阵特征值和特征向量的特殊性质(矩阵点乘特征向量等于特征值数乘特征向量),通过求特征值与特征向量来达到矩阵分解的效果

$ A = Q\Lambda Q^{-1}$
  这里,Q是由特征向量组成的矩阵,而Λ是特征值降序排列构成的一个对角矩阵(对角线上每个值是一个特征值,按降序排列,其他值为0),特征值的数值表示对应的特征的重要性。

  在很多情况下,最大的一小部分特征值的和即可以约等于所有特征值的和,而通过矩阵分解的降维就是通过在Q、Λ 中删去那些比较小的特征值及其对应的特征向量,使用一小部分的特征值和特征向量来描述整个矩阵,从而达到降维的效果。
  但是,实际问题中大多数矩阵是以奇异矩阵形式,而不是方阵的形式出现的,奇异值分解是特征值分解在奇异矩阵上的推广形式,它将一个维度为m×n的奇异矩阵A分解成三个部分 :

$ A = U\sum V^{T}$
  其中U、V是两个正交矩阵,其中的每一行(每一列)分别被称为左奇异向量和右奇异向量,他们和∑中对角线上的奇异值相对应,通常情况下我们只需要保留前k个奇异向量和奇异值即可,其中U是m×k矩阵,V是n×k矩阵,∑是k×k的方阵,从而达到减少存储空间的效果,即
$A_{m*n} = U_{m*m}\sum_{m*n}V^{T}_{n*n}\approx U_{m*k}\sum_{k*k}V^{T}_{k*n}$
  • svd奇异值分解函数
C
torch.svd(C)
CU, CS, CV = torch.svd(C)

# 验证SVD分解
torch.diag(CS)
torch.mm(torch.mm(CU, torch.diag(CS)), CV.t())

能够看出,上述输出完整还原了C矩阵,此时我们可根据svd输出结果对C进行降维,此时C可只保留第一列(后面的奇异值过小),即k=1

U1 = CU[:, 0].reshape(3, 1)         # U的第一列
U1
C1 = CS[0]                           # C的第一个值
C1
V1 = CV[:, 0].reshape(1, 3)           # V的第一行
V1
torch.mm((U1 * C1), V1)

此时输出的Cd矩阵已经和原矩阵C高度相似了,损失信息在R的计算中基本可以忽略不计,经过SVD分解,矩阵的信息能够被压缩至更小的空间内进行存储,从而为PCA(主成分分析)、LSI(潜在语义索引)等算法做好了数学工具层面的铺垫。

本节选读内容

另外,我们需要知道的是,除了利用逆矩阵求解线性方程组系数外,比较通用的方法是使用最小二乘法进行求解:

  • torch.lstsq:最小二乘法
      最小二乘法是最通用的线性方程拟合求解工具,我们可以利用最小二乘法的直接计算拟合直线的系数最优解。当然,本节仅介绍最小二乘法的函数调用,下节在介绍目标函数和优化手段时,还将进一步介绍最小二乘法的数学原理。
torch.lstsq(B.reshape(2, 1), A)
x, q = torch.lstsq(B.reshape(2, 1), A)
x
q

我们发现,最小二乘法返回了两个结果,分别是x的系数和QR分解后的QR矩阵。

  • solve函数与LU分解
torch.solve(B.reshape(2, 1), A)
  • LU分解函数
torch.lu(A)
  • 21
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

白白白飘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值