.. raw:: html
.. raw:: html
.. raw:: html
.. _sec_linear-algebra:
Đại số tuyến tính
=================
.. raw:: html
Bây giờ bạn đã có thể lưu trữ và xử lý dữ liệu, hãy cùng ôn qua những
kiến thức đại số tuyến tính cần thiết để hiểu và lập trình hầu hết các
mô hình được nhắc tới trong quyển sách này. Dưới đây, chúng tôi giới
thiệu các đối tượng toán học, số học và phép tính cơ bản trong đại số
tuyến tính, biểu diễn chúng bằng cả ký hiệu toán học và cách triển khai
lập trình tương ứng.
.. raw:: html
Số vô hướng
-----------
.. raw:: html
Nếu bạn chưa từng học đại số tuyến tính hay học máy, có lẽ bạn mới chỉ
có kinh nghiệm làm toán với từng con số riêng lẻ. Và nếu bạn đã từng
phải cân bằng sổ thu chi hoặc đơn giản là trả tiền cho bữa ăn, thì hẳn
bạn đã biết cách thực hiện các phép tính cơ bản như cộng trừ nhân chia
các cặp số. Ví dụ, nhiệt độ tại Palo Alto là :math:`52` độ Fahrenheit.
Chúng ta gọi các giá trị mà chỉ bao gồm một số duy nhất là *số vô hướng*
(*scalar*). Nếu bạn muốn chuyển giá trị nhiệt độ trên sang độ Celsius
(thang đo nhiệt độ hợp lý hơn theo hệ mét), bạn sẽ phải tính biểu thức
:math:`c = \frac{5}{9}(f - 32)` với giá trị :math:`f` bằng :math:`52`.
Trong phương trình trên, mỗi số hạng — :math:`5`, :math:`9` và
:math:`32` — là các số vô hướng. Các ký hiệu :math:`c` và :math:`f` được
gọi là *biến* và chúng biễu diễn các giá trị số vô hướng chưa biết.
.. raw:: html
Trong quyển sách này, chúng tôi sẽ tuân theo quy ước ký hiệu các biến vô
hướng bằng các chữ cái viết thường (chẳng hạn :math:`x`, :math:`y` và
:math:`z`). Chúng tôi ký hiệu không gian (liên tục) của tất cả các *số
thực* vô hướng là :math:`\mathbb{R}`. Vì tính thiết thực, chúng tôi sẽ
bỏ qua định nghĩa chính xác của *không gian*. Nhưng bạn cần nhớ
:math:`x \in \mathbb{R}` là cách toán học để thể hiện :math:`x` là một
số thực vô hướng. Ký hiệu :math:`\in` đọc là “thuộc” và đơn thuần biểu
diễn việc phần tử thuộc một tập hợp. Tương tự, ta có thể viết
:math:`x, y \in \{0, 1\}` để ký hiệu cho việc các số :math:`x` và
:math:`y` chỉ có thể nhận giá trị :math:`0` hoặc :math:`1`.
.. raw:: html
Trong mã nguồn MXNet, một số vô hướng được biễu diễn bằng một
``ndarray`` với chỉ một phần tử. Trong đoạn mã dưới đây, chúng ta khởi
tạo hai số vô hướng và thực hiện các phép tính quen thuộc như cộng, trừ,
nhân, chia và lũy thừa với chúng.
.. code:: python
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
.. parsed-literal::
:class: output
(array(5.), array(6.), array(1.5), array(9.))
.. raw:: html
.. raw:: html
.. raw:: html
Vector
------
.. raw:: html
Bạn có thể xem vector đơn thuần như một dãy các số vô hướng. Chúng ta
gọi các giá trị đó là *phần tử* (*thành phần*) của vector. Khi dùng
vector để biễu diễn các mẫu trong tập dữ liệu, giá trị của chúng thường
mang ý nghĩa liên quan tới đời thực. Ví dụ, nếu chúng ta huấn luyện một
mô hình dự đoán rủi ro vỡ nợ, chúng ta có thể gán cho mỗi ứng viên một
vector gồm các thành phần tương ứng với thu nhập, thời gian làm việc, số
lần vỡ nợ trước đó của họ và các yếu tố khác. Nếu chúng ta đang tìm hiểu
về rủi ro bị đau tim của bệnh nhân, 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 sinh tồn gần
nhất, nồng độ cholesterol, số phút tập thể dục mỗi ngày, v.v. Trong ký
hiệu toán học, chúng ta thường biểu diễn vector bằng chữ cái in đậm viết
thường (ví dụ :math:`\mathbf{x}`, :math:`\mathbf{y}`, và
:math:`\mathbf{z})`.
.. raw:: html
Trong MXNet, chúng ta làm việc với vector thông qua các ``ndarray``
:math:`1`-chiều. Thường thì ``ndarray`` có thể có chiều dài bất kỳ, tùy
thuộc vào giới hạn bộ nhớ máy tính.
.. code:: python
x = np.arange(4)
x
.. parsed-literal::
:class: output
array([0., 1., 2., 3.])
.. raw:: html
Một phần tử bất kỳ trong vector có thể được ký hiệu sử dụng chỉ số dưới.
Ví dụ ta có thể viết :math:`x_i` để ám chỉ phần tử thứ :math:`i` của
:math:`\mathbf{x}`. Lưu ý rằng phần tử :math:`x_i` là một số vô hướng
nên nó không được in đậm. Có rất nhiều tài liệu tham khảo xem vector cột
là chiều mặc định của vector, và quyển sách này cũng vậy. Trong toán
học, một vector có thể được viết như sau
.. math:: \mathbf{x} =\begin{bmatrix}x_{1} \\x_{2} \\ \vdots \\x_{n}\end{bmatrix},
:label: eq_vec_def
.. raw:: html
trong đó :math:`x_1, \ldots, x_n` 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``.
.. code:: python
x[3]
.. parsed-literal::
:class: output
array(3.)
.. raw:: html
.. raw:: html
.. raw:: html
.. raw:: html
.. raw:: html
Độ dài, Chiều, và Kích thước
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. raw:: html
Hãy quay lại với những khái niệm từ :numref:`sec_ndarray`. Một vector
đơn thuần là một dãy các số. Mỗi vector, tương tự như dãy, đều có một độ
dài. Trong ký hiệu toán học, nếu ta muốn nói rằng một vector
:math:`\mathbf{x}` chứa :math:`n` các số thực vô hướng, ta có thể biểu
diễn nó bằng :math:`\mathbf{x} \in \mathbb{R}^n`. Độ dài của một vector
còn được gọi là số **chiều** của vector.
.. raw:: html
Cũng giống như một dãy thông thường trong Python, chúng ta có thể xem độ
dài của của một ``ndarray`` bằng cách gọi hàm ``len()`` có sẵn của
Python.
.. code:: python
len(x)
.. parsed-literal::
:class: output
4
.. raw:: html
Khi một ``ndarray`` biễu diễn một vector (với chính xác một trục), ta
cũng có thể xem độ dài của nó 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) dọc theo mỗi trục
của ``ndarray``. Với các ``ndarray`` có duy nhất một trục, kích thước
của nó chỉ có một phần tử.
.. code:: python
x.shape
.. parsed-literal::
:class: output
(4,)
.. raw:: html
Ở đây cần lưu ý rằng, từ “chiều” là một từ đa nghĩa và khi đặt vào nhiều
ngữ cảnh thường dễ làm ta bị nhầm lẫn. Để làm rõ, chúng ta dùng số chiều
của một *vector* hoặc của một *trục* để chỉ độ dài của nó, tức là số
phần tử trong một vector hay một trục. Tuy nhiên, chúng ta sử dụng số
chiều của một ``ndarray`` để chỉ số trục của ``ndarray`` đó. Theo nghĩa
này, chiều của một trục của một ``ndarray`` là độ dài của trục đó.
.. raw:: html
.. raw:: html
.. raw:: html
.. raw:: html
.. raw:: html
Ma trận
-------
.. raw:: html
Giống như vector khái quát số vô hướng từ bậc :math:`0` sang bậc
:math:`1`, ma trận sẽ khái quát những vector từ bậc :math:`1` sang bậc
:math:`2`. Ma trận thường được ký hiệu với ký tự hoa và được in đậm (ví
dụ: :math:`\mathbf{X}`, :math:`\mathbf{Y}`, và :math:`\mathbf{Z}`); và
được biểu diễn bằng các ``ndarray`` với :math:`2` trục khi lập trình.
.. raw:: html
Trong ký hiệu toán học, ta dùng
:math:`\mathbf{A} \in \mathbb{R}^{m \times n}` để biểu thị một ma trận
:math:`\mathbf{A}` gồm :math:`m` hàng và :math:`n` cột các giá trị số
thực. Về mặt hình ảnh, ta có thể minh họa bất kỳ ma trận
:math:`\mathbf{A} \in \mathbb{R}^{m \times n}` như một bảng biểu mà mỗi
phần tử :math:`a_{ij}` nằm ở dòng thứ :math:`i` và cột thứ :math:`j` của
bảng:
.. math:: \mathbf{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}.
:label: eq_matrix_def
.. raw:: html
Với bất kỳ ma trận :math:`\mathbf{A} \in \mathbb{R}^{m \times n}` nào,
kích thước của ma trận :math:`\mathbf{A}` là (:math:`m`, :math:`n`) hay
:math:`m \times n`. 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*).
.. raw:: html
Ta có thể tạo một ma trận :math:`m \times n` trong MXNet bằng cách khai
báo kích thước của nó với hai thành phần :math:`m` và :math:`n` khi sử
dụng bất kỳ hàm khởi tạo ``ndarray`` nào mà ta thích.
.. code:: python
A = np.arange(20).reshape(5, 4)
A
.. parsed-literal::
:class: output
array([[ 0., 1., 2., 3.],
[ 4., 5., 6., 7.],
[ 8., 9., 10., 11.],
[12., 13., 14., 15.],
[16., 17., 18., 19.]])
.. raw:: html
Ta có thể truy cập phần tử vô hướng :math:`a_{ij}` của ma trận
:math:`\mathbf{A}` trong :eqref:``eq_matrix_def`` bằng cách khai báo chỉ
số dòng (:math:`i`) và chỉ số cột (:math:`j`), như là
:math:`[\mathbf{A}]_{ij}`. Khi những thành phần vô hướng của ma trận
:math:`\mathbf{A}`, 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 :math:`\mathbf{A}`
với các chỉ số ghi dưới, :math:`a_{ij}`, để chỉ thành phần
:math:`[\mathbf{A}]_{ij}`. 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ư
:math:`a_{2, 3j}` và :math:`[\mathbf{A}]_{2i-1, 3}`.
.. raw:: html
Đô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 :math:`\mathbf{A}` được ký hiệu
là :math:`\mathbf{A}^\top` và nếu :math:`\mathbf{B} = \mathbf{A}^\top`
thì :math:`b_{ij} = a_{ji}` với mọi :math:`i` và :math:`j`. Do đó,
chuyển vị của :math:`\mathbf{A}` trong :eqref:``eq_matrix_def`` là một
ma trận :math:`n \times m`:
.. math::
\mathbf{A}^\top =
\begin{bmatrix}
a_{11} & a_{21} & \dots & a_{m1} \\
a_{12} & a_{22} & \dots & a_{m2} \\
\vdots & \vdots & \ddots & \vdots \\
a_{1n} & a_{2n} & \dots & a_{mn}
\end{bmatrix}.
.. raw:: html
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``.
.. code:: python
A.T
.. parsed-literal::
:class: output
array([[ 0., 4., 8., 12., 16.],
[ 1., 5., 9., 13., 17.],
[ 2., 6., 10., 14., 18.],
[ 3., 7., 11., 15., 19.]])
.. raw:: html
Là một biến thể đặc biệt của ma trận vuông, *ma trận đối xứng*
(*symmetric matrix*) :math:`\mathbf{A}` có chuyển vị bằng chính nó:
:math:`\mathbf{A} = \mathbf{A}^\top`.
.. code:: python
B = np.array([[1, 2, 3], [2, 0, 4], [3, 4, 5]])
B
.. parsed-literal::
:class: output
array([[1., 2., 3.],
[2., 0., 4.],
[3., 4., 5.]])
.. code:: python
B == B.T
.. parsed-literal::
:class: output
array([[ True, True, True],
[ True, True, True],
[ True, True, True]])
.. raw:: html
Ma trận là một cấu trúc dữ liệu hữu ích: chúng cho phép ta tổ chức dữ
liệu có nhiều phương thức biến thể khác nhau. Ví dụ, các dòng trong ma
trận của chúng ta có thể tượng trưng cho các căn nhà khác nhau (các điểm
dữ liệu), còn các cột có thể tượng trưng cho những thuộc tính khác nhau
của ngôi nhà. Bạn có thể thấy quen thuộc với điều này nếu đã từng sử
dụng các phần mềm lập bảng tính hoặc đã đọc :numref:`sec_pandas`. Do
đó, mặc dù một vector đơn lẻ có hướng mặc định là một vector cột, trong
một ma trận biểu thị một tập dữ liệu bảng biểu, sẽ tốt hơn nếu ta xem
mỗi điểm dữ liệu như một vector dòng trong ma trận. Chúng ta sẽ thấy ở
những chương sau, quy ước này sẽ giúp dễ dàng áp dụng các kỹ thuật học
sâu thông dụng. Ví dụ, với trục ngoài cùng của ``ndarray``, ta có thể
truy cập hay duyệt qua các batch nhỏ của những điểm dữ liệu hoặc chỉ đơn
thuần là các điểm dữ liệu nếu không có batch nhỏ nào cả.
.. raw:: html
.. raw:: html
.. raw:: html
.. raw:: html
.. raw:: html
Tensor
------
.. raw:: html
Giống như vector khái quát hoá số vô hướng và ma trận khái quát hoá
vector, ta có thể xây dựng những cấu trúc dữ liệu với thậm chí nhiều
trục hơn. Tensor cho chúng ta một phương pháp tổng quát để miêu tả các
``ndarray`` với số trục bất kỳ. Ví dụ, vector là các tensor bậc một còn
ma trận là các tensor bậc hai. Tensor được ký hiệu với ký tự viết hoa sử
dụng một font chữ đặc biệt (ví dụ: :math:`\mathsf{X}`,
:math:`\mathsf{Y}`, và :math:`\mathsf{Z}`) và có cơ chế truy vấn (ví dụ:
:math:`x_{ijk}` and :math:`[\mathsf{X}]_{1, 2i-1, 3}`) giống như ma
trận.
.. raw:: html
Tensor sẽ trở nên quan trọng hơn khi ta bắt đầu làm việc với hình ảnh,
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* (*channel*) để xếp chồng các
kênh màu (đỏ, xanh lá và xanh dương). Tạm thời, ta sẽ bỏ qua các tensor
bậc cao hơn và tập trung vào những điểm cơ bản trước.
.. code:: python
X = np.arange(24).reshape(2, 3, 4)
X
.. parsed-literal::
:class: output
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.]]])
.. raw:: html
Các thuộc tính Cơ bản của Phép toán Tensor
------------------------------------------
.. raw:: html
Số vô hướng, vector, ma trận và tensor với một số trục bất kỳ có một vài
thuộc tính rất hữu dụng. Ví dụ, bạn có thể để ý từ định nghĩa của phép
toán theo từng phần tử (*elementwise*), bất kỳ phép toán theo từng phần
tử một ngôi nào cũng không làm thay đổi kích thước của toán hạng của nó.
Tương tự, cho hai tensor bất kỳ có cùng kích thước, kết quả của bất kỳ
phép toán theo từng phần tử hai ngôi sẽ là một tensor có cùng kích
thước. Ví dụ, cộng hai ma trận có cùng kích thước sẽ thực hiện phép cộng
theo từng phần tử giữa hai ma trận này.
.. code:: python
A = np.arange(20).reshape(5, 4)
B = A.copy() # Assign a copy of A to B by allocating new memory
A, A + B
.. parsed-literal::
:class: output
(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.]]))
.. raw:: html
Đặ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à :math:`\odot`). Xét
ma trận :math:`\mathbf{B} \in \mathbb{R}^{m \times n}` có phần tử dòng
:math:`i` và cột :math:`j` là :math:`b_{ij}`. Phép nhân Hadamard giữa ma
trận :math:`\mathbf{A}` (khai báo ở :eqref:``eq_matrix_def``) và
:math:`\mathbf{B}` là
.. math::
\mathbf{A} \odot \mathbf{B} =
\begin{bmatrix}
a_{11} b_{11} & a_{12} b_{12} & \dots & a_{1n} b_{1n} \\
a_{21} b_{21} & a_{22} b_{22} & \dots & a_{2n} b_{2n} \\
\vdots & \vdots & \ddots & \vdots \\
a_{m1} b_{m1} & a_{m2} b_{m2} & \dots & a_{mn} b_{mn}
\end{bmatrix}.
.. code:: python
A * B
.. parsed-literal::
:class: output
array([[ 0., 1., 4., 9.],
[ 16., 25., 36., 49.],
[ 64., 81., 100., 121.],
[144., 169., 196., 225.],
[256., 289., 324., 361.]])
.. raw:: html
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 đó.
.. code:: python
a = 2
X = np.arange(24).reshape(2, 3, 4)
a + X, (a * X).shape
.. parsed-literal::
:class: output
(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))
.. raw:: html
.. raw:: html
.. raw:: html
.. raw:: html
.. raw:: html
Rút gọn
-------
.. raw:: html
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à :math:`\sum`. Ta biểu diễn phép tính tổng các phần tử của một vector
:math:`\mathbf{x}` với độ dài :math:`d` dưới dạng
:math:`\sum_{i=1}^d x_i`. Trong mã nguồn, ta chỉ cần gọi hàm ``sum``.
.. code:: python
x = np.arange(4)
x, x.sum()
.. parsed-literal::
:class: output
(array([0., 1., 2., 3.]), array(6.))
.. raw:: html
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 :math:`m \times n` có thể
được viết là :math:`\sum_{i=1}^{m} \sum_{j=1}^{n} a_{ij}`.
.. code:: python
A.shape, A.sum()
.. parsed-literal::
:class: output
((5, 4), array(190.))
.. raw:: html
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 :math:`0`) 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``.
.. code:: python
A_sum_axis0 = A.sum(axis=0)
A_sum_axis0, A_sum_axis0.shape
.. parsed-literal::
:class: output
(array([40., 45., 50., 55.]), (4,))
.. raw:: html
Việc đặt ``axis=1`` sẽ rút gọn theo cột (trục :math:`1`) bằng việc tính
tổng tất cả các cột. Do đó, kích thước trục :math:`1` của đầu vào sẽ
không còn trong kích thước của đầu ra.
.. code:: python
A_sum_axis1 = A.sum(axis=1)
A_sum_axis1, A_sum_axis1.shape
.. parsed-literal::
:class: output
(array([ 6., 22., 38., 54., 70.]), (5,))
.. raw:: html
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.
.. code:: python
A.sum(axis=[0, 1]) # Same as A.sum()
.. parsed-literal::
:class: output
array(190.)
.. raw:: html
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 ý.
.. code:: python
A.mean(), A.sum() / A.size
.. parsed-literal::
:class: output
(array(9.5), array(9.5))
.. raw:: html
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.
.. code:: python
A.mean(axis=0), A.sum(axis=0) / A.shape[0]
.. parsed-literal::
:class: output
(array([ 8., 9., 10., 11.]), array([ 8., 9., 10., 11.]))
.. raw:: html
.. raw:: html
.. raw:: html
.. raw:: html
.. raw:: html
Tổng không rút gọn
~~~~~~~~~~~~~~~~~~
.. raw:: html
Tuy nhiê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``.
.. code:: python
sum_A = A.sum(axis=1, keepdims=True)
sum_A
.. parsed-literal::
:class: output
array([[ 6.],
[22.],
[38.],
[54.],
[70.]])
.. raw:: html
Ví dụ, vì ``sum_A`` vẫn giữ lại :math:`2` 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.
.. code:: python
A / sum_A
.. parsed-literal::
:class: output
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]])
.. raw:: html
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.
.. code:: python
A.cumsum(axis=0)
.. parsed-literal::
:class: output
array([[ 0., 1., 2., 3.],
[ 4., 6., 8., 10.],
[12., 15., 18., 21.],
[24., 28., 32., 36.],
[40., 45., 50., 55.]])
.. raw:: html
Tích vô hướng
-------------
.. raw:: html
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
:math:`\mathbf{x}, \mathbf{y} \in \mathbb{R}^d` cho trước, *tích vô
hướng* (*dot product*) :math:`\mathbf{x}^\top \mathbf{y}` (hoặc
:math:`\langle \mathbf{x}, \mathbf{y} \rangle`) là tổng các tích của
những phần tử có cùng vị trí:
:math:`\mathbf{x}^\top \mathbf{y} = \sum_{i=1}^{d} x_i y_i`.
.. code:: python
y = np.ones(4)
x, y, np.dot(x, y)
.. parsed-literal::
:class: output
(array([0., 1., 2., 3.]), array([1., 1., 1., 1.]), array(6.))
.. raw:: html
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:
.. code:: python
np.sum(x * y)
.. parsed-literal::
:class: output
array(6.)
.. raw:: html
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
:math:`\mathbf{x} \in \mathbb{R}^d`, và một tập các trọng số được biểu
thị bởi :math:`\mathbf{w} \in \mathbb{R}^d`, tổng trọng số của các giá
trị trong :math:`\mathbf{x}` theo các trọng số trong :math:`\mathbf{w}`
có thể được thể hiện bởi tích vô hướng
:math:`\mathbf{x}^\top \mathbf{w}`. Khi các trọng số không âm và có tổng
bằng một(\ :math:`\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.
.. raw:: html
.. raw:: html
.. raw:: html
.. raw:: html
.. raw:: html
Tích giữa Ma trận và Vector
---------------------------
.. raw:: html
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
:math:`\mathbf{A} \in \mathbb{R}^{m \times n}` và vector
:math:`\mathbf{x} \in \mathbb{R}^n` được định nghĩa và biểu diễn trong
:eq:`eq_matrix_def` và :eq:`eq_vec_def`. Ta sẽ bắt đầu bằng
việc biểu diễn ma trận :math:`\mathbf{A}` qua các vector hàng của nó.
.. math::
\mathbf{A}=
\begin{bmatrix}
\mathbf{a}^\top_{1} \\
\mathbf{a}^\top_{2} \\
\vdots \\
\mathbf{a}^\top_m \\
\end{bmatrix},
.. raw:: html
Mỗi :math:`\mathbf{a}^\top_{i} \in \mathbb{R}^n` là một vector hàng thể
hiện hàng thứ :math:`i` của ma trận :math:`\mathbf{A}`. Tích giữa ma
trận và vector :math:`\mathbf{A}\mathbf{x}` đơn giản chỉ là một vector
cột với chiều dài :math:`m`, với phần tử thứ :math:`i` là kết quả của
phép tích vô hướng :math:`\mathbf{a}^\top_i \mathbf{x}`:
.. math::
\mathbf{A}\mathbf{x}
= \begin{bmatrix}
\mathbf{a}^\top_{1} \\
\mathbf{a}^\top_{2} \\
\vdots \\
\mathbf{a}^\top_m \\
\end{bmatrix}\mathbf{x}
= \begin{bmatrix}
\mathbf{a}^\top_{1} \mathbf{x} \\
\mathbf{a}^\top_{2} \mathbf{x} \\
\vdots\\
\mathbf{a}^\top_{m} \mathbf{x}\\
\end{bmatrix}.
.. raw:: html
Chúng ta có thể nghĩ đến việc nhân một ma trận
:math:`\mathbf{A}\in \mathbb{R}^{m \times n}` với một vector như một
phép biến hình, chiếu vector từ không gian :math:`\mathbb{R}^{n}` thành
:math:`\mathbb{R}^{m}`. 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 đó.
.. raw:: html
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 :math:`1`) phải bằng với chiều của vector ``x``
(chiều dài của nó).
.. code:: python
A.shape, x.shape, np.dot(A, x)
.. parsed-literal::
:class: output
((5, 4), (4,), array([ 14., 38., 62., 86., 110.]))
.. raw:: html
.. raw:: html
.. raw:: html
.. raw:: html
.. raw:: html
Phép nhân Ma trận
-----------------
.. raw:: html
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ế.
.. raw:: html
Giả sử ta có hai ma trận :math:`\mathbf{A} \in \mathbb{R}^{n \times k}`
và :math:`\mathbf{B} \in \mathbb{R}^{k \times m}`:
.. math::
\mathbf{A}=\begin{bmatrix}
a_{11} & a_{12} & \cdots & a_{1k} \\
a_{21} & a_{22} & \cdots & a_{2k} \\
\vdots & \vdots & \ddots & \vdots \\
a_{n1} & a_{n2} & \cdots & a_{nk} \\
\end{bmatrix},\quad
\mathbf{B}=\begin{bmatrix}
b_{11} & b_{12} & \cdots & b_{1m} \\
b_{21} & b_{22} & \cdots & b_{2m} \\
\vdots & \vdots & \ddots & \vdots \\
b_{k1} & b_{k2} & \cdots & b_{km} \\
\end{bmatrix}.
.. raw:: html
Đặt :math:`\mathbf{a}^\top_{i} \in \mathbb{R}^k` là vector hàng biểu
diễn hàng thứ :math:`i` của ma trận :math:`\mathbf{A}` và
:math:`\mathbf{b}_{j} \in \mathbb{R}^k` là vector cột thứ :math:`j` của
ma trận :math:`\mathbf{B}`. Để tính ma trận tích
:math:`\mathbf{C} = \mathbf{A}\mathbf{B}`, cách đơn giản nhất là viết
các hàng của ma trận :math:`\mathbf{A}` và các cột của ma trận
:math:`\mathbf{B}`:
.. math::
\mathbf{A}=
\begin{bmatrix}
\mathbf{a}^\top_{1} \\
\mathbf{a}^\top_{2} \\
\vdots \\
\mathbf{a}^\top_n \\
\end{bmatrix},
\quad \mathbf{B}=\begin{bmatrix}
\mathbf{b}_{1} & \mathbf{b}_{2} & \cdots & \mathbf{b}_{m} \\
\end{bmatrix}.
.. raw:: html
Khi đó ma trận tích :math:`\mathbf{C} \in \mathbb{R}^{n \times m}` được
tạo với phần tử :math:`c_{ij}` bằng tích vô hướng
:math:`\mathbf{a}^\top_i \mathbf{b}_j`:
.. math::
\mathbf{C} = \mathbf{AB} = \begin{bmatrix}
\mathbf{a}^\top_{1} \\
\mathbf{a}^\top_{2} \\
\vdots \\
\mathbf{a}^\top_n \\
\end{bmatrix}
\begin{bmatrix}
\mathbf{b}_{1} & \mathbf{b}_{2} & \cdots & \mathbf{b}_{m} \\
\end{bmatrix}
= \begin{bmatrix}
\mathbf{a}^\top_{1} \mathbf{b}_1 & \mathbf{a}^\top_{1}\mathbf{b}_2& \cdots & \mathbf{a}^\top_{1} \mathbf{b}_m \\
\mathbf{a}^\top_{2}\mathbf{b}_1 & \mathbf{a}^\top_{2} \mathbf{b}_2 & \cdots & \mathbf{a}^\top_{2} \mathbf{b}_m \\
\vdots & \vdots & \ddots &\vdots\\
\mathbf{a}^\top_{n} \mathbf{b}_1 & \mathbf{a}^\top_{n}\mathbf{b}_2& \cdots& \mathbf{a}^\top_{n} \mathbf{b}_m
\end{bmatrix}.
.. raw:: html
Ta có thể coi tích hai ma trận :math:`\mathbf{AB}` như việc tính
:math:`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 :math:`n \times 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 :math:`5` hàng
:math:`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 :math:`5` hàng :math:`3` cột.
.. code:: python
B = np.ones(shape=(4, 3))
np.dot(A, B)
.. parsed-literal::
:class: output
array([[ 6., 6., 6.],
[22., 22., 22.],
[38., 38., 38.],
[54., 54., 54.],
[70., 70., 70.]])
.. raw:: html
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.
.. raw:: html
.. raw:: html
.. raw:: html
.. raw:: html
.. raw:: html
Chuẩn
-----
.. raw:: html
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.
.. raw:: html
Trong đại số tuyến tính, chuẩn của một vector là hàm số :math:`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
:math:`\mathbf{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ố
:math:`\alpha`, chuẩn của vector đó cũng co giãn theo *giá trị tuyệt
đối* của hằng số đó:
.. math:: f(\alpha \mathbf{x}) = |\alpha| f(\mathbf{x}).
.. raw:: html
Tính chất thứ hai cũng giống như bất đẳng thức tam giác:
.. math:: f(\mathbf{x} + \mathbf{y}) \leq f(\mathbf{x}) + f(\mathbf{y}).
.. raw:: html
Tính chất thứ ba phát biểu rằng chuẩn phải không âm:
.. math:: f(\mathbf{x}) \geq 0.
.. raw:: html
Đ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.
.. math:: \forall i, [\mathbf{x}]_i = 0 \Leftrightarrow f(\mathbf{x})=0.
.. raw:: html
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à :math:`\ell_2`. Giả sử rằng các thành phần trong vector
:math:`n` chiều :math:`\mathbf{x}` là :math:`x_1, \ldots, x_n`. *Chuẩn*
:math:`\ell_2` của :math:`\mathbf{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:
.. math:: \|\mathbf{x}\|_2 = \sqrt{\sum_{i=1}^n x_i^2},
.. raw:: html
Ở đó, chỉ số dưới :math:`2` thường được lược đi khi viết chuẩn
:math:`\ell_2`, ví dụ, :math:`\|\mathbf{x}\|` cũng tương đương với
:math:`\|\mathbf{x}\|_2`. Khi lập trình, ta có thể tính chuẩn
:math:`\ell_2` của một vector bằng cách gọi hàm ``linalg.norm``.
.. code:: python
u = np.array([3, -4])
np.linalg.norm(u)
.. parsed-literal::
:class: output
array(5.)
.. raw:: html
Trong học sâu, chúng ta thường gặp chuẩn :math:`\ell_2` bình phương hơn.
Bạn cũng sẽ thường xuyên gặp *chuẩn* :math:`\ell_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:
.. math:: \|\mathbf{x}\|_1 = \sum_{i=1}^n \left|x_i \right|.
.. raw:: html
So với chuẩn :math:`\ell_2`, nó ít bị ảnh ưởng bởi các giá trị ngoại
biên hơn. Để tính chuẩn :math:`\ell_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.
.. code:: python
np.abs(u).sum()
.. parsed-literal::
:class: output
array(7.)
.. raw:: html
Cả hai chuẩn :math:`\ell_2` và :math:`\ell_1` đều là trường hợp riêng
của một chuẩn tổng quát hơn, *chuẩn* :math:`\ell_p`:
.. math:: \|\mathbf{x}\|_p = \left(\sum_{i=1}^n \left|x_i \right|^p \right)^{1/p}.
.. raw:: html
Tương tự với chuẩn :math:`\ell_2` của vector, *chuẩn Frobenius* của một
ma trận :math:`\mathbf{X} \in \mathbb{R}^{m \times 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:
.. math:: \|\mathbf{X}\|_F = \sqrt{\sum_{i=1}^m \sum_{j=1}^n x_{ij}^2}.
.. raw:: html
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 :math:`\ell_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.
.. code:: python
np.linalg.norm(np.ones((4, 9)))
.. parsed-literal::
:class: output
array(6.)
.. raw:: html
.. raw:: html
.. raw:: html
.. raw:: html
.. raw:: html
.. _subsec_norms_and_objectives:
Chuẩn và Mục tiêu
~~~~~~~~~~~~~~~~~
.. raw:: html
Tuy không muốn đi quá nhanh nhưng chúng ta có thể xây dựng phần nào trực
giác để hiểu tại sao những khái niệm này lại hữu dụng. Trong học sâu, ta
thường cố giải các bài toán tối ưu: *cực đại hóa* xác suất xảy ra của dữ
liệu quan sát được; *cực tiểu hóa* khoảng cách giữa dự đoán và nhãn gốc.
Gán các biểu diễn vector cho các đối tượng (như từ, sản phẩm hay các bài
báo) để cực tiểu hóa khoảng cách giữa các đối tượng tương tự nhau và cực
đại hóa khoảng cách giữa các đối tượng khác nhau. Mục tiêu, thành phần
quan trọng nhất của một thuật toán học sâu (bên cạnh dữ liệu), thường
được biễu diễn diễn theo *chuẩn* (*norm*).
.. raw:: html
Bàn thêm về Đại số Tuyến tính
-----------------------------
.. raw:: html
Chỉ trong mục này, chúng tôi đã trang bị cho bạn tất cả những kiến thức
đại số tuyến tính cần thiết để hiểu một lượng lớn các mô hình học máy
hiện đại. Vẫn còn rất nhiều kiến thức đại số tuyến tính, phần lớn đều
hữu dụng cho học máy. Một ví dụ là phép phân tích ma trận ra các thành
phần, các phép phân tích này có thể tạo ra các cấu trúc thấp chiều trong
các tập dữ liệu thực tế. Có cả một nhánh của học máy tập trung vào sử
dụng các phép phân tích ma trận và tổng quát chúng lên cho các tensor
bậc cao để khám phá cấu trúc trong các tập dữ liệu và giải quyết các bài
toán dự đoán. Tuy nhiên, cuốn sách này chỉ tập trung vào học sâu. Và
chúng tôi tin rằng bạn sẽ muốn học thêm nhiều về toán một khi đã có thể
triển khai được các mô hình học máy hữu dụng cho các tập dữ liệu thực
tế. Bởi vậy, trong khi vẫn còn nhiều kiến thức toán cần bàn thêm ở phần
sau, chúng tôi sẽ kết thúc mục này ở đây.
.. raw:: html
Nếu bạn muốn học thêm về đại số tuyến tính, bạn có thể tham khảo
:numref:`sec_geometry-linear-algebric-ops` hoặc các nguồn tài liệu
xuất sắc tại
:cite:`Strang.1993,Kolter.2008,Petersen.Pedersen.ea.2008`.
.. raw:: html
.. raw:: html
.. raw:: html
Tóm tắt
-------
.. raw:: html
- Số vô hướng, vector, ma trận, và tensor là các đối tượng toán học cơ
bản trong đại số tuyến tính.
- Vector là dạng tổng quát của số vô hướng và ma trận là dạng tổng quát
của vector.
- Trong cách biểu diễn ``ndarray``, các số vô hướng, vector, ma trận và
tensor lần lượt có 0, 1, 2 và một số lượng tùy ý các trục.
- Một tensor có thể thu gọn theo một số trục bằng ``sum`` và ``mean``.
- Phép nhân theo từng phần tử của hai ma trận được gọi là tích Hadamard
của chúng. Phép toán này khác với phép nhân ma trận.
- Trong học sâu, chúng ta thường làm việc với các chuẩn như chuẩn
:math:`\ell_1`, chuẩn :math:`\ell_2` và chuẩn Frobenius.
- Chúng ta có thể thực hiện một số lượng lớn các toán tử trên số vô
hướng, vector, ma trận và tensor với các hàm của ``ndarray``.
.. raw:: html
Bài tập
-------
.. raw:: html
1. Chứng minh rằng chuyển vị của một ma trận chuyển vị là chính nó:
:math:`(\mathbf{A}^\top)^\top = \mathbf{A}`.
2. Cho hai ma trận :math:`\mathbf{A}` và :math:`\mathbf{B}`, chứng minh
rằng tổng của chuyển vị bằng chuyển vị của tổng:
:math:`\mathbf{A}^\top + \mathbf{B}^\top = (\mathbf{A} + \mathbf{B})^\top`.
3. Cho một ma trận vuông :math:`\mathbf{A}`, liệu rằng
:math:`\mathbf{A} + \mathbf{A}^\top` có luôn đối xứng? Tại sao?
4. Chúng ta đã định nghĩa tensor ``X`` với kích thước (:math:`2`,
:math:`3`, :math:`4`) trong mục này. Kết quả của ``len(X)`` là gì?
5. Cho một tensor ``X`` với kích thước bất kỳ, liệu ``len(X)`` có luôn
tương ứng với độ dài của một trục nhất định của ``X`` hay không? Đó
là trục nào?
6. Chạy ``A / A.sum(axis=1)`` và xem điều gì xảy ra. Bạn có phân tích
được nguyên nhân không?
7. Khi di chuyển giữa hai điểm ở Manhattan (đường phố hình bàn cờ),
khoảng cách tính bằng tọa độ (tức độ dài các đại lộ và phố) mà bạn
cần di chuyển là bao nhiêu? Bạn có thể đi theo đường chéo không? (Xem
thêm bản đồ Manhattan, New York để trả lời câu hỏi này)
8. Xét một tensor với kích thước (:math:`2`, :math:`3`, :math:`4`). Kích
thước của kết quả sau khi tính tổng theo trục :math:`0`, :math:`1` và
:math:`2` sẽ như thế nào?
9. Đưa một tensor với 3 trục hoặc hơn vào hàm ``linalg.norm`` và quan
sát kết quả. Hàm này thực hiện việc gì cho các ``ndarray`` với kích
thước bất kỳ?
.. raw:: html
.. raw:: html
Thảo luận
---------
- `Tiếng Anh `__
- `Tiếng Việt `__
.. raw:: html
.. raw:: html
Những người thực hiện
---------------------
Bản dịch trong trang này được thực hiện bởi:
- Đ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