Đại số tuyến tính là một bộ môn toán học quan trọng và có ảnh hưởng mạnh mẽ trong nhiều lĩnh vực, từ khoa học máy tính đến khoa học dữ liệu và trí tuệ nhân tạo. Nó cung cấp cho chúng ta một bộ công cụ mạnh mẽ để mô hình hóa và giải quyết các vấn đề phức tạp.
Trong bài viết này, chúng ta sẽ cùng nhau tìm hiểu về một số kiến thức cốt lõi ứng dụng của Đại số tuyến tính được sử dụng trong biểu diễn dữ liệu và các cách triển khai lập trình tương ứng.
Số vô hướng
Nếu bạn chưa học đại số tuyến tính hoặc học máy, bạn có thể hiểu rằng số vô hướng là các con số đơn lẻ được sử dụng hàng ngày để đo đếm hay tính toán dựa trên con số đơn thuần dựa trên các phép tính Cộng, Trừ, Nhân, Chia.
Ví dụ khi nói rằng nhiệt độ ở Mỹ là 52oF (độ Fahrenheit) thì ta gọi giá trị này là một số vô hướng (scalar). Nếu bạn muốn chuyển nhiệt độ này sang độ C tương ứng thì có thể áp dụng công thức toán học c = 5/9(f-32) trong đó f có giá trị là 52. Trong công thức toán này thì 5 hay 9 hay 32 đều là số vô hướng và c f là đại diện trị số vô hướng chưa biết.
Trong bài viết này, chúng tôi sử dụng quy ước ký hiệu các biến vô hướng bằng các chữ cái viết thường (ví dụ: x, y, z). Sử dụng ký hiệu ℝ để chỉ không gian liên tục của tất cả các số thực vô hướng.
Biểu thức x ∈ ℝ được sử dụng trong toán học để chỉ rằng x là một số thực vô hướng. Ký hiệu ∈ được đọc là “thuộc” và đơn giản chỉ thể hiện sự thuộc về một phần tử trong một tập hợp. Tương tự, chúng ta có thể viết x, y ∈ {0,1} để chỉ rằng các số x và y chỉ có thể nhận giá trị 0 hoặc 1.
Sử dụng thư viện Apache MXNet chúng ta biểu diễn một số vô hướng bằng một mảng đa chiều ndarray chỉ chứa một phần tử duy nhất và đoạn mã dưới đây sẽ khởi tạo hai số vô hướng và thực hiện các phép tính cộng, trừ, nhân, chia và lũy thừa với chúng.
from mxnet import np, npx
npx.set_np()
x = np.array(3.0)
y = np.array(2.0)
x + y, x * y, x / y, x ** y
Kết quả nhận được:
(array(5.), array(6.), array(1.5), array(9.))
Vector
Một vector đơn thuần có thể được xem như một dãy các số vô hướng, và các giá trị đó được gọi là các phần tử hay thành phần của vector. Khi sử dụng vector để biểu diễn các mẫu trong tập dữ liệu, các giá trị của vector thường mang ý nghĩa thực tế.
Ví dụ, trong việc huấn luyện một mô hình dự đoán rủi ro vỡ nợ của một công ty, chúng ta có thể gán mỗi công ty một vector gồm các thành phần tương ứng với thời điểm thu thập dữ liệu, thời gian hoạt động, số lần chậm trả trước đó và các yếu tố khác. Trong trường hợp nghiên cứu rủi ro bị đau tim của bệnh nhân, chúng ta có thể biểu diễn mỗi bệnh nhân bằng một vector gồm các phần tử mang thông tin về dấu hiệu sống còn gần nhất, nồng độ cholesterol, số phút tập thể dục hàng ngày, v.v. Trong ký hiệu toán học, chúng ta thường ký hiệu vector bằng chữ cái in đậm viết thường (ví dụ: 𝐱, 𝐲, 𝐳).
Trong MXNet, chúng ta làm việc với vector thông qua các ndarray 1-chiều. Thông thường, ndarray có thể có chiều dài tùy ý, tùy thuộc vào giới hạn bộ nhớ của máy tính.
Ví dụ, chúng ta có đoạn mã python sau đây:
x = np.arange(4)
x
Chúng ta nhận được:
array([0., 1., 2., 3.])
Mỗi phần tử trong vector có thể được ký hiệu bằng chỉ số dưới. Ví dụ, chúng ta có thể viết 𝑥𝑖 để chỉ đến phần tử thứ 𝑖 của vector 𝐱. Lưu ý rằng phần tử 𝑥𝑖 là một số vô hướng, không được in đậm. Thông thường, vector được coi là vector cột là chiều mặc định, và quyển sách này cũng tuân theo quy ước đó. Trong toán học, một vector có thể được biểu diễn như sau:
𝐱 = [𝑥1 𝑥2 ⋮ 𝑥𝑛],
trong đó 𝑥1, …, 𝑥𝑛 là các phần tử của vector. Trong mã nguồn, chúng ta sử dụng chỉ số để truy cập các phần tử trong ndarray.
Ví dụ:
x[3]
Kết quả là:
array(3.)
Độ dài, chiều và kích thước của một vector
Một vector đơn thuần là một dãy các số. Tương tự như một dãy, mỗi vector cũng có một độ dài. Trong ký hiệu toán học, khi chúng ta muốn nói rằng một vector 𝐱 chứa 𝑛 số thực vô hướng, chúng ta có thể biểu diễn nó bằng 𝐱 ∈ ℝ𝑛. Độ dài của một vector còn được gọi là số chiều của vector.
Giống như một dãy thông thường trong Python, chúng ta có thể xem độ dài của một ndarray bằng cách sử dụng hàm len() có sẵn trong Python.
len(x) -> sẽ cho kết quả là 4
Khi một mảng đa chiều ndarray biểu diễn một vector (với chính xác một trục), chúng ta cũng có thể xem độ dài của nó thông qua thuộc tính .shape (kích thước). Kích thước là một tuple liệt kê độ dài (số chiều) theo từng trục của ndarray. Với các ndarray có duy nhất một trục, kích thước chỉ có một phần tử.
x.shape -> sẽ cho kết quả là (4,)
Ở đây, cần lưu ý rằng từ “chiều” là một từ đa nghĩa và khi sử dụng trong nhiều ngữ cảnh, thường dễ gây nhầm lẫn. Để làm rõ, chúng ta sử dụng thuật ngữ “số chiều” của một vector hoặc một trục để chỉ độ dài của nó, tức là số phần tử trong một vector hoặc một trục. Tuy nhiên, chúng ta sử dụng thuật ngữ “số chiều” của một ndarray để chỉ số trục của ndarray đó. Theo nghĩa này, số chiều của một trục trong một ndarray là độ dài của trục đó.
Ma trận
Ma trận là một cấu trúc dữ liệu quan trọng và linh hoạt trong toán học và lập trình. Tương tự như vector, ma trận có thể được sử dụng để biểu diễn và tổ chức các dữ liệu số theo các chiều khác nhau. Ma trận thường được ký hiệu bằng các ký tự hoa in đậm như X, Y, và Z và được biểu diễn dưới dạng các mảng hai chiều trong lập trình.
Trong ký hiệu toán học, một ma trận A thuộc miền số thực ℝ có kích thước m hàng và n cột được ký hiệu là A ∈ ℝᵐ×ⁿ. Một cách hình ảnh, ta có thể đại diện cho ma trận A ∈ ℝᵐ×ⁿ như một bảng, với phần tử aᵢⱼ nằm ở dòng thứ i và cột thứ j của bảng:
A = [[a₁₁ a₁₂ ... a₁ₙ]
[a₂₁ a₂₂ ... a₂ₙ]
[... ... ⋱ ...]
[aₘ₁ aₘ₂ ... aₘₙ]]
Với bất kỳ ma trận A ∈ ℝᵐ×ⁿ nào, kích thước của ma trận 𝐀 là (𝑚, 𝑛) hay 𝑚×𝑛. Trong trường hợp đặc biệt, khi một ma trận có số dòng bằng số cột, dạng của nó là một hình vuông; như vậy, nó được gọi là một ma trận vuông (square matrix).
Ta có thể tạo một ma trận 𝑚×𝑛 trong MXNet bằng cách khai báo kích thước của nó với hai thành phần 𝑚 và 𝑛 khi sử dụng bất kỳ hàm khởi tạo ndarray
nào mà ta thích.
A = np.arange(20).reshape(5, 4)
A
array([[ 0., 1., 2., 3.],
[ 4., 5., 6., 7.],
[ 8., 9., 10., 11.],
[12., 13., 14., 15.],
[16., 17., 18., 19.]])
Ta có thể truy cập phần tử vô hướng 𝑎𝑖𝑗 của ma trận 𝐀 trong :eqref:eq_matrix_def
bằng cách khai báo chỉ số dòng (𝑖) và chỉ số cột (𝑗), như là [𝐀]𝑖𝑗. Khi những thành phần vô hướng của ma trận 𝐀, như trong :eqref:eq_matrix_def
chưa được đưa ra, ta có thể sử dụng ký tự viết thường của ma trận 𝐀 với các chỉ số ghi dưới, 𝑎𝑖𝑗, để chỉ thành phần [𝐀]𝑖𝑗. Nhằm giữ sự đơn giản cho các ký hiệu, dấu phẩy chỉ được thêm vào để phân tách các chỉ số khi cần thiết, như 𝑎2,3𝑗 và [𝐀]2𝑖−1,3.
Đôi khi, ta muốn hoán đổi các trục. Khi ta hoán đổi các dòng với các cột của ma trận, kết quả có được là chuyển vị (transpose) của ma trận đó. Về lý thuyết, chuyển vị của ma trận 𝐀 được ký hiệu là 𝐀⊤ và nếu 𝐁=𝐀⊤ thì 𝑏𝑖𝑗=𝑎𝑗𝑖 với mọi 𝑖 và 𝑗. Do đó, chuyển vị của 𝐀 trong :eqref:eq_matrix_def
là một ma trận 𝑛×𝑚:
Trong mã nguồn, ta lấy chuyển vị của một ma trận thông qua thuộc tính T
.
A.T
array([[ 0., 4., 8., 12., 16.],
[ 1., 5., 9., 13., 17.],
[ 2., 6., 10., 14., 18.],
[ 3., 7., 11., 15., 19.]])
Là một biến thể đặc biệt của ma trận vuông, ma trận đối xứng (symmetric matrix) 𝐀 có chuyển vị bằng chính nó: 𝐀=𝐀⊤.
B = np.array([[1, 2, 3], [2, 0, 4], [3, 4, 5]])
B
array([[1., 2., 3.],
[2., 0., 4.],
[3., 4., 5.]])
B == B.T
array([[ True, True, True],
[ True, True, True],
[ True, True, True]])
Ma trận là một cấu trúc dữ liệu hữu ích, cho phép tổ chức dữ liệu dưới nhiều hình thức khác nhau. Trong trường hợp ma trận biểu thị dữ liệu bảng, các dòng có thể tượng trưng cho các điểm dữ liệu khác nhau, trong khi các cột có thể tượng trưng cho các thuộc tính khác nhau của điểm dữ liệu đó.
Sự quen thuộc với cấu trúc này có thể xuất phát từ việc sử dụng các bảng tính. Do đó, trong ma trận biểu thị tập dữ liệu bảng, việc xem mỗi điểm dữ liệu như một vector dòng trong ma trận sẽ giúp cho việc áp dụng các kỹ thuật học sâu thông dụng dễ dàng hơn.
Ví dụ, chúng ta có thể truy cập hoặc lặp qua các nhóm nhỏ dữ liệu (batch) bằng cách sử dụng trục ngoài cùng của ndarray, hoặc chỉ đơn giản là duyệt qua từng điểm dữ liệu nếu không có batch nhỏ nào được sử dụng.
Tensor
Tensors là một cấu trúc dữ liệu tổng quát hơn so với vector và ma trận, cho phép chúng ta xây dựng các đối tượng với số trục tùy ý. Tensors cung cấp một phương pháp chung để biểu diễn ndarray với số trục linh hoạt. Ví dụ, vector là tensors bậc một và ma trận là tensors bậc hai. Tensors được ký hiệu bằng các ký tự in hoa sử dụng một kiểu chữ đặc biệt (ví dụ: X, Y, và Z) và có thể được truy cập bằng cách sử dụng cơ chế truy vấn (ví dụ: X[i, j, k] và X[1, 2, i-1, 3]) giống như ma trận.
Tensors trở nên quan trọng hơn khi làm việc với hình ảnh, vì chúng thường được biểu diễn dưới dạng ndarray với 3 trục tương ứng với chiều cao, chiều rộng và một trục kênh để xếp chồng các kênh màu (ví dụ: đỏ, xanh lá cây và xanh dương). Tạm thời, chúng ta sẽ bỏ qua các tensors bậc cao hơn và tập trung vào các khái niệm cơ bản trước.
Ví dụ, chúng ta có một tensor X có kích thước (2, 3, 4):
X = np.arange(24).reshape(2, 3, 4)
X
Cấu trúc của X là một mảng ba chiều với 2 phần tử trục đầu tiên, mỗi phần tử có kích thước 3×4. Khi in ra X, chúng ta có:
array([[[ 0., 1., 2., 3.],
[ 4., 5., 6., 7.],
[ 8., 9., 10., 11.]],
[[12., 13., 14., 15.],
[16., 17., 18., 19.],
[20., 21., 22., 23.]]])
Các thuộc tính cơ bản của các phép toán Tensor
Số vô hướng, vector, ma trận và tensor với số trục tùy ý đều có một số thuộc tính hữu ích. Ví dụ, chúng ta có thể nhận thấy từ định nghĩa rằng phép toán theo từng phần tử (elementwise) là có ý nghĩa, nghĩa là mọi phép toán theo từng phần tử một ngôi không làm thay đổi kích thước của toán hạng ban đầu. Tương tự, nếu chúng ta có hai tensor có cùng kích thước, kết quả của phép toán theo từng phần tử hai ngôi sẽ tạo ra một tensor có cùng kích thước. Ví dụ, nếu chúng ta cộng hai ma trận có cùng kích thước, thì phép cộng theo từng phần tử sẽ được thực hiện giữa các phần tử tương ứng trong hai ma trận.
A = np.arange(20).reshape(5, 4)
B = A.copy() # Assign a copy of A to B by allocating new memory
A, A + B
(array([[ 0., 1., 2., 3.],
[ 4., 5., 6., 7.],
[ 8., 9., 10., 11.],
[12., 13., 14., 15.],
[16., 17., 18., 19.]]),
array([[ 0., 2., 4., 6.],
[ 8., 10., 12., 14.],
[16., 18., 20., 22.],
[24., 26., 28., 30.],
[32., 34., 36., 38.]]))
Đặc biệt, phép nhân theo phần tử của hai ma trận được gọi là phép nhân Hadamard (Hadamard product – ký hiệu toán học là ⊙). Xét ma trận 𝐁∈ℝ𝑚×𝑛 có phần tử dòng 𝑖 và cột 𝑗 là 𝑏𝑖𝑗. Phép nhân Hadamard giữa ma trận 𝐀(khai báo ở :eqref:eq_matrix_def
) và 𝐁 là
A * B
array([[ 0., 1., 4., 9.],
[ 16., 25., 36., 49.],
[ 64., 81., 100., 121.],
[144., 169., 196., 225.],
[256., 289., 324., 361.]])
Nhân hoặc cộng một tensor với một số vô hướng cũng sẽ không thay đổi kích thước của tensor, mỗi phần tử của tensor sẽ được cộng hoặc nhân cho số vô hướng đó.
a = 2
X = np.arange(24).reshape(2, 3, 4)
a + X, (a * X).shape
(array([[[ 2., 3., 4., 5.],
[ 6., 7., 8., 9.],
[10., 11., 12., 13.]],
[[14., 15., 16., 17.],
[18., 19., 20., 21.],
[22., 23., 24., 25.]]]),
(2, 3, 4))
Rút gọn tensor
Một phép toán hữu ích mà ta có thể thực hiện trên bất kỳ tensor nào là phép tính tổng các phần tử của nó. Ký hiệu toán học của phép tính tổng là ∑. Ta biểu diễn phép tính tổng các phần tử của một vector x; với độ dài d; dưới dạng \(\sum_{i=1}^d x_i\). Trong mã nguồn, ta chỉ cần gọi hàm sum
.
x = np.arange(4)
x, x.sum()
(array([0., 1., 2., 3.]), array(6.))
Ta có thể biểu diễn phép tính tổng các phần tử của tensor có kích thước tùy ý. Ví dụ, tổng các phần tử của một ma trận m×n có thể được viết là \(\sum_{i=1}^{m} \sum_{j=1}^{n} a_{ij}\)
A.shape, A.sum()
((5, 4), array(190.))
Theo mặc định, hàm sum
sẽ rút gọn tensor dọc theo tất cả các trục của nó và trả về kết quả là một số vô hướng. Ta cũng có thể chỉ định các trục được rút gọn bằng phép tổng. Lấy ma trận làm ví dụ, để rút gọn theo chiều hàng (trục 00) bằng việc tính tổng tất cả các hàng, ta đặt axis=0
khi gọi hàm sum
.
A_sum_axis0 = A.sum(axis=0)
A_sum_axis0, A_sum_axis0.shape
(array([40., 45., 50., 55.]), (4,))
Việc đặt axis=1
sẽ rút gọn theo cột (trục 11) bằng việc tính tổng tất cả các cột. Do đó, kích thước trục 11 của đầu vào sẽ không còn trong kích thước của đầu ra.
A_sum_axis1 = A.sum(axis=1)
A_sum_axis1, A_sum_axis1.shape
(array([ 6., 22., 38., 54., 70.]), (5,))
Việc rút gọn ma trận dọc theo cả hàng và cột bằng phép tổng tương đương với việc cộng tất cả các phần tử trong ma trận đó lại.
A.sum(axis=[0, 1]) # Same as A.sum()
array(190.)
Một đại lượng liên quan là trung bình cộng. Ta tính trung bình cộng bằng cách chia tổng các phần tử cho số lượng phần tử. Trong mã nguồn, ta chỉ cần gọi hàm mean
với đầu vào là các tensor có kích thước tùy ý.
A.mean(), A.sum() / A.size
(array(9.5), array(9.5))
Giống như sum
, hàm mean
cũng có thể rút gọn tensor dọc theo các trục được chỉ định.
A.mean(axis=0), A.sum(axis=0) / A.shape[0]
(array([ 8., 9., 10., 11.]), array([ 8., 9., 10., 11.]))
Tổng tensor không rút gọn
việc giữ lại số các trục đôi khi là cần thiết khi gọi hàm sum
hoặc mean
, bằng cách đặt keepdims=True
.
sum_A = A.sum(axis=1, keepdims=True)
sum_A
array([[ 6.],
[22.],
[38.],
[54.],
[70.]])
Ví dụ, vì sum_A
vẫn giữ lại 22 trục sau khi tính tổng của mỗi hàng, chúng ta có thể chia A
cho sum_A
thông qua cơ chế lan truyền.
A / sum_A
array([[0. , 0.16666667, 0.33333334, 0.5 ],
[0.18181819, 0.22727273, 0.27272728, 0.3181818 ],
[0.21052632, 0.23684211, 0.2631579 , 0.28947368],
[0.22222222, 0.24074075, 0.25925925, 0.2777778 ],
[0.22857143, 0.24285714, 0.25714287, 0.27142859]])
Nếu chúng ta muốn tính tổng tích lũy các phần tử của A
dọc theo các trục, giả sử axis=0
(từng hàng một), ta có thể gọi hàm cumsum
. Hàm này không rút gọn chiều của tensor đầu vào theo bất cứ trục nào.
A.cumsum(axis=0)
array([[ 0., 1., 2., 3.],
[ 4., 6., 8., 10.],
[12., 15., 18., 21.],
[24., 28., 32., 36.],
[40., 45., 50., 55.]])
Tích vô hướng
Cho đến giờ, chúng ta mới chỉ thực hiện những phép tính từng phần tử tương ứng, như tổng và trung bình. Nếu đây là tất những gì chúng ta có thể làm, đại số tuyến tính có lẽ không xứng đáng để có nguyên một mục. Tuy nhiên, một trong nhưng phép tính căn bản nhất của đại số tuyến tính là tích vô hướng. Với hai vector \(\mathbf{x}, \mathbf{y} \in \mathbb{R}^d\) cho trước, tích vô hướng (dot product) \(\mathbf{x}^\top \mathbf{y}\) (hoặc ⟨x,y⟩) là tổng các tích của những phần tử có cùng vị trí: \(\mathbf{x}^\top \mathbf{y} = \sum_{i=1}^{d} x_i y_i\).
y = np.ones(4)
x, y, np.dot(x, y)
(array([0., 1., 2., 3.]), array([1., 1., 1., 1.]), array(6.))
Lưu ý rằng chúng ta có thể thể hiện tích vô hướng của hai vector một cách tương tự bằng việc thực hiện tích từng phần tử tương ứng rồi lấy tổng:
np.sum(x * y)
array(6.)
Tích vô hướng sẽ hữu dụng trong rất nhiều trường hợp. Ví dụ, với một tập các giá trị cho trước, biểu thị bởi vector \(\mathbf{x} \in \mathbb{R}^d\), và một tập các trọng số được biểu thị bởi \(\mathbf{x} \in \mathbb{R}^d\), tổng trọng số của các giá trị trong x theo các trọng số trong w có thể được thể hiện bởi tích vô hướng \(\mathbf{x}^\top \mathbf{w}\). Khi các trọng số không âm và có tổng bằng một \(\left(\sum_{i=1}^{d} {w_i} = 1\right)\), tích vô hướng thể hiện phép tính trung bình trọng số (weighted average). Sau khi được chuẩn hoá thành hai vector đơn vị, tích vô hướng của hai vector đó là giá trị cos của góc giữa hai vector đó. Chúng tôi sẽ giới thiệu khái niệm về độ dài ở các phần sau trong mục này.
Tích giữa ma trận và vector
Giờ đây, khi đã biết cách tính toán tích vô hướng, chúng ta có thể bắt đầu hiểu tích giữa ma trận và vector. Bạn có thể xem lại cách ma trận A∈Rm×n và vector x∈Rn được định nghĩa và biểu diễn ở trên, ta sẽ bắt đầu bằng việc biểu diễn ma trận A qua các vector hàng của nó.
Mỗi \(\mathbf{a}^\top_{i} \in \mathbb{R}^n\) là một vector hàng thể hiện hàng thứ i của ma trận A. Tích giữa ma trận và vector Ax đơn giản chỉ là một vector cột với chiều dài m, với phần tử thứ ilà kết quả của phép tích vô hướng \(\mathbf{a}^\top_i \mathbf{x}\):
Chúng ta có thể nghĩ đến việc nhân một ma trận A∈Rm×n với một vector như một phép biến hình, chiếu vector từ không gian Rn thành Rm. Những phép biến hình này hóa ra lại trở nên rất hữu dụng. Ví dụ, chúng ta có thể biểu diễn phép xoay là tích với một ma trận vuông. Bạn sẽ thấy ở những chương tiếp theo, chúng ta cũng có thể sử dụng tích giữa ma trận và vector để thực hiện hầu hết những tính toán cần thiết khi tính các tầng trong một mạng nơ-ron dựa theo kết quả của tầng trước đó.
Khi lập trình, để thực hiện nhân ma trận với vector ndarray
, chúng ta cũng sử dụng hàm dot
giống như tích vô hướng. Việc gọi np.dot(A, x)
với ma trận A
và một vector x
sẽ thực hiện phép nhân vô hướng giữa ma trận và vector. Lưu ý rằng chiều của cột A
(chiều dài theo trục 11) phải bằng với chiều của vector x
(chiều dài của nó).
A.shape, x.shape, np.dot(A, x)
((5, 4), (4,), array([ 14., 38., 62., 86., 110.]))
Phép nhân ma trận
Nếu bạn đã quen với tích vô hướng và tích ma trận-vector, tích ma trận-ma trận cũng tương tự như thế.
Giả sử ta có hai ma trận A∈Rn×k và B∈Rk×m:
Đặt \(\mathbf{a}^\top_{i} \in \mathbb{R}^k\) là vector hàng biểu diễn hàng thứ i của ma trận A và bj∈Rk là vector cột thứ j của ma trận B. Để tính ma trận tích C=AB, cách đơn giản nhất là viết các hàng của ma trận A và các cột của ma trận B:
Khi đó ma trận tích C∈Rn×m được tạo với phần tử cij bằng tích vô hướng \(\mathbf{a}^\top_i \mathbf{b}_j\):
Ta có thể coi tích hai ma trận AB như việc tính m phép nhân ma trận và vector, sau đó ghép các kết quả với nhau để tạo ra một ma trận n×m. Giống như tích vô hướng và phép nhân ma trận-vector, ta có thể tính phép nhân hai ma trận bằng cách sử dụng hàm dot
. Trong đoạn mã dưới đây, chúng ta tính phép nhân giữa A
và B
. Ở đây, A
là một ma trận với 5 hàng 4 cột và B
là một ma trận với 4
hàng 3
cột. Sau phép nhân này, ta thu được một ma trận với 5 hàng 3 cột.
B = np.ones(shape=(4, 3))
np.dot(A, B)
array([[ 6., 6., 6.],
[22., 22., 22.],
[38., 38., 38.],
[54., 54., 54.],
[70., 70., 70.]])
Phép nhân hai ma trận có thể được gọi đơn giản là phép nhân ma trận và không nên nhầm lẫn với phép nhân Hadamard.
Chuẩn
Một trong những toán tử hữu dụng nhất của đại số tuyến tính là chuẩn (norm). Nói dân dã thì, các chuẩn của một vector cho ta biết một vector lớn tầm nào. Thuật ngữ kích thước đang xét ở đây không nói tới số chiều không gian mà đúng hơn là về độ lớn của các thành phần.
Trong đại số tuyến tính, chuẩn của một vector là hàm số f ánh xạ một vector đến một số vô hướng, thỏa mãn các tính chất sau. Cho vector x bất kỳ, tính chất đầu tiên phát biểu rằng nếu chúng ta co giãn toàn bộ các phần tử của một vector bằng một hằng số α, chuẩn của vector đó cũng co giãn theo giá trị tuyệt đối của hằng số đó: f(αx) = |α|f(x)
Tính chất thứ hai cũng giống như bất đẳng thức tam giác: f(x+y) ≤ f(x)+f(y)
Tính chất thứ ba phát biểu rằng chuẩn phải không âm: f(x) ≥ 0
Điều này là hợp lý vì trong hầu hết các trường hợp thì kích thước nhỏ nhất cho các vật đều bằng 0. Tính chất cuối cùng yêu cầu chuẩn nhỏ nhất thu được khi và chỉ khi toàn bộ thành phần của vector đó bằng 0: ∀i,[x]i = 0 ⇔ f(x) = 0
Bạn chắc sẽ để ý là các chuẩn có vẻ giống như một phép đo khoảng cách. Và nếu còn nhớ khái niệm khoảng cách Euclid (định lý Pythagoras) được học ở phổ thông, thì khái niệm không âm và bất đẳng thức tam giác có thể gợi nhắc lại một chút. Thực tế là, khoảng cách Euclid cũng là một chuẩn: cụ thể là ℓ2. Giả sử rằng các thành phần trong vector n chiều x là x1,…,xn. Chuẩn ℓ2 của x là căn bậc hai của tổng các bình phương của các thành phần trong vector:
Ở đó, chỉ số dưới 22 thường được lược đi khi viết chuẩn ℓ2ℓ2, ví dụ, ∥x∥ cũng tương đương với ∥x∥2. Khi lập trình, ta có thể tính chuẩn ℓ2 của một vector bằng cách gọi hàm linalg.norm
.
u = np.array([3, -4])
np.linalg.norm(u)
array(5.)
Trong học sâu, chúng ta thường gặp chuẩn ℓ2 bình phương hơn. Bạn cũng sẽ thường xuyên gặp chuẩn ℓ1, chuẩn được biểu diễn bằng tổng các giá trị tuyệt đối của các thành phần trong vector:
So với chuẩn ℓ2, nó ít bị ảnh ưởng bởi các giá trị ngoại biên hơn. Để tính chuẩn ℓ1, chúng ta dùng hàm giá trị tuyệt đối rồi lấy tổng các thành phần.
np.abs(u).sum()
array(7.)
Cả hai chuẩn ℓ2 và ℓ1 đều là trường hợp riêng của một chuẩn tổng quát hơn, chuẩn ℓp:
Tương tự với chuẩn ℓ2 của vector, chuẩn Frobenius của một ma trận X∈Rm×n là căn bậc hai của tổng các bình phương của các thành phần trong ma trận:
Chuẩn Frobenius thỏa mãn tất cả các tính chất của một chuẩn vector. Nó giống chuẩn ℓ2 của một vector nhưng ở dạng của ma trận. Ta dùng hàm linalg.norm
để tính toán chuẩn Frobenius của ma trận.
np.linalg.norm(np.ones((4, 9)))
array(6.)
Chuẩn và mục tiêu
Trong đại số tuyến tính, chuẩn và mục tiêu là những khái niệm quan trọng mà chúng ta có thể sử dụng để có cái nhìn tổng quan và hiểu tại sao chúng có giá trị trong lĩnh vực này. Trong học sâu, chúng ta thường đối mặt với các bài toán tối ưu như cực đại hóa xác suất xảy ra của dữ liệu quan sát được hoặc cực tiểu hóa khoảng cách giữa dự đoán và nhãn thực tế. Để đạt được mục tiêu này, chúng ta thường gán các biểu diễn vector cho các đối tượng khác nhau như từ, sản phẩm hoặc bài báo, với mục đích là giảm thiểu khoảng cách giữa các đối tượng tương tự và tăng cường khoảng cách giữa các đối tượng khác biệt. Mục tiêu này, một thành phần quan trọng trong các thuật toán học sâu (bên cạnh dữ liệu), thường được biểu diễn dưới dạng chuẩn (norm)
Kết luận
Những khái niệm như số vô hướng, vector, ma trận và tensor là những khái niệm cơ bản trong đại số tuyến tính. Chúng ta có thể áp dụng chúng trong học sâu và trong quá trình biểu diễn dữ liệu trong quá trình huấn luyện.
Trong học sâu, chúng ta thường làm việc với các chuẩn như chuẩn ℓ1, chuẩn ℓ2 và chuẩn Frobenius. Các chuẩn này giúp chúng ta đo lường và điều chỉnh khoảng cách giữa các đối tượng, như vector hay ma trận, trong quá trình tối ưu hóa. Chúng giúp chúng ta tối đa hóa hiệu suất mô hình và tối thiểu hóa sai số.
Thêm vào đó, chúng ta có thể sử dụng các toán tử trên số vô hướng, vector, ma trận và tensor để thực hiện các phép tính và biến đổi dữ liệu trong quá trình huấn luyện. Với sự hỗ trợ của các hàm của ndarray, chúng ta có thể thực hiện các phép toán như phép nhân, tổng, trung bình, và các phép toán khác trên các đối tượng này.
Tổng quan, việc hiểu và ứng dụng những khái niệm này trong học sâu và biểu diễn dữ liệu giúp chúng ta nắm bắt và xử lý hiệu quả thông tin trong quá trình huấn luyện mô hình của chúng ta.
Lời cảm ơn
Chúng tôi cảm ơn các tác giả đã thực hiện biên dịch cuốn sách Dive into Deep Learning đã dịch rất công phu và dễ hiểu để có thể sử dụng được trong thực tế:
- Đoàn Võ Duy Thanh
- Lê Khắc Hồng Phúc
- Phạm Minh Đức
- Ngô Thế Anh Khoa
- Nguyễn Lê Quang Nhật
- Vũ Hữu Tiệp
- Mai Sơn Hải
- Phạm Hồng Vinh
Trong nội dung bài viết này, chúng tôi cố gắng diễn giải lại nội dung cho gần với thực tế và ứng dụng nó trong việc biểu diễn dữ liệu giúp cho người sử dụng có một góc nhìn sâu hơn về bản chất của vấn đề và khía cạnh toán trong trí tuệ nhân tạo.