Pre-trained Distilled BERT Trained on BookCorpus and English Wikipedia Data

Represent text as a sequence of vectors

Released in 2019, this model is a collection of 24 pre-trained miniature BERT nets of different depth and width, all trained using knowledge distillation (from the original BERT) and student pre-training. The largest net is equivalent to the BERT-base model and is 3 times smaller and 1.25 times faster than the teacher; the smallest net is 77 times smaller and 65 times faster. All nets are case-insensitive.

Number of models: 24

Training Set Information

Performance

Examples

Resource retrieval

Get the pre-trained net:

In[1]:=
NetModel["Pre-trained Distilled BERT Trained on BookCorpus and \
English Wikipedia Data"]
Out[2]=

NetModel parameters

This model consists of a family of individual nets, each identified by a specific parameter combination. Inspect the available parameters:

In[3]:=
NetModel["Pre-trained Distilled BERT Trained on BookCorpus and \
English Wikipedia Data", "ParametersInformation"]
Out[4]=

Pick a non-default net by specifying the parameters:

In[5]:=
NetModel[{"Pre-trained Distilled BERT Trained on BookCorpus and \
English Wikipedia Data", "AttentionHeads" -> 4, "AttentionUnits" -> 2, "InputType" -> "ListOfStrings"}]
Out[6]=

Pick a non-default uninitialized net:

In[7]:=
NetModel[{"Pre-trained Distilled BERT Trained on BookCorpus and \
English Wikipedia Data", "AttentionHeads" -> 4, "AttentionUnits" -> 12, "InputType" -> "ListOfStrings"}, "UninitializedEvaluationNet"]
Out[8]=

Basic usage

Given a piece of text, the default pre-trained distilled BERT net produces a sequence of feature vectors of size 512 (in general, the size of the feature vector is 64 × "AttentionHeads"), which corresponds to the sequence of input words or subwords:

In[9]:=
input = "Hello world! I am here";
embeddings = NetModel["Pre-trained Distilled BERT Trained on BookCorpus and \
English Wikipedia Data"][input];

Obtain dimensions of the embeddings:

In[10]:=
Dimensions@embeddings
Out[10]=

Visualize the embeddings:

In[11]:=
MatrixPlot@embeddings
Out[11]=

Transformer architecture

Each input text segment is first tokenized into words or subwords using a word-piece tokenizer and additional text normalization. Integer codes called token indices are generated from these tokens, together with additional segment indices:

In[12]:=
net = NetModel[{"Pre-trained Distilled BERT Trained on BookCorpus and \
English Wikipedia Data", "InputType" -> "ListOfStrings"}];
netencoder = NetExtract[net, "Input"]
Out[13]=

For each input subword token, the encoder yields a pair of indices that corresponds to the token index in the vocabulary and the index of the sentence within the list of input sentences:

In[14]:=
netencoder[{"Hello world!", "I am here"}]
Out[14]=

The list of tokens always starts with special token index 102, which corresponds to the classification index. Also, the special token index 103 is used as a separator between the different text segments. Each subword token is also assigned a positional index:

In[15]:=
net[{"Hello world!", "I am here"}, NetPort[{"embedding", "posembed", "Output"}]]
Out[15]=

A lookup is done to map these indices to numeric vectors of size 512:

In[16]:=
embeddings = net[{"Hello world!", "I am here"},
   {NetPort[{"embedding", "embeddingpos", "Output"}],
    NetPort[{"embedding", "embeddingtokens", "Output"}],
    NetPort[{"embedding", "embeddingsegments", "Output"}]}];
Map[MatrixPlot, embeddings]
Out[17]=

For each subword token, these three embeddings are combined by summing elements with ThreadingLayer:

In[18]:=
NetExtract[net, "embedding"]
Out[18]=

The transformer architecture then processes the vectors using six structurally identical self-attention blocks stacked in a chain:

In[19]:=
NetExtract[net, "encoder"]
Out[19]=

The key part of these blocks is the attention module comprising of parallel self-attention transformations, also called "AttentionHeads". The number of such blocks is given by the parameter "AttentionUnits":

In[20]:=
NetExtract[net, {"encoder", 1, 1, "attention"}]
Out[20]=

BERT-like models use self-attention, where the embedding of a given subword depends on the full input text. The following figure compares self-attention (lower left) to other types of connectivity patterns that are popular in deep learning:

Sentence analogies

Define a sentence embedding that takes the last feature vector from pre-trained distilled BERT subword embeddings (as an arbitrary choice):

In[21]:=
sentenceembedding = NetAppend[
  NetModel["Pre-trained Distilled BERT Trained on BookCorpus and \
English Wikipedia Data"], "pooling" -> SequenceLastLayer[]]
Out[22]=

Define a list of sentences:

In[23]:=
sentences = {"The music is soothing to the ears.", "The song blasted from the little radio.", "This soundtrack is too good.", "Food is needed for survival.", "If you are hungry, please eat.", "She cooks really well."};

Precompute the embeddings for a list of sentences:

In[24]:=
assoc = AssociationThread[sentences -> sentenceembedding[sentences]];

Visualize the similarity between the sentences using the net as a feature extractor:

In[25]:=
FeatureSpacePlot[assoc, LabelingFunction -> Callout]
Out[25]=

Train a classifier model with the subword embeddings

Get a text-processing dataset:

In[26]:=
train = ResourceData["Sample Data: Movie Review Sentence Polarity", "TrainingData"];
valid = ResourceData["Sample Data: Movie Review Sentence Polarity", "TestData"];

View a random sample of the dataset:

In[27]:=
RandomSample[train, 1]
Out[27]=

Precompute the pre-trained distilled BERT vectors for the training and the validation datasets (if available, GPU is highly recommended):

In[28]:=
trainembeddings = NetModel["Pre-trained Distilled BERT Trained on BookCorpus and \
English Wikipedia Data"][train[[All, 1]], TargetDevice -> "CPU"] -> train[[All, 2]];
validembeddings = NetModel["Pre-trained Distilled BERT Trained on BookCorpus and \
English Wikipedia Data"][valid[[All, 1]], TargetDevice -> "CPU"] -> valid[[All, 2]];

Define a network to classify the sequences of subword embeddings, using a max-pooling strategy:

In[29]:=
classifierhead = NetChain[{DropoutLayer[], NetMapOperator[2], AggregationLayer[Max, 1], SoftmaxLayer[]}, "Output" -> NetDecoder[{"Class", {"negative", "positive"}}]]
Out[29]=

Train the network on the precomputed vectors from the pre-trained distilled BERT:

In[30]:=
pdbertresults = NetTrain[classifierhead, trainembeddings, All,
  ValidationSet -> validembeddings,
  TargetDevice -> "CPU",
  MaxTrainingRounds -> 50]
Out[30]=

Check the classification error rate on the validation data:

In[31]:=
pdbertresults["ValidationMeasurements", "ErrorRate"]
Out[31]=

Compare the results with the performance of a classifier trained on context-independent word embeddings. Precompute the GloVe vectors for the training and the validation dataset:

In[32]:=
trainembeddingsglove = NetModel["GloVe 300-Dimensional Word Vectors Trained on Wikipedia \
and Gigaword 5 Data"][train[[All, 1]], TargetDevice -> "CPU"] -> train[[All, 2]];
validembeddingsglove = NetModel["GloVe 300-Dimensional Word Vectors Trained on Wikipedia \
and Gigaword 5 Data"][valid[[All, 1]], TargetDevice -> "CPU"] -> valid[[All, 2]];

Train the classifier on the precomputed GloVe vectors:

In[33]:=
gloveresults = NetTrain[classifierhead, trainembeddingsglove, All,
  ValidationSet -> validembeddingsglove,
  TrainingStoppingCriterion -> <|"Criterion" -> "ErrorRate", "Patience" -> 50|>,
  TargetDevice -> "CPU",
  MaxTrainingRounds -> 50]
Out[33]=

Compare the results obtained from the pre-trained distilled BERT with GloVe:

In[34]:=
Dataset[<|"PDBERT" -> pdbertresults["ValidationMeasurements"], "GloVe" -> gloveresults["ValidationMeasurements"]|>]
Out[34]=

Net information

Inspect the number of parameters of all arrays in the net:

In[35]:=
Information[
 NetModel["Pre-trained Distilled BERT Trained on BookCorpus and \
English Wikipedia Data"], "ArraysElementCounts"]
Out[36]=

Obtain the total number of parameters:

In[37]:=
Information[
 NetModel["Pre-trained Distilled BERT Trained on BookCorpus and \
English Wikipedia Data"], "ArraysTotalElementCount"]
Out[38]=

Obtain the layer type counts:

In[39]:=
Information[
 NetModel["Pre-trained Distilled BERT Trained on BookCorpus and \
English Wikipedia Data"], "LayerTypeCounts"]
Out[40]=

Display the summary graphic:

In[41]:=
Information[
 NetModel["Pre-trained Distilled BERT Trained on BookCorpus and \
English Wikipedia Data"], "SummaryGraphic"]
Out[42]=

Export to MXNet

Export the net into a format that can be opened in MXNet:

In[43]:=
jsonPath = Export[FileNameJoin[{$TemporaryDirectory, "net.json"}], NetModel["Pre-trained Distilled BERT Trained on BookCorpus and \
English Wikipedia Data"], "MXNet"]
Out[44]=

Export also creates a net.params file containing parameters:

In[45]:=
paramPath = FileNameJoin[{DirectoryName[jsonPath], "net.params"}]
Out[45]=

Get the size of the parameter file:

In[46]:=
FileByteCount[paramPath]
Out[46]=

Requirements

Wolfram Language 12.1 (March 2020) or above

Resource History

Reference