Talk to Me
Back to Blog

The Role of Model Selection

A detailed project write-up on cleaning noisy OCR text, detecting data leakage, comparing BERT and TF-IDF SVM models, validating on external data, and implementing Pegasos-style SVM optimization.

S

Shahan Ahmed

June 22, 2026·17 min read

Method guide

Short explanation of the algorithms

TF-IDF

TF-IDF converts OCR text into weighted word and phrase features. It gives more importance to terms that are frequent in one document but not common across all documents.

Linear SVM

SVM is a margin-based classifier. It tries to separate target and non-target documents with the widest possible boundary using hinge loss.

BERT

BERT is a transformer language model that reads text in context. It performed strongly, but long OCR documents created a token-length limitation.

Pegasos

Pegasos is a stochastic optimization method for training SVMs. It updates the model using margin-violating examples and a decaying learning rate.

This project started with a practical classification problem: given OCR text extracted from scanned documents, can a machine learning model identify whether a document belongs to a specific target document class?

The data came from OCR outputs, which means the text was not clean. It contained scanner artifacts, broken words, page headers, fax metadata, timestamps, provider names, repeated fragments, inconsistent spacing, and formatting noise. The goal was not simply to build a model that performed well on one train/test split. The real goal was to build a model that could generalize across newly collected OCR data.

The modeling process evolved in six stages:

  1. Fine-tuning a BERT-based classifier.
  2. Building a TF-IDF + Linear SVM model.
  3. Finding and fixing a major data leakage issue caused by empty text values.
  4. Validating the SVM on new external OCR data.
  5. Implementing Pegasos-style SVM training to understand stochastic SVM optimization.
  6. Comparing production SVM performance against manual Pegasos experiments.

The most important lesson from the project was that model accuracy alone is not enough. Data quality, leakage detection, external validation, and error analysis were just as important as model selection.

The starting point: OCR text classification

The classification task was binary.

LabelMeaning
1Target document class
0Non-target document class

The input was OCR text. Each row represented one document. The model learned from text features and predicted whether the document belonged to the target class.

At a high level, the pipeline looked like this:

OCR document text ↓ Text cleaning and normalization ↓ Feature representation ↓ Binary classifier ↓ Prediction: target or non-target

Because OCR text can be long and messy, I tested both deep learning and classical machine learning approaches.

Stage 1: fine-tuning BERT

The first modeling approach used BERT for sequence classification. The text was tokenized using a BERT tokenizer, then passed into a BERT classification head with two output labels.

The first BERT model used a maximum token length of 256. This means the model only saw the first 256 tokens of each OCR document. Training used weighted cross-entropy loss to address class balance.

The BERT 256-token model performed strongly.

MetricValue
Accuracy0.9951
Macro F10.9951
Target-class precision0.9946
Target-class recall0.9952
Target-class F10.9949
False positives10
False negatives9
Total errors19

This was already strong performance. However, error analysis showed an important limitation: some false negatives were not borderline cases. The model was very confident they were non-target documents even though they were target documents.

That led to an important observation: the evidence needed for classification sometimes appeared after the first 256 tokens. Since the model only saw the beginning of the OCR text, it could miss the most useful parts of the document.

To test this, I retrained BERT with a maximum token length of 512.

MetricValue
Accuracy0.9959
Macro F10.9958
Target-class precision0.9957
Target-class recall0.9957
Target-class F10.9957
Estimated total errorsabout 16

The 512-token model performed better than the 256-token version, but the core limitation remained: BERT still has a fixed input length. Long OCR documents can contain important signals beyond the model’s visible window.

This pushed the project toward classical text classification.

Stage 2: moving to TF-IDF + Linear SVM

The next model used TF-IDF features with a Linear SVM. This approach is much simpler than BERT, but it has several practical advantages for OCR classification:

  • It can use the full OCR text rather than only the first 256 or 512 tokens.
  • It works very well with sparse text signals.
  • It is fast to train and easy to inspect.
  • It is strong for document classification when classes have repeated phrases, templates, or domain-specific vocabulary.

The vectorizer used word n-grams from one to three words, sublinear term frequency, and a large feature space.

TfidfVectorizer( lowercase=True, ngram_range=(1, 3), min_df=3, max_df=0.95, max_features=500000, sublinear_tf=True )

The classifier used stochastic hinge-loss optimization.

SGDClassifier( loss="hinge", penalty="l2", alpha=1e-5, class_weight="balanced" )

This is effectively a large-scale linear SVM trained with stochastic optimization. The loss="hinge" setting makes it an SVM-style classifier, while penalty="l2" controls the weight size through regularization.

The regularization parameter was alpha = 1e-5. Conceptually, this plays a similar role to the lambda parameter in Pegasos-style SVM optimization. A smaller value allows the model to fit the data more closely, while a larger value forces stronger regularization.

The first SVM experiment looked extremely strong, but that result turned out to be misleading.

The data leakage discovery

At one point, the SVM produced an almost perfect result with only one error. That looked impressive, but it was suspicious.

A deeper diagnostic check revealed the issue: many rows in the non-target class had empty text in the column being used for training.

The duplicate hash check exposed the problem. The MD5 hash for the empty string appeared repeatedly:

d41d8cd98f00b204e9800998ecf8427e

This hash corresponds to an empty string. The analysis showed that a very large number of non-target rows had empty values in the text column, while the actual OCR text existed in another column.

This created a data leakage problem. The model could learn a dataset artifact instead of the real document signal:

empty text → non-target class non-empty text → target class

That is not real classification. It is learning a shortcut. The inflated result had to be rejected.

This was one of the most important lessons in the project: a model can achieve excellent metrics for the wrong reason.

Fixing the dataset

The dataset was repaired by checking both available text columns. If the main text column was empty, the pipeline used the backup OCR text column.

The repair logic was:

if text is not empty: use text else: use OCR text column

After fixing the text field, the dataset looked much more valid.

StepRows
Original corrected dataset104,093
After exact duplicate removal103,847

After deduplication, the corrected dataset contained:

ClassCountPercent
Target class52,37450.43%
Non-target class51,47349.57%
Total103,847100%

A conflict check was also performed to make sure the same exact OCR text did not appear with both labels. No conflicting duplicate labels were found.

This step made the later model results much more defensible.

Stage 3: SVM on the corrected dataset

After fixing the empty-text issue and removing exact duplicates, I trained the TF-IDF + Linear SVM again.

MetricValue
Test size20,770
Accuracy0.9992
Macro F10.9992
Target-class precision0.9996
Target-class recall0.9989
Target-class F10.9992
False positives4
False negatives12
Total errors16

The confusion matrix was:

Predicted non-targetPredicted target
True non-target10,2914
True target1210,463

This result was still extremely strong, but unlike the earlier inflated result, it was now based on a repaired and deduplicated dataset.

Stage 4: external positive-class recall test

After the corrected SVM performed well internally, the next question was whether it could generalize to newly collected OCR data.

A separate dataset containing 34,394 new target-class OCR records was prepared. This dataset had a nested OCR structure where text was stored inside a pages field. The cleaning pipeline extracted page-level text and combined it into document-level text.

The preparation results were:

StepCount
Raw new OCR records34,394
Duplicate records removed91
Unique records retained34,303
Empty-text rows0

This dataset contained only the positive class, so it could not be used to measure full accuracy or precision. Instead, it was used as an external recall test.

The existing SVM trained on the corrected 100K dataset was tested on the 34,303 new positive documents.

MetricValue
External positive-class rows34,303
Correctly detected target documents34,236
Missed target documents67
Target-class recall0.9980
Target-class F10.9990

This was an important validation step. The model had never trained on this new dataset, yet it detected 99.80% of the target documents.

This suggested that the SVM was not simply memorizing the corrected training set. It generalized well to newly collected OCR data.

Stage 5: building the combined 138K dataset

After the external test, the new 34,303 positive examples were merged into the corrected 100K dataset.

The final combined dataset contained:

ClassCountPercent
Target class86,67762.74%
Non-target class51,47337.26%
Total138,150100%

Because the combined dataset became more positive-heavy, the SVM retained class_weight="balanced". This prevented the model from over-favoring the larger target class.

The combined dataset was shuffled and split into train/test sets:

SplitCount
Training set110,520
Test set27,630

The SVM trained on the combined 138K dataset achieved:

MetricValue
Accuracy0.9993
Macro F10.9992
Target-class precision0.9995
Target-class recall0.9994
Target-class F10.9994
Non-target precision0.9989
Non-target recall0.9991
False positives9
False negatives11
Total errors20

The confusion matrix was:

Predicted non-targetPredicted target
True non-target10,2869
True target1117,324

This became the strongest production model result.

Why the SVM worked so well

The SVM performed extremely well because this classification problem had strong lexical and structural signals.

OCR documents often contain repeated domain-specific patterns, such as:

  • clinical complaint sections
  • history sections
  • diagnosis sections
  • assessment language
  • provider or visit descriptions
  • injury and treatment terminology
  • repeated document templates
  • class-specific phrase patterns

TF-IDF is very effective when the presence, absence, and frequency of terms or phrases carry strong classification information.

The SVM also had an advantage over BERT: it could use the full document text. BERT was limited by token length, while TF-IDF could represent signals appearing anywhere in the OCR text.

Stage 6: testing Pegasos-style SVM

After building the production SVM, I implemented a manual Pegasos-style SVM to better understand how stochastic SVM optimization works.

Pegasos is a primal SVM optimization method that uses stochastic sub-gradient updates. The core objective is:

minwλ2w2+1ni=1nmax(0,1yif(xi))\min_{\mathbf{w}} \frac{\lambda}{2} \lVert\mathbf{w}\rVert^2 + \frac{1}{n} \sum_{i=1}^{n} \max(0, 1 - y_i f(x_i))

The hinge-loss condition is based on the margin:

margin=yf(x)=y(wTx)\text{margin} = y f(x) = y(\mathbf{w}^{T}\mathbf{x})

If the margin satisfies

margin1\text{margin} \geq 1

the example is correctly classified with enough margin. If

margin<1\text{margin} < 1

the example violates the margin and contributes to the update.

The learning rate in Pegasos changes over time:

ηt=1λt\eta_t = \frac{1}{\lambda t}

SymbolMeaning
ηt\eta_t (eta sub t)learning rate at step tt
λ\lambda (lambda)regularization strength
ttupdate step

In the full Pegasos experiment, the settings were:

ParameterValue
λ\lambda (lambda)10510^{-5}
Epochs5
Batch size512
Max TF-IDF features300,000
N-gram range1 to 3
Class weightsYes

The learning rate starts large and decreases as training progresses. Early in training, the model makes larger corrections. Later, as t increases, the learning rate becomes smaller, allowing the model to stabilize.

Pegasos on a 30K sample

The first Pegasos experiment used a balanced 30,000-document sample. This was useful for learning and debugging.

ClassCount
Target class15,000
Non-target class15,000

The test set contained 6,000 documents.

MetricValue
Accuracy0.9940
Macro F10.9940
Target-class precision0.9924
Target-class recall0.9957
Target-class F10.9940
False positives23
False negatives13
Total errors36

The most useful training signal was the decline in margin violations:

EpochAverage margin violations per batch
1128.88
2128.19
3124.90
455.71
51.73

This showed that Pegasos was learning. By the final epoch, very few examples were violating the margin.

Pegasos on the full 138K dataset

After confirming the algorithm worked on a sample, I trained Pegasos on the full combined dataset.

MetricValue
Dataset size138,150
Train size110,520
Test size27,630
Accuracy0.9980
Macro F10.9979
Target-class precision0.9994
Target-class recall0.9975
Target-class F10.9984
Non-target precision0.9957
Non-target recall0.9990
False positives10
False negatives44
Total errors54

The confusion matrix was:

Predicted non-targetPredicted target
True non-target10,28510
True target4417,291

The margin violations dropped sharply:

EpochAverage margin violations per batch
1258.38
2156.99
33.21
42.37
52.24

This confirmed that the Pegasos-style optimizer was working well.

However, compared with the production SVM, Pegasos had more false negatives:

ModelFalse positivesFalse negativesTotal errors
Production TF-IDF + Linear SVM91120
Manual Pegasos SVM104454

If the priority is to avoid missing target documents, the production SVM remains better.

Pegasos on a separate external dataset

The full Pegasos model was also tested on a separate external dataset with 19,286 documents.

ClassCount
Target class9,303
Non-target class9,983
Total19,286

The result was:

MetricValue
Accuracy0.9824
Macro F10.9824
Target-class precision0.9839
Target-class recall0.9796
Target-class F10.9817
Non-target precision0.9810
Non-target recall0.9851
False positives149
False negatives190
Total errors339

The confusion matrix was:

Predicted non-targetPredicted target
True non-target9,834149
True target1909,113

This external performance was lower than the internal test performance. That is expected when the new dataset has different OCR quality, formatting, templates, or document distributions.

This showed the value of external validation. Internal test performance can be very high, but external data reveals how much the model depends on the original data distribution.

Model comparison summary

Model / ExperimentTest typeTest sizeAccuracyMacro F1Target recallFPFNTotal errors
BERT, 256-token limitInternal3,8580.99510.99510.995210919
BERT, 512-token limitInternalabout 3,8580.99590.99580.9957about 16
TF-IDF + Linear SVMCorrected 100K internal20,7700.99920.99920.998941216
TF-IDF + Linear SVMExternal positive-only recall34,3030.9980Not comparable0.9980N/A6767
TF-IDF + Linear SVMCombined 138K internal27,6300.99930.99920.999491120
Pegasos SVM30K balanced sample6,0000.99400.99400.9957231336
Pegasos SVMFull 138K internal27,6300.99800.99790.9975104454
Pegasos SVMExternal 19K dataset19,2860.98240.98240.9796149190339

Key observations

1. BERT worked well, but token length was a limitation

BERT performed strongly, but OCR documents are often long. When useful evidence appeared after the token limit, the model could miss it. Increasing the maximum length from 256 to 512 improved results, but did not fully remove the limitation.

2. TF-IDF + Linear SVM was surprisingly strong

The classical SVM outperformed BERT in this case. This happened because the task had strong document-level lexical signals, and TF-IDF could represent the full OCR text rather than only the beginning of the document.

3. Data leakage almost produced a misleading conclusion

The most important data issue was the discovery that many non-target rows had empty text in the modeling column. If this had not been detected, the model would have appeared almost perfect for the wrong reason.

The fix required:

  • checking text length by label
  • finding repeated empty-string hashes
  • repairing empty text using the correct OCR text column
  • removing exact duplicates
  • checking for conflicting labels

This turned the experiment from a misleading result into a defensible pipeline.

4. External validation was essential

The external positive-only recall test showed that the production SVM could generalize to newly collected OCR data. This was stronger evidence than internal accuracy alone.

5. Pegasos was useful for learning optimization

The manual Pegasos implementation helped explain how SVM optimization works:

  • hinge loss
  • margin violations
  • L2 regularization
  • lambda
  • decaying learning rate
  • stochastic updates
  • projection step

Pegasos performed well, but the production SVM remained stronger.

6. External datasets revealed domain shift

The full Pegasos model performed very well internally but dropped on a different external dataset. This suggests that OCR quality, provider templates, document formatting, or labeling differences matter.

Final production choice

The best production model was the TF-IDF + Linear SVM trained on the combined 138K dataset.

Its strengths were:

  • highest overall accuracy
  • lowest total error count
  • very low false negatives
  • fast training time
  • full-document OCR representation
  • strong external recall performance
  • easy reproducibility

The final production result was:

Final production metricValue
Accuracy99.93%
Target-class recall99.94%
False positives9
False negatives11
Total errors20 out of 27,630

For this task, the classical SVM was not just a baseline. It became the strongest model.

Conclusion

This project started with a modern deep learning model and ended with a classical linear model as the best practical solution.

BERT was useful and performed well, but the TF-IDF + Linear SVM better matched the structure of the problem. OCR documents contained repeated terms, templates, and document-level signals that sparse text features could capture very effectively.

The most important part of the project was not simply comparing algorithms. It was the process of making the data trustworthy. The discovery of empty text values in one class showed how easily a model can exploit artifacts instead of learning the real task.

After repairing the dataset, removing duplicates, validating externally, and comparing SVM with Pegasos-style optimization, the final conclusion was clear: a carefully cleaned dataset plus a well-tuned linear SVM produced the most accurate and reliable classifier for this OCR document classification task.

The overall lesson is simple:

In applied machine learning, the best model is not always the most complex model. The best model is the one that learns the real signal, survives external validation, and remains reliable after data quality problems are fixed.

More writing

Read more notes from Shahan Ahmed

Machine learning, data systems, healthcare analytics, and applied research notes.

All posts