Summarization › Code Implementation

Michael BrenndoerferJanuary 19, 202610 min read
Language AI HandbookAll chapters
Part 63 of 67LLM Applications
Chapter 479Summarization › Code Implementation
Reading section 5 of 10 · Code Implementation · 10 minSection 5 of 10 · 10 min

Book contents

Section 5 of 10 · 10 min read

Summarization › Code ImplementationOne section · Sections in this chapter5 / 10 ↓
Reading tools Definitions & inline links

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Article links

Make inline references clickable

Let us implement both approaches. We will start with a simple TextRank extractor from scratch, then use a pretrained BART model for abstractive summarization.

Setup

TextRank Extractor

We begin by implementing sentence similarity and the TextRank scoring algorithm.

In[4]:
Code
import re

import numpy as np


def preprocess(text):
    """Tokenize and clean a sentence."""
    words = re.findall(r"\b[a-z]+\b", text.lower())
    stopwords = {
        "the",
        "a",
        "an",
        "is",
        "are",
        "was",
        "were",
        "be",
        "been",
        "have",
        "has",
        "had",
        "do",
        "does",
        "did",
        "will",
        "would",
        "can",
        "could",
        "may",
        "might",
        "shall",
        "should",
        "must",
        "in",
        "on",
        "at",
        "to",
        "for",
        "of",
        "and",
        "or",
        "but",
        "that",
        "this",
        "it",
        "its",
        "with",
        "by",
        "from",
        "not",
    }
    return [w for w in words if w not in stopwords]


def sentence_similarity(s1_words, s2_words):
    """Compute overlap-based sentence similarity."""
    set1, set2 = set(s1_words), set(s2_words)
    intersection = set1 & set2
    if len(set1) == 0 or len(set2) == 0:
        return 0.0
    denominator = np.log(len(set1) + 1) + np.log(len(set2) + 1)
    return len(intersection) / denominator if denominator > 0 else 0.0


def textrank(sentences, top_k=3, damping=0.85, max_iter=50):
    """Run TextRank and return top-k sentences by score."""
    n = len(sentences)
    tokenized = [preprocess(s) for s in sentences]

    # Build similarity matrix
    sim_matrix = np.zeros((n, n))
    for i in range(n):
        for j in range(n):
            if i != j:
                sim_matrix[i][j] = sentence_similarity(
                    tokenized[i], tokenized[j]
                )

    # Row-normalize
    row_sums = sim_matrix.sum(axis=1, keepdims=True)
    row_sums[row_sums == 0] = 1  # Avoid division by zero
    sim_matrix = sim_matrix / row_sums

    # Power iteration
    scores = np.ones(n) / n
    for _ in range(max_iter):
        new_scores = (1 - damping) / n + damping * sim_matrix.T.dot(scores)
        if np.max(np.abs(new_scores - scores)) < 1e-6:
            break
        scores = new_scores

    # Select top-k sentences by original order
    ranked_idx = np.argsort(scores)[::-1][:top_k]
    selected_idx = sorted(ranked_idx)
    return [sentences[i] for i in selected_idx], scores

Now we apply TextRank to a sample document about climate change:

In[5]:
Code
document = """
Global average temperatures have risen by approximately 1.1 degrees Celsius since the pre-industrial era.
The primary driver is the increase in greenhouse gases, particularly carbon dioxide from burning fossil fuels.
Sea levels are rising at an accelerating rate, threatening coastal communities around the world.
The past decade has been the warmest on record, with 2023 ranking as the hottest year ever measured.
Extreme weather events including heatwaves, floods, and wildfires have become more frequent and severe.
International agreements like the Paris Accord aim to limit warming to 1.5 degrees Celsius above pre-industrial levels.
Scientists warn that exceeding 2 degrees of warming could trigger irreversible tipping points in Earth's climate system.
Renewable energy adoption has accelerated dramatically, with solar and wind power now cheaper than fossil fuels in most markets.
Carbon capture technologies are being developed, but currently operate at a fraction of the scale needed.
Adaptation measures, including improved infrastructure and early warning systems, are being deployed in vulnerable regions.
""".strip().split("\n")

document = [s.strip() for s in document if s.strip()]

summary_sentences, scores = textrank(document, top_k=3)
Out[6]:
Console
TextRank Summary:
------------------------------------------------------------
  Global average temperatures have risen by approximately 1.1 degrees Celsius since the pre-industrial era.
  The primary driver is the increase in greenhouse gases, particularly carbon dioxide from burning fossil fuels.
  International agreements like the Paris Accord aim to limit warming to 1.5 degrees Celsius above pre-industrial levels.

All Sentence Scores (sorted):
  [0.1689] International agreements like the Paris Accord aim to limit warming to...
  [0.1353] The primary driver is the increase in greenhouse gases, particularly c...
  [0.1195] Global average temperatures have risen by approximately 1.1 degrees Ce...
  [0.1107] Adaptation measures, including improved infrastructure and early warni...
  [0.1027] Carbon capture technologies are being developed, but currently operate...
  [0.0898] Renewable energy adoption has accelerated dramatically, with solar and...
  [0.0756] Scientists warn that exceeding 2 degrees of warming could trigger irre...
  [0.0616] Extreme weather events including heatwaves, floods, and wildfires have...
  [0.0360] Sea levels are rising at an accelerating rate, threatening coastal com...
  [0.0150] The past decade has been the warmest on record, with 2023 ranking as t...

The TextRank algorithm selected the three sentences that are most central to the document's topic network: the core temperature finding, the sea level rise threat (highly connected to the most common themes), and the tipping point warning. Sentences about specific policy agreements or adaptation measures score lower because they are less similar to the main body of content. Notice that the ranking reflects thematic centrality rather than position: sentences from the middle of the document can outrank earlier sentences if they are more representative of the document's overall vocabulary.

The bar chart below shows the full TextRank score distribution across all ten sentences. The gap between high-scoring and low-scoring sentences tells us how distinctly the graph-based algorithm separates central from peripheral content.

Out[7]:
Visualization
Horizontal bar chart of TextRank scores for 10 sentences with top 3 highlighted in orange.
TextRank sentence importance scores for a ten-sentence climate change document. The three highest-scoring sentences (shown in orange) are selected for the extractive summary. Scores reflect thematic centrality: sentences sharing vocabulary with many other sentences score higher regardless of their position. A dashed vertical line marks the score cutoff for the top-3 selection.

Implementing MMR for Redundancy Reduction

To see how MMR improves on naive sentence ranking, we implement a comparison. The key addition is the redundancy term that penalizes candidates similar to already-selected sentences.

In[8]:
Code
def word_overlap_similarity(s1, s2):
    """Simple cosine-style similarity for MMR."""
    w1 = set(preprocess(s1))
    w2 = set(preprocess(s2))
    if not w1 or not w2:
        return 0.0
    return len(w1 & w2) / (len(w1) * len(w2)) ** 0.5


def mmr_summary(sentences, scores, top_k=3, lambda_param=0.6):
    """
    Select sentences using Maximum Marginal Relevance.
    lambda_param controls relevance vs. diversity tradeoff.
    """
    selected = []
    remaining = list(range(len(sentences)))

    for _ in range(top_k):
        best_idx = None
        best_score = -np.inf

        for i in remaining:
            relevance = scores[i]
            if selected:
                redundancy = max(
                    word_overlap_similarity(sentences[i], sentences[j])
                    for j in selected
                )
            else:
                redundancy = 0.0

            mmr_score = (
                lambda_param * relevance - (1 - lambda_param) * redundancy
            )

            if mmr_score > best_score:
                best_score = mmr_score
                best_idx = i

        selected.append(best_idx)
        remaining.remove(best_idx)

    selected_sorted = sorted(selected)
    return [sentences[i] for i in selected_sorted]
Out[9]:
Console
MMR Summary (lambda=0.6, balanced relevance-diversity):
------------------------------------------------------------
  The primary driver is the increase in greenhouse gases, particularly carbon dioxide from burning fossil fuels.
  International agreements like the Paris Accord aim to limit warming to 1.5 degrees Celsius above pre-industrial levels.
  Adaptation measures, including improved infrastructure and early warning systems, are being deployed in vulnerable regions.

Most Redundant Sentence Pair (before MMR):
  Similarity: 0.322
  Sent A: Global average temperatures have risen by approximately 1.1 degrees Ce...
  Sent B: International agreements like the Paris Accord aim to limit warming to...

MMR tends to pick a more diverse set of sentences compared to pure TextRank ranking, especially when the top-ranked sentences are thematically similar to each other. The lambda parameter gives you direct control over this tradeoff: setting lambda closer to 1.0 produces a summary very similar to the pure relevance ranking, while values closer to 0.5 push the algorithm to explore less central but more novel sentences. In practice, values between 0.5 and 0.7 tend to produce the best balance between coverage and relevance.

The following visualization shows how different lambda values affect which sentences MMR selects, revealing the tradeoff between thematic centrality and content diversity.

Out[10]:
Visualization
Grid heatmap showing MMR sentence selection across 5 lambda values and 10 sentences.
Effect of the lambda parameter on MMR sentence selection for the climate change document. Each row shows which 3 sentences (marked with X) are selected at a given lambda value, ranging from high diversity (lambda=0.2) to pure relevance (lambda=1.0). At high lambda, MMR converges to the same selection as pure TextRank. As lambda decreases, the selection shifts toward peripheral sentences, trading relevance for broader topic coverage.

Abstractive Summarization with BART

Now we use a pretrained BART model to generate abstractive summaries. BART-large-cnn is specifically fine-tuned on the CNN/DailyMail summarization dataset, which contains over 300,000 news article-summary pairs. This fine-tuning teaches the model the specific output style expected for news summarization: neutral tone, factual language, and the inverted-pyramid structure where the most important facts come first.

In[11]:
Code
# Uncomment to install if needed:
# !uv pip install transformers torch

from transformers import BartForConditionalGeneration, BartTokenizer

# Load BART fine-tuned on CNN/DailyMail
bart_model_name = "facebook/bart-large-cnn"
bart_tokenizer = BartTokenizer.from_pretrained(bart_model_name)
bart_model = BartForConditionalGeneration.from_pretrained(bart_model_name)
In[12]:
Code
import torch

# Summarize the climate change document
article = " ".join(document)
inputs = bart_tokenizer(
    article, return_tensors="pt", max_length=1024, truncation=True
)
with torch.no_grad():
    summary_ids = bart_model.generate(
        inputs["input_ids"],
        max_length=80,
        min_length=30,
        num_beams=4,
        early_stopping=True,
    )
bart_summary = bart_tokenizer.decode(summary_ids[0], skip_special_tokens=True)
Out[13]:
Console
BART Abstractive Summary:
------------------------------------------------------------
Global average temperatures have risen by approximately 1.1 degrees Celsius since the pre-industrial era. Sea levels are rising at an accelerating rate, threatening coastal communities around the world. Extreme weather events including heatwaves, floods, and wildfires have become more frequent and severe.

Original length: 160 words
Summary length: 42 words
Compression ratio: 0.26

The BART summary illustrates the key advantages of abstractive over extractive approaches: it paraphrases rather than copies, combines facts naturally, and produces a fluent narrative sentence rather than a collection of extracted fragments. The compression ratio gives us a sense of how aggressively BART condenses the content: a ratio of 0.25 means the summary is one quarter the length of the original. Notice that BART makes editorial decisions about what to include and how to frame it, decisions that no extractive system can make because it cannot rearrange or rephrase.

Key Parameters

The key parameters for summarization are:

  • top_k: The number of sentences to include in an extractive summary. Larger values improve coverage but reduce conciseness. The right value depends on the acceptable summary length for your application.
  • damping (TextRank): The fraction of score contributed by the graph structure versus the uniform base. Values around 0.85 follow the original PageRank parameter. Lower values make all sentences more equal; higher values amplify the scores of the most central nodes.
  • lambda_param (MMR): Balances relevance against diversity. Values above 0.5 favor relevance; values below 0.5 favor diversity. The right value depends on how much redundancy you expect in the source document.
  • max_length / min_length (BART): Control the length of the generated abstractive summary. The model will not exceed max_length tokens or go below min_length, regardless of content length. Setting these bounds correctly is important: too tight and the model truncates useful content; too loose and the model may pad with low-quality additions.
  • num_beams: The number of beams in beam search decoding. Higher values explore more of the generation space and often improve quality but at proportional computational cost. For summarization, 4 to 8 beams is typical.
  • do_sample: When False, uses greedy or beam search decoding; when True, enables stochastic sampling. For summarization, deterministic decoding is usually preferred because the goal is accurate representation of the source, not creative variation.