Introduction to LLMs in Python: 10 Code-Along Examples

Get started with LLMs by running them. Ten copy-and-run Hugging Face examples covering sentiment, summarization, translation, text generation, NER, fill-mask, zero-shot classification, question answering, tokenization, and evaluation with ROUGE.

The LLMs guide’s central idea is that the Hugging Face pipeline function bundles a model, its tokenizer, and the task logic into a single call, so most natural language tasks become one line. This workbook runs one task per example, and along the way ties each to the transformer architecture behind it: encoder-only models understand and classify, decoder-only models generate, and encoder-decoder models transform one sequence into another. You will summarise, translate, generate, tag entities, fill masks, classify with zero examples, answer questions, look under the hood at tokenization, and finish by scoring a summary with a real metric.

1. The pipeline API: sentiment analysis

The pipeline is the entry point. Name a task and it downloads a sensible default model and handles everything from tokenization to decoding.

from transformers import pipeline
classifier = pipeline("sentiment-analysis")
for text in ["This course finally made transformers click.",
"The setup instructions were a nightmare."]:
result = classifier(text)[0]
print(f"{result['label']} ({result['score']:.3f}) {text}")
# POSITIVE (0.999) ...
# NEGATIVE (0.999) ...

Three lines and you have a working classifier. The score is the model’s confidence, and printing it alongside the label is a good habit: it tells you when the model is sure versus merely guessing. This sentiment model is encoder-only under the hood, the architecture built for understanding rather than generating.

2. Summarization with BART

Summarization condenses long text into a short version. BART is an encoder-decoder model: the encoder reads the whole document, the decoder writes a new, shorter sequence.

from transformers import pipeline
summarizer = pipeline("summarization", model="facebook/bart-large-cnn")
article = """
Organic search traffic rose 18 percent this quarter, driven by the site
migration that completed in April. Paid channels were flat, and social
declined as budgets shifted to search. Conversion improved from 2.1 to 2.6
percent after the new checkout launched, though average order value held steady.
"""
summary = summarizer(article, min_length=20, max_length=50)
print(summary[0]["summary_text"])

BART writes genuinely new sentences rather than copying phrases, which is what “abstractive” summarization means. The min_length and max_length arguments box the output, and tuning them is most of the practical craft: too loose and it rambles, too tight and it truncates mid-thought.

3. Translation with Helsinki-NLP

Translation is another encoder-decoder task: read a sentence in one language, produce it in another. The Helsinki-NLP models cover hundreds of language pairs, each a small dedicated model.

from transformers import pipeline
translator = pipeline("translation", model="Helsinki-NLP/opus-mt-en-fr")
sentences = ["Where is the nearest train station?",
"This dashboard updates every morning."]
for s in sentences:
print(s, "->", translator(s)[0]["translation_text"])
# Where is the nearest train station? -> Ou est la gare la plus proche ?

Swap opus-mt-en-fr for opus-mt-en-de and you translate to German instead; the pipeline call is identical. This is the same encoder-decoder shape as summarization, which is the point the guide draws out: one architecture family, many sequence-to-sequence tasks.

4. Text generation with GPT-2

Generation is the decoder-only task: predict the next token, append it, repeat. GPT-2 is small and fast, and the generation parameters give you control over length and creativity.

from transformers import pipeline
generator = pipeline("text-generation", model="gpt2")
outputs = generator(
"The three habits of a great data analyst are",
max_new_tokens=40,
temperature=0.7, # higher = more varied, lower = more predictable
num_return_sequences=2 # two independent continuations
)
for i, out in enumerate(outputs, 1):
print(f"--- {i} ---\n{out['generated_text']}\n")

Two continuations of the same prompt come back, and raising temperature toward 1.0 makes each run more surprising. Decoder-only is the family behind GPT, LLaMA, and every chat model; GPT-2 is the tiny ancestor that makes the mechanics visible without a download the size of a hard drive.

5. Named entity recognition

NER labels the people, places, and organisations in text. It is a token-classification task, so it needs an encoder that produces a label for every token, then groups them into spans.

from transformers import pipeline
ner = pipeline("ner", aggregation_strategy="simple") # group tokens into whole entities
text = "Priya Shah joined Datalad in London last March."
for entity in ner(text):
print(f"{entity['word']:10s} {entity['entity_group']} ({entity['score']:.2f})")
# Priya Shah PER
# Datalad ORG
# London LOC

aggregation_strategy="simple" is what merges the sub-word pieces of “Priya Shah” back into one entity with one label, which is almost always what you want. NER powers redaction, resume parsing, and search indexing, and like sentiment it runs on an encoder because the job is understanding, not generating.

6. Fill-mask: the task encoders are trained on

Fill-mask asks the model to predict a hidden word. This is not a party trick; it is the actual objective BERT-style encoders are pretrained on, so it reveals what the model learned.

from transformers import pipeline
unmasker = pipeline("fill-mask", model="distilbert-base-uncased")
results = unmasker("A data analyst spends most of their time [MASK] data.")
for r in results[:3]:
print(f"{r['token_str']:10s} {r['score']:.3f}")
# analyzing 0.21
# collecting 0.11 ...

The model ranks candidate words for the [MASK] position by probability. Seeing it prefer “analyzing” and “collecting” shows it has absorbed real associations from its training text. Every encoder model in this workbook was pretrained on exactly this masked-word task before being fine-tuned for sentiment, NER, or classification.

7. Zero-shot classification

Zero-shot classification sorts text into labels you invent at call time, with no training. It is the fastest way to prototype a classifier when you have no labelled data.

from transformers import pipeline
classifier = pipeline("zero-shot-classification", model="facebook/bart-large-mnli")
ticket = "My invoice shows the wrong VAT amount for last month."
labels = ["billing", "technical support", "sales", "complaint"]
result = classifier(ticket, candidate_labels=labels)
for label, score in zip(result["labels"], result["scores"]):
print(f"{label:18s} {score:.3f}")
# billing 0.79 ...

Change the labels list and rerun; there is no retraining step, which is what “zero-shot” means. This is invaluable for routing and triage prototypes where writing a labelled dataset first would be overkill.

8. Extractive question answering

Extractive QA finds the answer to a question inside a passage you supply, returning the exact span. Because the answer is lifted verbatim from the text, it is easy to verify.

from transformers import pipeline
qa = pipeline("question-answering",
model="distilbert-base-cased-distilled-squad")
context = """The onboarding project began on 3 February, led by Priya Shah.
The data audit is due on 28 March, with full rollout planned for July."""
for question in ["Who leads the project?", "When is the data audit due?"]:
answer = qa(question=question, context=context)
print(f"{question} -> {answer['answer']} ({answer['score']:.2f})")
# Who leads the project? -> Priya Shah (0.98)
# When is the data audit due? -> 28 March (0.97)

The model does not compose an answer; it points at one, returning a substring of the context plus a confidence. That makes extractive QA reliable and traceable, which is why it suits document search and knowledge-base lookup where a made-up answer would be dangerous.

9. Under the hood: tokenization and the three architectures

The pipeline hides the tokenizer, but seeing it demystifies everything above. AutoTokenizer loads the exact tokenizer a model was trained with, and the AutoModelFor... class you choose reflects the architecture family the task needs.

from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("distilbert-base-uncased")
text = "Transformers tokenize text into pieces."
tokens = tokenizer.tokenize(text)
ids = tokenizer.encode(text)
print("tokens:", tokens) # ['transformers', 'token', '##ize', ...] ## = continuation
print("ids: ", ids) # [101, 19081, ...] with [CLS]/[SEP] added
print("decoded:", tokenizer.decode(ids))
# padding + truncation are what make a batch uniform for fine-tuning
batch = tokenizer(["short text", "a much longer piece of input text here"],
padding=True, truncation=True, return_tensors="pt")
print("batch input_ids shape:", batch["input_ids"].shape)

Two things to take away. First, the model never sees words; it sees token ids, and padding=True, truncation=True is how you make a batch of different-length texts into one rectangular tensor for fine-tuning. Second, the architecture maps to the task: AutoModelForSequenceClassification wraps an encoder for classification, AutoModelForCausalLM wraps a decoder for generation, and AutoModelForSeq2SeqLM wraps an encoder-decoder for summarization and translation.

10. Evaluating outputs with ROUGE

You cannot improve what you cannot measure. Generation tasks use dedicated metrics: ROUGE for summaries, BLEU for translation, perplexity for fluency. The evaluate library computes them in a couple of lines.

import evaluate
rouge = evaluate.load("rouge")
reference = "Organic search drove an 18 percent traffic rise after the April migration."
generated = "Traffic rose 18 percent, driven by organic search following the April site migration."
scores = rouge.compute(predictions=[generated], references=[reference])
for metric, value in scores.items():
print(f"{metric}: {value:.3f}")
# rouge1: 0.60 rouge2: 0.31 rougeL: 0.53 ...

ROUGE measures the overlap of words and phrases between the generated summary and a human reference, so a higher score means closer to the reference. The same evaluate.load pattern gives you bleu for translation and perplexity for raw fluency. Metrics turn a vague sense that “the summary looks fine” into a number you can track as you change models or parameters.

Work through these and you have used the whole toolkit from the article: the pipeline API across summarization, translation, generation, NER, fill-mask, zero-shot, and QA; the tokenizer and the three architecture families beneath them; and a real evaluation metric. The mental model to keep is the guide’s: pick the task, and the architecture follows. Encoders understand, decoders generate, encoder-decoders transform, and the pipeline lets you reach any of them in a single line before you ever need to fine-tune.

See you soon.

View Comments (1)

Leave a Reply

Prev Next

Subscribe to My Newsletter

Subscribe to my email newsletter to get the latest posts delivered right to your email. Pure inspiration, zero spam.

Discover more from Discuss Data Science, Machine Learning and Analytics

Subscribe now to keep reading and get access to the full archive.

Continue reading