Spaces:
Runtime error
Runtime error
File size: 7,633 Bytes
0e017ad aef356d |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
"""
流程圖
讀取資料 → 分割資料 → 編碼 → 建立 Dataset / DataLoader
↓
建立模型(BERT+LSTM+CNN)
↓
BERT 輸出 [batch, seq_len, 768]
↓
BiLSTM [batch, seq_len, hidden_dim*2]
↓
CNN 模組 (Conv1D + Dropout + GlobalMaxPooling1D)
↓
Linear 分類器(輸出詐騙機率)
↓
訓練模型(Epochs)
↓
評估模型(Accuracy / F1 / Precision / Recall)
↓
儲存模型(.pth)
"""
#引入重要套件Import Library
import torch # PyTorch 主模組
import torch.nn as nn # 神經網路相關的層(例如 LSTM、Linear)
from transformers import BertTokenizer # BertTokenizer把文字句子轉換成 BERT 格式的 token ID,例如 [CLS] 今天 天氣 不錯 [SEP] → [101, 1234, 5678, ...]
from transformers import BertModel
# nn.Module是PyTorch所有神經網路模型的基礎類別,nn.Module 是 PyTorch 所有神經網路模型的基礎類別
class BertLSTM_CNN_Classifier(nn.Module):
def __init__(self, hidden_dim=128, num_layers=1, dropout=0.3):
# super()是Python提供的一個方法,用來呼叫「父類別的版本」的方法。
# 呼叫:super().__init__()讓父類別(nn.Module)裡面那些功能、屬性都被正確初始化。
# 沒super().__init__(),這些都不會正確運作,模型會壞掉。
# super() 就是 Python 提供給「子類別呼叫父類別方法」的方式
super().__init__()
# 載入中文預訓練的 BERT 模型,輸入為句子token IDs,輸出為每個 token 的向量,大小為 [batch, seq_len, 768]。
self.bert = BertModel.from_pretrained("ckiplab/bert-base-chinese") # 這是引入hugging face中的tranceformat
# 接收BERT的輸出(768 維向量),進行雙向LSTM(BiLSTM)建模,輸出為 [batch, seq_len, hidden_dim*2],例如 [batch, seq_len, 256]
"""
LSTM 接收每個token的768維向量(來自 BERT)作為輸入,
透過每個方向的LSTM壓縮成128維的語意向量。
由於是雙向LSTM,會同時從左到右(前向)和右到左(後向)各做一次,
最後將兩個方向的輸出合併為256維向量(128×2)。
每次處理一個 batch(例如 8 句話),一次走完整個時間序列。
"""
self.LSTM = nn.LSTM(input_size=768,
hidden_size=hidden_dim,
num_layers=num_layers,
batch_first=True,
bidirectional=True)
# CNN 模組:接在 LSTM 後的輸出上。將LSTM的輸出轉成卷積層格式,適用於Conv1D,CNN可學習位置不變的局部特徵。
self.conv1 = nn.Conv1d(in_channels=hidden_dim*2,
out_channels=128,
kernel_size=3, # 這裡kernel_size=3 為 3-gram 特徵
padding=1)
self.dropout = nn.Dropout(dropout) # 隨機將部分神經元設為 0,用來防止 overfitting。
self.global_maxpool = nn.AdaptiveAvgPool1d(1) #將一整句話的特徵濃縮成一個固定大小的句子表示向量
# 將CNN輸出的128維特徵向量輸出為一個「機率值」(詐騙或非詐騙)。
self.classifier = nn.Linear(128,1)
def forward(self, input_ids, attention_mask, token_type_ids):
#BERT 編碼
outputs = self.bert(input_ids=input_ids,
attention_mask=attention_mask,
token_type_ids=token_type_ids)
#.last_hidden_state是BertModel.from_pretrained(...)內部的key,會輸出 [batch, seq_len, 768]
hidden_states = outputs.last_hidden_state
# 送入 BiLSTM
# transpose(1, 2) 的用途是:讓 LSTM 輸出的資料形狀符合 CNN 所要求的格式
# 假設你原本 LSTM 輸出是: [batch_size, seq_len, hidden_dim*2] = [8, 128, 256]
# 但CNN(Conv1d)的輸入格式需要是:[batch_size, in_channels, seq_len] = [8, 256, 128]
# 因此你需要做:.transpose(1, 2)把 seq_len 和 hidden_dim*2 調換
LSTM_out, _ = self.LSTM(hidden_states) # [batch, seq_len, hidden_dim*2]
LSTM_out = LSTM_out.transpose(1, 2) # [batch, hidden_dim*2, seq_len]
# 卷積 + Dropout
x = self.conv1(LSTM_out) # [batch, 128, seq_len]
x = self.dropout(x)
#全局池化
# .squeeze(dim) 的作用是:把某個「維度大小為 1」的維度刪掉
# x = self.global_maxpool(x).squeeze(2) # 輸出是 [batch, 128, 1]
# 不 .squeeze(2),你會得到 shape 為 [batch, 128, 1],不方便後面接 Linear。
# .squeeze(2)=拿掉第 2 維(數值是 1) → 讓形狀變成 [batch, 128]
x = self.global_maxpool(x).squeeze(2) # [batch, 128]
#分類 & Sigmoid 機率輸出
logits = self.classifier(x)
#.sigmoid() → 把 logits 轉成 0~1 的機率.squeeze() → 變成一維 [batch] 長度的機率 list
"""例如:
logits = [[0.92], [0.05], [0.88], [0.41], ..., [0.17]]
→ sigmoid → [[0.715], [0.512], ...]
→ squeeze → [0.715, 0.512, ...]
"""
return logits.squeeze() # 最後輸出是一個值介於 0 ~ 1 之間,代表「為詐騙訊息的機率」。
"""
整個模型中每一個文字(token)始終是一個向量,隨著層數不同,這個向量代表的意義會更高階、更語意、更抽象。
在整個 BERT + LSTM + CNN 模型的流程中,「每一個文字(token)」都會被表示成一個「向量」來進行後續的計算與學習。
今天我輸入一個句子:"早安你好,吃飯沒"
BERT 的輸入包含三個部分:input_ids、attention_mask、token_type_ids,
這些是 BERT 所需的格式。BERT 會將句子中每個 token 編碼為一個 768 維的語意向量,
進入 BERT → 每個 token 變成語意向量:
BERT 輸出每個字為一個 768 維的語意向量
「早」 → [0.23, -0.11, ..., 0.45] 長度為 768
「安」 → [0.05, 0.33, ..., -0.12] 一樣 768
...
batch size 是 8,句子長度是 8,輸出 shape 為:
[batch_size=8, seq_len=8, hidden_size=768]
接下來這些向量會輸入到 LSTM,LSTM不會改變「一個token是一個向量」的概念,而是重新表示每個token的語境向量。
把每個原本 768 維的 token 壓縮成 hidden_size=128,雙向 LSTM → 拼接 → 每個 token 成為 256 維向量:
input_size=768 是從 BERT 接收的向量維度
hidden_size=128 表示每個方向的 LSTM 會把 token 壓縮為 128 維語意向量
num_layers=1 表示只堆疊 1 層 LSTM
bidirectional=True 表示是雙向
LSTM,除了從左讀到右,也會從右讀到左,兩個方向的輸出會合併(拼接),變成:
[batch_size=8, seq_len=8, hidden_size=256] # 因為128*2
接下來進入 CNN,CNN 仍然以「一個向量代表一個字」的形式處理:
in_channels=256(因為 LSTM 是雙向輸出)
out_channels=128 表示學習出 128 個濾波器,每個濾波器專門抓一種 n-gram(例如「早安你」),每個「片段」的結果輸出為 128 維特徵
kernel_size=3 表示每個濾波器看 3 個連續 token(像是一個 3-gram)或,把相鄰的 3 個字(各為 256 維)一起掃描
padding=1 為了保留輸出序列長度和輸入相同,避免邊界資訊被捨棄
CNN 輸出的 shape 就會是:
[batch_size=8, out_channels=128, seq_len=8],還是每個 token 有對應一個向量(只是這向量是 CNN 抽出的新特徵)
""" |