返回文章列表

自然語言處理注意力機制與Transformer架構

深入探討自然語言處理中的注意力機制與Transformer架構,分析其如何解決傳統RNN的限制,並解析BERT、GPT等預訓練語言模型如何推動NLP領域的革命性進展。

自然語言處理 機器學習

自然語言處理領域在過去數年間經歷了根本性的技術變革,而這場變革的核心推動力正是注意力機制與 Transformer 架構的出現。這兩項技術創新徹底改變了我們處理序列資料的方式,使得模型能夠更有效地捕捉長距離依賴關係,並實現前所未有的平行化運算能力。從機器翻譯到文字生成,從情感分析到問答系統,注意力機制與 Transformer 架構已經成為當代自然語言處理的基礎設施。

在注意力機制出現之前,循環神經網路及其變體如長短期記憶網路和門控循環單元是處理序列資料的主流方法。這些模型透過隱藏狀態將資訊從一個時間步傳遞到下一個,實現了對序列資料的建模。然而,這種序列性的處理方式存在兩個根本性的限制:首先,資訊在長序列中傳遞時會逐漸衰減,導致模型難以捕捉遠距離的依賴關係;其次,序列處理的本質使得模型無法平行化運算,嚴重限制了訓練效率。

注意力機制的提出優雅地解決了第一個問題。它允許模型在處理當前元素時,直接存取序列中所有其他元素的資訊,並根據相關性進行加權。這種機制模擬了人類認知中的選擇性注意力,使模型能夠聚焦於當前任務最相關的資訊。而 Transformer 架構則進一步解決了第二個問題,它完全拋棄了循環結構,僅使用注意力機制來建模序列依賴,從而實現了真正的平行化處理。

本文將深入探討注意力機制的原理與演進,詳細解析 Transformer 架構的組成與運作方式,並分析 BERT、GPT 等預訓練語言模型如何建立在這些技術基礎上,推動自然語言處理進入新的時代。

注意力機制的原理與演進

注意力機制最初被引入到序列到序列的學習任務中,特別是機器翻譯。在傳統的編碼器-解碼器架構中,編碼器將整個輸入序列壓縮成單一的上下文向量,解碼器則根據這個向量生成輸出序列。這種架構的問題在於,無論輸入序列多長,所有資訊都必須壓縮到固定大小的向量中,這顯然會造成資訊的丟失,特別是對於長序列而言。

注意力機制透過讓解碼器在每個時間步都能夠存取編碼器的所有隱藏狀態來解決這個問題。具體來說,在解碼每個輸出詞元時,模型會計算當前解碼器狀態與所有編碼器隱藏狀態之間的相關性分數,然後根據這些分數對編碼器狀態進行加權求和,得到當前時間步的上下文向量。這個上下文向量與解碼器狀態一起用於預測下一個輸出詞元。

注意力分數的計算方式有多種。最簡單的是點積注意力,它直接計算查詢向量與鍵向量之間的點積。加法注意力則使用一個前饋神經網路來計算分數。縮放點積注意力在點積注意力的基礎上除以鍵向量維度的平方根,以防止點積值過大導致軟最大函數梯度消失。這種縮放點積注意力成為了後續 Transformer 架構的標準選擇。

以下的流程圖展示了注意力機制在機器翻譯中的運作方式:

@startuml
!define PLANTUML_FORMAT svg
!theme _none_

skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100

' 注意力機制流程
(*) --> "輸入序列編碼"
"輸入序列編碼" --> "產生編碼器隱藏狀態"
"產生編碼器隱藏狀態" --> "計算注意力分數"
note right: 解碼器狀態與\n各編碼器狀態的相關性

"計算注意力分數" --> "軟最大正規化"
"軟最大正規化" --> "加權求和"
note right: 產生上下文向量

"加權求和" --> "結合解碼器狀態"
"結合解碼器狀態" --> "預測輸出詞元"
"預測輸出詞元" --> if "序列結束" then
  -->[是] (*)
else
  -->[否] "更新解碼器狀態"
  "更新解碼器狀態" --> "計算注意力分數"
endif

end note

end note

@enduml

這個流程圖展示了注意力機制如何在解碼過程中動態地聚焦於輸入序列的不同部分。每次預測輸出詞元時,模型都會重新計算注意力分數,產生新的上下文向量。這種動態的注意力分配使得模型能夠在翻譯不同部分時關注輸入序列的相應區域。

注意力機制的引入顯著提升了機器翻譯的品質,特別是對於長句子的翻譯。此外,注意力權重的視覺化還提供了模型可解釋性的窗口,讓我們能夠觀察模型在翻譯時關注輸入序列的哪些部分。這種可解釋性在後續的研究中被廣泛利用,用於診斷模型的行為和發現潛在的問題。

自注意力機制與多頭注意力

自注意力機制是注意力機制的一個重要變體,它允許序列中的每個元素都能夠存取序列中的所有其他元素,從而捕捉序列內部的依賴關係。在傳統的注意力機制中,注意力是在編碼器和解碼器之間計算的;而在自注意力中,注意力是在同一個序列內部計算的。這種機制使得模型能夠建模序列中任意兩個位置之間的依賴關係,無論它們相距多遠。

自注意力的計算涉及三組向量:查詢、鍵和值。對於序列中的每個元素,模型會生成對應的查詢、鍵和值向量。然後,每個元素的輸出是所有值向量的加權和,其中權重由該元素的查詢向量與所有元素的鍵向量之間的相關性決定。這個過程可以用矩陣運算高效地實現,從而支援平行化計算。

多頭注意力是自注意力的一個擴展,它並行地執行多個自注意力計算,每個「頭」使用不同的線性投影。多頭注意力的動機是讓模型能夠同時關注來自不同表示子空間的資訊,從而捕捉更豐富的模式。例如,一個頭可能關注語法結構,另一個頭可能關注語義關係,還有一個頭可能關注共指消解。多頭的輸出被連接起來並進行線性變換,得到最終的輸出。

以下的程式碼展示了自注意力機制與多頭注意力的實作:

# 匯入深度學習所需的套件
import torch
import torch.nn as nn
import torch.nn.functional as F
import math

class ScaledDotProductAttention(nn.Module):
    """
    縮放點積注意力類別

    此類別實作了 Transformer 架構中使用的縮放點積注意力機制。
    它計算查詢與鍵之間的相關性,並使用這些相關性對值進行加權。
    """

    def __init__(self, dropout=0.1):
        """
        初始化縮放點積注意力

        參數:
            dropout: float - dropout 比例,用於正規化
        """
        super(ScaledDotProductAttention, self).__init__()
        # 初始化 dropout 層
        self.dropout = nn.Dropout(dropout)

    def forward(self, query, key, value, mask=None):
        """
        前向傳播計算注意力

        參數:
            query: Tensor - 查詢張量,形狀為 (batch, heads, seq_len, d_k)
            key: Tensor - 鍵張量,形狀為 (batch, heads, seq_len, d_k)
            value: Tensor - 值張量,形狀為 (batch, heads, seq_len, d_v)
            mask: Tensor - 可選的遮罩張量,用於屏蔽特定位置

        回傳:
            tuple - (注意力輸出, 注意力權重)
        """
        # 取得鍵向量的維度,用於縮放
        d_k = query.size(-1)

        # 計算注意力分數:Q 與 K 的點積除以根號 d_k
        # 這個縮放防止點積值過大導致 softmax 梯度消失
        scores = torch.matmul(query, key.transpose(-2, -1)) / math.sqrt(d_k)

        # 如果有遮罩,將被遮罩的位置設為極小值
        if mask is not None:
            scores = scores.masked_fill(mask == 0, -1e9)

        # 使用 softmax 將分數轉換為機率分布
        attention_weights = F.softmax(scores, dim=-1)

        # 應用 dropout 進行正規化
        attention_weights = self.dropout(attention_weights)

        # 使用注意力權重對值進行加權求和
        output = torch.matmul(attention_weights, value)

        return output, attention_weights

class MultiHeadAttention(nn.Module):
    """
    多頭注意力類別

    此類別實作了 Transformer 架構中的多頭注意力機制。
    它並行地執行多個縮放點積注意力,並將結果連接起來。
    """

    def __init__(self, d_model, num_heads, dropout=0.1):
        """
        初始化多頭注意力

        參數:
            d_model: int - 模型維度
            num_heads: int - 注意力頭數
            dropout: float - dropout 比例
        """
        super(MultiHeadAttention, self).__init__()

        # 確保模型維度可以被頭數整除
        assert d_model % num_heads == 0, "d_model 必須能被 num_heads 整除"

        # 計算每個頭的維度
        self.d_k = d_model // num_heads
        self.num_heads = num_heads

        # 定義查詢、鍵、值的線性投影層
        self.W_q = nn.Linear(d_model, d_model)
        self.W_k = nn.Linear(d_model, d_model)
        self.W_v = nn.Linear(d_model, d_model)

        # 定義輸出的線性投影層
        self.W_o = nn.Linear(d_model, d_model)

        # 初始化縮放點積注意力
        self.attention = ScaledDotProductAttention(dropout)

    def forward(self, query, key, value, mask=None):
        """
        前向傳播計算多頭注意力

        參數:
            query: Tensor - 查詢張量,形狀為 (batch, seq_len, d_model)
            key: Tensor - 鍵張量
            value: Tensor - 值張量
            mask: Tensor - 可選的遮罩張量

        回傳:
            Tensor - 多頭注意力輸出
        """
        batch_size = query.size(0)

        # 線性投影並分割成多個頭
        # 形狀變化: (batch, seq_len, d_model) -> (batch, seq_len, num_heads, d_k)
        # -> (batch, num_heads, seq_len, d_k)
        query = self.W_q(query).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2)
        key = self.W_k(key).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2)
        value = self.W_v(value).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2)

        # 計算注意力
        attn_output, attn_weights = self.attention(query, key, value, mask)

        # 連接所有頭的輸出
        # 形狀變化: (batch, num_heads, seq_len, d_k) -> (batch, seq_len, d_model)
        attn_output = attn_output.transpose(1, 2).contiguous().view(
            batch_size, -1, self.num_heads * self.d_k
        )

        # 最終的線性投影
        output = self.W_o(attn_output)

        return output

# 使用範例
batch_size = 2
seq_len = 10
d_model = 512
num_heads = 8

# 建立隨機輸入
x = torch.randn(batch_size, seq_len, d_model)

# 初始化多頭注意力層
mha = MultiHeadAttention(d_model, num_heads)

# 計算自注意力輸出
output = mha(x, x, x)

print(f"輸入形狀: {x.shape}")
print(f"輸出形狀: {output.shape}")

這段程式碼展示了縮放點積注意力與多頭注意力的完整實作。縮放點積注意力計算查詢與鍵之間的相關性分數,並使用這些分數對值進行加權求和。多頭注意力則將輸入投影到多個子空間,並行計算注意力,然後連接結果。這種設計使得模型能夠同時捕捉多種不同類型的依賴關係。

Transformer 架構的完整解析

Transformer 架構由 Google 研究團隊在 2017 年提出,它完全基於注意力機制,不使用任何循環或卷積結構。這種設計使得 Transformer 能夠完全平行化處理序列,大幅提升了訓練效率。Transformer 的成功標誌著自然語言處理從循環神經網路時代進入了注意力機制時代。

Transformer 採用編碼器-解碼器架構。編碼器將輸入序列轉換為一系列連續的表示,解碼器則根據這些表示生成輸出序列。原始論文中的 Transformer 使用六層編碼器和六層解碼器,但這個數字可以根據任務需求調整。

編碼器的每一層包含兩個子層:多頭自注意力層和位置前饋神經網路。多頭自注意力層使序列中的每個位置都能夠關注序列中的所有位置,從而捕捉全域依賴關係。位置前饋神經網路則對每個位置獨立地進行非線性變換,增加模型的表達能力。每個子層都使用殘差連接和層正規化,這有助於訓練深層網路。

解碼器的每一層包含三個子層:遮罩多頭自注意力層、編碼器-解碼器注意力層和位置前饋神經網路。遮罩多頭自注意力層使用遮罩來防止解碼器看到未來的位置,確保自迴歸生成的正確性。編碼器-解碼器注意力層讓解碼器能夠關注編碼器的輸出,建立輸入與輸出之間的對應關係。

由於 Transformer 不使用循環結構,它無法像循環神經網路那樣自然地捕捉位置資訊。因此,Transformer 需要額外的位置編碼來注入序列的位置資訊。原始論文使用了正弦和餘弦函數的組合來生成位置編碼,這種設計使得模型能夠學習到相對位置的關係。

以下的圖表展示了 Transformer 架構的整體結構:

@startuml
!define PLANTUML_FORMAT svg
!theme _none_

skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100

' Transformer 架構圖
package "Transformer 架構" {
    package "編碼器 (N層)" {
        rectangle "輸入嵌入" as input_emb
        rectangle "位置編碼" as pos_enc_e
        rectangle "多頭自注意力" as enc_attn
        rectangle "加法與層正規化" as enc_norm1
        rectangle "前饋神經網路" as enc_ffn
        rectangle "加法與層正規化" as enc_norm2

        input_emb --> pos_enc_e
        pos_enc_e --> enc_attn
        enc_attn --> enc_norm1
        enc_norm1 --> enc_ffn
        enc_ffn --> enc_norm2
    }

    package "解碼器 (N層)" {
        rectangle "輸出嵌入" as output_emb
        rectangle "位置編碼" as pos_enc_d
        rectangle "遮罩多頭自注意力" as dec_masked_attn
        rectangle "加法與層正規化" as dec_norm1
        rectangle "編碼器-解碼器注意力" as enc_dec_attn
        rectangle "加法與層正規化" as dec_norm2
        rectangle "前饋神經網路" as dec_ffn
        rectangle "加法與層正規化" as dec_norm3

        output_emb --> pos_enc_d
        pos_enc_d --> dec_masked_attn
        dec_masked_attn --> dec_norm1
        dec_norm1 --> enc_dec_attn
        enc_dec_attn --> dec_norm2
        dec_norm2 --> dec_ffn
        dec_ffn --> dec_norm3
    }

    rectangle "線性變換" as linear
    rectangle "Softmax" as softmax

    enc_norm2 --> enc_dec_attn
    dec_norm3 --> linear
    linear --> softmax
}

@enduml

這個圖表展示了 Transformer 的完整架構。編碼器透過多層自注意力和前饋神經網路處理輸入序列,解碼器則使用遮罩自注意力來處理已生成的序列,並透過編碼器-解碼器注意力來整合輸入資訊。殘差連接和層正規化確保了深層網路的穩定訓練。

以下的程式碼展示了 Transformer 編碼器層的實作:

# 匯入所需的套件
import torch
import torch.nn as nn
import math

class PositionalEncoding(nn.Module):
    """
    位置編碼類別

    此類別生成正弦和餘弦位置編碼,為 Transformer 提供位置資訊。
    位置編碼的設計使得模型能夠學習相對位置關係。
    """

    def __init__(self, d_model, max_len=5000, dropout=0.1):
        """
        初始化位置編碼

        參數:
            d_model: int - 模型維度
            max_len: int - 最大序列長度
            dropout: float - dropout 比例
        """
        super(PositionalEncoding, self).__init__()
        self.dropout = nn.Dropout(dropout)

        # 建立位置編碼矩陣
        pe = torch.zeros(max_len, d_model)

        # 生成位置索引
        position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)

        # 計算除數項
        div_term = torch.exp(
            torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model)
        )

        # 應用正弦和餘弦函數
        # 偶數維度使用正弦,奇數維度使用餘弦
        pe[:, 0::2] = torch.sin(position * div_term)
        pe[:, 1::2] = torch.cos(position * div_term)

        # 增加批次維度並註冊為緩衝區
        pe = pe.unsqueeze(0)
        self.register_buffer('pe', pe)

    def forward(self, x):
        """
        將位置編碼加到輸入上

        參數:
            x: Tensor - 輸入張量,形狀為 (batch, seq_len, d_model)

        回傳:
            Tensor - 加上位置編碼的張量
        """
        # 取得對應長度的位置編碼並加到輸入上
        x = x + self.pe[:, :x.size(1), :]
        return self.dropout(x)

class PositionwiseFeedForward(nn.Module):
    """
    位置前饋神經網路類別

    此類別實作 Transformer 中的前饋神經網路,
    對每個位置獨立地進行兩層線性變換。
    """

    def __init__(self, d_model, d_ff, dropout=0.1):
        """
        初始化前饋神經網路

        參數:
            d_model: int - 模型維度
            d_ff: int - 前饋層隱藏維度
            dropout: float - dropout 比例
        """
        super(PositionwiseFeedForward, self).__init__()

        # 第一層線性變換:d_model -> d_ff
        self.linear1 = nn.Linear(d_model, d_ff)

        # 第二層線性變換:d_ff -> d_model
        self.linear2 = nn.Linear(d_ff, d_model)

        # dropout 層
        self.dropout = nn.Dropout(dropout)

    def forward(self, x):
        """
        前向傳播

        參數:
            x: Tensor - 輸入張量

        回傳:
            Tensor - 前饋網路輸出
        """
        # 第一層線性變換後應用 ReLU 激活函數
        x = self.dropout(F.relu(self.linear1(x)))

        # 第二層線性變換
        x = self.linear2(x)

        return x

class TransformerEncoderLayer(nn.Module):
    """
    Transformer 編碼器層類別

    此類別實作單層 Transformer 編碼器,包含多頭自注意力
    和前饋神經網路,以及殘差連接和層正規化。
    """

    def __init__(self, d_model, num_heads, d_ff, dropout=0.1):
        """
        初始化編碼器層

        參數:
            d_model: int - 模型維度
            num_heads: int - 注意力頭數
            d_ff: int - 前饋層隱藏維度
            dropout: float - dropout 比例
        """
        super(TransformerEncoderLayer, self).__init__()

        # 多頭自注意力層
        self.self_attn = MultiHeadAttention(d_model, num_heads, dropout)

        # 前饋神經網路
        self.feed_forward = PositionwiseFeedForward(d_model, d_ff, dropout)

        # 層正規化
        self.norm1 = nn.LayerNorm(d_model)
        self.norm2 = nn.LayerNorm(d_model)

        # dropout 層
        self.dropout = nn.Dropout(dropout)

    def forward(self, x, mask=None):
        """
        前向傳播

        參數:
            x: Tensor - 輸入張量
            mask: Tensor - 可選的注意力遮罩

        回傳:
            Tensor - 編碼器層輸出
        """
        # 自注意力子層,包含殘差連接和層正規化
        attn_output = self.self_attn(x, x, x, mask)
        x = self.norm1(x + self.dropout(attn_output))

        # 前饋神經網路子層,包含殘差連接和層正規化
        ff_output = self.feed_forward(x)
        x = self.norm2(x + self.dropout(ff_output))

        return x

class TransformerEncoder(nn.Module):
    """
    Transformer 編碼器類別

    此類別堆疊多個編碼器層,形成完整的 Transformer 編碼器。
    """

    def __init__(self, vocab_size, d_model, num_heads, d_ff, num_layers, dropout=0.1):
        """
        初始化 Transformer 編碼器

        參數:
            vocab_size: int - 詞彙表大小
            d_model: int - 模型維度
            num_heads: int - 注意力頭數
            d_ff: int - 前饋層隱藏維度
            num_layers: int - 編碼器層數
            dropout: float - dropout 比例
        """
        super(TransformerEncoder, self).__init__()

        # 詞嵌入層
        self.embedding = nn.Embedding(vocab_size, d_model)

        # 位置編碼
        self.pos_encoding = PositionalEncoding(d_model, dropout=dropout)

        # 堆疊多個編碼器層
        self.layers = nn.ModuleList([
            TransformerEncoderLayer(d_model, num_heads, d_ff, dropout)
            for _ in range(num_layers)
        ])

        # 縮放因子
        self.scale = math.sqrt(d_model)

    def forward(self, x, mask=None):
        """
        前向傳播

        參數:
            x: Tensor - 輸入序列的索引
            mask: Tensor - 可選的注意力遮罩

        回傳:
            Tensor - 編碼器輸出
        """
        # 詞嵌入並縮放
        x = self.embedding(x) * self.scale

        # 加上位置編碼
        x = self.pos_encoding(x)

        # 通過所有編碼器層
        for layer in self.layers:
            x = layer(x, mask)

        return x

# 使用範例
vocab_size = 30000
d_model = 512
num_heads = 8
d_ff = 2048
num_layers = 6

# 初始化編碼器
encoder = TransformerEncoder(vocab_size, d_model, num_heads, d_ff, num_layers)

# 建立輸入序列
batch_size = 2
seq_len = 20
input_ids = torch.randint(0, vocab_size, (batch_size, seq_len))

# 編碼
output = encoder(input_ids)

print(f"輸入形狀: {input_ids.shape}")
print(f"編碼器輸出形狀: {output.shape}")

這段程式碼展示了 Transformer 編碼器的完整實作,包括位置編碼、前饋神經網路和編碼器層。位置編碼使用正弦和餘弦函數來生成,前饋神經網路對每個位置獨立地進行變換,而編碼器層則組合了自注意力和前饋網路。這種模組化的設計使得程式碼易於理解和擴展。

預訓練語言模型的興起

Transformer 架構的成功為大規模預訓練語言模型的發展奠定了基礎。2018 年可以被視為自然語言處理的「ImageNet 時刻」,多個預訓練語言模型在這一年被提出,並在各種自然語言處理任務上取得了突破性的成果。這些模型透過在大規模無標註文字上進行預訓練,學習到通用的語言表示,然後可以在特定任務上進行微調。

BERT 是 Google 在 2018 年提出的預訓練語言模型,它使用 Transformer 的編碼器架構。BERT 的創新之處在於其雙向的預訓練目標。傳統的語言模型是單向的,只根據左側或右側的上下文來預測詞彙;而 BERT 使用遮罩語言模型任務,隨機遮罩輸入中的一部分詞彙,然後預測這些被遮罩的詞彙。這種雙向的上下文建模使得 BERT 能夠學習到更豐富的語言表示。

BERT 還使用了下一句預測任務,學習句子之間的關係。在這個任務中,模型需要判斷兩個句子是否是連續的。這個任務使得 BERT 能夠理解段落級別的語義,對於問答和自然語言推論等任務特別有幫助。

GPT 系列是 OpenAI 提出的另一個重要的預訓練語言模型家族。與 BERT 不同,GPT 使用 Transformer 的解碼器架構,並採用自迴歸的語言模型目標,即根據左側的上下文預測下一個詞彙。這種設計使得 GPT 特別適合文字生成任務。從 GPT-1 到 GPT-3,模型的規模不斷擴大,GPT-3 的參數量達到了 1750 億,展現出驚人的語言理解和生成能力。

ELMo 是較早提出的預訓練模型,它使用雙向長短期記憶網路來生成上下文相關的詞表示。ELMo 的貢獻在於證明了預訓練的詞表示可以顯著提升下游任務的效能,並且展示了上下文相關表示的優勢。一個詞在不同的上下文中可以有不同的表示,這對於處理多義詞特別有幫助。

以下的程式碼展示了如何使用預訓練的 BERT 模型:

# 匯入 Hugging Face Transformers 函式庫
# 注意:實際執行需要安裝 transformers 套件
# pip install transformers

import torch
import torch.nn as nn

class BertForClassification:
    """
    BERT 文字分類別模型類別

    此類別展示如何使用預訓練的 BERT 模型進行文字分類別任務。
    它載入預訓練模型,並在其上添加分類別層。
    """

    def __init__(self, num_classes, model_name='bert-base-uncased'):
        """
        初始化 BERT 分類別模型

        參數:
            num_classes: int - 分類別數
            model_name: str - 預訓練模型名稱
        """
        # 以下是使用 Hugging Face Transformers 的示範程式碼
        # 實際執行需要安裝並匯入 transformers 套件

        # from transformers import BertModel, BertTokenizer

        # 載入預訓練的 BERT 模型和分詞器
        # self.tokenizer = BertTokenizer.from_pretrained(model_name)
        # self.bert = BertModel.from_pretrained(model_name)

        # 定義分類別層
        # self.classifier = nn.Linear(768, num_classes)  # BERT-base 的隱藏維度是 768

        self.num_classes = num_classes
        self.model_name = model_name

        print(f"BERT 分類別模型初始化完成")
        print(f"分類別數: {num_classes}")
        print(f"預訓練模型: {model_name}")

    def preprocess(self, texts, max_length=128):
        """
        預處理文字資料

        參數:
            texts: list - 文字列表
            max_length: int - 最大序列長度

        回傳:
            dict - 分詞後的輸入
        """
        # 使用分詞器處理文字
        # inputs = self.tokenizer(
        #     texts,
        #     padding=True,
        #     truncation=True,
        #     max_length=max_length,
        #     return_tensors='pt'
        # )

        print(f"預處理 {len(texts)} 筆文字資料")
        print(f"最大長度: {max_length}")

        # 返回模擬的輸入
        return {'input_ids': None, 'attention_mask': None}

    def classify(self, texts):
        """
        對文字進行分類別

        參數:
            texts: list - 文字列表

        回傳:
            Tensor - 分類別結果
        """
        # 預處理
        # inputs = self.preprocess(texts)

        # 取得 BERT 輸出
        # with torch.no_grad():
        #     outputs = self.bert(**inputs)
        #     pooled_output = outputs.pooler_output

        # 分類別
        # logits = self.classifier(pooled_output)
        # predictions = torch.argmax(logits, dim=-1)

        print(f"分類別 {len(texts)} 筆文字")

        # 返回模擬的預測結果
        return None

    def fine_tune(self, train_data, epochs=3, learning_rate=2e-5):
        """
        微調模型

        參數:
            train_data: Dataset - 訓練資料
            epochs: int - 訓練週期數
            learning_rate: float - 學習率
        """
        # 微調步驟說明:
        # 1. 凍結或不凍結 BERT 的部分層
        # 2. 使用較小的學習率(通常為 2e-5 到 5e-5)
        # 3. 訓練較少的週期(通常為 2-4 個週期)

        print(f"開始微調模型")
        print(f"訓練週期: {epochs}")
        print(f"學習率: {learning_rate}")

        # optimizer = torch.optim.AdamW(self.parameters(), lr=learning_rate)

        # for epoch in range(epochs):
        #     for batch in train_data:
        #         # 前向傳播
        #         # 計算損失
        #         # 反向傳播
        #         # 更新參數
        #         pass

        print("微調完成")

# BERT 模型的預訓練任務說明
bert_pretraining_tasks = """
BERT 預訓練任務:

1. 遮罩語言模型 (Masked Language Model, MLM):
   - 隨機遮罩輸入中 15% 的詞彙
   - 其中 80% 替換為 [MASK]
   - 10% 替換為隨機詞彙
   - 10% 保持不變
   - 模型預測被遮罩的詞彙

2. 下一句預測 (Next Sentence Prediction, NSP):
   - 給定句子 A,判斷句子 B 是否是 A 的下一句
   - 50% 的情況 B 是真正的下一句
   - 50% 的情況 B 是隨機句子
   - 幫助模型理解句子間的關係
"""

print(bert_pretraining_tasks)

# GPT 模型的特點說明
gpt_characteristics = """
GPT 模型特點:

1. 架構:
   - 使用 Transformer 解碼器
   - 自迴歸語言模型目標

2. 預訓練:
   - 根據左側上下文預測下一個詞彙
   - 單向注意力(只能看到左側)

3. 應用:
   - 文字生成
   - 對話系統
   - 程式碼生成

4. 規模演進:
   - GPT-1: 1.17 億參數
   - GPT-2: 15 億參數
   - GPT-3: 1750 億參數
"""

print(gpt_characteristics)

這段程式碼展示了如何使用預訓練的 BERT 模型進行文字分類別。BERT 模型透過預訓練學習到通用的語言表示,然後可以在特定任務上進行微調。微調時通常使用較小的學習率和較少的訓練週期,以避免過度改變預訓練的權重。

模型效能最佳化與應用實踐

在實際應用中,預訓練語言模型面臨著推論延遲和資源消耗的挑戰。BERT-base 有 1.1 億參數,BERT-large 有 3.4 億參數,而 GPT-3 更是高達 1750 億參數。這些大型模型需要大量的計算資源和記憶體,在資源受限的環境中難以直接部署。

知識蒸餾是一種常用的模型壓縮技術。它訓練一個較小的「學生」模型來模仿較大的「教師」模型的行為。學生模型不僅學習正確的標籤,還學習教師模型的輸出分布,從而繼承教師模型的部分知識。DistilBERT 是知識蒸餾的成功案例,它比 BERT-base 小 40%,快 60%,但保留了 97% 的語言理解能力。

模型剪枝是另一種壓縮技術,它移除模型中不重要的連接或神經元。研究發現,預訓練語言模型中存在大量的冗餘,移除這些冗餘對效能的影響很小。剪枝可以在訓練後進行(後剪枝),也可以在訓練過程中進行(動態剪枝)。

量化將模型的權重和激活從 32 位浮點數轉換為較低精度的表示,如 8 位整數或 16 位浮點數。這不僅減少了模型的儲存空間,還加速了推論,因為低精度運算更快。然而,量化可能會導致精度損失,需要仔細調整量化策略。

在應用層面,預訓練語言模型已經被廣泛應用於各種自然語言處理任務。問答系統使用 BERT 來理解問題和段落,並找出答案的位置。情感分析使用預訓練模型來判斷文字的情感傾向。命名實體辨識使用這些模型來識別文字中的人名、地名、組織名等實體。機器翻譯則使用 Transformer 架構來實現高品質的語言轉換。

結語

注意力機制與 Transformer 架構的出現徹底改變了自然語言處理領域。它們解決了傳統循環神經網路在處理長序列時的瓶頸,實現了真正的平行化運算,並為大規模預訓練語言模型的發展奠定了基礎。BERT、GPT 等預訓練模型透過在大規模資料上進行預訓練,學習到通用的語言表示,然後可以在各種下游任務上進行微調,取得優異的效能。

這些技術的影響已經超越了自然語言處理領域,擴展到電腦視覺、語音處理等其他領域。視覺 Transformer 將 Transformer 架構應用於影像分類別,取得了與卷積神經網路相當甚至更好的結果。跨模態的預訓練模型則將文字和影像統一到同一個表示空間中,實現了跨模態的理解與生成。

展望未來,模型的規模可能會繼續增長,但效率和可解釋性將成為更重要的研究方向。如何在保持效能的同時減少計算成本,如何讓模型的決策更加透明和可信賴,這些問題將是未來研究的重點。同時,預訓練模型的社會影響也需要認真對待,包括偏見、隱私和濫用等問題。

對於實務工作者而言,理解注意力機制和 Transformer 架構的原理,掌握預訓練模型的使用和微調方法,是在當代自然語言處理領域工作的基本要求。這些技術不僅是學術研究的前沿,也是產業應用的基礎設施。持續關注技術的演進,並結合實際應用場景進行調適,才能真正發揮這些強大工具的潛力。