ScamGuard-AI / AI_Model_architecture.py
Bennie12's picture
Update AI_Model_architecture.py
0e017ad verified
"""
流程圖
讀取資料 → 分割資料 → 編碼 → 建立 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 抽出的新特徵)
"""