Chuyển đến nội dung chính

第 4 課:深度學習概述 — CNN、RNN、LSTM

深度學習架構概述:用於影像的 CNN、序列資料的 RNN 和 LSTM——這是理解 Transformer 誕生原因的重要前提。

🧠 人工智慧與機器學習 — 第 3 課 第 4 課:深度學習概述 — CNN、RNN、 長短期記憶網絡

人工智慧和法學碩士:從基礎到高級

第 1 部分:人工智慧和深度學習平台

亞洲開發網

簡介

在上一篇文章中,我們建構了 MLP——通用神經網路。但 MLP 有一個問題:它不理解資料的結構。對於圖像,MLP「看到」每個像素都是一樣的——它不知道哪個像素與哪個像素相鄰。對於文本,MLP 不知道哪些單字在前、哪些單字在後的順序。

本文介紹了三種專門的架構-每種架構都是為了處理特定類型的資料而建構的:

建築為而生主要思想現實生活中的例子
美國有線電視新聞網照片、影片“觀察每個小區域”Google 相簿將照片分類
RNN字串(文字、語音)“記住你讀過的內容”輸入訊息時的單字建議
LSTM長串《選擇性記憶》Google翻譯(2017 年之前)

劇透: 這三者都有 弱點,這就是為什麼 Transformer 在 2017 年問世 - 它改變了一切。讓我們找出原因。


1. 卷積神經網路 (CNN) —“觀察小區域”

1.1 MLP 的圖片問題

假設您想要辨識手寫數字(28×28 像素影像):

  • MLP: 將影像「拉伸」成一行 784 個數字 → 完全遺失位置資訊。影像的左上像素和中間像素被視為相同。此外,MLP 第一層需要 784×hidden_​​size 參數——很多!

  • CNN:「檢視」影像中的小區域(3×3 或 5×5 像素),找出局部特徵(邊緣、角點、曲線)。 更少的參數和更好的位置理解。

1.2 卷積—“滑動濾波器”

日常生活中的例子: 你用放大鏡看照片。小放大鏡(3×3公分),將玻璃從左上角滑到右下角。在每個位置,您記錄一個數字,總結該區域(亮/暗,是否有邊緣,是否有角...)。結果→「特徵圖」。

卷積圖解:濾波器在影像上滑動以建立特徵圖

Input image (6×6):     Filter (3×3):     Feature map (4×4):
┌─────────────────┐    ┌─────────┐       ┌─────────────┐
│ 1 1 1 │ 0 0 0  │    │ 1  0  1 │       │ 4  3  2  1  │
│ 1 1 1 │ 0 0 0  │  × │ 0  1  0 │   =   │ ...         │
│ 1 1 1 │ 0 0 0  │    │ 1  0  1 │       │ ...         │
│ 0 0 0 │ 1 1 1  │    └─────────┘       │ 1  2  3  4  │
│ 0 0 0 │ 1 1 1  │      ↑ Trượt          └─────────────┘
│ 0 0 0 │ 1 1 1  │      qua ảnh
└─────────────────┘

在每個位置:相應地相乘,然後相加(如點積!)→ 1 個數字。

CNN的3大優勢:

優勢說明範例
參數共用整個影像使用相同的濾鏡1 3×3 濾鏡 = 只有 9 個參數(而不是數千個)
本地連接每個神經元只看到一個小區域偵測邊緣、角點、紋理
平移不變性辨識物體,無論它們在哪裡無論貓在哪個角落,都能認出它們

1.3 CNN 架構 — 從像素到決策

CNN 堆疊了許多層,從 簡單特徵 → 複雜特徵:

Lớp 1: Phát hiện cạnh (edges) ─── Filter đơn giản
    ↓
Lớp 2: Phát hiện góc, texture ─── Kết hợp cạnh
    ↓
Lớp 3: Phát hiện bộ phận (mắt, mũi, tai) ─── Kết hợp góc + texture
    ↓
Lớp 4: Nhận ra đối tượng (mèo, chó) ─── Kết hợp bộ phận

例如: 貓咪辨識:像素→邊→三角形(耳朵)+圓形(眼睛)→「貓」!

import torch
import torch.nn as nn

class SimpleCNN(nn.Module):
    def __init__(self, num_classes=10):
        super().__init__()
        self.features = nn.Sequential(
            # === Lớp 1: Phát hiện đặc trưng cơ bản ===
            nn.Conv2d(1, 32, kernel_size=3, padding=1),  # 28×28 → 28×28, 32 filters
            nn.ReLU(),
            nn.MaxPool2d(2),                              # 28×28 → 14×14 (thu nhỏ)
            
            # === Lớp 2: Phát hiện đặc trưng phức tạp hơn ===
            nn.Conv2d(32, 64, kernel_size=3, padding=1), # 14×14 → 14×14, 64 filters
            nn.ReLU(),
            nn.MaxPool2d(2),                              # 14×14 → 7×7
        )
        self.classifier = nn.Sequential(
            nn.Flatten(),                # 64 × 7 × 7 = 3136 số
            nn.Linear(64 * 7 * 7, 128), # Dense layer
            nn.ReLU(),
            nn.Linear(128, num_classes)  # Output: 10 lớp (0-9)
        )

    def forward(self, x):
        features = self.features(x)      # Trích xuất đặc trưng
        return self.classifier(features)  # Phân loại

# Test
model = SimpleCNN(num_classes=10)
x = torch.randn(8, 1, 28, 28)  # 8 ảnh, 1 kênh (grayscale), 28×28
output = model(x)
print(f"Input: {x.shape} → Output: {output.shape}")  # [8, 10]
print(f"Tổng parameters: {sum(p.numel() for p in model.parameters()):,}")

1.4 著名的 CNN — 歷史里程碑

型號年份特點為什麼它很重要
LeNet-51998第一個務實的CNN讀取銀行支票上的號碼
亞歷克斯網路2012GPU 訓練、ReLU、Dropout贏得 ImageNet → 深度學習爆炸
VGG2014年很深(16-19層),只用3×3證明「更深=更好」
ResNet20152015跳過連接,152 類別解決非常深的網路中的梯度消失問題
高效網20192019智慧秤最佳化速度/準確度

💡練習 1: 1.手動計算:如果輸入是32×32影像,使用filter 5×5,stride=1,無padding → 特徵圖尺寸是多少?公式: output_size = (input_size - kernel_size) / stride + 1 2.如果我們加入padding=2會怎麼樣? 3.為什麼MaxPool2d(2)會將影像尺寸縮小1/2?


2. 序列資料問題—“序列很重要”

CNN 非常適合圖像,但是序列資料(文字、語音、時間序列)呢?

2.1 為什麼需要不同的架構?

範例 1 — 意義更改順序:

"Tôi KHÔNG thích pizza"  ≠  "Tôi thích pizza"
→ Từ "không" ở giữa thay đổi nghĩa TOÀN CÂU

範例 2 — 遠端上下文:

"Người phụ nữ mặc áo đỏ, đeo kính, cầm ô,
 đứng cạnh cây cổ thụ trong công viên,
 đang ____ với con chó."
→ Cần nhớ "người phụ nữ" từ đầu câu để điền đúng!

範例 3 — 可變長度:

Input 1: "Xin chào" (2 từ)
Input 2: "Hôm nay trời đẹp quá, chúng ta đi dạo nhé!" (9 từ)
→ MLP cần input cố định → không xử lý được!

CNN 可以處理短文本(TextCNN),但很難捕捉長句子開頭和結尾的單字之間的遠距離關係。

💡練習 2: 再想 2 個越南語範例,其中詞序完全改變了意義。建議:否定句、被動句...


3. 循環神經網路 (RNN) — “閱讀每個單字,將其記在腦海中”

3.1 主要思想

日常生活範例: 你讀一本小說。你一頁一頁地閱讀,每讀完一頁,你記住你讀過的內容(隱藏狀態)。新的一頁+舊的記憶→新的理解。

RNN 的工作原理完全相同:讀取每個單字,維護每一步都會更新的隱藏狀態(短期記憶):

     x₁ ("Con")    x₂ ("mèo")    x₃ ("đang")    x₄ ("ngủ")
       ↓              ↓              ↓               ↓
h₀ → [RNN] → h₁ → [RNN] → h₂ → [RNN] → h₃ → [RNN] → h₄
 ↑            ↑             ↑             ↑            ↑
(ban đầu    (nhớ          (nhớ          (nhớ         (nhớ toàn
 trống)     "Con")        "Con mèo")    "Con mèo     câu)
                                         đang")
h_t = tanh(W_hh × h_{t-1} + W_xh × x_t + b)
       ↑           ↑              ↑
   activation   ký ức cũ       từ mới
class SimpleRNN(nn.Module):
    def __init__(self, input_size, hidden_size, output_size):
        super().__init__()
        self.hidden_size = hidden_size
        self.rnn = nn.RNN(input_size, hidden_size, batch_first=True)
        self.fc = nn.Linear(hidden_size, output_size)

    def forward(self, x):
        # x: (batch, seq_len, input_size) — batch câu, mỗi câu là chuỗi
        out, hidden = self.rnn(x)
        # Dùng hidden state cuối (chứa ký ức toàn câu) để dự đoán
        return self.fc(hidden.squeeze(0))

# Ví dụ: phân loại câu positive/negative
rnn = SimpleRNN(input_size=128, hidden_size=256, output_size=2)
x = torch.randn(8, 20, 128)  # 8 câu, mỗi câu 20 từ, mỗi từ embedding 128
output = rnn(x)
print(f"Input: {x.shape} → Output: {output.shape}")  # [8, 2]

3.2 梯度消失-致命弱點

日常生活中的例子: 你玩「電話遊戲」-20個人排隊,第一個人低聲說一句話,傳遞給每個人。到了最後一個人,訊息就被完全扭曲了,因為經過了太多的步驟。

RNN 也是如此!當在多個時間步上反向傳播時,梯度連續乘以相同的矩陣 W:

∂L/∂h₀ = ∂L/∂h_T × (W)^T lần
案例後果數值範例
|W| < 1梯度 → 0 (消失)0.9^20 = 0.12 → 差點就輸了!
|W| > 1梯度→ 無窮大(爆炸)1.1^20 = 6.7 → 繁榮!

**實際後果:**基本 RNN 只記住 ~10-20 個最近的單字。對於一個 100 個單字長的句子,RNN 完全「忘記」了句子的開頭。

# Demo: gradient vanishing qua nhiều bước nhân
w = 0.9  # < 1
gradients = [w**t for t in range(20)]
print("Gradient từ t=0 đến t=19:")
print([f"{g:.4f}" for g in gradients])
# [1.0, 0.9, 0.81, 0.729, ..., 0.1351] → giảm 87%!

print(f"\nSau 20 bước: gradient chỉ còn {gradients[-1]:.1%} ban đầu")
print(f"Sau 50 bước: {0.9**50:.6f} → gần như = 0!")

💡練習 3:

  1. 如果 W = 1.1 而不是 0.9,20 步後的梯度是多少? 100步之後?
  2. 為什麼梯度裁切(將梯度限制在[-max, max]之間)有助於解決爆炸問題而不是消失問題?

4. 長短期記憶(LSTM)—“選擇性記憶”

4.1 主要思想

現實生活中的例子: 比較 RNN 與 LSTM:

  • RNN = 小便利貼上的註解 → 只能在舊的上面寫一點新的文字
  • LSTM = 關於 鎖定筆記本 的註解 → 可能:
    • 刪除你不需要的舊資訊(忘記門)—“刪除頁面”
    • 新增重要的新資訊(輸入門)—“寫入新頁面”
    • 閱讀所需資訊(輸出門)—“開啟頁面閱讀”

比較具有單元狀態和 3 個閘的 RNN 與 LSTM 架構

4.2 LSTM 的三個門

LSTM 維護 2 個資訊流:

  • 細胞狀態(C) =「筆記本」-長期記憶,像傳送帶一樣貫穿整個鏈條
  • 隱藏狀態 (h) = "便利貼" — 短期記憶,用於當前步驟
Forget Gate:  f_t = σ(W_f · [h_{t-1}, x_t] + b_f)    ← "Quên gì?"
Input Gate:   i_t = σ(W_i · [h_{t-1}, x_t] + b_i)    ← "Nhớ gì mới?"
              C̃_t = tanh(W_C · [h_{t-1}, x_t] + b_C)  ← "Thông tin mới"
Cell Update:  C_t = f_t × C_{t-1} + i_t × C̃_t         ← "Cập nhật sổ tay"
Output Gate:  o_t = σ(W_o · [h_{t-1}, x_t] + b_o)     ← "Output gì?"
              h_t = o_t × tanh(C_t)                     ← "Đọc & xuất"

具體範例 — LSTM 讀取句子「我出生於 越南。我在美國大學學習了 4 年。我的母語是 ___」:

步驟忘記大門輸入門細胞狀態
“越南”保留一切寫“國籍:越南”{國籍:越南}
「美國」保留 VN(不要忘記!)寫「出國留學:美國」{國籍:越南人,留學地:美國}
「4年」保留一切寫「時間:4年」{國籍:越南,留學:美國,4年}
「媽媽…」忘記「出國留學」聚焦「國籍」→ 輸出:「越南語」 ✅

4.3 代碼 LSTM — 情緒分類

class SentimentLSTM(nn.Module):
    def __init__(self, vocab_size, embed_dim, hidden_size, n_layers=2):
        super().__init__()
        self.embedding = nn.Embedding(vocab_size, embed_dim, padding_idx=0)
        self.lstm = nn.LSTM(
            embed_dim, hidden_size,
            num_layers=n_layers,     # 2 lớp LSTM xếp chồng
            batch_first=True,
            dropout=0.3,             # Chống overfit
            bidirectional=True       # Đọc cả 2 chiều!
        )
        self.fc = nn.Linear(hidden_size * 2, 1)  # ×2 vì bidirectional
        self.sigmoid = nn.Sigmoid()

    def forward(self, x):
        embedded = self.embedding(x)           # Token IDs → vectors
        lstm_out, (hidden, _) = self.lstm(embedded)
        # Lấy hidden state cuối của cả 2 chiều (forward + backward)
        hidden = torch.cat([hidden[-2], hidden[-1]], dim=1)
        return self.sigmoid(self.fc(hidden))   # 0→1: negative→positive

# Ví dụ sử dụng
model = SentimentLSTM(vocab_size=10000, embed_dim=128, hidden_size=256)
x = torch.randint(0, 10000, (8, 50))  # 8 câu, mỗi câu 50 tokens
output = model(x)
print(f"Sentiment predictions: {output.squeeze()[:3]}")  # 3 câu đầu

雙向 LSTM = 雙向閱讀(左→右和右→左)。為什麼?因為有時候你需要看背後才能理解其中的意思:

"Anh ấy không phải là người ____ nhất, nhưng rất chăm chỉ"
→ Cần đọc "nhưng rất chăm chỉ" (phía sau) để biết ____ = "giỏi" (chứ không phải "tệ")

4.4 比較 RNN、LSTM 和 GRU

循環神經網路LSTM格魯烏
大門03(忘記、輸入、輸出)2(重置、更新)
參數至少大多數平均
記得遠❌ ~10 字✅ ~200 字✅ ~200 字
列車速度快慢平均
何時使用演示/學習長文本,NLP文本長,數據少

GRU是LSTM的簡化版本:將遺忘+輸入門合併為更新門,去除細胞狀態。性能與 LSTM 相當,但參數較少。

💡練習 4:

  1. LSTM有幾個權重矩陣?提示:每個閘有 2 個矩陣(W 表示輸入,U 表示隱藏狀態),加上候選單元狀態。
  2. 如果hidden_size = 256,input_size = 128,則1個LSTM單元的總參數 = ?公式: 4 × (input_size × hidden_size + hidden_size × hidden_size + hidden_size)

5. LSTM 的限制以及為什麼我們需要 Transformer

儘管 LSTM 比 RNN 強大得多,但它仍然存在 2 個基本問題,從而引發了 Transformer 革命:

5.1 ❌ 順序處理 — “一次一個單字”

x₁ → h₁ → x₂ → h₂ → x₃ → h₃ → ... → x_1000 → h_1000
     ↑ phải chờ ↑ phải chờ ↑ phải chờ

LSTM 必須依序處理 — h₃ 取決於 h2,h2 取決於 h₁。這意味著:

後果說明影響力
未並行化GPU 有 10,000 個核心,但只使用 1 個浪費 99.99% GPU
慢速訓練句子 1000 個字 = 1000 個連續步驟日 → 週 → 月
無法擴充2x 長 = 2x 慢(線性)無法處理長書/文件

範例: 在 100 萬個句子 × 100 個字 = 1 億個連續步驟上訓練 LSTM。 Transformer 可以並行 = 快 100 倍!

5.2 ❌資訊瓶頸—“資訊瓶頸”

雖然細胞狀態有助於進一步記憶,但句子的所有含義必須「通過」固定大小的向量 h_T(例如 256 個數字):

"The cat that ate the mouse that lived in the house that Jack built sat on the mat"
                                                                      ↓
                                                              h_T = [0.2, -0.3, ...]
                                                              (nén 15 từ → 256 số!)

對於 1000 個單字的句子,256 位元向量不足以記住所有內容→訊息丟失。

5.3 ✅ 解決方案:Transformer (2017)

Transformer 透過 注意力機制 解決了 這兩個問題:

LSTM 問題變壓器解決方案
順序處理並行化 — 一次處理所有單字
資訊瓶頸注意 — 每個單字「直接看」其他每個單字
LSTM: x₁ → x₂ → x₃ → x₄ → x₅  (tuần tự, chậm)

Transformer:
x₁ ←→ x₂     
x₁ ←→ x₃     Tất cả từ "nhìn" nhau
x₁ ←→ x₄     cùng lúc, song song!
x₂ ←→ x₃     
...

這是**《Attention Is All You Need》** (2017) 文章的突破-用注意力機製完全取代 RNN/LSTM。我們將在下一篇文章中深入探討。

💡練習 5:

  1. LSTM 用 100 個步驟處理一個 100 個單字的句子。 Transformer 需要多少步? (提示:Transformer 並行處理所有單字)
  2. Transformer 為什麼會出現「二次注意力成本」問題?提示:如果有 N 個單詞,每個單字必須「看到」多少個其他單字?

6. 什麼時候我還應該使用 CNN/RNN/LSTM?

儘管 Transformer 在 NLP 中佔據主導地位,但 CNN 和 LSTM 在許多問題中仍然佔有一席之地:

CNN 仍用於:

  • 電腦視覺:ResNet、EfficientNet 仍然很流行(儘管 Vision Transformer 正在取代)
  • 音訊處理:用於音訊分類的一維卷積
  • 簡單文字分類:TextCNN 快速、輕量級且對於小問題高度準確
  • 邊緣設備:可在手機上運行的輕量級 CNN (MobileNet)

LSTM/GRU 仍用於:

  • 小型裝置上的時間序列:預測物聯網上的感測器數據
  • 串流資料:資料連續到達(不是批次)
  • 小資料集:Transformer 資料少很容易過擬合,LSTM 更好
  • 即時推理:LSTM處理每一步,反應快

規則:如果有大量資料+強大的GPU → Transformer。如果小數據+小裝置 → CNN/LSTM。

💡練習 6: 給定 3 個問題,你會選擇哪一種架構?說明:

  1. 將垃圾郵件/普通郵件分類(1000 封郵件,每封郵件約 50 個字)
  2. 聊天機器人回答問題(100萬次對話) 3.安全攝影機上的臉部辨識(即時)

總結

建築強弱用於黃金時代
美國有線電視新聞網本地模式,快速遠程、序列圖片、短文2012 年至今
RNN順序處理梯度消失簡單的序列2013–2017
LSTM長程記憶順序、緩慢中等序列2015–2018
變壓器並行、遠端成本 O(N²)NLP、法學碩士、願景2017 年–至今

進化時間表

1998: LeNet (CNN cho chữ số)
  ↓
2012: AlexNet (CNN thắng ImageNet → Deep Learning bùng nổ)
  ↓
2014: Sequence-to-Sequence (LSTM cho dịch thuật)
  ↓
2015: Attention + LSTM (cải thiện dịch thuật)
  ↓
2017: "Attention Is All You Need" → TRANSFORMER 💥
  ↓
2018: BERT, GPT-1 (Transformer thống trị NLP)
  ↓
2020+: GPT-3, ChatGPT → LLM thời đại mới

一般練習

  1. ✅ 完成每個部分的所有小練習(1,2,3,4,5,6)
  2. 使用 PyTorch,實現 1.3 節中的 SimpleCNN 並在 MNIST 資料集上進行訓練。準確率達 >98%。
  3. 實現 4.3 節中的 SentimentLSTM 並在電影評論資料集 (IMDB) 上進行訓練。準確率達 >85%。
  4. 實驗比較: 使用(a) MLP、(b) CNN (TextCNN)、(c) LSTM 訓練相同的文字分類問題。比較準確性、訓練時間和參數數量。
  5. 閱讀文章「Attention Is All You Need」的摘要 — 列出了作者給出的 3 個以 Transformer 取代 RNN 的理由。

下一篇文章: 我們將了解 注意力 機制 - Transformer 和所有現代法學碩士非凡力量的關鍵。