.. raw:: html .. _sec_single_variable_calculus: Giải tích một biến ================== .. raw:: html Trong :numref:`sec_calculus`, chúng ta đã thấy những thành phần cơ bản của giải tích vi phân. Trong mục này chúng ta sẽ đi sâu vào kiến thức nền tảng của giải tích và cách áp dụng chúng trong trong ngữ cảnh học máy. .. raw:: html Giải tích Vi phân ----------------- .. raw:: html Giải tích vi phân là nhánh toán học nghiên cứu về hành vi của các hàm số dưới các biến đổi nhỏ. Để thấy được tại sao đây lại là phần cốt lõi của học sâu, hãy cùng xem xét một ví dụ dưới đây. .. raw:: html Giả sử chúng ta có một mạng nơ-ron sâu với các trọng số được biễu diễn bằng một vector duy nhất :math:`\mathbf{w} = (w_1, \ldots, w_n)`. Cho trước một tập huấn luyện, chúng ta sẽ tập trung vào giá trị mất mát :math:`\mathcal{L}(\mathbf{w})` của mạng nơ-ron trên tập huấn luyện đó. .. raw:: html Đây là một hàm số cực kì phức tạp, biểu diễn chất lượng của tất cả các mô hình khả dĩ của một cấu trúc mạng cho trước trên tập dữ liệu này, nên gần như không thể chỉ ra được ngay một tập các trọng số :math:`\mathbf{w}` để cực tiểu hóa mất mát. Do vậy trên thực tế, chúng ta thường bắt đầu bằng việc khởi tạo *ngẫu nhiên* các trọng số, và tiến từng bước nhỏ theo hướng mà sẽ giảm giá trị mất mát nhanh nhất có thể. .. raw:: html Vấn đề bây giờ thoạt nhìn cũng không dễ hơn bao nhiêu: làm thế nào để tìm được hướng đi sẽ giảm giá trị hàm mất mát nhanh nhất có thể? Để trả lời câu hỏi này, trước hết ta hãy xét trường hợp chỉ có một trọng số: :math:`L(\mathbf{w}) = L(x)` với một số thực :math:`x` duy nhất. .. raw:: html Hãy cùng tìm hiểu xem chuyện gì sẽ xảy ra khi ta lấy giá trị :math:`x` và thay đổi nó với một lượng rất nhỏ thành :math:`x + \epsilon`. Nếu bạn muốn một con số rõ ràng, hãy nghĩ về một số như :math:`\epsilon = 0.0000001`. Để minh họa chuyện gì sẽ diễn ra, hãy vẽ ví dụ đồ thị của hàm số :math:`f(x) = \sin(x^x)`, trên khoảng :math:`[0, 3]`. .. code:: python %matplotlib inline from d2l import mxnet as d2l from IPython import display from mxnet import np, npx npx.set_np() # Plot a function in a normal range x_big = np.arange(0.01, 3.01, 0.01) ys = np.sin(x_big**x_big) d2l.plot(x_big, ys, 'x', 'f(x)') .. figure:: output_single-variable-calculus_vn_99f0a2_1_0.svg .. raw:: html Trong một khoảng lớn thế này, cách hàm số biến đổi rất khó nắm bắt. Tuy nhiên, nếu ta thu nhỏ khoảng xuống ví dụ như thành :math:`[1.75,2.25]`, ta thấy đồ thị trở nên đơn giản hơn rất nhiều. .. code:: python # Plot a the same function in a tiny range x_med = np.arange(1.75, 2.25, 0.001) ys = np.sin(x_med**x_med) d2l.plot(x_med, ys, 'x', 'f(x)') .. figure:: output_single-variable-calculus_vn_99f0a2_3_0.svg .. raw:: html Đỉnh điểm, nếu ta phóng gần vào một đoạn rất nhỏ, cách hàm số biến đổi trở nên đơn giản hơn rất nhiều: chỉ là một đường thẳng. .. code:: python # Plot a the same function in a tiny range x_small = np.arange(2.0, 2.01, 0.0001) ys = np.sin(x_small**x_small) d2l.plot(x_small, ys, 'x', 'f(x)') .. figure:: output_single-variable-calculus_vn_99f0a2_5_0.svg .. raw:: html Đây là một trong những quan sát cốt lõi nhất trong giải tích: hành vi của các hàm số phổ biến có thể được mô hình hóa bằng một đường thẳng trên một khoảng đủ nhỏ. Điều này nghĩa là với hầu hết các hàm số, chúng ta có thể trông đợi rằng khi dịch chuyển :math:`x` một khoảng nhỏ, :math:`f(x)` cũng sẽ dịch chuyển một khoảng nhỏ. Câu hỏi duy nhất mà chúng ta cần trả lời là “Sự thay đổi của giá trị đầu ra lớn gấp bao nhiêu lần so với sự thay đổi của giá trị đầu vào? Bằng một nửa? Hay sẽ lớn gấp đôi?” .. raw:: html Ta cũng có thể xét nó như tỷ lệ giữa sự thay đổi của đầu ra so với sự thay đổi nhỏ trong đầu vào của một hàm số. Chúng ta có thể biễu diễn nó dưới dạng toán học là: .. math:: \frac{L(x+\epsilon) - L(x)}{(x+\epsilon) - x} = \frac{L(x+\epsilon) - L(x)}{\epsilon}. .. raw:: html Những kiến thức trên đã đủ để chúng ta bắt đầu thực hành lập trình. Ví dụ, giả sử :math:`L(x) = x^{2} + 1701(x-4)^3`, ta có thể biết được độ lớn của giá trị này tại điểm :math:`x = 4` như sau: .. code:: python # Define our function def L(x): return x**2 + 1701*(x-4)**3 # Print the difference divided by epsilon for several epsilon for epsilon in [0.1, 0.001, 0.0001, 0.00001]: print(f'epsilon = {epsilon:.5f} -> {(L(4+epsilon) - L(4)) / epsilon:.5f}') .. parsed-literal:: :class: output epsilon = 0.10000 -> 25.11000 epsilon = 0.00100 -> 8.00270 epsilon = 0.00010 -> 8.00012 epsilon = 0.00001 -> 8.00001 .. raw:: html Nếu để ý kĩ, chúng ta sẽ nhận ra rằng kết quả của con số này xấp xỉ :math:`8`. Trong trường hợp ta giảm :math:`\epsilon` thì giá trị đầu ra ngày càng tiến gần đến :math:`8`. Vì vậy chúng ta có thể kết luận một cách chính xác, rằng mức độ thay đổi của đầu ra khi đầu vào thay đổi là :math:`8` tại điểm :math:`x=4`. Có thể viết dưới dạng toán học như sau: .. math:: \lim_{\epsilon \rightarrow 0}\frac{L(4+\epsilon) - L(4)}{\epsilon} = 8. .. raw:: html Một chút bàn luận ngoài lề về lịch sử: trong những thập kỷ đầu tiên của các nghiên cứu mạng nơ-ron, các nhà khoa học đã sử dụng thuật toán này (*sai phân hữu hạn - finite differences*) để đánh giá một hàm mất mát dưới các nhiễu loạn nhỏ: chỉ cần thay đổi trọng số và xem cách thức mà hàm mất mát thay đổi. Đây là một cách tính toán không hiệu quả, đòi hỏi đến hai lần tính hàm mất mát để thấy được sự tác động của một thay đổi lên hàm mất mát đó. Thậm chí nếu chúng ta sử dụng phương pháp này với vài nghìn tham số nhỏ, nó cũng sẽ đòi hỏi phải chạy mạng nơ-ron hàng nghìn lần trên toàn bộ dữ liệu. Phải đến năm 1986 thì vấn đề này với được giải quyết khi *thuật toán lan truyền ngược* (*backpropagation algorithm*) được giới thiệu ở :cite:`Rumelhart.Hinton.Williams.ea.1988` đã đem đến một giải pháp để tính toán sức ảnh hưởng của những thay đổi *bất kỳ* từ các trọng số lên hàm mất mát với thời gian tính toán chỉ bằng thời gian mô hình đưa ra dự đoán trên tập dữ liệu. .. raw:: html Quay lại với ví dụ của chúng ta, giá trị :math:`8` này biến thiên với các trị khác nhau của :math:`x`, vậy nên sẽ là hợp lý nếu chúng ta định nghĩa nó như là một hàm của :math:`x`. Một cách chính thống hơn, độ biến thiên của giá trị này được gọi là *đạo hàm* và được viết là: .. math:: \frac{df}{dx}(x) = \lim_{\epsilon \rightarrow 0}\frac{f(x+\epsilon) - f(x)}{\epsilon}. :label: eq_der_def .. raw:: html Các văn bản khác nhau sẽ sử dụng các ký hiệu khác nhau cho đạo hàm. Chẳng hạn, tất cả các ký hiệu dưới đây đều diễn giải cùng một ý nghĩa: .. math:: \frac{df}{dx} = \frac{d}{dx}f = f' = \nabla_xf = D_xf = f_x. .. raw:: html Phần lớn các tác giả sẽ chọn một ký hiệu duy nhất để sử dụng xuyên suốt, tuy nhiên không phải lúc nào điều này cũng được đảm bảo. Tốt hơn hết là chúng ta nên làm quen với tất cả các ký hiệu này. Ký hiệu :math:`\frac{df}{dx}` sẽ được sử dụng trong toàn bộ cuốn sách này, trừ trường hợp chúng ta cần lấy đạo hàm của một biểu thức phức tạp, khi đó chúng ta sẽ sử dụng :math:`\frac{d}{dx}f` để biểu diễn những biểu thức như .. math:: \frac{d}{dx}\left[x^4+\cos\left(\frac{x^2+1}{2x-1}\right)\right]. .. raw:: html Đôi khi, việc sử dụng định nghĩa của đạo hàm :eq:`eq_der_def` để thấy một cách trực quan cách một hàm thay đổi khi :math:`x` thay đổi một khoảng nhỏ là rất hữu ích: .. math:: \begin{aligned} \frac{df}{dx}(x) = \lim_{\epsilon \rightarrow 0}\frac{f(x+\epsilon) - f(x)}{\epsilon} & \implies \frac{df}{dx}(x) \approx \frac{f(x+\epsilon) - f(x)}{\epsilon} \\ & \implies \epsilon \frac{df}{dx}(x) \approx f(x+\epsilon) - f(x) \\ & \implies f(x+\epsilon) \approx f(x) + \epsilon \frac{df}{dx}(x). \end{aligned} :label: eq_small_change .. raw:: html Cần phải nói rõ hơn về phương trình cuối cùng. Nó cho chúng ta biết rằng nếu ta chọn một hàm số bất kỳ và thay đổi đầu vào một lượng nhỏ, sự thay đổi của đầu ra sẽ bằng với lượng nhỏ đó nhân với đạo hàm. .. raw:: html Bằng cách này, chúng ta có thể hiểu đạo hàm là hệ số tỷ lệ cho biết mức độ biến thiên của đầu ra khi đầu vào thay đổi. .. raw:: html .. _sec_derivative_table: Quy tắc Giải tích ----------------- .. raw:: html Bây giờ chúng ta sẽ học cách để tính đạo hàm của một hàm cụ thể. Dạy giải tích một cách chính quy sẽ phải chứng minh lại tất cả mọi thứ từ những định đề căn bản nhất. Tuy nhiên chúng tôi sẽ không làm như vậy mà sẽ cung cấp các quy tắc tính đạo hàm phổ biến thường gặp. .. raw:: html Các Đạo hàm phổ biến ~~~~~~~~~~~~~~~~~~~~ .. raw:: html Như ở :numref:`sec_calculus`, khi tính đạo hàm ta có thể sử dụng một chuỗi các quy tắc để chia nhỏ tính toán thành các hàm cơ bản. Chúng tôi sẽ nhắc lại chúng ở đây để bạn đọc dễ tham khảo. .. raw:: html - **Đạo hàm hằng số:** :math:`\frac{d}{dx}c = 0`. - **Đạo hàm hàm tuyến tính:** :math:`\frac{d}{dx}(ax) = a`. - **Quy tắc lũy thừa:** :math:`\frac{d}{dx}x^n = nx^{n-1}`. - **Đạo hàm hàm mũ cơ số tự nhiên:** :math:`\frac{d}{dx}e^x = e^x`. - **Đạo hàm hàm logarit cơ số tự nhiên:** :math:`\frac{d}{dx}\log(x) = \frac{1}{x}`. .. raw:: html Các Quy tắc tính Đạo hàm ~~~~~~~~~~~~~~~~~~~~~~~~ .. raw:: html Nếu mọi đạo hàm cần được tính một cách riêng biệt và lưu vào một bảng, giải tích vi phân sẽ gần như bất khả thi. Toán học đã mang lại một món quà giúp tổng quát hóa các đạo hàm ở phần trên và giúp tính các đạo hàm phức tạp hơn như đạo hàm của :math:`f(x) = \log\left(1+(x-1)^{10}\right)`. Như được đề cập trong :numref:`sec_calculus`, chìa khóa để thực hiện việc này là hệ thống hóa việc tính đạo hàm cho các hàm kết hợp theo nhiều cách: tổng, tích và hợp. .. raw:: html - **Quy tắc tổng.** :math:`\frac{d}{dx}\left(g(x) + h(x)\right) = \frac{dg}{dx}(x) + \frac{dh}{dx}(x)`. - **Quy tắc tích.** :math:`\frac{d}{dx}\left(g(x)\cdot h(x)\right) = g(x)\frac{dh}{dx}(x) + \frac{dg}{dx}(x)h(x)`. - **Quy tắc dây chuyền.** :math:`\frac{d}{dx}g(h(x)) = \frac{dg}{dh}(h(x))\cdot \frac{dh}{dx}(x)`. .. raw:: html Cùng xem chúng ta có thể sử dụng :eq:`eq_small_change` như thế nào để hiểu những quy tắc này. Với quy tắc tổng, xét chuỗi biến đổi sau đây: .. math:: \begin{aligned} f(x+\epsilon) & = g(x+\epsilon) + h(x+\epsilon) \\ & \approx g(x) + \epsilon \frac{dg}{dx}(x) + h(x) + \epsilon \frac{dh}{dx}(x) \\ & = g(x) + h(x) + \epsilon\left(\frac{dg}{dx}(x) + \frac{dh}{dx}(x)\right) \\ & = f(x) + \epsilon\left(\frac{dg}{dx}(x) + \frac{dh}{dx}(x)\right). \end{aligned} .. raw:: html Đồng nhất hệ số với :math:`f(x+\epsilon) \approx f(x) + \epsilon \frac{df}{dx}(x)`, ta có :math:`\frac{df}{dx}(x) = \frac{dg}{dx}(x) + \frac{dh}{dx}(x)` như mong đợi. Một cách trực quan, ta có thể giải thích như sau: khi thay đổi đầu vào :math:`x`, :math:`g` và :math:`h` cùng đóng góp tới sự thay đổi của :math:`\frac{dg}{dx}(x)` và :math:`\frac{dh}{dx}(x)` ở đầu ra. .. raw:: html Đối với quy tắc tích thì phức tạp hơn một chút và đòi hỏi một quan sát mới khi xử lý các biểu thức này. Cũng giống như trước, ta bắt đầu bằng :eq:`eq_small_change`: .. math:: \begin{aligned} f(x+\epsilon) & = g(x+\epsilon)\cdot h(x+\epsilon) \\ & \approx \left(g(x) + \epsilon \frac{dg}{dx}(x)\right)\cdot\left(h(x) + \epsilon \frac{dh}{dx}(x)\right) \\ & = g(x)\cdot h(x) + \epsilon\left(g(x)\frac{dh}{dx}(x) + \frac{dg}{dx}(x)h(x)\right) + \epsilon^2\frac{dg}{dx}(x)\frac{dh}{dx}(x) \\ & = f(x) + \epsilon\left(g(x)\frac{dh}{dx}(x) + \frac{dg}{dx}(x)h(x)\right) + \epsilon^2\frac{dg}{dx}(x)\frac{dh}{dx}(x). \\ \end{aligned} .. raw:: html Việc này giống với những tính toán trước đây, và dễ thấy kết quả của ta (:math:`\frac{df}{dx}(x) = g(x)\frac{dh}{dx}(x) + \frac{dg}{dx}(x)h(x)`) là số hạng được nhân với :math:`\epsilon`, nhưng vấn đề là ở số hạng nhân với giá trị :math:`\epsilon^{2}`. Chúng ta sẽ gọi số hạng này là *số hạng bậc cao*, bởi số mũ của :math:`\epsilon^2` cao hơn số mũ của :math:`\epsilon^1`. Về sau ta sẽ thấy rằng thi thoảng ta muốn giữ các số hạng này, tuy nhiên hiện tại có thể thấy rằng nếu :math:`\epsilon = 0.0000001`, thì :math:`\epsilon^{2}= 0.0000000000001`, là một số nhỏ hơn rất nhiều. Khi đưa :math:`\epsilon \rightarrow 0`, ta có thể bỏ qua các số hạng bậc cao. Ta sẽ quy ước sử dụng “:math:`\approx`” để ký hiệu rằng hai số hạng bằng nhau với sai số là các thành phần bậc cao. Nếu muốn biểu diễn chính quy hơn, ta có thể xét phương trình .. math:: \frac{f(x+\epsilon) - f(x)}{\epsilon} = g(x)\frac{dh}{dx}(x) + \frac{dg}{dx}(x)h(x) + \epsilon \frac{dg}{dx}(x)\frac{dh}{dx}(x), .. raw:: html và thấy rằng khi :math:`\epsilon \rightarrow 0`, số hạng bên phải cũng tiến về không. .. raw:: html Cuối cùng, với quy tắc dây chuyền, ta vẫn có thể tiếp tục khai triển sử dụng :eq:`eq_small_change` và thấy rằng: .. math:: \begin{aligned} f(x+\epsilon) & = g(h(x+\epsilon)) \\ & \approx g\left(h(x) + \epsilon \frac{dh}{dx}(x)\right) \\ & \approx g(h(x)) + \epsilon \frac{dh}{dx}(x) \frac{dg}{dh}(h(x))\\ & = f(x) + \epsilon \frac{dg}{dh}(h(x))\frac{dh}{dx}(x). \end{aligned} .. raw:: html Chú ý là ở dòng thứ hai trong chuỗi khai triển trên, chúng ta đã xem đối số :math:`h(x)` của hàm :math:`g` như là bị dịch đi bởi một lượng rất nhỏ :math:`\epsilon \frac{dh}{dx}(x)`. .. raw:: html Các quy tắc này cung cấp cho chúng ta một tập hợp các công cụ linh hoạt để tính toán đạo hàm của hầu như bất kỳ biểu thức nào ta muốn. Chẳng hạn như trong ví dụ sau: .. math:: \begin{aligned} \frac{d}{dx}\left[\log\left(1+(x-1)^{10}\right)\right] & = \left(1+(x-1)^{10}\right)^{-1}\frac{d}{dx}\left[1+(x-1)^{10}\right]\\ & = \left(1+(x-1)^{10}\right)^{-1}\left(\frac{d}{dx}[1] + \frac{d}{dx}[(x-1)^{10}]\right) \\ & = \left(1+(x-1)^{10}\right)^{-1}\left(0 + 10(x-1)^9\frac{d}{dx}[x-1]\right) \\ & = 10\left(1+(x-1)^{10}\right)^{-1}(x-1)^9 \\ & = \frac{10(x-1)^9}{1+(x-1)^{10}}. \end{aligned} .. raw:: html Mỗi dòng của ví dụ này đã sử dụng các quy tắc sau: .. raw:: html 1. Quy tắc dây chuyền và công thức đạo hàm của hàm logarit. 2. Quy tắc đạo hàm của tổng. 3. Đạo hàm của hằng số, quy tắc dây chuyền, và quy tắc đạo hàm của lũy thừa. 4. Quy tắc đạo hàm của tổng, đạo hàm của hàm tuyến tính, đạo hàm của hằng số. .. raw:: html Từ ví dụ trên, chúng ta có thể dễ dàng rút ra được hai điều: .. raw:: html 1. Chúng ta có thể lấy đạo hàm của bất kỳ hàm số nào mà có thể diễn tả được bằng tổng, tích, hằng số, lũy thừa, hàm mũ, và hàm logarit bằng cách sử dụng những quy tắc trên một cách máy móc. 2. Quá trình dùng những quy tắc này để tính đạo hàm bằng tay có thể sẽ rất tẻ nhạt và dễ mắc lỗi. .. raw:: html Rất may là hai điều này gộp chung lại gợi ý cho chúng ta một hướng phát triển: đây chính là cơ hội lý tưởng để tự động hóa bằng máy tính! Thật vậy, kỹ thuật lan truyền ngược, mà chúng ta sẽ gặp lại sau ở mục này, là một cách hiện thực hóa ý tưởng này. .. raw:: html Xấp xỉ Tuyến tính ~~~~~~~~~~~~~~~~~ .. raw:: html Thông thường khi làm việc với đạo hàm, sẽ rất hữu ích nếu chúng ta có thể diễn tả sự xấp xỉ ở trên theo phương diện hình học. Nói một cách cụ thể, phương trình này .. math:: f(x+\epsilon) \approx f(x) + \epsilon \frac{df}{dx}(x), .. raw:: html xấp xỉ giá trị của :math:`f` bằng một đường thẳng đi qua điểm :math:`(x, f(x))` và có độ dốc :math:`\frac{df}{dx}(x)`. Với cách hiểu này, ta nói rằng đạo hàm cho ta một xấp xỉ tuyến tính của hàm số :math:`f`, như minh họa dưới đây: .. code:: python # Compute sin xs = np.arange(-np.pi, np.pi, 0.01) plots = [np.sin(xs)] # Compute some linear approximations. Use d(sin(x)) / dx = cos(x) for x0 in [-1.5, 0, 2]: plots.append(np.sin(x0) + (xs - x0) * np.cos(x0)) d2l.plot(xs, plots, 'x', 'f(x)', ylim=[-1.5, 1.5]) .. figure:: output_single-variable-calculus_vn_99f0a2_9_0.svg .. raw:: html Đạo hàm Cấp cao ~~~~~~~~~~~~~~~ .. raw:: html Bây giờ, hãy cùng làm một việc mà nhìn sơ qua thì có vẻ kỳ quặc. Bắt đầu bằng việc lấy một hàm số :math:`f` và tính đạo hàm :math:`\frac{df}{dx}`. Nó sẽ cho chúng ta tốc độ thay đổi của :math:`f` tại bất cứ điểm nào. .. raw:: html Tuy nhiên, vì bản thân đạo hàm :math:`\frac{df}{dx}` cũng là một hàm số, không có gì ngăn cản chúng ta tiếp tục tính đạo hàm của :math:`\frac{df}{dx}` để có :math:`\frac{d^2f}{dx^2} = \frac{df}{dx}\left(\frac{df}{dx}\right)`. Chúng ta sẽ gọi đây là đạo hàm cấp hai của :math:`f`. Hàm số này là tốc độ thay đổi của tốc độ thay đổi của :math:`f`, hay nói cách khác, nó thể hiện tốc độ thay đổi của :math:`f` đang thay đổi như thế nào. Chúng ta có thể tiếp tục lấy đạo hàm như vậy thêm nhiều lần nữa để có được thứ gọi là đạo hàm cấp :math:`n`. Để ký hiệu được gọn gàng, chúng ta sẽ biểu thị đạo hàm cấp :math:`n` như sau: .. math:: f^{(n)}(x) = \frac{d^{n}f}{dx^{n}} = \left(\frac{d}{dx}\right)^{n} f. .. raw:: html Hãy tìm hiểu xem *tại sao* đây lại là một khái niệm hữu ích. Các hàm số :math:`f^{(2)}(x)`, :math:`f^{(1)}(x)`, và :math:`f(x)` được biểu diễn trong các đồ thị dưới đây. .. raw:: html Đầu tiên, xét trường hợp đạo hàm bậc hai :math:`f^{(2)}(x)` là một hằng số dương. Điều này nghĩa là độ dốc của đạo hàm bậc nhất là dương. Hệ quả là, đạo hàm bậc nhất :math:`f^{(1)}(x)` có thể khởi đầu ở âm, bằng không tại một điểm nào đó, rồi cuối cùng tăng lên dương. Điều này cho chúng ta biết độ dốc của hàm :math:`f` ban đầu và do đó, giá trị hàm :math:`f` sẽ giảm xuống đến điểm nào đó rồi tăng lên. Nói cách khác, đồ thị hàm :math:`f` là đường cong đi lên, có một cực tiểu như trong :numref:`fig_positive-second`. .. raw:: html .. _fig_positive-second: .. figure:: ../img/posSecDer.svg Nếu giả định rằng đạo hàm bậc hai là một hằng số dương, thì đạo hàm bậc nhất đồng biến, nghĩa là bản thân hàm đó có một cực tiểu. .. raw:: html Thứ hai là, nếu đạo hàm bậc hai là một hằng số âm, nghĩa là đạo hàm bậc nhất nghịch biến. Vậy tức là đạo hàm bậc nhất có thể khời đầu là dương, bằng không ở điểm nào đó, rồi giảm xuống âm. Do vậy, giá trị hàm :math:`f` tăng lên đến điểm nào đó rồi giảm xuống. Nói cách khác, đồ thị hàm :math:`f` là đường cong đi xuống, có một cực đại như trong :numref:`fig_negative-second`. .. raw:: html .. _fig_negative-second: .. figure:: ../img/negSecDer.svg Nếu giả định đạo hàm bậc hai là một hằng số âm, thì đạo hàm bậc nhất nghịch biến, nghĩa là hàm số có một cực đại. .. raw:: html Thứ ba là, nếu đạo hàm bậc hai luôn luôn bằng không, thì đạo hàm bậc nhất là hằng số! Nghĩa là hàm :math:`f` tăng (hoặc giảm) với tốc độ cố định, và đồ thị :math:`f` là một đường thẳng giống như trong :numref:`fig_zero-second`. .. raw:: html .. _fig_zero-second: .. figure:: ../img/zeroSecDer.svg Nếu ta giả định đạo hàm bậc hai bằng không, thì đạo hàm bậc nhất là hằng số, nên đồ thị hàm này là một đường thẳng. .. raw:: html Tóm lại, đạo hàm bậc hai có thể được hiểu như một cách miêu tả đường cong của đồ thị hàm :math:`f`. Đạo hàm bậc hai dương thì đồ thị cong lên, đạo hàm bậc hai âm thì hàm :math:`f` cong xuống, và nếu bằng không thì :math:`f` là một đường thẳng. .. raw:: html Hãy thử tiến xa hơn một bước. Xét hàm :math:`g(x) = ax^{2}+ bx + c`. Ta có thể tính được .. math:: \begin{aligned} \frac{dg}{dx}(x) & = 2ax + b \\ \frac{d^2g}{dx^2}(x) & = 2a. \end{aligned} .. raw:: html Nếu đã có sẵn một hàm :math:`f(x)`, ta có thể tính đạo hàm cấp một và cấp hai của nó để tìm các giá trị :math:`a, b`, và :math:`c` thỏa mãn hệ phương trình này. Cũng giống như ở mục trước ta đã thấy đạo hàm bậc một cho ra xấp xỉ tốt nhất bằng một đường thẳng, đạo hàm bậc hai cung cấp một xấp xỉ tốt nhất bằng một parabol. Hãy minh họa với trường hợp :math:`f(x) = \sin(x)`. .. code:: python # Compute sin xs = np.arange(-np.pi, np.pi, 0.01) plots = [np.sin(xs)] # Compute some quadratic approximations. Use d(sin(x)) / dx = cos(x) for x0 in [-1.5, 0, 2]: plots.append(np.sin(x0) + (xs - x0) * np.cos(x0) - (xs - x0)**2 * np.sin(x0) / 2) d2l.plot(xs, plots, 'x', 'f(x)', ylim=[-1.5, 1.5]) .. figure:: output_single-variable-calculus_vn_99f0a2_11_0.svg .. raw:: html Ta sẽ mở rộng ý tưởng này thành ý tưởng của *chuỗi Taylor* trong mục tiếp theo. .. raw:: html Chuỗi Taylor ~~~~~~~~~~~~ .. raw:: html *Chuỗi Taylor* cung cấp một phương pháp để xấp xỉ phương trình :math:`f(x)` nếu ta đã biết trước giá trị của :math:`n` cấp đạo hàm đầu tiên tại điểm :math:`x_0`: :math:`\left\{ f(x_0), f^{(1)}(x_0), f^{(2)}(x_0), \ldots, f^{(n)}(x_0) \right\}`. Ý tưởng là tìm một đa thức bậc :math:`n` có các đạo hàm tại :math:`x_0` khớp với các đạo hàm đã biết. .. raw:: html Ta đã thấy với trường hợp :math:`n=2` ở chương trước và với một chút biến đổi đại số, ta có được .. math:: f(x) \approx \frac{1}{2}\frac{d^2f}{dx^2}(x_0)(x-x_0)^{2}+ \frac{df}{dx}(x_0)(x-x_0) + f(x_0). .. raw:: html Như ta đã thấy ở trên, mẫu số :math:`2` là để rút gọn thừa số :math:`2` khi lấy đạo hàm bậc hai của :math:`x^2`, các đạo hàm bậc cao hơn đều bằng không. Cùng một cách lập luận cũng được áp dụng cho đạo hàm bậc một và phần giá trị :math:`f(x_0)`. .. raw:: html Nếu ta mở rộng cách lập luận này cho trường hợp :math:`n=3`, ta sẽ kết luận được .. math:: f(x) \approx \frac{\frac{d^3f}{dx^3}(x_0)}{6}(x-x_0)^3 + \frac{\frac{d^2f}{dx^2}(x_0)}{2}(x-x_0)^{2}+ \frac{df}{dx}(x_0)(x-x_0) + f(x_0). .. raw:: html với :math:`6 = 3 \times 2 = 3!` đến từ phần hằng số ta có được khi lấy đạo hàm bậc 3 của :math:`x^3`. .. raw:: html Hơn nữa, ta có thể lấy một đa thức bậc :math:`n` bằng cách .. math:: P_n(x) = \sum_{i = 0}^{n} \frac{f^{(i)}(x_0)}{i!}(x-x_0)^{i}. .. raw:: html với quy ước .. math:: f^{(n)}(x) = \frac{d^{n}f}{dx^{n}} = \left(\frac{d}{dx}\right)^{n} f. .. raw:: html Quả thật, :math:`P_n(x)` có thể được xem là đa thức bậc :math:`n` xấp xỉ tốt nhất của hàm :math:`f(x)`. .. raw:: html Dù ta sẽ không tìm hiểu kỹ sai số của xấp xỉ này, ta cũng nên nhắc tới giới hạn vô cùng. Trong trường hợp này, các hàm khả vi vô hạn lần như :math:`\cos(x)` hoặc :math:`e^{x}` có thể được biểu diễn xấp xỉ bằng vô số các số hạng. .. math:: f(x) = \sum_{n = 0}^\infty \frac{f^{(n)}(x_0)}{n!}(x-x_0)^{n}. .. raw:: html Lấy hàm :math:`f(x) = e^{x}` làm ví dụ. Vì :math:`e^{x}` là đạo hàm của chính nó, ta có :math:`f^{(n)}(x) = e^{x}`. Do đó, hàm :math:`e^{x}` có thể được tái tạo bằng cách tính chuỗi Taylor tại :math:`x_0 = 0`: .. math:: e^{x} = \sum_{n = 0}^\infty \frac{x^{n}}{n!} = 1 + x + \frac{x^2}{2} + \frac{x^3}{6} + \cdots. .. raw:: html Hãy cùng tìm hiểu cách lập trình và quan sát xem việc tăng bậc của xấp xỉ Taylor đưa ta đến gần hơn với hàm mong muốn :math:`e^x` như thế nào. .. code:: python # Compute the exponential function xs = np.arange(0, 3, 0.01) ys = np.exp(xs) # Compute a few Taylor series approximations P1 = 1 + xs P2 = 1 + xs + xs**2 / 2 P5 = 1 + xs + xs**2 / 2 + xs**3 / 6 + xs**4 / 24 + xs**5 / 120 d2l.plot(xs, [ys, P1, P2, P5], 'x', 'f(x)', legend=[ "Exponential", "Degree 1 Taylor Series", "Degree 2 Taylor Series", "Degree 5 Taylor Series"]) .. figure:: output_single-variable-calculus_vn_99f0a2_13_0.svg .. raw:: html Chuỗi Taylor có hai ứng dụng chính: .. raw:: html 1. *Ứng dụng lý thuyết*: Khi muốn tìm hiểu một hàm số quá phức tạp, ta thường dùng chuỗi Taylor để biến nó thành một đa thức để có thể làm việc trực tiếp. .. raw:: html 2. *Ứng dụng số học*: Việc tính toán một số hàm như :math:`e^x` hoặc :math:`\cos(x)` không đơn giản đối với máy tính. Chúng có thể lưu trữ một bảng giá trị với độ chính xác nhất định (và thường thì chúng làm vậy), nhưng việc đó vẫn không giải quyết được những câu hỏi như “Chữ số thứ 1000 của :math:`\cos(1)` là gì?”. Chuỗi Taylor thường có ích cho việc trả lời các câu hỏi như vậy. Tóm tắt ------- .. raw:: html - Đạo hàm có thể được sử dụng để biểu diễn mức độ thay đổi của hàm số khi đầu vào thay đổi một lượng nhỏ. - Các phép lấy đạo hàm cơ bản có thể kết hợp với nhau theo các quy tắc đạo hàm để tính những đạo hàm phức tạp tùy ý. - Đạo hàm có thể được tính nhiều lần để lấy đạo hàm cấp hai hoặc các cấp cao hơn. Mỗi lần tăng cấp đạo hàm cho ta thông tin chi tiết hơn về hành vi của hàm số. - Bằng việc sử dụng thông tin từ đạo hàm của một điểm dữ liệu, ta có thể xấp xỉ các hàm khả vi vô hạn lần bằng các đa thức lấy từ chuỗi Taylor. Bài tập ------- .. raw:: html 1. Đạo hàm của :math:`x^3-4x+1` là gì? 2. Đạo hàm của :math:`\log(\frac{1}{x})` là gì? 3. Đúng hay Sai: Nếu :math:`f'(x) = 0` thì :math:`f` có cực đại hoặc cực tiểu tại :math:`x`? 4. Cực tiểu của :math:`f(x) = x\log(x)` với :math:`x\ge0` ở đâu (ở đây ta giả sử rằng :math:`f` có giới hạn bằng :math:`0` tại :math:`f(0)`)? Thảo luận --------- - Tiếng Anh: `MXNet `__, `Pytorch `__, `Tensorflow `__ - Tiếng Việt: `Diễn đàn Machine Learning Cơ Bản `__ Những người thực hiện --------------------- Bản dịch trong trang này được thực hiện bởi: - Lê Khắc Hồng Phúc - Phạm Hồng Vinh - Vũ Hữu Tiệp - Nguyễn Lê Quang Nhật - Đoàn Võ Duy Thanh - Tạ H. Duy Nguyên - Mai Sơn Hải - Phạm Minh Đức - Nguyễn Văn Tâm - Nguyễn Văn Cường