Can I use BERT as a feature extractor without any finetuning on my specific data set?

Viewed 1998

I'm trying to solve a multilabel classification task of 10 classes with a relatively balanced training set consists of ~25K samples and an evaluation set consists of ~5K samples.

I'm using the huggingface:

model = transformers.BertForSequenceClassification.from_pretrained(...

and obtain quite nice results (ROC AUC = 0.98).

However, I'm witnessing some odd behavior which I don't seem to make sense of -

I add the following lines of code:

for param in model.bert.parameters():
    param.requires_grad = False

while making sure that the other layers of the model are learned, that is:

[param[0] for param in model.named_parameters() if param[1].requires_grad == True]
gives
['classifier.weight', 'classifier.bias']

Training the model when configured like so, yields some embarrassingly poor results (ROC AUC = 0.59).

I was working under the assumption that an out-of-the-box pre-trained BERT model (without any fine-tuning) should serve as a relatively good feature extractor for the classification layers. So, where do I got it wrong?

2 Answers

From my experience, you are going wrong in your assumption

an out-of-the-box pre-trained BERT model (without any fine-tuning) should serve as a relatively good feature extractor for the classification layers.

I have noticed similar experiences when trying to use BERT's output layer as a word embedding value with little-to-no fine-tuning, which also gave very poor results; and this also makes sense, since you effectively have 768*num_classes connections in the simplest form of output layer. Compared to the millions of parameters of BERT, this gives you an almost negligible amount of control over intense model complexity. However, I also want to cautiously point to overfitted results when training your full model, although I'm sure you are aware of that.

The entire idea of BERT is that it is very cheap to fine-tune your model, so to get ideal results, I would advise against freezing any of the layers. The one instance in which it can be helpful to disable at least partial layers would be the embedding component, depending on the model's vocabulary size (~30k for BERT-base).

I think the following will help in demystifying the odd behavior I reported here earlier –

First, as it turned out, when freezing the BERT layers (and using an out-of-the-box pre-trained BERT model without any fine-tuning), the number of training epochs required for the classification layer is far greater than that needed when allowing all layers to be learned.

For example,

Without freezing the BERT layers, I’ve reached:

ROC AUC = 0.98, train loss = 0.0988, validation loss = 0.0501 @ end of epoch 1

ROC AUC = 0.99, train loss = 0.0484, validation loss = 0.0433 @ end of epoch 2

Overfitting, train loss = 0.0270, validation loss = 0.0423 @ end of epoch 3

Whereas, when freezing the BERT layers, I’ve reached:

ROC AUC = 0.77, train loss = 0.2509, validation loss = 0.2491 @ end of epoch 10

ROC AUC = 0.89, train loss = 0.1743, validation loss = 0.1722 @ end of epoch 100

ROC AUC = 0.93, train loss = 0.1452, validation loss = 0.1363 @ end of epoch 1000

The (probable) conclusion that arises from these results is that working with an out-of-the-box pre-trained BERT model as a feature extractor (that is, freezing its layers) while learning only the classification layer suffers from underfitting.

This is demonstrated in two ways:

First, after running 1000 epochs, the model still hasn’t finished learning (the training loss is still higher than the validation loss).

Second, after running 1000 epochs, the loss values are still higher than the values achieved with the non-freeze version as early as the 1’st epoch.

To sum it up, @dennlinger, I think I completely agree with you on this:

The entire idea of BERT is that it is very cheap to fine-tune your model, so to get ideal results, I would advise against freezing any of the layers.

Related