perkbevis's picture
Add Gradio app for Transformer demos
b53f604 verified
Raw History Blame Contribute Delete
19.5 kB
"""
Understanding Transformers - demo app (Gradio)
Turns the experiments in `transformel.ipynb` into an interactive app:
GPT-2 (sampling), Tokenizer, Sentiment, NER, QA, Translation, Audio, Image processing.
Models are loaded lazily (on first use) and cached, so the Space starts quickly.
"""
import glob
import os
from functools import lru_cache
import gradio as gr
import librosa
import librosa.display
import numpy as np
import torch
import torch.nn.functional as F
from matplotlib.figure import Figure
from PIL import Image
from transformers import (
ASTForAudioClassification,
AutoFeatureExtractor,
AutoModelForCausalLM,
AutoModelForQuestionAnswering,
AutoModelForSeq2SeqLM,
AutoTokenizer,
pipeline,
)
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
def asset(name: str) -> str:
return os.path.join(BASE_DIR, name)
def find_audio():
"""Find the sample mp3 (audio.mp3 on the Space, or *audio.mp3 on GitHub)."""
for pattern in ("audio.mp3", "*audio*.mp3"):
hits = glob.glob(os.path.join(BASE_DIR, pattern))
if hits:
return hits[0]
return None
AUDIO_SAMPLE = find_audio()
IMAGE_SAMPLES = [
[asset(n)] for n in ("elmo.jpg", "sea.jpg", "white-flower.jpg") if os.path.exists(asset(n))
]
# =====================================================================
# 1. GPT-2: sampling strategies (greedy / top-k / top-p / temperature / random)
# =====================================================================
@lru_cache(maxsize=1)
def load_gpt2():
tok = AutoTokenizer.from_pretrained("openai-community/gpt2")
model = AutoModelForCausalLM.from_pretrained("openai-community/gpt2").eval()
return tok, model
def greedy_decode(logits):
return torch.argmax(logits, dim=-1).reshape(1)
def top_k_sampling(logits, k=50):
values, indices = torch.topk(logits, int(k))
probs = F.softmax(values, dim=-1)
return indices[torch.multinomial(probs, 1)]
def top_p_sampling(logits, p=0.9):
sorted_logits, sorted_indices = torch.sort(logits, descending=True)
sorted_probs = F.softmax(sorted_logits, dim=-1)
cumulative = sorted_probs.cumsum(dim=-1)
# Shift the cumulative sum by one step so the first token that crosses the
# threshold is always kept (otherwise, if the top token already has
# probability > p, every logit gets masked and the result is NaN).
mask = (cumulative - sorted_probs) > p
sorted_logits = sorted_logits.masked_fill(mask, float("-inf"))
filtered = F.softmax(sorted_logits, dim=-1)
return sorted_indices[torch.multinomial(filtered, 1)]
def temperature_sampling(logits, temperature=1.0):
probs = F.softmax(logits / max(float(temperature), 1e-4), dim=-1)
return torch.multinomial(probs, 1)
def random_sampling(logits):
return torch.multinomial(F.softmax(logits, dim=-1), 1)
def pick_token(logits, strategy, k, p, temperature):
if strategy == "Greedy":
return greedy_decode(logits)
if strategy == "Top-k":
return top_k_sampling(logits, k)
if strategy == "Top-p (Nucleus)":
return top_p_sampling(logits, p)
if strategy == "Temperature":
return temperature_sampling(logits, temperature)
return random_sampling(logits)
def generate(prompt, strategy, n_tokens, k, p, temperature, seed):
if not prompt or not prompt.strip():
raise gr.Error("Please enter a prompt.")
tok, model = load_gpt2()
if seed is not None and int(seed) >= 0:
torch.manual_seed(int(seed))
ids = tok(prompt, return_tensors="pt").input_ids
with torch.no_grad():
for _ in range(int(n_tokens)):
logits = model(ids).logits[0, -1]
nxt = pick_token(logits, strategy, k, p, temperature)
ids = torch.cat([ids, nxt.reshape(1, 1)], dim=1)
if int(nxt) == tok.eos_token_id:
break
return tok.decode(ids[0])
def next_token_probs(prompt):
if not prompt or not prompt.strip():
raise gr.Error("Please enter a sentence.")
tok, model = load_gpt2()
ids = tok(prompt, return_tensors="pt").input_ids
with torch.no_grad():
logits = model(ids).logits[0, -1]
probs = torch.softmax(logits, dim=-1)
top = torch.topk(probs, k=10)
return [[repr(tok.decode(int(i))), f"{float(v):.1%}"] for i, v in zip(top.indices, top.values)]
# =====================================================================
# 2. Tokenizer
# =====================================================================
def tokenize_text(text):
if not text or not text.strip():
raise gr.Error("Please enter a word or sentence.")
tok, _ = load_gpt2()
ids = tok(text).input_ids
rows = [[i, repr(tok.decode(i)), tok.convert_ids_to_tokens(i)] for i in ids]
return rows, f"{len(ids)} tokens"
# =====================================================================
# 3. Sentiment (general / FinBERT)
# =====================================================================
SENTIMENT_MODELS = {
"General (DistilBERT SST-2)": "distilbert/distilbert-base-uncased-finetuned-sst-2-english",
"Financial (FinBERT)": "ProsusAI/finbert",
}
@lru_cache(maxsize=None)
def sentiment_pipe(model_name):
return pipeline("text-classification", model=model_name)
def analyze_sentiment(text, which):
if not text or not text.strip():
raise gr.Error("Please enter some text.")
preds = sentiment_pipe(SENTIMENT_MODELS[which])(text[:1500], top_k=None)
return {p["label"]: float(p["score"]) for p in preds}
# =====================================================================
# 4. Named Entity Recognition
# =====================================================================
@lru_cache(maxsize=1)
def ner_pipe():
return pipeline(
"token-classification",
model="dbmdz/bert-large-cased-finetuned-conll03-english",
aggregation_strategy="simple",
)
def run_ner(text):
if not text or not text.strip():
raise gr.Error("Please enter some text.")
ents = sorted(ner_pipe()(text), key=lambda e: e["start"])
out, pos = [], 0
for e in ents:
if e["start"] > pos:
out.append((text[pos:e["start"]], None))
out.append((text[e["start"]:e["end"]], e["entity_group"]))
pos = e["end"]
if pos < len(text):
out.append((text[pos:], None))
return out
# =====================================================================
# 5. Question Answering (extractive, DistilBERT-SQuAD)
# =====================================================================
QA_CONTEXT = (
"Financial sentiment analysis is a challenging task due to the specialized "
"language and lack of labeled data in that domain. General-purpose models are "
"not effective enough because of the specialized language used in a financial "
"context. We hypothesize that pre-trained language models can help with this "
"problem because they require fewer labeled examples and they can be further "
"trained on domain-specific corpora. We introduce FinBERT, a language model "
"based on BERT, to tackle NLP tasks in the financial domain. Our results show "
"improvement in every measured metric on current state-of-the-art results for "
"two financial sentiment analysis datasets. We find that even with a smaller "
"training set and fine-tuning only a part of the model, FinBERT outperforms "
"state-of-the-art machine learning methods."
)
@lru_cache(maxsize=1)
def load_qa():
name = "distilbert/distilbert-base-uncased-distilled-squad"
return AutoTokenizer.from_pretrained(name), AutoModelForQuestionAnswering.from_pretrained(name).eval()
def answer_question(question, context):
if not question or not question.strip() or not context or not context.strip():
raise gr.Error("Please enter both a question and a context passage.")
tok, model = load_qa()
inputs = tok(question, context, return_tensors="pt", truncation="only_second", max_length=512)
with torch.no_grad():
out = model(**inputs)
start = int(torch.argmax(out.start_logits))
end = int(torch.argmax(out.end_logits)) + 1
if end <= start:
return "(No answer found in the passage)"
ids = inputs["input_ids"][0][start:end]
return tok.convert_tokens_to_string(tok.convert_ids_to_tokens(ids))
# =====================================================================
# 6. Machine translation (NLLB-200 distilled 600M)
# =====================================================================
LANGS = {
"French (fra_Latn)": "fra_Latn",
"Vietnamese (vie_Latn)": "vie_Latn",
"Spanish (spa_Latn)": "spa_Latn",
"German (deu_Latn)": "deu_Latn",
"Japanese (jpn_Jpan)": "jpn_Jpan",
}
@lru_cache(maxsize=1)
def load_nllb():
name = "facebook/nllb-200-distilled-600M"
tok = AutoTokenizer.from_pretrained(name, src_lang="eng_Latn")
return tok, AutoModelForSeq2SeqLM.from_pretrained(name).eval()
def translate(text, target):
if not text or not text.strip():
raise gr.Error("Please enter an English sentence to translate.")
tok, model = load_nllb()
inputs = tok(text, return_tensors="pt")
with torch.no_grad():
out = model.generate(
**inputs,
forced_bos_token_id=tok.convert_tokens_to_ids(LANGS[target]),
max_length=200,
)
return tok.decode(out[0], skip_special_tokens=True)
# =====================================================================
# 7. Audio: waveform/mel, classification (AST), speech->text, text->speech
# =====================================================================
def plot_audio(path):
if not path:
raise gr.Error("Please upload or record an audio file.")
y, sr = librosa.load(path)
fig = Figure(figsize=(10, 7), dpi=100)
ax1, ax2 = fig.subplots(2, 1)
ax1.plot(np.arange(len(y)) / sr, y, linewidth=0.5)
ax1.set_title(f"Waveform (sr={sr} Hz, {len(y) / sr:.1f}s)")
ax1.set_xlabel("time (s)")
ax1.set_ylabel("amplitude")
mel = librosa.feature.melspectrogram(y=y, sr=sr, n_mels=128, fmax=8000)
mel_db = librosa.power_to_db(mel, ref=np.max)
img = librosa.display.specshow(mel_db, sr=sr, x_axis="time", y_axis="log", fmax=8000, ax=ax2)
ax2.set_title("Mel spectrogram")
fig.colorbar(img, ax=ax2, format="%+2.0f dB")
fig.tight_layout()
return fig
@lru_cache(maxsize=1)
def load_ast():
name = "MIT/ast-finetuned-audioset-10-10-0.4593"
return AutoFeatureExtractor.from_pretrained(name), ASTForAudioClassification.from_pretrained(name).eval()
def classify_audio(path):
if not path:
raise gr.Error("Please upload or record an audio file.")
fe, model = load_ast()
y, sr = librosa.load(path, sr=16000)
inputs = fe(y, sampling_rate=16000, return_tensors="pt")
with torch.no_grad():
logits = model(**inputs).logits[0]
probs = torch.sigmoid(logits) # AudioSet is multi-label
top = torch.topk(probs, k=5)
return {model.config.id2label[int(i)]: float(v) for i, v in zip(top.indices, top.values)}
@lru_cache(maxsize=1)
def asr_pipe():
return pipeline("automatic-speech-recognition", model="facebook/wav2vec2-base-960h")
def speech_to_text(path):
if not path:
raise gr.Error("Please upload or record an English audio file.")
y, _ = librosa.load(path, sr=16000)
return asr_pipe()({"raw": y, "sampling_rate": 16000})["text"]
@lru_cache(maxsize=1)
def tts_pipe():
return pipeline("text-to-speech", model="suno/bark-small")
def text_to_speech(text):
if not text or not text.strip():
raise gr.Error("Please enter an English sentence.")
out = tts_pipe()(text[:200])
return out["sampling_rate"], np.asarray(out["audio"]).squeeze()
# =====================================================================
# 8. Image processing (RGB channels, grayscale, resize)
# =====================================================================
def process_image(image, ratio):
if image is None:
raise gr.Error("Please upload an image.")
image = image.convert("RGB")
arr = np.asarray(image)
red = arr.copy()
red[:, :, 1:] = 0
green = arr.copy()
green[:, :, [0, 2]] = 0
blue = arr.copy()
blue[:, :, :2] = 0
w, h = image.size
resized = image.resize((max(1, int(w * ratio)), max(1, int(h * ratio))))
info = f"Shape: {arr.shape} | Resize: {w}x{h} -> {resized.size[0]}x{resized.size[1]}"
gallery = [
(image, "Original"),
(Image.fromarray(red), "Red channel"),
(Image.fromarray(green), "Green channel"),
(Image.fromarray(blue), "Blue channel"),
(image.convert("L"), "Grayscale"),
(resized, f"Resize x{ratio}"),
]
return gallery, info
# =============================== UI ===============================
with gr.Blocks(title="Understanding Transformers") as demo:
gr.Markdown(
"# πŸ“‰ Understanding Transformers\n"
"A self-study AI project exploring the **Transformer** architecture through "
"hands-on experiments with Hugging Face models (text, audio, images). "
"Models are downloaded on first use, so the first run of each tab may take a while."
)
# ---- GPT-2 ----
with gr.Tab("✍️ GPT-2"):
with gr.Tabs():
with gr.Tab("Text generation"):
g_prompt = gr.Textbox(label="Prompt", value="What is machine learning?", lines=2)
with gr.Row():
g_strategy = gr.Dropdown(
["Greedy", "Top-k", "Top-p (Nucleus)", "Temperature", "Random"],
value="Top-k", label="Sampling strategy",
)
g_tokens = gr.Slider(5, 100, value=40, step=5, label="New tokens to generate")
with gr.Row():
g_k = gr.Slider(1, 100, value=50, step=1, label="k (Top-k)")
g_p = gr.Slider(0.1, 1.0, value=0.9, step=0.05, label="p (Top-p)")
g_temp = gr.Slider(0.1, 2.0, value=1.0, step=0.1, label="Temperature")
g_seed = gr.Number(value=-1, precision=0, label="Seed (-1 = random)")
g_out = gr.Textbox(label="Output", lines=8)
gr.Button("Generate", variant="primary").click(
generate, [g_prompt, g_strategy, g_tokens, g_k, g_p, g_temp, g_seed], g_out
)
with gr.Tab("Next-token probabilities"):
n_in = gr.Textbox(
label="Sentence",
value="I learn machine learning to enhance our understanding of the brain in",
lines=2,
)
n_out = gr.Dataframe(headers=["Token", "Probability"], interactive=False, label="Top 10")
gr.Button("Predict next token", variant="primary").click(next_token_probs, n_in, n_out)
# ---- Tokenizer ----
with gr.Tab("πŸ”€ Tokenizer"):
t_in = gr.Textbox(label="Word / sentence", value="antidisestablishmentarianism")
t_count = gr.Textbox(label="Count", interactive=False)
t_out = gr.Dataframe(headers=["ID", "Token (decoded)", "Raw token"], interactive=False)
gr.Button("Tokenize (GPT-2)", variant="primary").click(tokenize_text, t_in, [t_out, t_count])
gr.Examples(
[["unbelivable"], ["homoscedasticity"], ["pneumonoultramicroscopicsilicovolcanoconiosis"],
["floccinaucinihilipilification"]],
inputs=t_in, cache_examples=False,
)
# ---- Sentiment ----
with gr.Tab("πŸ’¬ Sentiment"):
s_which = gr.Radio(list(SENTIMENT_MODELS), value="General (DistilBERT SST-2)", label="Model")
s_in = gr.Textbox(label="Text", lines=3, value="This day is great!")
s_out = gr.Label(label="Result")
gr.Button("Analyze", variant="primary").click(analyze_sentiment, [s_in, s_which], s_out)
gr.Examples(
[["This day is terrible and i am so sad"],
["The company reported a strong increase in quarterly revenue, exceeding market expectations."],
["Shares fell after the firm reported lower-than-expected earnings"]],
inputs=s_in, cache_examples=False,
)
# ---- NER ----
with gr.Tab("🏷️ NER"):
ner_in = gr.Textbox(
label="Text", lines=3,
value="Apple announced record earnings in the United States on Monday.",
)
ner_out = gr.HighlightedText(label="Entities")
gr.Button("Recognize entities", variant="primary").click(run_ner, ner_in, ner_out)
gr.Examples(
[["I live in UK worked at Facebook after graduating from Harvard"]],
inputs=ner_in, cache_examples=False,
)
# ---- QA ----
with gr.Tab("❓ Question answering"):
qa_ctx = gr.Textbox(label="Context passage", value=QA_CONTEXT, lines=8)
qa_q = gr.Textbox(label="Question", value="What does FinBERT outperform?")
qa_out = gr.Textbox(label="Answer", interactive=False)
gr.Button("Answer", variant="primary").click(answer_question, [qa_q, qa_ctx], qa_out)
# ---- Translation ----
with gr.Tab("🌍 Translation"):
tr_in = gr.Textbox(label="English text", value="What is your name?", lines=2)
tr_lang = gr.Dropdown(list(LANGS), value="Vietnamese (vie_Latn)", label="Translate to")
tr_out = gr.Textbox(label="Translation", lines=2, interactive=False)
gr.Button("Translate", variant="primary").click(translate, [tr_in, tr_lang], tr_out)
# ---- Audio ----
with gr.Tab("🎧 Audio"):
with gr.Tabs():
with gr.Tab("Waveform & spectrogram"):
a1 = gr.Audio(type="filepath", label="Audio", value=AUDIO_SAMPLE)
a1_out = gr.Plot()
gr.Button("Plot", variant="primary").click(plot_audio, a1, a1_out)
with gr.Tab("Classification (AST)"):
a2 = gr.Audio(type="filepath", label="Audio", value=AUDIO_SAMPLE)
a2_out = gr.Label(num_top_classes=5, label="Top 5 AudioSet labels")
gr.Button("Classify", variant="primary").click(classify_audio, a2, a2_out)
with gr.Tab("Speech β†’ Text"):
a3 = gr.Audio(type="filepath", label="English audio", value=AUDIO_SAMPLE)
a3_out = gr.Textbox(label="Transcript", lines=4, interactive=False)
gr.Button("Transcribe", variant="primary").click(speech_to_text, a3, a3_out)
with gr.Tab("Text β†’ Speech"):
tts_in = gr.Textbox(label="English text (max 200 characters)", value="I like coding in python")
tts_out = gr.Audio(label="Generated speech")
gr.Markdown("*Bark-small is quite slow on CPU, please be patient.*")
gr.Button("Speak", variant="primary").click(text_to_speech, tts_in, tts_out)
# ---- Image ----
with gr.Tab("πŸ–ΌοΈ Image processing"):
i_in = gr.Image(type="pil", label="Image")
i_ratio = gr.Slider(0.1, 1.0, value=0.5, step=0.05, label="Resize ratio")
i_info = gr.Textbox(label="Info", interactive=False)
i_out = gr.Gallery(label="Results", columns=3, height="auto")
gr.Button("Process", variant="primary").click(process_image, [i_in, i_ratio], [i_out, i_info])
if IMAGE_SAMPLES:
gr.Examples(IMAGE_SAMPLES, inputs=i_in, cache_examples=False)
if __name__ == "__main__":
demo.queue().launch()