楔子
下面我們來一起復習一下線性代數的基礎知識,並同時使用numpy進行演示,所以需要你有一些關於numpy的知識(但不需要太多)。另外在線性代數中,存在行列式和矩陣,它們長得都差不多,都類似於二維表的格式。但是行列式要求其行數和列數必須相等,但是矩陣則沒有此要求,而我們在創建在numpy中創建行列式和矩陣的時候均使用numpy.array這個函數,這個函數創建的是數組,我們使用數組來模擬行列式和矩陣。這一點要知曉。
行列式
全排列與逆序數
全排列:
把n個不同的元素排成一列,叫做這n個元素的全排列(或排列)
n個不同元素進行排列,各個元素的位置不同,那么排列的結果也不同。我們將其所有可能出現的排列的總個數記作\(P_{n}\),且\(P_{n} = n!\)。很好理解,n個元素對應n個位置,第一個元素有n種選擇、第二個元素有n-1種選擇、...、最后一個元素只有一種選擇,因此\(P_{n}\)就是n的階乘。
import itertools
import math
# 計算n個元素所有可能出現的排列的總個數,以及n的階乘
def calc(lst: list):
return len(list(itertools.permutations(lst, len(lst)))), math.factorial(len(lst))
for _ in range(1, 11):
print(calc(list(range(_))))
"""
(1, 1)
(2, 2)
(6, 6)
(24, 24)
(120, 120)
(720, 720)
(5040, 5040)
(40320, 40320)
(362880, 362880)
(3628800, 3628800)
"""
# 手動將n個元素按照不同的順序進行全排列,然后計算所有可能出現的排列的總個數
# 然后再計算n的階乘,當n取1到10的時候,我們驗證它們是一樣的。
# 當然無論n取多大它們都是一樣的,只不過元素過多的話,創建其所有可能出現的排列會花不少時間
逆序數:
在一個排列\((i_{1},i_{2},...,i_{t},...,i_{s},...i_{n})\)中,如果\(i_{t}>i_{s}\),則稱這兩個數構成一個逆序。一個排列中所有的逆序的總數稱之為此排列的逆序數
看定義似乎有點難理解,說人話就是:對於排列中的任意一個數,只要它前面存在比它大的數,那么前面那個大的數和該數就構成了一個逆序。
比如:[3, 2, 4, 1, 5]這個排列:
第一個元素3前面沒有數,所以沒有元素能和它構成逆序,那么3這個元素的逆序數是0
而元素2的前面是3,3比2大,所以3和2就構成了一個逆序,那么2這個元素的逆序數就是1,因為存在一個逆序嘛。
元素4的前面是3和2,沒有比它大的數,所以也沒有元素能和它構成逆序,那么4這個元素的逆序數也是0
元素1的前面是3、2、4,三個元素都比它大,所以都可以和1構成逆序,那么1的逆序數就是3,因為前面存在3個比它大的元素、也就是有3個逆序
最后一個元素是5,前面沒有比它大的元素,所以5的逆序數是0
然后將每一個元素的逆序數加起來,就稱之為這個排列的逆序數,所以上面那個排列的逆序數就是:0 + 1 + 0 + 3 + 0 = 4
import numpy as np
# 計算排列的逆序數
def calc(lst):
arr = np.array(lst)
return np.sum([np.sum(arr[: idx] > val) for idx, val in enumerate(lst)])
# 我們剛剛舉例的那個排列
print(calc([3, 2, 4, 1, 5])) # 4
# 順序排列,顯然逆序數是0
print(calc(list(range(10)))) # 0
# 倒序排列,n個元素的排列,那么逆序數顯然是:1 + 2 + 3 + ... + n - 1
# 在紙上一寫馬上就能看出,當然聰明如你肯定在腦海里就能明白
print(calc(list(range(1, 101)[:: -1]))) # 4950
另外,當一個排列的逆序數為奇數,那么稱這個排列為奇排列;當一個排列的逆序數為偶數,那么稱這個排列為偶排列
n階行列式的概念
什么是n階行列式呢?形如:
\(\begin{vmatrix}a_{11} & a_{12} & \cdots & a_{1n} \\ a_{21} & a_{22} & \cdots & a_{2n} \\\vdots & \vdots & \ddots & \vdots\\ a_{n1} & a_{n2} & \cdots & a_{nn} \end{vmatrix}\)
這樣的式子,我們稱之為行列式,它的行數和列數是相等的。其中:\(a_{11}、a_{22}、a_{33}、...、a_{nn}\)成為行列式的主對角線;\(a_{1n}、...、a_{n1}\)稱為行列式的次對角線
另外這個行列式是可以計算的,任何一個行列式,最終計算的結果都是一個標量。但是怎么計算我們后面會說,也會演示如何使用numpy計算一個行列式的值。
另外,如果把行列式中的某一個元素所在的行和列都去掉,那么會得到一個n-1階的行列式,這個n-1階行列式就叫做這個元素的余子式。

以圖中的\(a_{23}\)為例,將其所在的行和列去掉,那么剩下的行列式(階數顯然會減1)就是\(a_{23}\)的余子式。
因此對於n階行列式來說,如果將元素\(a_{ij}\)(i表示第幾行、j表示第幾列)所在的行和列去掉,那么剩下的n-1階行列式就稱之為\(a_{ij}\)的余子式,記作\(M_{ij}\)
另外:\(A_{ij} = (-1)^{i+j}M_{ij}\),記作\(A_{ij}\)為元素\(a_{ij}\)的代數余子式
以上就是余子式和代數余子式。
n階行列式的計算
下面我們來看看如何計算一個n階行列式,我們先以2階行列式為例。
\(\begin{vmatrix}a & b \\ c & d \end{vmatrix} = ad - bc\)
所以:\(\begin{vmatrix}5 & 3 \\ 2 & 6 \end{vmatrix} = 5 * 6 - 3 * 2 = 24\)
import numpy as np
determinant = np.array([[5, 3], [2, 6]])
print(determinant)
"""
[[5 3]
[2 6]]
"""
# 計算行列式
print(np.linalg.det(determinant)) # 23.999999999999993
numpy計算行列式有一套自己快速的方法,計算任意階的行列式都可以使用這個方法。而numpy內部在計算的時候,可能會出現浮點數上的誤差,當然這不是numpy的問題,而是計算機在存儲浮點數的時候很容易出現這個結果。比如:在計算3和4的平方和、然后再開方,我們都知道是5,但是對於程序來講,它得出的結果可能是4.9999999999(當然未必會出現,只是舉個例子)。總之numpy計算的結果和24在大小上是沒有什么區別的,可以認為是相等的。
print(np.linalg.det(determinant).round()) # 24.0
或者加上一個.round(),會四舍五入取整。
三階行列式的計算:
\(\begin{vmatrix}a & b & c \\ d & e & f \\ x & y & z \end{vmatrix} = aez + cdy + xbf - cex - zbd - afy\)
所以:\(\begin{vmatrix}1 & 2 & 3 \\ 4 & 5 & 6 \\ 7 & 8 & 9 \end{vmatrix} = 1 * 5 * 9 + 3 * 4 * 8 + 7 * 2 * 6 - 3 * 5 * 7 - 9 * 2 * 4 - 1 * 6 * 8 = 45 + 96 + 84 - 105 - 72 - 48 = 0\)
import numpy as np
determinant = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(determinant)
"""
[[1 2 3]
[4 5 6]
[7 8 9]]
"""
print(np.linalg.det(determinant)) # 0.0
四階行列式的計算:
四階行列式的計算,選取任意的一行或者一列,注意:是任意的一行或者一列。然后讓每個元素乘上其對應的代數余子式,然后加起來就得到了該四階行列式的值。

四階行列式有四行、四列,隨便選擇一行或者一列,這里我們選擇第一行。然后讓每個元素乘上對應的代數余子式,再加起來就是這個四階行列式的值。我們看到第二項和第四項前面是符號,這是因為2所在的行是第一行、所在的列是第三列,所以\((-1)^{1+2} = -1\),同理4在第一行、第四列,因此\((-1)^{1+4} = -1\)。所以,我們說乘的是代數余子式,而不是余子式。
至於計算的話,我們就不演算了,同理對於更高階的行列式也是如此。但是顯然這個計算量是非常大的,但是不要緊,我們有numpy,numpy內部的方法可以灰常快速的計算行列式的值。
import numpy as np
determinant = np.arange(1, 17).reshape((4, 4))
print(determinant)
"""
[[ 1 2 3 4]
[ 5 6 7 8]
[ 9 10 11 12]
[13 14 15 16]]
"""
print(np.linalg.det(determinant)) # 0.0
這里我們只介紹到四階,對於高階也可以這么做,但是會很麻煩。所以行列式的展開是有技巧的,我們可以通過對行和列進行一些變換來簡化運算過程,后面也會說一些行列式在變換時的特點。
但只會進行說明,至於一個高階行列式具體應該怎么轉換、比如:如何轉換成上三角、下三角,以及高斯消元等等,我們只會提一嘴,但不會具體演示怎么計算的。而是會使用numpy,因為筆者是個程序猿,不是學生了,所以對於一個高階行列式,不會手動計算,而是會直接將數據交給numpy來計算,這一點還請理解。
import numpy as np
determinant = np.random.randint(1, 10, (8, 8))
print(determinant)
"""
[[5 3 7 7 2 6 6 1]
[6 2 3 5 9 5 3 7]
[3 1 5 6 5 7 3 7]
[9 8 9 2 9 1 5 2]
[7 9 6 4 3 8 1 9]
[8 2 7 3 3 1 8 7]
[1 5 6 5 4 4 4 2]
[3 4 8 5 5 9 6 1]]
"""
print(np.linalg.det(determinant).round()) # -2904450.0
我們即便是高階行列式,也是可以使用這個方法計算的,而且速度非常快。即使是100階的行列式,也可以瞬間出結果。
n階行列式的性質
行列式都具有哪些性質呢?我們來看一下
1.行列式和它的轉置行列式是相等的
import numpy as np
determinant = np.random.randint(1, 10, (5, 5))
print(determinant)
"""
[[4 1 1 7 1]
[1 9 1 7 9]
[8 1 1 4 1]
[8 1 4 8 3]
[8 4 4 6 2]]
"""
print(determinant.T)
"""
[[4 1 8 8 8]
[1 9 1 1 4]
[1 1 1 4 4]
[7 7 4 8 6]
[1 9 1 3 2]]
"""
print(np.linalg.det(determinant).round(), np.linalg.det(determinant.T).round()) # -3510.0 -3510.0
2.將行列式的某兩行或者某兩列交換位置,那么行列式會變號
import numpy as np
determinant = np.random.randint(1, 10, (5, 5))
print(determinant)
"""
[[2 1 9 9 9]
[2 2 2 1 5]
[9 1 8 4 9]
[9 7 3 2 7]
[3 7 1 2 8]]
"""
determinant_1 = determinant.copy()
# 將第1行和第4行交換位置
determinant_1[[0, 3]] = determinant_1[[3, 0]]
print(determinant_1)
"""
[[9 7 3 2 7]
[2 2 2 1 5]
[9 1 8 4 9]
[2 1 9 9 9]
[3 7 1 2 8]]
"""
print(np.linalg.det(determinant).round(), np.linalg.det(determinant_1).round()) # -641.0 641.0
3.行列式如果有兩行或者兩列完全相同,那么此行列式的值為0
import numpy as np
determinant = np.random.randint(1, 10, (5, 5))
print(determinant)
"""
[[7 8 2 5 2]
[3 4 5 2 5]
[3 5 1 5 8]
[6 6 9 8 4]
[1 5 5 3 1]]
"""
# 讓第3行和第1行相同
determinant[2] = determinant[0]
print(determinant)
"""
[[7 8 2 5 2]
[3 4 5 2 5]
[7 8 2 5 2]
[6 6 9 8 4]
[1 5 5 3 1]]
"""
print(np.linalg.det(determinant).round()) # 0.0
4.行列式整體乘上一個常數k,等價於行列式的某一行或者某一列乘上一個常數k
5.由第4個性質,我們也可以推出,如果某一行或者某一列有公約數,那么這個公約數可以提到行列式的外面。
6.由第3、第5兩個性質,我們也可以推出,如果有兩行或者兩列成比例,那么這個行列式的值為0
7.若行列式的某一行或者某一列都是兩個元素之和,則該行列式等於兩個行列式之和
我們來解釋一下第七個性質:
import numpy as np
determinant = np.array([
[1, 2, 3],
[2, 3, 4],
[11 + 12, 5 + 8, 17 + 1]
])
determinant1 = np.array([
[1, 2, 3],
[2, 3, 4],
[11, 5, 17]
])
determinant2 = np.array([
[1, 2, 3],
[2, 3, 4],
[12, 8, 1]
])
# 我們將determinant拆分成了determinant1和determinant2
print(np.linalg.det(determinant).round()) # -15.0
print(np.linalg.det(determinant1).round(), np.linalg.det(determinant2).round()) # -18.0 3.0
8.將行列式的某一行或者某一列乘上一個常數k,然后加到另外的一行或者一列,行列式的值不變。
為什么會有這個性質,可以結合前面幾條思考一下。
上三角和下三角行列式
主對角線下方元素全為0的行列式稱為上三角行列式,上方元素全為0的行列式稱為下三角行列式。無論是上三角行列式還是下三角行列式,它們的值都等於主對角線上所有元素的乘積。
import numpy as np
determinant = np.array([
[1, 0, 0, 0],
[2, 3, 0, 0],
[4, 5, 6, 0],
[4, 5, 6, 8],
])
# 我們將determinant拆分成了determinant1和determinant2
print(np.linalg.det(determinant).round(), 1 * 3 * 6 * 8) # 144.0 144
print(determinant.T)
"""
[[1 2 4 4]
[0 3 5 5]
[0 0 6 6]
[0 0 0 8]]
"""
print(np.linalg.det(determinant.T).round(), 1 * 3 * 6 * 8) # 144.0 144
克萊默法則
如果線性方程組
\(\begin{cases}a_{11}x_{1} + a_{12}x_{2} + \cdots + a_{1n}x_{n} = b_{1}, \\a_{21}x_{1} + a_{22}x_{2} + \cdots + a_{2n}x_{n} = b_{2}, \\ ... ... ... ... ...\\ a_{n1}x_{1} + a_{n2}x_{2} + \cdots + a_{nn}x_{n} = b_{n},\end{cases}\)
的系數行列式D不等於0,那么它有唯一解,\(x_{j} = \frac{D_{j}}{D}, j=1,2,3,...,n\)。其中\(D_{j}\)是把系數行列式D中第j列換成\(b_{1},b_{2},b_{3},...,b{n}\)所得到的行列式。
如果線性方程組
\(\begin{cases}a_{11}x_{1} + a_{12}x_{2} + \cdots + a_{1n}x_{n} = 0, \\a_{21}x_{1} + a_{22}x_{2} + \cdots + a_{2n}x_{n} = 0, \\ ... ... ... ... ...\\ a_{n1}x_{1} + a_{n2}x_{2} + \cdots + a_{nn}x_{n} = 0,\end{cases}\)
的系數行列式D不等於0,那么它沒有非零解。首先根據上面我們知道有唯一解,但如果常數項\(b_{1},b_{2},...,b_{n}\)全是0,那么這個唯一解一定是零解,也就是\(x_{1},x_{2},...,x_{n}\)只可能全部是0。但如果該線性方程組真的存在非零解,那么系數行列式的值必為0,如果不為0,那么不會存在非零解。
矩陣
矩陣,我們在numpy中依舊使用array的方式來創建
矩陣的概念
由m × n個數\(a_{ij}(i=1,2,3,...,m;j=1,2,3,...,n)\)排列而成的m行n列的數表
\(\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}\)
成為m行n列矩陣,簡稱m × n矩陣。這m×n個數稱之為該矩陣的元素,簡稱為"元"
同型矩陣和矩陣相等:
1.兩個矩陣的行數和列數都相等,那么稱這兩個矩陣為同型矩陣2.兩個矩陣為同型矩陣,並且對應位置的元素都相等,那么稱這兩個矩陣相等。
因此兩個矩陣如果想要相等,那么它的必要條件是這兩個矩陣首先要是同型矩陣,否則元素不會一一對應。
import numpy as np
arr1 = np.array([
[1, 2, 3],
[2, 3, 4]
])
arr2 = np.array([
[1, 2, 3],
[2, 3, 4]
])
# arr1和arr2就是同型矩陣
print(arr1 == arr2)
"""
[[ True True True]
[ True True True]]
"""
# 但是numpy會返回每一個元素比較的結果
# 可以使用np.all
print(np.all(arr1 == arr2)) # True
幾種特殊的矩陣
1.通過矩陣的概念我們得知,矩陣和行列式不同,行列式要求行數和列數必須一致,但是矩陣則沒有此要求。但是,如果矩陣的行數和列數也相等的話,那么這個矩陣稱之為方陣。行數和列數相等都為n,那么就稱這個矩陣為n階方陣
2.如果這個矩陣只有一行,稱為行矩陣(或行向量);該矩陣只有一列,那么稱為列矩陣(或列向量)。所以我們看到向量實際上是矩陣只有一行或者一列的特殊情況。
但是在numpy中,即使是行矩陣和列矩陣,我們依舊使用二維數組表示
import numpy as np
arr1 = np.array([[1, 2, 3]])
arr2 = np.array([[1], [2], [3]])
print(arr1)
"""
[[1 2 3]]
"""
print(arr2)
"""
[[1]
[2]
[3]]
"""
3.主對角線上的元素不全為零,而其它元素全為零的矩陣,稱為對角矩陣。記作:\(A = diag(λ_{1},λ_{2},...,λ{n})\)
4.元素全部為0的矩陣稱之為零矩陣。注意:階數不同的零矩陣是不相等的,因為它們不是同型矩陣
5.主對角線上的元素全部為1的對角矩陣,稱之為單位矩陣。所以首先是對角矩陣,然后主對角線上的元素全部是1。單位矩陣,一般用E表示
import numpy as np
# numpy生成單位矩陣
# 表示生成5階單位矩陣
arr = np.eye(5)
print(arr)
"""
[[1. 0. 0. 0. 0.]
[0. 1. 0. 0. 0.]
[0. 0. 1. 0. 0.]
[0. 0. 0. 1. 0.]
[0. 0. 0. 0. 1.]]
"""
# 另外
arr = np.eye(5, M=3)
# 我們看到如果加上一個參數M=3,那么等於在原來的基礎上只選三列
# M默認為None,如果給定一個具體的值,那么會選擇指定個數的列。如果超過了總列數,會用0填充
print(arr)
"""
[[1. 0. 0.]
[0. 1. 0.]
[0. 0. 1.]
[0. 0. 0.]
[0. 0. 0.]]
"""
arr = np.eye(5, k=1)
# 單位矩陣是主對角線上全部為1
# 如果指定k=1,那么會在主對角線往上平移一個單位,將對應的元素變成1
print(arr)
"""
[[0. 1. 0. 0. 0.]
[0. 0. 1. 0. 0.]
[0. 0. 0. 1. 0.]
[0. 0. 0. 0. 1.]
[0. 0. 0. 0. 0.]]
"""
# 同理k為負數,則是往下,這里k=-2
arr = np.eye(5, k=-2)
print(arr)
"""
[[0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0.]
[1. 0. 0. 0. 0.]
[0. 1. 0. 0. 0.]
[0. 0. 1. 0. 0.]]
"""
另外,一個矩陣的主對角線上所有元素的和稱之為矩陣的"跡"
import numpy as np
arr = np.random.randint(1, 10, (5, 5))
print(arr)
"""
[[5 2 9 3 7]
[1 4 9 6 2]
[6 8 1 9 9]
[4 3 4 6 5]
[9 2 5 7 3]]
"""
# 計算矩陣的跡
print(np.trace(arr)) # 19
6.對於一個n階方陣A,如果滿足\(a_{ij} = a_{ji}\),那么稱A為對稱矩陣。也就是說,元素關於主對角線是對稱的。
該矩陣:\(A = \begin{bmatrix}12&6&1\\6&8&0\\1&0&6\end{bmatrix}\)就是一個對稱矩陣,因為元素關於主對角線是對稱的。
7.對於一個矩陣:
\(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}\)
其行列式|A|的每個元素的代數余子式\(A_{ij}\)所構成矩陣
\(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}\)
稱之為A的伴隨矩陣
對於矩陣A和伴隨矩陣\(A^{*}\),有:\(AA^{*} = A^{*}A = |A|E\)
矩陣的運算
矩陣可以做加法,兩個同型矩陣相加,等於將矩陣中的對應元素相加。
import numpy as np
A = np.array([[1, 2, 3], [2, 3, 4]])
B = np.array([[11, 22, 33], [22, 33, 44]])
print(A)
"""
[[1 2 3]
[2 3 4]]
"""
print(B)
"""
[[11 22 33]
[22 33 44]]
"""
print(A + B)
"""
[[12 24 36]
[24 36 48]]
"""
但是在numpy中,即使形狀不同也是可以相加的,會進行廣播。矩陣相加有如下性質:
A + B = B + A(A + B) + C = A + (B + C)矩陣A里面的所有元素都乘以-1,得到的矩陣成為A的負矩陣,記作-AA + (-A) = 零矩陣,A - B = A + (-B)
矩陣可以和一個數相乘,等於矩陣內每一個元素都和這個數相乘。
所以這一點和行列式不同,行列式乘上一個數,等於行列式里面的某一行或者某一列和這個數相乘;但是矩陣和一個數相乘,等於矩陣內每一個元素都和這個數相乘。
import numpy as np
A = np.array([[1, 2, 3], [2, 3, 4]])
B = np.array([[11, 22, 33], [22, 33, 44]])
print(A)
"""
[[1 2 3]
[2 3 4]]
"""
print(A * 3)
"""
[[ 3 6 9]
[ 6 9 12]]
"""
矩陣和一個數相乘有如下特點:
(λμ)A = λ(μA)(λ + μ)A = λA + μAλ(A + B) = λA + λB
矩陣相加和數乘結合起來,成為矩陣的線性運算。另外在numpy中,支持的運算操作遠比目前介紹的豐富。
矩陣可以和矩陣相乘
關於矩陣和矩陣相乘,比如:A和B相乘,要求A矩陣的列數必須等於B矩陣的行數。然后A矩陣的每一行乘以B矩陣的每一列,最終得到的矩陣的行數等於A矩陣的行數、列數等於B矩陣的列數
import numpy as np
A = np.array([[1, 2], [2, 3], [3, 4]])
B = np.array([[1, 2, 3], [2, 3, 4]])
print(A)
"""
[[1 2]
[2 3]
[3 4]]
"""
print(B)
"""
[[1 2 3]
[2 3 4]]
"""
# 注意:這里我們要說明一下,我們目前創建的是數組,不是矩陣。只是用數組來模擬矩陣
# 所以不能A * B,而是要通過A @ B,來表示矩陣之間的乘法運算
print(A @ B)
"""
[[ 5 8 11]
[ 8 13 18]
[11 18 25]]
"""
# 因為*表示乘法運算,如果使用*,那么要先將數組轉化成矩陣。
# 但是np.matrix這個類要被移除了,不推薦使用了
print(np.matrix(A) * np.matrix(B))
"""
[[ 5 8 11]
[ 8 13 18]
[11 18 25]]
"""
# 因此雖然我們創建的是數組,但是可以使用@來進行矩陣之間乘法
# 既然A @ B表示矩陣之間的乘法,那A * B呢?
# A * B則是相當於矩陣的點乘,也就是A和B對應位置的元素直接相乘
A = np.array([[1, 2], [2, 3], [3, 4]])
B = np.array([[1, 2], [2, 3], [3, 4]])
print(A * B)
"""
[[ 1 4]
[ 4 9]
[ 9 16]]
"""
# 我們看到就是A和B對應位置的元素進行相乘。
# 所以矩陣的乘法和矩陣的點乘之間是不同的,矩陣的乘法是A矩陣的每一行和B矩陣的每一列進行內積
# 但是矩陣的點乘,則是對應位置的元素進行相乘。只不過我們目前創建的是數組,不是矩陣罷了
# 所以需要使用np.matrix轉成矩陣,再相乘。或者數組之間使用@操作符,表示直接按照矩陣相乘的方式進行運算。
# 至於數組本身之間的乘法,則是類似矩陣的點乘,也就是對應位置的元素進行相乘
矩陣相乘具有如下性質:
矩陣乘法一般不滿足交換律,即AB≠BA。其實一般來說,AB可以相乘,往往BA並不能相乘若矩陣滿足AB=零矩陣,並不能說明A或者B中間有一個是零矩陣
import numpy as np
A = np.array([[1, -1], [1, -1]])
B = np.array([[1, -1], [1, -1]])
print(A @ B)
"""
[[0 0]
[0 0]]
"""
(AB)C = A(BC)
import numpy as np
A = np.array([[2, 3], [1, -7]])
B = np.array([[-5, 1], [2, -5]])
C = np.array([[4, 2], [2, 6]])
print((A @ B) @ C)
"""
[[-42 -86]
[ -4 178]]
"""
print(A @ (B @ C))
"""
[[-42 -86]
[ -4 178]]
"""
A(B + C) = AB + AC
import numpy as np
A = np.array([[2, 3], [1, -7]])
B = np.array([[-5, 1], [2, -5]])
C = np.array([[4, 2], [2, 6]])
print(A @ (B + C))
"""
[[ 10 9]
[-29 -4]]
"""
print(A @ B + A @ C)
"""
[[ 10 9]
[-29 -4]]
"""
λ(AB) = (λA)B = A(λB)
import numpy as np
A = np.array([[2, 3], [1, -7]])
B = np.array([[-5, 1], [2, -5]])
print(3 * (A @ B))
"""
[[-12 -39]
[-57 108]]
"""
print((3 * A) @ B)
"""
[[-12 -39]
[-57 108]]
"""
print(A @ (3 * B))
"""
[[-12 -39]
[-57 108]]
"""
AE = EA = A, 單位矩陣E在矩陣乘法中類似於數字運算中的1
import numpy as np
A = np.array([[2, 3], [1, -7]])
print(A)
"""
[[ 2 3]
[ 1 -7]]
"""
print(A @ np.eye(2))
"""
[[ 2. 3.]
[ 1. -7.]]
"""
若A是n階方陣,則A的m次方乘上A的k次方,等於A的m+k次方;A先m次方、然后整體在k次方,等於A的m乘k次方。注意:但是一般來說,AB的k次方,不等於A次方乘上B的k次方
import numpy as np
A = np.random.randint(1, 10, (4, 4))
# 我們說數組之間的@,等價於矩陣的*
# 但是數組來說,如果使用@實現矩陣的冪次方運算比較費勁,其實也不算費勁
# 不過最方便的辦法還是將這里的數組轉為矩陣吧,這樣就可以使用**操作符了
A = np.matrix(A)
print(np.all(A ** 5 * A ** 8 == A ** (5 + 8))) # True
print(np.all(
(A ** 5) ** 8 == A ** (5 * 8)
)) # True
矩陣的轉置
把矩陣的A的行換成同序數的列所得到的矩陣叫做A的轉置矩陣,記作:\(A^{T}\)
import numpy as np
A = np.random.randint(1, 10, (2, 3))
print(A)
"""
[[6 1 9]
[9 4 8]]
"""
# 雖然我們創建的是數組,但是大部分情況下可以直接當成矩陣來用
# 直接調用A.T即可得到轉置矩陣
print(A.T)
"""
[[6 9]
[1 4]
[9 8]]
"""
注意:如果A為對稱矩陣,那么有\(A = A^{T}\)
轉置矩陣有如下性質:
- \((A^{T})^{T} = A\)
import numpy as np
A = np.random.randint(1, 10, (2, 3))
print(A)
"""
[[7 4 3]
[9 9 9]]
"""
print(A.T.T)
"""
[[7 4 3]
[9 9 9]]
"""
- \((A + B)^{T} = A^{T} + B^{T}\)
import numpy as np
A = np.random.randint(1, 10, (2, 3))
B = np.random.randint(1, 10, (2, 3))
print((A + B).T)
"""
[[ 8 7]
[15 12]
[13 11]]
"""
print(A.T + B.T)
"""
[[ 8 7]
[15 12]
[13 11]]
"""
- \((λA)^{T} = λA^{T}\)
import numpy as np
A = np.random.randint(1, 10, (2, 3))
print((3 * A).T)
"""
[[18 3]
[24 15]
[ 9 21]]
"""
print(3 * A.T)
"""
[[18 3]
[24 15]
[ 9 21]]
"""
- \((AB)^{T} = B^{T}A^{T}\)
import numpy as np
A = np.random.randint(1, 10, (2, 3))
B = np.random.randint(1, 10, (2, 3))
print((A * B).T)
"""
[[18 2]
[49 6]
[ 2 18]]
"""
print(B.T * A.T)
"""
[[18 2]
[49 6]
[ 2 18]]
"""
方陣的行列式
由n階方陣A的所有元素所構成的行列式,叫做方陣A的行列式,記作|A|或者det A。如果該方陣的行列式值為0,那么稱這個矩陣為奇異矩陣;值不為0,那么稱這個矩陣為非奇異矩陣。
- \(|A^{T}| = |A|\)
import numpy as np
A = np.random.randint(1, 10, (3, 3))
print(np.linalg.det(A).round(), np.linalg.det(A.T).round()) # 248.0 248.0
- \(|λA| = λ^{n}|A|\),這里是λ的n次方,很好理解,我們說矩陣和數相乘,那么這個數會作用於所有的元素,因此相當於每一行都提出來一個λ。總共有n行,那么對於行列式來講就是λ的n次方。
import numpy as np
A = np.random.randint(1, 10, (3, 3))
print(np.linalg.det(A * 5).round(), np.linalg.det(A).round() * 5 ** 3) # 12125.0 12125.0
- \(|AB| = |A||B|\)
import numpy as np
A = np.random.randint(1, 10, (3, 3))
B = np.random.randint(1, 10, (3, 3))
print(np.linalg.det(A @ B).round(), (np.linalg.det(A) * np.linalg.det(B)).round()) # 3737.0 3737.0
- \(|AB| = |BA|\)
import numpy as np
A = np.random.randint(1, 10, (3, 3))
B = np.random.randint(1, 10, (3, 3))
print(np.linalg.det(A @ B).round(), np.linalg.det(B @ A).round()) # 2622.0 2622.0
逆矩陣
對於n階方陣A,如果有一個n階方陣B,是的AB=BA=E,那么說方陣A是可逆的,並把方陣B叫做方陣A的逆矩陣。另外,A的逆矩陣也叫作\(A^{-1}\)
import numpy as np
A = np.random.randint(1, 10, (3, 3))
# 求逆矩陣B
B = np.linalg.inv(A)
print(B)
"""
[[ 0.25 -0.22916667 0.14583333]
[-0.75 0.60416667 -0.02083333]
[ 1. -0.58333333 -0.08333333]]
"""
print((A @ B).round())
"""
[[ 1. 0. -0.]
[-0. 1. -0.]
[ 0. 0. 1.]]
"""
我們說一個矩陣如果有逆矩陣,那么這個矩陣首先要是一個方陣。但是在numpy中,即使不是方陣, 也可以求出逆矩陣。
import numpy as np
A = np.random.randint(1, 10, (3, 5))
# 求逆矩陣使用inv,如果不是方陣,那么使用pinv
B = np.linalg.pinv(A)
print(B)
"""
[[-0.07192369 0.07431035 0.04447579]
[ 0.01839564 -0.00424631 0.02602996]
[ 0.05700139 -0.08014275 0.07874739]
[-0.01293216 -0.00438183 0.037499 ]
[ 0.05127439 0.07520378 -0.105209 ]]
"""
print((A @ B).round())
"""
[[ 1. 0. -0.]
[-0. 1. -0.]
[ 0. 0. 1.]]
"""
逆矩陣有如下性質:
- 方陣A可逆的充要條件是|A|≠0,且\(A^{-1} = \frac{1}{|A|}A^{*}\),其中\(A^{*}\)為A的伴隨矩陣
import numpy as np
A = np.array(
[
[2, 2],
[3, 3]
]
)
# 此矩陣對應的行列式值為0
print(np.linalg.det(A).round()) # 0.0
# 我們來求A的逆矩陣,我們說一個矩陣如果有逆矩陣,那么行列式的值必不為0
try:
print(np.linalg.inv(A))
except Exception as e:
print(e) # Singular matrix
# 報錯了,提示這個矩陣是奇異矩陣
- 若方陣A可逆,則\(A^{-1}\)可逆,且\((A^{-1})^{-1} = A\)
import numpy as np
A = np.array(
[
[5, 12],
[30, 3]
]
)
print(np.linalg.inv(np.linalg.inv(A)))
"""
[[ 5. 12.]
[30. 3.]]
"""
- 若方陣A可逆,且λ≠0,則λA可逆,且\((λA)^{-1} = \frac{1}{λ}A^{-1}\)。
import numpy as np
A = np.array(
[
[5, 12],
[30, 3]
]
)
print(np.linalg.inv(3 * A))
"""
[[-0.00289855 0.0115942 ]
[ 0.02898551 -0.00483092]]
"""
print(np.linalg.inv(A) * 1 / 3)
"""
[[-0.00289855 0.0115942 ]
[ 0.02898551 -0.00483092]]
"""
- 若方陣A,B為同型方陣且均可逆,則AB可逆,且\((AB)^{-1} = B^{-1}A^{-1}\)。
import numpy as np
A = np.random.randint(1, 10, (3, 3))
B = np.random.randint(1, 10, (3, 3))
print(np.linalg.inv(A @ B))
"""
[[ 0.01923077 -0.05 0.04807692]
[-0.00925926 0.08703704 -0.10648148]
[-0.01210826 -0.00925926 0.05306268]]
"""
print(np.linalg.inv(B) @ np.linalg.inv(A))
"""
[[ 0.01923077 -0.05 0.04807692]
[-0.00925926 0.08703704 -0.10648148]
[-0.01210826 -0.00925926 0.05306268]]
"""
-
若方陣A可逆,則\(A^{T}\)可逆,且\((A^{T})^{-1} = (A^{-1})^{T}\)。
另外,當|A|≠0、k為整數時,定義\(A^{0} = E\),\(A^{-k} = (A^{-1})^{k}\);當|A|≠0,λ、μ為整數時,\(A^{λ}A^{μ} = A^{λ + μ}\),\((A^{λ})^{μ} = A^{λμ}\),當然這個我們上面其實說過了
import numpy as np
A = np.random.randint(1, 10, (3, 3))
print(np.linalg.inv(A.T))
"""
[[ 0.16666667 0.16666667 -0.16666667]
[-0.04545455 -0.40909091 0.31818182]
[-0.15151515 0.3030303 0.06060606]]
"""
print(np.linalg.inv(A).T)
"""
[[ 0.16666667 0.16666667 -0.16666667]
[-0.04545455 -0.40909091 0.31818182]
[-0.15151515 0.3030303 0.06060606]]
"""
- 若A可逆,則有\(|A^{-1}| = |A|^{-1}\)
import numpy as np
A = np.random.randint(1, 10, (3, 3))
print(np.linalg.det(np.linalg.inv(A.T)))
"""
-0.010869565217391302
"""
print(np.linalg.det(A) ** -1)
"""
-0.010869565217391302
"""
矩陣的秩
在m×n矩陣A中任取k行k列(k≤m,k≤n,按照從上到下、從左到右的順序),所得到的k階行列式,稱之為A的k階子式。
而矩陣A的秩就是A中不為0的子式的最高階數,記作R(A)
import numpy as np
A = np.random.randint(1, 10, (8, 8))
print(A)
"""
[[3 2 9 8 1 4 9 8]
[9 8 8 5 6 8 9 4]
[1 9 2 1 2 9 3 7]
[8 2 5 2 6 1 5 5]
[1 6 3 1 4 1 4 2]
[5 5 9 9 2 5 8 5]
[1 7 9 1 3 5 5 9]
[1 5 6 2 6 1 1 9]]
"""
# matrix_rank表示求矩陣的秩
print(np.linalg.matrix_rank(A)) # 8
# 將最后一行全部改成0
A[-1] = 0
print(np.linalg.matrix_rank(A)) # 7
"""
首先該矩陣A是8階方陣,那么顯然它的子式最高階就是8,也就是矩陣A本身對應的行列式。而該行列式的值是不為0的,所以它的秩是8
然后我們將最后一行變成了0,顯然A本身對應的行列式的值變成了0,因此不存在不為0的8階子式。而且8階子式只有一個,就是A本身對應的行列式
既然8階沒有,那么選取7階,顯然7階是成立的。另外如果某個k階子式有多個,只要找到一個不為0的子式即可。
"""
# 其實如果你仔細學過線性代數的話,那么你應該知道一種很方便的求秩的方法
# 那就是將該矩陣通過線性變換轉化成階梯型,然后非零行的個數就是該矩陣的秩
# 不過至於怎么變換我們這里就不說了,因為計算的時候數據量一般會非常大,肯定不會手算。
# 我們只需要知道它的含義就可以了,至於計算的工作交給計算機
n維向量
向量以及向量組的概念
n個有次序的數\(a_{1}、a_{2}、a_{3}、...、a_{n}\)所組成的數組稱為n維向量(但是說實話,這樣的一個數組在numpy中實際上是一維的,里面有n個元素,但在數學上定義是n維的,所以要注意),這n個數稱為該向量的n個分量,第i個數\(a_{i}\)稱為第i個分量。
若干個同維數的行向量或者列向量所組成的集合叫做向量組
向量組的線性相關性
向量組表示向量
假設存在一個向量β和一個向量組\(A=(α_{1},α_{2},...,α_{n})\),那么我們可以使用向量組來表示該向量,如:\(β=λ_{1}α_{1}λ_{2}α_{2}+...+λ_{n}α_{n}\)。但是向量β能被向量A表示的充要條件是矩陣\(A=(α_{1},α_{2},...,α_{n})\)的秩等於矩陣\(B=(α_{1},α_{2},...,α_{n},b)\)的秩
線性相關和線性無關
對於一個向量組\(A=(α_{1},α_{2},...,α_{n})\),如果存在一組不全為0的數\(k_{1},k_{2},...,k_{n}\),使得\(k_{1}α_{1}+k_{2}α_{2}+...+k_{n}α_{n}=\vec{0}\),則稱向量組A是線性相關的,否則稱它為線性無關的。
所以由以上可得出:
任意一個向量組不是線性相關就是線性無關包含零向量的向量組一定線性相關單個非零向量組成的向量組一定是線性無關的兩個向量組成的向量組線性相關的充要條件是這兩個向量對應的分量成比例
另外,一個向量組\(A=(α_{1},α_{2},...,α_{n})\)線性相關的充分必要條件是它所構成的矩陣的秩小於向量組里向量的個數n,即R(A) < m;線性無關的充要條件則是R(A) = m
向量組的線性相關性有如下性質:
向量組內部分向量相關則整個向量組相關。很好理解,因為是一組不全為0的數,k1,k2...如果部分相關,那么讓剩下的全部為0即可。所以部分相關,那么整體也是相關的線性無關的向量組,將分量延長后也是線性無關的m個n維向量,當維數n小於m時,那么這個m個向量組成的向量組一定是線性相關的設向量組A: a1,a2,...,an線性無關,向量組B: a1,a2,...,an,b線性相關,則向量b一定可以被向量組A線性表示,並且是唯一的
最大無關組與向量組的秩
對於一個向量組A,如果能在A中選出r個向量,\(α_{1},α_{2},...,α_{r}\),滿足向量組\(A_{0}: α_{1},α_{2},...,α_{r}\)線性無關,並且向量組A中任意r+1個向量都線性相關,那么稱向量組\(A_{0}\)是向量組A的一個最大線性無關組。並且,最大線性無關組中向量的個數稱為向量組A的秩。
注意:只包含零向量的向量組沒有最大線性無關組,規定它的秩為0
所以向量組和矩陣還是比較類似,都是由多個行向量或者列向量所構成。所以矩陣的秩等於它行向量組的秩,也等於它列向量組的秩。
另外:如果一個向量組B能由向量組A線性表示,則向量組B的秩不大於向量組A的秩。
線性方程組
線程方程組有解的判定條件
定理一:n元齊次線性方程組\(A_{m×n}x=0\)有非零解的充要條件是系數矩陣的秩R(A)<n
定理二:n元非齊次線性方程組\(A_{m×n}x=b\)有解的充要條件是系數矩陣的秩R(A)等於增廣矩陣B=(A, b)的秩R(B)
線程方程組解的結構
基礎解系的定義
若向量組\(η_{1},η_{2},η_{3},...,η_{t}\)滿足:\(η_{1},η_{2},η_{3},...,η_{t}\)是Ax=0的一組線性無關的解,並且Ax=0的任意解都可由\(η_{1},η_{2},η_{3},...,η_{t}\)線性表示,那么稱\(η_{1},η_{2},η_{3},...,η_{t}\)為齊次線性方程組Ax=0的基礎解系。
如果向量組\(η_{1},η_{2},η_{3},...,η_{t}\)齊為次線性方程組Ax=0的基礎解系,那么Ax=0的通解可以表示為:\(x=k_{1}η_{1}+k_{2}η_{2}+...+k_{n}η_{n}, 其中k_{1},k_{2},...,k_{n}是任意常數\)。
但是說實話,我個人覺得一般方程存在基礎解系,那么基本上是等式的個數小於未知數的個數。比如:有三個等式,但是存在4個變量。
線性方程組的解法
形如\(A_{m×n}=0\)的齊次線性方程組:系數矩陣化成行最簡矩陣,便可寫出其通解
形如\(A_{m×n}=b\)的非齊次線性方程組:增廣矩陣化成階梯型矩陣,便可判斷其是否有解。如果有解, 化成行最簡矩陣,便可寫出其通解。
至於怎么化簡可以自己去搜索,這里我們來演示一下如何使用numpy來解方程
import numpy as np
"""
x+2y+z=7
2x-y+3z=7
3x+y+2z=18
求這個方程組的解
"""
# 首先將系數寫下來,排成一個矩陣,也就是得到系數矩陣
A = np.array([[1, 2, 1],
[2, -1, 3],
[3, 1, 2]])
# 將右邊的常數寫下來,排成一個矩陣
b = np.array([7, 7, 18])
# 求解,將參數傳進去
x = np.linalg.solve(A, b)
print(x) # [ 7. 1. -2.]
# 驗證,將系數矩陣和解相乘,會得到b
print(A @ x) # [ 7. 7. 18.]
矩陣的特征值和特征向量
定義
假設A是n階方陣,如果數λ和n維非零列向量x是關系式Ax=λx成立,那么這樣的數λ稱為方陣A的特征值,非零向量稱為A的特征向量。
所以把兩邊的x約掉,特征方程|A-λE|=0的根就是方陣A的特征值,注意:x是個向量,約掉得到單位矩陣E,而不是1。齊次線性方程(A - λE)=0的非零解就是對應的特征向量。
numpy其特征值和特征向量
import numpy as np
A = np.array([[3, -1], [-1, 3]])
w, v = np.linalg.eig(A)
print("特征值:", w)
"""
特征值: [4. 2.]
"""
print("特征向量:", v)
"""
特征向量: [[ 0.70710678 0.70710678]
[-0.70710678 0.70710678]]
"""
# 這個特征向量的值不止一種
# 當特征值為4的時候,特征向量為(x1, x2)且x1-x2=0
# 當特征值為2的時候,特征向量為(x1, x2)且x1+x2=0
