04 张量的线性代数运算
作者:互联网
一、矩阵的形变及特殊矩阵构造方法
函数 | 描述 |
---|---|
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
tensor([[1., 2., 3.],
[4., 5., 6.]])
# 转置
torch.t(t1)
tensor([[1., 4.],
[2., 5.],
[3., 6.]])
t1.t()
tensor([[1., 4.],
[2., 5.],
[3., 6.]])
torch.eye(3)
tensor([[1., 0., 0.],
[0., 1., 0.],
[0., 0., 1.]])
t = torch.arange(5)
t
tensor([0, 1, 2, 3, 4])
torch.diag(t)
tensor([[0, 0, 0, 0, 0],
[0, 1, 0, 0, 0],
[0, 0, 2, 0, 0],
[0, 0, 0, 3, 0],
[0, 0, 0, 0, 4]])
# 对角线向上偏移一位
torch.diag(t, 1)
tensor([[0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0],
[0, 0, 0, 2, 0, 0],
[0, 0, 0, 0, 3, 0],
[0, 0, 0, 0, 0, 4],
[0, 0, 0, 0, 0, 0]])
# 对角线向下偏移一位
torch.diag(t, -1)
tensor([[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0],
[0, 0, 2, 0, 0, 0],
[0, 0, 0, 3, 0, 0],
[0, 0, 0, 0, 4, 0]])
t1 = torch.arange(9).reshape(3, 3)
t1
tensor([[0, 1, 2],
[3, 4, 5],
[6, 7, 8]])
# 取上三角矩阵
torch.triu(t1)
tensor([[0, 1, 2],
[0, 4, 5],
[0, 0, 8]])
# 上三角矩阵向左下偏移一位
torch.triu(t1, -1)
tensor([[0, 1, 2],
[3, 4, 5],
[0, 7, 8]])
# 上三角矩阵向右上偏移一位
torch.triu(t1, 1)
tensor([[0, 1, 2],
[0, 0, 5],
[0, 0, 0]])
# 下三角矩阵
torch.tril(t1)
tensor([[0, 0, 0],
[3, 4, 0],
[6, 7, 8]])
二、矩阵的基本运算
函数 | 描述 |
---|---|
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只能作用于1维张量,且对于数值型对象,二者计算结果没有区别,两种函数只在进行复数运算时会有区别。
t = torch.arange(1, 4)
t
tensor([1, 2, 3])
torch.dot(t, t)
tensor(14)
torch.vdot(t, t)
tensor(14)
# 不能进行除了一维张量以外的计算
torch.dot(t1, t1)
---------------------------------------------------------------------------
RuntimeError Traceback (most recent call last)
<ipython-input-38-5eafa2b4bbd3> in <module>
1 # 不能进行除了一维张量以外的计算
----> 2 torch.dot(t1, t1)
RuntimeError: 1D tensors expected, but got 2D and 2D tensors
- mm:矩阵乘法
在PyTorch中,矩阵乘法其实是一个函数簇,除了矩阵乘法以外,还有批量矩阵乘法、矩阵相乘相加、批量矩阵相乘相加等等函数。
t1 = torch.arange(1, 7).reshape(2, 3)
t1
tensor([[1, 2, 3],
[4, 5, 6]])
t2 = torch.arange(1, 10).reshape(3, 3)
t2
tensor([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
# 对应位置元素相乘
t1 * t1
tensor([[ 1, 4, 9],
[16, 25, 36]])
# 矩阵乘法
torch.mm(t1, t2)
tensor([[30, 36, 42],
[66, 81, 96]])
矩阵乘法过程如下:
- mv:矩阵和向量相乘
met = torch.arange(1, 7).reshape(2, 3)
met
tensor([[1, 2, 3],
[4, 5, 6]])
vec = torch.arange(1, 4)
vec
tensor([1, 2, 3])
torch.mv(met, vec)
tensor([14, 32])
vec.reshape(3, 1) # 转化为列向量
tensor([[1],
[2],
[3]])
torch.mm(met, vec.reshape(3, 1))
tensor([[14],
[32]])
torch.mm(met, vec.reshape(3, 1)).flatten()
tensor([14, 32])
- bmm:批量矩阵相乘
所谓批量矩阵相乘,指的是三维张量的矩阵乘法。根据此前对张量结构的理解,我们知道,三维张量就是一个包含了多个相同形状的矩阵的集合。例如,一个(3, 2, 2)的张量,本质上就是一个包含了3个2*2矩阵的张量。而三维张量的矩阵相乘,则是三维张量内部各对应位置的矩阵相乘。
t3 = torch.arange(1, 13).reshape(3, 2, 2)
t3
tensor([[[ 1, 2],
[ 3, 4]],
[[ 5, 6],
[ 7, 8]],
[[ 9, 10],
[11, 12]]])
t4 = torch.arange(1, 19).reshape(3, 2, 3)
t4
tensor([[[ 1, 2, 3],
[ 4, 5, 6]],
[[ 7, 8, 9],
[10, 11, 12]],
[[13, 14, 15],
[16, 17, 18]]])
torch.bmm(t3, t4)
tensor([[[ 9, 12, 15],
[ 19, 26, 33]],
[[ 95, 106, 117],
[129, 144, 159]],
[[277, 296, 315],
[335, 358, 381]]])
- addmm:矩阵相乘后相加
addmm函数结构:addmm(input, mat1, mat2, beta=1, alpha=1)
输出结果:beta * input + alpha * (mat1 * mat2)
t1
tensor([[1, 2, 3],
[4, 5, 6]])
t2
tensor([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
t = torch.arange(3)
t
tensor([0, 1, 2])
torch.mm(t1, t2) # 矩阵乘法
tensor([[30, 36, 42],
[66, 81, 96]])
torch.addmm(t, t1, t2) # 先乘法后相加
tensor([[30, 37, 44],
[66, 82, 98]])
torch.addmm(t, t1, t2, beta = 0, alpha = 10)
tensor([[300, 360, 420],
[660, 810, 960]])
- addbmm:批量矩阵相乘后相加
和addmm类似,都是先乘后加,并且可以设置权重。不同的是addbmm是批量矩阵相乘,并且,在相加的过程中也是矩阵相加,而非向量加矩阵。
t = torch.arange(6).reshape(2, 3)
t
tensor([[0, 1, 2],
[3, 4, 5]])
t3
tensor([[[ 1, 2],
[ 3, 4]],
[[ 5, 6],
[ 7, 8]],
[[ 9, 10],
[11, 12]]])
t4
tensor([[[ 1, 2, 3],
[ 4, 5, 6]],
[[ 7, 8, 9],
[10, 11, 12]],
[[13, 14, 15],
[16, 17, 18]]])
torch.bmm(t3, t4)
tensor([[[ 9, 12, 15],
[ 19, 26, 33]],
[[ 95, 106, 117],
[129, 144, 159]],
[[277, 296, 315],
[335, 358, 381]]])
torch.addbmm(t, t3, t4)
tensor([[381, 415, 449],
[486, 532, 578]])
注: 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
tensor([[1., 2.],
[4., 5.]])
torch.trace(A)
tensor(6.)
当然,对于矩阵的迹来说,计算过程不需要是方阵
B = torch.arange(1, 7).reshape(2, 3)
B
tensor([[1, 2, 3],
[4, 5, 6]])
torch.trace(B)
tensor(6)
2.矩阵的秩(rank)
矩阵的秩(rank),是指矩阵中行或列的极大线性无关数,且矩阵中行、列极大无关数总是相同的,任何矩阵的秩都是唯一值,满秩指的是方阵(行数和列数相同的矩阵)中行数、列数和秩相同,满秩矩阵有线性唯一解等重要特性,而其他矩阵也能通过求解秩来降维。
- matrix_rank 计算矩阵的秩
A = torch.arange(1, 5).reshape(2, 2).float()
A
tensor([[1., 2.],
[3., 4.]])
torch.matrix_rank(A)
tensor(2)
B = torch.tensor([[1, 2],[2, 4]]).float()
B
tensor([[1., 2.],
[2., 4.]])
torch.matrix_rank(B)
tensor(1)
3.矩阵的行列式
行列式为矩阵的一个基本属性,通过行列式的计算,能够知道矩阵是否可逆,从而可以进一步求解矩阵所对应的线性方程。实际上行列式是矩阵进行线性变换的伸缩因子。
对于一个n维方阵,行列式的计算过程如下:
对于一个2*2的矩阵,行列式的计算就是主对角线元素之积减去另外两个元素之积
A = torch.tensor([[1, 2], [4, 5]]).float() # 秩的计算要求是浮点型张量
A
tensor([[1., 2.],
[4., 5.]])
torch.det(A)
tensor(-3.)
B
tensor([[1., 2.],
[2., 4.]])
torch.det(B)
tensor(-0.)
行列式的计算过程如下:
注: 只有方阵才能计算行列式
B = torch.arange(1, 7).reshape(2, 3)
B
tensor([[1, 2, 3],
[4, 5, 6]])
torch.det(B)
---------------------------------------------------------------------------
RuntimeError Traceback (most recent call last)
<ipython-input-5-beff1455abd9> in <module>
----> 1 torch.det(B)
RuntimeError: A must be batches of square matrices, but they are 3 by 2 matrices
3.线性方程组的矩阵表达形式
A = torch.arange(1, 5).reshape(2, 2).float()
A
tensor([[1., 2.],
[3., 4.]])
import matplotlib as mpl
import matplotlib.pyplot as plt
plt.plot(A[:, 0], A[:, 1], 'o')
在二维空间中找到一条直线拟合这两个点,也即构建一个线性回归模型,设置线性回归方程如下:
带入(1,2)和(3,4)两个点之后,我们还可以进一步将表达式改写成矩阵表示形式,改写过程如下
而用矩阵表示线性方程组,则是矩阵的另一种常用用途,接下来,我们就可以通过上述矩阵方程组来求解系数向量x。
首先一个基本思路是,如果有个和A矩阵相关的另一个矩阵,假设为 A − 1 A^{-1} A−1,可以使得二者相乘之后等于1,也就是 A ∗ A − 1 = 1 A * A^{-1} = 1 A∗A−1=1,那么在方程组左右两边同时左乘该矩阵,等式右边的计算结果 A − 1 ∗ B A^{-1} * B A−1∗B就将是x系数向量的取值。而此处的 A − 1 A^{-1} A−1就是所谓的A的逆矩阵。
逆矩阵定义:
在上述线性方程组求解场景中,我们已经初步看到了逆矩阵的用途,而一般来说,我们往往会通过伴随矩阵来进行逆矩阵的求解。由于伴随矩阵本身并无其他核心用途,且PyTorch中也未给出伴随矩阵的计算函数(目前),因此我们直接调用inverse函数来进行逆矩阵的计算。
注:并非所有矩阵都有逆矩阵,对于一个矩阵来说,首先必须是方阵,其次矩阵的秩不能为零,满足两个条件才能求解逆矩阵。
- inverse函数:求解逆矩阵
首先,根据上述矩阵表达式,重新定义A和B
A = torch.tensor([[1.0, 1], [3, 1]])
A
tensor([[1., 1.],
[3., 1.]])
B = torch.tensor([2.0, 4])
B
tensor([2., 4.])
然后使用inverse函数进行逆矩阵求解
torch.inverse(A)
tensor([[-0.5000, 0.5000],
[ 1.5000, -0.5000]])
逆矩阵的一些特性:
torch.mm(torch.inverse(A), A)
tensor([[ 1.0000e+00, -5.9605e-08],
[-1.1921e-07, 1.0000e+00]])
torch.mm(A, torch.inverse(A))
tensor([[ 1.0000e+00, -5.9605e-08],
[-1.1921e-07, 1.0000e+00]])
然后在方程组左右两边同时左乘 A − 1 A^{-1} A−1,求解x
torch.mv(torch.inverse(A), B)
tensor([1.0000, 1.0000])
最终得到线性方程为:
四、矩阵的分解
常见的矩阵分解有QR分解、LU分解、特征分解、SVD分解等
1.特征分解
特征分解中,矩阵分解形式为:
Q
Λ
Q
−
1
Q\Lambda Q^{-1}
QΛQ−1
其中,
Q
Q
Q和
Q
−
1
Q^{-1}
Q−1互为逆矩阵,并且
Q
Q
Q的列就是A的特征值所对应的特征向量,而
Λ
\Lambda
Λ为矩阵A的特征值按照降序排列组成的对角矩阵
- torch.eig函数:特征分解
A = torch.arange(1, 10).reshape(3, 3).float()
A
tensor([[1., 2., 3.],
[4., 5., 6.],
[7., 8., 9.]])
torch.eig(A, eigenvectors=True) # 注,此处需要输入参数为True才会返回矩阵的特征向量
torch.return_types.eig(
eigenvalues=tensor([[ 1.6117e+01, 0.0000e+00],
[-1.1168e+00, 0.0000e+00],
[-1.2253e-07, 0.0000e+00]]),
eigenvectors=tensor([[-0.2320, -0.7858, 0.4082],
[-0.5253, -0.0868, -0.8165],
[-0.8187, 0.6123, 0.4082]]))
输出结果中,eigenvalues表示特征值向量,即A矩阵分解后的 Λ \Lambda Λ矩阵的对角线元素值,并按照又大到小依次排列,eigenvectors表示A矩阵分解后的 Q Q Q矩阵,此处需要理解特征值,所谓特征值,可简单理解为对应列在矩阵中的信息权重,如果该列能够简单线性变换来表示其他列,则说明该列信息权重较大,反之则较小。特征值概念和秩的概念有点类似,但不完全相同,矩阵的秩表示矩阵列向量的最大线性无关数,而特征值的大小则表示某列向量能多大程度解读矩阵列向量的变异度,即所包含信息量,秩和特征值关系可用下面这个例子来进行解读。
B = torch.tensor([1, 2, 2, 4]).reshape(2, 2).float()
B
tensor([[1., 2.],
[2., 4.]])
torch.matrix_rank(B)
tensor(1)
torch.eig(B) # 返回结果中只有一个特征
torch.return_types.eig(
eigenvalues=tensor([[0., 0.],
[5., 0.]]),
eigenvectors=tensor([]))
C = torch.tensor([[1, 2, 3], [2, 4, 6], [3, 6, 9]]).float()
C
tensor([[1., 2., 3.],
[2., 4., 6.],
[3., 6., 9.]])
torch.eig(C) # 只有一个特征的有效值
torch.return_types.eig(
eigenvalues=tensor([[ 1.4000e+01, 0.0000e+00],
[-1.6447e-07, 0.0000e+00],
[ 2.8710e-07, 0.0000e+00]]),
eigenvectors=tensor([]))
特征值一般用于表示矩阵对应线性方程组解空间以及数据降维,当然,由于特征分解只能作用于方阵,而大多数实际情况下矩阵行列数未必相等,此时要进行类似的操作就需要采用和特征值分解思想类似的奇异值分解(SVD)。
2.奇异值分解
奇异值分解(SVD)来源于代数学中的矩阵分解问题,对于一个方阵来说,我们可以利用矩阵特征值和特征向量的特殊性质(矩阵点乘特征向量等于特征值数乘特征向量),通过求特征值与特征向量来达到矩阵分解的效果
A
=
Q
Λ
Q
−
1
A = Q\Lambda Q^{-1}
A=QΛQ−1
这里,Q是由特征向量组成的矩阵,而Λ是特征值降序排列构成的一个对角矩阵(对角线上每个值是一个特征值,按降序排列,其他值为0),特征值的数值表示对应的特征的重要性。
在很多情况下,最大的一小部分特征值的和即可以约等于所有特征值的和,而通过矩阵分解的降维就是通过在
Q
Q
Q、
Λ
\Lambda
Λ 中删去那些比较小的特征值及其对应的特征向量,使用一小部分的特征值和特征向量来描述整个矩阵,从而达到降维的效果。
但是,实际问题中大多数矩阵是以奇异矩阵形式,而不是方阵的形式出现的,奇异值分解是特征值分解在奇异矩阵上的推广形式,它将一个维度为m×n的奇异矩阵A分解成三个部分 :
A
=
U
∑
V
T
A = U\sum V^{T}
A=U∑VT
其中
U
U
U、
V
V
V是两个正交矩阵,其中的每一行(每一列)分别被称为左奇异向量和右奇异向量,他们和
∑
∑
∑中对角线上的奇异值相对应,通常情况下我们只需要保留前k个奇异向量和奇异值即可,其中
U
U
U是m×k矩阵,
V
V
V是n×k矩阵,
∑
∑
∑是k×k的方阵,从而达到减少存储空间的效果,即
A
m
∗
n
=
U
m
∗
m
∑
m
∗
n
V
n
∗
n
T
≈
U
m
∗
k
∑
k
∗
k
V
k
∗
n
T
A_{m*n} = U_{m*m}\sum_{m*n}V^{T}_{n*n}\approx U_{m*k}\sum_{k*k}V^{T}_{k*n}
Am∗n=Um∗mm∗n∑Vn∗nT≈Um∗kk∗k∑Vk∗nT
- svd奇异值分解函数
C
tensor([[1., 2., 3.],
[2., 4., 6.],
[3., 6., 9.]])
torch.svd(C)
torch.return_types.svd(
U=tensor([[-0.2673, -0.8018, -0.5345],
[-0.5345, -0.3382, 0.7745],
[-0.8018, 0.4927, -0.3382]]),
S=tensor([14.0000, 0.0000, 0.0000]),
V=tensor([[-0.2673, 0.0000, 0.9636],
[-0.5345, -0.8321, -0.1482],
[-0.8018, 0.5547, -0.2224]]))
CU, CS, CV = torch.svd(C)
# 验证SVD分解
torch.diag(CS)
tensor([[14.0000, 0.0000, 0.0000],
[ 0.0000, 0.0000, 0.0000],
[ 0.0000, 0.0000, 0.0000]])
torch.mm(torch.mm(CU, torch.diag(CS)), CV.t())
tensor([[1.0000, 2.0000, 3.0000],
[2.0000, 4.0000, 6.0000],
[3.0000, 6.0000, 9.0000]])
能够看出,上述输出完整还原了C矩阵,此时我们可根据svd输出结果对C进行降维,此时C可只保留第一列(后面的奇异值过小),即k=1
U1 = CU[:, 0].reshape(3, 1) # U的第一列
U1
tensor([[-0.2673],
[-0.5345],
[-0.8018]])
C1 = CS[0] # C的第一个值
C1
tensor(14.0000)
V1 = CV[:, 0].reshape(1, 3) # V的第一行
V1
tensor([[-0.2673, -0.5345, -0.8018]])
torch.mm((U1 * C1), V1)
tensor([[1.0000, 2.0000, 3.0000],
[2.0000, 4.0000, 6.0000],
[3.0000, 6.0000, 9.0000]])
由上述代码片段可以看出经过还原后的矩阵与原始矩阵高度相似,损失的信息基本可以忽略不计,经过SVD分解,矩阵的信息能够被压缩至更小的空间内进行存储,从而为PCA(主成分分析)、LSI(潜在语义索引)等算法做好了数学工具层面的铺垫。
注: 除了利用逆矩阵求解线性方程组系数外,比较通用的方法是使用最小二乘法进行求解
- torch.lstsq:最小二乘法
torch.lstsq(B.reshape(2, 1), A)
torch.return_types.lstsq(
solution=tensor([[1.0000],
[1.0000]]),
QR=tensor([[-3.1623, -1.2649],
[ 0.7208, -0.6325]]))
x, q = torch.lstsq(B.reshape(2, 1), A)
x
tensor([[1.0000],
[1.0000]])
q
tensor([[-3.1623, -1.2649],
[ 0.7208, -0.6325]])
- solve函数与LU分解
torch.solve(B.reshape(2, 1), A)
torch.return_types.solve(
solution=tensor([[1.],
[1.]]),
LU=tensor([[3.0000, 1.0000],
[0.3333, 0.6667]]))
- LU分解函数
torch.lu(A)
(tensor([[3.0000, 1.0000],
[0.3333, 0.6667]]),
tensor([2, 2], dtype=torch.int32))
标签:特征值,tensor,04,reshape,torch,矩阵,张量,t1,线性代数 来源: https://blog.csdn.net/weixin_42170119/article/details/121221528