DistilBERT Trained on BookCorpus and English Wikipedia Data

Represent text as a sequence of vectors

Released in 2019, this model utilizes the technique of knowledge distillation during pre-training, reducing the size of BERT models by 40% and making it 60% faster while retaining 99% of its language understanding capabilities.

Number of models: 3

Training Set Information

Performance

Examples

Resource retrieval

Get the pre-trained net:

In[1]:=
NetModel["DistilBERT Trained on BookCorpus and English Wikipedia \
Data"]
Out[1]=

NetModel parameters

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

In[2]:=
NetModel["DistilBERT Trained on BookCorpus and English Wikipedia \
Data", "ParametersInformation"]
Out[2]=

Pick a non-default net by specifying the parameters:

In[3]:=
NetModel[{"DistilBERT Trained on BookCorpus and English Wikipedia \
Data", "Type" -> "BaseMultilingualCased", "InputType" -> "ListOfStrings"}]
Out[3]=

Pick a non-default uninitialized net:

In[4]:=
NetModel[{"DistilBERT Trained on BookCorpus and English Wikipedia \
Data", "Type" -> "BaseUncased", "InputType" -> "ListOfStrings"}, "UninitializedEvaluationNet"]
Out[4]=

Basic usage

Given a piece of text, the DistilBERT net produces a sequence of feature vectors of size 768, which correspond to the sequence of input words or subwords:

In[5]:=
input = "Hello world! I am here";
embeddings = NetModel["DistilBERT Trained on BookCorpus and English Wikipedia \
Data"][input];

Obtain dimensions of the embeddings:

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

Visualize the embeddings:

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

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[8]:=
net = NetModel[{"DistilBERT Trained on BookCorpus and English \
Wikipedia Data", "InputType" -> "ListOfStrings"}];
netencoder = NetExtract[net, "Input"]
Out[9]=

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

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

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[11]:=
net[{"Hello world!", "I am here"}, NetPort[{"embedding", "posembed", "Output"}]]
Out[11]=

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

In[12]:=
embeddings = net[{"Hello world!", "I am here"},
   {NetPort[{"embedding", "embeddingpos", "Output"}],
    NetPort[{"embedding", "embeddingwords", "Output"}]}];
Map[MatrixPlot, embeddings]
Out[13]=

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

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

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

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

The key part of these blocks is the attention module comprising of 12 parallel self-attention transformations, also called “attention heads”:

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

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 DistilBERT subword embeddings (as an arbitrary choice):

In[17]:=
sentenceembedding = NetAppend[
  NetModel["DistilBERT Trained on BookCorpus and English Wikipedia \
Data"], "pooling" -> SequenceLastLayer[]]
Out[17]=

Define a list of sentences in two broad categories (food and music):

In[18]:=
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[19]:=
assoc = AssociationThread[sentences -> sentenceembedding[sentences]];

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

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

Train a classifier model with the subword embeddings

Get a text-processing dataset:

In[21]:=
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[22]:=
RandomSample[train, 1]
Out[22]=

Precompute the DistilBERT vectors for the training and the validation datasets (if available, GPU is highly recommended):

In[23]:=
trainembeddings = NetModel["DistilBERT Trained on BookCorpus and English Wikipedia \
Data"][train[[All, 1]], TargetDevice -> "GPU"] -> train[[All, 2]];
validembeddings = NetModel["DistilBERT Trained on BookCorpus and English Wikipedia \
Data"][valid[[All, 1]], TargetDevice -> "GPU"] -> valid[[All, 2]];

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

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

Train the network on the precomputed vectors from DistilBERT:

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

Check the classification error rate on the validation data:

In[26]:=
distilbertresults["ValidationMeasurements", "ErrorRate"]
Out[26]=

Let’s 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[27]:=
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[28]:=
gloveresults = NetTrain[classifierhead, trainembeddingsglove, All,
  ValidationSet -> validembeddingsglove,
  TrainingStoppingCriterion -> <|"Criterion" -> "ErrorRate", "Patience" -> 50|>,
  TargetDevice -> "CPU",
  MaxTrainingRounds -> 50]
Out[28]=

Compare the results obtained with GPT and with GloVe:

In[29]:=
Dataset[<|"DistilBERT" -> distilbertresults["ValidationMeasurements"],
   "GloVe" -> gloveresults["ValidationMeasurements"]|>]
Out[29]=

Net information

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

In[30]:=
Information[
 NetModel["DistilBERT Trained on BookCorpus and English Wikipedia \
Data"], "ArraysElementCounts"]
Out[30]=

Obtain the total number of parameters:

In[31]:=
Information[
 NetModel["DistilBERT Trained on BookCorpus and English Wikipedia \
Data"], "ArraysTotalElementCount"]
Out[31]=

Obtain the layer type counts:

In[32]:=
Information[
 NetModel["DistilBERT Trained on BookCorpus and English Wikipedia \
Data"], "LayerTypeCounts"]
Out[32]=

Display the summary graphic:

In[33]:=
Information[
 NetModel["DistilBERT Trained on BookCorpus and English Wikipedia \
Data"], "SummaryGraphic"]
Out[33]=

Export to MXNet

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

In[34]:=
jsonPath = Export[FileNameJoin[{$TemporaryDirectory, "net.json"}], NetModel["DistilBERT Trained on BookCorpus and English Wikipedia \
Data"], "MXNet"]
Out[34]=

Export also creates a net.params file containing parameters:

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

Get the size of the parameter file:

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

Requirements

Wolfram Language 12.1 (March 2020) or above

Resource History

Reference