R-ViHSDModel / test.py
leminhhung0101's picture
Create test.py
d07fac7 verified
Raw History Blame Contribute Delete
11.3 kB
# ============================================================
# TEST / INFERENCE RIÊNG
# Load toàn bộ model đã train và dự đoán dữ liệu mới
# ============================================================
!pip -q install -U transformers accelerate scikit-learn scipy joblib sentencepiece
import os
import re
import gc
import math
import joblib
import numpy as np
import pandas as pd
import torch
from pathlib import Path
from scipy import sparse
from scipy.special import softmax
from sklearn.feature_extraction.text import TfidfVectorizer
from transformers import (
AutoTokenizer,
AutoModelForSequenceClassification,
)
from torch.utils.data import Dataset, DataLoader
# ============================================================
# 1. PATH
# ============================================================
# Thư mục model đã train ở notebook trước
WORK_DIR = Path(
"/content/drive/MyDrive/rvihsd_stacking_work"
)
# File dữ liệu MỚI cần dự đoán
TEST_CSV = "/content/drive/MyDrive/new_test.csv"
# File kết quả
OUTPUT_CSV = "/content/drive/MyDrive/new_test_prediction.csv"
# ============================================================
# 2. CONFIG
# ============================================================
MAX_LENGTH = 128
BATCH_SIZE = 64
LABELS = [
"CLEAN",
"OFFENSIVE",
"HATE",
]
NOISE_LABELS = [
"ORIGINAL",
"NO_DIACRITICS",
"TEENCODE",
"CHAR_REPEAT",
"PUNCT_NOISE",
"OBFUSCATION",
"MIXED",
]
id2label = {
i: x
for i, x in enumerate(LABELS)
}
id2noise = {
i: x
for i, x in enumerate(NOISE_LABELS)
}
# ============================================================
# 3. CLASS DualTfidf
# PHẢI CÓ vì joblib cần class này khi load vectorizer
# ============================================================
class DualTfidf:
def __init__(self):
self.char = None
self.word = None
def transform(self, texts):
texts = (
pd.Series(texts)
.fillna("")
.astype(str)
.tolist()
)
Xc = self.char.transform(texts)
Xw = self.word.transform(texts)
return sparse.hstack(
[Xc, Xw],
format="csr",
dtype=np.float32,
)
# ============================================================
# 4. SURFACE FEATURES
# PHẢI GIỐNG HỆT LÚC TRAIN META-MODEL
# ============================================================
VI_DIACRITICS = set(
"ăâđêôơưáàảãạắằẳẵặấầẩẫậ"
"éèẻẽẹếềểễệ"
"íìỉĩị"
"óòỏõọốồổỗộớờởỡợ"
"úùủũụứừửữự"
"ýỳỷỹỵ"
"ĂÂĐÊÔƠƯÁÀẢÃẠẮẰẲẴẶẤẦẨẪẬ"
"ÉÈẺẼẸẾỀỂỄỆ"
"ÍÌỈĨỊ"
"ÓÒỎÕỌỐỒỔỖỘỚỜỞỠỢ"
"ÚÙỦŨỤỨỪỬỮỰ"
"ÝỲỶỸỴ"
)
def meta_surface_features(texts):
rows = []
for text in texts:
s = str(text)
n = max(len(s), 1)
alpha = max(
sum(ch.isalpha() for ch in s),
1
)
punct = sum(
(not ch.isalnum())
and
(not ch.isspace())
for ch in s
)
diac = sum(
ch in VI_DIACRITICS
for ch in s
)
repeats = len(
re.findall(
r"(.)\1{2,}",
s.lower()
)
)
obfus = len(
re.findall(
r"(?<=[A-Za-zÀ-ỹ])[.*_~](?=[A-Za-zÀ-ỹ])",
s
)
)
rows.append([
math.log1p(len(s)),
punct / n,
diac / alpha,
min(repeats, 10) / 10.0,
min(obfus, 10) / 10.0,
])
return np.asarray(
rows,
dtype=np.float32
)
# ============================================================
# 5. KIỂM TRA MODEL FILE
# ============================================================
VISO_DIR = WORK_DIR / "visobert_full"
VECTORIZER_PATH = (
WORK_DIR / "full_vectorizer.joblib"
)
HATE_SVM_PATH = (
WORK_DIR / "full_hate_svm.joblib"
)
NOISE_SVM_PATH = (
WORK_DIR / "full_noise_svm.joblib"
)
META_PATH = (
WORK_DIR / "meta_model.joblib"
)
required = [
VISO_DIR / "config.json",
VECTORIZER_PATH,
HATE_SVM_PATH,
NOISE_SVM_PATH,
META_PATH,
]
for p in required:
if not p.exists():
raise FileNotFoundError(
f"Không tìm thấy: {p}"
)
print("Tất cả model files đều tồn tại.")
# ============================================================
# 6. LOAD TF-IDF + SVM + META MODEL
# ============================================================
print("\nLoading TF-IDF/SVM...")
full_vec = joblib.load(
VECTORIZER_PATH
)
full_hate_svm = joblib.load(
HATE_SVM_PATH
)
full_noise_svm = joblib.load(
NOISE_SVM_PATH
)
meta_model = joblib.load(
META_PATH
)
print("TF-IDF/SVM/meta-model loaded.")
# ============================================================
# 7. LOAD ViSoBERT
# ============================================================
device = torch.device(
"cuda"
if torch.cuda.is_available()
else "cpu"
)
print("\nDevice:", device)
print("Loading ViSoBERT...")
tokenizer = AutoTokenizer.from_pretrained(
VISO_DIR
)
visobert = (
AutoModelForSequenceClassification
.from_pretrained(VISO_DIR)
)
visobert.to(device)
visobert.eval()
print("ViSoBERT loaded.")
# ============================================================
# 8. DATASET CHO ViSoBERT
# ============================================================
class TestDataset(Dataset):
def __init__(self, texts):
self.texts = (
pd.Series(texts)
.fillna("")
.astype(str)
.tolist()
)
def __len__(self):
return len(self.texts)
def __getitem__(self, idx):
return self.texts[idx]
# ============================================================
# 9. ViSoBERT INFERENCE
# ============================================================
def predict_visobert(
texts,
batch_size=BATCH_SIZE
):
dataset = TestDataset(texts)
loader = DataLoader(
dataset,
batch_size=batch_size,
shuffle=False,
)
all_probs = []
with torch.no_grad():
for batch_texts in loader:
enc = tokenizer(
list(batch_texts),
padding=True,
truncation=True,
max_length=MAX_LENGTH,
return_tensors="pt",
)
enc = {
k: v.to(device)
for k, v in enc.items()
}
outputs = visobert(**enc)
probs = torch.softmax(
outputs.logits,
dim=1
)
all_probs.append(
probs.cpu().numpy()
)
return np.concatenate(
all_probs,
axis=0
).astype(np.float32)
# ============================================================
# 10. LOAD TEST DATA
# ============================================================
test_df = pd.read_csv(
TEST_CSV,
dtype={"id": str}
)
required_cols = {
"id",
"text",
}
missing = required_cols - set(
test_df.columns
)
if missing:
raise ValueError(
f"File test thiếu cột: {missing}"
)
test_df = test_df[
["id", "text"]
].copy()
test_df["id"] = (
test_df["id"]
.astype(str)
)
test_df["text"] = (
test_df["text"]
.fillna("")
.astype(str)
)
print(
"\nSố mẫu test:",
len(test_df)
)
display(
test_df.head()
)
# ============================================================
# 11. TF-IDF
# ============================================================
print("\n[1/4] TF-IDF...")
X_test = full_vec.transform(
test_df["text"]
)
print(
"TF-IDF shape:",
X_test.shape
)
# ============================================================
# 12. SVM PREDICTIONS
# ============================================================
print("\n[2/4] SVM inference...")
hate_svm_score = (
full_hate_svm
.decision_function(X_test)
.astype(np.float32)
)
noise_score = (
full_noise_svm
.decision_function(X_test)
.astype(np.float32)
)
noise_soft = softmax(
noise_score,
axis=1
).astype(np.float32)
print(
"Hate SVM:",
hate_svm_score.shape
)
print(
"Noise SVM:",
noise_score.shape
)
# ============================================================
# 13. ViSoBERT
# ============================================================
print("\n[3/4] ViSoBERT inference...")
viso_prob = predict_visobert(
test_df["text"]
)
print(
"ViSoBERT:",
viso_prob.shape
)
# ============================================================
# 14. META SURFACE FEATURES
# ============================================================
surface = meta_surface_features(
test_df["text"]
)
print(
"Surface:",
surface.shape
)
# ============================================================
# 15. STACKING
#
# Thứ tự PHẢI giống lúc train:
#
# ViSoBERT probability = 3
# SVM hate score = 3
# noise softmax = 7
# surface = 5
#
# Tổng = 18 features
# ============================================================
print("\n[4/4] Stacking...")
meta_X = np.hstack([
viso_prob,
hate_svm_score,
noise_soft,
surface,
]).astype(np.float32)
print(
"Meta feature shape:",
meta_X.shape
)
assert meta_X.shape[1] == 18
# Hate classification
hate_pred_id = meta_model.predict(
meta_X
).astype(int)
# Noise classification
noise_pred_id = noise_score.argmax(
axis=1
)
# ============================================================
# 16. CHUYỂN ID -> LABEL
# ============================================================
pred_label = [
id2label[int(x)]
for x in hate_pred_id
]
pred_noise = [
id2noise[int(x)]
for x in noise_pred_id
]
# ============================================================
# 17. TẠO OUTPUT
# ============================================================
output = pd.DataFrame({
"id":
test_df["id"].values,
"pred_label":
pred_label,
"pred_noise_type":
pred_noise,
})
# ============================================================
# 18. KIỂM TRA
# ============================================================
assert len(output) == len(test_df)
assert output["id"].is_unique
assert set(
output["pred_label"]
).issubset(LABELS)
assert set(
output["pred_noise_type"]
).issubset(NOISE_LABELS)
print("\nPrediction distribution:")
print(
"\nLABEL:"
)
print(
output["pred_label"]
.value_counts()
)
print(
"\nNOISE:"
)
print(
output["pred_noise_type"]
.value_counts()
)
# ============================================================
# 19. SAVE
# ============================================================
output.to_csv(
OUTPUT_CSV,
index=False,
encoding="utf-8",
)
print(
"\nSaved:",
OUTPUT_CSV
)
display(
output.head(20)
)