Đại số tuyến tính là một nhánh của toán học liên quan đến các phương trình tuyến tính và các phương trình này thường được biểu diễn thông qua các phép tính trong ma trận hoặc vectơ. Trong Numpy, cung cấp sẵn cho chúng ta khá nhiều hàm liên quan đến đại số tuyến tính. Một số hàm có thể kể đến như, hàm nhân 2 ma trận (dot matrix), hàm tính nghịch đảo ma trận, hàm giải phương trình tuyến tính, hàm tính định thức ma trận.…trong bài hôm nay chúng ta sẽ cùng nhau tìm hiểu các hàm đại số tuyến tính này trong Numpy!

1. Nhân hai vectơ (vector dot) trong Numpy

Phép tích vectơ hay nhân vectơ hay còn được gọi là tích có hướng của vectơ là một phép toán nhị nguyên. Khi đó hai vectơ sẽ thực hiện nhân các thành phần của vectơ này với thành phần của vectơ kia sau đó thực hiện phép tính tổng.

Trong Numpy, để thực hiện phép nhân hai vectơ, ta cần sử dụng hàm np.vdot() và truyền vào hai vectơ có kích thước giống nhau.

from traceback import print_tb
import numpy as np 

# Vector gom 4 thanh phan
a = np.array([1, 2, 3, 4]) 
print("Vector a")
print(a)

# Vector gom 4 thanh phan
b = np.array([5, 6, 7, 8])
print("Vector b")
print(b) 

# Nhan 2 vector
x = np.vdot(a,b)
print("Vector a x b")
print(x)

Kết quả:

Vector a
[1 2 3 4]
Vector b
[5 6 7 8]
Vector a x b
70

Kết quả trên được tính như sau:

[1*5 + 2*6 + 3*7 + 4*8] = 70

Trong trường hợp chúng ta nhập vào 2 ma trận và yêu cầu thực hiện phép nhân 2 ma trận cho hàm np.vdot() thì hàm này sẽ tự động làm phẳng 2 ma trận này, nghĩa là chuyển ma trận về vectơ và sau đó mới thực hiện phép nhân 2 vectơ:

import numpy as np 

# Ma tran 2 x 2
a = np.array([
    [1,2],
    [3,4]
]) 
print("Ma tran a")
print(a)

# Ma tran 2 x 2
b = np.array([
    [11,12],
    [13,14]
]) 
print("Ma tran b")
print(b) 

# Lam phang 2 ma tran va thuc hien nhan 2 ma tran
x = np.vdot(a,b)
print("Lam phang 2 ma tran va nhan 2 vector a x b")
print(x)

Kết quả:

Ma tran a
[[1 2]
 [3 4]]
Ma tran b
[[11 12]
 [13 14]]
Lam phang 2 ma tran va nhan 2 vector a x b
130

Kết quả trên được tính như sau:

[1*11 + 2*12 + 3*13 + 4*14] = 130

2. Nhân hai ma trận (dot matrix) trong Numpy

Trong toán học, phép nhân ma trận là phép toán nhị phân tạo ra ma trận từ hai ma trận. Điều kiện cần để nhân 2 ma trận đó là số lượng cột trong ma trận thứ nhất phải bằng số lượng hàng trong ma trận thứ hai. Phép nhân được thực hiện sẽ tạo ra ma trận kết quả là tích ma trận, có số lượng hàng của ma trận đầu tiên và số cột của ma trận thứ hai.

Trong Numpy, để thực hiện phép nhân hai ma trận, ta cần sử dụng hàm np.dot() và truyền vào hai ma trận có kích thước thỏa mãn điều kiện nhân 2 ma trận ở trên.

from traceback import print_tb
import numpy as np 

# Ma tran kich thuoc 3 x 3
a = np.array([
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
]) 
print("Ma tran a")
print(a)

# Ma tran kich thuoc 3 x 2
b = np.array([
    [2,3],
    [4,5],
    [6,7]
])
print("Ma tran b")
print(b) 

# Nhan 2 ma tran
x = np.dot(a,b)
print("Ma tran a x b")
print(x)

Kết quả:

Ma tran a
[[1 2 3]
[4 5 6]
[7 8 9]]
Ma tran b
[[2 3]
[4 5]
[6 7]]
Ma tran a x b
[[28 34]
 [64 79]
 [100 124]]

Kết quả trên được tính như sau:

[1*2 + 2*4 + 3*6, 1*3 + 2*5 + 3*7]   [28 34]
[4*2 + 5*4 + 6*6, 4*3 + 5*5 + 6*7] = [64 79]
[7*2 + 8*4 + 9*6, 7*3 + 8*5 + 9*7]   [100 124]

3. Chuyển vị ma trận (transpose) trong Numpy

Ma trận chuyển vị là một ma trận Acủa ma trận A. Khi đó, số dòng của ma trận A sẽ được chuyển thành số cột của ma trận Avà số cột của ma trận A sẽ được chuyển thành số dòng của ma trận AT

Ví dụ dưới đây, sử dụng hàm transpose() để tạo ra một ma trận chuyển vị của ma trận A trong Numpy:

import numpy as np

# Khai bao mot ma tran 2 x 3
A = np.array([
    [-2, 5, 6],
    [5, 2, 7]]
)

# Hien thi ma tran A ban dau
print("Ma tran A")
print(A)

# Ma tran chuyen vi cua ma tran A
AT = A.transpose()

# Hien thi ma tran chuyen vi cua ma tran A
print("Ma tran chuyen vi cua A")
print(AT)

Kết quả:

Ma tran A
[[-2  5  6]
 [ 5  2  7]]
Ma tran chuyen vi cua A
[[-2  5]
 [ 5  2]
 [ 6  7]]

Lưu ý: Khi một ma trận có kích thước m x n được chuyển vị, khi đó ma trận chuyển vị sẽ có kích thước n x m.

4. Định thức ma trận (determinant) trong Numpy

Định thức trong đại số tuyến tính là một hàm cho mỗi ma trận vuông A, tương ứng với số vô hướng, ký hiệu là det(A). Định thức được sử dụng để giải và biện luận các hệ phương trình đại số tuyến tính và chỉ được tính trong ma trận vuông có kích thước n x n.

Trong Numpy, hàm np.linalg.det() được sử dụng để tính toán định thức của ma trận đầu vào. Ví dụ dưới đây, tính định thức của ma trận 2 x 2 như sau:

import numpy as np 

# Ma tran 2 x 2
a = np.array([
    [3, 4],
    [1, 2]
]) 
print("Ma tran a")
print(a)

# Tinh dinh thuc cua ma tran a
d = np.linalg.det(a)
print("Dinh thuc cua ma tran a")
print(d)

Kết quả:

Ma tran a
[[3 4]
 [1 2]]
Dinh thuc cua ma tran a
2.000000000000000

Kết quả trên được tính như sau:

3*2 - 4-1 = 2

Ví dụ tiếp theo, tính định thức của ma trận kích thước 3 x 3 bằng hàm np.linalg.det() như sau:

import numpy as np 

# Ma tran 3 x 3
a = np.array([
    [3, 4, 0],
    [1, 2, 5],
    [7, 4, 8]
]) 
print("Ma tran a")
print(a)

# Tinh dinh thuc cua ma tran a
d = np.linalg.det(a)
print("Dinh thuc cua ma tran a")
print(d)

Kết quả:

Ma tran a
[[3 4 0]
 [1 2 5]
 [7 4 8]]
Dinh thuc cua ma tran a
96.0000

5. Nghịch đảo ma trận (inverse matrix) trong Numpy

Một ma trận khả nghịch hay ma trận không suy biến là một ma trận vuông và có ma trận nghịch đảo trong phép nhân ma trận. Ma trận nghịch đảo của ma trận A được ký hiệu là A^-1 và được tính theo công thức:

Trong Numpy, để tính nghịch đảo của một ma trận, ta sẽ cần sử dụng hàm np.linalg.inv() – hàm này nhận vào một ma trận có kích thước n x n sau đó thực hiện tính và trả về một ma trận nghịch đảo của ma trận ban đầu.

Ví dụ dưới đây, sử dụng hàm np.linalg.inv() để tính nghich đảo của một ma trận vuông có kích thước 3 x 3 như sau:

import numpy as np 

# Ma tran 3 x 3
a = np.array([
    [3, 4, 0],
    [1, 2, 5],
    [7, 4, 8]
]) 
print("Ma tran a")
print(a)

# Tinh nghich dao cua ma tran a
inv = np.linalg.inv(a) 
print("Nghich dao cua ma tran a")
print(inv)

Kết quả:

Ma tran a
[[3 4 0]
 [1 2 5]
 [7 4 8]]
Nghich dao cua ma tran a
[[-0.04166667 -0.33333333  0.20833333]
 [ 0.28125     0.25       -0.15625   ]
 [-0.10416667  0.16666667  0.02083333]]

Ví dụ tiếp theo, tính nghịch đảo của ma trận vuông 2 x 2 bằng hàm np.linalg.inv() như sau:

import numpy as np 

# Ma tran 2 x 2
a = np.array([
    [3, 4], 
    [1, 2]
]) 
print("Ma tran a")
print(a)

# Tinh nghich dao cua ma tran a
inv = np.linalg.inv(a) 
print("Nghich dao cua ma tran a")
print(inv)

Kết quả:

Ma tran a
[[3 4]
 [1 2]]
Nghich dao cua ma tran a
[[ 1.  -2. ]
 [-0.5  1.5]]

6. Hạng của ma trận (matrix rank) trong Numpy

Hạng (rank) của một ma trận Asố chiều của không gian vectơ được sinh (span) bởi các vectơ cột của nó. Điều này nghĩa là hạng của ma trận A chính là số cột độc lập tuyến tính tối đa của A. Hạng của một ma trận thường được ký hiệu là: rank(A) hoặc rank A

Trong Numpy, để tìm ra hạng của một ma trận, khi đó ta sẽ chỉ cần sử dụng hàm np.linalg.matrix_rank() để thực hiện việc này.

import numpy as np

# Ma tran 3 x 3
a = np.array([
    [3, 4, 0],
    [1, 2, 5],
    [7, 4, 8]
]) 
print("Ma tran a")
print(a)

# Tinh hang cua ma tran a
r = np.linalg.matrix_rank(a)
print("Hang cua ma tran a la")
print(r)

Kết quả:

Ma tran a
[[3 4 0]
 [1 2 5]
 [7 4 8]]
Hang cua ma tran a la
3

7. Tìm nghiệm hệ phương trình tuyến tính trong Numpy

Hệ phương trình tuyến tính là một phương trình toán học với tập hợp nhiều phương trình và nhiều ẩn. Ví dụ một hệ phương trình tuyến tính dưới đây gồm 3 phương trình và ba ẩn x, y, z – việc của chúng ta là giải phương trình để tìm ra các ẩn số x, y, z sao cho thỏa mãn vế trái bằng vế phải phương trình.

Trong Numpy, hàm np.linalg.solve() được sử dụng cung cấp nghiệm của phương trình tuyến tính ở dạng ma trận. Ví dụ chúng ta có hệ phương trình tuyến tính sau:

3x + 2y - z = 1
2x - 2y + 4z = -2
-x + 1/2y - z = 0

Hệ phương trình tuyến tính trên có thể tách ra thành các ma trận và thực hiện giải nghiệm như sau:

Ma tran he so
   [[3, 2 -1]      
A = [2, -2 4]
    [-1 1/2 -1]] 

Ma tran an
    [x]
X = [y]
    [z]

Ma tran he so tu do
    [1]
B = [-2]
    [0]

Ta co he phuong trinh tuyen tinh tu cac ma tran tren
A*X = B

Giai he phuong trinh tuyen tinh
=> X = A^-1 * B
        
       [1]
=> X = [-2]
       [-2]

Lưu ý: Trong công thức X = A^-1 * B ta dễ dàng nhận thấy rằng ma trận A^-1 chính là ma trận nghịch đảo của ma trận A.

Khi thao tác với Numpy, chúng ta sẽ không cần thực hiện giải tay để tìm các nghiệm này, thay vào đó ta sẽ cần sử dụng hàm np.linalg.solve()truyền vào ma trận hệ số (ma trận A) và ma trận hệ số tự do (ma trận B) kết quả của hàm sẽ trả về một ma trận nghiệm (ma trận X)

import numpy as np 

# He phuong trinh tuyen tinh
"""
3x + 2y - z = 1 
2x - 2y + 4z = -2 
-x + 1/2y - z = 0
"""
# Lap ma tran he so 
A = np.array([
    [3, 2, -1],
    [2, -2, 4],
    [-1, 1/2, -1]
]) 
print("Ma tran he so A")
print(A)

# Lap ma tran he so tu do
B = np.array([1, -2, 0]) 
print("Ma tran he so tu do B")
print(B)

# Giai nghiem cua he phuong trinh
X = np.linalg.solve(A, B)
print("Ma tran nghiem X")
print(X)

Kết quả:

Ma tran he so A
[[ 3.   2.  -1. ]
 [ 2.  -2.   4. ]
 [-1.   0.5 -1. ]]
Ma tran he so tu do B
[ 1 -2  0]
Ma tran nghiem X
[ 1. -2. -2.]

Kết luận: Việc giải nghiêm của ma trận bằng hàm np.linalg.solve() hoàn toàn cho ra kết quả trùng với khi sử dụng phương pháp tính theo công thức X = A^-1 * B.