How can I best use Hugging Face’s transformer models to auto-generate quiz questions or reading comprehension tasks for students in an education app?

How can I use Hugging Face AI models (like BERT, GPT, T5, etc.) to automatically create quiz questions or reading comprehension exercises for students inside an education app?

1 Like

For example, if you were to use the T5 model, it would look something like this.

"""
Simple QG → QA-verify → MCQ demo using Hugging Face Transformers.

Models:
- QG (end-to-end): valhalla/t5-base-e2e-qg
  https://huggingface.co/valhalla/t5-base-e2e-qg
- QA verifier (extractive, SQuAD2): deepset/roberta-base-squad2
  https://huggingface.co/deepset/roberta-base-squad2
- Distractor generator (optional): voidful/bart-distractor-generation-both
  https://huggingface.co/voidful/bart-distractor-generation-both

Transformers pipelines docs:
https://huggingface.co/docs/transformers/en/main_classes/pipelines
"""

from typing import List, Dict
from transformers import pipeline

# 1) Load pipelines (cache by default). Adjust device_map if you have a GPU.
qg = pipeline("text2text-generation",
              model="valhalla/t5-base-e2e-qg",            # e2e QG from paragraph
              max_new_tokens=64)

qa = pipeline("question-answering",
              model="deepset/roberta-base-squad2")        # verifies answerability

# Optional distractor generator. Comment out if you prefer retrieval-based distractors.
try:
    dg = pipeline("text2text-generation",
                  model="voidful/bart-distractor-generation-both",
                  max_new_tokens=24)
except Exception:
    dg = None  # falls back to simple distractors later


def _split_e2e_output(text: str) -> List[str]:
    """
    Many e2e QG checkpoints return multiple questions separated by tokens/newlines.
    This splitter is conservative. Tweak for your chosen model’s formatting.
    """
    parts = []
    for sep in ["<sep>", "\n", " ? ", "? "]:
        if sep in text:
            parts = [p.strip() for p in text.split(sep)]
            break
    if not parts:
        parts = [text.strip()]
    # Re-append '?' where missing.
    return [p if p.endswith("?") else (p + "?") for p in parts if p]


def generate_items(context: str, max_q: int = 5) -> List[Dict]:
    """
    Input: passage string.
    Output: list of dicts with {question, answer, distractors}.
    """
    # Stage 1: propose questions
    out = qg(context, do_sample=False)
    raw = out[0]["generated_text"] if isinstance(out, list) else out["generated_text"]
    questions = _split_e2e_output(raw)[:max_q]

    items = []
    for q in questions:
        # Stage 2: verify with extractive reader
        pred = qa(question=q, context=context)
        ans = pred.get("answer", "").strip()
        conf = float(pred.get("score", 0.0))

        # Keep only confident, non-empty answers
        if not ans or conf < 0.35:  # tune threshold per your QA model
            continue

        # Stage 3: distractors (seq2seq) or a trivial fallback
        distractors = []
        if dg is not None:
            d_in = f"question: {q}  answer: {ans}  context: {context}"
            try:
                d = dg(d_in, do_sample=False)[0]["generated_text"].strip()
                if d and d.lower() != ans.lower():
                    distractors.append(d)
            except Exception:
                pass

        if not distractors:
            # naive fallback: take distinct words from context far from the answer
            # Replace with retrieval-based candidates in production.
            tokens = [t.strip(",.;:()") for t in context.split() if len(t) > 3]
            distractors = list({t for t in tokens if t.lower() not in ans.lower()})[:3]

        items.append({
            "question": q,
            "answer": ans,
            "distractors": distractors[:3]
        })
    return items


if __name__ == "__main__":
    sample = (
        "The Moon is Earth's only natural satellite. It formed about 4.5 billion years ago, "
        "likely after a giant impact. The average distance to the Moon is about 384,400 kilometers, "
        "and its gravitational influence causes ocean tides on Earth."
    )
    mcqs = generate_items(sample, max_q=4)
    for i, it in enumerate(mcqs, 1):
        print(f"\nQ{i}. {it['question']}")
        print(f"   A: {it['answer']}")
        for j, d in enumerate(it['distractors'], 1):
            print(f"   D{j}: {d}")
"""
Q1. What is Earth's only natural satellite?
   A: The Moon
   D1: C E n the E , , G G G

Q2. How long ago did the Moon form?
   A: 4.5 billion years ago
   D1: C E n n n , , G G G

Q3. What is the average distance to the Moon?
   A: 384,400 kilometers
   D1: The E n n n , , , G
"""