CHRISTOPHER POTTS: Welcome back, everyone.
This is part 6 in our series on Contextual Word
Representations.
We're going to be talking about practical fine tuning.
It's time to get hands-on with these parameters we've
been talking about.
So here's the guiding idea.
Your existing architecture, say, for the current original system
and bake off probably can benefit
from contextual representation.
We've seen that in many, many contexts
and I know you know these things.
The notebook fine tuning shows you
how to bring in transformer representations in two ways.
First, with simple featurization, and then
with full-on fine tuning.
And I'm going to talk about both of those in this screencast.
The heart of this idea is that by extending existing
PyTorch modules from the course code distribution,
you can very easily create customized fine tuning models
with just a few lines of code.
And that should be really empowering in terms
of exploring lots of different designs
and seeing how best to use these parameters for your problem.
I just want to mention that really
and truly, this is only possible because of the amazing work
that the Hugging Face team has done
to make these parameters accessible to all of us.
So let's start with simple featurization.
And I actually want to rewind to our discussion
of recurrent neural networks and think about how we represent
examples for those models.
In the standard mode, we have as our examples lists of tokens
here.
We convert those into lists of indices,
and those indices help us look up
vector representations of those words in some fixed embedding
space.
And the result of that is that each example is represented
by a list of vectors.
That's important to keep in mind.
We tend to think of the model as taking
as its inputs lists of tokens and having an embedding.
But from the point of view of the model itself,
it really wants to process as inputs lists of vectors.
And that's the empowering idea because if we use it
in fixed embedding, of course, then these two occurrences of A
will be the same vector, and these two
occurrences of B across examples will be the same vector.
But the model doesn't really care
that there's a same vector.
We could, if we wanted to, convert directly
from token sequences into lists of vectors using
a device like a BERT model.
And that would allow that A in the first position
and A in the third could correspond
to different vectors, or B across these two examples
might correspond to different vectors.
That would be the contextual representation
part of these models.
And again, from the point of view of the RNN,
we can feed these indirectly.
That's straight forward.
This is a complete recipe for doing
that using the SST code and the PyTorch
modules from the course code distribution.
So you can see that beyond the setup stuff which we've
done a few times, the feature function is just
going to use BERT functionality to look up the example's
indices and then convert them into vector representations.
And here, as a summary, we're going
to use the representation above the class token.
But lots of things are possible at that point.
And then when we have our model wrapper here,
we set up a Torch RNN Classifier.
And there're just two things of note.
First, we say use embedding equals false,
because we're going to feed vectors in directly.
There's no embedding involved here.
And we also don't need to have a vocabulary.
You could specify one, but it's not
involved because fundamentally, again,
the model deals directly with vectors.
And then at SST experiment, you, again,
say vectorized equals false.
And that is a complete recipe for bringing
in BERT representations with the standard RNN.
This isn't quite fine tuning though,
so let's think about how we might get added benefits
from actually updating those BERT parameters as opposed
to just using them as frozen representations
inputs to another model.
What I'd encourage you to do is think
about subclassing the PyTorch modules that
are included in our course code distribution.
Because then, you will be able to write code, just oriented
toward your model architecture, and a lot
of the details of optimization and data processing
will be handled for you.
This is, I hope, a powerful example of that.
It comes from the tutorial PyTorch models notebook.
It's a Torch Softmax Classifier, and the only thing
we have to do is rewrite this build_graph function to specify
one single dense layer.
We are using as our base class the Torch Shallow Neural
Classifier which handles everything else
about setting up this model and optimizing it.
If we wanted to go in the other direction
and instead fit a really deep model,
we could, again, begin from Torch Shallow Neural Classifier
and rewrite the build_graph function so that it just
has more layers, essentially.
And then what's happening in this init method
is we're just giving the user access
to the various hyperparameters that they could
choose to set up this model.
Finally, here's a more involved example.
This one, we start with a PyTorch NN module, kind of all
the way down at the base here.
This is a Torch Linear Regression model.
We set up the weight parameters here
and then we have this single forward pass,
which corresponds to the structure
of a simple linear regression.
Now, for the actual interface, we
need to do a little bit more work here.
So we set up the loss so that it's
appropriate for our regression model.
So most of the classifiers we've been looking at up until now.
build_graph just uses the NN module
that I showed you a second ago.
We need to do a little bit of work in build_dataset
and rewrite that so we process linear regression
data correctly.
And then we do need to rewrite the predict and score
functions to be kind of good citizens of the code base
and allow for hyperparameter optimization and cross
validation and so forth.
But that's, again, straightforward,
and fundamentally for predict we're actually making use
of the base class's _predict method for the heavy lifting
there.
And then score, of course, is just moving us out of the mode
of evaluating classifiers and into the mode of evaluating
regression models.
That's all you need to do.
And again, conspicuously absent from this
is most of the aspects of data processing
and all of the details of optimization.
The base class Torch model base here
has a very full featured fit method
that you can use to optimize these models
and do hyperparameter exploration.
And that brings us to the star of the show, which
would be BERT fine tuning with Hugging Face parameters.
Here we'll start with a PyTorch nn.module.
We load in a BERT module as we've done before,
and make sure to set it to train so that it can be updated.
And then the new parameters here are really
just this classifier layer, a dense layer
that's going to be oriented toward the classification
structure that we want our model to have.
The forward method calls the forward method
of the BERT model, and you get a bunch of representations.
There are a lot of options here.
What I've decided to do is just use the Hugging Face Pooler
Output, which is some parameters on top
of the class token as the input to the classifier.
When we optimize this model, with luck in a productive way,
not only will these classifier parameters be updated, but also
all the parameters of this BERT model
that you loaded in in train mode.
The interface is a little bit involved here.
So what we do is provide the user
with some flexibility about what choices to make.
build_graph, again just loads in the module
that I showed you just a second ago.
And then build_dataset is a bit involved.
But what we do fundamentally is use the BERT tokenizer to batch
encode our data.
And then we do a little bit of processing on the output
labels to make sure PyTorch can make sense of them.
That's really it.
In the heart of this, it's just that we're, again,
using Hugging Face functionality to represent
our data to the BERT model.
And then this is the really interesting part.
Calling the forward method and then fitting
the classifier on top is pretty much all you need to do.
And of course, that opens up a world of options.
Reps here has lots of other things
that you could use as the input to this classifier layer.
And many of them actually might be
more productive than the simple approach that I've taken here.