.. raw:: html .. raw:: html .. raw:: html .. _sec_calculus: Giải tích ========= .. raw:: html Tìm diện tích của một đa giác vẫn là một bí ẩn cho tới ít nhất :math:`2.500` năm trước, khi người Hy Lạp cổ đại chia đa giác thành các tam giác và cộng diện tích của chúng lại. Để tìm diện tích của các hình cong, như hình tròn, người Hy Lạp cổ đại đặt các đa giác nội tiếp bên trong các hình cong đó. Như trong :numref:`fig_circle_area`, một đa giác nội tiếp với càng nhiều cạnh bằng nhau thì càng xấp xỉ đúng hình tròn. Quy trình này còn được biết đến như *phương pháp vét kiệt*. .. raw:: html .. _fig_circle_area: .. figure:: ../img/polygon_circle.svg Tìm diện tích hình tròn bằng phương pháp vét kiệt. .. raw:: html Phương pháp vét kiệt chính là khởi nguồn của *giải tích tích phân* (sẽ được miêu tả trong :numref:`sec_integral_calculus`). Hơn :math:`2.000` năm sau, nhánh còn lại của giải tích, *giải tích vi phân*, ra đời. Trong những ứng dụng quan trọng nhất của giải tích vi phân, các bài toán tối ưu hoá sẽ tìm *cách tốt nhất* để thực hiện một công việc nào đó. Như đã bàn đến trong :numref:`subsec_norms_and_objectives`, các bài toán như vậy vô cùng phổ biến trong học sâu. .. raw:: html Trong học sâu, chúng ta *huấn luyện* các mô hình, cập nhật chúng liên tục để chúng ngày càng tốt hơn khi học với nhiều dữ liệu hơn. Thông thường, trở nên tốt hơn tương đương với cực tiểu hoá một *hàm mất mát*, một điểm số sẽ trả lời câu hỏi “mô hình của ta đang *tệ* tới mức nào?” Câu hỏi này lắt léo hơn ta tưởng nhiều. Mục đích cuối cùng mà ta muốn là mô hình sẽ hoạt động tốt trên dữ liệu mà nó chưa từng nhìn thấy. Nhưng chúng ta chỉ có thể khớp mô hình trên dữ liệu mà ta đang có thể thấy. Do đó ta có thể chia việc huấn luyện mô hình thành hai vấn đề chính: i) *tối ưu hoá*: quy trình huấn luyện mô hình trên dữ liệu đã thấy. ii) *tổng quát hoá*: dựa trên các nguyên tắc toán học và sự uyên thâm của người huấn luyện để tạo ra các mô hình mà tính hiệu quả của nó vượt ra khỏi tập dữ liệu huấn luyện. .. raw:: html Để giúp bạn hiểu các bài toán tối ưu hóa và các phương pháp tối ưu hóa trong các chương sau, ở đây chúng tôi sẽ cung cấp một chương ngắn vỡ lòng về các kĩ thuật giải tích vi phân thông dụng trong học sâu. .. raw:: html .. raw:: html .. raw:: html .. raw:: html .. raw:: html Đạo hàm và Vi phân ------------------ .. raw:: html Chúng ta bắt đầu bằng việc đề cập tới khái niệm đạo hàm, một bước quan trọng của hầu hết các thuật toán tối ưu trong học sâu. Trong học sâu, ta thường chọn những hàm mất mát khả vi theo các tham số của mô hình. Nói đơn giản, với mỗi tham số, ta có thể xác định hàm mất mát tăng hoặc giảm nhanh như thế nào khi tham số đó *tăng* hoặc *giảm* chỉ một lượng cực nhỏ. .. raw:: html Giả sử ta có một hàm :math:`f: \mathbb{R} \rightarrow \mathbb{R}` có đầu vào và đầu ra đều là số vô hướng. *Đạo hàm* của :math:`f` được định nghĩa là .. math:: f'(x) = \lim_{h \rightarrow 0} \frac{f(x+h) - f(x)}{h}, :label: eq_derivative .. raw:: html nếu giới hạn này tồn tại. Nếu :math:`f'(a)` tồn tại, :math:`f` được gọi là *khả vi* (*differentiable*) tại :math:`a`. Nếu :math:`f` khả vi tại mọi điểm trong một khoảng, thì hàm này được gọi là khả vi trong khoảng đó. Ta có thể giải nghĩa đạo hàm :math:`f'(x)` trong :eq:`eq_derivative` như là tốc độ thay đổi *tức thời* của hàm :math:`f` theo biến :math:`x`. Cái gọi là tốc độ thay đổi tức thời được dựa trên độ biến thiên :math:`h` trong :math:`x` khi :math:`h` tiến về :math:`0`. .. raw:: html Để minh họa cho khái niệm đạo hàm, hãy thử với một ví dụ. Định nghĩa :math:`u = f(x) = 3x^2-4x`. .. code:: python %matplotlib inline from d2l import mxnet as d2l from IPython import display from mxnet import np, npx npx.set_np() def f(x): return 3 * x ** 2 - 4 * x .. raw:: html Cho :math:`x=1` và :math:`h` tiến về :math:`0`, kết quả của phương trình :math:`\frac{f(x+h) - f(x)}{h}` trong :eqref:``eq_derivative`` tiến về :math:`2`. Dù thử nghiệm này không phải là một dạng chứng minh toán học, lát nữa ta sẽ thấy rằng quả thật đạo hàm của :math:`u'` là :math:`2` khi :math:`x=1`. .. code:: python def numerical_lim(f, x, h): return (f(x + h) - f(x)) / h h = 0.1 for i in range(5): print('h=%.5f, numerical limit=%.5f' % (h, numerical_lim(f, 1, h))) h *= 0.1 .. parsed-literal:: :class: output h=0.10000, numerical limit=2.30000 h=0.01000, numerical limit=2.03000 h=0.00100, numerical limit=2.00300 h=0.00010, numerical limit=2.00030 h=0.00001, numerical limit=2.00003 .. raw:: html .. raw:: html .. raw:: html Hãy làm quen với một vài ký hiệu cùng được dùng để biểu diễn đạo hàm. Cho :math:`y = f(x)` với :math:`x` và :math:`y` lần lượt là biến độc lập và biến phụ thuộc của hàm :math:`f`. Những biểu diễn sau đây là tương đương nhau: .. math:: f'(x) = y' = \frac{dy}{dx} = \frac{df}{dx} = \frac{d}{dx} f(x) = Df(x) = D_x f(x), .. raw:: html với ký hiệu :math:`\frac{d}{dx}` và :math:`D` là các *toán tử vi phân* (*differentiation operator*) để chỉ các phép toán *vi phân*. Ta có thể sử dụng các quy tắc lấy đạo hàm của các hàm thông dụng sau đây: .. raw:: html - :math:`DC = 0` (:math:`C` là một hằng số), - :math:`Dx^n = nx^{n-1}` (*quy tắc lũy thừa*, :math:`n` là số thực bất kỳ), - :math:`De^x = e^x`, - :math:`D\ln(x) = 1/x.` .. raw:: html Để lấy đạo hàm của một hàm được tạo từ vài hàm đơn giản hơn, ví dụ như từ những hàm thông dụng ở trên, có thể dùng các quy tắc hữu dụng dưới đây. Giả sử hàm :math:`f` và :math:`g` đều khả vi và :math:`C` là một hằng số, ta có *quy tắc nhân hằng số* .. math:: \frac{d}{dx} [Cf(x)] = C \frac{d}{dx} f(x), .. raw:: html *quy tắc tổng* .. math:: \frac{d}{dx} [f(x) + g(x)] = \frac{d}{dx} f(x) + \frac{d}{dx} g(x), .. raw:: html *quy tắc nhân* .. math:: \frac{d}{dx} [f(x)g(x)] = f(x) \frac{d}{dx} [g(x)] + g(x) \frac{d}{dx} [f(x)], .. raw:: html và *quy tắc đạo hàm phân thức* .. math:: \frac{d}{dx} \left[\frac{f(x)}{g(x)}\right] = \frac{g(x) \frac{d}{dx} [f(x)] - f(x) \frac{d}{dx} [g(x)]}{[g(x)]^2}. .. raw:: html Bây giờ ta có thể áp dụng các quy tắc ở trên để tìm đạo hàm :math:`u' = f'(x) = 3 \frac{d}{dx} x^2-4\frac{d}{dx}x = 6x-4`. Vậy nên, với :math:`x = 1`, ta có :math:`u' = 2`: điều này đã được kiểm chứng với thử nghiệm lúc trước ở mục này khi kết quả có được cũng tiến tới :math:`2`. Giá trị đạo hàm này cũng đồng thời là độ dốc của đường tiếp tuyến với đường cong :math:`u = f(x)` tại :math:`x = 1`. .. raw:: html .. raw:: html .. raw:: html .. raw:: html .. raw:: html Để minh họa cách hiểu này của đạo hàm, ta sẽ dùng ``matplotlib``, một thư viện vẽ biểu đồ thông dụng trong Python. Ta cần định nghĩa một số hàm để cấu hình thuộc tính của các biểu đồ được tạo ra bởi ``matplotlib``. Trong đoạn mã sau, hàm ``use_svg_display`` chỉ định ``matplotlib`` tạo các biểu đồ ở dạng svg để có được chất lượng ảnh sắc nét hơn. .. code:: python # Saved in the d2l package for later use def use_svg_display(): """Use the svg format to display a plot in Jupyter.""" display.set_matplotlib_formats('svg') .. raw:: html Ta định nghĩa hàm ``set_figsize`` để chỉ định kích thước của biểu đồ. Lưu ý rằng ở đây ta đang dùng trực tiếp ``d2l.plt`` do câu lệnh ``from matplotlib import pyplot as plt`` đã được đánh dấu để lưu vào gói ``d2l`` trong phần Lời nói đầu. .. code:: python # Saved in the d2l package for later use def set_figsize(figsize=(3.5, 2.5)): """Set the figure size for matplotlib.""" use_svg_display() d2l.plt.rcParams['figure.figsize'] = figsize .. raw:: html Hàm ``set_axes`` sau cấu hình thuộc tính của các trục biểu đồ tạo bởi ``matplotlib``. .. code:: python # Saved in the d2l package for later use def set_axes(axes, xlabel, ylabel, xlim, ylim, xscale, yscale, legend): """Set the axes for matplotlib.""" axes.set_xlabel(xlabel) axes.set_ylabel(ylabel) axes.set_xscale(xscale) axes.set_yscale(yscale) axes.set_xlim(xlim) axes.set_ylim(ylim) if legend: axes.legend(legend) axes.grid() .. raw:: html Với ba hàm cấu hình biểu đồ trên, ta định nghĩa hàm ``plot`` để vẽ nhiều đồ thị một cách nhanh chóng vì ta sẽ cần minh họa khá nhiều đồ thị xuyên suốt cuốn sách. .. code:: python # Saved in the d2l package for later use def plot(X, Y=None, xlabel=None, ylabel=None, legend=[], xlim=None, ylim=None, xscale='linear', yscale='linear', fmts=['-', 'm--', 'g-.', 'r:'], figsize=(3.5, 2.5), axes=None): """Plot data points.""" d2l.set_figsize(figsize) axes = axes if axes else d2l.plt.gca() # Return True if X (ndarray or list) has 1 axis def has_one_axis(X): return (hasattr(X, "ndim") and X.ndim == 1 or isinstance(X, list) and not hasattr(X[0], "__len__")) if has_one_axis(X): X = [X] if Y is None: X, Y = [[]] * len(X), X elif has_one_axis(Y): Y = [Y] if len(X) != len(Y): X = X * len(Y) axes.cla() for x, y, fmt in zip(X, Y, fmts): if len(x): axes.plot(x, y, fmt) else: axes.plot(y, fmt) set_axes(axes, xlabel, ylabel, xlim, ylim, xscale, yscale, legend) .. raw:: html Giờ ta có thể vẽ đồ thị của hàm số :math:`u = f(x)` và đường tiếp tuyến của nó :math:`y = 2x - 3` tại :math:`x=1`, với hệ số :math:`2` là độ dốc của tiếp tuyến. .. code:: python x = np.arange(0, 3, 0.1) plot(x, [f(x), 2 * x - 3], 'x', 'f(x)', legend=['f(x)', 'Tangent line (x=1)']) .. figure:: output_calculus_vn_509ba6_13_0.svg .. raw:: html .. raw:: html .. raw:: html Đạo hàm riêng ------------- .. raw:: html Cho tới giờ, ta đã làm việc với đạo hàm của các hàm một biến. Trong học sâu, các hàm lại thường phụ thuộc vào *nhiều* biến. Do đó, ta cần mở rộng ý tưởng của đạo hàm cho các hàm *nhiều biến* đó. .. raw:: html Cho :math:`y = f(x_1, x_2, \ldots, x_n)` là một hàm với :math:`n` biến. *Đạo hàm riêng* của :math:`y` theo tham số thứ :math:`i`, :math:`x_i`, là .. math:: \frac{\partial y}{\partial x_i} = \lim_{h \rightarrow 0} \frac{f(x_1, \ldots, x_{i-1}, x_i+h, x_{i+1}, \ldots, x_n) - f(x_1, \ldots, x_i, \ldots, x_n)}{h}. .. raw:: html Để tính :math:`\frac{\partial y}{\partial x_i}`, ta chỉ cần coi :math:`x_1, \ldots, x_{i-1}, x_{i+1}, \ldots, x_n` là các hằng số và tính đạo hàm của :math:`y` theo :math:`x_i`. Để biểu diễn đạo hàm riêng, các ký hiệu sau đây đều có ý nghĩa tương đương: .. math:: \frac{\partial y}{\partial x_i} = \frac{\partial f}{\partial x_i} = f_{x_i} = f_i = D_i f = D_{x_i} f. .. raw:: html .. raw:: html .. raw:: html Gradient -------- .. raw:: html Chúng ta có thể ghép các đạo hàm riêng của mọi biến trong một hàm nhiều biến để thu được vector *gradient* của hàm số đó. Giả sử rằng đầu vào của hàm :math:`f: \mathbb{R}^n \rightarrow \mathbb{R}` là một vector :math:`n` chiều :math:`\mathbf{x} = [x_1, x_2, \ldots, x_n]^\top` và đầu ra là một số vô hướng. Gradient của hàm :math:`f(\mathbf{x})` theo :math:`\mathbf{x}` là một vector gồm :math:`n` đạo hàm riêng đó: .. math:: \nabla_{\mathbf{x}} f(\mathbf{x}) = \bigg[\frac{\partial f(\mathbf{x})}{\partial x_1}, \frac{\partial f(\mathbf{x})}{\partial x_2}, \ldots, \frac{\partial f(\mathbf{x})}{\partial x_n}\bigg]^\top. .. raw:: html Biểu thức :math:`\nabla_{\mathbf{x}} f(\mathbf{x})` thường được viết gọn thành :math:`\nabla f(\mathbf{x})` trong trường hợp không sợ nhầm lẫn. .. raw:: html Cho :math:`\mathbf{x}` là một vector :math:`n`-chiều, các quy tắc sau thường được dùng khi tính vi phân hàm đa biến: .. raw:: html - Với mọi :math:`\mathbf{A} \in \mathbb{R}^{m \times n}`, :math:`\nabla_{\mathbf{x}} \mathbf{A} \mathbf{x} = \mathbf{A}^\top`, - Với mọi :math:`\mathbf{A} \in \mathbb{R}^{n \times m}`, :math:`\nabla_{\mathbf{x}} \mathbf{x}^\top \mathbf{A} = \mathbf{A}`, - Với mọi :math:`\mathbf{A} \in \mathbb{R}^{n \times n}`, :math:`\nabla_{\mathbf{x}} \mathbf{x}^\top \mathbf{A} \mathbf{x} = (\mathbf{A} + \mathbf{A}^\top)\mathbf{x}`, - :math:`\nabla_{\mathbf{x}} \|\mathbf{x} \|^2 = \nabla_{\mathbf{x}} \mathbf{x}^\top \mathbf{x} = 2\mathbf{x}`. .. raw:: html Tương tự, với bất kỳ ma trận :math:`\mathbf{X}` nào, ta đều có :math:`\nabla_{\mathbf{X}} \|\mathbf{X} \|_F^2 = 2\mathbf{X}`. Sau này ta sẽ thấy, gradient sẽ rất hữu ích khi thiết kế thuật toán tối ưu trong học sâu. .. raw:: html .. raw:: html .. raw:: html .. raw:: html Quy tắc dây chuyền ------------------ .. raw:: html Tuy nhiên, những gradient như thế có thể khó để tính toán. Đó là bởi vì các hàm nhiều biến trong học sâu đa phần là những *hàm hợp*, nên ta không thể áp dụng các quy tắc đề cập ở trên để lấy vi phân cho những hàm này. May mắn thay, *quy tắc dây chuyền* cho phép chúng ta lấy vi phân của các hàm hợp. .. raw:: html Trước tiên, chúng ta hãy xem xét các hàm một biến. Giả sử hai hàm :math:`y=f(u)` và :math:`u=g(x)` đều khả vi, quy tắc dây chuyền được mô tả như sau .. math:: \frac{dy}{dx} = \frac{dy}{du} \frac{du}{dx}. .. raw:: html Giờ ta sẽ xét trường hợp tổng quát hơn đối với các hàm nhiều biến. Giả sử một hàm khả vi :math:`y` có các biến số :math:`u_1, u_2, \ldots, u_m`, trong đó mỗi biến :math:`u_i` là một hàm khả vi của các biến :math:`x_1, x_2, \ldots, x_n`. Lưu ý rằng :math:`y` cũng là hàm của các biến :math:`x_1, x_2, \ldots, x_n`. Quy tắc dây chuyền cho ta .. math:: \frac{dy}{dx_i} = \frac{dy}{du_1} \frac{du_1}{dx_i} + \frac{dy}{du_2} \frac{du_2}{dx_i} + \cdots + \frac{dy}{du_m} \frac{du_m}{dx_i} .. raw:: html cho mỗi :math:`i = 1, 2, \ldots, n`. .. raw:: html Tóm tắt ------- .. raw:: html - Vi phân và tích phân là hai nhánh con của giải tích, trong đó vi phân được ứng dụng rộng rãi trong các bài toán tối ưu hóa của học sâu. - Đạo hàm có thể được hiểu như là tốc độ thay đổi tức thì của một hàm số đối với các biến số. Nó cũng là độ dốc của đường tiếp tuyến với đường cong của hàm. - Gradient là một vector có các phần tử là đạo hàm riêng của một hàm nhiều biến theo tất cả các biến số của nó. - Quy tắc dây chuyền cho phép chúng ta lấy vi phân của các hàm hợp. .. raw:: html Bài tập ------- .. raw:: html *dịch đoạn phía trên* 1. Vẽ đồ thị của hàm số :math:`y = f(x) = x^3 - \frac{1}{x}` và đường tiếp tuyến của nó tại :math:`x = 1`. 1. Tìm gradient của hàm số :math:`f(\mathbf{x}) = 3x_1^2 + 5e^{x_2}`. 1. Gradient của hàm :math:`f(\mathbf{x}) = \|\mathbf{x}\|_2` là gì? 1. Có thể dùng quy tắc dây chuyền cho trường hợp sau đây không: :math:`u = f(x, y, z)`, với :math:`x = x(a, b)`, :math:`y = y(a, b)` và :math:`z = z(a, b)`? .. raw:: html .. raw:: html .. raw:: html Thảo luận --------- - `Tiếng Anh `__ - `Tiếng Việt `__ 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 - Vũ Hữu Tiệp - Nguyễn Cảnh Thướng - Phạm Minh Đức - Tạ H. Duy Nguyên