SriragData commited on
Commit
f12132c
·
1 Parent(s): 83d73f9

Smart MCQ Solver: DeBERTa-v3-large demo

Browse files
Files changed (3) hide show
  1. README.md +34 -7
  2. app.py +151 -0
  3. requirements.txt +5 -0
README.md CHANGED
@@ -1,14 +1,41 @@
1
  ---
2
- title: Smart Mcq Solver
3
- emoji: 👀
4
- colorFrom: green
5
- colorTo: purple
6
  sdk: gradio
7
- sdk_version: 6.22.0
8
- python_version: '3.12'
9
  app_file: app.py
10
  pinned: false
11
  license: mit
 
12
  ---
13
 
14
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Smart MCQ Solver
3
+ emoji: 🧠
4
+ colorFrom: blue
5
+ colorTo: indigo
6
  sdk: gradio
7
+ sdk_version: 5.9.1
 
8
  app_file: app.py
9
  pinned: false
10
  license: mit
11
+ short_description: DeBERTa-v3-large ranking five candidate MCQ answers
12
  ---
13
 
14
+ # Smart MCQ Solver
15
+
16
+ Five-option multiple-choice question answering over science and philosophy,
17
+ scored by MAP@3. Fine-tuned `microsoft/deberta-v3-large` used as an
18
+ `AutoModelForMultipleChoice` cross-encoder.
19
+
20
+ | Metric | Value |
21
+ |---|---|
22
+ | 3-fold grouped CV MAP@3 | **0.7567** |
23
+ | Random MAP@3 baseline | 0.3667 |
24
+ | Leakage-free project estimate | 0.6817 |
25
+
26
+ Set the `MODEL_ID` Space variable to your model repo, e.g.
27
+ `your-username/smart-mcq-deberta-v3-large`.
28
+
29
+ ## How it works
30
+
31
+ Each of the five options is paired with the question to form five
32
+ `(question, option)` sequences of shape `(5, L)`. The encoder scores each pair
33
+ independently; a linear head reduces each to one logit; the five logits are
34
+ reshaped to `(1, 5)` and softmaxed, so the options compete in a single
35
+ distribution. The top three, in order, are the MAP@3 submission.
36
+
37
+ ## Limitations
38
+
39
+ Trained on 2,000 rows covering 252 unique questions. Closed-book — no retrieval,
40
+ no citations, and no abstention mechanism, so it ranks five options confidently
41
+ regardless of whether it knows the topic.
app.py ADDED
@@ -0,0 +1,151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Smart MCQ Solver - Gradio demo.
2
+
3
+ Loads the fine-tuned DeBERTa-v3-large multiple-choice model from the Hub and
4
+ ranks five candidate answers for a question.
5
+ """
6
+ import os
7
+ import numpy as np
8
+ import torch
9
+ import gradio as gr
10
+ from transformers import AutoTokenizer, AutoModelForMultipleChoice
11
+
12
+ # Available only on ZeroGPU hardware. On CPU-basic the import fails and we stay on CPU,
13
+ # so this one file runs unchanged on either.
14
+ try:
15
+ import spaces
16
+ HAS_ZEROGPU = True
17
+ except ImportError:
18
+ HAS_ZEROGPU = False
19
+
20
+ MODEL_ID = os.environ.get("MODEL_ID", "SriragData/smart-mcq-deberta-v3-large")
21
+ MAX_LEN = 256
22
+ LAB = list("ABCDE")
23
+
24
+ torch.set_num_threads(max(1, (os.cpu_count() or 2) // 2))
25
+
26
+ print(f"loading {MODEL_ID} ...")
27
+ tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)
28
+
29
+ # fp32 is forced explicitly on purpose: transformers honours the dtype recorded in
30
+ # config.json, and an fp16 DeBERTa-v3 collapses to a constant output with no error
31
+ # raised. The kwarg was renamed (torch_dtype -> dtype) between versions, so try both
32
+ # rather than pinning ourselves to one.
33
+ try:
34
+ model = AutoModelForMultipleChoice.from_pretrained(MODEL_ID, dtype=torch.float32)
35
+ except TypeError:
36
+ model = AutoModelForMultipleChoice.from_pretrained(MODEL_ID, torch_dtype=torch.float32)
37
+ model = model.eval()
38
+ assert next(model.parameters()).dtype == torch.float32, "model did not load in fp32"
39
+ print("loaded. params:", sum(p.numel() for p in model.parameters()))
40
+
41
+
42
+ TEMPLATES = [
43
+ "Pick the best possible answer:", "Select the most accurate option:",
44
+ "Identify the correct statement:", "Determine the correct option:",
45
+ "Choose the correct answer:", "Which of the following is correct?",
46
+ ]
47
+
48
+
49
+ def clean(prompt: str) -> str:
50
+ """Strip the boilerplate wrappers the training data was generated with."""
51
+ for t in TEMPLATES:
52
+ prompt = prompt.replace(t, "")
53
+ return prompt.strip()
54
+
55
+
56
+ @torch.no_grad()
57
+ def solve(question, a, b, c, d, e):
58
+ options = [a, b, c, d, e]
59
+ if not question or not question.strip():
60
+ return "Enter a question.", None
61
+ if any(not str(o).strip() for o in options):
62
+ return "All five options are required.", None
63
+
64
+ # On ZeroGPU, CUDA only exists inside the decorated call, so resolve the device here
65
+ # rather than at import time.
66
+ device = "cuda" if torch.cuda.is_available() else "cpu"
67
+ m = model.to(device)
68
+
69
+ q = clean(question)
70
+ enc = tokenizer([q] * 5, [str(o) for o in options],
71
+ truncation=True, max_length=MAX_LEN,
72
+ padding=True, return_tensors="pt")
73
+ logits = m(input_ids=enc["input_ids"].unsqueeze(0).to(device),
74
+ attention_mask=enc["attention_mask"].unsqueeze(0).to(device)).logits[0]
75
+ probs = torch.softmax(logits.float().cpu(), dim=-1).numpy()
76
+
77
+ order = np.argsort(-probs)
78
+ top3 = " ".join(LAB[i] for i in order[:3])
79
+ verdict = (f"### {LAB[order[0]]} — {options[order[0]]}\n\n"
80
+ f"**MAP@3 submission format:** `{top3}`")
81
+
82
+ rows = [[LAB[i], str(options[i])[:200], round(float(probs[i]), 4),
83
+ int(np.where(order == i)[0][0]) + 1] for i in range(5)]
84
+ rows.sort(key=lambda r: r[3])
85
+ return verdict, rows
86
+
87
+
88
+ if HAS_ZEROGPU:
89
+ # Requests a GPU slice for the duration of the call. Applied programmatically so the
90
+ # same file still imports on CPU-basic hardware, where `spaces` does not exist.
91
+ solve = spaces.GPU(duration=60)(solve)
92
+
93
+
94
+ EXAMPLES = [
95
+ ["Which philosopher argued that existence precedes essence?",
96
+ "Immanuel Kant", "Jean-Paul Sartre", "David Hume", "Rene Descartes", "Baruch Spinoza"],
97
+ ["What is the primary function of mitochondria in a eukaryotic cell?",
98
+ "Protein synthesis", "Storage of genetic material",
99
+ "Production of ATP through oxidative phosphorylation",
100
+ "Breakdown of cellular waste", "Regulation of cell division"],
101
+ ["According to the second law of thermodynamics, what happens to the entropy "
102
+ "of an isolated system over time?",
103
+ "It decreases to zero", "It remains exactly constant",
104
+ "It never decreases", "It oscillates periodically", "It becomes negative"],
105
+ ]
106
+
107
+ CSS = """
108
+ .gradio-container {max-width: 1000px !important}
109
+ footer {visibility: hidden}
110
+ """
111
+
112
+ with gr.Blocks(title="Smart MCQ Solver", css=CSS, theme=gr.themes.Soft()) as demo:
113
+ gr.Markdown(
114
+ "# Smart MCQ Solver\n"
115
+ "Fine-tuned **DeBERTa-v3-large** ranking five candidate answers. "
116
+ "Scored by MAP@3: credit 1, 1/2, 1/3 if the correct option is ranked 1st, 2nd or 3rd.\n\n"
117
+ "*3-fold grouped CV MAP@3 **0.7567** — random baseline is 0.3667. "
118
+ "Closed-book: it answers from its weights, with no retrieval and no abstention.*"
119
+ )
120
+ with gr.Row():
121
+ with gr.Column(scale=3):
122
+ question = gr.Textbox(label="Question", lines=2,
123
+ placeholder="Ask a science or philosophy question...")
124
+ with gr.Row():
125
+ a = gr.Textbox(label="A")
126
+ b = gr.Textbox(label="B")
127
+ with gr.Row():
128
+ c = gr.Textbox(label="C")
129
+ d = gr.Textbox(label="D")
130
+ e = gr.Textbox(label="E")
131
+ btn = gr.Button("Rank the options", variant="primary")
132
+ with gr.Column(scale=2):
133
+ verdict = gr.Markdown(label="Answer")
134
+ table = gr.Dataframe(headers=["Option", "Text", "Probability", "Rank"],
135
+ datatype=["str", "str", "number", "number"],
136
+ label="All five, ranked", wrap=True)
137
+
138
+ btn.click(solve, [question, a, b, c, d, e], [verdict, table])
139
+ gr.Examples(EXAMPLES, inputs=[question, a, b, c, d, e])
140
+
141
+ gr.Markdown(
142
+ "---\n"
143
+ "**Limitations.** Trained on 2,000 rows covering 252 unique questions, so coverage "
144
+ "is narrow. It has no way to say *I don't know* — it will rank five options "
145
+ "confidently even when it knows nothing about the topic. Reported scores reflect a "
146
+ "dataset whose test split overlaps its training split heavily; the leakage-free "
147
+ "estimate for this project is 0.6817."
148
+ )
149
+
150
+ if __name__ == "__main__":
151
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ # gradio comes from sdk_version in README.md - do not pin it here.
2
+ # torch is provided by the Space image (pinning it can break ZeroGPU) - do not pin it.
3
+ transformers>=4.44
4
+ sentencepiece>=0.2.0
5
+ protobuf>=4.25