Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import torch | |
| from transformers import AutoTokenizer, AutoModelForTokenClassification | |
| # ============================================================ | |
| # BalPOS - Balochi Part-of-Speech Tagger | |
| # CPU Hugging Face Space | |
| # ============================================================ | |
| MODEL_NAME = "shahbakhsh/BalPOS" | |
| DEVICE = torch.device("cpu") | |
| print("=" * 60) | |
| print("BalPOS - Balochi Part-of-Speech Tagger") | |
| print("=" * 60) | |
| print(f"Model: {MODEL_NAME}") | |
| print("Device: CPU") | |
| print("Loading model...") | |
| # ============================================================ | |
| # LOAD MODEL ONCE | |
| # ============================================================ | |
| tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME) | |
| model = AutoModelForTokenClassification.from_pretrained(MODEL_NAME) | |
| model.to(DEVICE) | |
| model.eval() | |
| print("Model loaded successfully.") | |
| print(f"Labels: {model.config.num_labels}") | |
| print("=" * 60) | |
| # ============================================================ | |
| # POS COLORS | |
| # ============================================================ | |
| LABEL_COLORS = { | |
| "NOUN": "#93c5fd", | |
| "PROPN": "#a5b4fc", | |
| "PRON": "#c4b5fd", | |
| "VERB": "#86efac", | |
| "AUX": "#6ee7b7", | |
| "ADJ": "#fde68a", | |
| "ADV": "#fca5a5", | |
| "ADP": "#fdba74", | |
| "CCONJ": "#f9a8d4", | |
| "SCONJ": "#f0abfc", | |
| "DET": "#67e8f9", | |
| "NUM": "#d9f99d", | |
| "PART": "#e5e7eb", | |
| "PUNCT": "#d1d5db", | |
| "INTJ": "#fecaca", | |
| "X": "#e5e7eb", | |
| } | |
| # ============================================================ | |
| # POS TAGGING FUNCTION | |
| # ============================================================ | |
| def tag_sentence(sentence): | |
| if not sentence or not sentence.strip(): | |
| return ( | |
| [], | |
| [], | |
| "⚠️ Please enter a Balochi sentence." | |
| ) | |
| sentence = sentence.strip() | |
| words = sentence.split() | |
| # Keep CPU inference practical | |
| if len(words) > 128: | |
| return ( | |
| [], | |
| [], | |
| "⚠️ Please enter a shorter sentence. " | |
| "Maximum supported length is 128 words." | |
| ) | |
| try: | |
| # Preserve word boundaries | |
| inputs = tokenizer( | |
| words, | |
| is_split_into_words=True, | |
| return_tensors="pt", | |
| truncation=True, | |
| max_length=256 | |
| ) | |
| word_ids = inputs.word_ids(batch_index=0) | |
| inputs = { | |
| key: value.to(DEVICE) | |
| for key, value in inputs.items() | |
| } | |
| # CPU inference | |
| with torch.inference_mode(): | |
| outputs = model(**inputs) | |
| logits = outputs.logits | |
| probabilities = torch.softmax(logits, dim=-1) | |
| predictions = logits.argmax(dim=-1)[0] | |
| confidences = probabilities.max(dim=-1).values[0] | |
| results = [] | |
| table_rows = [] | |
| seen_words = set() | |
| for token_index, word_id in enumerate(word_ids): | |
| if word_id is None: | |
| continue | |
| if word_id in seen_words: | |
| continue | |
| if word_id >= len(words): | |
| continue | |
| seen_words.add(word_id) | |
| predicted_id = predictions[token_index].item() | |
| label = model.config.id2label.get( | |
| predicted_id, | |
| str(predicted_id) | |
| ) | |
| confidence = confidences[token_index].item() * 100 | |
| word = words[word_id] | |
| results.append((word, label)) | |
| table_rows.append([word, label, f"{confidence:.1f}%"]) | |
| if not results: | |
| return ( | |
| [], | |
| [], | |
| "⚠️ No predictions were produced." | |
| ) | |
| average_confidence = sum( | |
| float(row[2].replace("%", "")) | |
| for row in table_rows | |
| ) / len(table_rows) | |
| summary = ( | |
| f"**{len(results)} tokens** · " | |
| f"**{len(set(label for _, label in results))} POS categories** · " | |
| f"**{average_confidence:.1f}% average confidence**" | |
| ) | |
| return (results, table_rows, summary) | |
| except Exception as error: | |
| print("Prediction error:", repr(error)) | |
| return ( | |
| [], | |
| [], | |
| "❌ An error occurred while processing the sentence." | |
| ) | |
| # ============================================================ | |
| # EXAMPLES | |
| # ============================================================ | |
| EXAMPLES = [ | |
| ["چہ وتی فلسفہ ءِ استاد ءَ آ اشکیتگ ات"], | |
| ["اے دوئیں دبستانانی گوناپ دیگءَ پرکے ھست۔"], | |
| ["اولی جست ءِ پسو نہ اِنت۔"], | |
| ["تہ اے پیمیں جست بے نہ بنت۔"], | |
| ] | |
| # ============================================================ | |
| # CUSTOM CSS | |
| # ============================================================ | |
| CUSTOM_CSS = """ | |
| .gradio-container { | |
| max-width: 1200px !important; | |
| margin: auto !important; | |
| } | |
| #hero { | |
| text-align: center; | |
| padding: 24px 12px 12px 12px; | |
| } | |
| #hero h1 { | |
| font-size: clamp(2rem, 5vw, 3.2rem); | |
| margin-bottom: 8px; | |
| } | |
| #balochi-input textarea { | |
| direction: rtl !important; | |
| text-align: right !important; | |
| font-size: 1.15rem !important; | |
| line-height: 2 !important; | |
| } | |
| #tag-button { | |
| min-height: 48px !important; | |
| font-weight: 600 !important; | |
| } | |
| .card { | |
| border-radius: 16px !important; | |
| padding: 16px !important; | |
| } | |
| #summary { | |
| text-align: center; | |
| padding: 10px; | |
| } | |
| @media (max-width: 768px) { | |
| .gradio-container { | |
| padding-left: 10px !important; | |
| padding-right: 10px !important; | |
| } | |
| #hero { | |
| padding-top: 12px; | |
| } | |
| #hero h1 { | |
| font-size: 2rem; | |
| } | |
| #balochi-input textarea { | |
| font-size: 1rem !important; | |
| } | |
| } | |
| @media (max-width: 430px) { | |
| #hero h1 { | |
| font-size: 1.7rem; | |
| } | |
| #hero p { | |
| font-size: 0.9rem; | |
| } | |
| } | |
| footer { | |
| display: none !important; | |
| } | |
| """ | |
| # ============================================================ | |
| # BUILD GRADIO APP | |
| # ============================================================ | |
| with gr.Blocks(title="BalPOS - Balochi POS Tagger") as demo: | |
| # -------------------------------------------------------- | |
| # HEADER | |
| # -------------------------------------------------------- | |
| with gr.Column(elem_id="hero"): | |
| gr.Markdown( | |
| """ | |
| # 🏔️ BalPOS | |
| ## Balochi Part-of-Speech Tagger | |
| """ | |
| ) | |
| gr.Markdown( | |
| """ | |
| Automatically predict Universal Part-of-Speech (UPOS) | |
| tags for Balochi text. | |
| """ | |
| ) | |
| # -------------------------------------------------------- | |
| # MAIN | |
| # -------------------------------------------------------- | |
| with gr.Row(): | |
| # INPUT | |
| with gr.Column(scale=1, elem_classes=["card"]): | |
| gr.Markdown("### ✍️ Enter Balochi Text") | |
| sentence_input = gr.Textbox( | |
| label="Balochi Sentence", | |
| placeholder="بلوچی جملہ ایتلاپ کن ...", | |
| lines=5, | |
| rtl=True, | |
| text_align="right", | |
| elem_id="balochi-input" | |
| ) | |
| with gr.Row(): | |
| clear_btn = gr.Button("Clear") | |
| submit_btn = gr.Button( | |
| "🏷️ Tag Sentence", | |
| variant="primary", | |
| elem_id="tag-button" | |
| ) | |
| gr.Examples( | |
| examples=EXAMPLES, | |
| inputs=sentence_input, | |
| label="Try an example" | |
| ) | |
| # OUTPUT | |
| with gr.Column(scale=1, elem_classes=["card"]): | |
| gr.Markdown("### 🔎 POS Predictions") | |
| highlighted_output = gr.HighlightedText( | |
| label="Tagged Text", | |
| color_map=LABEL_COLORS, | |
| show_legend=True | |
| ) | |
| summary_output = gr.Markdown( | |
| "Enter a sentence to begin.", | |
| elem_id="summary" | |
| ) | |
| # -------------------------------------------------------- | |
| # TABLE | |
| # -------------------------------------------------------- | |
| with gr.Column(elem_classes=["card"]): | |
| gr.Markdown("### 📊 Token-Level Results") | |
| table_output = gr.Dataframe( | |
| headers=["Token", "UPOS", "Confidence"], | |
| datatype=["str", "str", "str"], | |
| interactive=False, | |
| wrap=True | |
| ) | |
| # -------------------------------------------------------- | |
| # MODEL INFORMATION | |
| # -------------------------------------------------------- | |
| with gr.Row(): | |
| with gr.Column(elem_classes=["card"]): | |
| gr.Markdown( | |
| """ | |
| ### 🧠 Model Information | |
| **Model:** `shahbakhsh/BalPOS` | |
| **Task:** Balochi POS Tagging | |
| **Backbone:** BalBERT | |
| **UPOS labels:** 16 | |
| ### Reported final evaluation | |
| | Metric | Score | | |
| |---|---:| | |
| | Accuracy | **87.26%** | | |
| | Balanced Accuracy | **78.81%** | | |
| | Macro F1 | **78.99%** | | |
| | Weighted F1 | **87.22%** | | |
| | MCC | **0.8526** | | |
| """ | |
| ) | |
| with gr.Column(elem_classes=["card"]): | |
| gr.Markdown( | |
| """ | |
| ### 🏷️ Supported UPOS Tags | |
| `NOUN` · `PROPN` · `PRON` · `VERB` | |
| `AUX` · `ADJ` · `ADV` · `ADP` | |
| `CCONJ` · `SCONJ` · `DET` · `NUM` | |
| `PART` · `PUNCT` · `INTJ` · `X` | |
| """ | |
| ) | |
| # ========================================================== | |
| # IMPORTANT: ALL EVENT HANDLERS MUST STAY INSIDE gr.Blocks | |
| # (this indentation is what the previous version was missing) | |
| # ========================================================== | |
| submit_btn.click( | |
| fn=tag_sentence, | |
| inputs=sentence_input, | |
| outputs=[highlighted_output, table_output, summary_output] | |
| ) | |
| sentence_input.submit( | |
| fn=tag_sentence, | |
| inputs=sentence_input, | |
| outputs=[highlighted_output, table_output, summary_output] | |
| ) | |
| clear_btn.click( | |
| fn=lambda: ("", [], [], "Enter a sentence to begin."), | |
| inputs=None, | |
| outputs=[sentence_input, highlighted_output, table_output, summary_output] | |
| ) | |
| # ============================================================ | |
| # LAUNCH | |
| # ============================================================ | |
| if __name__ == "__main__": | |
| demo.launch( | |
| css=CUSTOM_CSS, | |
| theme=gr.themes.Soft( | |
| primary_hue="teal", | |
| neutral_hue="slate" | |
| ) | |
| ) |