chyams Claude Opus 4.6 commited on
Commit
f79d011
Β·
0 Parent(s):

Embedding Explorer: initial HuggingFace Space

Browse files

Two-tab Gradio app for Lecture 7 (Embeddings & Transformers):
- Explore: 3D word visualization with click-to-show neighbors
- Vector Math: word arithmetic (king - man + woman β‰ˆ queen)

GloVe 300d embeddings, Plotly 3D, configurable examples via env vars.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Files changed (3) hide show
  1. README.md +39 -0
  2. app.py +542 -0
  3. requirements.txt +5 -0
README.md ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Embedding Explorer
3
+ emoji: πŸ“
4
+ colorFrom: purple
5
+ colorTo: indigo
6
+ sdk: gradio
7
+ sdk_version: "5.12.0"
8
+ app_file: app.py
9
+ pinned: false
10
+ ---
11
+
12
+ # Embedding Explorer
13
+
14
+ Interactive word vector visualization for the Responsible AI course at Huston-Tillotson University.
15
+
16
+ ## Features
17
+
18
+ - **Explore** β€” Type words, see them in 3D space. Similar words cluster together. Click a word to see its nearest neighbors.
19
+ - **Vector Math** β€” Word arithmetic like `king - man + woman β‰ˆ queen`. Results reveal hidden relationships encoded in the vectors.
20
+
21
+ ## Configuration
22
+
23
+ All examples are configurable via HuggingFace Space environment variables (no code changes needed):
24
+
25
+ | Variable | Default | Description |
26
+ |----------|---------|-------------|
27
+ | `EXPLORE_EXAMPLES` | `["dog cat fish car truck", ...]` | JSON list of word groups for Explore tab |
28
+ | `ARITHMETIC_EXAMPLES` | `["king - man + woman", ...]` | JSON list of expressions for Vector Math tab |
29
+ | `N_NEIGHBORS` | `8` | Number of nearest neighbors to show on click |
30
+
31
+ ## Model
32
+
33
+ Uses [GloVe](https://nlp.stanford.edu/projects/glove/) (Global Vectors for Word Representation) trained on Wikipedia + Gigaword β€” 400K words, 300 dimensions. Downloaded automatically on first startup via gensim (~376 MB, cached for subsequent runs).
34
+
35
+ ## Course
36
+
37
+ Responsible AI: Technology, Power, and Justice (COSC-2300A)
38
+ Huston-Tillotson University, Spring 2026
39
+ Instructor: Chris Hyams
app.py ADDED
@@ -0,0 +1,542 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Embedding Explorer β€” Interactive word vector visualization
3
+ Responsible AI: Technology, Power, and Justice
4
+ Huston-Tillotson University
5
+
6
+ Two modes:
7
+ Explore β€” type words, see them in 3D, click to show nearest neighbors
8
+ Vector Math β€” word arithmetic (king - man + woman β‰ˆ queen)
9
+
10
+ Configuration (HuggingFace Space environment variables):
11
+ EXPLORE_EXAMPLES β€” JSON list of space-separated word groups
12
+ ARITHMETIC_EXAMPLES β€” JSON list of arithmetic expressions
13
+ N_NEIGHBORS β€” number of neighbors to show on click (default 8)
14
+ """
15
+
16
+ import os
17
+ import json
18
+ import re
19
+
20
+ import numpy as np
21
+ import plotly.graph_objects as go
22
+ from sklearn.decomposition import PCA
23
+ import gradio as gr
24
+
25
+ # ── Configuration (all changeable via HF Space env vars) ─────
26
+
27
+ EXPLORE_EXAMPLES = json.loads(os.environ.get("EXPLORE_EXAMPLES", json.dumps([
28
+ "dog cat fish car truck",
29
+ "king queen man woman prince princess",
30
+ "paris france berlin germany tokyo japan",
31
+ ])))
32
+
33
+ ARITHMETIC_EXAMPLES = json.loads(os.environ.get("ARITHMETIC_EXAMPLES", json.dumps([
34
+ "king - man + woman",
35
+ "paris - france + germany",
36
+ "germany - hitler + italy",
37
+ ])))
38
+
39
+ N_NEIGHBORS = int(os.environ.get("N_NEIGHBORS", "8"))
40
+
41
+ # ── Course design system colors ──────────────────────────────
42
+
43
+ PURPLE = "#63348d"
44
+ PURPLE_LIGHT = "#ded9f4"
45
+ PURPLE_DARK = "#301848"
46
+ GOLD = "#f0c040"
47
+ PINK = "#de95a0"
48
+ DARK = "#1a1a2e"
49
+ GRAY = "#888888"
50
+ BG = "#fafafa"
51
+
52
+ # ── Load GloVe embeddings on startup ─────────────────────────
53
+
54
+ print("=" * 60)
55
+ print("Loading GloVe word vectors (glove-wiki-gigaword-300)...")
56
+ print("First run downloads ~376 MB β€” subsequent starts use cache.")
57
+ print("=" * 60)
58
+
59
+ import gensim.downloader as api
60
+
61
+ model = api.load("glove-wiki-gigaword-300")
62
+ VOCAB = set(model.key_to_index.keys())
63
+ DIMS = model.vector_size
64
+
65
+ print(f"Ready: {len(VOCAB):,} words, {DIMS} dimensions each")
66
+
67
+
68
+ # ── Helpers ──────────────────────────────────────────────────
69
+
70
+ def tokenize(text):
71
+ """Split text into lowercase words, deduplicated, order preserved."""
72
+ words = re.split(r"[,\s]+", text.lower().strip())
73
+ seen = set()
74
+ out = []
75
+ for w in words:
76
+ w = w.strip()
77
+ if w and w not in seen:
78
+ seen.add(w)
79
+ out.append(w)
80
+ return out
81
+
82
+
83
+ def parse_expression(expr):
84
+ """Parse 'king - man + woman' β†’ (positives, negatives, ordered).
85
+
86
+ ordered is [(word, sign), ...] for display formatting.
87
+ """
88
+ tokens = re.findall(r"[a-z']+|[+-]", expr.lower())
89
+ pos, neg, ordered = [], [], []
90
+ sign = "+"
91
+ for t in tokens:
92
+ if t in "+-":
93
+ sign = t
94
+ elif t in VOCAB:
95
+ (pos if sign == "+" else neg).append(t)
96
+ ordered.append((t, sign))
97
+ return pos, neg, ordered
98
+
99
+
100
+ def fit_pca_3d(vectors):
101
+ """PCA β†’ 3D. Returns (coords, fitted_pca)."""
102
+ n = len(vectors)
103
+ nc = min(3, n, vectors.shape[1])
104
+ pca = PCA(n_components=nc)
105
+ coords = pca.fit_transform(vectors)
106
+ if nc < 3:
107
+ coords = np.hstack([coords, np.zeros((n, 3 - nc))])
108
+ return coords, pca
109
+
110
+
111
+ def project_3d(pca, vectors):
112
+ """Project new vectors into an existing PCA space."""
113
+ coords = pca.transform(vectors)
114
+ if coords.shape[1] < 3:
115
+ coords = np.hstack([coords, np.zeros((len(vectors), 3 - coords.shape[1]))])
116
+ return coords
117
+
118
+
119
+ def layout_3d(height=560):
120
+ """Shared Plotly 3D layout."""
121
+ return dict(
122
+ scene=dict(
123
+ xaxis=dict(visible=False),
124
+ yaxis=dict(visible=False),
125
+ zaxis=dict(visible=False),
126
+ bgcolor=BG,
127
+ camera=dict(eye=dict(x=1.5, y=1.5, z=1.2)),
128
+ ),
129
+ paper_bgcolor="white",
130
+ margin=dict(l=0, r=0, t=10, b=10),
131
+ showlegend=True,
132
+ legend=dict(
133
+ yanchor="top", y=0.99, xanchor="left", x=0.01,
134
+ bgcolor="rgba(255,255,255,0.85)",
135
+ font=dict(family="Inter, sans-serif", size=12),
136
+ ),
137
+ height=height,
138
+ font=dict(family="Inter, sans-serif"),
139
+ )
140
+
141
+
142
+ def blank(msg):
143
+ """Empty placeholder figure with a centered message."""
144
+ fig = go.Figure()
145
+ fig.add_annotation(
146
+ text=msg, xref="paper", yref="paper", x=0.5, y=0.5,
147
+ showarrow=False,
148
+ font=dict(size=16, color=GRAY, family="Inter, sans-serif"),
149
+ )
150
+ fig.update_layout(
151
+ xaxis_visible=False, yaxis_visible=False,
152
+ height=560, paper_bgcolor="white", plot_bgcolor="white",
153
+ margin=dict(l=0, r=0, t=0, b=0),
154
+ )
155
+ return fig
156
+
157
+
158
+ # ── Explore mode ─────────────────────────────────────────────
159
+
160
+ def explore(words_text, selected):
161
+ """3D scatter of typed words; select a radio button to show neighbors."""
162
+
163
+ if not words_text or not words_text.strip():
164
+ return (
165
+ blank("Enter words above to visualize them in 3D"),
166
+ "",
167
+ gr.update(choices=[], value=None, visible=False),
168
+ )
169
+
170
+ all_words = tokenize(words_text)
171
+ valid = [w for w in all_words if w in VOCAB]
172
+ bad = [w for w in all_words if w not in VOCAB]
173
+
174
+ if len(valid) < 3:
175
+ msg = "Enter at least 3 valid words for 3D visualization."
176
+ if bad:
177
+ msg += f"<br>Not in vocabulary: {', '.join(bad)}"
178
+ return blank(msg), "", gr.update(choices=[], value=None, visible=False)
179
+
180
+ valid = valid[:12] # cap to keep plot readable
181
+
182
+ # Main word vectors β€” PCA fitted on these only (stable positions)
183
+ vecs = np.array([model[w] for w in valid])
184
+ main_coords, pca = fit_pca_3d(vecs)
185
+
186
+ # Neighbors (projected into the same PCA space)
187
+ nbr_data, nbr_coords = [], None
188
+ if selected and selected != "(clear)" and selected in VOCAB and selected in valid:
189
+ nbrs = model.most_similar(selected, topn=N_NEIGHBORS)
190
+ nbr_data = [(w, s) for w, s in nbrs if w not in valid]
191
+ if nbr_data:
192
+ nv = np.array([model[w] for w, _ in nbr_data])
193
+ nbr_coords = project_3d(pca, nv)
194
+ else:
195
+ selected = None
196
+
197
+ # ── Build figure ──
198
+ fig = go.Figure()
199
+
200
+ # Main words
201
+ fig.add_trace(go.Scatter3d(
202
+ x=main_coords[:, 0].tolist(),
203
+ y=main_coords[:, 1].tolist(),
204
+ z=main_coords[:, 2].tolist(),
205
+ mode="markers+text",
206
+ text=valid,
207
+ textposition="top center",
208
+ textfont=dict(size=14, color=DARK),
209
+ marker=dict(
210
+ size=9, color=PURPLE, opacity=0.9,
211
+ line=dict(width=1, color="white"),
212
+ ),
213
+ name="Words",
214
+ hoverinfo="text",
215
+ hovertext=valid,
216
+ ))
217
+
218
+ # Neighbors + connection lines
219
+ if nbr_data and nbr_coords is not None:
220
+ fig.add_trace(go.Scatter3d(
221
+ x=nbr_coords[:, 0].tolist(),
222
+ y=nbr_coords[:, 1].tolist(),
223
+ z=nbr_coords[:, 2].tolist(),
224
+ mode="markers+text",
225
+ text=[w for w, _ in nbr_data],
226
+ textposition="top center",
227
+ textfont=dict(size=11, color=GRAY),
228
+ marker=dict(
229
+ size=5, color=PURPLE_LIGHT, opacity=0.8,
230
+ line=dict(width=1, color=PURPLE),
231
+ ),
232
+ name=f'Near "{selected}"',
233
+ hoverinfo="text",
234
+ hovertext=[f"{w} ({s:.3f})" for w, s in nbr_data],
235
+ ))
236
+
237
+ # Dotted lines from selected word to its neighbors
238
+ si = valid.index(selected)
239
+ for i in range(len(nbr_data)):
240
+ fig.add_trace(go.Scatter3d(
241
+ x=[main_coords[si, 0], nbr_coords[i, 0]],
242
+ y=[main_coords[si, 1], nbr_coords[i, 1]],
243
+ z=[main_coords[si, 2], nbr_coords[i, 2]],
244
+ mode="lines",
245
+ line=dict(color=PURPLE_LIGHT, width=2, dash="dot"),
246
+ showlegend=False, hoverinfo="none",
247
+ ))
248
+
249
+ fig.update_layout(**layout_3d())
250
+
251
+ # Status
252
+ status = f"**{len(valid)} words** in 3D"
253
+ if bad:
254
+ status += f" Β· Not found: {', '.join(bad)}"
255
+ if nbr_data:
256
+ status += f" Β· {len(nbr_data)} neighbors of **{selected}**"
257
+
258
+ choices = ["(clear)"] + valid
259
+ return (
260
+ fig,
261
+ status,
262
+ gr.update(choices=choices, value=selected or "(clear)", visible=True),
263
+ )
264
+
265
+
266
+ # ── Arithmetic mode ──────────────────────────────────────────
267
+
268
+ def arithmetic(expression):
269
+ """3D visualization of word vector arithmetic."""
270
+
271
+ if not expression or not expression.strip():
272
+ return blank("Enter an expression like: king - man + woman"), ""
273
+
274
+ pos, neg, ordered = parse_expression(expression)
275
+
276
+ if len(pos) + len(neg) < 2:
277
+ # Check for words not in vocab
278
+ all_tokens = re.findall(r"[a-z']+", expression.lower())
279
+ bad = [t for t in all_tokens if t not in VOCAB and t not in "+-"]
280
+ msg = "Need at least 2 valid words for arithmetic."
281
+ if bad:
282
+ msg += f"<br>Not in vocabulary: {', '.join(bad)}"
283
+ return blank(msg), ""
284
+
285
+ # Compute nearest words to result vector
286
+ try:
287
+ raw_results = model.most_similar(positive=pos, negative=neg, topn=10)
288
+ except Exception as e:
289
+ return blank(f"Error: {e}"), ""
290
+
291
+ # Filter results β€” exclude operands
292
+ operands = list(dict.fromkeys(pos + neg)) # dedupe, preserve order
293
+ result_entries = [(w, s) for w, s in raw_results if w not in operands][:5]
294
+
295
+ if not result_entries:
296
+ return blank("No results found (all matches were input words)"), ""
297
+
298
+ # Compute the actual result vector
299
+ rv = np.zeros(DIMS)
300
+ for w in pos:
301
+ rv += model[w]
302
+ for w in neg:
303
+ rv -= model[w]
304
+
305
+ # Collect all words for PCA
306
+ result_words = [w for w, _ in result_entries]
307
+ all_words = operands + result_words
308
+ all_vecs = np.array([model[w] for w in all_words])
309
+
310
+ # Include result vector in PCA fit for best view
311
+ combined = np.vstack([all_vecs, rv.reshape(1, -1)])
312
+ coords_all, _ = fit_pca_3d(combined)
313
+
314
+ rv_coord = coords_all[-1]
315
+ coords = coords_all[:-1]
316
+
317
+ n_op = len(operands)
318
+ top_result = result_entries[0]
319
+ other_results = result_entries[1:]
320
+
321
+ # ── Build figure ──
322
+ fig = go.Figure()
323
+
324
+ # Positive operands
325
+ pi = [i for i, w in enumerate(operands) if w in pos]
326
+ if pi:
327
+ fig.add_trace(go.Scatter3d(
328
+ x=[coords[i, 0] for i in pi],
329
+ y=[coords[i, 1] for i in pi],
330
+ z=[coords[i, 2] for i in pi],
331
+ mode="markers+text",
332
+ text=[operands[i] for i in pi],
333
+ textposition="top center",
334
+ textfont=dict(size=14, color=DARK),
335
+ marker=dict(size=10, color=PURPLE, opacity=0.9),
336
+ name="Positive (+)",
337
+ hoverinfo="text",
338
+ hovertext=[operands[i] for i in pi],
339
+ ))
340
+
341
+ # Negative operands
342
+ ni = [i for i, w in enumerate(operands) if w in neg]
343
+ if ni:
344
+ fig.add_trace(go.Scatter3d(
345
+ x=[coords[i, 0] for i in ni],
346
+ y=[coords[i, 1] for i in ni],
347
+ z=[coords[i, 2] for i in ni],
348
+ mode="markers+text",
349
+ text=[operands[i] for i in ni],
350
+ textposition="top center",
351
+ textfont=dict(size=14, color=DARK),
352
+ marker=dict(size=10, color=PINK, opacity=0.9),
353
+ name="Negative (βˆ’)",
354
+ hoverinfo="text",
355
+ hovertext=[operands[i] for i in ni],
356
+ ))
357
+
358
+ # Top result β€” gold diamond
359
+ ti = n_op # index of first result word in coords
360
+ fig.add_trace(go.Scatter3d(
361
+ x=[coords[ti, 0]], y=[coords[ti, 1]], z=[coords[ti, 2]],
362
+ mode="markers+text",
363
+ text=[f"β‰ˆ {top_result[0]}"],
364
+ textposition="top center",
365
+ textfont=dict(size=16, color=PURPLE),
366
+ marker=dict(
367
+ size=14, color=GOLD, opacity=1.0, symbol="diamond",
368
+ line=dict(width=2, color=PURPLE),
369
+ ),
370
+ name=f"Result: {top_result[0]}",
371
+ hoverinfo="text",
372
+ hovertext=[f"{top_result[0]} (similarity: {top_result[1]:.3f})"],
373
+ ))
374
+
375
+ # Other result words β€” smaller
376
+ if other_results:
377
+ oi = list(range(n_op + 1, n_op + 1 + len(other_results)))
378
+ fig.add_trace(go.Scatter3d(
379
+ x=[coords[i, 0] for i in oi],
380
+ y=[coords[i, 1] for i in oi],
381
+ z=[coords[i, 2] for i in oi],
382
+ mode="markers+text",
383
+ text=[f"{w} ({s:.2f})" for w, s in other_results],
384
+ textposition="top center",
385
+ textfont=dict(size=11, color=GRAY),
386
+ marker=dict(size=5, color=PURPLE_LIGHT, opacity=0.7),
387
+ name="Other matches",
388
+ hoverinfo="text",
389
+ hovertext=[f"{w} ({s:.3f})" for w, s in other_results],
390
+ ))
391
+
392
+ # Computed result vector β€” faint cross marker
393
+ fig.add_trace(go.Scatter3d(
394
+ x=[rv_coord[0]], y=[rv_coord[1]], z=[rv_coord[2]],
395
+ mode="markers",
396
+ marker=dict(size=7, color=GOLD, opacity=0.4, symbol="cross"),
397
+ name="Computed point",
398
+ hoverinfo="text",
399
+ hovertext=["Exact result of vector arithmetic"],
400
+ ))
401
+
402
+ # Dashed lines: each operand β†’ computed result (shows the path)
403
+ for i in range(n_op):
404
+ fig.add_trace(go.Scatter3d(
405
+ x=[coords[i, 0], rv_coord[0]],
406
+ y=[coords[i, 1], rv_coord[1]],
407
+ z=[coords[i, 2], rv_coord[2]],
408
+ mode="lines",
409
+ line=dict(color=PURPLE_LIGHT, width=2, dash="dot"),
410
+ showlegend=False, hoverinfo="none",
411
+ ))
412
+
413
+ fig.update_layout(**layout_3d())
414
+
415
+ # Status text
416
+ parts = []
417
+ for w, s in ordered:
418
+ if not parts:
419
+ parts.append(w)
420
+ elif s == "+":
421
+ parts.append(f"+ {w}")
422
+ else:
423
+ parts.append(f"βˆ’ {w}")
424
+ expr_str = " ".join(parts)
425
+
426
+ status = f"**{expr_str} β‰ˆ {top_result[0]}** (similarity: {top_result[1]:.3f})\n\n"
427
+ status += "Top 5: " + " Β· ".join(
428
+ f"**{w}** ({s:.2f})" for w, s in result_entries
429
+ )
430
+
431
+ return fig, status
432
+
433
+
434
+ # ── Gradio UI ────────────────────────────────────────────────
435
+
436
+ CSS = """
437
+ .gradio-container { max-width: 1200px !important; }
438
+ h1 { color: #63348d !important; }
439
+ """
440
+
441
+ with gr.Blocks(
442
+ title="Embedding Explorer",
443
+ theme=gr.themes.Soft(
444
+ primary_hue="purple",
445
+ font=gr.themes.GoogleFont("Inter"),
446
+ ),
447
+ css=CSS,
448
+ ) as demo:
449
+
450
+ gr.Markdown(
451
+ "# Embedding Explorer\n"
452
+ "Words in AI models are stored as **vectors** β€” long lists of numbers "
453
+ "that encode meaning. Similar words have similar vectors. "
454
+ "This tool lets you explore these representations in 3D using "
455
+ "[GloVe](https://nlp.stanford.edu/projects/glove/) word vectors "
456
+ f"({len(VOCAB):,} words, {DIMS} dimensions)."
457
+ )
458
+
459
+ with gr.Tabs():
460
+
461
+ # ── Explore tab ──
462
+ with gr.Tab("Explore"):
463
+ gr.Markdown(
464
+ "*Enter words to see them in 3D space. "
465
+ "Similar words cluster together. "
466
+ "Click a word below the plot to see its nearest neighbors.*"
467
+ )
468
+ with gr.Row():
469
+ with gr.Column(scale=1):
470
+ exp_in = gr.Textbox(
471
+ label="Words (space-separated, min 3)",
472
+ placeholder="dog cat fish car truck",
473
+ lines=1,
474
+ )
475
+ exp_btn = gr.Button("Show in 3D", variant="primary")
476
+ exp_status = gr.Markdown("")
477
+ exp_radio = gr.Radio(
478
+ label="Click to see nearest neighbors",
479
+ choices=[], value=None,
480
+ visible=False, interactive=True,
481
+ )
482
+ gr.Examples(
483
+ examples=[[e] for e in EXPLORE_EXAMPLES],
484
+ inputs=[exp_in],
485
+ label="Try these",
486
+ )
487
+ with gr.Column(scale=2):
488
+ exp_plot = gr.Plot(label="Embedding Space")
489
+
490
+ # Events
491
+ exp_btn.click(
492
+ lambda w: explore(w, None),
493
+ inputs=[exp_in],
494
+ outputs=[exp_plot, exp_status, exp_radio],
495
+ )
496
+ exp_in.submit(
497
+ lambda w: explore(w, None),
498
+ inputs=[exp_in],
499
+ outputs=[exp_plot, exp_status, exp_radio],
500
+ )
501
+ exp_radio.change(
502
+ explore,
503
+ inputs=[exp_in, exp_radio],
504
+ outputs=[exp_plot, exp_status, exp_radio],
505
+ )
506
+
507
+ # ── Vector Math tab ──
508
+ with gr.Tab("Vector Math"):
509
+ gr.Markdown(
510
+ "*Word vectors can do math! "
511
+ "The results reveal hidden relationships between words.*"
512
+ )
513
+ with gr.Row():
514
+ with gr.Column(scale=1):
515
+ math_in = gr.Textbox(
516
+ label="Expression",
517
+ placeholder="king - man + woman",
518
+ lines=1,
519
+ )
520
+ math_btn = gr.Button("Compute", variant="primary")
521
+ math_status = gr.Markdown("")
522
+ gr.Examples(
523
+ examples=[[e] for e in ARITHMETIC_EXAMPLES],
524
+ inputs=[math_in],
525
+ label="Try these",
526
+ )
527
+ with gr.Column(scale=2):
528
+ math_plot = gr.Plot(label="Vector Arithmetic")
529
+
530
+ # Events
531
+ math_btn.click(
532
+ arithmetic,
533
+ inputs=[math_in],
534
+ outputs=[math_plot, math_status],
535
+ )
536
+ math_in.submit(
537
+ arithmetic,
538
+ inputs=[math_in],
539
+ outputs=[math_plot, math_status],
540
+ )
541
+
542
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ gradio>=5.0
2
+ gensim>=4.3.0
3
+ scikit-learn>=1.3
4
+ plotly>=5.18
5
+ numpy>=1.24