Split before making windows
To know whether a model has learned or only memorised, part of the text is kept aside as validation data and never trained on. With windows there is a trap. Windows overlap, so if you cut all the windows first and then deal them out, a validation window can share most of its tokens with a training window. The model would be tested on text it has nearly seen. The fix is to split the text first and cut windows inside each part.
Learn it
Words to know
- validation set
- Examples kept out of training and used only to check how the model does on data it has not seen.
- data leakage
- When information from the validation data gets into training, so the model looks better than it is.
- context window
- The number of tokens the model reads at one time.
How it works
- Split the token list at
cut: positions beforecutare training, the rest are validation. - Inside each part, take every stretch of
context + 1tokens, moving the start one position at a time. The extra token is the last target. - No window may cross the cut. A part with
Ntokens givesN - contextwindows, or none if that is 0 or less.
Worked example
Input tokens = [0, 1, 2, 3, 4, 5, 6] cut = 4 context = 2 Output [[[0, 1, 2], [1, 2, 3]], [[4, 5, 6]]]
| Part | Tokens | Windows of 3 |
|---|---|---|
| Training | [0, 1, 2, 3] | [0, 1, 2] and [1, 2, 3] |
| Validation | [4, 5, 6] | [4, 5, 6] |
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | |
|---|---|---|---|---|---|---|---|
| tokens | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
| window | 0 | 1 | 2 | ||||
| window | 1 | 2 | 3 | ||||
| window | 4 | 5 | 6 |
- training
- validation
The stretches [2, 3, 4] and [3, 4, 5] are left out on purpose. They would cross the cut and put validation tokens into training.
Why it matters
The validation loss from chapter 4 only means something if the split is clean. The workshop's GPT splits its text this way, so its validation loss is measured on sentences it has not trained on.
Your turn
Split a token sequence into training and validation parts, then list every full window inside each part.
tokens- the token IDs of the whole text
cut- the position where validation begins; between 0 and the length of
tokens context- the model's window length; positive
- Returns
- The list
[train_windows, validation_windows]. Each window hascontext + 1tokens, windows start one position apart, and none crossescut.
Example 1
Input tokens = [0, 1, 2, 3, 4, 5, 6] cut = 4 context = 2 Output [[[0, 1, 2], [1, 2, 3]], [[4, 5, 6]]]
Training tokens [0, 1, 2, 3] give two windows. Validation tokens [4, 5, 6] give one.
Example 2
Input tokens = [0, 1, 2, 3, 4, 5] cut = 3 context = 2 Output [[[0, 1, 2]], [[3, 4, 5]]]
Each part has 3 tokens, which is one window of 3.
Example 3
Input tokens = [0, 1] cut = 1 context = 2 Output [[], []]
Each part has one token, too few for a window of 3. Both lists are empty.
Hints
Each hint gives away a little more. Open one only when you need it.
Hint 1: Where to start
Split first:
tokens[:cut]andtokens[cut:]. Then do the same job on each part, so write that job as a small helper function.Hint 2: The key step
In a part of length
N, a window ofcontext + 1tokens can start at positions 0 toN - context - 1. That isrange(N - context), which is empty when the part is too short.Hint 3: The outline
Define
windows(part)returningpart[i : i + context + 1]for eachiinrange(len(part) - context). Return[windows(tokens[:cut]), windows(tokens[cut:])].
Solution
From here on the page gives the method away. If you are still solving, go back to the hints.
Step by step
- Split the text before making any window. After the split, the two parts share no token. Whatever is cut from one cannot overlap the other.
- Every stretch of context + 1 tokens inside one part. The extra token is the target for the last position.
rangeof 0 or a negative number is empty, so a short part gives no windows and no error.
The same numbered steps are the comments in the solution.
Python
def solve(tokens, cut, context):
# 1. Split the text before making any window
train = tokens[:cut]
validation = tokens[cut:]
# 2. Every stretch of context + 1 tokens inside one part
def windows(part):
return [part[i : i + context + 1] for i in range(len(part) - context)]
return [windows(train), windows(validation)]Watch out
- Do not make the windows first and split the list of windows. Neighbouring windows share all but one token, so the two sides would overlap.
- A window has
context + 1tokens, notcontext: the input and one more token for the final target.
Check yourself
Why split the text before cutting windows?
Windows overlap. Splitting afterwards puts nearly identical windows on both sides, and the validation loss would look better than it should.
Why does each window hold context + 1 tokens?
The input is context tokens long, and the target for its last position is one token further.