Bỏ qua

Vi tích phân

Vi tích phân nắm bắt tốc độ thay đổi tức thời. Tài liệu này bao quát giới hạn, đạo hàm, các quy tắc vi phân, quy tắc dẫn hàm hàm hợp (nền tảng của lan truyền ngược), và các đạo hàm thường gặp được dùng xuyên suốt trong học máy.

  • Ở các chương trước, ta đã học cách biểu diễn dữ liệu dưới dạng vector và biến đổi chúng bằng ma trận. Nhưng nhiều hiện tượng trong thế giới thực không tĩnh tại. Một chiếc xe hơi tăng tốc, giá cổ phiếu dao động, hàm mất mát của một mạng nơ-ron thay đổi khi các trọng số được cập nhật. Giải tích (Calculus) là bộ môn toán học của sự thay đổi.

  • Giải tích đặt ra hai câu hỏi: sự vật đang thay đổi nhanh thế nào ngay lúc này? (vi tích phân) và nó đã tích lũy được bao nhiêu qua thời gian? (tích phân). Phần này giải quyết câu hỏi "nhanh thế nào".

  • Hãy tưởng tượng bạn đang lái xe và liếc nhìn đồng hồ tốc độ. Nó chỉ 60 km/h. Con số đó không phải là vận tốc trung bình của cả chuyến đi; đó là vận tốc của bạn tại chính thời điểm đó. Vi tích phân cung cấp cho ta các công cụ để tính toán những tốc độ thay đổi tức thời như vậy.

  • Nhưng trước hết, hãy cùng ôn lại phương trình của một đường thẳng: \(y = mx + b\).

  • Đây là mối quan hệ đơn giản nhất giữa hai đại lượng.

    • \(b\)giao điểm trục tung (y-intercept), nơi đường thẳng cắt trục y (giá trị khởi đầu khi \(x = 0\)).
    • \(m\)độ dốc (slope), tức tốc độ thay đổi: cứ mỗi đơn vị \(x\) tăng thêm, \(y\) thay đổi một lượng \(m\).
    • Nếu \(m = 3\), đường thẳng dốc lên cao; nếu \(m = 0\), đường thẳng nằm ngang; nếu \(m = -2\), đường thẳng đi xuống.
  • Độ dốc được tính bằng \(m = \frac{\Delta y}{\Delta x} = \frac{y_2 - y_1}{x_2 - x_1}\), tỉ lệ giữa "độ thay đổi của \(y\)" và "độ thay đổi của \(x\)".

Phương trình đường thẳng: b là giao điểm trục tung, m là độ dốc (độ cao trên độ dài)

  • Một khi biết \(m\)\(b\), ta có thể tính \(y\) ứng với mọi \(x\).

  • Ví dụ, nếu \(m = 2\)\(b = 3\), thì tại \(x = 5\): \(y = 2(5) + 3 = 13\).

  • Hai tham số này xác định hoàn toàn đường thẳng, và việc dự đoán bất kỳ đầu ra nào chỉ là thay số vào.

  • Với một đường thẳng, độ dốc là như nhau ở mọi nơi.

  • Ý tưởng này được khái quát hóa vượt ra ngoài đường thẳng. Bất kỳ hàm số nào cũng là một quy tắc ánh xạ đầu vào thành đầu ra, và một khi biết công thức của nó (các tham số và hình dạng), ta có thể tính đầu ra cho mọi đầu vào và vẽ đồ thị kết quả.

  • \(y = x^2\) cho một parabol, \(y = \sin(x)\) cho một sóng, \(y = e^x\) cho sự tăng trưởng hàm mũ. Mỗi công thức xác định một đường cong cụ thể, và việc thoải mái đọc một hàm số như một hình dạng là thiết yếu cho mọi thứ phía sau.

  • Với một đường thẳng, độ dốc là như nhau ở mọi nơi. Nhưng hầu hết các hàm thú vị đều cong, nên độ dốc thay đổi từ điểm này sang điểm khác. Giải tích cung cấp cho ta một cách để tìm độ dốc tại bất kỳ điểm đơn lẻ nào trên một đường cong.

  • Ta cũng cần khái niệm giới hạn (limit). Một giới hạn mô tả giá trị mà một hàm tiến tới khi đầu vào của nó càng lúc càng tiến gần một mục tiêu nào đó, mà không nhất thiết phải đạt tới nó.

\[\lim_{x \to a} f(x) = L\]
  • Câu trên đọc là: "khi \(x\) tiến tới \(a\), \(f(x)\) tiến tới \(L\)." Hàm không cần thiết phải thực sự bằng \(L\) tại \(x = a\). Nó chỉ cần tiến gần tùy ý.

  • Ví dụ, xét \(f(x) = \frac{x^2 - 1}{x - 1}\). Nếu bạn thay trực tiếp \(x = 1\), bạn được \(\frac{0}{0}\), không xác định.

  • Nhưng hãy thử các giá trị gần 1: \(f(0.9) = 1.9\), \(f(0.99) = 1.99\), \(f(1.01) = 2.01\). Các đầu ra rõ ràng đang hướng tới 2.

  • Về mặt đại số, ta có thể thấy lý do: phân tích tử số thành \((x-1)(x+1)\), rút gọn các số hạng \((x-1)\), và ta được \(f(x) = x + 1\) với mọi \(x \neq 1\). Vậy khi \(x \to 1\), \(f(x) \to 2\).

  • Hàm có một "lỗ hổng" tại \(x = 1\), nhưng giới hạn vẫn tồn tại.

  • Giới hạn là nền tảng mà mọi thứ khác trong giải tích dựa vào.

  • Đạo hàm của một hàm \(f(x)\) tại một điểm \(x = a\) đo lường tốc độ thay đổi tức thời. Về mặt hình học, nó là độ dốc của đường tiếp tuyến với đường cong tại điểm đó.

Đạo hàm là độ dốc của đường tiếp tuyến tại một điểm trên đường cong

  • Để tính độ dốc này, ta bắt đầu với hai điểm trên đường cong và tính độ dốc của đường thẳng đi qua chúng (một đường secant). Sau đó ta trượt điểm thứ hai càng lúc càng gần điểm thứ nhất, và xem đường secant tiến tới độ dốc nào. Đây là thương sai phân (difference quotient):
\[f'(a) = \lim_{h \to 0} \frac{f(a + h) - f(a)}{h}\]

Khi h thu nhỏ, đường secant tiến gần đường tiếp tuyến

  • Tử số \(f(a+h) - f(a)\) là sự thay đổi của đầu ra. Mẫu số \(h\) là sự thay đổi của đầu vào. Tỉ lệ của chúng là tốc độ thay đổi trung bình trên một khoảng rất nhỏ. Khi \(h \to 0\), trung bình này trở thành tốc độ tức thời.

  • Ví dụ, đặt \(f(x) = x^2\). Tại \(x = 3\):

\[f'(3) = \lim_{h \to 0} \frac{(3+h)^2 - 9}{h} = \lim_{h \to 0} \frac{9 + 6h + h^2 - 9}{h} = \lim_{h \to 0} (6 + h) = 6\]
  • Vậy tại \(x = 3\), hàm \(x^2\) đang tăng với tốc độ 6 đơn vị đầu ra trên mỗi đơn vị đầu vào.

  • Một hàm được gọi là khả vi (differentiable) tại một điểm nếu giới hạn này tồn tại. Để điều đó xảy ra, hàm phải liên tục (không nhảy), trơn (không có góc nhọn), và được xác định trong một lân cận quanh điểm đó.

  • Nếu bạn có thể vẽ đường cong mà không nhấc bút và không có bất kỳ đoạn gấp khúc nào, thì nó có khả năng khả vi tại đó.

  • Việc tính đạo hàm từ định nghĩa giới hạn mỗi lần sẽ rất tẻ nhạt. May mắn thay, một nắm quy tắc cho phép ta vi phân hầu hết mọi hàm một cách nhanh chóng.

  • Quy tắc hằng số: đạo hàm của một hằng số bằng không. Nếu \(f(x) = 5\), thì \(f'(x) = 0\). Một đường nằm ngang có độ dốc bằng không.

  • Quy tắc lũy thừa: công cụ chủ lực của vi phân. Hạ số mũ xuống và giảm nó đi một:

\[\frac{d}{dx} x^n = n x^{n-1}\]
  • Ví dụ: \(\frac{d}{dx} x^3 = 3x^2\). Hàm bậc ba trở thành bậc hai. Điều này đúng với mọi số mũ thực, bao gồm cả âm và phân số: \(\frac{d}{dx} x^{-1} = -x^{-2}\)\(\frac{d}{dx} \sqrt{x} = \frac{d}{dx} x^{1/2} = \frac{1}{2}x^{-1/2}\).

  • Quy tắc tổng/hiệu: vi phân từng số hạng.

\[\frac{d}{dx}[f(x) \pm g(x)] = f'(x) \pm g'(x)\]
  • Quy tắc tích: khi hai hàm được nhân với nhau, đạo hàm không đơn thuần là tích của các đạo hàm. Thay vào đó:
\[\frac{d}{dx}[f(x) \cdot g(x)] = f'(x)g(x) + f(x)g'(x)\]
  • Hãy hình dung nó như: "tốc độ thay đổi của hàm thứ nhất nhân với hàm thứ hai, cộng với hàm thứ nhất nhân với tốc độ thay đổi của hàm thứ hai." Ví dụ, \(\frac{d}{dx}[x^2 \sin x] = 2x \sin x + x^2 \cos x\).

  • Quy tắc thương: cho một tỉ số của hai hàm:

\[\frac{d}{dx}\left[\frac{f(x)}{g(x)}\right] = \frac{f'(x)g(x) - f(x)g'(x)}{[g(x)]^2}\]
  • Một mẹo ghi nhớ hữu ích: "dưới nhân d-thượng trừ trên nhân d-dưới, chia cho bình phương của cái ở dưới."

  • Quy tắc dẫn hàm hàm hợp (chain rule): quy tắc quan trọng nhất cho học máy. Khi các hàm được hợp thành (một hàm nằm trong hàm khác), đạo hàm là tích của các đạo hàm dọc theo chuỗi:

\[\frac{d}{dx} f(g(x)) = f'(g(x)) \cdot g'(x)\]
  • Hãy hình dung nó như việc lột một củ hành. Vi phân hàm ngoài (giữ nguyên hàm trong), sau đó nhân với đạo hàm của hàm trong.

Quy tắc dẫn hàm hàm hợp: vi phân hàm ngoài, nhân với đạo hàm của hàm trong

  • Ví dụ, \(\frac{d}{dx} (3x + 1)^5 = 5(3x+1)^4 \cdot 3 = 15(3x+1)^4\). Hàm ngoài là \((\cdot)^5\) và hàm trong là \(3x+1\).

  • Quy tắc dẫn hàm hàm hợp là nền tảng toán học của lan truyền ngược (backpropagation) trong mạng nơ-ron. Một mạng sâu là một chuỗi dài các hàm hợp thành. Để tính hàm mất mát thay đổi như thế nào đối với từng trọng số, ta áp dụng quy tắc dẫn hàm hàm hợp lặp lại từ tầng đầu ra ngược về đầu vào, nhân các đạo hàm cục bộ tại mỗi bước.

  • Dưới đây là những đạo hàm thường gặp nhất mà bạn sẽ bắt gặp. Mỗi cái có thể được suy ra từ định nghĩa giới hạn, nhưng việc thuộc lòng chúng sẽ tiết kiệm thời gian:

Function Derivative Notes
\(e^x\) \(e^x\) Hàm duy nhất bằng chính đạo hàm của nó
\(a^x\) \(a^x \ln a\) Khái quát hóa hàm mũ
\(\ln x\) \(\frac{1}{x}\) Logarit tự nhiên
\(\log_a x\) \(\frac{1}{x \ln a}\) Logarit tổng quát
\(\sin x\) \(\cos x\)
\(\cos x\) \(-\sin x\) Chú ý dấu âm
\(\tan x\) \(\sec^2 x\)
  • Hàm mũ \(e^x\) thật đáng chú ý: nó là hàm duy nhất bằng chính đạo hàm của nó. Đây là lý do \(e\) xuất hiện ở khắp mọi nơi trong học máy, từ hàm kích hoạt softmax đến các phân bố xác suất.

  • Quy tắc L'Hopital xử lý các giới hạn tạo ra các dạng không xác định như \(\frac{0}{0}\) hoặc \(\frac{\infty}{\infty}\). Khi thay trực tiếp cho ta một trong các dạng này, bạn có thể lấy đạo hàm của tử số và mẫu số riêng biệt rồi thử lại giới hạn:

\[\lim_{x \to a} \frac{f(x)}{g(x)} = \lim_{x \to a} \frac{f'(x)}{g'(x)}\]
  • Điều kiện: cả \(f\)\(g\) đều phải khả vi gần \(a\), và \(g'(x) \neq 0\) gần \(a\) (trừ có thể tại chính \(a\)). Giới hạn ban đầu phải cho một dạng không xác định.

  • Ví dụ: \(\lim_{x \to 0} \frac{\sin x}{x}\). Thay trực tiếp cho \(\frac{0}{0}\). Áp dụng quy tắc L'Hopital: \(\lim_{x \to 0} \frac{\cos x}{1} = 1\). Giới hạn này là nền tảng, nó xuất hiện trong xử lý tín hiệu và phân tích Fourier.

  • Bạn có thể áp dụng quy tắc lặp lại nếu kết quả vẫn không xác định. Chẳng hạn, \(\lim_{x \to 0} \frac{1 - \cos x}{x^2}\) cho \(\frac{0}{0}\). Áp dụng lần 1: \(\lim_{x \to 0} \frac{\sin x}{2x}\), vẫn \(\frac{0}{0}\). Áp dụng lần 2: \(\lim_{x \to 0} \frac{\cos x}{2} = \frac{1}{2}\).

  • Nếu hai hàm khả vi, thì tổng, hiệu, tích, hợp, và thương (với mẫu khác không) của chúng cũng khả vi. Đây là lý do ta có thể tự tin vi phân các biểu thức phức tạp được xây dựng từ những mảnh đơn giản.

Bài tập lập trình (dùng CoLab hoặc notebook)

  1. Trực quan hóa các hàm phổ biến. Vẽ \(x^2\), \(\sin(x)\), và \(e^x\) cạnh nhau để xây dựng trực giác về cách các công thức khác nhau tạo ra các hình dạng khác nhau. Thử thay đổi các tham số (vd. \(2x^2\), \(\sin(2x)\)) và quan sát cách các đường cong thay đổi.

    import jax.numpy as jnp
    import matplotlib.pyplot as plt
    
    x = jnp.linspace(-3, 3, 300)
    
    fig, axes = plt.subplots(1, 3, figsize=(12, 3))
    axes[0].plot(x, x**2, color="#e74c3c")
    axes[0].set_title("x²  (parabola)")
    axes[1].plot(x, jnp.sin(x), color="#3498db")
    axes[1].set_title("sin(x)  (wave)")
    axes[2].plot(x, jnp.exp(x), color="#27ae60")
    axes[2].set_title("eˣ  (exponential)")
    for ax in axes:
        ax.axhline(0, color="gray", linewidth=0.5)
        ax.axvline(0, color="gray", linewidth=0.5)
    plt.tight_layout()
    plt.show()
    

  2. Dùng tự động vi phân của JAX để tính đạo hàm của \(f(x) = x^3 - 2x + 1\) tại một số điểm. So sánh với đạo hàm phân tích \(f'(x) = 3x^2 - 2\).

    import jax
    import jax.numpy as jnp
    
    f = lambda x: x**3 - 2*x + 1
    df = jax.grad(f)
    
    for x in [0.0, 1.0, 2.0, -1.0]:
        print(f"x={x:5.1f}  autodiff: {df(x):.4f}  analytical: {3*x**2 - 2:.4f}")
    

  3. Kiểm tra quy tắc dẫn hàm hàm hợp bằng phương pháp số. Định nghĩa \(f(x) = \sin(x^2)\), tính đạo hàm qua jax.grad, và so sánh với kết quả phân tích \(2x\cos(x^2)\).

    import jax
    import jax.numpy as jnp
    
    f = lambda x: jnp.sin(x**2)
    df = jax.grad(f)
    
    for x in [0.5, 1.0, 2.0]:
        auto = df(x)
        analytical = 2*x * jnp.cos(x**2)
        print(f"x={x:.1f}  autodiff: {auto:.6f}  analytical: {analytical:.6f}")
    

  4. Trực quan hóa đạo hàm. Vẽ \(f(x) = x^3 - 3x\) và đạo hàm của nó \(f'(x) = 3x^2 - 3\) trên cùng một đồ thị. Chú ý nơi \(f'(x) = 0\) tương ứng với các đỉnh và đáy của \(f\).

    import jax
    import jax.numpy as jnp
    import matplotlib.pyplot as plt
    
    f = lambda x: x**3 - 3*x
    # jax.grad works on scalars; jax.vmap vectorises it to operate on an array of inputs at once
    df = jax.vmap(jax.grad(f))
    
    x = jnp.linspace(-2.5, 2.5, 200)
    plt.plot(x, jax.vmap(f)(x), label="f(x)")
    plt.plot(x, df(x), label="f'(x)", linestyle="--")
    plt.axhline(0, color="gray", linewidth=0.5)
    plt.legend()
    plt.title("A function and its derivative")
    plt.show()