Spaces:
Runtime error
Runtime error
Download app.py from perkbevis/Transformel-models: direct link, hf CLI and curl.
- Browser
- Download file 19.5 kB
-
https://huggingface.co/spaces/perkbevis/Transformel-models/resolve/main/app.py
- Command line
-
hf download hf://spaces/perkbevis/Transformel-models/app.py
-
curl -L -o app.py https://huggingface.co/spaces/perkbevis/Transformel-models/resolve/main/app.py
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) | |
| # ===================================================================== | |
| 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", | |
| } | |
| 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 | |
| # ===================================================================== | |
| 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." | |
| ) | |
| 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", | |
| } | |
| 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 | |
| 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)} | |
| 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"] | |
| 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() | |