Lesson 4.张量的线性代数运算
? ? ? ?也就是PyTorch中BLAS和LAPACK模塊的相關運算。
??PyTorch中并未設置單獨的矩陣對象類型,因此PyTorch中,二維張量就相當于矩陣對象,并且擁有一系列線性代數相關函數和方法。
??在實際機器學習和深度學習建模過程中,矩陣或者高維張量都是基本對象類型,而矩陣所涉及到的線性代數理論也是深度學習用戶必備的基本數學基礎。因此,本節在介紹張量的線性代數運算時,也會回顧基本的矩陣運算,及其基本線性代數的數學理論基礎,以期在強化張量的線性代數運算過程中,也進一步夯實同學的線性代數數學基礎。
??另外,在實際的深度學習建模過程中,往往會涉及矩陣的集合,也就是三維甚至是四維張量的計算,因此在部分場景中,我們也將把二維張量計算拓展到更高維的張量計算。
一、BLAS和LAPACK概覽
??BLAS(Basic Linear Algeria Subprograms)和LAPACK(Linear Algeria Package)模塊提供了完整的線性代數基本方法,由于涉及到函數種類較多,因此此處對其進行簡單分類,具體包括:
- 矩陣的形變及特殊矩陣的構造方法:包括矩陣的轉置、對角矩陣的創建、單位矩陣的創建、上/下三角矩陣的創建等;
- 矩陣的基本運算:包括矩陣乘法、向量內積、矩陣和向量的乘法等,當然,此處還包含了高維張量的基本運算,將著重探討矩陣的基本運算拓展至三維張量中的基本方法;
- 矩陣的線性代數運算:包括矩陣的跡、矩陣的秩、逆矩陣的求解、伴隨矩陣和廣義逆矩陣等;
- 矩陣分解運算:特征分解、奇異值分解和SVD分解等。
相關內容如果涉及數學基礎,將在講解過程中逐步補充。
二、矩陣的形變及特殊矩陣構造方法
??矩陣的形變方法其實也就是二維張量的形變方法,在此基礎上本節將補充轉置的基本方法。另外,在實際線性代數運算過程中,經常涉及一些特殊矩陣,如單位矩陣、對角矩陣等,相關創建方法如下:
# 創建一個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]])三、矩陣的基本運算
??矩陣不同于普通的二維數組,其具備一定的線性代數含義,而這些特殊的性質,其實就主要體現在矩陣的基本運算上。課程中常見的矩陣基本運算如下所示:
矩陣的基本運算
- dot\vdot:點積計算
? ? ? ? 注意,在PyTorch中,dot和vdot只能作用于一維張量,且對于數值型對象,二者計算結果并沒有區別,兩種函數只在進行復數運算時會有區別。更多復數運算的規則,我們將在涉及復數運算的場景中再進行詳細說明。
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:矩陣和向量相乘
? ? ? ? PyTorch中提供了一類非常特殊的矩陣和向量相乘的函數,矩陣和向量相乘的過程我們可以看成是先將向量轉化為列向量然后再相乘。
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])? ? ? ? 理解:mv函數本質上提供了一種二維張量和一維張量相乘的方法,在線性代數運算過程中,有很多矩陣乘向量的場景,典型的如線性回歸的求解過程,通常情況下我們需要將向量轉化為列向量(或者某些編程語言就默認向量都是列向量)然后進行計算,但PyTorch中單獨設置了一個矩陣和向量相乘的方法,從而簡化了行/列向量的理解過程和將向量轉化為列向量的轉化過程。
- 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]]]) ''''1234左乘123456......'''Point:
- 三維張量包含的矩陣個數需要相同;
- 每個內部矩陣,需要滿足矩陣乘法的條件,也就是左乘矩陣的行數要等于右乘矩陣的列數。
- addmm:矩陣相乘后相加
addmm函數結構:addmm(input, mat1, mat2, beta=1, alpha=1)
輸出結果:beta * input + alpha * (mat1 * mat2)
- 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]]) # 12+106+296+1 = 415注:addbmm會在原來三維張量基礎之上,對其內部矩陣進行求和
四、矩陣的線性代數運算
??如果說矩陣的基本運算是矩陣基本性質,那么矩陣的線性代數運算,則是我們利用矩陣數據類型在求解實際問題過程中經常涉及到的線性代數方法,具體相關函數如下:
矩陣的線性代數運算
? ? ? ? 同時,由于線性代數所涉及的數學基礎知識較多,從實際應用的角度出發,我們將有所側重的介紹實際應用過程中需要掌握的相關內容,并通過本節末尾的實際案例,來加深線性代數相關內容的理解。
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計算矩陣的秩
3.矩陣的行列式(det)
??所謂行列式,我們可以簡單將其理解為矩陣的一個基本性質或者屬性,通過行列式的計算,我們能夠知道矩陣是否可逆,從而可以進一步求解矩陣所對應的線性方程。當然,更加專業的解釋,行列式的作為一個基本數學工具,實際上就是矩陣進行線性變換的伸縮因子。
對于任何一個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.)A的行列式計算過程如下:
?對于行列式的計算,要求二維張量必須是方正,也就是行列數必須一致。
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 matrices3.線性方程組的矩陣表達形式
??在正式進入到更進一步矩陣運算的討論之前,我們需要對矩陣建立一個更加形象化的理解。通常來說,我們會把高維空間中的一個個數看成是向量,而由這些向量組成的數組看成是一個矩陣。例如:(1,2),(3,4)是二維空間中的兩個點,矩陣A就代表這兩個點所組成的矩陣。
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矩陣相關的另一個矩陣,假設為𝐴^(?1),可以使得二者相乘之后等于1,也就是𝐴?𝐴^(?1)=1,那么在方程組左右兩邊同時左乘該矩陣,等式右邊的計算結果𝐴^(?1)?𝐵就將是x系數向量的取值。而此處的𝐴^(?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]])然后在方程組左右兩邊同時左乘𝐴^(?1),求解x
torch.mv(torch.inverse(A), B) #tensor([1.0000, 1.0000])最終得到線性方程為:
當然,上述計算過程只是一個簡化的線性方程組求解系數的過程,同時也是一個簡單的一元線性方程擬合數據的過程,關于常用求解線性方程組系數的最小二乘法,可以先閱讀本節末尾的選讀內容,更多線性回歸相關內容,我們將在下周進行詳細講解。
五、矩陣的分解
??矩陣的分解也是矩陣運算中的常規計算,矩陣分解也有很多種類,常見的例如QR分解、LU分解、特征分解、SVD分解等等等等,雖然大多數情況下,矩陣分解都是在形式上將矩陣拆分成幾種特殊矩陣的乘積,但本質上,矩陣的分解是去探索矩陣更深層次的一些屬性。本節將主要圍繞特征分解和SVD分解展開講解,更多矩陣分解的運算,我們將在后續課程中逐漸進行介紹。值得一提的是,此前的逆矩陣,其實也可以將其看成是一種矩陣分解的方式,分解之后的等式如下:
而大多數情況下,矩陣分解都是分解成形如下述形式
1.特征分解
特征分解中,矩陣分解形式為:
?其中,Q和𝑄^(?1)互為逆矩陣,并且Q的列就是A的特征值所對應的特征向量,而Λ為矩陣A的特征值按照降序排列組成的對角矩陣。
- torch.eig函數:特征分解
輸出結果中,eigenvalues表示特征值向量,即A矩陣分解后的Λ矩陣的對角線元素值,并按照由大到小依次排列,eigenvectors表示A矩陣分解后的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)
??奇異值分解(SVD)來源于代數學中的矩陣分解問題,對于一個方陣來說,我們可以利用矩陣特征值和特征向量的特殊性質(矩陣點乘特征向量等于特征值數乘特征向量),通過求特征值與特征向量來達到矩陣分解的效果
這里,Q是由特征向量組成的矩陣,而Λ是特征值降序排列構成的一個對角矩陣(對角線上每個值是一個特征值,按降序排列,其他值為0),特征值的數值表示對應的特征的重要性。 在很多情況下,最大的一小部分特征值的和即可以約等于所有特征值的和,而通過矩陣分解的降維就是通過在Q、Λ 中刪去那些比較小的特征值及其對應的特征向量,使用一小部分的特征值和特征向量來描述整個矩陣,從而達到降維的效果。 但是,實際問題中大多數矩陣是以奇異矩陣形式,而不是方陣的形式出現的,奇異值分解是特征值分解在奇異矩陣上的推廣形式,它將一個維度為m×n的奇異矩陣A分解成三個部分 :
其中U、V是兩個正交矩陣,其中的每一行(每一列)分別被稱為左奇異向量和右奇異向量,他們和∑中對角線上的奇異值相對應,通常情況下我們只需要保留前k個奇異向量和奇異值即可,其中U是m×k矩陣,V是n×k矩陣,∑是k×k的方陣,從而達到減少存儲空間的效果,即?
- svd奇異值分解函數
驗證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]])此時輸出的Cd矩陣已經和原矩陣C高度相似了,損失信息在R的計算中基本可以忽略不計,經過SVD分解,矩陣的信息能夠被壓縮至更小的空間內進行存儲,從而為PCA(主成分分析)、LSI(潛在語義索引)等算法做好了數學工具層面的鋪墊。?
本節選讀內容
另外,我們需要知道的是,除了利用逆矩陣求解線性方程組系數外,比較通用的方法是使用最小二乘法進行求解:
- torch.lstsq:最小二乘法 (LeaST SQuare (最小二乘))
??最小二乘法是最通用的線性方程擬合求解工具,我們可以利用最小二乘法的直接計算擬合直線的系數最優解。當然,本節僅介紹最小二乘法的函數調用,下節在介紹目標函數和優化手段時,還將進一步介紹最小二乘法的數學原理。
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]])我們發現,最小二乘法返回了兩個結果,分別是x的系數和QR分解后的QR矩陣。
- solve函數與LU分解
- LU分解函數
總結
以上是生活随笔為你收集整理的Lesson 4.张量的线性代数运算的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Lesson 3.张量的广播和科学运算
- 下一篇: Lesson 5.基本优化思想与最小二乘