Danh sách bài viết

Bài 7: Activation function: Tanh

Tanh (hyperbolic tangent): công thức, range (-1, 1), zero-centered, đạo hàm 1 - tanh^2(z), quan hệ với sigmoid tanh(z) = 2 sigma(2z) - 1. So sánh với sigmoid và ReLU. Khi nào tanh vẫn được dùng trong DL hiện đại: RNN, LSTM, GRU, GAN generator, RL action bound. Code NumPy và PyTorch.

24/05/2026
11 phút đọc
2 lượt xem
1

Mục tiêu bài học

Sau bài học, bạn sẽ:

  • Viết được công thức tanh \( \tanh(z) = \frac{e^z - e^{-z}}{e^z + e^{-z}} \) và đạo hàm \( \tanh'(z) = 1 - \tanh^2(z) \).
  • Giải thích tính chất zero-centered của tanh và lợi thế so với sigmoid trong training.
  • Chứng minh được đẳng thức \( \tanh(z) = 2 \sigma(2z) - 1 \) — tanh là phiên bản rescaled của sigmoid.
  • Biết tanh vẫn saturate và vẫn bị vanishing gradient — không phải lời giải triệt để như ReLU (B6).
  • Biết khi nào tanh vẫn xuất hiện trong DL hiện đại: RNN / LSTM / GRU (B36-38), GAN generator output, RL action bound.
  • Implement tanh bằng NumPy và dùng torch.tanh trong PyTorch.

Bài này kế tiếp Bài 6 — ReLU và biến thể và đóng phần activation cho hidden layer trước khi sang Bài 8 — Softmax cho output layer multi-class.

2

Tanh — công thức

Tanh — viết tắt của hyperbolic tangent — định nghĩa qua hàm mũ:

\[ \tanh(z) = \frac{e^z - e^{-z}}{e^z + e^{-z}} \]

Nhân tử và mẫu với \( e^z \) thu được dạng tương đương hay dùng trong implementation stable:

\[ \tanh(z) = \frac{1 - e^{-2z}}{1 + e^{-2z}} \]

Đường cong S-shape giống sigmoid nhưng đối xứng quanh gốc toạ độ:

z tanh(z) 1 -1 0 saturate tanh'(0) = 1 (max) saturate

Một vài giá trị tham chiếu (làm tròn 4 chữ số):

  • \( \tanh(-2) \approx -0.9640 \).
  • \( \tanh(-1) \approx -0.7616 \).
  • \( \tanh(0) = 0 \).
  • \( \tanh(1) \approx 0.7616 \).
  • \( \tanh(2) \approx 0.9640 \).
  • \( \tanh(5) \approx 0.9999 \) — gần như chạm asymptote 1.
3

Tính chất của tanh

  • Range: \( \tanh(z) \in (-1, 1) \) với mọi \( z \in \mathbb{R} \). Cận không bao giờ đạt nhưng tiến tới khi \( z \to \pm\infty \).
  • Tâm: \( \tanh(0) = 0 \) — đi qua gốc toạ độ.
  • Zero-centered: trung bình output gần 0 nếu input phân tán quanh 0 — khác sigmoid (output luôn dương, trung bình \( \approx 0.5 \) khi input chuẩn hoá quanh 0).
  • Smooth, monotonic, khả vi mọi nơi: thuận tiện cho gradient-based optimization.
  • Hàm lẻ: \( \tanh(-z) = -\tanh(z) \) — đối xứng qua gốc.
  • Asymptote: tiệm cận \( y = -1 \) khi \( z \to -\infty \) và \( y = 1 \) khi \( z \to +\infty \).

Trong các tính chất trên, zero-centered là điểm tạo khác biệt thực tiễn so với sigmoid — phân tích kỹ ở mục 6.

4

Quan hệ với sigmoid

Tanh và sigmoid không phải hai hàm độc lập — tanh là phiên bản rescaled (và shifted) của sigmoid:

\[ \tanh(z) = 2 \sigma(2z) - 1 \]

Chứng minh ngắn. Từ \( \sigma(2z) = \frac{1}{1 + e^{-2z}} \):

\[ 2 \sigma(2z) - 1 = \frac{2}{1 + e^{-2z}} - 1 = \frac{2 - (1 + e^{-2z})}{1 + e^{-2z}} = \frac{1 - e^{-2z}}{1 + e^{-2z}} = \tanh(z) \]

Ý nghĩa: lấy sigmoid, kéo dãn input theo hệ số 2, ánh xạ output từ \( (0, 1) \) sang \( (-1, 1) \) bằng phép biến đổi affine \( y \mapsto 2y - 1 \). Vì tanh thực chất là sigmoid rescaled, nó kế thừa cùng cấu trúc S-shape và cùng vấn đề saturation — chỉ khác range và độ dốc.

Hệ quả thực tế: trong code, có thể implement tanh bằng 2 * sigmoid(2 * z) - 1 nếu đã có sigmoid stable. Tuy nhiên framework đã có np.tanh / torch.tanh implement trực tiếp và stable hơn — không cần tự build.

5

Đạo hàm tanh

Backpropagation cần \( \tanh'(z) \). Dùng quy tắc thương với \( f = e^z - e^{-z} \), \( g = e^z + e^{-z} \):

\[ \tanh'(z) = \frac{f' g - f g'}{g^2} = \frac{(e^z + e^{-z})^2 - (e^z - e^{-z})^2}{(e^z + e^{-z})^2} \]

Triển khai tử số: \( (e^z + e^{-z})^2 - (e^z - e^{-z})^2 = 4 \) (đẳng thức quen, có thể nhận ra qua hằng đẳng thức \( (a+b)^2 - (a-b)^2 = 4ab \) với \( ab = 1 \)). Vậy:

\[ \tanh'(z) = \frac{4}{(e^z + e^{-z})^2} = 1 - \tanh^2(z) \]

Bước cuối từ \( 1 - \tanh^2(z) = 1 - \left( \frac{e^z - e^{-z}}{e^z + e^{-z}} \right)^2 = \frac{(e^z + e^{-z})^2 - (e^z - e^{-z})^2}{(e^z + e^{-z})^2} = \frac{4}{(e^z + e^{-z})^2} \).

Một số giá trị đạo hàm:

  • Tại \( z = 0 \): \( \tanh(0) = 0 \Rightarrow \tanh'(0) = 1 - 0 = 1 \) — giá trị tối đa, cao gấp 4 lần max của sigmoid (0.25).
  • Tại \( z = \pm 1 \): \( \tanh'(\pm 1) = 1 - 0.7616^2 \approx 0.4200 \).
  • Tại \( z = \pm 2 \): \( \tanh'(\pm 2) = 1 - 0.9640^2 \approx 0.0707 \).
  • Tại \( z = \pm 5 \): \( \tanh'(\pm 5) \approx 1 - 0.9999^2 \approx 0.0002 \) — đã rất nhỏ.
  • Khi \( |z| \to \infty \): \( \tanh(z) \to \pm 1 \Rightarrow \tanh'(z) \to 0 \).

Dạng \( \tanh'(z) = 1 - \tanh^2(z) \) tiện cho backprop: forward đã tính \( a = \tanh(z) \), khi backward chỉ cần \( 1 - a^2 \) — không phải gọi exp lần nữa.

6

Pros so với sigmoid

Tanh có 2 lợi thế cụ thể so với sigmoid:

1. Zero-centered → gradient không bias dấu.

Output sigmoid luôn dương, kéo theo dấu của \( \frac{\partial L}{\partial w_i} = \frac{\partial L}{\partial z} \cdot x_i \) chung cho mọi \( w_i \) của neuron (vì \( x_i > 0 \) luôn). Trajectory update phải đi zigzag — chỉ được di chuyển trong các hướng có dấu nhất quán (xem B5 mục "Not zero-centered").

Tanh output có cả dấu dương và âm, trung bình gần 0 nếu input đối xứng quanh 0 → dấu của \( x_i \) khác nhau giữa các neuron → update đi thẳng hơn theo hướng tối ưu. Tốc độ hội tụ thực tế tốt hơn sigmoid.

2. Max gradient = 1 (so với sigmoid 0.25) → vanishing nhẹ hơn.

Trong một network \( L \) layer dùng tanh, tích các \( \tanh'(z^{(\ell)}) \) bị chặn trên bởi \( 1^L = 1 \) — không tự nhân cho 1 hệ số \( \le 0.25 \) như sigmoid. Với \( L = 10 \), sigmoid cho tích \( \le 0.25^{10} \approx 10^{-7} \), tanh không có chặn cứng kiểu vậy. Network tanh học được sâu hơn network sigmoid với cùng setup.

Lưu ý: "nhẹ hơn" không nghĩa là "hết". Tanh vẫn vanishing — mục 7.

7

Cons — vẫn vanishing

Tanh không phải giải pháp triệt để cho vanishing gradient:

  • Vẫn saturate: khi \( |z| > 3 \), \( \tanh'(z) < 0.01 \). Khi \( |z| > 5 \), \( \tanh'(z) \approx 0 \) trong float32. Neuron rơi vào vùng này gần như không cập nhật weight — y hệt vấn đề của sigmoid, chỉ là ngưỡng \( |z| \) hơi rộng hơn.
  • Tốn computation hơn ReLU: tanh cần \( e^z \) và \( e^{-z} \) (hoặc \( e^{2z} \)) — 10-20 cycles trên CPU. ReLU chỉ là \( \max(0, z) \) — 1-2 cycles.
  • Backprop vẫn nhân chuỗi \( \tanh'(z) \): nếu vài layer rơi vào saturate, tích tụt nhanh về 0.

Khoảng 1990s-2000s, tanh là mặc định cho hidden layer thay cho sigmoid trong các MLP — đặc biệt sau khi LeCun và cộng sự (1998, "Efficient BackProp") khuyến nghị tanh cùng với input chuẩn hoá. Nhưng deep network \( L > 10 \) layer vẫn không train nổi với tanh — đó là động lực để chuyển sang ReLU năm 2010-2011 (Glorot, Bordes, Bengio).

8

Tanh so với ReLU

Đặt cạnh ReLU (B6):

  • ReLU \( = \max(0, z) \): với \( z > 0 \), đạo hàm bằng 1 (không saturate phía dương). Cheap (1-2 cycles). Không bị bão hoà ở vùng dương. Là mặc định cho hidden layer của CNN, MLP modern (ResNet, VGG, ViT MLP block, v.v.).
  • Tanh: saturate cả 2 phía, gradient bị chặn. Đắt hơn ReLU. Nhưng output bounded \( (-1, 1) \) — đây là tính năng chứ không phải bug trong một số kiến trúc.

Quy tắc thực tế hiện đại:

  • Hidden layer của feed-forward network (CNN, MLP, Transformer MLP block): ReLU hoặc biến thể (GELU, SiLU).
  • RNN cổ điển / LSTM / GRU: tanh vẫn là choice mặc định cho cell update — vì cần output bounded để cell state không trôi (mục 9).
  • Output layer cần range \( [-1, 1] \) (GAN generator cho ảnh đã normalize, RL action bound): tanh.
9

Khi nào dùng tanh trong DL hiện đại

Tanh không còn là default cho hidden layer của deep feed-forward network, nhưng giữ chỗ đứng cụ thể:

  • RNN vanilla, LSTM, GRU (B36-38): cell state cần bounded để không tích luỹ vô hạn theo time-step. LSTM dùng tanh ở 2 vị trí: ứng viên cell state \( \tilde{c}_t = \tanh(W_c x_t + U_c h_{t-1} + b_c) \) và output gate cuối \( h_t = o_t \odot \tanh(c_t) \). GRU tương tự cho hidden state candidate. Sigmoid dùng cho gate (giá trị \( (0, 1) \) như "mở bao nhiêu phần trăm"), tanh dùng cho giá trị state — phân công này gần như đứng yên 25 năm nay.
  • Transformer biến thể: một số positional encoding learnable, gated MLP variants vẫn dùng tanh; bản gốc Transformer (Vaswani 2017) dùng ReLU trong MLP block, các biến thể sau dùng GELU / SiLU.
  • GAN generator: output layer mapping về \( [-1, 1] \) khi ảnh input được normalize sang khoảng đó (DCGAN, Radford 2015 đề xuất chuẩn này). Discriminator vẫn dùng leaky ReLU.
  • Reinforcement Learning: action space continuous bounded (steering angle trong xe tự lái, joint angle robot) — output layer dùng tanh để bound chính xác, hoặc tanh + rescale tới khoảng action.
  • Embedding normalization: vài kiến trúc gắn tanh lên embedding để giữ norm có trần — ít phổ biến hơn, layer-norm thường được chuộng hơn.

Đặc điểm chung của các use case: tanh đặt ở một layer cụ thể hoặc một thành phần của recurrent cell, không xếp chồng hàng chục layer tanh liên tiếp — nên vanishing gradient không phải vấn đề thực tế.

10

Numerical stability

Implementation ngây thơ \( (e^z - e^{-z}) / (e^z + e^{-z}) \) tràn số khi \( |z| \) lớn vì \( e^z \) overflow tại \( z \approx 700 \) (float64) hoặc \( z \approx 88 \) (float32).

Cách viết stable: chọn nhánh theo dấu \( z \), dùng đẳng thức tương đương:

\[ \tanh(z) = \operatorname{sign}(z) \cdot \left( 1 - \frac{2}{e^{2|z|} + 1} \right) \]

Hoặc viết lại theo dạng có \( e^{-2|z|} \) (luôn \( \in (0, 1] \)):

\[ \tanh(z) = \begin{cases} \displaystyle \frac{1 - e^{-2z}}{1 + e^{-2z}} & \text{nếu } z \ge 0 \\ \displaystyle -\frac{1 - e^{2z}}{1 + e^{2z}} & \text{nếu } z < 0 \end{cases} \]

NumPy (np.tanh), PyTorch (torch.tanh), TensorFlow (tf.tanh) đều dùng implementation stable dựa trên thư viện C math (tanh trong libm). Trong code production, gọi thẳng API có sẵn — không tự build từ np.exp.

11

Bảng tóm tắt Tanh vs Sigmoid

Tiêu chí Sigmoid Tanh
Công thức \( \sigma(z) = \frac{1}{1 + e^{-z}} \) \( \tanh(z) = \frac{e^z - e^{-z}}{e^z + e^{-z}} \)
Range \( (0, 1) \) \( (-1, 1) \)
Giá trị tại \( z = 0 \) 0.5 0
Zero-centered Không
Đạo hàm \( \sigma(z)(1 - \sigma(z)) \) \( 1 - \tanh^2(z) \)
Max đạo hàm 0.25 (tại \( z = 0 \)) 1.0 (tại \( z = 0 \))
Saturate khi \( |z| \) lớn
Vanishing gradient Có (nặng) Có (nhẹ hơn)
Cost tính toán 1 lần \( \exp \) 2 lần \( \exp \) (hoặc 1 lần \( e^{2z} \))
Quan hệ \( \tanh(z) = 2\sigma(2z) - 1 \)
Use case chính Output binary classification, gate LSTM/GRU Cell state RNN/LSTM/GRU, GAN generator, RL action
12

Code Python

Implement tanh, đạo hàm, kiểm tra đẳng thức với sigmoid:

import numpy as np


def sigmoid(z):
    return np.where(
        z >= 0,
        1.0 / (1.0 + np.exp(-z)),
        np.exp(z) / (1.0 + np.exp(z)),
    )


def tanh(z):
    # np.tanh đã stable cho mọi z trong float64
    return np.tanh(z)


def tanh_derivative(z):
    t = tanh(z)
    return 1.0 - t * t


zs = np.array([-5.0, -2.0, -1.0, 0.0, 1.0, 2.0, 5.0])
for z in zs:
    t = tanh(z)
    dt = tanh_derivative(z)
    print(f"z = {z: .1f}  tanh(z) = {t: .6f}  tanh'(z) = {dt:.6f}")

Output (làm tròn):

z = -5.0  tanh(z) = -0.999909  tanh'(z) = 0.000181
z = -2.0  tanh(z) = -0.964028  tanh'(z) = 0.070651
z = -1.0  tanh(z) = -0.761594  tanh'(z) = 0.419974
z =  0.0  tanh(z) =  0.000000  tanh'(z) = 1.000000
z =  1.0  tanh(z) =  0.761594  tanh'(z) = 0.419974
z =  2.0  tanh(z) =  0.964028  tanh'(z) = 0.070651
z =  5.0  tanh(z) =  0.999909  tanh'(z) = 0.000181

Verify đẳng thức \( \tanh(z) = 2 \sigma(2z) - 1 \):

zs = np.array([-3.0, -1.0, 0.0, 1.0, 3.0])
lhs = np.tanh(zs)
rhs = 2 * sigmoid(2 * zs) - 1
print(np.allclose(lhs, rhs))   # True
print(lhs - rhs)               # gần như 0 mọi phần tử

So sánh max derivative sigmoid vs tanh:

grid = np.linspace(-10, 10, 1001)
sig_d = sigmoid(grid) * (1 - sigmoid(grid))
tanh_d = 1 - np.tanh(grid) ** 2
print(f"max sigmoid'  = {sig_d.max():.4f}")    # 0.2500
print(f"max tanh'     = {tanh_d.max():.4f}")   # 1.0000
print(f"tỷ số         = {tanh_d.max() / sig_d.max():.1f}x")

Tỷ số \( 4\times \) — max gradient của tanh cao gấp 4 lần sigmoid, khớp với phép biến đổi \( \tanh'(z) = 2 \cdot 2 \cdot \sigma'(2z) = 4 \sigma'(2z) \) đạt max tại \( z = 0 \).

Demo bound output kiểu RNN preview — không build cell hoàn chỉnh, chỉ minh hoạ tại sao tanh giữ state trong tầm kiểm soát:

# Mô phỏng cell update kiểu vanilla RNN: h_t = tanh(W * h_{t-1} + b)
# với W = 2.0, b = 0.5 — input hệ số > 1, dễ blow-up nếu không bound
np.random.seed(0)
h = 0.1
W, b = 2.0, 0.5
for t in range(20):
    h = np.tanh(W * h + b)
    print(f"step {t:2d}: h = {h: .6f}")
# Quan sát: h hội tụ về một fixed point trong (-1, 1), không thoát.

Nếu thay np.tanh(W * h + b) bằng W * h + b (không activation), \( h \) sẽ phân kỳ vô hạn theo bước. Tanh đảm bảo \( |h| < 1 \) tại mọi thời điểm — đây là lý do tanh được giữ trong RNN dù vanishing tồn tại.

13

Tanh trong PyTorch

PyTorch 2.x cung cấp 3 dạng tương đương — functional, module, tensor method:

import torch
import torch.nn as nn
import torch.nn.functional as F

z = torch.tensor([-1000.0, -2.0, -1.0, 0.0, 1.0, 2.0, 1000.0])

# Dạng functional (khuyến nghị)
print(torch.tanh(z))
# tensor([-1.0000, -0.9640, -0.7616,  0.0000,  0.7616,  0.9640,  1.0000])

# Dạng tensor method
print(z.tanh())

# Dạng module — dùng trong nn.Sequential
act = nn.Tanh()
print(act(z))

# F.tanh là alias deprecated từ PyTorch 1.x — vẫn chạy, nhưng warning.
# Khuyến nghị: dùng torch.tanh trực tiếp.

Ví dụ dùng tanh ở output layer của một GAN generator đơn giản:

class Generator(nn.Module):
    def __init__(self, z_dim=100, img_dim=784):
        super().__init__()
        self.net = nn.Sequential(
            nn.Linear(z_dim, 256),
            nn.ReLU(),
            nn.Linear(256, img_dim),
            nn.Tanh(),  # output ảnh trong [-1, 1] để khớp normalize input
        )

    def forward(self, z):
        return self.net(z)

Khi dùng cho LSTM / GRU, không cần khai báo tanh thủ công — nn.LSTM, nn.GRU đã hard-code tanh ở vị trí cell update và sigmoid ở gate (chi tiết B37-38). API người dùng chỉ cấu hình input_size, hidden_size, num_layers.

14

Tanh và softmax — chuẩn bị Bài 8

Sigmoid, tanh, ReLU đều là activation per-neuron — mỗi neuron tính output độc lập với neuron khác trong cùng layer. Tanh nhận một số thực, trả về một số thực.

Softmax (B8) khác hẳn: nhận vector logit \( \mathbf{z} \in \mathbb{R}^K \) (toàn bộ output layer cho \( K \) class), trả về vector xác suất \( \mathbf{p} \in \mathbb{R}^K \) tổng bằng 1:

\[ p_k = \frac{e^{z_k}}{\sum_{j=1}^{K} e^{z_j}} \]

Phân loại:

  • Tanh / sigmoid / ReLU: element-wise, dùng ở hidden layer hoặc output bounded độc lập.
  • Softmax: cross-neuron, dùng ở output layer khi cần phân phối xác suất K class (multi-class classification).

Bài 8 sẽ làm rõ softmax, đạo hàm Jacobian, mối quan hệ với cross-entropy loss, và lý do gộp Softmax + CrossEntropyLoss thành nn.CrossEntropyLoss trong PyTorch.

15

Bài tập

  1. Tính tay \( \tanh(z) \) cho \( z = -2, -1, 0, 1, 2 \) (dùng máy tính cầm tay cho \( e^z \)). Đối chiếu với bảng ở mục 2.
  2. Verify đẳng thức \( \tanh(z) = 2 \sigma(2z) - 1 \) tại \( z = -1, 0, 1 \) bằng cách tính riêng từng vế.
  3. Chứng minh đạo hàm \( \tanh'(z) = 1 - \tanh^2(z) \) bằng cách lấy đạo hàm trực tiếp công thức \( \tanh(z) = (e^z - e^{-z})/(e^z + e^{-z}) \).
  4. So sánh max gradient: chứng minh \( \max_z \tanh'(z) = 1 \) và \( \max_z \sigma'(z) = 0.25 \). Tỷ số \( 4 \) đến từ đâu trong phép biến đổi \( \tanh(z) = 2 \sigma(2z) - 1 \)?
  5. Chứng minh tính lẻ: \( \tanh(-z) = -\tanh(z) \) với mọi \( z \).
  6. Implement tanh_from_sigmoid(z) dùng đẳng thức \( 2 \sigma(2z) - 1 \), so sánh với np.tanh(z) tại \( z = \pm 1, \pm 5, \pm 10 \). Sai số nằm ở mức float precision nào?
  7. Vẽ tanh và sigmoid trên cùng plot với matplotlib trong khoảng \( z \in [-5, 5] \). (Mô tả code: plt.plot(z, np.tanh(z), label='tanh'); plt.plot(z, sigmoid(z), label='sigmoid'); plt.legend().) Quan sát: tanh dốc hơn quanh 0, biên độ gấp đôi.
  8. Với network 10 layer dùng tanh, weight khởi tạo sao cho mỗi \( \tanh'(z^{(\ell)}) \approx 0.5 \). Ước tính bậc độ lớn gradient ở layer đầu so với layer cuối. So sánh với cùng network dùng sigmoid và \( \sigma'(z^{(\ell)}) \approx 0.2 \) (B5 mục 8).
Đáp án ngắn
  1. \( \tanh(-2) \approx -0.9640 \), \( \tanh(-1) \approx -0.7616 \), \( \tanh(0) = 0 \), \( \tanh(1) \approx 0.7616 \), \( \tanh(2) \approx 0.9640 \).
  2. Tại \( z = 1 \): \( 2 \sigma(2) - 1 = 2 \cdot 0.8808 - 1 = 0.7616 = \tanh(1) \). Tại \( z = 0 \): \( 2 \cdot 0.5 - 1 = 0 = \tanh(0) \). Tương tự \( z = -1 \).
  3. Xem mục 5 — quy tắc thương kết hợp hằng đẳng thức \( (a+b)^2 - (a-b)^2 = 4ab \).
  4. \( \tanh(z) = 2 \sigma(2z) - 1 \Rightarrow \tanh'(z) = 4 \sigma'(2z) \). Tại \( z = 0 \): \( 4 \sigma'(0) = 4 \cdot 0.25 = 1 \). Hệ số 4 = (2 từ rescale input) × (2 từ rescale output).
  5. \( \tanh(-z) = \frac{e^{-z} - e^z}{e^{-z} + e^z} = -\frac{e^z - e^{-z}}{e^z + e^{-z}} = -\tanh(z) \).
  6. Sai số khoảng \( 10^{-15} \) đến \( 10^{-16} \) (machine epsilon float64) — về cơ bản identical.
  7. Sigmoid: max 0.25 tại \( z = 0 \), nhanh tụt về 0. Tanh: max 1.0 tại \( z = 0 \), tụt chậm hơn, đối xứng âm-dương.
  8. Tanh: \( 0.5^{10} \approx 10^{-3} \) — 3 bậc độ lớn. Sigmoid: \( 0.2^{10} \approx 10^{-7} \) — 7 bậc. Tanh vanishing nhẹ hơn 4 bậc trong cùng setup.
16

Tóm tắt

  • Tanh \( = (e^z - e^{-z}) / (e^z + e^{-z}) \), range \( (-1, 1) \), \( \tanh(0) = 0 \), hàm lẻ.
  • Quan hệ với sigmoid: \( \tanh(z) = 2 \sigma(2z) - 1 \) — tanh là sigmoid rescaled input \( \times 2 \) và output từ \( (0, 1) \) sang \( (-1, 1) \).
  • Đạo hàm: \( \tanh'(z) = 1 - \tanh^2(z) \), max \( = 1 \) tại \( z = 0 \) (gấp 4 lần max sigmoid).
  • Pros so sigmoid: zero-centered → gradient không bias dấu, hội tụ nhanh hơn; max gradient lớn hơn → vanishing nhẹ hơn.
  • Cons: vẫn saturate khi \( |z| > 3 \), vẫn vanishing trong deep network; đắt hơn ReLU.
  • So với ReLU: ReLU là mặc định cho hidden layer của CNN/MLP modern. Tanh giữ vai trò ở: RNN/LSTM/GRU cell state, GAN generator output, RL action bound.
  • Implementation stable: dùng np.tanh / torch.tanh — không tự build từ exp.
  • Tanh là activation per-neuron; bài kế (B8) chuyển sang softmax — activation cross-neuron cho phân phối xác suất.