使用 Hugging Face 的 Transformers 庫來實現 BERT 模型的訓練微調(fine-tuning),以進行垃圾郵件的辨識分類。

Weibert Weiberson 崴寶
17 min readMay 14, 2024

--

Using Hugging Face’s Transformers to implement fine-tuning of the BERT model for classifying spam emails.

SMSSpamCollection >> 訓練會用到的Dataset
SMSSpamCollection_test >> 測試會用到的Dataset

SMSSpamCollection_bert.py >> 訓練檔案
SMSSpamCollection_bert_predict.py >> 預測檔案

Github Repository — Bert_HugginFace_Train_Predict_SpamEmails

目錄
指令介紹
Docker
訓練模型Training
預測模型Prediction
預測結果

指令介紹

python SMSSpamCollection_bert.py #訓練指令
python SMSSpamCollection_bert_predict.py #預測指令

Docker

weitsung50110/bert_huggingface >> 此為我安裝好的 Docker image 環境。

docker pull weitsung50110/bert_huggingface:1.0

訓練模型Training:

資料準備:

SMS Spam Collection Dataset 可以在kaggle下載,我也有放在我的github repo當中,SMSSpamCollection 歡迎大家取用。

https://www.kaggle.com/datasets/uciml/sms-spam-collection-dataset

模型準備:

使用 BERT 模型的預訓練版本 “bert-base-uncased”,透過 BertForSequenceClassification 來建立文本分類模型。
初始化 tokenizer,將文本轉換成模型可接受的輸入格式。

| Model                                       | #params | Language  |
|---------------------------------------------|---------|-----------|
| bert-base-uncased | 110M | English |
| bert-large-uncased | 340M | English |
| bert-base-cased | 110M | English |
| bert-large-cased | 340M | English |
| bert-base-chinese | 110M | Chinese |
| bert-base-multilingual-cased | 110M | Multiple |
| bert-large-uncased-whole-word-masking | 340M | English |
| bert-large-cased-whole-word-masking | 340M | English |

根據Hugging Face官方文檔中,可以看到有非常多模型可以選擇,而本研究是使用google-bert/bert-base-uncased

Hugging Face BERT community : https://huggingface.co/google-bert

使用 tokenizer 轉換文字:

定義了一個自訂的 Dataset 類別,用來建立訓練集和驗證集的 Dataset。
使用 tokenizer 將文字轉換為模型可接受的輸入格式。

# 初始化 tokenizer
tokenizer = BertTokenizer.from_pretrained(model_name)

# 初始化模型
model = BertForSequenceClassification.from_pretrained(model_name, num_labels=2)

# 將資料集分成訓練集和驗證集
X = list(df['message'])
y = list(df['label'])
X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2)

# 使用 tokenizer 將文字轉換為模型可接受的輸入格式
X_train_tokenized = tokenizer(X_train, padding=True, truncation=True, max_length=512)
X_val_tokenized = tokenizer(X_val, padding=True, truncation=True, max_length=512)

# Create torch dataset # 定義自訂的 Dataset 類別
class Dataset(torch.utils.data.Dataset):
def __init__(self, encodings, labels=None):
self.encodings = encodings
self.labels = labels

def __getitem__(self, idx):
item = {key: torch.tensor(val[idx]) for key, val in self.encodings.items()}
if self.labels:
item["labels"] = torch.tensor(self.labels[idx])
return item

def __len__(self):
return len(self.encodings["input_ids"])

# 建立訓練集和驗證集的 Dataset
train_dataset = Dataset(X_train_tokenized, y_train)
val_dataset = Dataset(X_val_tokenized, y_val)
  1. 初始化 tokenizer 和模型:使用 Hugging Face 的 Transformers 庫中的 BertTokenizerBertForSequenceClassification 類別,從預訓練的 BERT 模型中初始化 tokenizer 和分類模型。model_name 指定了要使用的預訓練模型,這裡使用了 bert-base-uncased,這是一個英文模型。

在後面預測的部分,tokenizer有更詳細教學

  1. 準備資料集:從讀取的資料中取出文本和標籤,然後使用 train_test_split 函式將資料集分成訓練集和驗證集,其中設置了驗證集佔總資料集的 20%。
  2. 使用 tokenizer 轉換文字:將訓練集和驗證集的文本資料使用 tokenizer 轉換成模型可接受的輸入格式。這包括將文本轉換成 token IDs,並進行 padding 和截斷,確保每個輸入序列的長度相同,這裡設置了最大長度為 512。
  3. 建立自訂的 Dataset 類別:定義了一個自訂的 Dataset 類別,用來封裝資料集,使其可以被 PyTorch 的 DataLoader 使用。該類別接受 tokenized 的資料和對應的標籤,並在 __getitem__ 方法中將其轉換成 PyTorch 張量格式。
  4. 建立訓練集和驗證集的 Dataset 物件:將 tokenized 的訓練集和驗證集資料以及對應的標籤傳入自訂的 Dataset 類別,建立訓練集和驗證集的 Dataset 物件。

這樣做的目的是為了準備好訓練所需的資料格式,使其可以被 PyTorch 模型接受並用於訓練。

訓練模型:

定義了計算評估指標的函式,包括準確率(accuracy)、召回率(recall)、精確率(precision)、F1 分數(F1 score)。

def compute_metrics(p):
pred, labels = p
pred = np.argmax(pred, axis=1)

accuracy = accuracy_score(y_true=labels, y_pred=pred)
recall = recall_score(y_true=labels, y_pred=pred)
precision = precision_score(y_true=labels, y_pred=pred)
f1 = f1_score(y_true=labels, y_pred=pred)

return {"accuracy": accuracy, "precision": precision, "recall": recall, "f1": f1}

初始化了 Trainer,設定了訓練相關的參數,包括訓練集、驗證集、計算評估指標的函式等。

使用 trainer.train() 開始訓練模型,同時設置了提前停止訓練的機制,以防止過度擬合。

  • output_dir: 指定訓練過程中模型和日誌等輸出的目錄。
  • evaluation_strategy: 指定評估策略,這裡設置為 "steps",表示基於步驟數進行評估。
  • eval_steps: 指定在訓練過程中每隔多少步進行一次評估。
  • per_device_train_batch_size: 每個訓練裝置(device)的批次大小。
  • per_device_eval_batch_size: 每個評估裝置的批次大小。
  • num_train_epochs: 訓練的總時代數(epochs)。
  • seed: 隨機種子,用於重現性。
  • load_best_model_at_end: 是否在訓練結束時載入最佳模型。
  • logging_steps: 每隔多少步輸出一次訓練日誌。
  • report_to: 指定將訓練進度報告到哪個工具,這裡設置為 "tensorboard",表示將訓練進度報告到 TensorBoard。
args = TrainingArguments(
output_dir="output",
evaluation_strategy="steps",
eval_steps=500,
per_device_train_batch_size=8,
per_device_eval_batch_size=8,
num_train_epochs=3,
seed=0,
load_best_model_at_end=True,
logging_steps=10, # 每 10 個步驟輸出一次訓練日誌
report_to="tensorboard",
)

seed 參數是用於設置隨機種子的,它的作用是確保在訓練過程中的隨機操作(例如參數初始化、數據順序洗牌等)是可重現的。通常情況下,當我們希望每次運行訓練過程時得到相同的結果時,就會設置隨機種子。這對於實驗的可重現性和結果的一致性非常重要。

load_best_model_at_end 參數用於控制是否在訓練結束時載入最佳的模型。在訓練過程中,模型的性能可能會隨著時間逐漸提升或者下降,因此通常會在每個評估步驟或者一定間隔之後進行模型性能的評估,並保存當前最佳的模型。當訓練結束時,這個參數可以確保載入最佳的模型,而不是最後一個模型,這樣可以確保我們得到的是在驗證集上性能最好的模型。

預測模型Prediction

文本被tokenized後會變成什麼模樣 ?

X_test_tokenized = tokenizer(X_test, padding=True, truncation=True, max_length=512)
print(X_test_tokenized)

X_test_tokenized 裡面包含 Tokenization 的結果,每個樣本都包含了三個部分:input_idstoken_type_idsattention_mask

{'input_ids': [[101, 2323, 1045, 3288, 1037, 5835, 1997, 4511, 2000, 2562, 2149, 21474, 1029, 2074, 12489, 999, 1045, 1521, 2222, 3288, 2028, 7539, 1012, 102, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 
[101, 2131, 1996, 6745, 2718, 3614, 5524, 2005, 2489, 999, 11562, 2023, 4957, 1024, 8299, 1013, 1013, 1056, 5244, 1012, 2898, 3669, 3726, 1012, 4012, 1013, 5950, 1012, 1059, 19968, 1029, 8909, 1027, 26667, 24087, 2549, 4215, 2692, 27717, 19841, 24087, 2581, 22907, 14526, 1004, 2034, 1027, 2995, 1067, 1039, 102]],
'token_type_ids': [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
'attention_mask': [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]}
  1. input_ids: 這是每個文本樣本轉換後的 token 序列。每個 token 都對應到 BERT 模型的詞彙表中的一個索引。在這個例子中,每個樣本都包含了一個長度為 50 的 token 序列。如果某個樣本的 token 數量不足 50,則會使用 0 進行填充,直到達到指定的序列長度。

101 代表[CLS]符號,102 代表[SEP]符號

  1. token_type_ids: 這個部分是用來區分不同句子的。在這裡,所有的 token 都屬於同一個句子,因此對應的值都是 0。在處理文本對任務時,將會有兩個句子,並使用 0 和 1 來區分它們。
  2. attention_mask: 這個部分用來指示哪些 token 是模型在處理時應該關注的。在這裡,所有的 token 都是被處理的,因此對應的值都是 1。在填充的部分,對應的值則是 0,用於告訴模型這些部分是填充的,不應該參與計算。

這些 tokenization 結果是 BERT 模型在處理文本數據時所需的輸入格式,其中包括了文本的 token 序列、句子區分和注意力遮罩等信息。

預測結果

# Make prediction
raw_pred, _, _ = test_trainer.predict(test_dataset)

# Preprocess raw predictions
y_pred = np.argmax(raw_pred, axis=1)

raw_pred 為包含 9 個元素的一維數組。

[[ 4.478097 -4.762506 ]
[-3.7398722 3.903498 ]
[-3.7016623 3.8625014]
[-3.7578042 3.9365413]
[-3.6734304 3.8043854]
[ 4.5997095 -4.8369007]
[-3.3514545 3.4255216]
[-3.7296603 3.8799422]
[ 3.270107 -3.534067 ]]

np.argmax 函式返回一個包含 9 個元素的一維數組,其中每個元素是對應行的預測結果(0 或 1)。
如果第一個數值較大,則對應位置的元素為 0;如果第二個數值較大,則對應位置的元素為 1。

最後預測出來的答案如下

[0 1 1 1 1 0 1 1 0]#預測答案

預測正確!!

基本上只要在內容有寫上 "free" ,"click link"之類的字樣,就很容易就會被判斷為垃圾郵件~

測試Dataset >> 在SMSSpamCollection_test當中

大家可以發揮創意,在測試Dataset加入自己的郵件語句,實測模型能否辨認出來。

添加的方式:

  1. 可以直接加在資料集的最下面即可。
  2. 把舊的句子刪掉,把你想預測的寫在第一列,這樣結果就會只有一個輸出。

嘿!你是否曾經對程式碼感到無比困惑,或者對 debug 的過程想大喊三聲「為什麼」? 不用擔心,崴寶的社群就是你的救星!🌟

📌 Discord 群組:

📌 LINE 社團:

在這裡,你將找到志同道合的小夥伴,無論是新手還是資深高手,大家都來這裡一起探索、分享和大聊特聊!

社群媒體

Instagram:

https://www.instagram.com/weibert_music/ https://www.instagram.com/weibert_coding/

YouTube:

https://www.youtube.com/@weibert

Threads

https://www.threads.net/@weibert_coding

FB粉絲專頁:

https://www.facebook.com/weibert1/

GitHub:

https://github.com/weitsung50110

TikTok:

https://www.tiktok.com/@weibert1

崴寶網站:

https://weitsung50110.github.io/

— 未來會在yotube頻道well崴寶程式開發天堂拍成影片教學

--

--