Iteration 3, value by value
A personal model at 6.8% error, a third fewer than before. Behind it: a base that starts 16 points better, a plan chosen by 120 searched trials, and the lucky run that changed how we choose.
TL;DR
Win and failureThe question Iteration 3 changed the synthetic data, the GPU, the writer's lines and the personalising plan. What did each value rest on, and what did it buy?
Iteration 3 rebuilt the fake handwriting the model learns from, and the base model got much better at reading the writer before seeing a single line of theirs. Then a search on a rented GPU picked how to fit it to the writer, and taught us something on the way: the winning run was partly luck, so a plan is now judged by five runs, not one. The result reads the writer's test lines with a third fewer mistakes than our best model so far.
- Personal v3.1-stable reads the writer's 24 test lines at 6.8% error, from 10.3% for v2.1-personal: 41 wrong letters of 600 instead of 62, from fewer of the writer's lines (68, not 85), within the 50 ms speed budget.
- Base v3, with nothing of the writer's in it, reads them at 27.2%, down from 43.3%. Five data changes at once, so we know the data helped, not which change did.
- Two searches, 120 trials on a rented GPU for $1.36, picked how to personalise. Freezing any layer of the base always hurt.
- The first winner was a lucky run: made again, its 10.9% became 15.6%. Over five seeds the steadier plan wins, 11.4% against 12.8% for the old plan.
- Decoder settings tuned on the dev lines won there and lost on the test lines, so the defaults stay. The whole iteration cost about $17.
Judge a training plan by its mean over several seeds, not by its best run: on a small dev set, the winner of a search is partly luck.
Why read on The same plan on the same lines scored 10.9%, then 15.6%. The five-seed chart that settled which plan to keep, and why the best trial of a search is the one to trust least.
1 The question
Iteration 3 rebuilt the base model's data and searched for how to personalise it. Every value had to rest on a measurement.
Iteration 3 has two halves. The first rebuilds the base model The general reader, trained on lots of synthetic handwriting before it ever sees yours. Changing it: A better base gives a better personal model: it starts lower and learns your hand faster. Alone it can still read your writing badly. Here: From v1 on, trained on a cloud GPU from a recipe; v2's base alone gets 43.6% of your letters wrong, 13.1% once personalised.: the general reader, trained on 300,000 lines of synthetic data Training data written by code instead of collected from people: here, handwriting fonts turned into pen paths with timing. Changing it: It's unlimited and licence-clean, but if it doesn't look like real writing the model learns the wrong thing: the synthetic-to-real gap. Here: 300,000 lines for v2, from 134 fonts under open licences (OFL or Apache). before it sees a single line of the writer's. The second fits it to the writer: fine-tuning Continuing to train an already-trained model for a short while on new data: here, your own lines. Changing it: It adapts a general model to your hand from very little data. Too long, or with too high a learning rate, and it memorises your lines or forgets general writing. Here: 40 varied copies of each of your 25 lines, plus 2,000 synthetic lines replayed. It took v2 from 43.6% to 13.1% errors..
Each half is a list of values: how many words, which GPU, how many epochs, how big a step. Every value here was set one of three ways, and the episode says which:
- Measured: a small experiment or a count decided it before the big run.
- Searched: a tool tried many values on held-out lines and kept the best.
- Kept: the model's size, its training settings and the decoder are v2's, so the change can be read against v2.
- Personal v3.1-stable, test lines
- 6.8%
- from 10.3% (v2.1-personal)
- Base v3, before personalising
- 27.2%
- from 43.3% (base v2)
- Personalising trials searched
- 120
- $1.36 on a rented GPU
- All of iteration 3
- ≈ $17
- base $15.06, personalising $1.99
2 Five changes to what the base reads
v3's synthetic ink writes more like the writer and covers what the writer writes. Each change answers a measurement.
Episode 4 found that the old synthetic ink didn't write like a hand: it lifted the pen too often and drew jagged paths. A closer look at what the writer actually writes found more gaps. v3 fixes five things at once:
| Change | v2 | v3 | Why |
|---|---|---|---|
| How font ink moves | 1.8 pen lifts, a jagged path, written fast | 0.96 pen lifts, 10.1 points and 0.67 s per letter | The writer's own rates, measured on their recorded lines (episode 4). |
| Hand-like words | none | a quarter of the lines built from InkSight words | Font letters are traced skeletons; InkSight gives the strokes a pen would draw. |
| Characters written | 116 of 177 | all 177, with amounts, units, n°, checkboxes, more math | 61 characters never appeared, among them $ # & @ ; … β Σ ± ∞. The writer uses 17 of them. |
| Vocabulary | 5,000 words per language | 20,000 | Covers 88.5% of the words the writer writes, up from 74.9%. |
| Share of math | 15% of lines | 25% | The writer's own share. Math was the worst-read kind of line: 23.5% error. |
What InkSight is, and why its words aren't joined
InkSight is an open model (Apache-2.0 weights) that looks at a picture of a word and writes down the pen strokes a hand would use to draw it. We give it our own words rendered in our own open fonts, so no one's real handwriting goes in.
Its words lift the pen about 1.2 times per letter, where traced font ink lifts it 1.8 times. Those lifts are learned from real pens: dots on i's, crossed t's. So they are kept as they are, while font ink is joined down to the writer's 0.96.
Before paying for the GPU, a sample of 3,000 v3 lines was checked against the writer's ink. v2's lines had raised five warnings in that check, on pen lifts, points and time per letter, a rare capital and characters never seen. v3's raised none.
3 How many words to ask InkSight for: 3,000, not 6,000
The first guess counted words. The model reads letters, and by that measure 3,000 words already show almost everything.
The first plan was 6,000 words, picked by eye from how much of the texts' words a bank covers. But the recogniser reads letters, not words. A word missing from the bank is still written, in font ink.
What InkSight adds is how a hand forms and joins letters. So the measure that matters is how many letters and letter pairs the model sees in InkSight style, and that saturates early:
| Row | Value | Interval |
|---|---|---|
| 1,000 words | 92.3% | |
| 2,000 words | 95.6% | |
| 3,000 words | 97.2% | |
| 4,000 words | 97.8% | |
| 6,000 words | 98.7% | |
| 8,000 words | 99.2% | |
| 10,000 words | 99.9% |
And each extra word costs more than the one before. InkSight writes one point at a time, a batch of words finishes only when its longest word does, and rarer words are longer.
- Images derendered
- 5,774
- 3,000 words in 2 fonts each
- Faithful
- 97%
- 5,621 kept; 2,935 of 3,000 words
- Cost
- $13.29
- 4 h 31 min; $11 planned
A word counts as faithful when its strokes trace at least 80% of the letters' centre lines and at least 80% of the strokes stay on the ink. Midway through, the bill looked headed for $21 to $26; later batches held shorter words, and it ended at $13.29.
4 Which GPU: the cheapest epoch, not the fastest card
A 16-minute benchmark priced one epoch on six GPUs. The model is too small to fill any of them, so the mid-range card won.
Left to itself, the cloud provider (Modal) takes whichever of three GPU A graphics card: thousands of small cores doing arithmetic in parallel, which is what training mostly is. Changing it: Training is many times faster than on a CPU; rented by the second, so time is money. Here: A Modal T4 at about $0.59 an hour: v2's base cost $1.42. is free first. So before training, the same model, batch size and number format ran on six of them, and each was priced per epoch, including the processor cores and memory the provider also bills.
| Row | Value | Interval |
|---|---|---|
| A10G | $0.093 | |
| L40S | $0.108 | |
| L4 | $0.117 | |
| A100 40 GB | $0.159 | |
| T4 | $0.163 | |
| H100 | $0.206 |
| GPU | Lines per second | Minutes per epoch | Dollars per epoch |
|---|---|---|---|
| A10G | 1,521 | 3.2 | $0.093 |
| L40S | 1,941 | 2.5 | $0.108 |
| L4 | 989 | 4.9 | $0.117 |
| A100 40 GB | 1,381 | 3.5 | $0.159 |
| T4 | 604 | 8.0 | $0.163 |
| H100 | 1,813 | 2.7 | $0.206 |
The provider's default tries an L4 first: 26% dearer and 53% slower per epoch than the A10G. The batch size and number format stayed as the recipe says, since they change what the model learns, not just how fast.
5 The training run: 30 minutes, 87 cents
12 epochs finished on the A10G in half an hour, ended by the plan rather than a clock.
The 300,000 lines were made on 60 processor machines at once, checked against the writer's ink, and only then was the GPU started. Episode 3 had found a clock that cut runs short, so the time limit was raised from 150 to 220 minutes: the 12 epoch One full pass over every training line. Changing it: More epochs keep lowering the training error until it levels off. Past that point they cost time for nothing, or the model starts memorising the training lines. The best epoch is the one that scores best on held-out lines, not the last one. Here: v1's base ran 29 epochs of about 97,000 lines (about 1.5 minutes each on a T4); v2's ran 12 epochs of 290,000 lines (about 12 minutes each). Personalising stops after 6 epochs or 10 minutes. end training, not the clock.
- Training
- 30 min
- 12 of 12 epochs, about 2,000 lines/s
- GPU machine
- $0.87
- $1.39 with the 60 machines making lines
- Data check
- 0 errors
- 2 small warnings, run kept going
The two warnings, and why they didn't stop the run
36 lines too narrow per letter, of 300,000: short math like "n < ∞" in narrow fonts. Negligible.
Three fonts with four times a typical share. Only 6 of the 134 fonts draw all of β γ Σ ∑ ± ∞, so every line carrying them has to go to those few. That is the cost of writing every character, not a fault in the data; drawing rare symbols in more fonts is noted for v4.
6 Win: the base reads the writer 16 points better
Before seeing any of the writer's lines, base v3 gets 27.2% of their letters wrong, against 43.3% for base v2.
Both bases were read on the same 24 test lines (600 characters) with the same decoder. Neither has seen any of the writer's ink.
| Row | Value | Interval |
|---|---|---|
| All 24 lines, v2 | 43.3% | 38% to 49% |
| All 24 lines, v3 | 27.2% | 21% to 33% |
| French, v2 | 47.6% | |
| French, v3 | 22.7% | |
| English, v2 | 40.2% | |
| English, v3 | 27.8% | |
| Mixed, v2 | 38.0% | |
| Mixed, v3 | 26.5% | |
| Math, v2 | 49.4% | |
| Math, v3 | 36.5% |
| Measure | Base v2 | Base v3 | Change, points |
|---|---|---|---|
| CER Character error rate: substituted, deleted and inserted letters, divided by the number of letters in the right text. Changing it: The main score here; lower is better. It weighs every letter the same, and it can go over 100% when a reading has many extra letters. Here: The goal is 15% or less. v2 personalised: 13.1%. | 43.3% | 27.2% | −16.2 |
| Without the language model (greedy decoding Reading the model's output by taking the top guess at every point, then applying CTC's rules. Changing it: Fast and simple, but it can't use spelling knowledge, so it produces things like "1%e" for "the". Here: Greedy CER is the network on its own: 18.4% for v2 personalised, against 13.1% with the beam search.) | 48.7% | 32.0% | −16.7 |
| WER Word error rate: the same as CER, counted in whole words. Changing it: Harsher than CER: one wrong letter makes the whole word wrong. Closer to how a reader judges a note. Here: v2 personalised: 40.2%, against 13.1% CER. | 83.6% | 65.7% | −17.9 |
| French (7 lines) | 47.6% | 22.7% | −24.9 |
| English (7 lines) | 40.2% | 27.8% | −12.4 |
| Mixed (4 lines) | 38.0% | 26.5% | −11.6 |
| Math (6 lines) | 49.4% | 36.5% | −12.9 |
| Time within which 95% of lines are read | 49.6 ms | 51.0 ms | +1.4 ms |
In letters: 260 wrong of 600 for base v2, 163 for base v3. Paired line by line, the step is −16.2 points (−21.8 to −10.6): base v3 is better on 20 lines and worse on 3. The gain shows with and without the language model, so it is the network reading better, not the decoder guessing better.
Base v3 alone now reads the writer about as well as v1 did after personalising (24.5%). Its worst lines are still symbols and units: "60 km/h × 1,5 h = 90 km", "x² − 9 = (x − 3)(x + 3)". Speed is unchanged: the same 3 × 192 network, and 1.4 ms is about the size of this laptop's run-to-run noise.
Episode 4, for a base trained on ink that writes like the writer: base CER under 30% (from 43%), personalised CER falls with it, and the data check shows no drift warning.
Base v3: 27.2%, under 30%. Personalised v3.1-stable: 6.8%, from 10.3% (section 13). The data check gave no warning on pen lifts, points or pace; its one width warning was on 36 narrow math lines of 300,000. v3 is that base plus four other changes, so this tests the bet, not the ink fix alone. Prediction held
7 Tried and not kept: a word language model
A word-level language model was built and measured. It gained 3 or 4 letters of 600, inside the noise, so it stays out.
The decoder already uses a language model A model of which letters tend to follow which, learned from plain text. Changing it: It fixes readings the network finds ambiguous. Weighted too heavily, it overrules what you actually wrote, such as rare words and names. Here: A character model (each letter given the 4 before it) from 120,000 Tatoeba sentences per language, weighted 0.5. over letters (which letters tend to follow which) and a bonus for the writer's own words. A model over whole words would prefer "meeting" to "meeling".
- A word model of the 20,000 most used words and 100,000 word pairs, about 0.5 MB a language.
- Its weight chosen on held-out lines, then the test lines read once.
- Best result: 0.4 points better, 3 or 4 characters of 600, for about 1 MB more to download.
- The errors left are symbols, units and the writer's own words (names, jargon, "rdv"). A dictionary of common words knows none of them.
- It's worth measuring again with 100 or more test lines, or once the network makes fewer errors of its own.
Two ready-made tools were also set aside: KenLM and pyctcdecode can't run in the browser, where the model reads. Instead, a brute-force check scores every possible reading of small made-up inputs and makes sure the beam search finds the best one, in 130 cases. It found readings that could never be produced still taking up room; removing them saved 1 to 2% of decoding time.
8 Personalising: where to start, and which lines to learn from
Start from base v3, learn from both recording rounds at once, and keep a fifth of the lines aside to choose settings with.
Three models could have been the starting point:
| Start from | Test CER | Why, or why not |
|---|---|---|
| v0.2 local base | 32.8% | A small 1 × 128 network, 12 times fewer weights. It caps what can be learned, and it already saw every training line. |
| v2.1-personal | 10.3% | Today's best, but built on v2's data, and fitted on all 85 training lines: none left to choose settings with. |
| Base v3 | 27.2% | Picked. 16 points better than base v2 as a start, the same size and speed as v2.1-personal, and it has never seen a line of the writer's. |
The last point matters most. To choose settings honestly, some of the writer's lines have to be ones the model hasn't learned from.
The writer has recorded two rounds: 25 training lines and 15 test lines, then 60 and 24. Personal v3 learns from both at once: 85 training lines, one copy of each (one round had been uploaded twice).
- Lines it learns from
- 68
- the fit
- Dev lines
- 17
- 441 characters, to choose settings
- Test lines
- 24
- read once, at the very end
Three piles of lines, and why each stays in its pile
The fit is what the model trains on. Its score there is flattered: it has seen the answers.
dev set A second set of held-out lines, for choosing settings and the best epoch. Changing it: It keeps the test set untouched, so the final score isn't flattered by choices made while looking at it. Here: A fifth of the writer's training lines, held out: the decoder and the personalising settings are tuned on them, never on the test lines. are a fifth of each language's training lines, picked the same way every run. They are never trained on, so they say how a choice of settings does on new ink. Settings are chosen by them.
test set Examples kept aside and never trained on, used only to score the model. Changing it: It's the only honest measure of how a model does on new writing. Too small and the score is noisy; used for tuning, and it stops being honest. Here: 15 of your English lines today; 30 planned, in the same language mix as your notes. judge once. Choosing anything by them, even which of two models to keep, would let their luck into the score.
9 Searching the plan: 60 trials on a rented GPU
Six personalising settings searched together, each trial about a minute, all for 52 cents. Freezing layers always hurt.
Personalising has six settings, and they interact: a bigger learning rate How big a step each weight takes after every batch. Changing it: Too small and training creeps, wasting GPU time. Too big and it overshoots: the loss bounces or blows up. It's usually the first setting to tune. Here: 0.002 for the base models; 0.001 for personalising, which starts near a good solution and shouldn't be knocked away from it. may want fewer epochs. So Optuna, an open-source search library, tried them together:
| Setting | Range | v3's plan |
|---|---|---|
| Learning rate: how big each training step is | 0.0001 to 0.005 | 0.001 |
| Varied copies of each of the writer's lines | 5 to 80 | 40 |
| replayed Mixing some of the original training data back in while fine-tuning on new data. Changing it: It keeps the general skill while the model learns the new data; too much and the new data has less effect. Here: 2,000 synthetic lines alongside your lines. alongside, to keep what the base knew | 0 to 4,000 | 2,000 |
| Epochs: passes over all of it | 2 to 12 | 6 |
| Share of points whose timing is hidden (dropout Hiding part of the input or the network at random, during training only. Changing it: It stops the model leaning on any one point or feature, so it generalises better. Too much and it learns slowly. Here: The time feature (dt) is hidden on 20% of points.) | 0 to 50% | 20% |
| The base's lowest LSTM A recurrent layer: it reads a sequence one step at a time and keeps a memory (its state) of what it has seen. Changing it: Well suited to pen paths, but each step waits for the one before, which limits how much a GPU can speed it up. Here: Each layer has four gates (input, forget, cell, output) deciding what to keep and what to forget. layers kept frozen | 0 to 3 | 0 |
On this laptop a trial would take several minutes: personalising reads about 12.5 lines a second there, and can't use more than two processor cores. On an A10G the same model trains at about 1,500 lines a second, about 100 times faster. So the search ran on Modal, as the base had.
The search kept three rules:
- v3's own plan is trial 0. It read the dev lines at 13.4%, so every result is measured against it.
- Weak trials stop early: after each epoch, a trial worse than the middle of the others at that point is dropped.
- No clock cuts a trial short: each one may run an hour and needs about a minute. Not one was cut, where the v2.1 models had run 2 of their 6 epochs.
- Trials
- 60
- 29 finished, 31 stopped early
- On the A10G
- 19 min
- $0.52
- Best (trial 53)
- 10.9%
- from 13.4%: 11 letters fewer
| Row | Value | Interval |
|---|---|---|
| none, search 1 | 10.9% | |
| none, search 2 | 10.4% | |
| lowest 1, search 1 | 12.5% | |
| lowest 1, search 2 | 13.2% | |
| lowest 2, search 1 | 15.9% | |
| lowest 2, search 2 | 14.3% | |
| all 3, search 1 | 25.2% | |
| all 3, search 2 | 23.8% |
The idea behind freezing was that low layers read pen movement, which every hand shares, and only the top layers need to learn a new hand. The data says otherwise: this writer's hand needs the low layers too.
Episode 5: the best trial beats today's plan by more than 3 characters on the dev lines. Learning rate and epochs matter more than copies and replay. Freezing the lowest layer helps or changes nothing; freezing all of them hurts.
The bests beat the plan by 11 and 13 letters, and freezing all three hurt badly. But the copies setting mattered most in the first search, and freezing even one layer hurt in both. Prediction failed
10 A best at the edge of the map: search wider
The first winner sat at the limit of two ranges, so a second search widened them. Its best landed in the middle.
The first search's best used 2 epochs, the fewest it allowed, and a learning rate 95% of the way to its ceiling. A best at the edge of a range says the real best may lie past it.
So a second search ran with room on both sides: down to 1 epoch, and learning rates up to 0.01. It was a study of its own, because the search method learns only from trials that shared the same ranges.
| Setting | v3's plan | Search 1: short | Search 2: long |
|---|---|---|---|
| Dev CER | 13.4% | 10.9% | 10.4% |
| Learning rate | 0.001 | 0.0042 | 0.0017 |
| Copies per line | 40 | 19 | 70 |
| Synthetic lines replayed | 2,000 | 1,370 | 361 |
| Epochs | 6 | 2 | 8 |
| Timing hidden | 20% | 1% | 14% |
| Layers frozen | 0 | 0 | 0 |
| Search time and cost | – | 19 min, $0.52 | 31 min, $0.84 |
The second best was not past the old edges but in the middle of the new ranges: 8 epochs at a moderate rate, with its runners-up close by (11.3% to 12.0%). Two different recipes, one short and hot, one long and gentle, 2.5 and 3 points better than v3's plan.
| Setting | Search 1 | Search 2 |
|---|---|---|
| Copies per line | 46% | 16% |
| Epochs | 21% | 15% |
| Learning rate | 20% | 34% |
| Timing hidden | 9% | 4% |
| Synthetic lines replayed | 3% | 25% |
| Layers frozen | 0% | 6% |
The ranking changed between the searches. Importance belongs to the ranges searched as much as to the setting: widen the learning rate's range, and it has more room to make a difference.
The two bests differ by 2 letters of 441, inside the noise. A rule was written before measuring: within 3 letters, keep the plan that catastrophic forgetting When a model trained on new data loses what it knew before. Changing it: A personalised model could read your hand and nothing else. Mixing old data back in (replay) and a small learning rate prevent it. Here: Checked every personalisation: error on synthetic lines stays flat. less of what the base knew.
Measured on 2,000 synthetic lines neither had seen, base v3 read 1.4%, the short plan 3.6% and the long plan 4.2%. The short plan won the tie, and became the recipe for personal v3.1.
11 Failure: the 10.9% that didn't come back
Made again from its recipe, the short plan scored 15.6%. One run per plan can't rank plans.
Personal v3.1 was made from its recipe: same plan, same lines, same dev lines. It scored 15.6% on the dev lines, not 10.9%: 21 letters worse, with nothing changed.
Training on a GPU isn't exactly repeatable. The loss this model learns from adds up many small numbers in parallel, and the order changes from run to run. Rounding differs in the last digit, and training amplifies it from the first epoch. A short plan at a high learning rate amplifies it most.
Why the best of 120 trials is the one to trust least
Every trial's score is part plan, part luck. A search keeps the lowest score, so it favours plans that are good, and also runs that were lucky.
The more trials, and the noisier each one, the more the winner owes to luck: the winner's curse. On 17 dev lines, where a letter is 0.2%, there is plenty of noise to be lucky with.
So a search's best is a shortlist, not a verdict. The fix is to run the finalists again, with different seed The starting number for every random choice: which font, which word, how much slant. Changing it: Same recipe and same seed give the same data, so a version can be rebuilt. Different seeds show how much a result is luck. Here: Pinned in each recipe., and compare what they do on average.
12 Five seeds each: judge a plan by its mean and its spread
Over five runs each, the long plan wins: 11.4% on average, against 12.8% for v3's plan and 13.9% for the short one.
A seed now picks the varied copies and seeds training, so a plan can be run several times on purpose. The two finalists and v3's own plan, as the reference, were each trained with four more seeds, one call at a time.
| Row | Value | Interval |
|---|---|---|
| Long plan (5 runs) | 11.4% | 10% to 13% |
| v3's plan (4 runs) | 12.8% | 12% to 13% |
| Short plan (6 runs) | 13.9% | 11% to 16% |
| Plan | Runs, by seed | Mean | Spread (sd) |
|---|---|---|---|
| Long plan | 0: 10.4% · 1: 10.9% · 2: 10.7% · 3: 12.2% · 4: 12.7% | 11.4% | 1.0 points |
| v3's plan | 1: 12.0% · 2: 13.4% · 3: 12.9% · 4: 12.9% | 12.8% | 0.6 points |
| Short plan | 0: 10.9% · 0, again: 15.6% · 1: 15.6% · 2: 14.3% · 3: 12.9% · 4: 14.3% | 13.9% | 1.8 points |
The long plan is ahead of v3's plan by 1.4 points on average, and of the short plan by 2.6. Measured against the spread, those gaps are 2.7 and 2.9 times their own uncertainty: unlikely to be luck, though five runs is still a small sample.
Mean, spread and t, in plain words
The mean is what a plan does on a typical run. The spread (standard deviation) is how far one run usually lands from it: about 1 point for the long plan, almost 2 for the short one.
To compare two means, divide the gap by how uncertain it is, which depends on both spreads and how many runs there were (Welch's t). Near 1, the gap could easily be luck; past 2, it rarely is.
v3's own plan is the steadiest of the three (spread 0.6), just not the best. A plan that is reliably good can beat one that is sometimes brilliant.
The long plan, with seed 0, became the release v3.1-stable (every run of it, epoch by epoch, is in episode 8). Made from its recipe, it read the dev lines at 10.43%, exactly its earlier run: this time the run repeated.
| Value | v3's plan | v3.1-stable | Set by |
|---|---|---|---|
| Starting point | base v3 | base v3 | a test score, and no lines seen |
| Lines learned from | one round | both rounds: 68, 17 held out | more of the writer's lines is the largest lever |
| Learning rate | 0.001 | 0.0017 | two searches, five seeds |
| Copies per line | 40 | 70 | two searches, five seeds |
| Synthetic lines replayed | 2,000 | 361 | two searches, five seeds |
| Epochs | 6 | 8 | two searches, five seeds |
| Timing hidden | 20% | 14% | two searches, five seeds |
| Layers frozen | 0 | 0 | every frozen layer hurt |
| Time limit | 10 min | 60 min, never reached | the epochs end training (about a minute) |
| Seed | – | 0 | the plan's best of five |
- Personalising calls to Modal
- 20
- searches, finalists, repeats, releases
- All of them
- $1.99
- one model: about 3 cents
- On this laptop
- 12 h +
- for one thorough search
13 Win: 6.8% on the test lines
Personal v3.1-stable gets 41 of 600 test letters wrong, a third fewer than v2.1-personal, from fewer of the writer's lines, and inside the speed budget.
The last steps kept their order: tune the decoder on the dev lines (section 14), then read the 24 test lines once. They are the same lines v2.1-personal was judged on.
- Character error rate
- 6.8%
- 95% interval 4.6% to 9.2%
- Word error rate
- 23.6%
- from 32.9% (v2.1-personal)
- 95% of lines read within
- 45.1 ms
- budget 50 ms: passed
| Row | Value | Interval |
|---|---|---|
| v2 base | 43.3% | 38% to 49% |
| v3 base | 27.2% | 21% to 33% |
| v2.1-personal | 10.3% | 7% to 14% |
| v3.1-stable | 6.8% | 5% to 9% |
Paired line by line against v2.1-personal, the step is −3.5 points (−6.0 to −1.3). v3.1-stable is better on 13 lines, worse on 3, and the interval stays clear of zero.
| Language | Lines | v2.1-personal | v3.1-stable | No language model |
|---|---|---|---|---|
| French | 7 | 9.7% | 5.9% | 8.6% |
| English | 7 | 9.1% | 5.7% | 7.7% |
| Mixed | 4 | 9.9% | 8.3% | 9.1% |
| Math | 6 | 15.3% | 9.4% | 8.2% |
Every language improves. Math reads better without the language model (8.2% against 9.4%): that model learned which letters follow which from ordinary text, so on a formula it pushes the reading toward words. A math-aware language model, or none for math lines, is a cheap thing to try next.
On all 39 test lines of both rounds (1,036 characters), v3.1-stable reads 8.6% (5.3% to 13.4%), with a word error rate of 26.4%. The first round's 15 English lines are the harder ones: English reads 9.5% with them, against 5.7% without.
The model is exported for the browser (8.6 MB, the same reading as the training code on 10 of 10 check lines) and is now the one the app reads with.
14 Not kept: decoder settings tuned on the dev lines
Tuning the decoder won 4 letters on the dev lines and lost 6 on the test lines, so the defaults stay.
As in episode 5, the decoder's settings were searched on the dev lines: 100 trials, keeping the fastest settings within half a point of the most accurate. The rule set beforehand: keep tuned settings only if they also win on the test lines.
| Settings | Dev lines | Test lines | 95% of lines within, three alternating runs |
|---|---|---|---|
| Default (kept) | 10.2% | 6.8% | 47.3 ms · 45.6 ms · 46.1 ms |
| Tuned | 9.3% | 7.8% | 52.2 ms · 41.7 ms · 41.7 ms |
The tuned settings gained 4 letters on the 441 dev characters and lost 6 on the 600 test characters. A gain of four letters is inside the noise of 17 lines, and settings chosen on those lines fit their quirks. The defaults stay, and they also stayed within the 50 ms budget on every timing run, where the tuned ones went over once.
15 What carries over
Seven habits for anyone choosing values for a small model on little data.
- Measure what the model actually learns from. Here, letters and letter pairs, not words: by that measure, half the planned word bank bought almost all of the benefit.
- Benchmark before renting. For a small model the fastest GPU is rarely the cheapest epoch, and a default "whatever is free" choice can cost a quarter more for half the speed.
- Know how big a difference is in units you can picture. One letter of 441 is 0.2%: two settings half a point apart are two letters apart.
- When a search's best sits at the edge of a range, widen the range before believing it.
- Treat a search's winner as a shortlist. Rerun the finalists with several seeds and pick by the mean and the spread, not by the best run.
- A gain on a small dev set is a hypothesis. Four letters better on the dev lines turned into six worse on the test lines.
- Write each decision down as a recipe, with its reasons and every run behind it. A recipe is never edited once it has made a model: when the luck in v3.1 showed, a new recipe replaced it.