import gradio as gr import torch import spaces import torch.nn as nn import numpy as np import os import zipfile import urllib.request from tqdm import tqdm # --- 1. MODEL ARCHITECTURE --- class BiLSTMClassifier(nn.Module): def __init__(self, input_dim=300, hidden_dim=256, num_classes=5): super().__init__() self.lstm = nn.LSTM(input_dim, hidden_dim, batch_first=True, bidirectional=True, num_layers=2, dropout=0.3) self.dropout = nn.Dropout(0.3) self.fc = nn.Linear(hidden_dim * 2, num_classes) def forward(self, x): _, (h, _) = self.lstm(x) h = torch.cat([h[-2], h[-1]], dim=-1) h = self.dropout(h) return self.fc(h) # Helper class to display a download progress bar in the terminal class DownloadProgressBar(tqdm): def update_to(self, b=1, bsize=1, tsize=None): if tsize is not None: self.total = tsize self.update(b * bsize - self.n) def load_glove(): zip_path = "glove.6B.zip" txt_path = "glove/glove.6B.300d.txt" os.makedirs("glove", exist_ok=True) # 1. DOWNLOAD WITH PROGRESS BAR if not os.path.exists(txt_path): if not os.path.exists(zip_path): print("Downloading GloVe embeddings (~822 MB)...") url = "https://huggingface.co/stanfordnlp/glove/resolve/main/glove.6B.zip" with DownloadProgressBar(unit='B', unit_scale=True, miniters=1, desc="Downloading GloVe") as t: urllib.request.urlretrieve(url, filename=zip_path, reporthook=t.update_to) # 2. EXTRACT WITH FEEDBACK print("Extracting GloVe zip file...") with zipfile.ZipFile(zip_path, 'r') as zip_ref: zip_ref.extractall("glove") print("Extraction complete!") # 3. LOAD TEXT FILE WITH LINE-BY-LINE PROGRESS BAR embeddings = {} print("Loading vectors into memory...") with open(txt_path, encoding="utf-8") as f: # GloVe 6B 300d has exactly 400,000 lines/words for line in tqdm(f, total=400000, desc="Parsing GloVe Vectors"): values = line.split() embeddings[values[0]] = np.array(values[1:], dtype=np.float32) print(f"Successfully loaded {len(embeddings):,} GloVe vectors!") return embeddings def text_to_seq(text, glove_dict, max_len=50, dim=300): tokens = str(text).lower().split()[:max_len] vecs = [glove_dict.get(t, np.zeros(dim, dtype=np.float32)) for t in tokens] while len(vecs) < max_len: vecs.append(np.zeros(dim, dtype=np.float32)) return np.array(vecs, dtype=np.float32) device = torch.device("cpu") model = BiLSTMClassifier() if os.path.exists("bilstm_weights.pth"): model.load_state_dict(torch.load("bilstm_weights.pth", map_location=device)) model.to(device) model.eval() glove = load_glove() OPTIONS = ["A", "B", "C", "D", "E"] # --- 3. PREDICTION FUNCTION --- @spaces.GPU def predict_mcq(prompt, opt_a, opt_b, opt_c, opt_d, opt_e): if not os.path.exists("bilstm_weights.pth"): return "Error: Model weights not found.", {} input_options = [opt_a, opt_b, opt_c, opt_d, opt_e] if not prompt or not all(input_options): return "Please fill in the prompt and all 5 options.", {} seqs = [] for opt in input_options: combined = str(prompt) + " " + str(opt) seqs.append(text_to_seq(combined, glove)) seqs_tensor = torch.tensor(np.array(seqs, dtype=np.float32)) with torch.no_grad(): scores = [] for i in range(5): s = model(seqs_tensor[i].unsqueeze(0))[:, i].item() scores.append(s) # Format outputs top_3_idx = np.argsort(scores)[::-1][:3] top_3_preds = " ".join([OPTIONS[i] for i in top_3_idx]) # Create a dictionary of scores for the Gradio Label component confidence_dict = {OPTIONS[i]: float(scores[i]) for i in range(5)} return f"Top 3 Predictions: {top_3_preds}", confidence_dict # --- 4. GRADIO UI LAYOUT --- with gr.Blocks() as demo: gr.Markdown("# 🧠 Smart MCQ Solver (BiLSTM)") gr.Markdown("Enter a prompt and 5 options to get the top 3 predicted answers.") with gr.Row(): with gr.Column(scale=2): prompt_input = gr.Textbox(lines=3, label="Question / Prompt") opt_a_input = gr.Textbox(label="Option A") opt_b_input = gr.Textbox(label="Option B") opt_c_input = gr.Textbox(label="Option C") opt_d_input = gr.Textbox(label="Option D") opt_e_input = gr.Textbox(label="Option E") submit_btn = gr.Button("Predict Answer", variant="primary") with gr.Column(scale=1): text_output = gr.Textbox(label="Result", lines=2) label_output = gr.Label(label="Raw Model Scores (Logits)") submit_btn.click( fn=predict_mcq, inputs=[prompt_input, opt_a_input, opt_b_input, opt_c_input, opt_d_input, opt_e_input], outputs=[text_output, label_output] ) if __name__ == "__main__": # Launch locally or on Spaces demo.launch(theme=gr.themes.Soft(), ssr_mode=False)