Working with Hugging Face

Hugging Face serves as an AI platform featuring pretrained models, datasets, and demo applications. It enables users to run models locally or via inference providers, catering to various data processing tasks efficiently.

Think of Hugging Face as GitHub for AI. Three things live on the platform: pretrained models you can download and use immediately without training, community-curated datasets for training and evaluation, and Spaces where developers host live demo applications built on top of those models.

Every model comes with a model card — the equivalent of a README — explaining what it does, how it was trained, its license, and example usage. Most models are usable in three to four lines of Python. Two libraries cover the majority of use cases.

pip install transformers datasets huggingface_hub

transformers handles loading and running models locally. datasets downloads and manages data from the Hub. huggingface_hubenables calls to hosted inference providers when you need to run models remotely.

Running Models: Local vs Inference Providers

You have two ways to run a model. Local inference downloads the model to your machine and runs it there. Inference providers send your prompt to a hosted service that runs the model on their hardware and returns the result. Local works well for small to medium models; providers handle models too large for a laptop.

Local inference with pipeline

The pipeline class is the simplest entry point. You specify a task and a model, and it handles tokenization, model loading, and output formatting.

from transformers import pipeline
text_generator = pipeline(
task='text-generation',
model='openai-community/gpt2'
)
results = text_generator(
'What if AI',
max_new_tokens=10,
num_return_sequences=2
)
for result in results:
print(result['generated_text'])

Think of pipeline as a vending machine. You pick a task and a model, and it handles everything behind the scenes: downloading the weights, converting your text to numbers, running the computation, and converting the output back to words. max_new_tokens=10 limits the length of each continuation, and num_return_sequences=2 asks for two different completions from the same prompt.

Inference providers

For large models that will not fit locally, route requests through InferenceClient. This requires an HF_TOKEN environment variable set to your Hugging Face access token.

import os
from huggingface_hub import InferenceClient
client = InferenceClient(
provider='together',
api_key=os.environ['HF_TOKEN'],
)
completion = client.chat.completions.create(
model='deepseek-ai/DeepSeek-V3',
messages=[
{'role': 'user', 'content': 'What is the capital of France?'}
],
)
print(completion.choices[0].message)

Your prompt travels to a provider (Together AI here) that has the hardware to run the model, and the response comes back over the network. The chat format uses the same role/content structure as the OpenAI API, which makes it straightforward to switch between providers. Your token authenticates the request.

SituationUse
Small or medium model (GPT-2, DistilBERT, BART)Local pipeline
Large model (70B+, DeepSeek, Llama 3 70B)Inference Provider
Offline or privacy-sensitive dataLocal pipeline
Quick prototype with no GPUInference Provider

Working with Datasets

The Hub hosts thousands of datasets, filterable by modality (text, image, audio) and task type. Each dataset has a dataset card with license, size, and source details, plus a browser-based viewer for inspecting rows before committing to a download. Most datasets use Apache Arrow under the hood — a columnar format optimized for fast filtering and slicing.

Loading data

from datasets import load_dataset
dataset = load_dataset('wikimedia/wikipedia', '20231101.en')
dataset = load_dataset('wikimedia/wikipedia', '20231101.en', split='train')

Without a split argument, load_dataset returns a DatasetDict containing all available splits. Specifying split='train' returns just that split as a Dataset object, which is faster and cheaper on bandwidth when you only need one portion.

Filtering and slicing

filtered = dataset.filter(lambda row: 'football' in row['text'])
example = filtered.select(range(1))
print(example[0]['text'])

.filter walks every row and keeps only those where the lambda returns True.select(range(1)) slices by row index — here, just the first match. Both return a Dataset object rather than a plain list, which is why you index into example[0] to read the actual row.

OperationCode
Load specific splitload_dataset('id', split='train')
Filter rowsdata.filter(lambda r: condition)
Slice rowsdata.select(range(10))
Access a rowdata[0]
Access a column valuedata[0]['column_name']
Get column namesdata.column_names
Number of rowslen(data)

Text Classification

Text classification assigns a category to a piece of text. Common uses include sentiment analysis, grammar checking, topic labeling, and natural language inference. The pipeline call is the same for all of them; the model determines what kind of classification is performed.

Standard classification

from transformers import pipeline
grammar_model = pipeline(
task='text-classification',
model='textattack/distilbert-base-uncased-CoLA'
)
print(grammar_model('He eat pizza everyday'))

This model was trained on CoLA (Corpus of Linguistic Acceptability), a dataset of English sentences labeled as grammatically acceptable or not. It returns a label and a confidence score. The model does not understand English the way a person does; it has learned statistical patterns that correlate with grammatical correctness.

Question-Natural-Language-Inference

QNLI tests whether a passage actually answers a given question. Question and passage are concatenated into a single string, and the model returns entailment (the passage answers the question) or not_entailment (it does not).

qnli_model = pipeline(
task='text-classification',
model='cross-encoder/qnli-electra-base'
)
output = qnli_model(
"Where is the capital of France?, Brittany is known for its stunning coastline."
)
print(output)

A passage about coastal scenery does not answer a question about a capital city, so not_entailment is the expected result here.

Zero-shot classification

Zero-shot classification lets you define the categories at inference time without retraining.

text = "AI-powered robots assist in complex brain surgeries with precision."
zero_shot_model = pipeline(
task='zero-shot-classification',
model='facebook/bart-large-mnli'
)
categories = ['politics', 'science', 'sports']
output = zero_shot_model(text, categories)
print(f"Top label: {output['labels'][0]} with score: {output['scores'][0]}")

A standard classifier is trained on fixed categories and cannot generalize beyond them. A zero-shot model was trained on natural-language entailment, which allows it to reason about any label you provide by asking itself “does this text entail it belongs to this category?” It ranks candidates by entailment confidence and returns them sorted from highest to lowest. Labels still need to be semantically distinct from each other or the scores will be noisy.

Summarization

Summarization models fall into two types. Extractive models select key sentences verbatim from the source, making them reliable for legal, financial, or scientific documents where faithfulness matters. Abstractive models rephrase content in new words, producing more natural output for articles and blog posts. The pipeline call is identical for both; the choice of model determines which approach is used.

from transformers import pipeline
summarizer = pipeline(
task='summarization',
model='cnicu/t5-small-booksum'
)
original_text = "..."
summary = summarizer(original_text)
print(f"Original length: {len(original_text)}")
print(f"Summary length: {len(summary[0]['summary_text'])}")

Without constraints, a summarizer might produce a trivially short output or reproduce most of the input. min_new_tokensand max_new_tokens set a floor and ceiling on the generated length. Tokens are sub-word chunks — roughly 0.75 words each on average.

summarizer = pipeline(
task='summarization',
model='facebook/bart-large-cnn',
min_new_tokens=10,
max_new_tokens=150
)

min_new_tokens=10 ensures the model writes at least a sentence. max_new_tokens=150 caps the output at roughly 110 words, keeping summaries readable and preventing the model from reproducing large sections of the source.

Auto Classes

pipeline is a convenience wrapper. When you need more control — raw logits, custom tokenization rules, or the ability to combine a tokenizer from one model with weights from another — you work directly with Auto classes.

Loading model and tokenizer separately

from transformers import AutoModelForSequenceClassification, AutoTokenizer
sentiment_model = AutoModelForSequenceClassification.from_pretrained(
'distilbert-base-uncased-finetuned-sst-2-english'
)
sentiment_tokenizer = AutoTokenizer.from_pretrained(
'distilbert-base-uncased-finetuned-sst-2-english'
)
tokens = sentiment_tokenizer.tokenize('Hugging Face makes AI accessible')
print(tokens)

Auto classes let you hold the model and tokenizer as separate objects rather than a sealed unit. Once separated, you can inspect intermediate steps, modify either component, or wire them together in non-standard ways. Calling .tokenize() shows exactly how the model processes your input text before any numbers are involved.

Assembling a custom pipeline

You can hand a pipeline pre-loaded objects rather than a model name string.

from transformers import AutoModelForSequenceClassification, AutoTokenizer, pipeline
sentiment_model = AutoModelForSequenceClassification.from_pretrained(
'distilbert-base-uncased-finetuned-sst-2-english'
)
sentiment_tokenizer = AutoTokenizer.from_pretrained(
'distilbert-base-uncased-finetuned-sst-2-english'
)
sentiment_pipeline = pipeline(
task='sentiment-analysis',
model=sentiment_model,
tokenizer=sentiment_tokenizer
)
print(sentiment_pipeline('Hugging Face is wonderful'))

This pattern is useful when you need the same model or tokenizer in multiple places without downloading twice, or when you want to modify a component before plugging it in. The calling convention is identical to a name-based pipeline once it is assembled.

SituationUse
Standard task, default behaviorpipeline
Need raw logits or probabilitiesAuto classes
Custom tokenization (truncation, padding)Auto classes
Mix tokenizer from model A with model BAuto classes
Batch inference with manual controlAuto classes
Quick prototypepipeline

Document Question Answering

Document QA takes two inputs — a question and a context — and returns a span of text extracted directly from the context. The answer is always drawn from the source; it is not generated from scratch.

Extracting text from a PDF

from pypdf import PdfReader
reader = PdfReader('hr-policies.pdf')
document_text = ''
for page in reader.pages:
document_text += page.extract_text()

A PDF is not plain text; it is a formatted layout that may include headers, columns, and images. PdfReader walks through each page and extracts the text it finds, which gets concatenated into a single string so the QA model can search the entire document at once. This works well for text-based PDFs. Scanned or image-only PDFs need a separate OCR step.

Running the QA pipeline

doc_qa = pipeline(
task='question-answering',
model='distilbert-base-cased-distilled-squad'
)
question = 'What is the notice period for resignation?'
result = doc_qa(
question=question,
context=document_text
)
print(result['answer'])

The model scans the full context and identifies which span of words most likely answers the question, similar to a very fast ctrl+F with reading comprehension behind it. Both question and context are required keyword arguments. The result includes the answer string, the character positions where it starts and ends in the source, and a confidence score.

Three practical limitations apply. Most QA models cap at 512 tokens, so long documents need to be chunked before being passed in. The model will always return a span even when the answer is not present in the document — check result['score']and set a minimum threshold before trusting the output. PDF text extraction is also imperfect; scanned pages and multi-column layouts frequently produce garbled text that reduces QA accuracy.

Choosing the Right Tool

The task you want to perform determines which component to reach for. Use pipeline(task=..., model=...) for running any standard model end-to-end. For large models that will not fit locally, route requests through InferenceClient to a hosted provider. When you need raw logits, custom preprocessing, or precise control over tokenization, use AutoModel and AutoTokenizer directly. Use load_dataset() from the datasets library for any training or evaluation data.

For the task itself: text-classification handles sentiment, grammar, and topic labeling. zero-shot-classification classifies into any labels you define at inference time without retraining. summarization condenses long text, with the model choice determining whether the result is extractive or abstractive. question-answering combined with a PDF parser lets you query documents directly using natural-language questions.

See you soon.

View Comments (5)

Leave a Reply

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