|
import csv |
|
import os |
|
from collections import Counter |
|
from morpheme.morpheme_stanza_v1 import extract_inflectional_morphemes |
|
|
|
|
|
MORPHEME_NUM_MAP = { |
|
"Plural": "1", |
|
"Possessive": "2", |
|
"3rd Person Singular": "3", |
|
"Past Tense": "4", |
|
"Progressive": "6" |
|
} |
|
MORPHEME_LABEL_MAP = {v: k for k, v in MORPHEME_NUM_MAP.items()} |
|
ALL_MORPHEME_LABELS = list(MORPHEME_NUM_MAP.keys()) |
|
|
|
class Detector: |
|
def analyze(self, text): |
|
annotations = extract_inflectional_morphemes(text) |
|
print(f"\n🔎 DEBUG: Analyzing utterance: '{text}'") |
|
for i, ann in enumerate(annotations): |
|
print(f" Annotation {i}: {ann}") |
|
filtered = [] |
|
for a in annotations: |
|
morph = a.get("inflectional_morpheme") |
|
word = a.get("word") |
|
if morph in MORPHEME_NUM_MAP and word: |
|
filtered.append((word.lower(), morph)) |
|
else: |
|
print(f" Skipping invalid annotation: {a}") |
|
print(f" Filtered predictions: {filtered}") |
|
return filtered |
|
|
|
def to_morpheme_string(morpheme_list, utterance): |
|
tokens = utterance.strip().split() |
|
token_tags = ['0'] * len(tokens) |
|
lowered_tokens = [t.lower() for t in tokens] |
|
|
|
for word, morph in morpheme_list: |
|
matches = [i for i, tok in enumerate(lowered_tokens) if tok == word] |
|
if matches: |
|
token_tags[matches[0]] = MORPHEME_NUM_MAP[morph] |
|
else: |
|
print(f"⚠ Word '{word}' not found in utterance: '{utterance}'") |
|
return ''.join(token_tags) |
|
|
|
def eval_morpheme(dataset_path, morpheme_detector): |
|
output_dir = "benchmark_result/morpheme" |
|
os.makedirs(output_dir, exist_ok=True) |
|
output_path = os.path.join(output_dir, "result.csv") |
|
|
|
with open(dataset_path, newline='', encoding='utf-8') as f: |
|
reader = csv.DictReader(f) |
|
data = list(reader) |
|
|
|
results = [] |
|
TP_counter = Counter() |
|
FP_counter = Counter() |
|
FN_counter = Counter() |
|
|
|
for i, row in enumerate(data): |
|
print("\n=== NEW SAMPLE ===") |
|
utterance = row["cleaned_transcription"] |
|
gold_str = row.get("morpheme_code", "") |
|
print(f"Row {i}: {utterance}") |
|
print(f"Raw GOLD code: '{gold_str}'") |
|
|
|
gold = [] |
|
if gold_str: |
|
gold_numbers = [x.strip() for x in gold_str.split()] |
|
gold = [MORPHEME_LABEL_MAP.get(num) for num in gold_numbers if num in MORPHEME_LABEL_MAP] |
|
print(f"Parsed GOLD labels: {gold}") |
|
|
|
pred_morphemes = morpheme_detector.analyze(utterance) |
|
|
|
gold_set = set(gold) |
|
pred_set = set([m for (_, m) in pred_morphemes]) |
|
|
|
TP = gold_set & pred_set |
|
FP = pred_set - gold_set |
|
FN = gold_set - pred_set |
|
|
|
if TP: print(f" TP: {TP}") |
|
if FP: print(f"⚠ FP: {FP}") |
|
if FN: print(f" FN: {FN}") |
|
if not TP and not FP and not FN: print("No match — check formatting.") |
|
|
|
for m in TP: |
|
TP_counter[m] += 1 |
|
for m in FP: |
|
FP_counter[m] += 1 |
|
for m in FN: |
|
FN_counter[m] += 1 |
|
|
|
predicted_str = to_morpheme_string(pred_morphemes, utterance) |
|
token_count = len(utterance.strip().split()) |
|
gold_str_fixed = ''.join(gold_str.split()).ljust(token_count, '0')[:token_count] |
|
|
|
results.append({ |
|
"utterance": utterance, |
|
"gold": gold_str_fixed, |
|
"predicted": predicted_str, |
|
}) |
|
|
|
with open(output_path, "w", newline='', encoding='utf-8') as f: |
|
writer = csv.DictWriter(f, fieldnames=["utterance", "gold", "predicted"]) |
|
writer.writeheader() |
|
writer.writerows(results) |
|
|
|
|
|
print("\n\n=== Evaluation Metrics ===") |
|
observed_labels = {m for m in TP_counter.keys() | FP_counter.keys() | FN_counter.keys()} |
|
if "Comparative" in observed_labels: |
|
eval_labels = ALL_MORPHEME_LABELS |
|
else: |
|
eval_labels = [m for m in ALL_MORPHEME_LABELS if m != "Comparative"] |
|
|
|
macro_p, macro_r, macro_f1 = 0, 0, 0 |
|
for label in eval_labels: |
|
TP = TP_counter[label] |
|
FP = FP_counter[label] |
|
FN = FN_counter[label] |
|
precision = TP / (TP + FP) if TP + FP > 0 else 0 |
|
recall = TP / (TP + FN) if TP + FN > 0 else 0 |
|
f1 = 2 * precision * recall / (precision + recall) if precision + recall > 0 else 0 |
|
macro_p += precision |
|
macro_r += recall |
|
macro_f1 += f1 |
|
print(f"{MORPHEME_NUM_MAP[label]} ({label}): Precision={precision:.3f}, Recall={recall:.3f}, F1={f1:.3f}") |
|
|
|
n = len(eval_labels) |
|
print("\n-- Macro-Averaged Metrics --") |
|
print(f"Precision: {macro_p / n:.3f}") |
|
print(f"Recall: {macro_r / n:.3f}") |
|
print(f"F1 Score: {macro_f1 / n:.3f}") |
|
|
|
if __name__ == "__main__": |
|
dataset_path = "./data/enni_salt_for_morpheme/test.csv" |
|
morpheme_detector = Detector() |
|
eval_morpheme(dataset_path, morpheme_detector) |
|
|