Subword tokenization 技術有效平衡了字元級和詞級標記化的優缺點,解決了傳統方法在處理新詞和罕見詞時的困境。BPE 等演算法透過分析大量文字資料,將詞彙分解成子詞,有效減少詞表大小並保留詞彙間的語義關係。嵌入式表示技術則將文字轉換為數值向量,使機器學習模型能夠理解和處理文字資料。從早期的 Word2Vec 到現代的 Transformer 模型,嵌入技術不斷演進,提升了模型對詞語含義和上下文資訊的理解能力。隨著轉移學習的興起,預訓練模型和嵌入層的結合,為 NLP 任務提供了更有效的解決方案。

Subword Tokenizers 的原理與實作

Subword tokenizers 能夠有效地在字元、子詞和詞彙之間找到平衡,從而實作高效的文字標記化。Subword tokenization 演算法並非固定不變,而是在實際標記化之前需要進行「訓練」階段,以找到字元級和詞級標記化之間的最佳平衡。

為何需要 Subword Tokenization

傳統的詞級標記化方法在面對新詞或罕見詞時會遇到困難,因為它們可能未曾在訓練資料中出現。另一方面,字元級標記化雖然能夠處理任何字元序列,但可能會導致序列過長,從而增加模型的複雜度。

Subword tokenization 結合了兩者的優點,將詞彙分解為子詞(subwords),這些子詞是詞彙中的常見片段。例如,單詞 “swimming” 可以被分解為 [“swim”, “##m##”, “##ing”],其中 “swim”、"##m##" 和 “##ing” 都是子詞。

內容解密:

  1. 子詞的定義:子詞是詞彙中的一部分,它們可以是字首、字尾或詞根。
  2. 子詞的好處:子詞能夠減少詞表的大小,同時保留詞彙之間的語義關係。
  3. 訓練過程:Subword tokenization 演算法透過分析大量文字資料,找出重複出現的字元序列,並將其合併為子詞。

訓練 Subword Tokenizer

要訓練一個 subword tokenizer,需要準備大量的文字資料,並使用特定的演算法來找出最佳的子詞分割。常見的演算法包括 Byte Pair Encoding (BPE)、WordPiece 和 SentencePiece。

程式碼範例:

from tokenizers import Tokenizer
from tokenizers.models import BPE
from tokenizers.trainers import BpeTrainer

# 初始化 tokenizer
tokenizer = Tokenizer(BPE(unk_token="[UNK]"))

# 初始化 trainer
trainer = BpeTrainer(special_tokens=["[UNK]", "[CLS]", "[SEP]", "[PAD]", "[MASK]"])

# 指定訓練資料
files = [f"data/wikitext-103-raw/wiki.{split}.raw" for split in ["test", "train", "valid"]]

# 訓練 tokenizer
tokenizer.train(files, trainer)

內容解密:

  1. 初始化 tokenizer:使用 BPE 演算法初始化 tokenizer,並指定未知詞元(unk_token)為 “[UNK]"。
  2. 初始化 trainer:使用 BpeTrainer 初始化 trainer,並指定特殊詞元(special_tokens)。
  3. 指定訓練資料:指定用於訓練 tokenizer 的文字檔案。
  4. 訓練 tokenizer:使用指定的訓練資料和 trainer 來訓練 tokenizer。

Subword Tokenization 的優勢

Subword tokenization 能夠有效地減少未知詞的數量,同時保留詞彙之間的語義關係。此外,它還能夠減少詞表的大小,從而降低模型的複雜度。

嵌入式表示(Embeddings):機器如何「理解」文字

在我們探討自然語言處理(NLP)的底層架構旅程的第一階段中,我們已經瞭解如何使用斷詞器(tokenizers)將文字資料轉換成更適合神經網路處理的格式。接下來的拼圖是嵌入層(embedding layer)。如果斷詞器是模型用來讀取文字的工具,那麼嵌入層就是它們用來理解文字的機制。

理解與讀取文字

長期以來,機器能夠以數位方式表示字元(以及延伸的字、句子等)。使用二進位編碼方案進行語言和通訊的想法至少可以追溯到19世紀電報的發明。

最早的語言編碼形式之一是摩斯密碼。在這個系統中,二進位訊號,如開啟和關閉燈光,或傳送一系列長短音訊脈衝,用於表示不同的字元。如果兩個人有二進位通訊的方式,並就二進位序列的含義達成一致,他們就可以可靠地使用摩斯密碼進行通訊。這是將自然人類語言嵌入機器可以某種程度上使用的二進位格式的最早和最簡單的方法之一。注意到摩斯密碼,如圖5-1所示,只使用點和破折號——類別似於現代數位通訊中使用的1和0。

自那時以來,我們在編碼文字/語言的能力方面取得了許多改進。這些改進包括音訊錄音;更新的編碼演算法,如ASCII,它使用整數來表示文字(如圖5-2所示);以及Unicode,它允許我們使用來自許多不同指令碼的字元。

除了偶爾出現的資料完整性、損壞等實際問題外,我們已經能夠可靠地儲存、傳送和存取文字資料很長一段時間了。然而,僅僅將文字表示為字元,只能帶我們走到這一步。

為何需要嵌入式表示

程式碼範例:簡單的嵌入式表示

import numpy as np

# 定義一個簡單的嵌入層
class SimpleEmbedding:
    def __init__(self, vocab_size, embedding_dim):
        self.embedding = np.random.rand(vocab_size, embedding_dim)

    def get_embedding(self, token_id):
        return self.embedding[token_id]

# 使用範例
vocab_size = 1000
embedding_dim = 128
embedding_layer = SimpleEmbedding(vocab_size, embedding_dim)
token_id = 42
embedding_vector = embedding_layer.get_embedding(token_id)
print(f"Token ID {token_id} 的嵌入向量:{embedding_vector}")

內容解密:

  1. 我們定義了一個名為SimpleEmbedding的類別,用於建立一個簡單的嵌入層。
  2. __init__方法中,我們初始化了一個隨機的嵌入矩陣,大小為vocab_size x embedding_dim
  3. get_embedding方法根據給定的token_id傳回對應的嵌入向量。
  4. 在使用範例中,我們建立了一個具有1000個詞彙和128維嵌入向量的嵌入層,並檢索了ID為42的標記的嵌入向量。

探討嵌入式表示

嵌入式表示是將文字或其他型別的資料轉換成數值向量,使得機器學習模型能夠理解和處理這些資料。在NLP中,嵌入式表示通常用於將單詞或標記轉換成密集向量,以便於神經網路進行處理。

為什麼需要嵌入式表示?

簡單地將文字表示為索引或one-hot向量並不足以讓模型理解文字的語義。嵌入式表示透過將文字對映到高維空間中的向量,能夠捕捉單詞之間的語義關係和上下文資訊。

嵌入式表示的工作原理

嵌入式表示通常透過訓練神經網路來學習得到。在訓練過程中,模型會根據上下文資訊調整單詞的向量表示,使得具有相似上下文的單詞具有相似的向量表示。

理解文字:如何讓機器「明白」文字的意義

在數位時代,電腦可以輕易地儲存和重複顯示大量的文字資訊。例如,無論是多麼複雜的單詞,如“supercalifragilisticexpialidocious”,電腦都可以完美地重複顯示數百萬次。然而,在很長一段時間裡,電腦並不具備理解文字背後所蘊含的意義和上下文的能力。

人類與電腦對文字的理解差異

人類在閱讀文字時,不僅僅是接收原始的資訊,還會根據過去累積的知識對所讀內容進行理解和詮釋。這種能力使得語言溝通變得有意義且高效。對於電腦來說,要達到類別似人類的理解能力,需要將文字以新的方式編碼成數字,這種編碼方式應該強調文字的意義而非原始字元。

將文字轉換為數字的挑戰

將文字轉換為數字是一項具有挑戰性的任務。如何用數字來描述一個詞語背後的意義、情感和聯想?例如,“奧運會”這個詞,不僅代表一項體育盛事,還蘊含著激情、歷史和榮譽等豐富的內涵。不同的個體可能會對同一個詞語有不同的理解和詮釋,因此很難達成共識。

利用數字屬性表示文字

為瞭解決這個問題,可以透過賦予文字多個數字屬性來描述其特性。例如,使用社交媒體關注者數量來衡量名聲,使用智商來衡量智力,使用財富來衡量成功。雖然這些衡量標準並不完美,但它們是具體的,因此是有用的。類別似地,可以用多個數字屬性來描述一個詞語,如使用某個量表來評估某種食物的辣度,或是使用某個指標來評估一個人的收入狀況。

詞向量(Word Vectors)的概念

為了讓電腦能夠理解文字的意義,可以利用機器學習的方法自動為詞語賦予數字屬性,也就是所謂的詞向量(Word Vectors)。詞向量的基本思想是將每個詞語表示為一個多維向量,向量中的每個維度代表詞語的一個屬性或特徵。雖然手動為每個詞語定義這些屬性是非常困難和主觀的,但可以利用機器學習演算法,透過分析大量文字資料,自動學習出詞語的向量表示。

詞向量的實用價值

詞向量提供了一種將文字轉換為數字的有效方法,使得電腦能夠更好地理解和處理自然語言。雖然這種表示方法不一定是完美的,但它對於許多自然語言處理任務來說是非常有用的。透過利用詞向量,可以開發出更為先進的自然語言處理模型,從而在諸如文字分類別、情感分析和機器翻譯等領域取得更好的效果。

詞向量的運作原理

要建立詞向量,首先需要了解詞語在上下文中的使用模式。電腦可以透過分析大量文字資料,學習到詞語之間的相似性和關聯性。例如,透過觀察哪些詞語經常一起出現,或者在相似的上下文中使用,可以推斷出這些詞語之間可能存在某種語義上的聯絡。

利用上下文學習詞向量

利用機器學習演算法,可以自動地從大量文字資料中學習出詞語的向量表示。這種方法不需要手動定義詞語的屬性,而是透過分析詞語在不同上下文中的使用模式來自動學習出詞向量的表示。這種方法使得電腦能夠更好地理解詞語之間的語義關係,從而提高自然語言處理任務的效果。

# 示例:使用Word2Vec模型訓練詞向量
from gensim.models import Word2Vec

# 假設sentences是一個包含多個句子的列表,每個句子又是單詞的列表
sentences = [["我", "愛", "吃", "蘋果"], ["蘋果", "是一種", "水果"]]

# 訓練Word2Vec模型
model = Word2Vec(sentences, vector_size=100, window=5, min_count=1)

# 取得詞語“蘋果”的向量表示
vector = model.wv['蘋果']

print(vector)

#### 內容解密:
這段程式碼展示瞭如何使用Word2Vec模型來訓練詞向量首先我們準備了一個包含多個句子的列表每個句子都是單詞的列表然後我們使用Gensim函式庫中的Word2Vec函式來訓練模型其中`vector_size=100`表示每個詞語將被表示為一個100維的向量,`window=5`表示在訓練時考慮到前後5個詞語的上下文,`min_count=1`表示即使某個詞語只出現一次也會為其生成向量最後我們可以透過模型的`wv`屬性來取得某個特定詞語蘋果”)的向量表示

詞向量:機器如何「理解」詞語

在自然語言處理領域,如何讓機器理解詞語的含義一直是個重要的課題。我們假設可以將詞語的含義編碼為一系列數字,這些數字描述了詞語的各種屬性。現在,我們需要找到一種方法將詞語對映到它們的數字屬性上。由於人工處理這個任務需要耗費大量時間,因此我們需要使用機器來完成這項工作。

陣列與向量

嚴格來說,陣列和向量並不是同一件事。陣列是一種資料結構,可以按照特定方式排列資料。而向量則是一種抽象的數學物件,具有精確的定義。在純數學領域中,這種區別很重要。但在實際應用中,向量和一維陣列基本上可以視為同一件事。在本文中,我們將交替使用這兩個術語。

在機器學習和資料處理中,我們經常使用單熱向量(one-hot vector)。單熱向量是一種陣列,其中一個元素為1,其他元素均為0。下圖展示了一個單熱向量的例子。

單熱向量

第$n$個詞在字典中的單熱表示是第$n$個元素為1,其餘元素均為0的向量。我們將這個單熱向量記為$o_n$。假設「orange」是字典中的第1152個詞,那麼對應的單熱向量就是$o_{1152}$。我們可以透過以下程式碼告訴電腦:

o = torch.zeros(20000)
o[1152] = 1

向量的維度等於字典的大小。例如,如果字典中有20,000個詞,那麼單熱詞向量的維度就是20,000。這是因為每個詞在字典中佔據一個位置,而陣列中只能有一個非零元素。

內容解密:

  1. torch.zeros(20000):建立一個大小為20,000的零向量,代表字典中所有詞的初始狀態。
  2. o[1152] = 1:將第1152個元素設為1,代表「orange」這個詞。

詞嵌入

單熱向量的組成部分並不具有語義意義。我們需要將單熱向量對映到另一個具有語義意義的向量。矩陣乘法是實作這一轉換的好工具。

矩陣的作用是將某個向量$o_n$對映到另一個向量$e_n$。$e_n$是一個新的向量,對應於字典中的第$n$個詞。與$o_n$不同,$e_n$的組成部分具有一定的語義意義,例如味道或與其他詞的押韻程度。

用方程式表示就是:

$e_n = E \cdot o_n$

或者用程式碼表示:

E = torch.nn.Embedding(num_embeddings=20000, embedding_dim=300)
e = E(o)

在這裡,我們將20,000維的單熱向量嵌入到一個只有300個組成部分的較小向量中。與單熱向量不同,嵌入向量的組成部分可以是任意浮點數。

內容解密:

  1. torch.nn.Embedding(num_embeddings=20000, embedding_dim=300):建立一個嵌入層,將20,000維的單熱向量對映到300維的嵌入向量。
  2. e = E(o):將單熱向量$o$傳入嵌入層,得到對應的嵌入向量$e$。

值得注意的是,在實作嵌入矩陣乘法時,實際上並不是進行真正的矩陣乘法。由於$o_n$是單熱向量,大部分元素都是0,因此進行矩陣乘法時會有很多無用的乘法運算。相反,我們可以直接從嵌入矩陣中取出對應的列,這樣就大大簡化了運算。

Word2Vec

幾年前,我們透過Word2Vec、GloVe等演算法生成了詞嵌入矩陣。這些演算法會遍歷文字,進行一些分析,然後為詞彙表中的每個詞生成一個靜態向量。

然而,這些方法現在已經不再流行,因為有更新、更高效的系統出現了。例如Flair和fastText等函式庫提供了更好的解決方案。

此外,Word2Vec的一個主要缺點是它沒有考慮上下文資訊。這是應該避免使用它的最重要原因之一。

嵌入技術的演進:從Word2Vec到現代轉移學習

在自然語言處理(NLP)領域,詞嵌入(Word Embeddings)是一種將文字轉換為機器可理解的向量表示的技術。早期的Word2Vec技術曾經是詞嵌入的主流方法,但隨著技術的發展,現代的轉移學習(Transfer Learning)和根據Transformer的模型已經成為主流。

詞嵌入的問題:上下文理解的侷限

考慮以下兩個句子: 「我要去搶銀行。」 「嘿,那是一條很酷的河岸。」

很明顯,「bank」這個詞在不同的上下文中有不同的含義。使用Word2Vec時,這兩個「bank」會被對映到相同的向量表示,這意味著模型會將它們視為相同的事物,即使它們的含義不同。

解決方案:上下文相關的嵌入表示

解決這個問題的方法是生成不僅與目標詞相關,還與其上下文(句子、段落或檔案)相關的嵌入表示。這種方法能夠更好地捕捉詞語在不同上下文中的細微差別。

從Word2Vec到Token2Vec

嚴格來說,Word2Vec應該被稱為「Token2Vec」,因為它實際上使用的是標記(tokens)而非單純的詞語。Word2Vec能夠為未完全形成的詞語生成嵌入表示,這意味著它可以處理子詞(subword)標記。

子詞標記化

子詞標記化技術,如位元組對編碼(Byte-Pair Encoding),能夠構建更為精細的詞彙表,其中包含代表一般字尾的特殊標記,如「xxer」和「xxest」。這樣,模型可以透過獨立的向量來理解具有相同詞根但不同字尾的詞語,如「fast」、「faster」和「fastest」。

現代嵌入技術:轉移學習時代

在轉移學習時代,我們通常不需要從網路下載預訓練的嵌入矩陣。相反,我們可以使用預訓練的Transformer模型,它們自帶嵌入層,能夠根據預訓練資料集生成向量表示。在微調模型的過程中,嵌入層也會被更新,從而捕捉特定資料集的細微差別。

實踐中的嵌入技術

理論上講,現代的嵌入技術應該比傳統的Word2Vec更為有效。但是,我們需要透過實際的實驗來驗證這一點。在實踐中,使用轉移學習和根據Transformer的模型,可以獲得更好的結果。

程式碼實踐
# 使用Hugging Face Transformers函式庫載入預訓練模型和標記器
from transformers import BertTokenizer, BertModel

# 載入預訓練的BERT模型和標記器
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
model = BertModel.from_pretrained('bert-base-uncased')

# 對輸入文字進行標記化和數值化
inputs = tokenizer("Hello, world!", return_tensors="pt")

# 取得嵌入表示
outputs = model(**inputs)

# 輸出最後一層隱藏狀態的嵌入表示
last_hidden_state = outputs.last_hidden_state

內容解密:

  1. 程式碼功能:展示如何使用Hugging Face Transformers函式庫載入預訓練的BERT模型和標記器,並對輸入文字進行標記化和數值化,最終取得嵌入表示。
  2. BertTokenizerBertModel:分別用於文字標記化和取得嵌入表示。
  3. return_tensors="pt":指定傳回PyTorch張量。
  4. model(**inputs):將輸入傳遞給模型,取得輸出。
  5. outputs.last_hidden_state:取得最後一層隱藏狀態的嵌入表示,這些表示可以用於下游任務。

這種實踐方法結合了預訓練模型的強大能力和特定資料集的細微差別,能夠在許多NLP任務中取得優異的結果。