simon-clmtd commited on
Commit
0f9f04c
Β·
verified Β·
1 Parent(s): ff372d4

Upload folder using huggingface_hub

Browse files
__pycache__/app.cpython-312.pyc ADDED
Binary file (11.7 kB). View file
 
__pycache__/inference.cpython-312.pyc CHANGED
Binary files a/__pycache__/inference.cpython-312.pyc and b/__pycache__/inference.cpython-312.pyc differ
 
app.py CHANGED
@@ -3,7 +3,8 @@ Gradio Web Application for Neural Transducer Morphological Inflection.
3
  Features:
4
  - Language selection (English, German, French, Italian)
5
  - Side-by-side comparison (100 examples vs 1,000 examples)
6
- - Decoding mode (Greedy vs Beam search)
 
7
  - Detailed action alignment traces (COPY, DELETE, INSERT, SUBSTITUTE)
8
  - Curated linguistic examples
9
  """
@@ -40,7 +41,26 @@ def format_action_trace(trace):
40
  return "\n".join(rows)
41
 
42
 
43
- def inflect(language_label, mode, lemma, features_str):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  if not lemma or not features_str:
45
  return (
46
  "Please provide both a lemma and UniMorph features.",
@@ -48,11 +68,15 @@ def inflect(language_label, mode, lemma, features_str):
48
  "-",
49
  "Waiting for valid input...",
50
  "",
 
51
  )
52
 
53
  iso = LANGUAGE_MAP.get(language_label, "eng")
54
  lemma = lemma.strip()
55
  features_str = features_str.strip()
 
 
 
56
 
57
  try:
58
  if mode == "Compare Both (100 vs 1000)":
@@ -60,9 +84,15 @@ def inflect(language_label, mode, lemma, features_str):
60
  res_1000 = engine.predict(iso, "1000", lemma, features_str, beam_width=1)
61
 
62
  pred_100 = res_100.predicted_form
 
 
 
63
  score_100 = f"{res_100.decoder_score:.4f}"
64
 
65
  pred_1000 = res_1000.predicted_form
 
 
 
66
  score_1000 = f"{res_1000.decoder_score:.4f}"
67
 
68
  trace_text = (
@@ -72,22 +102,36 @@ def inflect(language_label, mode, lemma, features_str):
72
 
73
  warn = ""
74
  if res_1000.unknown_features:
75
- warn = f"⚠️ Unseen feature tags (treated as UNK): {', '.join(res_1000.unknown_features)}"
76
 
77
- return pred_100, score_100, pred_1000, score_1000, trace_text, warn
78
 
79
  elif mode == "100 Examples (Low-Resource)":
80
  res = engine.predict(iso, "100", lemma, features_str, beam_width=1)
81
- warn = f"⚠️ Unseen feature tags: {', '.join(res.unknown_features)}" if res.unknown_features else ""
82
- return res.predicted_form, f"{res.decoder_score:.4f}", "-", "-", format_action_trace(res.action_trace), warn
 
 
 
83
 
84
  else: # 1000 Examples
85
  res = engine.predict(iso, "1000", lemma, features_str, beam_width=1)
86
- warn = f"⚠️ Unseen feature tags: {', '.join(res.unknown_features)}" if res.unknown_features else ""
87
- return "-", "-", res.predicted_form, f"{res.decoder_score:.4f}", format_action_trace(res.action_trace), warn
 
 
 
88
 
89
  except Exception as e:
90
- return f"Error: {e}", "-", "-", "-", str(e), ""
 
 
 
 
 
 
 
 
91
 
92
 
93
  # Gradio UI definition
@@ -112,6 +156,17 @@ with gr.Blocks(title="Neural Transducer: Morphological Inflection", theme=gr.the
112
  choices=["Compare Both (100 vs 1000)", "100 Examples (Low-Resource)", "1000 Examples (Medium-Resource)"],
113
  value="Compare Both (100 vs 1000)",
114
  )
 
 
 
 
 
 
 
 
 
 
 
115
  lemma_input = gr.Textbox(
116
  label="Lemma",
117
  placeholder="e.g. sing",
@@ -122,6 +177,12 @@ with gr.Blocks(title="Neural Transducer: Morphological Inflection", theme=gr.the
122
  placeholder="e.g. V;PST",
123
  value="V;PST",
124
  )
 
 
 
 
 
 
125
  run_btn = gr.Button("Generate Inflection", variant="primary")
126
 
127
  with gr.Column(scale=1):
@@ -136,26 +197,40 @@ with gr.Blocks(title="Neural Transducer: Morphological Inflection", theme=gr.the
136
 
137
  warning_box = gr.Markdown("")
138
 
 
 
 
 
139
  with gr.Accordion("πŸ” Transducer Edit Action Trace (Alignment Steps)", open=False):
140
  trace_box = gr.Code(label="Action Sequence", language="markdown", interactive=False)
141
 
142
  gr.Markdown("### πŸ’‘ Click an Example to Load")
143
  example_rows = []
144
  for ex in CURATED_EXAMPLES:
145
- example_rows.append([ex["language"], "Compare Both (100 vs 1000)", ex["lemma"], ex["features"]])
146
 
147
  gr.Examples(
148
  examples=example_rows,
149
- inputs=[lang_dropdown, mode_radio, lemma_input, feat_input],
150
- outputs=[out_100, score_100, out_1000, score_1000, trace_box, warning_box],
151
  fn=inflect,
152
  cache_examples=False,
153
  )
154
 
 
 
 
 
 
 
 
 
 
 
155
  run_btn.click(
156
  fn=inflect,
157
- inputs=[lang_dropdown, mode_radio, lemma_input, feat_input],
158
- outputs=[out_100, score_100, out_1000, score_1000, trace_box, warning_box],
159
  )
160
 
161
  if __name__ == "__main__":
 
3
  Features:
4
  - Language selection (English, German, French, Italian)
5
  - Side-by-side comparison (100 examples vs 1,000 examples)
6
+ - Random sampling from official CoNLL-SIGMORPHON 2017 Dev and Test splits with gold target display
7
+ - Training exposure indicators (Lemma seen? Feature combination seen?) for both regimes
8
  - Detailed action alignment traces (COPY, DELETE, INSERT, SUBSTITUTE)
9
  - Curated linguistic examples
10
  """
 
41
  return "\n".join(rows)
42
 
43
 
44
+ def build_coverage_markdown(lang_iso, lemma, features_str):
45
+ if not lemma or not features_str:
46
+ return ""
47
+ cov = engine.check_training_coverage(lang_iso, lemma, features_str)
48
+ low_lemma = "βœ… Seen in training" if cov["100"]["lemma_seen"] else "❌ Unseen (zero-shot lemma)"
49
+ med_lemma = "βœ… Seen in training" if cov["1000"]["lemma_seen"] else "❌ Unseen (zero-shot lemma)"
50
+
51
+ low_feat = "βœ… Seen in training" if cov["100"]["features_seen"] else "❌ Unseen combination"
52
+ med_feat = "βœ… Seen in training" if cov["1000"]["features_seen"] else "❌ Unseen combination"
53
+
54
+ md = f"""
55
+ | Training Exposure | 100 Examples (Low) | 1,000 Examples (Medium) |
56
+ | :--- | :--- | :--- |
57
+ | **Lemma** (`{lemma}`) | {low_lemma} | {med_lemma} |
58
+ | **Features** (`{features_str}`) | {low_feat} | {med_feat} |
59
+ """
60
+ return md.strip()
61
+
62
+
63
+ def inflect(language_label, mode, lemma, features_str, gold_target=""):
64
  if not lemma or not features_str:
65
  return (
66
  "Please provide both a lemma and UniMorph features.",
 
68
  "-",
69
  "Waiting for valid input...",
70
  "",
71
+ "",
72
  )
73
 
74
  iso = LANGUAGE_MAP.get(language_label, "eng")
75
  lemma = lemma.strip()
76
  features_str = features_str.strip()
77
+ gold_target = (gold_target or "").strip()
78
+
79
+ cov_md = build_coverage_markdown(iso, lemma, features_str)
80
 
81
  try:
82
  if mode == "Compare Both (100 vs 1000)":
 
84
  res_1000 = engine.predict(iso, "1000", lemma, features_str, beam_width=1)
85
 
86
  pred_100 = res_100.predicted_form
87
+ if gold_target:
88
+ status_100 = " βœ… Match" if pred_100 == gold_target else " ❌ Mismatch"
89
+ pred_100 = f"{pred_100}{status_100}"
90
  score_100 = f"{res_100.decoder_score:.4f}"
91
 
92
  pred_1000 = res_1000.predicted_form
93
+ if gold_target:
94
+ status_1000 = " βœ… Match" if pred_1000 == gold_target else " ❌ Mismatch"
95
+ pred_1000 = f"{pred_1000}{status_1000}"
96
  score_1000 = f"{res_1000.decoder_score:.4f}"
97
 
98
  trace_text = (
 
102
 
103
  warn = ""
104
  if res_1000.unknown_features:
105
+ warn = f"⚠️ Unseen individual tag tokens (treated as UNK): {', '.join(res_1000.unknown_features)}"
106
 
107
+ return pred_100, score_100, pred_1000, score_1000, trace_text, warn, cov_md
108
 
109
  elif mode == "100 Examples (Low-Resource)":
110
  res = engine.predict(iso, "100", lemma, features_str, beam_width=1)
111
+ pred = res.predicted_form
112
+ if gold_target:
113
+ pred += " βœ… Match" if pred == gold_target else " ❌ Mismatch"
114
+ warn = f"⚠️ Unseen individual tag tokens: {', '.join(res.unknown_features)}" if res.unknown_features else ""
115
+ return pred, f"{res.decoder_score:.4f}", "-", "-", format_action_trace(res.action_trace), warn, cov_md
116
 
117
  else: # 1000 Examples
118
  res = engine.predict(iso, "1000", lemma, features_str, beam_width=1)
119
+ pred = res.predicted_form
120
+ if gold_target:
121
+ pred += " βœ… Match" if pred == gold_target else " ❌ Mismatch"
122
+ warn = f"⚠️ Unseen individual tag tokens: {', '.join(res.unknown_features)}" if res.unknown_features else ""
123
+ return "-", "-", pred, f"{res.decoder_score:.4f}", format_action_trace(res.action_trace), warn, cov_md
124
 
125
  except Exception as e:
126
+ return f"Error: {e}", "-", "-", "-", str(e), "", cov_md
127
+
128
+
129
+ def sample_random_item(language_label, split_choice):
130
+ iso = LANGUAGE_MAP.get(language_label, "eng")
131
+ sample = engine.get_random_sample(iso, split=split_choice)
132
+ if not sample:
133
+ return "sing", "V;PST", "", "No samples available."
134
+ return sample["lemma"], sample["features"], sample["target"]
135
 
136
 
137
  # Gradio UI definition
 
156
  choices=["Compare Both (100 vs 1000)", "100 Examples (Low-Resource)", "1000 Examples (Medium-Resource)"],
157
  value="Compare Both (100 vs 1000)",
158
  )
159
+
160
+ with gr.Group():
161
+ gr.Markdown("#### 🎲 Benchmark Evaluation Sampler")
162
+ with gr.Row():
163
+ split_selector = gr.Radio(
164
+ label="Benchmark Split",
165
+ choices=["Dev Set", "Test Set"],
166
+ value="Dev Set",
167
+ )
168
+ sample_btn = gr.Button("🎲 Sample Random Item", variant="secondary")
169
+
170
  lemma_input = gr.Textbox(
171
  label="Lemma",
172
  placeholder="e.g. sing",
 
177
  placeholder="e.g. V;PST",
178
  value="V;PST",
179
  )
180
+ gold_target_input = gr.Textbox(
181
+ label="Gold Target Form (Optional / Benchmark Reference)",
182
+ placeholder="Populated automatically when sampling from Dev/Test",
183
+ value="",
184
+ )
185
+
186
  run_btn = gr.Button("Generate Inflection", variant="primary")
187
 
188
  with gr.Column(scale=1):
 
197
 
198
  warning_box = gr.Markdown("")
199
 
200
+ with gr.Group():
201
+ gr.Markdown("### πŸ“Š Training Data Exposure")
202
+ coverage_box = gr.Markdown("Enter an input or sample from the benchmark to view coverage.")
203
+
204
  with gr.Accordion("πŸ” Transducer Edit Action Trace (Alignment Steps)", open=False):
205
  trace_box = gr.Code(label="Action Sequence", language="markdown", interactive=False)
206
 
207
  gr.Markdown("### πŸ’‘ Click an Example to Load")
208
  example_rows = []
209
  for ex in CURATED_EXAMPLES:
210
+ example_rows.append([ex["language"], "Compare Both (100 vs 1000)", ex["lemma"], ex["features"], ""])
211
 
212
  gr.Examples(
213
  examples=example_rows,
214
+ inputs=[lang_dropdown, mode_radio, lemma_input, feat_input, gold_target_input],
215
+ outputs=[out_100, score_100, out_1000, score_1000, trace_box, warning_box, coverage_box],
216
  fn=inflect,
217
  cache_examples=False,
218
  )
219
 
220
+ sample_btn.click(
221
+ fn=sample_random_item,
222
+ inputs=[lang_dropdown, split_selector],
223
+ outputs=[lemma_input, feat_input, gold_target_input],
224
+ ).then(
225
+ fn=inflect,
226
+ inputs=[lang_dropdown, mode_radio, lemma_input, feat_input, gold_target_input],
227
+ outputs=[out_100, score_100, out_1000, score_1000, trace_box, warning_box, coverage_box],
228
+ )
229
+
230
  run_btn.click(
231
  fn=inflect,
232
+ inputs=[lang_dropdown, mode_radio, lemma_input, feat_input, gold_target_input],
233
+ outputs=[out_100, score_100, out_1000, score_1000, trace_box, warning_box, coverage_box],
234
  )
235
 
236
  if __name__ == "__main__":
data_index.json ADDED
The diff for this file is too large to render. See raw diff
 
inference.py CHANGED
@@ -83,3 +83,49 @@ class TransducerSpaceEngine:
83
  features_str=features,
84
  beam_width=beam_width,
85
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
  features_str=features,
84
  beam_width=beam_width,
85
  )
86
+
87
+ def _load_data_index(self) -> Dict:
88
+ if not hasattr(self, "_data_index"):
89
+ index_path = os.path.join(os.path.dirname(__file__), "data_index.json")
90
+ if not os.path.exists(index_path):
91
+ # Fallback to demo_core location if needed
92
+ index_path = os.path.join(os.path.dirname(__file__), "..", "demo_core", "data_index.json")
93
+ if os.path.exists(index_path):
94
+ with open(index_path, "r", encoding="utf-8") as f:
95
+ self._data_index = json.load(f)
96
+ else:
97
+ self._data_index = {}
98
+ return self._data_index
99
+
100
+ def get_random_sample(self, lang: str, split: str = "dev") -> Optional[Dict[str, str]]:
101
+ import random
102
+ data = self._load_data_index()
103
+ split_key = "dev" if split.lower().startswith("dev") else "test"
104
+ samples = data.get(lang, {}).get(split_key, [])
105
+ if not samples:
106
+ return None
107
+ return random.choice(samples)
108
+
109
+ def check_training_coverage(self, lang: str, lemma: str, features_str: str) -> Dict[str, Dict[str, bool]]:
110
+ data = self._load_data_index()
111
+ lang_data = data.get(lang, {}).get("training_coverage", {})
112
+ lemma = lemma.strip()
113
+ features_norm = set(features_str.strip().split(";"))
114
+
115
+ result = {}
116
+ for regime in ["100", "1000"]:
117
+ cov = lang_data.get(regime, {})
118
+ seen_lemmas = set(cov.get("lemmas", []))
119
+ raw_feats = cov.get("features", [])
120
+ seen_feats_sets = [set(f.split(";")) for f in raw_feats]
121
+
122
+ lemma_seen = lemma in seen_lemmas
123
+ # Feat combination seen if exact string matches or tag set matches
124
+ feats_seen = (features_str.strip() in raw_feats) or (features_norm in seen_feats_sets)
125
+
126
+ result[regime] = {
127
+ "lemma_seen": lemma_seen,
128
+ "features_seen": feats_seen,
129
+ }
130
+ return result
131
+