Download test.py from leminhhung0101/R-ViHSDModel: direct link, hf CLI and curl.
- Browser
- Download file 11.3 kB
-
https://huggingface.co/leminhhung0101/R-ViHSDModel/resolve/main/test.py
- Command line
-
hf download hf://leminhhung0101/R-ViHSDModel/test.py
-
curl -L -o test.py https://huggingface.co/leminhhung0101/R-ViHSDModel/resolve/main/test.py
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) | |
| ) |