vietnamese-toxic-comment-classification / teencode_normalizer.py
NhanChanDeo
Upload Vietnamese Toxic Comment Classification project
8058cb3
Raw
History Blame Contribute Delete
11.8 kB
"""
teencode_normalizer.py
──────────────────────
Vietnamese teencode / internet-slang β†’ standard-form normalizer.
Usage:
from teencode_normalizer import normalize_teencode
clean = normalize_teencode("ko bik j luΓ΄n Γ‘")
# → "không biết gì luôn Ñ"
The mapping dictionary is hand-curated for the Vietnamese toxic-comment
classification task (ViHOS / UIT-VSMEC style data). It intentionally
preserves vulgar terms in their full form so downstream classifiers can
learn to detect them.
All replacements are **case-insensitive** and respect word boundaries
so that partial matches inside longer words are not corrupted.
"""
import re
from typing import Dict
# ────────────────────────────────────────────────────────────────────
# Teencode β†’ Standard Vietnamese mapping
# ────────────────────────────────────────────────────────────────────
TEENCODE_MAP: Dict[str, str] = {
# ── Common abbreviations ──────────────────────────────────────
"ko": "khΓ΄ng",
"k": "khΓ΄ng",
"kg": "khΓ΄ng",
"khg": "khΓ΄ng",
"kh": "khΓ΄ng",
"hk": "khΓ΄ng",
"hem": "khΓ΄ng",
"hΓ΄ng": "khΓ΄ng",
"kΓ΄": "khΓ΄ng",
"0": "khΓ΄ng", # single-digit zero used as "khΓ΄ng"
"dc": "được",
"dk": "được",
"Δ‘c": "được",
"Δ‘k": "được",
"duoc": "được",
"ns": "nΓ³i",
"nch": "nΓ³i chuyện",
"nc": "nΓ³i chuyện",
"bt": "bình thường",
"bth": "bình thường",
"bn": "bao nhiΓͺu",
"bnhieu": "bao nhiΓͺu",
"bh": "bao giờ",
"bjh": "bao giờ",
"lm": "lΓ m",
"lam": "lΓ m",
"mk": "mình",
"mik": "mình",
"mn": "mọi người",
"mng": "mọi người",
"ng": "người",
"ngta": "người ta",
"nta": "người ta",
"nt": "nhαΊ―n tin",
"tl": "trả lời",
"rep": "trả lời",
"ib": "inbox",
"fb": "facebook",
"acc": "tΓ i khoαΊ£n",
"ad": "admin",
"amin": "admin",
"adm": "admin",
"a": "anh",
"e": "em",
"aj": "ai",
"aq": "ai",
"j": "gì",
"ji": "gì",
"z": "vαΊ­y",
"v": "vαΊ­y",
"vl": "vΓ£i",
"r": "rα»“i",
"oy": "rα»“i",
"rui": "rα»“i",
"roi": "rα»“i",
"m": "mΓ y",
"t": "tao",
"tui": "tΓ΄i",
"cx": "cΕ©ng",
"cg": "cΕ©ng",
"cug": "cΕ©ng",
"cux": "cΕ©ng",
"vs": "vα»›i",
"voi": "vα»›i",
"nma": "nhΖ°ng mΓ ",
"nhma": "nhΖ°ng mΓ ",
"trc": "trΖ°α»›c",
"trog": "trong",
"trg": "trong",
"ngoai": "ngoΓ i",
"ngΓ²ai": "ngoΓ i",
"dg": "đường",
"Δ‘g": "đường",
"hiu": "hiểu",
"bik": "biαΊΏt",
"biet": "biαΊΏt",
"bit": "biαΊΏt",
"wa": "quΓ‘",
"qΓ‘": "quΓ‘",
"qa": "quΓ‘",
"lun": "luΓ΄n",
"luon": "luΓ΄n",
"nek": "nè",
"nè": "nè",
"nha": "nhΓ©",
"nhaz": "nhΓ©",
"nhen": "nhΓ©",
"hen": "nhΓ©",
"thik": "thΓ­ch",
"thix": "thΓ­ch",
"pn": "phα»₯ nα»―",
"ck": "chα»“ng",
"vk": "vợ",
"gd": "gia Δ‘Γ¬nh",
"gΔ‘": "gia Δ‘Γ¬nh",
"xh": "xΓ£ hα»™i",
"dt": "Δ‘iện thoαΊ‘i",
"Δ‘t": "Δ‘iện thoαΊ‘i",
"sdt": "sα»‘ Δ‘iện thoαΊ‘i",
"zui": "vui",
"zαΊ­y": "vαΊ­y",
"zì": "gì",
"dzαΊ­y": "vαΊ­y",
"dz": "vαΊ­y",
"dαΊ­y": "vαΊ­y",
"vαΊ­y": "vαΊ­y",
"mΓ­": "vα»›i",
"iu": "yΓͺu",
"yeu": "yΓͺu",
"cmt": "bình luận",
"comt": "bình luận",
"share": "chia sαΊ»",
"fl": "theo dΓ΅i",
"like": "thΓ­ch",
"sub": "Δ‘Δƒng kΓ½",
"clip": "video",
"tks": "cαΊ£m Ζ‘n",
"thanks": "cαΊ£m Ζ‘n",
"tq": "cαΊ£m Ζ‘n",
"ty": "cαΊ£m Ζ‘n",
"sr": "xin lα»—i",
"sorry": "xin lα»—i",
"plz": "xin hΓ£y",
"pls": "xin hΓ£y",
"ok": "được",
"oke": "được",
"okie": "được",
"okee": "được",
"ngon": "ngon",
"xin": "xin",
"ot": "tăng ca",
"cty": "cΓ΄ng ty",
"cong ty": "cΓ΄ng ty",
"nyc": "người yΓͺu cΕ©",
"ny": "người yΓͺu",
"hs": "học sinh",
"sv": "sinh viΓͺn",
"gv": "giΓ‘o viΓͺn",
"hsg": "học sinh giỏi",
"ctv": "cα»™ng tΓ‘c viΓͺn",
"hcm": "hα»“ chΓ­ minh",
"hn": "hΓ  nα»™i",
"sg": "sΓ i gΓ²n",
"vn": "việt nam",
"nck": "nghiΓͺn cα»©u khoa học",
"Δ‘h": "Δ‘αΊ‘i học",
"dh": "Δ‘αΊ‘i học",
"thpt": "trung học phα»• thΓ΄ng",
"thcs": "trung học cƑ sở",
"ths": "thαΊ‘c sΔ©",
"ts": "tiαΊΏn sΔ©",
"gs": "giΓ‘o sΖ°",
"pgs": "phΓ³ giΓ‘o sΖ°",
# ── Vulgar / toxic teencode ───────────────────────────────────
# (kept in full form intentionally for hate-speech detection)
"vcl": "vΓ£i cαΊ£ lα»“n",
"vkl": "vΓ£i cαΊ£ lα»“n",
"vcc": "vãi cả chưởng",
"vl": "vΓ£i lα»“n",
"vcΔ‘": "vΓ£i cαΊ£ Δ‘Γ‘i",
"Δ‘m": "Δ‘α»₯ mαΊΉ",
"dm": "Δ‘α»₯ mαΊΉ",
"dmm": "Δ‘α»₯ mαΊΉ mΓ y",
"Δ‘mm": "Δ‘α»₯ mαΊΉ mΓ y",
"dkm": "Δ‘α»₯ con mαΊΉ",
"Δ‘km": "Δ‘α»₯ con mαΊΉ",
"dcm": "Δ‘α»₯ con mαΊΉ",
"Δ‘cm": "Δ‘α»₯ con mαΊΉ",
"clm": "con lα»“n mΓ y",
"cl": "cΓ‘i lα»“n",
"cmm": "con mαΊΉ mΓ y",
"cc": "cαΊ·c",
"ccc": "cαΊ·c",
"ccm": "cΓ‘i cαΊ·c mΓ y",
"bkl": "bα»‘ mΓ y cΓ‘i lα»“n",
"Δ‘Γ©o": "Δ‘Γ©o",
"deo": "Δ‘Γ©o",
"Δ‘ΓΉ": "Δ‘α»₯",
"du": "Δ‘α»₯",
"wtf": "cÑi quÑi gì",
"stfu": "cΓ’m mα»“m Δ‘i",
"lol": "haha",
"lmao": "haha",
"bruh": "bαΊ‘n Ζ‘i",
"omg": "trời Ƒi",
"haiz": "thở dài",
"huhu": "khΓ³c",
"hihi": "hehe",
"kk": "haha",
"kkk": "haha",
"haha": "haha",
"hehe": "hehe",
"tl": "trả lời",
"tks": "cαΊ£m Ζ‘n",
"ngu": "ngu",
"Γ³c chΓ³": "ngu",
"vkΔ‘": "vΓ£i cαΊ£ Δ‘Γ‘i",
# ── Obfuscated spellings ──────────────────────────────────────
"rag": "rαΊ±ng",
"trog": "trong",
"nguoi": "người",
"ngui": "người",
"nuoc": "nΖ°α»›c",
"nc": "nΖ°α»›c",
"nhug": "nhΖ°ng",
"nug": "nhΖ°ng",
"vΓ’g": "vΓ’ng",
"thg": "thαΊ±ng",
"thag": "thαΊ±ng",
"thăg": "thằng",
"cog": "cΓ΄ng",
"nhΓ  nc": "nhΓ  nΖ°α»›c",
"ks": "khΓ‘ch sαΊ‘n",
"bv": "bệnh viện",
"sg": "sΓ i gΓ²n",
"cp": "chΓ­nh phα»§",
"td": "thα»§ Δ‘Γ΄",
# ── Elongated / repeated chars (handled by regex below) ───────
# ── Gen-Z / newer slang ───────────────────────────────────────
"crush": "người thưƑng",
"flex": "khoe",
"slay": "quΓ‘ đỉnh",
"real": "thαΊ­t",
"cap": "nΓ³i dα»‘i",
"nocap": "thαΊ­t luΓ΄n",
"no cap": "thαΊ­t luΓ΄n",
"ib": "nhαΊ―n riΓͺng",
"toxic": "Δ‘α»™c hαΊ‘i",
"simp": "theo Δ‘uα»•i mΓΉ quΓ‘ng",
"gato": "ghen Δƒn tα»©c ở",
"hΓ³ng": "tΓ² mΓ²",
"ship": "ghΓ©p Δ‘Γ΄i",
"out trΓ¬nh": "vượt trα»™i",
"bΓ³c phα»‘t": "tα»‘ cΓ‘o",
"trαΊ©u": "ngu",
"Δ‘u trend": "theo xu hΖ°α»›ng",
"chill": "thΖ° giΓ£n",
"vibe": "cαΊ£m giΓ‘c",
"deep": "sΓ’u sαΊ―c",
"αΊ£o tung chαΊ£o": "quΓ‘ αΊ£o",
"chαΊ±m zn": "trαΊ§m cαΊ£m",
"xΓ m": "nhαΊ£m nhΓ­",
"xΓ m lol": "nhαΊ£m nhΓ­",
"gαΊ₯u": "người yΓͺu",
"cẩu lưƑng": "tình cảm ngọt ngào",
"sαΊΏn": "sαΊΏn sΓΊa",
"xα»‹n xΓ²": "tα»‘t Δ‘αΊΉp",
"gΓ©t gΓ΄": "let's go",
"Γ©t Γ΄ Γ©t": "kΓͺu cα»©u",
"u là trời": "trời Ƒi",
}
# ────────────────────────────────────────────────────────────────────
# Build the compiled regex once at import time.
# Sort by length (longest first) so that multi-word keys like
# "nhΓ  nc" match before the single-word "nc".
# ────────────────────────────────────────────────────────────────────
_SORTED_KEYS = sorted(TEENCODE_MAP.keys(), key=len, reverse=True)
# Build pattern: each key is escaped and wrapped in word-boundaries.
# Vietnamese word boundaries need a bit of care β€” we use lookbehind /
# lookahead for whitespace-or-string-edge so that diacritics are not
# treated as non-word characters by \b.
_PATTERN = re.compile(
r"(?<!\w)(" + "|".join(re.escape(k) for k in _SORTED_KEYS) + r")(?!\w)",
re.IGNORECASE,
)
def _replace(match: re.Match) -> str:
"""Return the standard form for the matched teencode token."""
token = match.group(0).lower()
return TEENCODE_MAP.get(token, token)
# ────────────────────────────────────────────────────────────────────
# Additional regex rules applied *after* dictionary replacement
# ────────────────────────────────────────────────────────────────────
# 1. Collapse runs of 3+ identical characters β†’ 2 (e.g. "Δ‘αΊΉpppp" β†’ "Δ‘αΊΉpp")
_REPEAT_CHAR = re.compile(r"(.)\1{2,}")
# 2. Normalise whitespace
_MULTI_SPACE = re.compile(r"\s{2,}")
def normalize_teencode(text: str) -> str:
"""
Normalise Vietnamese teencode / internet slang in *text*.
Steps
-----
1. Dictionary-based word replacement (case-insensitive, word-boundary aware).
2. Collapse repeated characters (β‰₯ 3 identical β†’ 2).
3. Strip leading / trailing whitespace and collapse internal runs.
Parameters
----------
text : str
Raw Vietnamese comment text.
Returns
-------
str
Text with teencode replaced by standard Vietnamese.
"""
if not isinstance(text, str) or not text:
return text
# Step 1 – dictionary replacement
text = _PATTERN.sub(_replace, text)
# Step 2 – collapse repeated characters
text = _REPEAT_CHAR.sub(r"\1\1", text)
# Step 3 – whitespace tidy-up
text = _MULTI_SPACE.sub(" ", text).strip()
return text
# ────────────────────────────────────────────────────────────────────
# Quick smoke test when run as a script
# ────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
examples = [
"ko bik j luΓ΄n Γ‘",
"mng Ζ‘i ad ib e vs nha",
"thg nΓ y ngu vcl Δ‘m",
"sao m ko rep t z",
"chill quΓ‘ Δ‘i hehe crush iu wa",
"Bọn này toàn xàm lol vcl dmm",
"Γ©t Γ΄ Γ©t αΊ£o tung chαΊ£o luΓ΄n",
"Δ‘αΊΉppppp quΓ‘aaaaa",
]
for ex in examples:
print(f" IN: {ex}")
print(f" OUT: {normalize_teencode(ex)}")
print()