Danh sách bài viết

Bài 6: Activation function: ReLU và biến thể (LeakyReLU)

ReLU \( \max(0, z) \) là activation mặc định cho hidden layer của deep network từ 2012. Bài này phân tích công thức, đạo hàm, ưu điểm tránh vanishing gradient, vấn đề dying ReLU, các biến thể Leaky ReLU / PReLU / ELU / GELU / SwiGLU, He initialization, và PyTorch API.

24/05/2026
13 phút đọc
0 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 ReLU \( \text{ReLU}(z) = \max(0, z) \) và đạo hàm của nó.
  • Giải thích vì sao ReLU tránh được vanishing gradient mà sigmoid (Bài 5) mắc phải.
  • Mô tả được hiện tượng dying ReLU: nguyên nhân, dấu hiệu, cách phát hiện.
  • Biết các biến thể Leaky ReLU, PReLU, ELU, GELU, SiLU / Swish, SwiGLU và khi nào nên dùng.
  • Chọn được activation phù hợp cho hidden layer trong các bối cảnh: MLP/CNN cổ điển, Transformer / LLM.
  • Dùng đúng API trong PyTorch 2.x: nn.ReLU, nn.LeakyReLU, nn.GELU...

Bài này tiếp nối Bài 5 — Sigmoid và vanishing gradient: ta đã thấy vì sao sigmoid không phù hợp cho hidden layer; bài này giải thích lựa chọn thay thế là gì.

2

ReLU — công thức

ReLU (Rectified Linear Unit) định nghĩa:

\[ \text{ReLU}(z) = \max(0, z) = \begin{cases} z & \text{nếu } z > 0 \\ 0 & \text{nếu } z \le 0 \end{cases} \]

Đồ thị: hai đoạn thẳng nối tại gốc toạ độ — trục \( z \) trên nửa trục âm, đường \( y = z \) trên nửa trục dương.

z ReLU(z) vùng "chết" grad = 0 grad = 1 không vanish 0

Vài giá trị tham chiếu:

  • \( \text{ReLU}(-3) = 0 \).
  • \( \text{ReLU}(-0.1) = 0 \).
  • \( \text{ReLU}(0) = 0 \).
  • \( \text{ReLU}(2) = 2 \).
  • \( \text{ReLU}(100) = 100 \).

Đáng chú ý: ReLU không bị chặn trên — output có thể lớn tuỳ ý. Đây là khác biệt cấu trúc lớn so với sigmoid \( \in (0, 1) \) hay tanh \( \in (-1, 1) \).

3

Đạo hàm ReLU

Đạo hàm trên từng nhánh tính trực tiếp:

\[ \text{ReLU}'(z) = \begin{cases} 1 & \text{nếu } z > 0 \\ 0 & \text{nếu } z < 0 \end{cases} \]

Tại \( z = 0 \), hàm không khả vi theo nghĩa cổ điển — đạo hàm trái bằng 0, đạo hàm phải bằng 1. Trong thực tế, framework chọn một giá trị quy ước thuộc sub-gradient \( [0, 1] \); PyTorch và TensorFlow dùng \( \text{ReLU}'(0) = 0 \). Lựa chọn này không ảnh hưởng training vì \( z \) chính xác bằng 0 hầu như không xảy ra trong float32.

So sánh nhanh với sigmoid:

  • Sigmoid: \( \sigma'(z) \in (0, 0.25] \), luôn dương nhưng nhỏ. Mỗi layer nhân vào một thừa số \( \le 0.25 \) → vanishing gradient.
  • ReLU: \( \text{ReLU}'(z) \in \{0, 1\} \). Khi \( z > 0 \), gradient đi qua nguyên vẹn — không co lại.
4

Vì sao ReLU thay sigmoid ở hidden layer

Bài 5 cho thấy gradient layer đầu của network sigmoid \( L \) layer bị chặn trên bởi \( 0.25^L \), nhanh chóng tiến về 0. ReLU sửa điểm này:

  • Không vanish khi active: với neuron có \( z > 0 \), \( \text{ReLU}'(z) = 1 \). Tích nhiều thừa số dạng \( 1 \) không co lại — gradient lan ngược tới layer đầu vẫn còn nguyên độ lớn (chỉ bị scale bởi các ma trận \( W^{(\ell)} \) chứ không bị tắt bởi activation).
  • Computational đơn giản: chỉ cần so sánh với 0 — 1-2 cycles CPU, không cần \( \exp \) (10-20 cycles). Trên GPU, kernel ReLU đơn giản và memory-bound.
  • Không có saturation phía dương: \( z \) lớn vẫn cho gradient = 1. Không có vùng "kẹt" như đuôi sigmoid.

Hệ quả thực tế: deep network sigmoid 5-10 layer training rất chậm (đã rõ trước 2010). Cùng kiến trúc thay activation thành ReLU thì training hội tụ nhanh hơn nhiều lần và xử lý được mạng sâu hàng chục layer. Glorot, Bordes, Bengio (2011) báo cáo ReLU vượt sigmoid / tanh trên cả benchmark unsupervised pre-training lẫn supervised cho deep network.

5

Sparse activation

Vì \( \text{ReLU}(z) = 0 \) với mọi \( z \le 0 \), một phần neuron của layer "tắt" (output bằng 0) trong từng forward pass. Với weight và input có distribution gần đối xứng quanh 0, kỳ vọng khoảng một nửa số neuron tắt mỗi lượt forward.

Điều này tạo ra sparse activation: vector kích hoạt ở mỗi layer có nhiều entry chính xác bằng 0. Một số hệ quả:

  • Biểu diễn thưa: mỗi sample chỉ kích hoạt một tập con neuron — gần với mô hình "neuron sinh học chỉ bắn khi đủ stimulus" (Glorot et al. 2011).
  • Regularization ngầm: ít neuron tham gia mỗi forward pass → giảm capacity hiệu dụng, đỡ overfitting (nhưng không thay thế được dropout / weight decay).
  • Compute thưa hơn (về lý thuyết): các zero có thể bỏ qua trong tính toán downstream — trong thực tế GPU vẫn dense compute, nhưng hardware specialized (sparse cores) khai thác được điểm này.

So sánh: sigmoid hầu như không bao giờ cho output chính xác bằng 0; mọi neuron đều "bật một phần", biểu diễn dense.

6

Lịch sử ngắn

  • 2000: Hahnloser và cộng sự đề xuất hàm ramp \( \max(0, z) \) trong bối cảnh mạng neural sinh học.
  • 2010: Nair và Hinton dùng ReLU cho Restricted Boltzmann Machines, gọi là "rectified linear units".
  • 2011: Glorot, Bordes, Bengio công bố Deep Sparse Rectifier Neural Networks — ReLU train được deep network (5+ layer) supervised mà không cần pre-training, vượt sigmoid / tanh.
  • 2012: AlexNet (Krizhevsky, Sutskever, Hinton) thắng ImageNet với CNN dùng ReLU. ReLU thành mặc định cho hidden layer của CNN.
  • 2013: Maas, Hannun, Ng giới thiệu Leaky ReLU để xử lý dying ReLU trong acoustic model.
  • 2015: He, Zhang, Ren, Sun đề xuất PReLU và He initialization, đạt độ chính xác siêu phàm trên ImageNet (PReLU-net).
  • 2015-2016: ELU (Clevert et al.), SELU (Klambauer et al.) — biến thể smooth.
  • 2016-2018: GELU (Hendrycks & Gimpel 2016), Swish / SiLU (Ramachandran et al. 2017) xuất hiện và được Transformer adopt.
  • 2020 → nay: GELU mặc định trong BERT, GPT-2, GPT-3. SwiGLU (Shazeer 2020) dùng trong PaLM, LLaMA, Mistral cho FFN block.
7

Dying ReLU

Nhược điểm cố hữu của ReLU: một khi neuron rơi vào trạng thái \( z \le 0 \) cho mọi sample trong tập huấn luyện, nó "chết" vĩnh viễn.

Chuỗi nguyên nhân:

  1. Pre-activation \( z = W \mathbf{x} + b \le 0 \) cho mọi sample.
  2. Output \( \text{ReLU}(z) = 0 \) → đóng góp 0 vào loss.
  3. Đạo hàm \( \text{ReLU}'(z) = 0 \) → gradient theo \( W, b \) của neuron đó bằng 0.
  4. Weight không update. Lần forward kế tiếp, vẫn \( z \le 0 \). Vòng lặp khép kín.

Nguyên nhân thường gặp:

  • Learning rate quá lớn: một bước update đưa weight về vùng làm \( z \le 0 \) cho tất cả sample. Trường hợp kinh điển nhất.
  • Weight init xấu: bias âm lớn ban đầu, hoặc weight scale sai cho ReLU (xem He init ở mục 15).
  • Input lệch âm hoặc chưa normalize: nếu \( \mathbf{x} \) trung bình lớn về phía dương kết hợp với weight âm, \( z \) lệch âm.
  • Bias drift trong gradient flow: gradient lớn liên tục đẩy bias xuống.

Phép đo thực tế: trong một network ReLU đã train, đếm tỉ lệ neuron có \( \text{activation} = 0 \) trên toàn bộ tập validation. Tỉ lệ này thường nằm trong khoảng 30-70% — phần lớn là "dead" theo định nghĩa hẹp (chết hoàn toàn, không bao giờ active). Nếu phần lớn neuron của một layer dead → layer đó mất capacity, model underfit.

Cách giảm dying ReLU:

  • Giảm learning rate hoặc dùng warmup.
  • Dùng He initialization (mục 15).
  • Normalize input (batch norm B23, layer norm).
  • Thử biến thể có nhánh âm khác 0: Leaky ReLU, PReLU, ELU, GELU.
8

Leaky ReLU

Ý tưởng: thay vì cho nhánh âm bằng 0, cho nó một độ dốc nhỏ \( \alpha \) để gradient không bao giờ đúng bằng 0.

\[ \text{LeakyReLU}(z) = \begin{cases} z & \text{nếu } z > 0 \\ \alpha z & \text{nếu } z \le 0 \end{cases} \]

Đạo hàm:

\[ \text{LeakyReLU}'(z) = \begin{cases} 1 & \text{nếu } z > 0 \\ \alpha & \text{nếu } z < 0 \end{cases} \]

Mặc định \( \alpha = 0.01 \) (Maas, Hannun, Ng 2013). Một số implementation dùng \( \alpha = 0.2 \) cho GAN (Radford et al., DCGAN 2015).

Nhánh âm có gradient \( \alpha \ne 0 \) → weight vẫn được update khi \( z < 0 \) → neuron không "chết" vĩnh viễn. Đánh đổi: layer không còn sparse activation thật sự (không có entry bằng 0 chính xác), bù lại tránh được dying ReLU.

Thực tế: Leaky ReLU thường ngang ngửa ReLU trên image task — sự khác biệt nhỏ, đôi khi ReLU vẫn tốt hơn nhờ sparse. Dùng Leaky khi đo được dying ReLU rõ rệt trên model hiện tại.

9

PReLU — Parametric ReLU

Cùng dạng Leaky ReLU nhưng \( \alpha \) là tham số học được bằng backpropagation thay vì hyperparameter cố định:

\[ \text{PReLU}(z) = \begin{cases} z & \text{nếu } z > 0 \\ \alpha z & \text{nếu } z \le 0 \end{cases}, \quad \alpha \text{ học được} \]

Hai biến thể cấu hình:

  • Channel-wise: mỗi channel (CNN) hoặc mỗi feature có \( \alpha \) riêng — thêm vài trăm tham số, không đáng kể.
  • Shared: toàn layer dùng chung 1 \( \alpha \).

He et al. (2015, "Delving Deep into Rectifiers") báo cáo PReLU + He init đẩy độ chính xác top-5 ImageNet vượt mức người (4.94%). PReLU phổ biến trong các kiến trúc CNN tinh chỉnh đầu 2015-2016, sau đó ít được dùng trong các kiến trúc tổng quát vì cải thiện nhỏ và tăng độ phức tạp.

10

ELU — Exponential Linear Unit

Clevert, Unterthiner, Hochreiter (2015) đề xuất nhánh âm smooth dạng mũ:

\[ \text{ELU}(z) = \begin{cases} z & \text{nếu } z > 0 \\ \alpha (e^z - 1) & \text{nếu } z \le 0 \end{cases} \]

Mặc định \( \alpha = 1 \). Tính chất:

  • Smooth tại 0: đạo hàm trái và phải đều bằng \( \alpha \) (= 1 với mặc định). Hàm khả vi mọi nơi.
  • Bounded dưới: nhánh âm bị chặn dưới bởi \( -\alpha \).
  • Mean activation gần 0: làm distribution của activation cân bằng hơn so với ReLU (vốn không bao giờ âm), giảm bias shift giữa các layer.

Đánh đổi: cần tính \( e^z \) — đắt hơn ReLU. SELU (Klambauer et al. 2017) là ELU đã được scale với hệ số đặc biệt để self-normalize, nhưng yêu cầu kết hợp với init đặc thù (LeCun normal) và không dùng với BatchNorm — ít linh hoạt trong thực tế.

11

GELU — Gaussian Error Linear Unit

Hendrycks và Gimpel (2016) định nghĩa:

\[ \text{GELU}(z) = z \cdot \Phi(z) \]

với \( \Phi(z) = \frac{1}{2}\big[ 1 + \text{erf}(z / \sqrt{2}) \big] \) là CDF của phân phối Gaussian chuẩn.

Approximation thường dùng (rẻ hơn tính erf):

\[ \text{GELU}(z) \approx 0.5 z \big( 1 + \tanh\big[ \sqrt{2/\pi} (z + 0.044715 z^3) \big] \big) \]

Tính chất:

  • Smooth mọi nơi, không có điểm gãy như ReLU.
  • Non-monotonic: có một dip nhỏ phía âm — \( \text{GELU}(z) < 0 \) cho \( z \) hơi âm rồi tiến về 0 khi \( z \to -\infty \).
  • Tránh dying gradient: nhánh âm có gradient khác 0 (rất nhỏ với \( z \) âm lớn, nhưng không bằng 0).
  • Diễn giải xác suất: có thể xem là dropout dạng deterministic với "xác suất giữ" phụ thuộc \( z \).

GELU là activation mặc định trong các kiến trúc Transformer hiện đại: BERT (Devlin et al. 2018), GPT-2, GPT-3 (Brown et al. 2020), ViT (Dosovitskiy et al. 2020).

12

SiLU / Swish, Mish, SwiGLU

SiLU (Sigmoid Linear Unit) còn gọi là Swish:

\[ \text{SiLU}(z) = z \cdot \sigma(z) = \frac{z}{1 + e^{-z}} \]

Đề xuất độc lập trong Hendrycks & Gimpel (2016, swish-1) và Ramachandran, Zoph, Le (2017, neural architecture search). Tính chất gần GELU — smooth, non-monotonic, có dip nhỏ phía âm. Dùng trong EfficientNet (Tan & Le 2019) và một số CNN hiện đại.

Mish (Misra 2019):

\[ \text{Mish}(z) = z \cdot \tanh(\ln(1 + e^z)) \]

Smooth, không bị chặn trên, có dip phía âm như SiLU / GELU. Đôi khi cho điểm số nhỉnh hơn ReLU trên benchmark CNN, nhưng compute đắt; ít dùng trong production.

SwiGLU (Shazeer 2020) thực ra là một biến thể cấu trúc FFN dùng SiLU/Swish làm gate, không phải một activation đơn lẻ:

\[ \text{SwiGLU}(\mathbf{x}; W, V, b, c) = \text{SiLU}(W \mathbf{x} + b) \odot (V \mathbf{x} + c) \]

Trong đó \( \odot \) là element-wise product. SwiGLU thay block FFN \( \text{Linear} \to \text{GELU} \to \text{Linear} \) bằng nhánh gate; dùng nhiều trong LLM hiện đại: PaLM (Chowdhery et al. 2022), LLaMA (Touvron et al. 2023), Mistral, Qwen.

13

Khi nào chọn cái nào

Khuyến nghị thực tế ngắn gọn:

  • Default cho hidden layer: ReLU. Đủ tốt cho hầu hết MLP, CNN cổ điển. Đi cùng He init.
  • Khi đo được dying ReLU rõ rệt (nhiều neuron dead trên validation, model underfit): chuyển sang Leaky ReLU \( \alpha = 0.01 \) hoặc thử ELU.
  • Transformer / LLM: GELU là mặc định an toàn (BERT, GPT). LLM scale lớn (LLaMA, PaLM, Mistral) dùng SwiGLU trong FFN.
  • CNN hiện đại (EfficientNet, MobileNetV3): SiLU/Swish.
  • GAN: Leaky ReLU \( \alpha = 0.2 \) trong discriminator (theo DCGAN), ReLU cho generator.

Lưu ý: thay đổi activation thường mang lại cải thiện ở mức 1-2% trên benchmark, không phải khác biệt căn bản. Trước khi tinh chỉnh activation, ưu tiên các yếu tố lớn hơn: data, kiến trúc, regularization, learning rate schedule.

14

Output layer không dùng ReLU

ReLU và biến thể chỉ dùng cho hidden layer. Output layer chọn activation theo bài toán:

  • Regression: thường không activation (linear) — output có thể là số thực bất kỳ. Nếu biết target dương (vd. giá nhà, lượng mưa), có thể dùng ReLU hoặc Softplus ở output, nhưng cần cẩn thận về dying ReLU ngay tại output.
  • Binary classification: sigmoid (Bài 5) → xác suất \( \in (0, 1) \).
  • Multi-label classification: sigmoid trên từng output independent.
  • Multi-class classification: softmax (Bài 8) → phân phối xác suất trên \( K \) class.

Lý do không dùng ReLU ở output binary / multi-class: cần một xác suất ràng buộc trong \( [0, 1] \) (hoặc tổng = 1). ReLU không cho ràng buộc đó.

15

He initialization đi cùng ReLU

ReLU làm "tắt" khoảng một nửa neuron mỗi forward pass → variance của activation giảm một nửa qua mỗi layer nếu init giữ nguyên kiểu Xavier (vốn thiết kế cho sigmoid / tanh). Hệ quả: signal tan dần qua layer sâu, gradient cũng vậy.

He et al. (2015) đề xuất khởi tạo weight với:

\[ W \sim \mathcal{N}\left(0, \frac{2}{n_{\text{in}}}\right) \]

(so với Xavier: \( \text{Var}(W) = 1 / n_{\text{in}} \)). Hệ số 2 bù đúng cho việc ReLU tắt một nửa input, giữ variance activation ổn định qua các layer.

Khuyến nghị ngắn (chi tiết B20):

  • ReLU / LeakyReLU / PReLU → He init (kaiming_normal_ hoặc kaiming_uniform_ trong PyTorch).
  • Sigmoid / tanh → Xavier (Glorot) init (xavier_normal_ / xavier_uniform_).
  • SELU → LeCun normal.

PyTorch 2.x default cho nn.Linear là Kaiming uniform với \( a = \sqrt{5} \) — biến thể của He init phù hợp ReLU. Hầu hết trường hợp không cần init thủ công.

16

PyTorch API

PyTorch 2.x cung cấp đầy đủ functional và module form:

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

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

# ReLU
print(F.relu(z))                       # tensor([0., 0., 0., 1., 2.])
print(nn.ReLU()(z))                    # tensor([0., 0., 0., 1., 2.])

# Leaky ReLU — alpha = negative_slope, default 0.01
print(F.leaky_relu(z, negative_slope=0.01))
# tensor([-0.0200, -0.0100,  0.0000,  1.0000,  2.0000])

# PReLU — alpha học được
prelu = nn.PReLU(num_parameters=1, init=0.25)
print(prelu(z))

# ELU
print(F.elu(z, alpha=1.0))
# tensor([-0.8647, -0.6321,  0.0000,  1.0000,  2.0000])

# GELU — default approximate='none' (dùng erf)
print(F.gelu(z))
# tensor([-0.0455, -0.1587,  0.0000,  0.8413,  1.9545])

# SiLU / Swish
print(F.silu(z))
# tensor([-0.2384, -0.2689,  0.0000,  0.7311,  1.7616])

Khi build network, dùng module form trong nn.Sequential:

model = nn.Sequential(
    nn.Linear(784, 256),
    nn.ReLU(inplace=True),       # inplace=True tiết kiệm memory
    nn.Linear(256, 128),
    nn.ReLU(inplace=True),
    nn.Linear(128, 10),
)

Tham số inplace=True ghi đè tensor input → tiết kiệm memory cho tensor lớn. Chỉ dùng khi input đó không cần cho gradient của module trước đó — với ReLU thuần sau Linear, an toàn.

17

Code Python

Implement ReLU, LeakyReLU và đạo hàm bằng NumPy:

import numpy as np


def relu(z):
    return np.maximum(0.0, z)


def relu_derivative(z):
    return (z > 0).astype(z.dtype)


def leaky_relu(z, alpha=0.01):
    return np.where(z > 0, z, alpha * z)


def leaky_relu_derivative(z, alpha=0.01):
    return np.where(z > 0, 1.0, alpha)


zs = np.array([-2.0, -1.0, 0.0, 1.0, 2.0])
print("z              :", zs)
print("ReLU(z)        :", relu(zs))
print("ReLU'(z)       :", relu_derivative(zs))
print("LeakyReLU(z)   :", leaky_relu(zs, alpha=0.1))
print("LeakyReLU'(z)  :", leaky_relu_derivative(zs, alpha=0.1))

Output:

z              : [-2. -1.  0.  1.  2.]
ReLU(z)        : [0. 0. 0. 1. 2.]
ReLU'(z)       : [0. 0. 0. 1. 1.]
LeakyReLU(z)   : [-0.2 -0.1  0.   1.   2. ]
LeakyReLU'(z)  : [0.1 0.1 0.1 1.  1. ]

Demo dying ReLU ngắn — một neuron đơn với learning rate quá lớn rơi vào trạng thái chết:

import numpy as np

rng = np.random.default_rng(0)

# Dữ liệu giả: 100 sample, 4 feature, target nhị phân
X = rng.standard_normal((100, 4))
y = (X.sum(axis=1) > 0).astype(np.float32)

# 1 lớp ReLU duy nhất với 8 neuron
W = rng.standard_normal((4, 8)) * 0.5
b = np.zeros(8)
lr = 1.0   # learning rate cố ý quá lớn

for step in range(50):
    Z = X @ W + b              # pre-activation
    A = np.maximum(0.0, Z)     # ReLU
    # Loss giả lập: MSE giữa mean activation và y
    pred = A.mean(axis=1)
    grad_pred = (pred - y) / X.shape[0]      # (100,)
    grad_A = np.broadcast_to(grad_pred[:, None] / 8, A.shape)
    grad_Z = grad_A * (Z > 0)                # đạo hàm ReLU
    grad_W = X.T @ grad_Z
    grad_b = grad_Z.sum(axis=0)
    W -= lr * grad_W
    b -= lr * grad_b

Z_final = X @ W + b
dead_neurons = ((Z_final <= 0).all(axis=0)).sum()
print(f"Dead neurons: {dead_neurons}/8")

Chạy thử cho thấy với lr = 1.0 nhiều neuron rơi vào dead state (\( z \le 0 \) cho mọi sample). Giảm lr về 0.01-0.05, hoặc thay np.maximum(0, z) bằng leaky_relu(z, alpha=0.01), số dead neuron giảm rõ rệt.

Plot 5 activation trên cùng range — hữu ích để hình dung khác biệt:

import numpy as np
import matplotlib.pyplot as plt
from scipy.special import erf

z = np.linspace(-4, 4, 401)

sigmoid = 1.0 / (1.0 + np.exp(-z))
tanh = np.tanh(z)
relu = np.maximum(0.0, z)
leaky = np.where(z > 0, z, 0.1 * z)
gelu = 0.5 * z * (1.0 + erf(z / np.sqrt(2.0)))

for name, y in [("sigmoid", sigmoid), ("tanh", tanh),
                ("ReLU", relu), ("LeakyReLU(0.1)", leaky), ("GELU", gelu)]:
    plt.plot(z, y, label=name)
plt.axhline(0, color="gray", lw=0.5)
plt.axvline(0, color="gray", lw=0.5)
plt.legend(); plt.grid(True); plt.show()
18

Bài tập

  1. Tính tay \( \text{ReLU}(z) \) và \( \text{ReLU}'(z) \) tại \( z = -2, -1, 0, 1, 2 \).
  2. Implement leaky_reluleaky_relu_derivative với \( \alpha = 0.1 \) bằng NumPy. Test trên cùng tập \( z \) ở câu 1.
  3. Chạy demo dying ReLU ở mục 17 với lr lần lượt 0.01, 0.1, 1.0. Đếm số dead neuron sau 50 step. Vẽ biểu đồ.
  4. Lặp lại câu 3 nhưng thay ReLU bằng LeakyReLU \( \alpha = 0.01 \). Đếm số neuron có \( \text{activation} = 0 \) cho mọi sample. Nhận xét.
  5. Trong một network 20 layer toàn ReLU, giả sử ở mỗi layer xác suất một neuron có \( z > 0 \) là 0.5. Ước tính kỳ vọng tỉ lệ neuron active đồng thời ở cả 20 layer cho một sample bất kỳ.
  6. So sánh GELU và SiLU bằng cách vẽ chúng trên \( z \in [-4, 4] \). Tìm điểm cực tiểu của mỗi hàm (xấp xỉ).
  7. Trong PyTorch, tạo nn.Sequential với 3 nn.Linear + ReLU. So sánh tốc độ forward khi inplace=True vs inplace=False trên batch \( 10^5 \times 1024 \).
Đáp án ngắn
  1. \( \text{ReLU}: [0, 0, 0, 1, 2] \). \( \text{ReLU}': [0, 0, 0, 1, 1] \) (theo quy ước \( \text{ReLU}'(0) = 0 \) của PyTorch).
  2. \( \text{LeakyReLU}(0.1): [-0.2, -0.1, 0, 1, 2] \). Đạo hàm: \( [0.1, 0.1, 0.1, 1, 1] \).
  3. Số dead neuron tăng theo lr: thường 0-1 với 0.01, 2-3 với 0.1, 5-7 với 1.0 (random seed cụ thể có thể khác).
  4. Leaky giảm rõ — thường còn 0-1 neuron có activation luôn ≤ 0, vì gradient nhánh âm vẫn cho phép update.
  5. \( 0.5^{20} \approx 9.5 \times 10^{-7} \) — kỳ vọng cực kỳ ít sample kích hoạt được "đường" qua đủ 20 layer. Trong thực tế các neuron không độc lập nên con số thực tế khác xa, nhưng sparse hơn theo độ sâu.
  6. GELU và SiLU đều có cực tiểu phía âm: GELU tại \( z \approx -0.751 \), giá trị \( \approx -0.170 \). SiLU tại \( z \approx -1.278 \), giá trị \( \approx -0.279 \).
  7. inplace=True tiết kiệm khoảng tensor activation; tốc độ forward thường nhanh hơn vài %, chênh lệch lớn hơn nếu memory bandwidth là bottleneck.
19

Tóm tắt

  • ReLU \( \text{ReLU}(z) = \max(0, z) \): tính \( \le 2 \) cycles, đạo hàm \( \in \{0, 1\} \), không bị vanish ở nhánh dương. Mặc định cho hidden layer của MLP / CNN từ 2012.
  • Sparse activation: khoảng 50% neuron tắt mỗi forward pass — biểu diễn thưa, regularization ngầm.
  • Dying ReLU: neuron có \( z \le 0 \) cho mọi sample → gradient = 0 → weight không update → chết vĩnh viễn. Gây ra bởi learning rate lớn, weight init xấu, input lệch.
  • Leaky ReLU \( \alpha = 0.01 \): cho nhánh âm độ dốc nhỏ, tránh dying. PReLU: \( \alpha \) học được.
  • ELU: nhánh âm smooth dạng \( \alpha(e^z - 1) \), mean activation gần 0.
  • GELU \( z \cdot \Phi(z) \): smooth, non-monotonic. Mặc định trong BERT, GPT, ViT.
  • SiLU / Swish \( z \cdot \sigma(z) \): dùng trong EfficientNet, MobileNetV3. SwiGLU là biến thể FFN dùng SiLU làm gate — chuẩn trong LLaMA, PaLM, Mistral.
  • Khuyến nghị: default ReLU + He init cho hidden. Dying ReLU thì thử Leaky / ELU. Transformer / LLM dùng GELU hoặc SwiGLU.
  • Output layer chọn theo bài toán: linear cho regression, sigmoid cho binary / multi-label, softmax cho multi-class — không dùng ReLU ở output.