BERT, explained · Part 4 of 6 · Covers §3.2, §4, A.3, A.5, B.1

Fine-tuning and Results: GLUE, SQuAD and SWAG

Sections 3.2 and 4 of the BERT paper, line by line: how one pre-trained model becomes a classifier, a question answerer and a multiple-choice solver by adding a tiny output layer, and what the results tables really say. With a real fine-tune of BERT-base on SST-2 and MRPC, and our own span search scored on the full SQuAD dev sets.

BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding. Jacob Devlin, Ming-Wei Chang, Kenton Lee, Kristina Toutanova. NAACL 2019, 2018. arXiv:1810.04805

Part 3 built a pre-trained BERT: a model that has read 3.3 billion words and learned to fill in blanks and to tell whether two sentences belong together. Nobody has asked it to do anything useful yet.

This part is about the second step of the recipe: fine-tuning. We read Section 3.2 (how fine-tuning works), then Section 4 (the experiments on eleven tasks), and the appendix sections that go with them (A.3, A.5 and B.1). Along the way we fine-tune BERT ourselves on a laptop GPU and check every formula in code.

One model, many tasks

The key sentence is the last one. Put a question and a passage into one sequence, [CLS] question [SEP] passage [SEP], and let every token attend to every other token. A question word can now look at passage words, and a passage word can look at question words, in all 12 layers. That is cross attention, in both directions, for free. BERT needs no special pair-matching layer.

Plugging a task into BERT

Pre-training saw pairs: [CLS] sentence A [SEP] sentence B [SEP]. Fine-tuning fills the same two slots.paraphrase (MRPC, QQP)[CLS]sentence 1[SEP]sentence 2[SEP]same meaning?entailment (MNLI, RTE)[CLS]premise[SEP]hypothesis[SEP]entail / contradict / neutralquestion answering (SQuAD)[CLS]question[SEP]passage[SEP]answer spanone text (SST-2, CoLA, NER)[CLS]the text[SEP]emptylabel or tagssegment Asegment B
The input side of every task. Pre-training taught BERT the format [CLS] A [SEP] B [SEP]. A paraphrase task puts its two sentences there; entailment puts the premise and the hypothesis; question answering puts the question and the passage; a single-text task leaves B empty.

The output side has only two cases:

  • Sentence-level tasks (one answer per input, like "positive" or "entailment") read the final vector of [CLS], called CC, and add one small classification layer.
  • Token-level tasks (one answer per token, like named-entity tags or the start and end of an answer) read the final vector of every token, T1,T2,…T_1, T_2, \dots, and add one small layer that is applied to each token.

Why packing two texts into one sequence is enough

Section 3.2 also makes a claim that is easy to read past: "encoding a concatenated text pair with self-attention effectively includes bidirectional cross attention between two sentences". Before BERT, models for text pairs encoded each text on its own, then added a special layer to compare them. The paper names two such systems:

Two ways to let a question and a passage look at each otherBERT: one square self-attention over the packed pairkey (column)[CLS]whosat?[SEP]acatsat.[SEP][CLS][CLS] attends to [CLS][CLS] attends to who[CLS] attends to sat[CLS] attends to ?[CLS] attends to [SEP][CLS] attends to a[CLS] attends to cat[CLS] attends to sat[CLS] attends to .[CLS] attends to [SEP]whowho attends to [CLS]who attends to whowho attends to satwho attends to ?who attends to [SEP]who attends to awho attends to catwho attends to satwho attends to .who attends to [SEP]satsat attends to [CLS]sat attends to whosat attends to satsat attends to ?sat attends to [SEP]sat attends to asat attends to catsat attends to satsat attends to .sat attends to [SEP]?? attends to [CLS]? attends to who? attends to sat? attends to ?? attends to [SEP]? attends to a? attends to cat? attends to sat? attends to .? attends to [SEP][SEP][SEP] attends to [CLS][SEP] attends to who[SEP] attends to sat[SEP] attends to ?[SEP] attends to [SEP][SEP] attends to a[SEP] attends to cat[SEP] attends to sat[SEP] attends to .[SEP] attends to [SEP]aa attends to [CLS]a attends to whoa attends to sata attends to ?a attends to [SEP]a attends to aa attends to cata attends to sata attends to .a attends to [SEP]catcat attends to [CLS]cat attends to whocat attends to satcat attends to ?cat attends to [SEP]cat attends to acat attends to catcat attends to satcat attends to .cat attends to [SEP]satsat attends to [CLS]sat attends to whosat attends to satsat attends to ?sat attends to [SEP]sat attends to asat attends to catsat attends to satsat attends to .sat attends to [SEP].. attends to [CLS]. attends to who. attends to sat. attends to ?. attends to [SEP]. attends to a. attends to cat. attends to sat. attends to .. attends to [SEP][SEP][SEP] attends to [CLS][SEP] attends to who[SEP] attends to sat[SEP] attends to ?[SEP] attends to [SEP][SEP] attends to a[SEP] attends to cat[SEP] attends to sat[SEP] attends to .[SEP] attends to [SEP]query (row)Older models: a separate cross-attentionkey (column)acatsat.whowho attends to awho attends to catwho attends to satwho attends to .satsat attends to asat attends to catsat attends to satsat attends to .?? attends to a? attends to cat? attends to sat? attends to .query (row)rows: question words; columns: passage words.A separate block, built on top oftwo independently encoded texts.within one textacross the two texts (question ↔ passage): cross attention, for free
Left: BERT's packed input "[CLS] who sat ? [SEP] a cat sat . [SEP]" has one square attention table. The blue blocks are attention within each text; the orange blocks are attention between them, in both directions. Right: an older pair model's separate cross-attention, which is just one of BERT's orange blocks.

It is not just a picture. In a public BERT-base fine-tuned for SQuAD, given "Where was Ada born?" and "Ada Lovelace was born in London in 1815.", question words really do look at the passage, in every layer (bert_part4_math.py):

plain text
layer  1: question -> passage 0.190   passage -> question 0.107   question -> [CLS]/[SEP] 0.306
layer  6: question -> passage 0.170   passage -> question 0.072   question -> [CLS]/[SEP] 0.503
layer 10: question -> passage 0.191   passage -> question 0.196   question -> [CLS]/[SEP] 0.414
layer 12: question -> passage 0.087   passage -> question 0.045   question -> [CLS]/[SEP] 0.740
averaged over all 12 layers and heads, "born" (question) gives 0.162 of its attention to the passage

(Each number is the average share of attention, over all heads, that flows from one block to the other. Much of the rest goes to [CLS] and [SEP], which many heads use as a resting place.)

Figure 4: the four shapes of a task

The paper draws these cases in Appendix A.5.

How to read each panel:

  • (a) Sentence pair classification. Two sentences go in; the red arrow comes out of CC only. Tasks: MNLI, QQP, QNLI, STS-B, MRPC, RTE, and SWAG.
  • (b) Single sentence classification. One sentence; the arrow comes out of CC. Tasks: SST-2, CoLA.
  • (c) Question answering. Question and paragraph; the arrows come out of the paragraph tokens, marking where the answer starts and ends. Task: SQuAD v1.1.
  • (d) Single sentence tagging. One sentence; an arrow comes out of every token with a tag such as B-PER (beginning of a person's name) or O (outside any name). Task: CoNLL-2003 NER, which Part 5 covers.

Fine-tuning is cheap

We will test the "few hours on a GPU" claim ourselves later in this part, on a laptop.

The fine-tuning settings

Which settings did the authors use for fine-tuning? Appendix A.3 answers.

The learning rates are tiny compared with pre-training (1e-4, Part 3). That is on purpose: the pre-trained weights are already good, and fine-tuning should nudge them, not overwrite them.

The size of the grid is 2 batch sizes × 3 learning rates × 3 epoch counts = 18 runs per task. With runs that take minutes, that is affordable.

Section 4: the experiments

The nine GLUE tasks

First, the benchmark's own summary table:

Before the results, let us meet the tasks. Appendix B.1 describes each one.

Here are all nine in plain words, with one made-up example each:

TaskInputQuestion the model answersExample
MNLIpremise + hypothesisentailment, contradiction or neutral?"A man plays guitar." / "A person makes music." → entailment
QQPtwo Quora questionsdo they ask the same thing?"How do I learn Python?" / "What is the best way to learn Python?" → same
QNLIquestion + one sentencedoes the sentence contain the answer?"Where was Ada born?" / "Ada was born in London." → yes
SST-2one movie-review sentencepositive or negative?"A gorgeous, witty film." → positive
CoLAone sentenceis it acceptable English?"The more you read, the more you learn." → yes; "Read more you the." → no
STS-Btwo sentenceshow similar are they, from 1 to 5?"A dog runs." / "A puppy is running." → about 4.5
MRPCtwo news sentencesdo they mean the same?→ yes or no
RTEpremise + hypothesisdoes the first imply the second?like MNLI, two classes, small data
WNLItwo sentences with a pronoun(left out by the paper)

The classification layer

The paper writes the loss in one breath. Let us write it out, for one example whose correct label is yy:

logits=C W⊤∈RK,P(label k)=elogitsk∑m=1Kelogitsm,loss=−log⁡P(label y)\text{logits} = C\,W^\top \in \mathbb{R}^{K}, \qquad P(\text{label } k) = \frac{e^{\text{logits}_k}}{\sum_{m=1}^{K} e^{\text{logits}_m}}, \qquad \text{loss} = -\log P(\text{label } y)

where:

  • CC is the final vector of the [CLS] token, a row of H=768H = 768 numbers (for BERT-base);
  • WW is the new weight matrix with KK rows (one per label) and HH columns, and W⊤W^\top is it flipped, so C W⊤C\,W^\top gives one number per label;
  • logitsk\text{logits}_k is the score of label kk, the dot product of CC with row kk of WW;
  • the fraction is the softmax, which turns the KK scores into probabilities that add up to 1;
  • yy is the correct label, and the loss is minus the log of its probability.

The paper's "log⁡(softmax(CWT))\log(\text{softmax}(CW^T))" is the log-probability of the right label; training maximises it, which is the same as minimising the loss above. (Real implementations also add a bias of KK numbers to the logits; the paper leaves it out of the formula.)

A toy example you can check by hand, with H=4H = 4 and K=3K = 3:

C=(0.5, −1, 2, 0.1),W=(100.5001−0.52−0.50.501)C = (0.5,\ -1,\ 2,\ 0.1), \qquad W = \begin{pmatrix} 1 & 0 & 0.5 & 0 \\ 0 & 1 & -0.5 & 2 \\ -0.5 & 0.5 & 0 & 1 \end{pmatrix}

Each logit is the dot product of CC with one row of WW:

logit0=(0.5)(1)+(−1)(0)+(2)(0.5)+(0.1)(0)=1.50,logit1=−1.80,logit2=−0.65\text{logit}_0 = (0.5)(1) + (-1)(0) + (2)(0.5) + (0.1)(0) = 1.50, \quad \text{logit}_1 = -1.80, \quad \text{logit}_2 = -0.65

Softmax: e1.50=4.482e^{1.50} = 4.482, e−1.80=0.165e^{-1.80} = 0.165, e−0.65=0.522e^{-0.65} = 0.522, which add up to 5.1695.169. So P=(0.867, 0.032, 0.101)P = (0.867,\ 0.032,\ 0.101), and if the right label is 0 the loss is −log⁡0.867=0.143-\log 0.867 = 0.143.

The classification head, shape by shape (toy numbers: H = 4, K = 3)C1 × H×WᵀH × K=logits1 × Ksoftmaxclass 0: 0.867class 1: 0.032class 2: 0.101logits = C Wᵀ = [1.50, -1.80, -0.65] → softmax = [0.867, 0.032, 0.101]if the right class is 0: loss = −log 0.867 = 0.143. In BERT-base: C has H = 768 numbers, W is K × 768, so K × 768 + K new weights.
The classification head, shape by shape, with the toy numbers: C (1 × H) times Wᵀ (H × K) gives K logits; softmax gives K probabilities. In BERT-base, H = 768.

The same arithmetic on a real fine-tuned model, a public BERT-base fine-tuned on SST-2 (textattack/bert-base-uncased-SST-2), where label 1 means positive:

plain text
"a gorgeous, witty, seductive movie."  gold label 1
  C W^T = [-4.316, 4.135]  (library: [-4.316, 4.135])
  softmax = [0.0002, 0.9998];  loss = -log P(gold) = 0.0002
"the plot is dull and the acting is worse."  gold label 0
  C W^T = [3.883, -3.500]  (library: [3.883, -3.499])
  softmax = [0.9994, 0.0006];  loss = -log P(gold) = 0.0006

After fine-tuning, the gap between the two logits is large (8.45 and 7.38), so the model is very sure, and right, on both.

Sentence-level tasks read one vector: the final vector of [CLS][CLS]amanplays[SEP]aperson...[SEP]BERT (12 layers, all fine-tuned)every token looks at every other tokenC768 numbers(pooler: dense + tanh)W: K x 768the only new weightssoftmaxentailmentneutralcontradictionexample probabilities
The sentence-level head. All tokens go through BERT; only the final [CLS] vector C is read. One new matrix W turns it into K scores, and softmax turns the scores into probabilities. The probabilities drawn are an illustration.

Checking the formula in code

How many new weights is that? For a 3-label task, WW has 3×768=2,3043 \times 768 = 2{,}304 numbers, plus 3 for the bias: 2,307. I built the classifier by hand and compared it with the Hugging Face BertForSequenceClassification on the same weights:

python
from transformers import AutoTokenizer, BertForSequenceClassification
import torch

tok = AutoTokenizer.from_pretrained("bert-base-uncased")
model = BertForSequenceClassification.from_pretrained("bert-base-uncased", num_labels=3).eval()
enc = tok("A man is playing a guitar.", "A person is making music.", return_tensors="pt")
label = torch.tensor([0])

out = model(**enc, labels=label, output_hidden_states=True)
C = out.hidden_states[-1][:, 0]                       # final vector of [CLS], shape (1, 768)
pooled = torch.tanh(model.bert.pooler.dense(C))       # the "pooler" (see below)
W, b = model.classifier.weight, model.classifier.bias # W: (3, 768)
logits = pooled @ W.T + b                             # C W^T (+ bias)
loss = -torch.log_softmax(logits, -1)[0, label]       # -log softmax(C W^T)[y]

The real output (from bert_part4_heads.py):

plain text
input tokens: ['[CLS]', 'a', 'man', 'is', 'playing', 'a', 'guitar', '.', '[SEP]', 'a', 'person', 'is', 'making', 'music', '.', '[SEP]']
C (final [CLS] vector): shape (1, 768);  W: shape (3, 768)  (K = 3 labels, H = 768)
logits from the library:  [-0.1763, -0.9611, 0.0659]
logits by hand (pooled C): [-0.1763, -0.9611, 0.0659]
loss from the library 1.004374  vs  -log softmax(C W^T)[label] by hand 1.004374
probabilities (untrained W, so they mean nothing yet): [0.366, 0.167, 0.467]
with the raw C instead of the pooled C the logits would be [-0.0016, -0.1106, -0.1406] (the library uses the pooled C)
new parameters for this task: 2,307 out of 109,484,547 (0.0021%)

The hand-written loss and the library's loss agree to all six printed decimals. The probabilities are meaningless here, because WW is still random: fine-tuning has not started.

New weights added for each task, next to the 109.5 million that are fine-tunedGLUE, K = 2 (SST-2)1,538GLUE, K = 3 (MNLI)2,307SQuAD (S and E)1,538SWAG (one vector V)769BERT-base itself (all trained)109,482,240The new head is about 0.001% to 0.002% of the model. Fine-tuning still updates every one of the 109.5 million weights.
How many new weights each task adds: 1,538 for a two-label GLUE task, 2,307 for MNLI's three labels, 1,538 for SQuAD's S and E, and 769 for SWAG's single vector. All 109.5 million weights of BERT-base are updated during fine-tuning as well.

Table 1: the GLUE results

How to read it, column by column:

  • The small numbers under the names are training-set sizes: MNLI has 392k examples, RTE only 2.5k. Keep these in mind: they explain where the biggest gains are.
  • MNLI-(m/mm) has two numbers: accuracy on the "matched" test set (the same genres of text as the training data) and the "mismatched" set (different genres). BERT-large: 86.7/85.9.
  • QQP, MRPC: F1 scores. STS-B: Spearman correlation. The rest: accuracy.
  • Average: the plain average of the columns, which the caption says is "slightly different than the official GLUE score" because WNLI is left out.

And the rows:

  • Pre-OpenAI SOTA: the best published result on each task before OpenAI GPT, each from a different specialised system.
  • BiLSTM+ELMo+Attn: the GLUE paper's own baseline, a recurrent network using ELMo features.
  • OpenAI GPT: the left-to-right Transformer from Part 1, fine-tuned the same way.
  • BERT-base and BERT-large: one model each, one task at a time ("single-model, single task").

The two averages check out against the table: 79.6−75.1=4.579.6 - 75.1 = 4.5 and 82.1−75.1=7.082.1 - 75.1 = 7.0, where 75.1 is OpenAI GPT's average (the best earlier row). Note these are points, though the paper writes "%".

Two more sentences in this paragraph matter. First, "BERT-base and OpenAI GPT are nearly identical in terms of model architecture apart from the attention masking": the 4.5-point gap comes mostly from reading in both directions and from the pre-training tasks (Part 5 tests this). Second, footnote 9 says the GLUE test labels are hidden, and the authors "only made a single GLUE evaluation server submission for each of BERT-base and BERT-large". They did not tune on the test set.

Our own fine-tune: SST-2 and MRPC on a laptop GPU

Reading a results table is one thing. Let us run the recipe. I fine-tuned the released bert-base-uncased on two GLUE tasks with the paper's settings, on the GPU of an Apple M5 Pro laptop:

  • SST-2 (sentiment, 67,349 training sentences, 872 dev sentences);
  • MRPC (paraphrase, 3,668 training pairs, 408 dev pairs).

The settings: batch size 32, 3 epochs, learning rate 2e-5 for SST-2 (one value from the paper's grid; I did not search), Adam with weight decay 0.01, the learning rate rising linearly over the first 10% of steps and then falling linearly to zero (as in Google's released run_classifier.py), dropout 0.1, one fixed seed. The classifier reads the pooled [CLS] vector, like the released code.

python
model = BertForSequenceClassification.from_pretrained("bert-base-uncased", num_labels=2).to("mps")
opt = torch.optim.AdamW(groups, lr=2e-5, weight_decay=0.01)   # no decay on biases and LayerNorm
steps = 3 * math.ceil(len(train) / 32)                         # 3 epochs, batch 32
warm = int(0.1 * steps)                                        # 10% warmup, then linear decay
sched = LambdaLR(opt, lambda s: s / warm if s < warm else (steps - s) / (steps - warm))

for ids, tt, am, y in batches(train):                          # every weight is trained
    loss = model(input_ids=ids, token_type_ids=tt, attention_mask=am, labels=y).loss
    loss.backward(); clip_grad_norm_(model.parameters(), 1.0)
    opt.step(); sched.step(); opt.zero_grad()

Here is the SST-2 run, the first and last lines of the real log (bert_part4_finetune.py):

plain text
task sst2: 67349 training examples, 872 dev examples, device mps
longest training input: 66 tokens; inputs cut at 128: 0
batch 32, 3 epochs = 6315 steps, learning rate 2e-05, warmup 631 steps, weight decay 0.01
before fine-tuning (random classifier layer): dev accuracy 0.4908
step   210/6315  epoch 1  train loss 0.5933  dev accuracy 0.8544  dev F1 0.8486  (82 s)
...
step  6300/6315  epoch 3  train loss 0.0790  dev accuracy 0.9255  dev F1 0.9279  (2888 s)
step  6315/6315  epoch 3  train loss 0.0487  dev accuracy 0.9255  dev F1 0.9279  (2894 s)
FINAL sst2 dev accuracy 0.9255 (807/872), dev F1 0.9279, training time 48.3 minutes
0.800.850.900.950.00.20.40.6epoch 1epoch 2epoch 3step 210: dev accuracy 0.8544, train loss 0.5933step 420: dev accuracy 0.8876, train loss 0.3189step 630: dev accuracy 0.8819, train loss 0.2718step 840: dev accuracy 0.9174, train loss 0.2369step 1050: dev accuracy 0.9220, train loss 0.2145step 1260: dev accuracy 0.9140, train loss 0.2088step 1470: dev accuracy 0.9220, train loss 0.1871step 1680: dev accuracy 0.9174, train loss 0.1956step 1890: dev accuracy 0.9232, train loss 0.1755step 2100: dev accuracy 0.9174, train loss 0.1761step 2310: dev accuracy 0.9289, train loss 0.1251step 2520: dev accuracy 0.9346, train loss 0.1159step 2730: dev accuracy 0.9197, train loss 0.1110step 2940: dev accuracy 0.9266, train loss 0.1369step 3150: dev accuracy 0.9174, train loss 0.1226step 3360: dev accuracy 0.9255, train loss 0.1224step 3570: dev accuracy 0.9243, train loss 0.1145step 3780: dev accuracy 0.9220, train loss 0.1147step 3990: dev accuracy 0.9243, train loss 0.1150step 4200: dev accuracy 0.9197, train loss 0.1109step 4410: dev accuracy 0.9243, train loss 0.0752step 4620: dev accuracy 0.9300, train loss 0.0785step 4830: dev accuracy 0.9278, train loss 0.0738step 5040: dev accuracy 0.9220, train loss 0.0771step 5250: dev accuracy 0.9220, train loss 0.0826step 5460: dev accuracy 0.9266, train loss 0.0747step 5670: dev accuracy 0.9300, train loss 0.0614step 5880: dev accuracy 0.9289, train loss 0.0839step 6090: dev accuracy 0.9266, train loss 0.0703step 6300: dev accuracy 0.9255, train loss 0.0790step 6315: dev accuracy 0.9255, train loss 0.0487dev accuracy 0.9255train loss 0.049dev accuracyloss
Our SST-2 run. Dev accuracy (blue) climbs above 0.9 within the first few hundred steps; training loss (orange) keeps falling for all three epochs.

SST-2: 92.55% dev accuracy (807 of 872 sentences right), after 6,315 steps. Before fine-tuning, with a random WW, the same model scored 49.1%: a coin flip. The paper reports 92.7 for BERT-base on the SST-2 dev set (Table 5, in Part 5) and 93.5 on the hidden test set (Table 1). Our single run, with one learning rate and no search, lands at 92.55. (During training, dev accuracy touched 93.5% at step 2,520; I report the model at the end of the 3 epochs, which is what a fixed number of epochs means, not the best checkpoint: picking the best checkpoint by its dev score would flatter the dev score.)

Note how fast it learns: dev accuracy was already 85.4% after the first 210 steps, which is about 10% of one epoch. Almost all of the knowledge was already in the pre-trained weights; fine-tuning only has to connect it to the two labels.

The run took 48 minutes of wall time on the laptop GPU, including 31 evaluations on the dev set, and other experiments were sharing the GPU for most of it, so treat the time as an upper bound. That matches the paper's "a few hours on a GPU" comfortably.

MRPC is the opposite kind of task: only 3,668 training pairs. Here I did what the paper did for GLUE and ran all four learning rates from its grid, one run each, keeping everything else fixed:

Learning rateMRPC dev accuracyF1Time
5e-587.0% (355/408)90.92.7 min
4e-584.8% (346/408)89.52.7 min
3e-583.3% (340/408)88.62.7 min
2e-583.3% (340/408)88.52.8 min
MRPC dev accuracy, one run per learning ratelr 5e-55e-5: 87.0%87.0%lr 4e-54e-5: 84.8%84.8%lr 3e-53e-5: 83.3%83.3%lr 2e-52e-5: 83.3%83.3%the paper reports 86.7 for BERT-base (Table 5, dev); bars start at 0
MRPC dev accuracy for each of the paper's four learning rates, one run each with the same seed.

The best learning rate here is 5e-5, with 87.0% dev accuracy (355/408) and F1 90.9. The paper reports 86.7 dev accuracy for BERT-base on MRPC (Table 5). Three honest remarks:

  • The same job is not even exactly repeatable. Before the grid, I had run the 2e-5 job once with the same seed and got 81.9% (334/408), against 83.3% in the grid. GPU arithmetic on the Apple GPU is not bit-for-bit repeatable, and on 408 dev examples one changed answer moves accuracy by 0.25 points. This is the instability the paper mentions, and why it reports "average Dev Set accuracy from 5 random restarts" in its model-size study (Part 5).
  • The spread between learning rates is large for such a small dataset, which is exactly what Appendix A.3 warns about ("large data sets ... were far less sensitive to hyperparameter choice than small data sets").
  • MRPC is lopsided: 68% of its pairs are paraphrases (the GLUE paper notes this), so always answering "yes" already scores about 68% on dev. Before fine-tuning, our random head answered "no" every time and scored 31.6%, the exact mirror image. This is why GLUE reports F1 next to accuracy for MRPC.

SQuAD v1.1: finding the answer in a paragraph

What a SQuAD example looks like, from the dataset's own paper:

The start probability, written out:

Pistart=eS⋅Ti∑jeS⋅TjP^{\text{start}}_i = \frac{e^{S \cdot T_i}}{\sum_{j} e^{S \cdot T_j}}

where:

  • TiT_i is the final vector of passage token ii (768 numbers);
  • SS is the learned start vector (768 numbers), and S⋅TiS \cdot T_i their dot product: one score per token, high when token ii "looks like the start of an answer to this question";
  • the sum in the bottom runs over all tokens jj of the paragraph, so the probabilities over the paragraph add up to 1.

The end probability PiendP^{\text{end}}_i is the same with EE in place of SS. To answer, pick the span that maximises

score(i,j)=S⋅Ti+E⋅Tjwith j≥i\text{score}(i, j) = S \cdot T_i + E \cdot T_j \qquad \text{with } j \ge i

where ii is the start token and jj the end token. The condition j≥ij \ge i just says an answer cannot end before it starts. Adding the two dot products is the same as multiplying the two probabilities (the softmax bottoms are the same for every span), so this picks the most likely start-and-end pair.

A toy example. A passage of four tokens, with these dot products:

S⋅T=(0.5, 1.0, 3.0, 0.2),E⋅T=(2.8, 0.1, 1.0, 1.5)S \cdot T = (0.5,\ 1.0,\ 3.0,\ 0.2), \qquad E \cdot T = (2.8,\ 0.1,\ 1.0,\ 1.5)

Every cell of the table below is S⋅Ti+E⋅TjS \cdot T_i + E \cdot T_j. The largest number in the whole table is 5.8, at i=2,j=0i = 2, j = 0: but that span ends before it starts. Among the cells with j≥ij \ge i (on or above the diagonal), the best is 4.5, at i=2,j=3i = 2, j = 3. The softmaxes give P2start=0.782P^{\text{start}}_2 = 0.782 and P3end=0.181P^{\text{end}}_3 = 0.181, so if (2, 3) is the true span, the training loss is −log⁡0.782−log⁡0.181=0.245+1.709=1.954-\log 0.782 - \log 0.181 = 0.245 + 1.709 = 1.954.

Span scores score(i, j) = S·Tᵢ + E·Tⱼ, toy passage of 4 tokensend jstart i012303.30.61.52.013.81.12.02.525.83.14.04.533.00.31.21.7S·T = [0.5, 1.0, 3.0, 0.2]E·T = [2.8, 0.1, 1.0, 1.5]highest of all: i=2, j=0, score 5.8but it ends before it starts: crossed outbest with j ≥ i: i=2, j=3, score 4.5training loss if (2, 3) is right:−log 0.782 − log 0.181 = 1.954
The toy span table. Rows are start positions i, columns end positions j, each cell is S·Tᵢ + E·Tⱼ. Cells below the diagonal (end before start) are not allowed; the highest of those, 5.8, is ignored. The best allowed span is (2, 3) with 4.5.

The training loss for one example whose true answer starts at token ss and ends at token ee:

loss=−log⁡Psstart−log⁡Peend\text{loss} = -\log P^{\text{start}}_{s} - \log P^{\text{end}}_{e}

That is the paper's "sum of the log-likelihoods of the correct start and end positions", with a minus sign so that lower is better.

Running it

I did not fine-tune a SQuAD model myself. I used a public BERT-base checkpoint that someone else fine-tuned on SQuAD v1.1, csarron/bert-base-uncased-squad-v1, and wrote the span search myself. First, a check that the model really is the paper's design: its output layer is a 2×7682 \times 768 matrix, row 0 is SS and row 1 is EE. Computing S⋅TiS \cdot T_i by hand from the hidden states reproduces the model's start scores:

python
W = model.qa_outputs.weight          # (2, 768): row 0 = S, row 1 = E
out = model(**enc, output_hidden_states=True)
T = out.hidden_states[-1][0]         # T_i for every token, (tokens, 768)
start_by_hand = T @ W[0] + model.qa_outputs.bias[0]     # S . T_i

def best_span(start, end, passage_tokens, max_len=30):
    """max over i <= j of S.T_i + E.T_j, inside the passage, at most 30 tokens long"""
    best = None
    for i in passage_tokens:
        for j in passage_tokens:
            if i <= j < i + max_len and (best is None or start[i] + end[j] > best[0]):
                best = (start[i] + end[j], i, j)
    return best

(The real script only tries the 20 best starts and 20 best ends, like Google's released run_squad.py, which is much faster and finds the same answer in practice.) I used the paper's own abstract as the passage. The real output:

plain text
SQuAD v1.1 model: the output layer is (2, 768) -> row 0 is the start vector S, row 1 the end vector E (H = 768)
  it also has a bias: start +0.0049, end +0.0048 (a constant added to every position, so it cancels in the softmax)
Q: What does BERT stand for?
   answer: "Bidirectional Encoder Representations from Transformers"  (tokens 21..30, span score S.T_i + E.T_j = 15.16)
   runner-up spans: "Bidirectional Encoder Representations from Transformers." 11.21; "Bidirectional Encoder Representations" 10.90
   most likely start tokens: bid 0.989, transformers 0.009, en 0.001
   check: |S.T_i by hand - model start logit| max = 1.0e-05; softmax with vs without bias differs by 3.6e-07
Q: What is the GLUE score of BERT?
   answer: "80.5%"  (tokens 91..94, span score S.T_i + E.T_j = 16.81)
   runner-up spans: "to 80.5%" 10.68; "80.5% and SQuAD v1.1 question answering Test F1 to 93.2" 10.14
   most likely start tokens: 80 0.996, to 0.002, pushing 0.001
   check: |S.T_i by hand - model start logit| max = 7.6e-06; softmax with vs without bias differs by 1.2e-08
Q: On how many tasks does BERT get new results?
   answer: "eleven"  (tokens 81..81, span score S.T_i + E.T_j = 11.59)
   runner-up spans: "eleven natural language processing tasks" 9.38; "eleven natural language processing" 6.92
   most likely start tokens: eleven 0.990, on 0.002, natural 0.002
   check: |S.T_i by hand - model start logit| max = 8.6e-06; softmax with vs without bias differs by 1.2e-07
Q: What does BERT learn from?
   answer: "Transformers"  (tokens 30..30, span score S.T_i + E.T_j = 9.87)
   runner-up spans: "Bidirectional Encoder Representations from Transformers" 7.65; "Transformers. BERT is designed to pre-train deep bidirectional representations from unlabeled text" 6.76
   most likely start tokens: transformers 0.810, bid 0.088, un 0.061
   check: |S.T_i by hand - model start logit| max = 6.7e-06; softmax with vs without bias differs by 7.5e-08
Q: What does BERT stand for?P(start)bert: 0.000,: 0.000which: 0.000stands: 0.000for: 0.000bid: 0.9890.99##ire: 0.000##ction: 0.000##al: 0.000en: 0.001##code: 0.000##r: 0.000representations: 0.000from: 0.000transformers: 0.009P(end)bert: 0.000,: 0.000which: 0.000stands: 0.000for: 0.000bid: 0.000##ire: 0.000##ction: 0.000##al: 0.000en: 0.000##code: 0.000##r: 0.001representations: 0.0140.01from: 0.000transformers: 0.9660.97bert,whichstandsforbid##ire##ction##alen##code##rrepresentationsfromtransformers
Start and end probabilities over part of the passage for "What does BERT stand for?". Almost all the start probability sits on "bid" (the first piece of "Bidirectional") and almost all the end probability on "transformers", so the best span is the expansion of BERT.

Three things to notice:

  • The formula is exactly the model. S⋅TiS \cdot T_i computed by hand matches the model's start scores to about 10−510^{-5} (rounding noise of the GPU). The checkpoint also has a bias number added to every start score; adding the same number to every token does not change a softmax, and the output shows the probabilities differ by less than 10−610^{-6}.
  • "bid" is a word piece. The tokenizer cut "Bidirectional" into pieces (Part 2), and the answer starts at the first piece.
  • It can be wrong. For "What does BERT learn from?", the model answered "Transformers". The right answer, "unlabeled text", is in the passage, but the phrase "Representations from Transformers" fooled it. Fine-tuned models match patterns; they do not reason like a reader.

Scoring on the full SQuAD v1.1 dev set

One passage proves nothing about quality. So I ran the same checkpoint, with my own span search, on all questions of the SQuAD v1.1 dev set, using the official answer normalisation (lower case, no punctuation, no "a/an/the").

Written out, for one prediction and one gold answer, after normalising both (lowercase, remove punctuation and the words "a", "an", "the"):

precision=#shared words#predicted words,recall=#shared words#gold words,F1=2⋅precision⋅recallprecision+recall\text{precision} = \frac{\#\text{shared words}}{\#\text{predicted words}}, \qquad \text{recall} = \frac{\#\text{shared words}}{\#\text{gold words}}, \qquad F_1 = \frac{2 \cdot \text{precision} \cdot \text{recall}}{\text{precision} + \text{recall}}

When a question has several human answers, the score against the best-matching one counts. Worked examples:

plain text
pred "Bidirectional Encoder Representations" | gold "Bidirectional Encoder Representations from Transformers"
   shared words 3, precision 1.000, recall 0.600, F1 0.750, EM 0
pred "the Broncos defeated the Panthers" -> "broncos defeated panthers" | gold "Denver Broncos" -> "denver broncos"
   shared words 1, precision 0.333, recall 0.500, F1 0.400, EM 0

For the first: F1=2×1×0.6/(1+0.6)=0.75F_1 = 2 \times 1 \times 0.6 / (1 + 0.6) = 0.75.

Exact match and F1: word overlap after normalising (lowercase, no punctuation, no a/an/the)predictionbidirectionalencoderrepresentationsgoldbidirectionalencoderrepresentationsfromtransformersshared 3: precision 3/3 = 1.000, recall 3/5 = 0.600, F1 = 0.750, EM = 0predictionbroncosdefeatedpanthersgolddenverbroncosshared 1: precision 1/3 = 0.333, recall 1/2 = 0.500, F1 = 0.400, EM = 0
Two predictions scored against the gold answer, word by word. Shared words are highlighted. Missing two of five gold words gives F1 0.75; one shared word out of three predicted and two gold gives F1 0.4. Exact match is 0 for both.

Long passages do not fit in 384 tokens, so each one is cut into overlapping windows with a stride of 128 tokens (the settings of the released code), and the best span over all windows wins.

plain text
SQuAD v1.1 dev: 10570 questions (10753 windows of 384 tokens, stride 128): EM 80.79, F1 88.08  (9.3 min)
   a miss: Q "What was the theme of Super Bowl 50?" predicted "to determine the champion of the National Football League (NFL) for the 2015 season", gold ""golden anniversary""
   a miss: Q "What was the theme of Super Bowl 50?" predicted "to determine the champion of the National Football League (NFL) for the 2015 season", gold ""golden anniversary""
   a miss: Q "How many times have the Panthers been in the Super Bowl?" predicted "eight", gold "2"

On all 10,570 dev questions, this checkpoint with my decoding scores EM 80.79 and F1 88.08. Table 2 (next) lists 80.8 EM and 88.5 F1 for the authors' own BERT-base on the same dev set. So a BERT-base fine-tuned by someone else, decoded by my 60 lines of span search, lands within half a point of the paper. (The checkpoint's model page reports EM 80.91 and F1 88.23 with its own evaluation code; the small gap is down to decoding details such as how many candidate spans are tried.)

The misses are instructive. "What was the theme of Super Bowl 50?" appears twice because the dev set really contains two copies of that question (two separate entries with slightly different gold answers); both times the model picked a long span about the purpose of the game instead of "golden anniversary". And for "How many times have the Panthers been in the Super Bowl?" it answered "eight" where the answer is "2": a number from the right paragraph, attached to the wrong fact.

Table 2

Two footnotes qualify Table 2:

The arithmetic: the ensemble's 93.2 test F1 minus the top leaderboard ensemble's 91.7 is the "+1.5". And the single model with TriviaQA (91.8 test F1) beats that top ensemble (91.7), which is the sentence "our single BERT model outperforms the top ensemble system in terms of F1 score".

SQuAD v2.0: when there is no answer

The decision rule, written out:

answer with the best span if   s^i,j>snull+τ,otherwise say "no answer"\text{answer with the best span if } \;\hat{s}_{i,j} > s_{\text{null}} + \tau, \quad \text{otherwise say "no answer"}

where:

  • snull=S⋅C+E⋅Cs_{\text{null}} = S \cdot C + E \cdot C is the score of the "span" that starts and ends at [CLS] (recall that CC is the final vector of [CLS]);
  • s^i,j\hat{s}_{i,j} is the score of the best real span inside the passage, with j≥ij \ge i;
  • τ\tau (the Greek letter tau) is a threshold: a number you choose. A larger τ\tau makes the model more careful (it answers less often).

Again I used a public checkpoint, deepset/bert-base-uncased-squad2 (BERT-base fine-tuned on SQuAD 2.0 by its authors, not by me), and computed both scores myself for two answerable and two unanswerable questions about the same abstract:

python
s_null = start[0] + end[0]                 # S.C + E.C: [CLS] is token 0
s_hat, i, j = best_span(start, end, passage_tokens)
answer = passage[i..j] if s_hat > s_null + tau else "no answer"
plain text
SQuAD 2.0 Q: What does BERT stand for?
   s_null = S.C + E.C = 10.18;  best span "Bidirectional Encoder Representations from Transformers" scores 23.25;  difference +13.07
SQuAD 2.0 Q: What is the GLUE score of BERT?
   s_null = S.C + E.C = 11.64;  best span "80.5%" scores 22.88;  difference +11.24
SQuAD 2.0 Q: Who won the football world cup in 2018?
   s_null = S.C + E.C = 21.13;  best span "BERT" scores 1.22;  difference -19.92
SQuAD 2.0 Q: How many layers does BERT have?
   s_null = S.C + E.C = 13.19;  best span "all" scores 16.16;  difference +2.97

The rule with real numbers, from a public BERT-base fine-tuned on SQuAD 2.0 (deepset/bert-base-uncased-squad2), on our BERT-abstract passage, with τ=4.00\tau = 4.00 chosen on the dev set (next section):

plain text
Q: What does BERT stand for?
   s_null = S.C + E.C = 5.08 + 5.10 = 10.18
   best span "Bidirectional Encoder Representations from Transformers": S.T_i + E.T_j = 11.69 + 11.56 = 23.25
   tau = +4.00: 23.25 > 14.18 ?  yes -> answer
Q: Who won the football world cup in 2018?
   s_null = S.C + E.C = 10.51 + 10.63 = 21.13
   best span "BERT": S.T_i + E.T_j = 0.33 + 0.89 = 1.22
   tau = +4.00: 1.22 > 25.13 ?  no -> no answer
Q: How many layers does BERT have?
   s_null = S.C + E.C = 6.61 + 6.58 = 13.19
   best span "all": S.T_i + E.T_j = 8.45 + 7.71 = 16.16
   tau = 0:     16.16 > 13.19 ?  yes -> answer
   tau = +4.00: 16.16 > 17.19 ?  no -> no answer

The last question shows what τ\tau is for. The abstract does not say how many layers BERT has, and the best span ("all") is nonsense. With τ=0\tau = 0 the model would answer it; with τ=4\tau = 4 it correctly declines.

SQuAD 2.0 model: best span score minus the no-answer scoreWhat does BERT stand for?What does BERT stand for?: +13.07+13.07What is the GLUE score of BERT?What is the GLUE score of BERT?: +11.24+11.24Who won the football world cup in 2018?Who won the football world cup in 2018?: -19.92-19.92How many layers does BERT have?How many layers does BERT have?: +2.97+2.97answer: a span winsabstain: [CLS] wins
Best span score minus the no-answer score for four questions. Positive bars: a span wins and the model answers. Negative bars: the [CLS] option wins and the model says there is no answer. This uses a threshold of 0.

Read the differences:

  • For the two answerable questions, the best span beats the no-answer score easily (+13.07 and +11.24).
  • For the football question, nothing in the passage fits: the best span ("BERT") scores only 1.22, while [CLS] scores 21.13. The model says "no answer", which is right.
  • The last question is the interesting one. "How many layers does BERT have?" sounds like it should be answerable, but the abstract never says. With τ=0\tau = 0 the model answers "all" (from "in all layers"), because that span beats snulls_{\text{null}} by +2.97. That is a confident wrong answer, and exactly the kind of mistake the threshold τ\tau exists to reduce: with τ\tau above 2.97, the model would abstain.

Choosing τ on the dev set

The paper picks τ\tau "on the dev set to maximize F1". I did exactly that on the full SQuAD 2.0 dev set: for every question, store s^i,j−snull\hat{s}_{i,j} - s_{\text{null}}, then try thresholds from -6 to +6 in steps of 0.25.

plain text
SQuAD v2.0 dev: 11873 questions, 5945 have no answer
   tau = 0:    F1 78.47, EM 75.47, answers 56.7% of questions
   best tau = +4.00: F1 78.98, EM 76.22, answers 52.1% of questions  (8.2 min)
757677787980-6-4-20+2+4+6best τ +4.00: F1 78.98τ = 0: F1 78.47threshold τ (answer only if the best span beats the no-answer score by more than τ)SQuAD 2.0 dev F1
SQuAD 2.0 dev F1 as the threshold changes. Too low and the model answers questions that have no answer; too high and it refuses questions it could answer. The best tau sits in between.

Of the 11,873 dev questions, 5,945 have no answer. With τ=0\tau = 0, the checkpoint gets F1 78.47. The best threshold on this dev set is τ=+4.00\tau = +4.00, which gives F1 78.98 (EM 76.22) and makes the model answer 52.1% of the questions. The paper only reports BERT-large on SQuAD 2.0 (81.9 dev F1 in Table 3), so there is no BERT-base number to compare with directly; the checkpoint's model page reports 78.62 F1 with its own evaluation code.

One caution, which applies to the paper too: choosing τ\tau on the dev set and then reporting the dev score flatters the dev score a little. That is why the paper's headline number is the test score, computed by the SQuAD organisers on hidden answers with the τ\tau chosen on dev.

Table 3

SWAG: choosing the best ending

The scoring, written out for the four choices k=1,…,4k = 1, \dots, 4:

sk=Ck⋅w,P(choice k)=esk∑m=14esms_k = C_k \cdot w, \qquad P(\text{choice } k) = \frac{e^{s_k}}{\sum_{m=1}^{4} e^{s_m}}

where CkC_k is the [CLS] vector when BERT reads "sentence + ending kk", ww is the one new vector (768 numbers), and the softmax runs across the four choices. Unlike GLUE, the softmax is not over labels of one input, but over four separate inputs.

A toy example with H=4H = 4: four [CLS] vectors and one learned vector w=(2,−1,0.5,1)w = (2, -1, 0.5, 1):

s1=(2)(0.9)+(−1)(0.2)+(0.5)(−0.1)+(1)(0.4)=1.95,s2=−1.75,s3=1.35,s4=−0.30s_1 = (2)(0.9) + (-1)(0.2) + (0.5)(-0.1) + (1)(0.4) = 1.95, \quad s_2 = -1.75, \quad s_3 = 1.35, \quad s_4 = -0.30

Softmax over the four scores gives (0.596, 0.015, 0.327, 0.063)(0.596,\ 0.015,\ 0.327,\ 0.063). If ending 1 is right, the loss is −log⁡0.596=0.518-\log 0.596 = 0.518.

SWAG: one sequence per choice, one score per sequence, softmax across the four[CLS]She opened the fridge and[SEP]took out a carton of milk.BERTC1 . w = s1[CLS]She opened the fridge and[SEP]flew to the moon.BERTC2 . w = s2[CLS]She opened the fridge and[SEP]the fridge sang a song.BERTC3 . w = s3[CLS]She opened the fridge and[SEP]painted the ocean blue.BERTC4 . w = s4soft-maxThe same BERT runs four times. The only new weights: one vector w (768 numbers) and one bias.
SWAG. Each ending is paired with the sentence and read by BERT on its own. One learned vector w turns each [CLS] vector into a score, and a softmax across the four scores picks the ending.

The head in code, checked against BertForMultipleChoice (untrained, so the probabilities mean nothing yet; the point is the shapes and the arithmetic):

python
e = tok([ctx] * 4, endings, return_tensors="pt", padding=True)
e = {k: v.unsqueeze(0) for k, v in e.items()}                # (1 example, 4 choices, tokens)
C = model.bert(**{k: v.view(4, -1) for k, v in e.items()}).pooler_output   # (4, 768)
scores = C @ model.classifier.weight.T + model.classifier.bias           # (4, 1): one score each
probs = torch.softmax(scores.view(1, 4), -1)                 # softmax across the 4 choices
plain text
SWAG input: (1, 4, 16) = (batch, 4 choices, tokens); the vector w: (1, 768)
scores C.w by hand: [-0.3572, 0.141, -0.337, -0.0369];  library: [-0.3572, 0.141, -0.337, -0.0369]
softmax over the 4 choices (untrained w): [0.198, 0.326, 0.202, 0.273]

A careful reading note: SWAG was released in 2018 and built to be hard for the models of its day (its authors filtered out endings that their own models found easy, a method they call Adversarial Filtering). Within months, a fine-tuned BERT came close to human scores on it. This pattern (a benchmark is published, a bigger pre-trained model nearly solves it) repeated many times after BERT.

Next, in Part 5: the ablation studies. Is next sentence prediction really needed? How much of the gain is reading both ways? Do bigger models keep helping? And can BERT be used without fine-tuning at all, as a feature extractor?

Run it yourself

Three scripts produce every number in this part, in code/papers/bert/:

bash
pip install torch transformers datasets
python bert_part4_heads.py            # the GLUE and SWAG heads checked by hand (CPU, seconds)
python bert_part4_finetune.py sst2    # fine-tune BERT-base on SST-2 (GPU recommended)
python bert_part4_finetune.py mrpc 2e-5   # MRPC with one learning rate; try 5e-5, 4e-5, 3e-5 too
python bert_part4_squad.py            # span search, SQuAD v1.1 and v2.0 dev sets (public checkpoints)
Terminal output of bert_part4_heads.py: the GLUE classifier logits and loss computed by hand match the library, and the SWAG scores match
The real output of bert_part4_heads.py.
Terminal output of the SST-2 fine-tuning run: dev accuracy every few hundred steps and the final result
The real log of the SST-2 fine-tuning run (bert_part4_finetune.py sst2).
Terminal output of the MRPC fine-tuning run with learning rate 5e-5
The real log of the best MRPC run (bert_part4_finetune.py mrpc 5e-5).
Terminal output of bert_part4_squad.py: the span search on the abstract, SQuAD v1.1 dev EM and F1, the SQuAD 2.0 null scores and the threshold search
The real output of bert_part4_squad.py.

References

The BERT paper

  1. J. Devlin, M.-W. Chang, K. Lee, K. Toutanova. BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding. NAACL 2019 (ACL Anthology).
  2. Google Research. BERT code (run_classifier.py, run_squad.py, modeling.py).

Papers the BERT paper cites in this part

  1. A. Wang, A. Singh, J. Michael, F. Hill, O. Levy, S. R. Bowman. GLUE: A Multi-Task Benchmark and Analysis Platform for Natural Language Understanding. BlackboxNLP workshop at EMNLP 2018.
  2. A. Williams, N. Nangia, S. R. Bowman. A Broad-Coverage Challenge Corpus for Sentence Understanding through Inference (MultiNLI). NAACL 2018.
  3. R. Socher et al. Recursive Deep Models for Semantic Compositionality Over a Sentiment Treebank (SST-2). EMNLP 2013.
  4. A. Warstadt, A. Singh, S. R. Bowman. Neural Network Acceptability Judgments (CoLA). TACL 2019.
  5. D. Cer, M. Diab, E. Agirre, I. Lopez-Gazpio, L. Specia. SemEval-2017 Task 1: Semantic Textual Similarity Multilingual and Crosslingual Focused Evaluation (STS-B). SemEval 2017.
  6. W. B. Dolan, C. Brockett. Automatically Constructing a Corpus of Sentential Paraphrases (MRPC). IWP 2005.
  7. L. Bentivogli, B. Magnini, I. Dagan, H. T. Dang, D. Giampiccolo. The Fifth PASCAL Recognizing Textual Entailment Challenge (RTE). TAC 2009.
  8. H. J. Levesque, E. Davis, L. Morgenstern. The Winograd Schema Challenge (WNLI). KR 2012 (cited as an AAAI spring symposium paper, 2011).
  9. P. Rajpurkar, J. Zhang, K. Lopyrev, P. Liang. SQuAD: 100,000+ Questions for Machine Comprehension of Text. EMNLP 2016.
  10. P. Rajpurkar, R. Jia, P. Liang. Know What You Don't Know: Unanswerable Questions for SQuAD (SQuAD 2.0). ACL 2018.
  11. M. Joshi, E. Choi, D. S. Weld, L. Zettlemoyer. TriviaQA: A Large Scale Distantly Supervised Challenge Dataset for Reading Comprehension. ACL 2017.
  12. R. Zellers, Y. Bisk, R. Schwartz, Y. Choi. SWAG: A Large-Scale Adversarial Dataset for Grounded Commonsense Inference. EMNLP 2018.
  13. A. P. Parikh, O. Täckström, D. Das, J. Uszkoreit. A Decomposable Attention Model for Natural Language Inference. EMNLP 2016.
  14. M. Seo, A. Kembhavi, A. Farhadi, H. Hajishirzi. Bidirectional Attention Flow for Machine Comprehension (BiDAF). ICLR 2017.
  15. A. W. Yu et al. QANet: Combining Local Convolution with Global Self-Attention for Reading Comprehension. ICLR 2018.
  16. A. Radford, K. Narasimhan, T. Salimans, I. Sutskever. Improving Language Understanding by Generative Pre-Training (OpenAI GPT). OpenAI, 2018.

Other sources used in this part

  1. Public checkpoints: csarron/bert-base-uncased-squad-v1, deepset/bert-base-uncased-squad2 and textattack/bert-base-uncased-SST-2.
  2. Code for this part: bert_part4_heads.py, bert_part4_finetune.py, bert_part4_squad.py, bert_part4_math.py.