Spaces:
Paused
Paused
File size: 5,963 Bytes
d63774a | 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 | import re
from collections import Counter
from underthesea import text_normalize as uts_text_normalize, word_tokenize
_MEDICAL_TERM_MAP = {
"xray": "x-quang",
"x ray": "x-quang",
"x-ray": "x-quang",
"x quang": "x-quang",
"mri scan": "mri",
"mr": "mri",
"ct scan": "ct",
"ct-scan": "ct",
"cat scan": "ct",
"computed tomography": "ct",
"transverse plane": "mặt phẳng ngang",
"transverse plane": "mặt phẳng ngang",
"coronal plane": "mặt phẳng vành",
"sagittal plane": "mặt phẳng dọc",
"elliptical": "hình elip",
"spleen": "lách",
"liver": "gan",
"lung": "phổi",
"lungs": "phổi",
"heart": "tim",
"brain": "não",
"kidney": "thận",
"bladder": "bàng quang",
"cardiomegaly": "tim to",
}
_NON_CANONICAL_ALIASES = {
"xray",
"x ray",
"x-ray",
"x quang",
"mri scan",
"mr",
"ct scan",
"ct-scan",
"cat scan",
"computed tomography",
"transverse plane",
"coronal plane",
"sagittal plane",
"elliptical",
"spleen",
"liver",
"lung",
"lungs",
"heart",
"brain",
"kidney",
"bladder",
"cardiomegaly",
}
def text_normalize(text: str) -> str:
"""Wrapper để chuẩn hóa Unicode và spacing cho tiếng Việt."""
if not text:
return ""
return uts_text_normalize(str(text))
def normalize_answer(text: str) -> str:
"""
Chuẩn hóa đáp án về dạng canonical để train/eval ổn định.
"""
if not text:
return ""
text = text_normalize(str(text))
text = text.replace("_", " ")
text = text.lower().strip()
text = re.sub(r"[@#]{1,2}", " ", text)
text = re.sub(r"[“”\"']", "", text)
text = re.sub(r"[,:;!?()\[\]{}]+", " ", text)
text = re.sub(r"\s+", " ", text).strip()
for src, dst in sorted(_MEDICAL_TERM_MAP.items(), key=lambda item: -len(item[0])):
text = re.sub(rf"\b{re.escape(src)}\b", dst, text)
text = re.sub(r"\s+", " ", text).strip()
text = re.sub(r"[.\-]+$", "", text).strip()
return text
def _tokenize_vietnamese_words(text: str) -> list[str]:
normalized = normalize_answer(text)
if not normalized:
return []
try:
tokens = word_tokenize(normalized)
return [token.strip() for token in tokens if token and token.strip()]
except Exception:
return normalized.split()
def count_words(text: str) -> int:
return len(_tokenize_vietnamese_words(text))
def _trim_to_max_words(text: str, max_words: int) -> str:
words = _tokenize_vietnamese_words(text)
if len(words) <= max_words:
return " ".join(words)
return " ".join(words[:max_words])
def _choose_best_answer_text(answer_vi: str, answer_full_vi: str, max_words: int) -> str:
short_answer = normalize_answer(answer_vi)
full_answer = normalize_answer(answer_full_vi)
if short_answer and count_words(short_answer) <= max_words:
return short_answer
if full_answer:
return _trim_to_max_words(full_answer, max_words)
return _trim_to_max_words(short_answer, max_words)
def get_target_answer(item: dict, max_words: int = 10) -> str:
"""
Chọn target answer ngắn, chuẩn hóa và không vượt quá số từ cho phép.
"""
answer_vi = item.get("answer_vi", "")
answer_full_vi = item.get("answer_full_vi", "")
answer = _choose_best_answer_text(answer_vi, answer_full_vi, max_words=max_words)
if answer:
return answer
fallback = item.get("answer", "")
return _trim_to_max_words(fallback, max_words)
def postprocess_answer(text: str, max_words: int = 10) -> str:
"""
Chuẩn hóa output model và cắt ngắn về tối đa `max_words`.
Không mở rộng câu trả lời để tránh làm xấu exact match.
"""
if not text:
return ""
text = clean_vqa_output(text)
text = normalize_answer(text)
return _trim_to_max_words(text, max_words=max_words)
def is_medical_term_compliant(text: str) -> bool:
"""
Heuristic nhẹ: không còn alias y khoa phổ biến chưa canonicalize.
"""
normalized = normalize_answer(text)
if not normalized:
return False
for alias in _NON_CANONICAL_ALIASES:
if re.search(rf"\b{re.escape(alias)}\b", normalized):
return False
return True
def majority_answer(answers: list[str]) -> str:
"""
Trả về câu trả lời xuất hiện nhiều nhất trong danh sách.
"""
if not answers:
return ""
if isinstance(answers, str):
return normalize_answer(answers)
counts = Counter([normalize_answer(a) for a in answers])
return counts.most_common(1)[0][0]
def clean_vqa_output(text: str) -> str:
"""
Làm sạch output từ tokenizer trước khi postprocess.
"""
if not text:
return ""
text = re.sub(r"@@\s?", "", text)
text = re.sub(r"##_?", "", text)
text = re.sub(r"^\s*yes\s*,?\s*", "có ", text, flags=re.IGNORECASE)
text = re.sub(r"^\s*no\s*,?\s*", "không ", text, flags=re.IGNORECASE)
text = re.sub(
r"^\s*(the answer is|the image is|this image is|the scan is|the ct is|the mri is|there is|there are)\s+",
"",
text,
flags=re.IGNORECASE,
)
text = re.sub(
r"^(có|không)\s+(the\s+)?(image|scan|x-ray|xray|mri|ct|picture|photo|radiograph)\s+(is|shows?|depicts?|demonstrates?|reveals?|indicates?|presents?)\s+",
r"\1 ",
text,
flags=re.IGNORECASE,
)
text = re.sub(
r"^(the\s+)?(image|scan|x-ray|xray|mri|ct|picture|photo|radiograph)\s+(is|shows?|depicts?|demonstrates?|reveals?|indicates?|presents?)\s+",
"",
text,
flags=re.IGNORECASE,
)
text = re.sub(r"\b(answer|response|assistant|trả lời)\b\s*:?\s*$", "", text, flags=re.IGNORECASE)
text = re.sub(r"\s+", " ", text).strip()
return text
|