dongtruong1910 commited on
Commit
f817340
·
1 Parent(s): ad5bfe2
Files changed (8) hide show
  1. Dockerfile +25 -0
  2. api.py +34 -0
  3. requirements.txt +6 -0
  4. saved_models/best_model.pth +3 -0
  5. src/__init__.py +0 -0
  6. src/configs.py +34 -0
  7. src/model.py +33 -0
  8. src/predict.py +119 -0
Dockerfile ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 1. Chọn hệ điều hành Python 3.9
2
+ FROM python:3.9
3
+
4
+ # 2. Tạo thư mục làm việc
5
+ WORKDIR /code
6
+
7
+ # 3. Copy file requirements và cài đặt thư viện
8
+ COPY ./requirements.txt /code/requirements.txt
9
+ # Cài torch bản CPU cho nhẹ (tùy chọn, hoặc cài thường cũng được)
10
+ RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
11
+
12
+ # 4. Copy toàn bộ code vào trong
13
+ COPY ./src /code/src
14
+ COPY ./saved_models /code/saved_models
15
+ COPY ./api.py /code/api.py
16
+
17
+ # 5. Cấp quyền cho user (Hugging Face yêu cầu)
18
+ RUN useradd -m -u 1000 user
19
+ USER user
20
+ ENV HOME=/home/user \
21
+ PATH=/home/user/.local/bin:$PATH
22
+
23
+ # 6. Mở cổng 7860 (Cổng bắt buộc của Hugging Face)
24
+ # Lưu ý: Code api.py của bạn đang chạy port 8000, ta sẽ đổi lệnh chạy ở đây
25
+ CMD ["uvicorn", "api:app", "--host", "0.0.0.0", "--port", "7860"]
api.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI
2
+ from pydantic import BaseModel
3
+ import uvicorn
4
+ import sys
5
+ import os
6
+
7
+ sys.path.append(os.path.dirname(os.path.abspath(__file__)))
8
+ from src.predict import HateSpeechPredictor
9
+
10
+ app = FastAPI()
11
+ print("--> Đang khởi động Server...")
12
+ predictor = HateSpeechPredictor()
13
+
14
+
15
+ class Item(BaseModel):
16
+ text: str
17
+
18
+
19
+ @app.post("/predict")
20
+ def predict(item: Item):
21
+ # Gọi hàm predict thông minh (đã xử lý đoạn văn)
22
+ result = predictor.predict(item.text)
23
+
24
+ return {
25
+ "text": item.text,
26
+ "prediction": result['label'],
27
+ "confidence": f"{result['confidence']:.2%}",
28
+ "is_toxic": result['is_toxic'],
29
+ "flagged_sentence": result['flagged_sentence']
30
+ }
31
+
32
+
33
+ if __name__ == "__main__":
34
+ uvicorn.run(app, host="0.0.0.0", port=8000)
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ torch
2
+ transformers
3
+ pyvi
4
+ fastapi
5
+ uvicorn
6
+ pydantic
saved_models/best_model.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:09517912757395f534419b53f4f8cdaaa75d3ed6d16cc68da5d4c26cd43dc261
3
+ size 540084487
src/__init__.py ADDED
File without changes
src/configs.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import os
3
+
4
+ # Lấy đường dẫn gốc của dự án
5
+ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
6
+
7
+
8
+ class Config:
9
+ # --- ĐƯỜNG DẪN DỮ LIỆU ---
10
+ TRAIN_PATH = os.path.join(BASE_DIR, 'data', 'raw', 'train.csv')
11
+ DEV_PATH = os.path.join(BASE_DIR, 'data', 'raw', 'dev.csv')
12
+ TEST_PATH = os.path.join(BASE_DIR, 'data', 'raw', 'test.csv')
13
+
14
+ # Nơi lưu model
15
+ MODEL_SAVE_PATH = os.path.join(BASE_DIR, 'saved_models', 'best_model.pth')
16
+
17
+ # --- CẤU HÌNH PHOBERT ---
18
+ MODEL_NAME = "vinai/phobert-base"
19
+
20
+ # Tham số xử lý văn bản
21
+ MAX_LEN = 100 # Độ dài câu tối đa
22
+ N_CLASSES = 3 # <--- DÒNG BẠN ĐANG THIẾU (0: Clean, 1: Offensive, 2: Hate)
23
+
24
+ # --- THAM SỐ HUẤN LUYỆN (Fine-tuning) ---
25
+ BATCH_SIZE = 16 # PhoBERT nặng nên để batch size nhỏ (16 hoặc 8)
26
+ EPOCHS = 10
27
+ LEARNING_RATE = 2e-5 # Learning rate rất nhỏ cho Transformer
28
+
29
+ # Tự động chọn GPU
30
+ DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
31
+
32
+
33
+ if __name__ == '__main__':
34
+ print(f"Device: {Config.DEVICE}")
src/model.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch.nn as nn
2
+ from transformers import AutoModel
3
+ from .configs import Config
4
+
5
+
6
+ class HateSpeechModel(nn.Module):
7
+ def __init__(self, n_classes):
8
+ super(HateSpeechModel, self).__init__()
9
+ # Load khung xương PhoBERT
10
+ self.bert = AutoModel.from_pretrained(Config.MODEL_NAME, weights_only=False)
11
+
12
+ # Khóa bớt các tầng đầu để train nhanh hơn (Optional - Tùy chọn)
13
+ for param in self.bert.parameters():
14
+ param.requires_grad = True
15
+
16
+ # Thêm đầu ra phân loại
17
+ self.drop = nn.Dropout(p=0.3)
18
+ self.fc = nn.Linear(768, n_classes) # 768 là kích thước vector của PhoBERT Base
19
+
20
+ def forward(self, input_ids, attention_mask):
21
+ # Cho dữ liệu chạy qua PhoBERT
22
+ # output[0] là hidden states, output[1] là pooled output (vector đại diện câu)
23
+ outputs = self.bert(
24
+ input_ids=input_ids,
25
+ attention_mask=attention_mask
26
+ )
27
+
28
+ # Lấy vector đại diện của token [CLS] (token đầu tiên)
29
+ # Nó chứa ý nghĩa của toàn bộ câu
30
+ pooled_output = outputs[1]
31
+
32
+ output = self.drop(pooled_output)
33
+ return self.fc(output)
src/predict.py ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn.functional as F
3
+ from transformers import AutoTokenizer
4
+ from pyvi import ViTokenizer
5
+ from src.configs import Config
6
+ from src.model import HateSpeechModel
7
+ import os
8
+ import re
9
+
10
+
11
+ class HateSpeechPredictor:
12
+ def __init__(self, model_path=None):
13
+ self.device = Config.DEVICE
14
+ print(f"--> Đang khởi tạo Predictor trên: {self.device}")
15
+
16
+ # 1. Load Tokenizer & Model
17
+ self.tokenizer = AutoTokenizer.from_pretrained(Config.MODEL_NAME)
18
+ self.model = HateSpeechModel(n_classes=Config.N_CLASSES)
19
+
20
+ # 2. Load Weights
21
+ if model_path is None:
22
+ model_path = Config.MODEL_SAVE_PATH
23
+
24
+ if os.path.exists(model_path):
25
+ self.model.load_state_dict(torch.load(model_path, map_location=self.device))
26
+ self.model.to(self.device)
27
+ self.model.eval()
28
+ print("--> Đã load model thành công!")
29
+ else:
30
+ raise FileNotFoundError(f"Chưa có file model tại {model_path}")
31
+
32
+ # Map nhãn
33
+ self.labels_map = {0: "CLEAN", 1: "OFFENSIVE", 2: "HATE"}
34
+ # Map mức độ nghiêm trọng (để so sánh)
35
+ self.severity_map = {"CLEAN": 0, "OFFENSIVE": 1, "HATE": 2}
36
+
37
+ def _split_sentences(self, text):
38
+ """Hàm tách đoạn văn thành các câu nhỏ"""
39
+ # Tách dựa trên dấu chấm, chấm than, chấm hỏi, hoặc xuống dòng
40
+ sentences = re.split(r'(?<!\w\.\w.)(?<![A-Z][a-z]\.)(?<=\.|\?|\!|\n)\s', text)
41
+ return [s.strip() for s in sentences if len(s.strip()) > 1]
42
+
43
+ def _predict_single(self, text):
44
+ """Dự đoán cho 1 câu đơn"""
45
+ text_segmented = ViTokenizer.tokenize(text)
46
+
47
+ encoding = self.tokenizer.encode_plus(
48
+ text_segmented,
49
+ max_length=Config.MAX_LEN,
50
+ truncation=True,
51
+ padding='max_length',
52
+ add_special_tokens=True,
53
+ return_attention_mask=True,
54
+ return_tensors='pt'
55
+ )
56
+
57
+ input_ids = encoding['input_ids'].to(self.device)
58
+ attention_mask = encoding['attention_mask'].to(self.device)
59
+
60
+ with torch.no_grad():
61
+ outputs = self.model(input_ids, attention_mask)
62
+ probs = F.softmax(outputs, dim=1)
63
+
64
+ max_prob, pred_idx = torch.max(probs, dim=1)
65
+ return self.labels_map[pred_idx.item()], max_prob.item()
66
+
67
+ def predict(self, text):
68
+ """
69
+ Hàm chính: Xử lý cả đoạn văn.
70
+ Logic: Tách câu -> Dự đoán từng câu -> Lấy nhãn NẶNG NHẤT.
71
+ """
72
+ sentences = self._split_sentences(text)
73
+
74
+ final_label = "CLEAN"
75
+ final_conf = 0.0
76
+ max_severity = 0
77
+ flagged_sentence = "" # Lưu lại câu bị vi phạm
78
+
79
+ # Nếu đoạn văn quá ngắn hoặc không tách được, coi là 1 câu
80
+ if len(sentences) == 0:
81
+ sentences = [text]
82
+
83
+ for sent in sentences:
84
+ label, conf = self._predict_single(sent)
85
+ severity = self.severity_map[label]
86
+
87
+ # Cập nhật nếu tìm thấy câu nặng hơn (HATE > OFFENSIVE > CLEAN)
88
+ # Hoặc cùng mức độ nhưng độ tin cậy cao hơn
89
+ if severity > max_severity:
90
+ max_severity = severity
91
+ final_label = label
92
+ final_conf = conf
93
+ flagged_sentence = sent
94
+ elif severity == max_severity and conf > final_conf:
95
+ final_conf = conf
96
+ flagged_sentence = sent
97
+
98
+ # Nếu là CLEAN thì không cần flagged_sentence
99
+ if final_label == "CLEAN":
100
+ flagged_sentence = None
101
+
102
+ return {
103
+ "label": final_label,
104
+ "confidence": final_conf,
105
+ "is_toxic": final_label != "CLEAN",
106
+ "flagged_sentence": flagged_sentence # Câu "tội đồ" làm bài bị chặn
107
+ }
108
+
109
+
110
+ # Test
111
+ if __name__ == "__main__":
112
+ p = HateSpeechPredictor()
113
+ # Test đoạn văn dài
114
+ paragraph = "Hôm nay trời đẹp. Nhưng mày là đồ ngu. Đi chơi thôi."
115
+ result = p.predict(paragraph)
116
+
117
+ print(f"Input: {paragraph}")
118
+ print(f"Kết quả: {result['label']} ({result['confidence']:.2%})")
119
+ print(f"Câu vi phạm: {result['flagged_sentence']}")