HardBERT

Next Sentence Prediction

BERT: Pre-training of Deep Bidirectional Transformers

Hard

Problem

Construct deterministic training pairs for BERT's next-sentence-prediction objective. Each pair specification identifies sentence A and sentence B by document and sentence indices. Label a pair as one only when sentence B immediately follows sentence A in the same document. Otherwise label it as zero.

Return a Python list containing one dictionary per specification in the original order. Every dictionary must contain exactly sentence_a, sentence_b, and is_next. The two sentence values are strings and is_next is an integer.

Theory

Next Sentence Prediction (NSP) is the second of BERT's two pre-training objectives. While Masked Language Modeling (MLM) teaches the model to understand token-level context, NSP trains the model to understand relationships between sentences. Given a pair (A, B), the model predicts whether B is the actual next sentence following A in the corpus (IsNext) or a randomly sampled sentence (NotNext). It was introduced in Devlin et al. (2019), "BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding."


What It Is / What It Does

NSP is a binary classification task applied to sentence pairs during BERT's pre-training. The model receives two segments separated by a [SEP] token, with a [CLS] token prepended. The final hidden state of [CLS] is fed through a linear classification head to produce a binary prediction.

The two classes are:

The balanced 50/50 split means the trivial baseline (always predicting one class) achieves 50% accuracy. BERT's pre-trained NSP classifier reaches approximately 97-98% accuracy, confirming the model learns meaningful inter-sentence relationships.

NSP trains jointly with MLM. Every training example simultaneously has masked tokens (for MLM) and a sentence-pair label (for NSP). The total pre-training loss is the sum of the MLM loss and the NSP loss.


Key Equations

Let h_{\text{CLS}} \in \mathbb{R}^h denote the final hidden state of the [CLS] token from the last Transformer layer, where h is the hidden dimension (768 for BERT-Base, 1024 for BERT-Large).

The NSP head applies a linear transformation followed by softmax:

\text{logits} = h_{\text{CLS}} \cdot W + b

where W \in \mathbb{R}^{h \times 2} is the weight matrix and b \in \mathbb{R}^2 is the bias vector. This produces two logits for the IsNext and NotNext classes.

The probability distribution over the two classes via softmax:

P(\text{IsNext}) = \frac{e^{\text{logits}_1}}{e^{\text{logits}_0} + e^{\text{logits}_1}}

P(\text{NotNext}) = \frac{e^{\text{logits}_0}}{e^{\text{logits}_0} + e^{\text{logits}_1}}

The NSP loss is standard cross-entropy:

\mathcal{L}_{\text{NSP}} = -\left[ y \log P(\text{IsNext}) + (1 - y) \log P(\text{NotNext}) \right]

where y = 1 for IsNext pairs and y = 0 for NotNext pairs.

The total BERT pre-training loss combines both objectives:

\mathcal{L}_{\text{total}} = \mathcal{L}_{\text{MLM}} + \mathcal{L}_{\text{NSP}}

Both losses are weighted equally (weight = 1.0) in the original implementation. There is no scaling factor between them.


How NSP Training Data is Generated

BERT constructs training data at the sentence-pair level. Two "sentences" (actually spans of contiguous text, which may contain multiple linguistic sentences) are selected to form each input pair.

The generation procedure:

The resulting input is formatted with special tokens:

[\text{CLS}] \; A_1 \; A_2 \; \ldots \; A_n \; [\text{SEP}] \; B_1 \; B_2 \; \ldots \; B_m \; [\text{SEP}]

Three types of embeddings are summed for each token position:

The combined sequence length (A + B + special tokens) must not exceed 512 tokens.


Why NSP Was Included

Devlin et al. included NSP because many downstream NLP tasks require understanding relationships between two pieces of text. Tasks that benefit:

The paper states: "Many important downstream tasks such as Question Answering and Natural Language Inference are based on understanding the relationship between two sentences, which is not directly captured by language modeling."

NSP forces the [CLS] token to encode a global, cross-sentence representation that captures whether two segments are coherently related. This representation can then be fine-tuned for sentence-pair tasks.

Devlin et al. reported ablation results showing that removing NSP hurt performance on QNLI (86.4% to 84.3%), MNLI (84.4% to 83.7%), and SQuAD (88.5 F1 to 87.3 F1), providing direct evidence that NSP pre-training transferred useful knowledge.


Paper Context

In the original BERT paper, NSP is introduced alongside MLM as one of two complementary pre-training objectives. No prior approach had captured inter-sentence relationships, making NSP a novel contribution.

Key details from the paper:


The NSP Controversy

While NSP was presented as beneficial in the original paper, subsequent research challenged its utility significantly.

RoBERTa (Liu et al., 2019)

The most impactful challenge came from RoBERTa, which systematically evaluated NSP through controlled experiments:

RoBERTa found that removing NSP consistently improved or matched performance. The FULL-SENTENCES configuration without NSP outperformed the original BERT setup on SQuAD, MNLI, SST-2, and RACE.

Why NSP Might Hurt

ALBERT and Sentence Order Prediction (SOP)

Lan et al. (2020) proposed ALBERT, replacing NSP with Sentence Order Prediction (SOP). SOP uses two consecutive sentences but swaps their order for negatives instead of using random sentences. This forces learning discourse coherence rather than topic detection. ALBERT showed SOP consistently outperformed NSP, confirming the problem was NSP's trivial negative sampling rather than the idea of sentence-level pre-training.


Numerical Example

Consider two sentences from a document about climate:

IsNext Pair Construction

The tokenized input (simplified):

[\text{CLS}] \; \text{Global} \; \text{temperatures} \; \text{have} \; \text{risen} \; \ldots \; [\text{SEP}] \; \text{This} \; \text{warming} \; \text{is} \; \ldots \; [\text{SEP}]

NotNext Pair Construction

Replace sentence B with the random sentence B':

[\text{CLS}] \; \text{Global} \; \text{temperatures} \; \text{have} \; \text{risen} \; \ldots \; [\text{SEP}] \; \text{The} \; \text{stock} \; \text{market} \; \ldots \; [\text{SEP}]

Forward Pass Through NSP Head

Assume BERT-Base (h = 768). After the 12 Transformer layers, extract $ \in \mathbb{R}^{768}$ at position 0.

First, the pooler transforms h_{\text{CLS}}:

h_{\text{pooled}} = \tanh(W_p \cdot h_{\text{CLS}} + b_p) \in \mathbb{R}^{768}

Then the NSP head produces logits:

\text{logits} = h_{\text{pooled}} \cdot W + b \in \mathbb{R}^2

where W \in \mathbb{R}^{768 \times 2} and b \in \mathbb{R}^2.

Suppose for the IsNext pair, the logits are:

\text{logits} = [-1.2, \; 2.8]

Applying softmax:

P(\text{NotNext}) = \frac{e^{-1.2}}{e^{-1.2} + e^{2.8}} = \frac{0.301}{0.301 + 16.445} = \frac{0.301}{16.746} = 0.018

P(\text{IsNext}) = \frac{e^{2.8}}{e^{-1.2} + e^{2.8}} = \frac{16.445}{16.746} = 0.982

The model predicts IsNext with 98.2% confidence. The cross-entropy loss with y = 1:

\mathcal{L}_{\text{NSP}} = -\log P(\text{IsNext}) = -\log(0.982) = 0.018

Now suppose for the NotNext pair, the logits are:

\text{logits} = [3.1, \; -0.9]

P(\text{NotNext}) = \frac{e^{3.1}}{e^{3.1} + e^{-0.9}} = \frac{22.198}{22.198 + 0.407} = 0.982

\mathcal{L}_{\text{NSP}} = -\log P(\text{NotNext}) = -\log(0.982) = 0.018

Both examples show low loss, confirming the model correctly distinguishes IsNext from NotNext pairs.


Pitfalls


Examples

Example 1

Input
documents = [["The cat sat.","It was sunny."],["Dogs bark.","They also play."]], pair_specs = [{"doc_a":0,"sent_a":0,"doc_b":0,"sent_b":1}]
Output
[{"sentence_a":"The cat sat.","sentence_b":"It was sunny.","is_next":1}]
Explanation
Sentence B immediately follows sentence A in the same document, so is_next is one.

Example 2

Input
documents = [["The cat sat.","It was sunny."],["Dogs bark.","They also play."]], pair_specs = [{"doc_a":0,"sent_a":0,"doc_b":1,"sent_b":0}]
Output
[{"sentence_a":"The cat sat.","sentence_b":"Dogs bark.","is_next":0}]

Example 3

Input
documents = [["A0","A1","A2"],["B0","B1"]], pair_specs = [{"doc_a":0,"sent_a":1,"doc_b":0,"sent_b":2},{"doc_a":1,"sent_a":0,"doc_b":0,"sent_b":2},{"doc_a":0,"sent_a":0,"doc_b":0,"sent_b":2}]
Output
[{"sentence_a":"A1","sentence_b":"A2","is_next":1},{"sentence_a":"B0","sentence_b":"A2","is_next":0},{"sentence_a":"A0","sentence_b":"A2","is_next":0}]

Hints

  1. Read each sentence with documents[doc_index][sentence_index].
  2. Check document equality and whether sent_b equals sent_a + 1.

Requirements

Constraints

Starter Code

def create_nsp_pairs(documents: list, pair_specs: list) -> list:
    """
    Returns sentence_a, sentence_b, and is_next dictionaries in a list.
    """
    pass

Test Cases

CaseMatches
Consecutive pairpublic
Different documentspublic
Mixed pairspublic