Spaces:
Sleeping
Sleeping
File size: 7,883 Bytes
702ce96 bbeccb7 702ce96 3e73af9 702ce96 3e73af9 702ce96 3e73af9 702ce96 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 |
import spaces
import torch
import gradio as gr
from transformers import AutoTokenizer, AutoModelForSequenceClassification
MODEL_NAME = "cross-encoder/nli-deberta-v3-small"
OPTION_LETTERS = ["A", "B", "C", "D", "E"]
MAX_LENGTH = 256
# The model is loaded on the first prediction rather than at import time, so the
# Space finishes booting immediately and a slow download can never crash startup.
_MODEL_CACHE = {}
def load_model():
"""Load tokenizer and model once and keep them in memory."""
if "model" not in _MODEL_CACHE:
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
model = AutoModelForSequenceClassification.from_pretrained(MODEL_NAME)
model.eval()
# NLI checkpoints do not agree on label ordering, so the entailment
# column is read from the config instead of being hard-coded. A wrong
# index would silently invert the ranking.
entailment_index = None
for label, index in model.config.label2id.items():
if label.lower().startswith("entail"):
entailment_index = index
if entailment_index is None:
entailment_index = model.config.num_labels - 1
_MODEL_CACHE["tokenizer"] = tokenizer
_MODEL_CACHE["model"] = model
_MODEL_CACHE["entailment_index"] = entailment_index
return (
_MODEL_CACHE["tokenizer"],
_MODEL_CACHE["model"],
_MODEL_CACHE["entailment_index"],
)
@spaces.GPU(duration=30)
def score_options(prompt, options):
"""Return one entailment probability per option, in A-E order."""
tokenizer, model, entailment_index = load_model()
model.to("cuda")
premises = [prompt] * len(options)
encoded = tokenizer(
premises,
options,
truncation=True,
max_length=MAX_LENGTH,
padding=True,
return_tensors="pt",
).to("cuda")
with torch.no_grad():
logits = model(**encoded).logits
def rank_options(option_scores):
"""Return option letters sorted from highest score to lowest."""
order = sorted(range(len(option_scores)), key=lambda i: option_scores[i], reverse=True)
return [OPTION_LETTERS[i] for i in order]
def format_results(prompt, options, option_scores, ranked_letters):
"""Build the markdown result table shown to the user."""
score_by_letter = dict(zip(OPTION_LETTERS, option_scores))
text_by_letter = dict(zip(OPTION_LETTERS, options))
lines = ["| Rank | Option | Answer text | Entailment score |", "|---|---|---|---|"]
for position, letter in enumerate(ranked_letters[:3], start=1):
answer_text = text_by_letter[letter]
if len(answer_text) > 90:
answer_text = answer_text[:90] + "..."
lines.append(
f"| {position} | **{letter}** | {answer_text} | {score_by_letter[letter]:.4f} |"
)
remaining = ", ".join(
f"{letter} ({score_by_letter[letter]:.4f})" for letter in ranked_letters[3:]
)
lines.append("")
lines.append(f"Not in the top three: {remaining}")
return "\n".join(lines)
def predict(prompt, option_a, option_b, option_c, option_d, option_e):
"""Validate the inputs, score the options and return the top three."""
prompt = (prompt or "").strip()
options = [(text or "").strip() for text in
[option_a, option_b, option_c, option_d, option_e]]
if not prompt:
return "Please enter a question before predicting.", ""
missing = [OPTION_LETTERS[i] for i, text in enumerate(options) if not text]
if missing:
return f"Please fill in every option. Still empty: {', '.join(missing)}.", ""
try:
option_scores = score_options(prompt, options)
except Exception as error: # keeps the app alive if the model fails to load
return f"Something went wrong while running the model: {error}", ""
ranked_letters = rank_options(option_scores)
results_table = format_results(prompt, options, option_scores, ranked_letters)
submission_string = " ".join(ranked_letters[:3])
return results_table, submission_string
EXAMPLE_ONE = [
"Which property of a material determines how much it resists the flow of "
"electric current?",
"Resistivity, an intrinsic property that depends on the material itself",
"Mass, since heavier materials always conduct electricity less well",
"Volume, because a larger object always has a larger current",
"Colour, as darker materials absorb more electrical energy",
"Density, which fixes the number of free electrons in every metal",
]
EXAMPLE_TWO = [
"What is the main reason a catalyst increases the rate of a chemical reaction?",
"It provides an alternative pathway with a lower activation energy",
"It increases the total energy released by the reaction",
"It shifts the position of equilibrium towards the products",
"It raises the temperature of the reaction mixture directly",
"It permanently bonds to the reactants and is consumed by them",
]
DESCRIPTION = """
# Smart MCQ Solver
Type a multiple-choice question and its five options. The model ranks all five and
returns the **top three in order**, which is the submission format used by the
Kaggle *Smart MCQ Solver Challenge* (metric: MAP@3).
**What is running here:** `cross-encoder/nli-deberta-v3-small` used zero-shot.
The question is the premise, each option is a hypothesis, and options are ranked
by entailment probability. This is Model 2 from the project. It is not the
fine-tuned eight-member DeBERTa-v3 ensemble that produced the 0.736 leaderboard
score, so expect weaker answers than that number suggests.
Devang Khirsaria · 23f1001439 · BS in Data Science and Applications, IIT Madras
"""
with gr.Blocks() as demo:
gr.Markdown(DESCRIPTION)
with gr.Row():
with gr.Column():
prompt_box = gr.Textbox(
label="Question",
placeholder="Type the question here",
lines=3,
value=EXAMPLE_ONE[0],
)
option_boxes = []
for index, letter in enumerate(OPTION_LETTERS):
option_boxes.append(
gr.Textbox(
label=f"Option {letter}",
placeholder=f"Text of option {letter}",
lines=2,
value=EXAMPLE_ONE[index + 1],
)
)
with gr.Row():
predict_button = gr.Button("Predict top 3", variant="primary")
clear_button = gr.Button("Clear")
with gr.Row():
example_one_button = gr.Button("Load physics example")
example_two_button = gr.Button("Load chemistry example")
with gr.Column():
results_output = gr.Markdown(
label="Ranked predictions",
value="Press **Predict top 3** to run the model. "
"The first run downloads the model and takes about a minute; "
"later runs take a couple of seconds.",
)
submission_output = gr.Textbox(
label="Kaggle submission format",
info="Exactly the three letters you would write in the Prediction column.",
)
all_inputs = [prompt_box] + option_boxes
predict_button.click(
fn=predict,
inputs=all_inputs,
outputs=[results_output, submission_output],
)
clear_button.click(
fn=lambda: ["", "", "", "", "", ""],
inputs=None,
outputs=all_inputs,
)
example_one_button.click(
fn=lambda: EXAMPLE_ONE,
inputs=None,
outputs=all_inputs,
)
example_two_button.click(
fn=lambda: EXAMPLE_TWO,
inputs=None,
outputs=all_inputs,
)
if __name__ == "__main__":
demo.launch()
|