klon8-base / src /scripts /title_clean.py
voraseth's picture
Upload folder using huggingface_hub
7d3eb8f verified
Raw History Blame Contribute Delete
6.7 kB
"""One shared title cleaner for training titles and every prompt set (user 2026-09-29: "remove the decoration from
all topic titles").
Forum titles carry decoration that is not part of the topic: symbol runs (❦ • ♥ ✿ ◆ ~ * ...), repeated punctuation
("หญิงน่ะหรือ.....?"), reply prefixes ("Re:"), bracketed notes, isolated decorative Latin letters (the "O ... O"
frames), and "+ member" signatures. Left in place, the syllable segmenter glues punctuation into syllables
('เหมือน. ..' -> 'เห', 'มือ', 'น.'), which puts junk units into the title vocabulary and the conditioning signal.
clean_title(t) keeps Thai text, Thai digits and meaningful Latin words/digits (VS, VITAS, 2020), replaces every
other character by a space, drops the decorations above, and collapses whitespace. It never changes Thai letters and never returns an empty
title (a title of symbols only is kept as written, stripped). Only a bracket at the start or end is a note;
a bracket inside the title keeps its text (ขอเป็นทาส(หัวใจ)เธอ).
Usage: title_clean.py report OUT.json # before/after counts for the training titles and every prompt set
"""
import re, sys, json, unicodedata
REPLY = re.compile(r"^\s*(?:re|fw|fwd)\s*(?:[::]\s*|(?=[“”\"'‘’«]))", re.I) # "Re: ...", "re“...”"
MEMBER = re.compile(r"\s*\+\s*[A-Za-z0-9_.]+\s*$") # "... + benuser" signature at the end
BRACKET_LEAD = re.compile(r"^\s*[\(\[\{(][^\)\]\})]*[\)\]\})]") # "[กลอนสด] ..." - a note, not topic
BRACKET_TAIL = re.compile(r"[\(\[\{(][^\)\]\})]*[\)\]\})][^ก-๙A-Za-z0-9]*$") # "... (แฮะๆ)"; mid-title brackets keep their text
EMOTICON = re.compile(r"<3+|\^\^+|[:;]-?[\)\(DP]") # "<3", "^^", ":)" - not "3"
KEEP = re.compile(r"[ก-ฺเ-๎๐-๙A-Za-z0-9ๆ ]")
DECOR_LATIN = {"o", "x", "oo", "ooo", "xo", "ox", "xx", "pz", "t"} # frame letters: "O ขอใจ O", "XX ... XX", lone T
def clean_title(t):
t = orig = unicodedata.normalize("NFC", t or "")
t = REPLY.sub("", t)
t = MEMBER.sub("", t)
t = EMOTICON.sub(" ", t)
inner = BRACKET_TAIL.sub(" ", BRACKET_LEAD.sub(" ", t))
if re.search(r"[ก-๎]", inner): t = inner # drop a leading/trailing bracketed note unless it IS the title
t = "".join(ch if KEEP.match(ch) else " " for ch in t)
words = [w for w in t.split() if w.lower() not in DECOR_LATIN]
# a Latin word that sits between Thai words without spaces in the raw title (เป็ดVSไก่) was split by the
# symbol pass only if it was surrounded by symbols; Thai runs and Latin runs are otherwise kept as written
out = re.sub(r"\s+", " ", " ".join(words)).strip()
return out or re.sub(r"\s+", " ", orig).strip() # never empty: a title of symbols only stays as written
def _files():
import glob
return ["data/modern_bot_train.jsonl"] + sorted(glob.glob("data/prompts/*.jsonl"))
def report(out):
import random
rec, ex = {}, []
for f in _files():
n = ch = emp = 0
for l in open(f, encoding="utf-8"):
if not l.strip(): continue
r = json.loads(l)
for k in ("title", "text", "prompt"):
if k in r and isinstance(r[k], str):
n += 1; c = clean_title(r[k])
if c != r[k]:
ch += 1
if len(ex) < 400: ex.append((f, r[k], c))
if not c: emp += 1
break
rec[f] = {"titles": n, "changed": ch, "empty_after": emp}
random.Random(0).shuffle(ex)
json.dump({"files": rec, "examples": ex[:30]}, open(out, "w", encoding="utf-8"), ensure_ascii=False, indent=1)
for f, v in rec.items(): print(f" {f}: {v['titles']} titles, {v['changed']} changed, {v['empty_after']} empty after cleaning")
def _test():
cases = {
"• • ♥ ขอใจ..ได้ไหม ♥ • •": "ขอใจ ได้ไหม",
"Re: อยากอยู่เป็นคู่ทีไรเจ็บทุกที ชิ": "อยากอยู่เป็นคู่ทีไรเจ็บทุกที ชิ",
"อยากเป็นเด็ก~~~~%?": "อยากเป็นเด็ก",
"รู้สึกเหมือน. ..ไร้พระจันทร์ ((": "รู้สึกเหมือน ไร้พระจันทร์",
"เป็ดVSไก่": "เป็ดVSไก่",
"O ฝากเพลง O": "ฝากเพลง",
"❦• แอบรักคนเขียนกลอนค๊า •❦": "แอบรักคนเขียนกลอนค๊า",
"หญิงน่ะหรือ.....?": "หญิงน่ะหรือ",
"ใครสักคน + benuser": "ใครสักคน",
"คิดถึง (แฮะๆ)": "คิดถึง",
"(กลอนสด)": "กลอนสด",
"ปี 2020 ของฉัน": "ปี 2020 ของฉัน",
"ทุกๆวัน": "ทุกๆวัน",
"รักร้าวเพราะโควิด": "รักร้าวเพราะโควิด",
"": "",
"ขอเป็นทาส(หัวใจ)เธอ": "ขอเป็นทาส หัวใจ เธอ",
"[กลอนสด] ฝนตก": "ฝนตก",
"re“ฝากใจ”": "ฝากใจ",
"remember ฉัน": "remember ฉัน",
"XX คิดถึง XX": "คิดถึง",
"Pz ลาก่อน": "ลาก่อน",
"T รักเธอ T": "รักเธอ",
"?": "?",
"♥♥": "♥♥",
"รับสมัครคนแก้เหงา (ตามสเปคนะเออ) <3": "รับสมัครคนแก้เหงา",
"คนนี้แหละ...ใช่เลย ^^": "คนนี้แหละ ใช่เลย",
}
bad = [(k, clean_title(k), v) for k, v in cases.items() if clean_title(k) != v]
assert not bad, bad
# never changes a Thai letter: the Thai characters of the output are a subsequence of the input's
for k in cases:
o = clean_title(k); it = iter(k)
assert all(ch in it for ch in o if "ก" <= ch <= "๎"), k
assert o or not k.strip(), k
print(f"title_clean: {len(cases)} cases pass")
if __name__ == "__main__":
if sys.argv[1:2] == ["report"]: report(sys.argv[2])
elif sys.argv[1:2] == ["test"]: _test()
else: print(__doc__)