File size: 10,346 Bytes
34e621c | 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 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 | """
Preprocessing module — Member B (Dũng)
Chuẩn hóa ký tự, khử nhiễu (de-obfuscation) và trích xuất đặc trưng bảng
"""
import re
import unicodedata
from typing import Callable, Optional
# ---------------------------------------------------------------------------
# Basic text cleaning & de-obfuscation
# ---------------------------------------------------------------------------
LEET_DICT = {
"0": "o", "1": "i", "3": "e", "4": "a",
"5": "s", "7": "t", "@": "a", "$": "s",
}
ZERO_WIDTH_CHARS = ["\u200b", "\u200c", "\u200d", "\ufeff", "\u00ad"]
def remove_zero_width(text: str) -> str:
"""Xoa cac ky tu tang hinh (Zero-width joiners, soft hyphens...)."""
for zwc in ZERO_WIDTH_CHARS:
text = text.replace(zwc, "")
return text
def normalize_unicode(text: str) -> str:
"""Dua cac ky tu 'giong nhau ve nghia' ve mot dang thong nhat (NFKC)."""
return unicodedata.normalize("NFKC", text)
def decode_leetspeak(text: str) -> str:
"""Chuyen cac ky tu leetspeak pho bien ve ky tu Latin thong thuong."""
for k, v in LEET_DICT.items():
text = text.replace(k, v)
return text
def clean_and_deobfuscate_text(text: str) -> str:
"""
Pipeline chuan hoa van ban dau vao:
1. Chuan hoa dang bieu dien Unicode (NFC).
2. Loai bo cac ky tu dieu khien an (Zero-width characters).
3. Giai ma leetspeak.
4. Chuan hoa khoang trang du thua.
"""
if not isinstance(text, str):
return ""
text = unicodedata.normalize("NFC", text)
text = remove_zero_width(text)
text = decode_leetspeak(text)
text = re.sub(r"\s+", " ", text).strip()
return text
# ---------------------------------------------------------------------------
# Code-switching, Teencode & Low-Resource Language (LRL) Heuristics
# ---------------------------------------------------------------------------
COMMON_VIET_SLANG_CODE_SWITCH = {
# Common English-Vietnamese code-switching phrases
"ok không": "được không",
"ok ko": "được không",
"ok k": "được không",
"check giùm": "kiểm tra giúp",
"check hộ": "kiểm tra giúp",
"bypass được": "vượt rào được",
"bypass đc": "vượt rào được",
"pass đc": "vượt qua được",
"fix bug": "sửa lỗi",
"delete hộ": "xóa giúp",
"hack nick": "chiếm đoạt tài khoản",
"share code": "chia sẻ mã nguồn",
"gen text": "tạo văn bản",
"drop database": "xóa cơ sở dữ liệu",
# Common Vietnamese teencode
"ko": "không", "k": "không", "hok": "không", "khum": "không",
"dc": "được", "đc": "được", "dk": "được",
"j": "gì", "ms": "mới", "lm": "làm",
"ntn": "như thế nào", "vs": "với", "ns": "nói",
}
NON_LATIN_SCRIPTS = [
(re.compile(r"[\u1780-\u17FF]"), "Khmer"),
(re.compile(r"[\u0E80-\u0EFF]"), "Lao"),
(re.compile(r"[\u0E00-\u0E7F]"), "Thai"),
(re.compile(r"[\u0400-\u04FF]"), "Cyrillic"),
(re.compile(r"[\u4E00-\u9FFF]"), "Chinese"),
]
def normalize_code_switching_and_slang(text: str) -> str:
"""Chuan hoa cac tu ngu pha tron ngon ngu (Code-switching) va teencode."""
lowered = text.lower()
for slang, replacement in COMMON_VIET_SLANG_CODE_SWITCH.items():
if slang in lowered:
# Replace case-insensitively using regex word boundary when possible
pattern = re.compile(rf"\b{re.escape(slang)}\b", re.IGNORECASE)
text = pattern.sub(replacement, text)
return text
# ---------------------------------------------------------------------------
# Universal Language Pivot & Translation Layer
# ---------------------------------------------------------------------------
class UniversalTranslator:
"""
Module dich da ngon ngu & ngon ngu it tai nguyen (LRL) ve ngon ngu chuan (Pivot: EN/VI).
Ho tro:
1. Custom translation_fn (neu dung MarianMT, NLLB-200, Google Translate API, v.v.)
2. Fallback heuristic: Giu nguyen text va chuyen hoa code-switching / teencode.
"""
_custom_translator_fn = None
@classmethod
def set_translator(cls, fn: Callable[[str, str], str]):
"""Cai dat ham dich tuy chon: fn(text, target_lang='en') -> translated_text."""
cls._custom_translator_fn = staticmethod(fn)
@classmethod
def translate_to_pivot(cls, text: str, target_lang: str = "en") -> str:
"""
Dich van ban bat ky ve ngon ngu ban le (Pivot Language: mac dinh la tieng Anh).
Giup ca Ingress Classifier va LLM de dang doi chieu voi luat an toan.
"""
if not text or not text.strip():
return ""
# Neu nguoi dung da dang ky model dich (NLLB, MarianMT, Google API...)
if cls._custom_translator_fn is not None:
try:
return cls._custom_translator_fn(text, target_lang)
except Exception:
pass
# Fallback mac dinh: Chuan hoa code-switching & leetspeak
normalized = normalize_code_switching_and_slang(text)
return normalized
def detect_code_switching_and_lrl(text: str) -> dict:
"""
Phat hien su xuat hien cua da ngon ngu trong cung mot prompt:
- Code-switching (Pha tron tieng Viet va tieng Anh / tu muon)
- Low-Resource Scripts (Khmer, Lao, Thai, Cyrillic homoglyphs, v.v.)
"""
if not text:
return {
"has_code_switching": False,
"has_lrl_script": False,
"detected_scripts": [],
"needs_translation": False,
}
detected_scripts = []
for pattern, script_name in NON_LATIN_SCRIPTS:
if pattern.search(text):
detected_scripts.append(script_name)
# Detect code-switching (chua ca tu vung tieng Viet va tu tieng Anh/muon)
words = re.findall(r"\b\w+\b", text.lower())
has_viet_indicator = any(w in words for w in ["không", "được", "hộ", "giúp", "làm", "là", "và", "của", "cho", "tôi", "em", "anh"])
has_eng_indicator = any(w in words for w in ["ok", "check", "bypass", "ignore", "prompt", "system", "hack", "mode", "pass", "code", "run", "script"])
has_code_switch = (has_viet_indicator and has_eng_indicator) or any(k in text.lower() for k in ["ok không", "ok ko", "bypass được", "check giùm"])
has_lrl = len(detected_scripts) > 0
return {
"has_code_switching": has_code_switch,
"has_lrl_script": has_lrl,
"detected_scripts": detected_scripts,
"needs_translation": bool(has_lrl or has_code_switch),
}
def compute_subword_fertility_rate(text: str, tokenizer=None) -> float:
"""
Tinh Subword Fertility Rate (SFR):
SFR = So luong tokens / So luong tu thong thuong.
- Neu khong co tokenizer Transformer, dung xap xi byte/subword heuristic.
- SFR binh thuong: 1.0 - 1.5.
- SFR cao (> 2.0): Dau hieu ngon ngu hiem (LRL), code-switching, hoac chuoi ky tu bi lam nhieu.
"""
if not text:
return 1.0
words = text.strip().split()
word_cnt = max(len(words), 1)
if tokenizer is not None:
try:
tokens = tokenizer.tokenize(text)
return len(tokens) / word_cnt
except Exception:
pass
# Heuristic fallback: kiem tra ky tu phi Latin va ty le do dai tu
non_ascii_chars = len(re.findall(r"[^\x00-\x7F]", text))
char_len = len(text)
heuristic_token_est = word_cnt + (non_ascii_chars * 0.8) + (char_len / 4.0)
return float(heuristic_token_est / word_cnt)
def preprocess_text(text: str) -> str:
"""
Pipeline tien xu ly day du dung cho feature engineering:
NFKC > Zero-width removal > Leetspeak decode > Code-switching/Slang normalize
"""
if not isinstance(text, str):
return ""
text = normalize_unicode(text)
text = remove_zero_width(text)
text = decode_leetspeak(text)
text = normalize_code_switching_and_slang(text)
return text
# ---------------------------------------------------------------------------
# Feature Engineering -- Character-level statistics
# ---------------------------------------------------------------------------
JAILBREAK_KEYWORDS = [
"ignore", "forget", "jailbreak", "developer mode", "act as",
"system prompt", "new instructions", "dan mode", "disregard all",
"ignore all", "bypass", "unrestricted", "no restrictions",
"pretend", "roleplay", "simulate", "override",
# Da ngon ngu / Code-switching keywords
"vượt rào", "bỏ qua", "lệnh hệ thống", "chế độ bảo trì",
]
def text_length(text: str) -> int:
return len(text)
def word_count(text: str) -> int:
return len(text.split())
def special_char_count(text: str) -> int:
return len(re.findall(r"[^a-zA-Z0-9\s]", text))
def upper_char_count(text: str) -> int:
return sum(c.isupper() for c in text)
def digit_count(text: str) -> int:
return sum(c.isdigit() for c in text)
def count_jailbreak_keywords(text: str) -> int:
text_lower = text.lower()
return sum(1 for kw in JAILBREAK_KEYWORDS if kw in text_lower)
def upper_ratio(text: str) -> float:
if not text:
return 0.0
return upper_char_count(text) / len(text)
def extract_tabular_features(text: str, tokenizer=None) -> dict:
"""
Trich xuat day du cac dac trung bang cho mot van ban dau vao.
Returns dict with tabular & cross-lingual features:
text_length, word_count, special_count, upper_count, digit_count,
attack_keyword_count, upper_ratio, sfr_score, has_code_switching, is_lrl_risk.
"""
clean = preprocess_text(text)
lrl_info = detect_code_switching_and_lrl(text)
sfr = compute_subword_fertility_rate(text, tokenizer=tokenizer)
return {
"text_length": text_length(clean),
"word_count": word_count(clean),
"special_count": special_char_count(clean),
"upper_count": upper_char_count(clean),
"digit_count": digit_count(clean),
"attack_keyword_count": count_jailbreak_keywords(clean),
"upper_ratio": upper_ratio(clean),
"sfr_score": round(sfr, 4),
"has_code_switching": int(lrl_info["has_code_switching"]),
"has_lrl_script": int(lrl_info["has_lrl_script"]),
"is_lrl_risk": int(lrl_info["has_lrl_script"] or (sfr > 2.2 and lrl_info["has_code_switching"])),
}
|