This page shows the chapter notebook with its saved results. Constructed examples do not measure competition gains.
Chapter 49: Text Feature Engineering
Try the decision first
At what typo rate, and at what term coverage, does a character TF-IDF model overtake a word model?
Write your expectation before opening the worked results. Use the chapter activity to compare supported inputs. To change the calculation itself, run the notebook on GitHub.
Your answers stay on this page until you download them. They are not sent to a server.
Worked notebook and saved results
At what typo rate, and at what term coverage, does a character TF-IDF model overtake a word model?
Step Zero of the chapter is to measure coverage before choosing a representation, with the warning that no universal overlap threshold decides it. This demonstration finds where the switch falls in one controlled setting and shows that it depends on the metric.
Constructed text and typos injected only at assessment time; the sizes of these effects are properties of this generator, not a competition result.
The experiment
Constructed product-style titles of 7 invented words: four classes, each with 40 class-specific words (30% of the words in its titles) and a shared Zipf-weighted background of 200 words. Models are trained on 800 clean titles and scored on 800 more in which each word is misspelled (substitution, deletion or swap) with the control's probability. Three logistic regressions use TF-IDF with sublinear tf, fitted on training text only: words, characters (analyzer 'char', n-grams 3 to 5, the chapter's baseline) and the two matrices side by side. Coverage is the share of assessment tokens, repeats included, that appear in the training text. All numbers are means over 8 corpora.
Calculation code
import numpy as np
from scipy.sparse import hstack
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import log_loss
from kaggle_companion.activities._common import clean
SEED = 49
DRAWS = 8 # independent corpora; every estimate is a mean over draws
N_DOCS, N_CLASSES, TITLE_WORDS = 800, 4, 7
LETTERS = np.array(list("abcdefghijklmnopqrstuvwxyz"))
def invent_words(rng, n):
"""Pronounceable-enough made-up words of 5 to 9 letters (no real language, so nothing is memorised from outside)."""
return ["".join(rng.choice(LETTERS, rng.integers(5, 10))) for _ in range(n)]
def misspell(word, rng):
"""One typo: substitute a letter, drop a letter or swap two neighbours."""
i, kind = rng.integers(len(word)), rng.integers(3)
if kind == 0:
return word[:i] + str(rng.choice(LETTERS)) + word[i + 1:]
if kind == 1 and len(word) > 3:
return word[:i] + word[i + 1:]
return word[:i] + word[i + 1] + word[i] + word[i + 2:] if i < len(word) - 1 else word
def titles(n, vocabulary, rng, typo_rate=0.0):
"""Product-style titles: 30% of words come from the class's own list, the rest from a shared Zipf-weighted background."""
own, background = vocabulary
weights = 1 / np.arange(1, len(background) + 1)
weights /= weights.sum()
labels = rng.integers(0, N_CLASSES, n)
docs = []
for c in labels:
words = [own[c][rng.integers(len(own[c]))] if rng.random() < 0.3 else background[rng.choice(len(background), p=weights)]
for _ in range(TITLE_WORDS)]
docs.append(" ".join(misspell(w, rng) if rng.random() < typo_rate else w for w in words))
return docs, labels
def coverage(train_docs, test_docs):
"""Step Zero: the share of assessment tokens (counted with repeats, so weighted by frequency) seen in training."""
seen = set(" ".join(train_docs).split())
tokens = " ".join(test_docs).split()
return float(np.mean([t in seen for t in tokens]))
def run(typo_rate):
rng = np.random.default_rng(SEED)
names = ("word", "char", "both")
rows = {k: {"log_loss": [], "accuracy": []} for k in names}
cover = []
for _ in range(DRAWS):
vocabulary = ([invent_words(rng, 40) for _ in range(N_CLASSES)], invent_words(rng, 200))
train, y_train = titles(N_DOCS, vocabulary, rng) # clean training text
test, y_test = titles(N_DOCS, vocabulary, rng, typo_rate) # typos appear only here
cover.append(coverage(train, test))
word = TfidfVectorizer(analyzer="word", sublinear_tf=True) # vocabularies are fitted on training text only
char = TfidfVectorizer(analyzer="char", ngram_range=(3, 5), sublinear_tf=True)
Wtr, Ctr, Wte, Cte = word.fit_transform(train), char.fit_transform(train), word.transform(test), char.transform(test)
for name, A, B in (("word", Wtr, Wte), ("char", Ctr, Cte), ("both", hstack([Wtr, Ctr]).tocsr(), hstack([Wte, Cte]).tocsr())):
p = LogisticRegression(C=10, max_iter=500).fit(A, y_train).predict_proba(B)
rows[name]["log_loss"].append(log_loss(y_test, p, labels=range(N_CLASSES)))
rows[name]["accuracy"].append((p.argmax(1) == y_test).mean())
advantage = np.array(rows["word"]["log_loss"]) - np.array(rows["char"]["log_loss"]) # positive: characters are better
return clean({
"typo_rate": typo_rate, "coverage": np.mean(cover),
**{k: {"log_loss": np.mean(v["log_loss"]), "accuracy": np.mean(v["accuracy"])} for k, v in rows.items()},
"char_advantage": advantage.mean(), "char_advantage_se": advantage.std(ddof=1) / np.sqrt(DRAWS),
"char_win_rate": float((advantage > 0).mean()),
"per_draw": {k: v["log_loss"] for k, v in rows.items()},
"documents": N_DOCS, "draws": DRAWS,
})
Measure it across the control
The control is share of assessment words that are misspelled. Each value below runs the whole experiment from the generator up.
Calculation code
import pandas as pd
from kaggle_companion.activities.ch49 import explain
VALUES = [0, 0.1, 0.2, 0.4]
results = {value: run(value) for value in VALUES}
table = pd.DataFrame({str(v): explain(results[v], v)['metrics'] for v in VALUES})
print(table.to_string())0 0.1 0.2 0.4 Token coverage 0.999 0.905 0.814 0.630 Word log loss 0.348 0.389 0.434 0.537 Character log loss 0.399 0.413 0.425 0.454 Combined log loss 0.334 0.360 0.389 0.450 Word accuracy 0.900 0.883 0.864 0.811 Character accuracy 0.898 0.896 0.890 0.887
Look at it
The figure shows the default setting, share of assessment words that are misspelled = 0.2.
Calculation code
import matplotlib.pyplot as plt
from kaggle_companion.activities.ch49 import draw, NCOLS, HEIGHT
DEFAULT = 0.2
fig, axes = plt.subplots(1, NCOLS, figsize=(6.6 if NCOLS == 1 else 10.4, HEIGHT), layout='constrained')
draw(axes, results[DEFAULT], DEFAULT)
plt.show()What the result says
With 20% of assessment words misspelled, 0.814 of assessment tokens are in the training vocabulary. Word log loss is 0.434 and character log loss 0.425: 0.434 - 0.425 = 0.009 (standard error 0.004). The character model is ahead on log loss by a small margin, between two and three standard errors: this is near the switch. Accuracy is 0.864 for words and 0.890 for characters; the combined model has log loss 0.389 and accuracy 0.879.
- Coverage of assessment tokens: 0.814.
- Log loss, word minus character: 0.434 - 0.425 = +0.009 (positive means characters are better).
- Accuracy, character minus word: 0.890 - 0.864 = +0.026.
- Combined model against the better single model: 0.389 - 0.425 = -0.037 log loss.
Apply this to your competition
- Compute frequency-weighted coverage of the assessment text against the training vocabulary before choosing a representation.
- Compare word, character and combined TF-IDF with a regularised linear model on identical rows, fitted on training text only.
- Judge the comparison by the scored metric: log loss and accuracy can pick different models near the switch.
- Keep the sparse baseline when adding embeddings, and re-check the comparison on text noised the way the test set is.
Book location: Chapter 49, A Small Sparse Comparator.
Apply a lesson from this chapter
Select a lesson whose conditions match your project. State how you would test it and what would make you reject it.
Filter accession identifiers by family and literal article presence
Conditions
- reference extraction corpora include noisy identifier families
Action
- Proposed adaptation of source observation: Apply family-specific acceptance rules and verify extracted identifiers literally occur in article text.
Check
- Proposed verification: Measure precision/recall by repository and inspect rejected valid IDs.
Limits
- Prefix lists and case policies are corpus-specific and may reduce recall for new families.
Keep text and structured features sparse for regularized regression
Conditions
- high-dimensional bag-of-words features accompany structured predictors
Action
- Proposed: bind sparse structured design matrices and text matrices for ridge regression.
Check
- Proposed: verify matrix row identities before binding and evaluate regularization on a forward split; compare sparse memory with an equivalent dense representation.
Limits
- No memory saving or isolated gain is reported; cv.glmnet uses five folds without shown temporal fold assignments, and inference explicitly selects s=0.001 rather than a shown selected lambda.
Constrain neural text output with a training lexicon
Conditions
- diacritization predictions correspond to known undiacritized words
Action
- Proposed: choose a nearest lexicon variant, break distance ties by training frequency, and preserve predicted word-ending diacritics.
Check
- Proposed: compare neural-only and lexicon-corrected error, separating internal and word-ending diacritics; sort tied candidate variants before seeded random selection, or use deterministic tie selection, and check repeated-process outputs.
Limits
- The nearest-word distance removes only the last character before comparing. Tied candidates inherit unordered set iteration, so a random seed alone does not make cross-process selection reproducible. The lexicon must use permitted training text.
Constrain type outputs and evaluate metadata enrichment separately
Conditions
- document reference type has two valid outputs and repeated family mentions
Action
- Proposed adaptation of source observation: Compare constrained context classification, per-family consistency and targeted metadata enrichment separately.
Check
- Proposed verification: Check classification confusion by family and mixed primary/secondary references in one article.
Limits
- Majority rules may erase genuine mixed use; richer metadata did not improve the reported private result.
Build query-result alignment features before a custom metric objective
Conditions
- search relevance depends on overlap and semantic alignment
Action
- Proposed adaptation of source observation: Compare explicit query-result similarity features with a regression baseline before implementing a custom metric objective.
Check
- Proposed verification: Ablate alignment features and verify custom gradient/Hessian behavior against numerical checks.
Limits
- No isolated feature gains are supplied; the custom-objective failure is local, not universal.
Choose text ensemble members by complementarity
Conditions
- multiple high-scoring text models have similar errors
Action
- Proposed adaptation of source observation: Compare raw and alternate preprocessing models by incremental blend contribution, not standalone rank alone.
Check
- Proposed verification: Measure error overlap and held-out blended metric.
Limits
- The report gives no numeric isolated gain and is specific to Russian moderation data.
Audit semantic loss from aggressive text cleanup
Conditions
- forum classification preprocessing may remove class-bearing syntax
Action
- Proposed adaptation of source observation: Compare raw text with each cleanup group and inspect zero-vector unknown words before retaining aggressive normalization.
Check
- Proposed verification: Measure class-wise accuracy and inspect deleted numbers/code/emojis.
Limits
- The source reports the recipe without cleanup ablations; improvement cannot be attributed to preprocessing.
Align salary training loss with MAE before complex feature additions
Conditions
- text-heavy salary regression is scored by absolute error
Action
- Proposed adaptation of source observation: Compare separate field vectorization and direct MAE training with existing loss/feature alternatives.
Check
- Proposed verification: Evaluate original salary MAE and ablate features under identical splits.
Limits
- The hypothesis that networks learned missing features is unproven; local failed features may help other architectures.
Retain stopwords in contextual sentiment preprocessing
Conditions
- function words carry negation and semantic structure
Action
- Proposed conditional application: Compare stopword retention with removal rather than applying generic text-cleaning defaults.
Check
- Proposed check: Compare retention/removal on identical splits with negation-focused examples, keeping tokenizer and other cleaning fixed.
Limits
- The7%report lacks a baseline and percentage convention; large validation-to-leaderboard gaps limit confidence in the split.
Treat untested embedding distances as hypotheses
Conditions
- TF-IDF text matching has plausible semantic alternatives
Action
- Proposed conditional application: Consider Word Mover's Distance with word embeddings as an explicitly untested comparison.
Check
- Proposed check: Compare WMD against sparse TF-IDF on held-out query groups; measure distance-computation cost.
Limits
- Advice only; superiority claim belongs to cited work and is not original competition evidence.
Weight ambiguous labels by reference multiplicity
Conditions
- decoded text maps to several known classes with different reference frequencies
Action
- Proposed adaptation of source observation: Count reference occurrences per class and sample proportionally rather than uniformly.
Check
- Proposed verification: Compare expected accuracy against uniform sampling on ambiguities with known answers.
Limits
- Requires a legitimately available reference corpus; source-specific ambiguity handling does not transfer to ordinary unseen text.
Ablate intuitive fraud-text indicators
Conditions
- fraud hypotheses suggest manually extracted text flags
Action
- Proposed adaptation of source observation: Evaluate each family of flags against an unchanged text baseline before expanding it.
Check
- Proposed verification: Measure incremental validation effect and inspect flagged false positives.
Limits
- Local negative result does not rule out these signals in other domains.
Match language-specific pretraining to resume text
Conditions
- resume sections are written in French
Action
- Proposed conditional application: Fine-tune a French pretrained CamemBERT classifier for education, experience and skills.
Check
- Proposed check: Compare French and multilingual encoders on resume- or applicant-grouped holdouts, ensuring sections of one resume stay together.
Limits
- Repository includes winner-owned postcompetition improvements; later scores must not be attributed to the original submission.
Adapt pretrained text representations using grouped questions and domain tokens
Conditions
- question-answer labels share titles and domain-specific vocabulary
Action
- Proposed adaptation of source observation: Group validation by repeated question title and compare domain-adapted representations with the generic baseline.
Check
- Proposed verification: Audit duplicates and evaluate layer pooling and domain pretraining separately.
Limits
- Reported improvements combine several changes; domain metadata may be unavailable or biased.
Try linear models on raw sparse text features
Conditions
- raw TF-IDF inputs are high-dimensional and sparse
Action
- Proposed conditional application: Include linear models before assuming nonlinear trees or kernel SVR are stronger.
Check
- Proposed check: Compare regularized linear and nonlinear learners on identical sparse features and query-held-out metric; record time/memory.
Limits
- Linear strength depends on representation and regularization; no general nonlinear inferiority.
Record failed NLP additions without expanding ambiguous claims
Conditions
- many plausible NLP additions compete for experimental time
Action
- Proposed adaptation of source observation: Keep the successful simple baseline and compare additional methods one at a time with explicit configurations.
Check
- Proposed verification: Reproduce negative comparisons and distinguish the unexplained semi-supervised variant from successful pseudo-labeling.
Limits
- The list lacks configurations and contains an ambiguous semi-supervised label; do not infer all semi-supervised learning failed.
Align sparse feature widths at load time
Conditions
- SVMLight files independently omit trailing all-zero columns
Action
- Proposed: pad the narrower sparse matrix with zero columns before fitting and prediction.
Check
- Proposed: verify that feature index meanings are identical in both files; compare width-padded matrices with files loaded using a common explicit feature dimension.
Limits
- Zero-padding repairs width mismatch only, not mismatched feature dictionaries or column meaning.
Restrict sequence reconstruction to valid character classes
Conditions
- character-level diacritization includes spaces and special tokens
Action
- Proposed: preserve original characters and append predicted diacritics only for letters and non-special diacritic labels.
Check
- Proposed: assert output retains the undiacritized original text and inspect alignment around boundary and special tokens.
Limits
- The loop preserves only the zipped character/prediction prefix; mismatched lengths truncate text. squeeze()[1:] requires explicit boundary and batch-shape checks, and the file-write variable is inconsistent. No execution or gain is shown.
Refine neural diacritization with statistical postprocessing
Conditions
- Arabic sequence labeling has statistical consistency constraints
Action
- Proposed conditional application: Train a BiLSTM neural model and a separate statistical postprocessor on its output.
Check
- Proposed check: Compare neural-only and corrected diacritic error rates on document-disjoint held-out Arabic text.
Limits
- Correction rules/training details absent; no isolated gain or readability benefit established.
Use external task predictions as essay-quality features
Conditions
- small training data has related externally scored essays
Action
- Proposed conditional application: Train TF-IDF plus LightGBM models on anonymized external essays and use their predicted scores as features.
Check
- Proposed check: Audit overlap with external essays, fit feature transforms under the intended validation protocol, and ablate external score features on prompt- or writer-held-out data.
Limits
- External essay tasks may be mismatched or duplicated; external inference increased cost and the author gave up the efficiency prize.
Measure reliance on a text-label shortcut
Conditions
- a punctuation pattern may correlate with sentiment labels
Action
- Proposed conditional application: Train with and without the unmatched-parentheses pattern, then cross-evaluate each model on both validation versions.
Check
- Proposed check: Run the four original/removed train/evaluate combinations on matched validation membership; report confusion matrices and subgroup accuracy.
Limits
- Removing punctuation can alter actual sentiment; reported GPU runs take about 16–26 hours.
Preserve script and tone cues in multilingual toxicity text
Conditions
- normalization can remove orthographic or emotional cues
Action
- Proposed conditional application: Use NFC normalization while preserving emoji, repeated punctuation, censored spellings and Bengali ZWJ/ZWNJ.
Check
- Proposed check: Ablate normalization/removal on language-stratified holdouts and inspect token/orthographic changes.
Limits
- Orthographic validity and emotional cues vary by language; no social-safety benefit established.