File size: 4,078 Bytes
8b1f68b
a9e89a3
8b1f68b
a9e89a3
8b1f68b
a9e89a3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8b1f68b
a9e89a3
 
 
 
 
 
8b1f68b
a9e89a3
 
 
 
8b1f68b
a9e89a3
24ca47b
8b1f68b
a9e89a3
 
 
 
 
 
 
a916de4
a9e89a3
 
 
 
 
 
a916de4
8b1f68b
 
 
 
24ca47b
a9e89a3
8b1f68b
 
a9e89a3
 
 
75208b4
 
a9e89a3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7fa25d5
 
a9e89a3
 
 
 
7fa25d5
16c2c8f
a916de4
7fa25d5
 
a916de4
a9e89a3
 
 
 
 
 
 
a916de4
a9e89a3
 
 
 
 
 
7fa25d5
16c2c8f
a9e89a3
 
 
 
 
 
 
 
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
156
157
158
159
160
import os
import shutil
import zipfile

import gradio as gr
import torch
from transformers import AutoModelForSequenceClassification, AutoTokenizer


ZIP_PATH = "model.zip"
EXTRACT_DIR = "./model"
MODEL_DIR = os.path.join(EXTRACT_DIR, "final_model")
DEVICE = torch.device("cpu")


def prepare_model_files():
    config_path = os.path.join(MODEL_DIR, "config.json")
    weights_path = os.path.join(MODEL_DIR, "model.safetensors")

    if os.path.isfile(config_path) and os.path.isfile(weights_path):
        print("模型已經解壓完成。", flush=True)
        return

    if not os.path.isfile(ZIP_PATH):
        raise FileNotFoundError("找不到 model.zip")

    if os.path.exists(EXTRACT_DIR):
        shutil.rmtree(EXTRACT_DIR)

    os.makedirs(EXTRACT_DIR, exist_ok=True)

    print("正在解壓模型……", flush=True)

    with zipfile.ZipFile(ZIP_PATH, "r") as zip_ref:
        zip_ref.extractall(EXTRACT_DIR)

    print("模型解壓完成。", flush=True)

    if not os.path.isfile(config_path):
        raise FileNotFoundError(
            f"找不到 {config_path},請確認 ZIP 裡面有 final_model 資料夾。"
        )

    if not os.path.isfile(weights_path):
        raise FileNotFoundError(
            f"找不到 {weights_path},請確認 model.safetensors 已包含在 ZIP 中。"
        )


prepare_model_files()

print("正在載入 tokenizer……", flush=True)

tokenizer = AutoTokenizer.from_pretrained(
    MODEL_DIR,
    local_files_only=True
)

print("正在載入 BERT 模型到 CPU……", flush=True)

model = AutoModelForSequenceClassification.from_pretrained(
    MODEL_DIR,
    local_files_only=True
)

model.to(DEVICE)
model.eval()

print("模型載入成功。", flush=True)


LABEL_MAP = {
    0: "詐騙訊息",
    1: "真實訊息"
}


def predict(text: str) -> str:
    text = text.strip()

    if not text:
        return "請先輸入要判斷的訊息。"

    inputs = tokenizer(
        text,
        return_tensors="pt",
        truncation=True,
        padding=True,
        max_length=256
    )

    inputs = {
        key: value.to(DEVICE)
        for key, value in inputs.items()
    }

    with torch.inference_mode():
        outputs = model(**inputs)
        probabilities = torch.softmax(outputs.logits, dim=-1)[0]

    probabilities = probabilities.cpu()

    pred_id = int(torch.argmax(probabilities).item())
    confidence = float(probabilities[pred_id].item() * 100)

    scam_probability = float(probabilities[0].item() * 100)
    real_probability = float(probabilities[1].item() * 100)

    pred_label = LABEL_MAP.get(
        pred_id,
        f"未知類別 {pred_id}"
    )

    if pred_id == 0:
        advice = (
            "請勿立即匯款、提供銀行帳號、密碼、信用卡資料或簡訊驗證碼。"
            "建議透過官方管道查證,必要時撥打 165 反詐騙專線。"
        )
    else:
        advice = (
            "模型判斷較接近真實訊息,但模型仍可能誤判。"
            "若內容涉及金錢、帳戶或個人資料,仍應再次查證。"
        )

    return (
        f"🔍 判斷結果:{pred_label}\n\n"
        f"📊 模型信心度:{confidence:.2f}%\n\n"
        f"🚨 詐騙機率:{scam_probability:.2f}%\n"
        f"✅ 真實機率:{real_probability:.2f}%\n\n"
        f"⚠️ 提醒:{advice}"
    )


demo = gr.Interface(
    fn=predict,
    inputs=gr.Textbox(
        lines=6,
        label="訊息內容",
        placeholder="請輸入要判斷的新聞、貼文或可疑訊息……"
    ),
    outputs=gr.Textbox(
        label="辨識結果",
        lines=8
    ),
    title="LINE Bot BERT 詐騙/真實訊息辨識 API",
    description=(
        "輸入文字後,系統會判斷內容較接近詐騙訊息或真實訊息。"
        "建議輸入較完整的內容,文字過短可能降低辨識效果。"
    ),
    api_name="predict"
)


if __name__ == "__main__":
    demo.launch(
        server_name="0.0.0.0",
        server_port=7860,
        ssr_mode=False,
        show_error=True
    )