18.4. Giải tích Nhiều biến

Bây giờ chúng ta đã có hiểu biết vững chắc về đạo hàm của một hàm đơn biến, hãy cùng trở lại câu hỏi ban đầu về hàm mất mát của (nhiều khả năng là) hàng tỷ trọng số.

18.4.1. Đạo hàm trong Không gian Nhiều chiều

Nhớ lại Section 18.3, ta đã bàn luận về điều gì sẽ xảy ra nếu chỉ thay đổi một trong số hàng tỷ các trọng số và giữ nguyên những trọng số còn lại. Điều này hoàn toàn không có gì khác với một hàm đơn biến, nên ta có thể viết

(18.4.1)\[L(w_1+\epsilon_1, w_2, \ldots, w_N) \approx L(w_1, w_2, \ldots, w_N) + \epsilon_1 \frac{d}{dw_1} L(w_1, w_2, \ldots, w_N).\]

Chúng ta sẽ gọi đạo hàm của một biến trong khi không thay đổi những biến còn lại là đạo hàm riêng (partial derivative), và ký hiệu đạo hàm này là \(\frac{\partial}{\partial w_1}\) trong phương trình (18.4.1).

Bây giờ, tiếp tục thay đổi \(w_2\) một khoảng nhỏ thành \(w_2 + \epsilon_2\):

(18.4.2)\[\begin{split}\begin{aligned} L(w_1+\epsilon_1, w_2+\epsilon_2, \ldots, w_N) & \approx L(w_1, w_2+\epsilon_2, \ldots, w_N) + \epsilon_1 \frac{\partial}{\partial w_1} L(w_1, w_2+\epsilon_2, \ldots, w_N) \\ & \approx L(w_1, w_2, \ldots, w_N) \\ & \quad + \epsilon_2\frac{\partial}{\partial w_2} L(w_1, w_2, \ldots, w_N) \\ & \quad + \epsilon_1 \frac{\partial}{\partial w_1} L(w_1, w_2, \ldots, w_N) \\ & \quad + \epsilon_1\epsilon_2\frac{\partial}{\partial w_2}\frac{\partial}{\partial w_1} L(w_1, w_2, \ldots, w_N) \\ & \approx L(w_1, w_2, \ldots, w_N) \\ & \quad + \epsilon_2\frac{\partial}{\partial w_2} L(w_1, w_2, \ldots, w_N) \\ & \quad + \epsilon_1 \frac{\partial}{\partial w_1} L(w_1, w_2, \ldots, w_N). \end{aligned}\end{split}\]

Một lần nữa, ta lại sử dụng ý tưởng đã thấy ở (18.4.1) rằng \(\epsilon_1\epsilon_2\) là một số hạng bậc cao và có thể được loại bỏ tương tự như cách mà ta có thể loại bỏ \(\epsilon^{2}\) trong mục trước. Cứ tiếp tục theo cách này, ta có

(18.4.3)\[L(w_1+\epsilon_1, w_2+\epsilon_2, \ldots, w_N+\epsilon_N) \approx L(w_1, w_2, \ldots, w_N) + \sum_i \epsilon_i \frac{\partial}{\partial w_i} L(w_1, w_2, \ldots, w_N).\]

Thoạt nhìn đây có vẻ là một mớ hỗn độn, tuy nhiên chú ý rằng phép tổng bên phải chính là biểu diễn của phép tích vô hướng và ta có thể khiến chúng trở nên quen thuộc hơn. Với

(18.4.4)\[\boldsymbol{\epsilon} = [\epsilon_1, \ldots, \epsilon_N]^\top \; \text{và} \; \nabla_{\mathbf{x}} L = \left[\frac{\partial L}{\partial x_1}, \ldots, \frac{\partial L}{\partial x_N}\right]^\top,\]

ta có

(18.4.5)\[L(\mathbf{w} + \boldsymbol{\epsilon}) \approx L(\mathbf{w}) + \boldsymbol{\epsilon}\cdot \nabla_{\mathbf{w}} L(\mathbf{w}).\]

Ta gọi vector \(\nabla_{\mathbf{w}} L\)gradient của \(L\).

Phương trình (18.4.5) đáng để ta suy ngẫm. Nó có dạng đúng y như những gì ta đã thấy trong trường hợp một chiều, chỉ khác là tất cả đã được biến đổi về dạng vector và tích vô hướng. Điều này cho chúng ta biết một cách xấp xỉ hàm \(L\) sẽ thay đổi như thế nào với một nhiễu loạn bất kỳ ở đầu vào. Như ta sẽ thấy trong mục tiếp theo, đây sẽ là một công cụ quan trọng giúp chúng ta hiểu được cách học từ thông tin chứa trong gradient dưới góc nhìn hình học.

Nhưng trước tiên, hãy cùng kiểm tra phép xấp xỉ này với một ví dụ. Giả sử ta đang làm việc với hàm

(18.4.6)\[f(x, y) = \log(e^x + e^y) \text{ với gradient } \nabla f (x, y) = \left[\frac{e^x}{e^x+e^y}, \frac{e^y}{e^x+e^y}\right].\]

Xét một điểm \((0, \log(2))\), ta có

(18.4.7)\[f(x, y) = \log(3) \text{ với gradient } \nabla f (x, y) = \left[\frac{1}{3}, \frac{2}{3}\right].\]

Vì thế, nếu muốn tính xấp xỉ \(f\) tại \((\epsilon_1, \log(2) + \epsilon_2)\), ta có một ví dụ cụ thể của (18.4.5):

(18.4.8)\[f(\epsilon_1, \log(2) + \epsilon_2) \approx \log(3) + \frac{1}{3}\epsilon_1 + \frac{2}{3}\epsilon_2.\]

Ta có thể kiểm tra với đoạn mã bên dưới để xem phép xấp xỉ chính xác tới mức nào.

%matplotlib inline
from d2l import mxnet as d2l
from IPython import display
from mpl_toolkits import mplot3d
from mxnet import autograd, np, npx
npx.set_np()

def f(x, y):
    return np.log(np.exp(x) + np.exp(y))
def grad_f(x, y):
    return np.array([np.exp(x) / (np.exp(x) + np.exp(y)),
                     np.exp(y) / (np.exp(x) + np.exp(y))])

epsilon = np.array([0.01, -0.03])
grad_approx = f(0, np.log(2)) + epsilon.dot(grad_f(0, np.log(2)))
true_value = f(0 + epsilon[0], np.log(2) + epsilon[1])
f'approximation: {grad_approx}, true Value: {true_value}'
'approximation: 1.0819457, true Value: 1.0821242'

18.4.2. Ý nghĩa Hình học của Gradient và Thuật toán Hạ Gradient

Nhìn lại (18.4.5):

(18.4.9)\[L(\mathbf{w} + \boldsymbol{\epsilon}) \approx L(\mathbf{w}) + \boldsymbol{\epsilon}\cdot \nabla_{\mathbf{w}} L(\mathbf{w}).\]

Giả sử ta muốn sử dụng thông tin gradient để cực tiểu hóa mất mát \(L\). Hãy cùng tìm hiểu cách hoạt động về mặt hình học của thuật toán hạ gradient được mô tả lần đầu ở Section 2.5. Các bước của thuật toán được miêu tả dưới đây:

  1. Bắt đầu với giá trị ban đầu ngẫu nhiên của tham số \(\mathbf{w}\).
  2. Tìm một hướng \(\mathbf{v}\) tại \(\mathbf{w}\) sao cho \(L\) giảm một cách nhanh nhất.
  3. Tiến một bước nhỏ về hướng đó: \(\mathbf{w} \rightarrow \mathbf{w} + \epsilon\mathbf{v}\).
  4. Lặp lại.

Thứ duy nhất mà chúng ta không biết chính xác cách làm là cách tính toán vector \(\mathbf{v}\) tại bước thứ hai. Ta gọi \(\mathbf{v}\)hướng hạ dốc nhất (direction of steepest descent). Sử dụng những hiểu biết về mặt hình học của phép tích vô hướng từ Section 18.1, ta có thể viết lại (18.4.5) như sau

(18.4.10)\[L(\mathbf{w} + \mathbf{v}) \approx L(\mathbf{w}) + \mathbf{v}\cdot \nabla_{\mathbf{w}} L(\mathbf{w}) = L(\mathbf{w}) + \|\nabla_{\mathbf{w}} L(\mathbf{w})\|\cos(\theta).\]

Để thuận tiện, ta giả định hướng của chúng ta có độ dài bằng một và sử dụng \(\theta\) để biểu diễn góc giữa \(\mathbf{v}\)\(\nabla_{\mathbf{w}} L(\mathbf{w})\). Nếu muốn \(L\) giảm càng nhanh, ta sẽ muốn giá trị của biểu thức trên càng âm càng tốt. Cách duy nhất để chọn hướng đi trong phương trình này là thông qua \(\cos(\theta)\), vì thế ta sẽ muốn giá trị này âm nhất có thể. Nhắc lại kiến thức của hàm cô-sin, giá trị âm nhất của hàm này là \(\cos(\theta) = -1\), là khi góc giữa vector gradient và hướng cần chọn là \(\pi\) radian hay \(180\) độ. Cách duy nhất để đạt được điều này là di chuyển theo hướng hoàn toàn ngược lại: chọn \(\mathbf{v}\) theo hướng hoàn toàn ngược chiều với \(\nabla_{\mathbf{w}} L(\mathbf{w})\)!

Điều này dẫn ta đến với một trong những thuật toán quan trọng nhất của học máy: hướng hạ dốc nhất cùng hướng với \(-\nabla_{\mathbf{w}}L(\mathbf{w})\). Vậy nên thuật toán của ta sẽ được viết lại như sau.

  1. Bắt đầu với một lựa chọn ngẫu nhiên cho giá trị ban đầu của các tham số \(\mathbf{w}\).
  2. Tính toán \(\nabla_{\mathbf{w}} L(\mathbf{w})\).
  3. Tiến một bước nhỏ về hướng ngược lại của nó: \(\mathbf{w} \rightarrow \mathbf{w} - \epsilon\nabla_{\mathbf{w}} L(\mathbf{w})\).
  4. Lặp lại.

Thuật toán cơ bản này dù đã được chỉnh sửa và kết hợp theo nhiều cách bởi các nhà nghiên cứu, nhưng khái niệm cốt lõi vẫn là như nhau. Sử dụng gradient để tìm hướng giảm mất mát nhanh nhất có thể và cập nhật các tham số để dịch chuyển về hướng đó.

18.4.3. Một vài chú ý về Tối ưu hóa

Xuyên suốt cuốn sách, ta chỉ tập trung vào những kỹ thuật tối ưu hóa số học vì một nguyên nhân thực tế là: mọi hàm ta gặp phải trong học sâu quá phức tạp để có thể tối ưu hóa một cách tường minh.

Tuy nhiên, sẽ rất hữu ích nếu hiểu được những kiến thức hình học ta có được ở trên nói gì về tối ưu hóa các hàm một cách trực tiếp.

Giả sử ta muốn tìm giá trị của \(\mathbf{x}_0\) giúp cực tiểu hóa một hàm \(L(\mathbf{x})\) nào đó. Và có một người nào đó đưa ta một giá trị và cho rằng đây là giá trị giúp cực tiểu hóa \(L\). Bằng cách nào ta có thể kiểm chứng rằng đáp án của họ là hợp lý?

Xét lại (18.4.5):

(18.4.11)\[L(\mathbf{x}_0 + \boldsymbol{\epsilon}) \approx L(\mathbf{x}_0) + \boldsymbol{\epsilon}\cdot \nabla_{\mathbf{x}} L(\mathbf{x}_0).\]

Nếu giá trị gradient khác không, ta biết rằng ta có thể bước một bước về hướng \(-\epsilon \nabla_{\mathbf{x}} L(\mathbf{x}_0)\) để tìm một giá trị \(L\) nhỏ hơn. Do đó, nếu ta thực sự ở điểm cực tiểu, sẽ không thể có trường hợp đó! Ta có thể kết luận rằng nếu \(\mathbf{x}_0\) là một cực tiểu, thì \(\nabla_{\mathbf{x}} L(\mathbf{x}_0) = 0\). Ta gọi những điểm mà tại đó \(\nabla_{\mathbf{x}} L(\mathbf{x}_0) = 0\)các điểm tới hạn (critical points).

Điều này rất hữu ích, bởi vì trong một vài thiết lập hiếm gặp, ta có thể tìm được các điểm có gradient bằng không một cách tường minh, và từ đó tìm được điểm có giá trị nhỏ nhất.

Với một ví dụ cụ thể, xét hàm

(18.4.12)\[f(x) = 3x^4 - 4x^3 -12x^2.\]

Hàm này có đạo hàm

(18.4.13)\[\frac{df}{dx} = 12x^3 - 12x^2 -24x = 12x(x-2)(x+1).\]

Các điểm cực trị duy nhất khả dĩ là tại \(x = -1, 0, 2\), khi hàm lấy giá trị lần lượt là \(-5,0, -32\), và do đó ta có thể kết luận rằng ta cực tiểu hóa hàm khi \(x = 2\). Ta có thể kiểm chứng nhanh bằng đồ thị.

x = np.arange(-2, 3, 0.01)
f = (3 * x**4) - (4 * x**3) - (12 * x**2)

d2l.plot(x, f, 'x', 'f(x)')
../_images/output_multivariable-calculus_vn_3c62bf_3_0.svg

Điều này nhấn mạnh một thực tế quan trọng cần biết kể cả khi làm việc dưới dạng lý thuyết hay số học: các điểm khả dĩ duy nhất mà tại đó hàm là cực tiểu (hoặc cực đại) sẽ có đạo hàm tại đó bằng không, tuy nhiên, không phải tất cả các điểm có đạo hàm bằng không sẽ là cực tiểu (hay cực đại) toàn cục.

18.4.4. Quy tắc Dây chuyền cho Hàm đa biến

Giả sử là ta có một hàm bốn biến (\(w, x, y\), and \(z\)) được tạo ra bằng cách kết hợp các hàm con:

(18.4.14)\[\begin{split}\begin{aligned}f(u, v) & = (u+v)^{2} \\u(a, b) & = (a+b)^{2}, \qquad v(a, b) = (a-b)^{2}, \\a(w, x, y, z) & = (w+x+y+z)^{2}, \qquad b(w, x, y, z) = (w+x-y-z)^2.\end{aligned}\end{split}\]

Các chuỗi phương trình như trên xuất hiện thường xuyên khi ta làm việc với các mạng nơ-ron, do đó cố gắng hiểu xem làm thế nào để tính gradient của các hàm này là thiết yếu. Fig. 18.4.1 biểu diễn trực quan mỗi liên hệ trực tiếp giữa biến này với biến khác.

../_images/chain-net1.svg

Fig. 18.4.1 Các quan hệ của hàm ở trên với các nút biểu diễn giá trị và mũi tên cho biết sự phụ thuộc hàm.

Ta có thể kết hợp các phương trình trong (18.4.14) để có

(18.4.15)\[f(w, x, y, z) = \left(\left((w+x+y+z)^2+(w+x-y-z)^2\right)^2+\left((w+x+y+z)^2-(w+x-y-z)^2\right)^2\right)^2.\]

Tiếp theo ta có thể lấy đạo hàm bằng cách chỉ sử dụng các đạo hàm đơn biến, nhưng nếu làm vậy ta sẽ nhanh chóng bị ngợp trong các số hạng, mà đa phần là bị lặp lại! Thật vậy, ta có thể thấy ở ví dụ dưới đây:

(18.4.16)\[\begin{split}\begin{aligned} \frac{\partial f}{\partial w} & = 2 \left(2 \left(2 (w + x + y + z) - 2 (w + x - y - z)\right) \left((w + x + y + z)^{2}- (w + x - y - z)^{2}\right) + \right.\\ & \left. \quad 2 \left(2 (w + x - y - z) + 2 (w + x + y + z)\right) \left((w + x - y - z)^{2}+ (w + x + y + z)^{2}\right)\right) \times \\ & \quad \left(\left((w + x + y + z)^{2}- (w + x - y - z)^2\right)^{2}+ \left((w + x - y - z)^{2}+ (w + x + y + z)^{2}\right)^{2}\right). \end{aligned}\end{split}\]

Kế đến nếu ta cũng muốn tính \(\frac{\partial f}{\partial x}\), ta sẽ lại kết thúc với một phương trình tương tự với nhiều thành phần bị lặp lại, và nhiều thành phần lặp lại chung giữa hai đạo hàm. Điều này thể hiện một khối lượng lớn công việc bị lãng phí, và nếu ta tính các đạo hàm theo cách này, toàn bộ cuộc cách mạng học sâu sẽ chấm dứt trước khi nó bắt đầu!

Ta hãy chia nhỏ vấn đề này. Ta sẽ bắt đầu bằng cách thử hiểu \(f\) thay đổi thế nào khi \(a\) thay đổi, giả định cần thiết là tất cả \(w, x, y\), và \(z\) không tồn tại. Ta sẽ lập luận giống như lần đầu tiên ta làm việc với gradient. Hãy lấy \(a\) và cộng một lượng nhỏ \(\epsilon\) vào nó.

(18.4.17)\[\begin{split}\begin{aligned} & f(u(a+\epsilon, b), v(a+\epsilon, b)) \\ \approx & f\left(u(a, b) + \epsilon\frac{\partial u}{\partial a}(a, b), v(a, b) + \epsilon\frac{\partial v}{\partial a}(a, b)\right) \\ \approx & f(u(a, b), v(a, b)) + \epsilon\left[\frac{\partial f}{\partial u}(u(a, b), v(a, b))\frac{\partial u}{\partial a}(a, b) + \frac{\partial f}{\partial v}(u(a, b), v(a, b))\frac{\partial v}{\partial a}(a, b)\right]. \end{aligned}\end{split}\]

Dòng đầu tiên theo sau từ định nghĩa đạo hàm từng phần, và dòng thứ hai theo sau từ định nghĩa gradient. Thật khó khăn để lần theo các biến khi tính đạo hàm, như trong biểu thức \(\frac{\partial f}{\partial u}(u(a, b), v(a, b))\), cho nên ta thường rút gọn nó để dễ nhớ hơn

(18.4.18)\[\frac{\partial f}{\partial a} = \frac{\partial f}{\partial u}\frac{\partial u}{\partial a}+\frac{\partial f}{\partial v}\frac{\partial v}{\partial a}.\]

Sẽ rất hữu ích khi ta suy nghĩ về ý nghĩa của biến đổi này. Ta đang cố gắng hiểu làm thế nào một hàm có dạng \(f(u(a, b), v(a, b))\) thay đổi giá trị của nó khi \(a\) thay đổi. Có hai hướng có thể xảy ra: \(a \rightarrow u \rightarrow f\)\(a \rightarrow v \rightarrow f\). Ta có thể lần lượt tính toán đóng góp của cả hai hướng này thông qua quy tắc dây chuyền: \(\frac{\partial w}{\partial u} \cdot \frac{\partial u}{\partial x}\)\(\frac{\partial w}{\partial v} \cdot \frac{\partial v}{\partial x}\), rồi cộng gộp lại.

các hàm được kết nối ở bên trái như trong Fig. 18.4.2.

../_images/chain-net2.svg

Fig. 18.4.2 Một ví dụ khác về quy tắc dây chuyền.

Để tính toán \(\frac{\partial f}{\partial y}\), chúng ta cần tính tổng toàn bộ đường đi từ \(y\) đến \(f\) (trường hợp này có 3 đường đi):

(18.4.19)\[\frac{\partial f}{\partial y} = \frac{\partial f}{\partial a} \frac{\partial a}{\partial u} \frac{\partial u}{\partial y} + \frac{\partial f}{\partial u} \frac{\partial u}{\partial y} + \frac{\partial f}{\partial b} \frac{\partial b}{\partial v} \frac{\partial v}{\partial y}.\]

Hiểu quy tắc dây chuyền theo cách này giúp chúng ta thấy được dòng chảy của gradient xuyên suốt mạng và vì sao một số lựa chọn kiến trúc như trong LSTM (Section 9.2) hoặc các tầng phần dư (Section 7.6) có thể định hình quá trình học bằng cách kiểm soát dòng chảy gradient.

18.4.5. Thuật toán Lan truyền ngược (Backpropagation)

Hãy xem lại ví dụ (18.4.14) ở phần trước:

(18.4.20)\[\begin{split}\begin{aligned} f(u, v) & = (u+v)^{2} \\ u(a, b) & = (a+b)^{2}, \qquad v(a, b) = (a-b)^{2}, \\ a(w, x, y, z) & = (w+x+y+z)^{2}, \qquad b(w, x, y, z) = (w+x-y-z)^2. \end{aligned}\end{split}\]

Nếu muốn tính \(\frac{\partial f}{\partial w}\) chẳng hạn, ta có thể áp dụng quy tắc dây chuyền đa biến để thấy:

(18.4.21)\[\begin{split}\begin{aligned} \frac{\partial f}{\partial w} & = \frac{\partial f}{\partial u}\frac{\partial u}{\partial w} + \frac{\partial f}{\partial v}\frac{\partial v}{\partial w}, \\ \frac{\partial u}{\partial w} & = \frac{\partial u}{\partial a}\frac{\partial a}{\partial w}+\frac{\partial u}{\partial b}\frac{\partial b}{\partial w}, \\ \frac{\partial v}{\partial w} & = \frac{\partial v}{\partial a}\frac{\partial a}{\partial w}+\frac{\partial v}{\partial b}\frac{\partial b}{\partial w}. \end{aligned}\end{split}\]

Chúng ta hãy thử sử dụng cách phân tách này để tính \(\frac{\partial f}{\partial w}\). Tất cả những gì chúng ta cần ở đây là các đạo hàm riêng:

(18.4.22)\[\begin{split}\begin{aligned} \frac{\partial f}{\partial u} = 2(u+v), & \quad\frac{\partial f}{\partial v} = 2(u+v), \\ \frac{\partial u}{\partial a} = 2(a+b), & \quad\frac{\partial u}{\partial b} = 2(a+b), \\ \frac{\partial v}{\partial a} = 2(a-b), & \quad\frac{\partial v}{\partial b} = -2(a-b), \\ \frac{\partial a}{\partial w} = 2(w+x+y+z), & \quad\frac{\partial b}{\partial w} = 2(w+x-y-z). \end{aligned}\end{split}\]

Khi lập trình, các tính toán này trở thành một biểu thức khá dễ quản lý.

# Compute the value of the function from inputs to outputs
w, x, y, z = -1, 0, -2, 1
a, b = (w + x + y + z)**2, (w + x - y - z)**2
u, v = (a + b)**2, (a - b)**2
f = (u + v)**2
print(f'    f at {w}, {x}, {y}, {z} is {f}')

# Compute the single step partials
df_du, df_dv = 2*(u + v), 2*(u + v)
du_da, du_db, dv_da, dv_db = 2*(a + b), 2*(a + b), 2*(a - b), -2*(a - b)
da_dw, db_dw = 2*(w + x + y + z), 2*(w + x - y - z)

# Compute the final result from inputs to outputs
du_dw, dv_dw = du_da*da_dw + du_db*db_dw, dv_da*da_dw + dv_db*db_dw
df_dw = df_du*du_dw + df_dv*dv_dw
print(f'df/dw at {w}, {x}, {y}, {z} is {df_dw}')
    f at -1, 0, -2, 1 is 1024
df/dw at -1, 0, -2, 1 is -4096

Tuy nhiên, cần lưu ý rằng điều này không làm cho các phép tính chẳng hạn như \(\frac{\partial f}{\partial x}\) trở nên đơn giản. Lý do nằm ở cách chúng ta chọn để áp dụng quy tắc dây chuyền. Nếu nhìn vào những gì chúng ta đã làm ở trên, chúng ta luôn giữ \(\partial w\) ở mẫu khi có thể. Với cách này, chúng ta áp dụng quy tắc dây chuyền để xem \(w\) thay đổi các biến khác như thế nào. Nếu đó là những gì chúng ta muốn thì cách này quả là một ý tưởng hay. Tuy nhiên, nghĩ lại về mục tiêu của học sâu: chúng ta muốn thấy từng tham số thay đổi giá trị mất mát như thế nào. Về cốt lõi, chúng ta luôn muốn áp dụng quy tắc dây chuyền và giữ \(\partial f\) ở tử số bất cứ khi nào có thể!

Cụ thể hơn, chúng ta có thể viết như sau:

(18.4.23)\[\begin{split}\begin{aligned} \frac{\partial f}{\partial w} & = \frac{\partial f}{\partial a}\frac{\partial a}{\partial w} + \frac{\partial f}{\partial b}\frac{\partial b}{\partial w}, \\ \frac{\partial f}{\partial a} & = \frac{\partial f}{\partial u}\frac{\partial u}{\partial a}+\frac{\partial f}{\partial v}\frac{\partial v}{\partial a}, \\ \frac{\partial f}{\partial b} & = \frac{\partial f}{\partial u}\frac{\partial u}{\partial b}+\frac{\partial f}{\partial v}\frac{\partial v}{\partial b}. \end{aligned}\end{split}\]

Lưu ý rằng cách áp dụng quy tắc dây chuyền này buộc chúng ta phải tính rõ \(\frac{\partial f}{\partial u}, \frac{\partial f}{\partial v}, \frac{\partial f}{\partial a}, \frac{\partial f}{\partial b}, \; \text{và} \; \frac{\partial f}{\partial w}\). Chúng ta cũng có thể thêm vào các phương trình:

(18.4.24)\[\begin{split}\begin{aligned} \frac{\partial f}{\partial x} & = \frac{\partial f}{\partial a}\frac{\partial a}{\partial x} + \frac{\partial f}{\partial b}\frac{\partial b}{\partial x}, \\ \frac{\partial f}{\partial y} & = \frac{\partial f}{\partial a}\frac{\partial a}{\partial y}+\frac{\partial f}{\partial b}\frac{\partial b}{\partial y}, \\ \frac{\partial f}{\partial z} & = \frac{\partial f}{\partial a}\frac{\partial a}{\partial z}+\frac{\partial f}{\partial b}\frac{\partial b}{\partial z}. \end{aligned}\end{split}\]

và tiếp đó theo dõi \(f\) biến đổi như thế nào khi chúng ta thay đổi bất kỳ nút nào trong toàn bộ mạng. Hãy cùng lập trình nó.

# Compute the value of the function from inputs to outputs
w, x, y, z = -1, 0, -2, 1
a, b = (w + x + y + z)**2, (w + x - y - z)**2
u, v = (a + b)**2, (a - b)**2
f = (u + v)**2
print(f'f at {w}, {x}, {y}, {z} is {f}')

# Compute the derivative using the decomposition above
# First compute the single step partials
df_du, df_dv = 2*(u + v), 2*(u + v)
du_da, du_db, dv_da, dv_db = 2*(a + b), 2*(a + b), 2*(a - b), -2*(a - b)
da_dw, db_dw = 2*(w + x + y + z), 2*(w + x - y - z)
da_dx, db_dx = 2*(w + x + y + z), 2*(w + x - y - z)
da_dy, db_dy = 2*(w + x + y + z), -2*(w + x - y - z)
da_dz, db_dz = 2*(w + x + y + z), -2*(w + x - y - z)

# Now compute how f changes when we change any value from output to input
df_da, df_db = df_du*du_da + df_dv*dv_da, df_du*du_db + df_dv*dv_db
df_dw, df_dx = df_da*da_dw + df_db*db_dw, df_da*da_dx + df_db*db_dx
df_dy, df_dz = df_da*da_dy + df_db*db_dy, df_da*da_dz + df_db*db_dz

print(f'df/dw at {w}, {x}, {y}, {z} is {df_dw}')
print(f'df/dx at {w}, {x}, {y}, {z} is {df_dx}')
print(f'df/dy at {w}, {x}, {y}, {z} is {df_dy}')
print(f'df/dz at {w}, {x}, {y}, {z} is {df_dz}')
f at -1, 0, -2, 1 is 1024
df/dw at -1, 0, -2, 1 is -4096
df/dx at -1, 0, -2, 1 is -4096
df/dy at -1, 0, -2, 1 is -4096
df/dz at -1, 0, -2, 1 is -4096

Việc tính đạo hàm từ \(f\) trở ngược về đầu vào thay vì từ đầu vào đến đầu ra (như chúng ta đã thực hiện ở đoạn mã đầu tiên ở trên) là lý do cho cái tên lan truyền ngược (backpropagation) của thuật toán. Có hai bước:

  1. Tính giá trị của hàm và đạo hàm riêng theo từng bước đơn lẻ từ đầu đến cuối. Mặc dù không được thực hiện ở trên, hai việc này có thể được kết hợp vào một lượt truyền xuôi duy nhất.
  2. Tính toán đạo hàm của \(f\) từ cuối về đầu. Chúng ta gọi đó là lượt truyền ngược.

Đây chính xác là những gì mỗi thuật toán học sâu thực thi để tính gradient của giá trị mất mát theo từng trọng số của mạng trong mỗi lượt lan truyền. Thật thú vị vì chúng ta có một sự phân tách như trên.

Để tóm gọn phần này, hãy xem nhanh ví dụ sau.

# Initialize as ndarrays, then attach gradients
w, x, y, z = np.array(-1), np.array(0), np.array(-2), np.array(1)

w.attach_grad()
x.attach_grad()
y.attach_grad()
z.attach_grad()

# Do the computation like usual, tracking gradients
with autograd.record():
    a, b = (w + x + y + z)**2, (w + x - y - z)**2
    u, v = (a + b)**2, (a - b)**2
    f = (u + v)**2

# Execute backward pass
f.backward()

print(f'df/dw at {w}, {x}, {y}, {z} is {w.grad}')
print(f'df/dx at {w}, {x}, {y}, {z} is {x.grad}')
print(f'df/dy at {w}, {x}, {y}, {z} is {y.grad}')
print(f'df/dz at {w}, {x}, {y}, {z} is {z.grad}')
df/dw at -1.0, 0.0, -2.0, 1.0 is -4096.0
df/dx at -1.0, 0.0, -2.0, 1.0 is -4096.0
df/dy at -1.0, 0.0, -2.0, 1.0 is -4096.0
df/dz at -1.0, 0.0, -2.0, 1.0 is -4096.0

Tất cả những gì chúng ta làm ở trên có thể được thực hiện tự động bằng cách gọi hàm f.backwards().

18.4.6. Hessian

Như với giải tích đơn biến, việc xem xét đạo hàm bậc cao hơn cũng hữu ích để xấp xỉ tốt hơn một hàm so với việc chỉ sử dụng gradient.

Một vấn đề trước mắt khi làm việc với đạo hàm bậc cao hơn của hàm đa biến đó là cần phải tính toán một số lượng lớn đạo hàm. Nếu chúng ta có một hàm \(f(x_1, \ldots, x_n)\) với \(n\) biến, chúng ta có thể cần \(n^{2}\) đạo hàm bậc 2, chẳng hạn để lựa chọn \(i\)\(j\):

(18.4.25)\[\frac{d^2f}{dx_idx_j} = \frac{d}{dx_i}\left(\frac{d}{dx_j}f\right).\]

Biểu thức này được hợp thành một ma trận gọi là Hessian:

(18.4.26)\[\begin{split}\mathbf{H}_f = \begin{bmatrix} \frac{d^2f}{dx_1dx_1} & \cdots & \frac{d^2f}{dx_1dx_n} \\ \vdots & \ddots & \vdots \\ \frac{d^2f}{dx_ndx_1} & \cdots & \frac{d^2f}{dx_ndx_n} \\ \end{bmatrix}.\end{split}\]

Không phải mọi hạng tử của ma trận này đều độc lập. Thật vậy, chúng ta có thể chứng minh rằng miễn là cả hai đạo hàm riêng hỗn hợp - mixed partials (đạo hàm riêng theo nhiều hơn một biến số) có tồn tại và liên tục, thì hàm số luôn tồn tại và liên tục với mọi \(i\)\(j\),

(18.4.27)\[\frac{d^2f}{dx_idx_j} = \frac{d^2f}{dx_jdx_i}.\]

Điều này suy ra được bằng việc xem xét khi ta thay đổi hàm lần lượt theo \(x_i\) rồi \(x_j\), và ngược lại thay đổi \(x_j\) rồi \(x_i\), và so sánh hai kết quả này, biết rằng cả hai thứ tự này ảnh hưởng đến đầu ra của \(f\) như nhau.

Như với các hàm đơn biến, chúng ta có thể sử dụng những đạo hàm này để hiểu rõ hơn về hành vi của hàm số lân cận một điểm. Cụ thể, chúng ta có thể sử dụng nó để tìm hàm bậc hai phù hợp nhất lân cận \(\mathbf{x}_0\) tương tự như trong giải tích đơn biến.

Hãy tham khảo một ví dụ. Giả sử rằng \(f(x_1, x_2) = a + b_1x_1 + b_2x_2 + c_{11}x_1^{2} + c_{12}x_1x_2 + c_{22}x_2^{2}\). Đây là một dạng tổng quát của hàm bậc hai 2 biến. Nếu chúng ta nhìn vào giá trị của hàm, gradient và Hessian của nó (18.4.26), tất cả tại điểm 0:

(18.4.28)\[\begin{split}\begin{aligned} f(0,0) & = a, \\ \nabla f (0,0) & = \begin{bmatrix}b_1 \\ b_2\end{bmatrix}, \\ \mathbf{H} f (0,0) & = \begin{bmatrix}2 c_{11} & c_{12} \\ c_{12} & 2c_{22}\end{bmatrix}, \end{aligned}\end{split}\]

Chúng ta có thể thu lại được đa thức ban đầu bằng cách đặt:

(18.4.29)\[f(\mathbf{x}) = f(0) + \nabla f (0) \cdot \mathbf{x} + \frac{1}{2}\mathbf{x}^\top \mathbf{H} f (0) \mathbf{x}.\]

Nhìn chung, nếu chúng ta tính toán khai triển này tại mọi điểm \(\mathbf{x}_0\), ta có:

(18.4.30)\[f(\mathbf{x}) = f(\mathbf{x}_0) + \nabla f (\mathbf{x}_0) \cdot (\mathbf{x}-\mathbf{x}_0) + \frac{1}{2}(\mathbf{x}-\mathbf{x}_0)^\top \mathbf{H} f (\mathbf{x}_0) (\mathbf{x}-\mathbf{x}_0).\]

Cách này hoạt động cho bất cứ đầu vào thứ nguyên nào và cung cấp gần đúng nhất hàm bậc hai cho một hàm bất kỳ tại một điểm. Lấy biểu đồ của hàm sau làm ví dụ.

(18.4.31)\[f(x, y) = xe^{-x^2-y^2}.\]

Có thể tính toán gradient và Hessian như sau:

(18.4.32)\[\begin{split}\nabla f(x, y) = e^{-x^2-y^2}\begin{pmatrix}1-2x^2 \\ -2xy\end{pmatrix} \; \text{and} \; \mathbf{H}f(x, y) = e^{-x^2-y^2}\begin{pmatrix} 4x^3 - 6x & 4x^2y - 2y \\ 4x^2y-2y &4xy^2-2x\end{pmatrix}.\end{split}\]

Kết hợp một chút đại số, ta thấy rằng hàm bậc hai xấp xỉ tại \([-1,0]^\top\) là:

(18.4.33)\[f(x, y) \approx e^{-1}\left(-1 - (x+1) +(x+1)^2+y^2\right).\]
# Construct grid and compute function
x, y = np.meshgrid(np.linspace(-2, 2, 101),
                   np.linspace(-2, 2, 101), indexing='ij')
z = x*np.exp(- x**2 - y**2)

# Compute approximating quadratic with gradient and Hessian at (1, 0)
w = np.exp(-1)*(-1 - (x + 1) + (x + 1)**2 + y**2)

# Plot function
ax = d2l.plt.figure().add_subplot(111, projection='3d')
ax.plot_wireframe(x, y, z, **{'rstride': 10, 'cstride': 10})
ax.plot_wireframe(x, y, w, **{'rstride': 10, 'cstride': 10}, color='purple')
d2l.plt.xlabel('x')
d2l.plt.ylabel('y')
d2l.set_figsize()
ax.set_xlim(-2, 2)
ax.set_ylim(-2, 2)
ax.set_zlim(-1, 1)
ax.dist = 12
../_images/output_multivariable-calculus_vn_3c62bf_11_0.svg

Điều này tạo cơ sở cho Thuật toán Newton được thảo luận ở Section 11.7, trong đó chúng ta lặp đi lặp lại việc tối ưu hoá để tìm ra hàm bậc hai phù hợp nhất và sau đó cực tiểu hoá hàm bậc hai đó.

18.4.7. Giải tích Ma trận

Đạo hàm của các hàm có liên quan đến ma trận hoá ra rất đẹp. Phần này sẽ nặng về mặt ký hiệu, vì vậy độc giả có thể bỏ qua trong lần đọc đầu tiên. Tuy nhiên sẽ rất hữu ích khi biết rằng đạo hàm của các hàm liên quan đến các phép toán ma trận thường gọn gàng hơn nhiều so với suy nghĩ ban đầu của chúng ta, đặc biệt là bởi sự quan trọng của các phép tính ma trận trong các ứng dụng học sâu.

Hãy xem một ví dụ. Giả sử chúng ta có một vài vector cột cố định \(\boldsymbol{\beta}\), và chúng ta muốn lấy hàm tích \(f(\mathbf{x}) = \boldsymbol{\beta}^\top\mathbf{x}\), và hiểu cách tích vô hướng thay đổi khi chúng ta thay đổi \(\mathbf{x}\).

Ký hiệu có tên ma trận đạo hàm sắp xếp theo mẫu số - denominator layout matrix derivative sẽ hữu ích khi làm việc với ma trận đạo hàm trong học máy, trong đó chúng ta tập hợp các đạo hàm riêng theo mẫu số của vi phân, biểu diễn thành các dạng vector, ma trận hoặc tensor. Trong trường hợp này, chúng ta viết:

(18.4.34)\[\begin{split}\frac{df}{d\mathbf{x}} = \begin{bmatrix} \frac{df}{dx_1} \\ \vdots \\ \frac{df}{dx_n} \end{bmatrix},\end{split}\]

mà ở đây nó khớp với hình dạng của vector cột \(\mathbf{x}\).

Triển khai hàm của chúng ta thành các thành tố

(18.4.35)\[f(\mathbf{x}) = \sum_{i = 1}^{n} \beta_ix_i = \beta_1x_1 + \cdots + \beta_nx_n.\]

Nếu bây giờ ta tính đạo hàm riêng theo \(\beta_1\) chẳng hạn, để ý rằng tất cả các phần tử bằng không ngoại trừ số hạng đầu tiên là \(x_1\) nhân với \(\beta_1\). Vì thế, ta có

(18.4.36)\[\frac{df}{dx_1} = \beta_1,\]

hoặc tổng quát hơn đó là

(18.4.37)\[\frac{df}{dx_i} = \beta_i.\]

Bây giờ ta có thể gộp chúng lại thành một ma trận như sau

(18.4.38)\[\begin{split}\frac{df}{d\mathbf{x}} = \begin{bmatrix} \frac{df}{dx_1} \\ \vdots \\ \frac{df}{dx_n} \end{bmatrix} = \begin{bmatrix} \beta_1 \\ \vdots \\ \beta_n \end{bmatrix} = \boldsymbol{\beta}.\end{split}\]

Biểu thức trên minh họa một vài yếu tố về giải tích ma trận mà ta sẽ gặp trong suốt phần này:

  • Đầu tiên, các tính toán sẽ trở nên khá phức tạp.
  • Thứ hai, kết quả cuối cùng sẽ gọn gàng hơn quá trình tính toán trung gian, và sẽ luôn có bề ngoài giống với trường hợp đơn biến. Trong trường hợp này, hãy lưu ý rằng \(\frac{d}{dx}(bx) = b\)\(\frac{d}{d\mathbf{x}} (\boldsymbol{\beta}^\top\mathbf{x}) = \boldsymbol{\beta}\) là như nhau.
  • Thứ ba, các chuyển vị có thể xuất hiện mà thoạt nhìn không biết chính xác từ đâu ra. Lý do chủ yếu là do ta quy ước đạo hàm sẽ có cùng kích thước với mẫu số, do đó khi nhân ma trận, ta cần lấy chuyển vị tương ứng để khớp với kích thước ban đầu.

Ta hãy thử một phép tính khó hơn làm ví dụ minh họa trực quan. Giả sử ta có một vector cột \(\mathbf{x}\) và một ma trận vuông \(A\), và ta ta muốn tính biểu thức sau:

(18.4.39)\[\frac{d}{d\mathbf{x}}(\mathbf{x}^\top A \mathbf{x}).\]

Để thuận tiện cho việc ký hiệu, ta hãy viết lại bài toán bằng ký hiệu Einstein.

(18.4.40)\[\mathbf{x}^\top A \mathbf{x} = x_ia_{ij}x_j.\]

Để tính đạo hàm, ta cần tính các giá trị sau với từng giá trị của biến \(k\):

(18.4.41)\[\frac{d}{dx_k}(\mathbf{x}^\top A \mathbf{x}) = \frac{d}{dx_k}x_ia_{ij}x_j.\]

Theo quy tắc nhân, ta có

(18.4.42)\[\frac{d}{dx_k}x_ia_{ij}x_j = \frac{dx_i}{dx_k}a_{ij}x_j + x_ia_{ij}\frac{dx_j}{dx_k}.\]

Với số hạng như \(\frac{dx_i}{dx_k}\), không khó để thấy rằng đạo hàm trên có giá trị bằng 1 khi \(i=k\), ngược lại nó sẽ bằng 0. Điều này có nghĩa là mọi số hạng với \(i\)\(k\) khác nhau sẽ biến mất khỏi tổng trên, vì thế các số hạng duy nhất còn lại trong tổng đầu tiên đó là những số hạng với \(i=k\). Lập luận tương tự cũng áp dụng cho số hạng thứ hai khi ta cần \(j=k\). Từ đó, ta có

(18.4.43)\[\frac{d}{dx_k}x_ia_{ij}x_j = a_{kj}x_j + x_ia_{ik}.\]

Hiện tại, tên của các chỉ số trong ký hiệu Einstein là tùy ý - việc \(i\)\(j\) khác nhau không quan trọng cho tính toán tại thời điểm này, vì thế ta có thể gán lại chỉ số sao cho cả hai đều chứa \(i\)

(18.4.44)\[\frac{d}{dx_k}x_ia_{ij}x_j = a_{ki}x_i + x_ia_{ik} = (a_{ki} + a_{ik})x_i.\]

Bây giờ, ta cần luyện tập một chút để có thể đi sâu hơn. Ta hãy thử xác định kết quả trên theo các phép toán ma trận. \(a_{ki} + a_{ik}\) là phần tử thứ \(k, i\) của \(\mathbf{A} + \mathbf{A}^\top\). Từ đó, ta có

(18.4.45)\[\frac{d}{dx_k}x_ia_{ij}x_j = [\mathbf{A} + \mathbf{A}^\top]_{ki}x_i.\]

Tương tự, hạng tử này là tích của ma trận \(\mathbf{A} + \mathbf{A}^\top\) với vector \(\mathbf{x}\), nên ta có

(18.4.46)\[\left[\frac{d}{d\mathbf{x}}(\mathbf{x}^\top A \mathbf{x})\right]_k = \frac{d}{dx_k}x_ia_{ij}x_j = [(\mathbf{A} + \mathbf{A}^\top)\mathbf{x}]_k.\]

Ta thấy phần tử thứ \(k\) của đạo hàm mong muốn từ (18.4.39) đơn giản là phần tử thứ \(k\) của vector bên vế phải, và do đó hai phần tử này là như nhau. Điều này dẫn đến

(18.4.47)\[\frac{d}{d\mathbf{x}}(\mathbf{x}^\top A \mathbf{x}) = (\mathbf{A} + \mathbf{A}^\top)\mathbf{x}.\]

Biểu thức trên cần nhiều biến đổi để suy ra được hơn ở phần trước, nhưng kết quả cuối cùng vẫn sẽ gọn gàng. Hơn thế nữa, hãy xem xét tính toán dưới đây cho đạo hàm đơn biến thông thường:

(18.4.48)\[\frac{d}{dx}(xax) = \frac{dx}{dx}ax + xa\frac{dx}{dx} = (a+a)x.\]

Tương tự, \(\frac{d}{dx}(ax^2) = 2ax = (a+a)x\). Một lần nữa, ta lại thu được kết quả nhìn giống với trường hợp đơn biến nhưng với một phép chuyển vị.

Tại thời điểm này, cách tính trên có vẻ khá đáng ngờ, vì vậy ta hãy thử tìm hiểu lý do tại sao. Khi ta lấy đạo hàm ma trận như trên, đầu tiên ta giả sử biểu thức ta nhận được sẽ là một biểu thức ma trận khác: một biểu thức mà ta có thể viết nó dưới dạng tích và tổng của các ma trận và chuyển vị của chúng. Nếu một biểu thức như vậy tồn tại, nó sẽ phải đúng cho tất cả các ma trận. Do đó, nó sẽ đúng với ma trận \(1 \times 1\), trong đó tích ma trận chỉ là tích của các số, tổng ma trận chỉ là tổng, và phép chuyển vị không có tác dụng gì! Nói cách khác, bất kỳ biểu thức nào chúng ta nhận được phải phù hợp với biểu thức đơn biến. Điều này có nghĩa là khi ta biết đạo hàm đơn biến tương ứng, với một chút luyện tập ta có thể đoán được các đạo hàm ma trận!

Cùng kiểm nghiệm điều này. Giả sử \(\mathbf{X}\) là ma trận \(n \times m\), \(\mathbf{U}\) là ma trận \(n \times r\)\(\mathbf{V}\) là ma trận \(r \times m\). Ta sẽ tính

(18.4.49)\[\frac{d}{d\mathbf{V}} \|\mathbf{X} - \mathbf{U}\mathbf{V}\|_2^{2} = \;?\]

Phép tính này khá quan trọng trong phân rã ma trận. Tuy nhiên, ở đây nó chỉ đơn giản là một đạo hàm mà ta cần tính. Hãy thử tưởng tượng xem nó sẽ như thế nào đối với ma trận \(1\times1\). Trong trường hợp này, ta có biểu thức sau

(18.4.50)\[\frac{d}{dv} (x-uv)^{2}= -2(x-uv)u,\]

Có thể thấy, đây là một đạo hàm khá phổ thông. Nếu ta thử chuyển đổi nó thành một biểu thức ma trận, ta có

(18.4.51)\[\frac{d}{d\mathbf{V}} \|\mathbf{X} - \mathbf{U}\mathbf{V}\|_2^{2}= -2(\mathbf{X} - \mathbf{U}\mathbf{V})\mathbf{U}.\]

Tuy nhiên, nếu ta nhìn kỹ, điều này không hoàn toàn đúng. Hãy nhớ lại \(\mathbf{X}\) có kích thước \(n \times m\), giống \(\mathbf{U}\mathbf{V}\), nên ma trận \(2(\mathbf{X} - \mathbf{U}\mathbf{V})\) có kích thước \(n \times m\). Mặt khác \(\mathbf{U}\) có kích thước \(n \times r\), và ta không thể nhân một ma trận \(n \times m\) với một ma trận \(n \times r\) vì số chiều của chúng không khớp nhau!

Ta muốn nhận \(\frac{d}{d\mathbf{V}}\), cùng kích thước với \(\mathbf{V}\)\(r \times m\). Vì vậy ta bằng cách nào đó cần phải nhân một ma trận \(n \times m\) với một ma trận \(n \times r\) (có thể phải chuyển vị) để có ma trận \(r \times m\). Ta có thể làm điều này bằng cách nhân \(U^\top\) với \((\mathbf{X} - \mathbf{U}\mathbf{V})\). Vì vậy, ta có thể đoán nghiệm cho (18.4.49)

(18.4.52)\[\frac{d}{d\mathbf{V}} \|\mathbf{X} - \mathbf{U}\mathbf{V}\|_2^{2}= -2\mathbf{U}^\top(\mathbf{X} - \mathbf{U}\mathbf{V}).\]

Để chứng minh rằng điều này là đúng, ta cần một tính toán chi tiết. Nếu bạn tin rằng quy tắc trực quan ở trên là đúng, bạn có thể bỏ qua phần trình bày này. Để tính toán

(18.4.53)\[\frac{d}{d\mathbf{V}} \|\mathbf{X} - \mathbf{U}\mathbf{V}\|_2^2,\]

với mỗi \(a\)\(b\), ta phải tính.

(18.4.54)\[\frac{d}{dv_{ab}} \|\mathbf{X} - \mathbf{U}\mathbf{V}\|_2^{2}= \frac{d}{dv_{ab}} \sum_{i, j}\left(x_{ij} - \sum_k u_{ik}v_{kj}\right)^2.\]

Hãy nhớ lại rằng tất cả các phần tử của \(\mathbf{X}\)\(\mathbf{U}\) là hằng số khi tính \(\frac{d}{dv_{ab}}\), chúng ta có thể đẩy đạo hàm bên trong tổng, và áp dụng quy tắc dây chuyền sau đó bình phương lên để có

(18.4.55)\[\frac{d}{dv_{ab}} \|\mathbf{X} - \mathbf{U}\mathbf{V}\|_2^{2}= \sum_{i, j}2\left(x_{ij} - \sum_k u_{ik}v_{kj}\right)\left(-\sum_k u_{ik}\frac{dv_{kj}}{dv_{ab}} \right).\]

Tương tự phần diễn giải trước, ta có thể để ý rằng \(\frac{dv_{kj}}{dv_{ab}}\) chỉ khác không nếu \(k=a\)\(j=b\). Nếu một trong hai điều kiện đó không thỏa, số hạng trong tổng bằng không, ta có thể tự do loại bỏ nó. Ta thấy rằng

(18.4.56)\[\frac{d}{dv_{ab}} \|\mathbf{X} - \mathbf{U}\mathbf{V}\|_2^{2}= -2\sum_{i}\left(x_{ib} - \sum_k u_{ik}v_{kb}\right)u_{ia}.\]

Một điểm tinh tế quan trọng ở đây là yêu cầu về \(k=a\) không xảy ra bên trong tổng phía trong bởi \(k\) chỉ là một biến tùy ý để tính tổng các số hạng trong tổng phía trong. Một ví dụ dễ hiểu hơn:

(18.4.57)\[\frac{d}{dx_1} \left(\sum_i x_i \right)^{2}= 2\left(\sum_i x_i \right).\]

Từ đây, ta có thể bắt đầu xác định các thành phần của tổng. Đầu tiên,

(18.4.58)\[\sum_k u_{ik}v_{kb} = [\mathbf{U}\mathbf{V}]_{ib}.\]

Cho nên toàn bộ biểu thức bên trong tổng là

(18.4.59)\[x_{ib} - \sum_k u_{ik}v_{kb} = [\mathbf{X}-\mathbf{U}\mathbf{V}]_{ib}.\]

Điều này nghĩa là giờ đây đạo hàm của ta có thể viết dưới dạng

(18.4.60)\[\frac{d}{dv_{ab}} \|\mathbf{X} - \mathbf{U}\mathbf{V}\|_2^{2}= -2\sum_{i}[\mathbf{X}-\mathbf{U}\mathbf{V}]_{ib}u_{ia}.\]

Chúng ta có thể muốn nó trông giống như phần tử \(a, b\) của một ma trận để có thể sử dụng các kỹ thuật trong các ví dụ trước đó nhằm đạt được một biểu thức ma trận, nghĩa là ta cần phải hoán đổi thứ tự của các chỉ số trên \(u_{ia}\). Nếu để ý \(u_{ia} = [\mathbf{U}^\top]_{ai}\), ta có thể viết

(18.4.61)\[\frac{d}{dv_{ab}} \|\mathbf{X} - \mathbf{U}\mathbf{V}\|_2^{2}= -2\sum_{i} [\mathbf{U}^\top]_{ai}[\mathbf{X}-\mathbf{U}\mathbf{V}]_{ib}.\]

Đây là tích một ma trận, vì thế ta có thể kết luận

(18.4.62)\[\frac{d}{dv_{ab}} \|\mathbf{X} - \mathbf{U}\mathbf{V}\|_2^{2}= -2[\mathbf{U}^\top(\mathbf{X}-\mathbf{U}\mathbf{V})]_{ab}.\]

và vì vậy ta có lời giải cho (18.4.49)

(18.4.63)\[\frac{d}{d\mathbf{V}} \|\mathbf{X} - \mathbf{U}\mathbf{V}\|_2^{2}= -2\mathbf{U}^\top(\mathbf{X} - \mathbf{U}\mathbf{V}).\]

Lời giải này trùng với biểu thức mà ta đoán ở phía trên!

Lúc này cũng dễ hiểu nếu ta tự hỏi “Tại sao không viết tất cả các quy tắc giải tích đã từng học thành dạng ma trận? Điều này rõ ràng là công việc máy móc. Tại sao ta không đơn giản là làm hết một lần cho xong?” Và thực sự có những quy tắc như thế, [Petersen et al., 2008] cho ta một bản tóm tắt tuyệt vời. Tuy nhiên, vì số cách kết hợp các phép toán ma trận nhiều hơn hẳn so với các giá trị một biến, nên có nhiều quy tắc đạo hàm ma trận hơn các quy tắc dành cho hàm cho một biến. Thông thường, tốt nhất là làm việc với các chỉ số, hoặc dùng vi phân tự động khi thích hợp.

18.4.8. Tóm tắt

  • Với không gian nhiều chiều, chúng ta có thể định nghĩa gradient cùng mục đích như các đạo hàm một chiều. Điều này cho phép ta thấy cách một hàm đa biến thay đổi như thế nào khi có bất kỳ thay đổi nhỏ xảy ra ở đầu vào.
  • Thuật toán lan truyền ngược có thể được xem như một phương pháp trong việc tổ chức quy tắc dây chuyền đa biến cho phép tính toán hiệu quả các đạo hàm riêng.
  • Giải tích ma trận cho phép chúng ta viết các đạo hàm của biểu thức ma trận một cách gọn gàng hơn.

18.4.9. Bài tập

  1. Cho một vector cột \(\boldsymbol{\beta}\), tính các đạo hàm của cả hai ma trận \(f(\mathbf{x}) = \boldsymbol{\beta}^\top\mathbf{x}\) và ma trận \(g(\mathbf{x}) = \mathbf{x}^\top\boldsymbol{\beta}\). Hãy cho biết tại sao bạn lại ra cùng đáp án?
  2. Cho \(\mathbf{v}\) là một vector \(n\) chiều. Vậy \(\frac{\partial}{\partial\mathbf{v}}\|\mathbf{v}\|_2\)? là gì?
  3. Cho \(L(x, y) = \log(e^x + e^y)\). Tính toán gradient. Tổng của các thành phần của gradient là gì?
  4. Cho \(f(x, y) = x^2y + xy^2\). Chứng minh rằng điểm tới hạn duy nhất là \((0,0)\). Bằng việc xem xét \(f(x, x)\), hãy xác định xem \((0,0)\) là cực đại, cực tiểu, hay không phải cả hai.
  5. Giả sử ta đang tối thiểu hàm \(f(\mathbf{x}) = g(\mathbf{x}) + h(\mathbf{x})\). Làm cách nào ta có thể diễn giải bằng hình học điều kiện \(\nabla f = 0\) thông qua \(g\)\(h\)?

18.4.10. Thảo luận

18.4.11. 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 Hồng Vinh
  • Nguyễn Lê Quang Nhật
  • Nguyễn Văn Quang
  • Nguyễn Thanh Hòa
  • Nguyễn Văn Cường
  • Trần Yến Thy
  • Nguyễn Mai Hoàng Long