Thomas Piacentini
← Atelier

Fiche

Classic Text Processing and TF-IDF

Texte original · anglais

Tokenize, stem, lemmatize, count, weight with TF-IDF, classify sentiment and find topics with LDA or BERTopic.

Classic text pipeline

  1. Tokenize split the string into word tokens. Keep punctuation as its own tokens.
  2. Stem or lemmatize stem for speed. Lemmatize with a POS tag when meaning matters.
  3. Negation scope prefix tokens after a negation word with NOT_ up to punctuation.
  4. Count bag of words. One row per document, one column per vocabulary word.
  5. Weight TF-IDF scales down common words. L2 rows make cosine a dot product.
  6. Classify Naive Bayes for a baseline, logistic regression for production.
  7. Evaluate per-class precision and recall, macro-F1, confusion matrix.
Sources

Tasks in code

TaskCode
Tokenize, stem and lemmatizepreprocess(text, pos_tagger=demo_pos_tagger)
Porter step 1astem_step_1a(word)
Table lemmatizer with fallbacklemmatize(word, pos)
Count matrixbag_of_words(docs, build_vocab(docs))
TF-IDF rows on the unit spherel2_normalize(tfidf(bow))
Scope negationapply_negation(tokens)
Naive Bayes baselinetrain_nb(docs_by_class, vocab, alpha=1.0)
LDA by collapsed Gibbs samplingcollapsed_gibbs_lda(docs, n_topics=3, n_iters=300, seed=42)
Sources

NLTK: tokens, stems, lemmas

nltk.download("punkt_tab")
nltk.download("wordnet")
nltk.download("averaged_perceptron_tagger_eng")
tokens = word_tokenize(text)contractions, Unicode
stems = [PorterStemmer().stem(t) for t in tokens] # all five phases
tagged = pos_tag(tokens)Penn Treebank tags
lemmatizer = WordNetLemmatizer()
lemmas = [lemmatizer.lemmatize(t, nltk_pos_to_wordnet(tag)) for t, tag in tagged]V>v J>a R>r else n
Sources

Sentiment baseline in scikit-learn

pipe = Pipeline([
("tfidf", TfidfVectorizer(
ngram_range=(1, 2),bigrams, so not_good is a feature
min_df=2,
sublinear_tf=True,dampens repeated words
stop_words=None)),keeps negations
("clf", LogisticRegression(C=1.0, max_iter=1000)),
])
pipe.fit(X_train, y_train)
print(pipe.score(X_test, y_test))75% to 85% on SST-2 with the 3 flags
Sources

Vectorizer knobs

CommandWhat it does
CountVectorizer()Tokenization, vocabulary and BoW in one call
TfidfVectorizer()Adds IDF weighting and L2 normalization
ngram_range=(1, 2)Includes bigrams. Usually boosts classification
min_df=2Drops words in fewer than 2 docs
max_df=0.95Drops words in more than 95% of docs
stop_words="english"Builtin stopword list. Not for sentiment
sublinear_tf=TrueUses 1 + log(tf) instead of raw tf
get_feature_names_out()Lists the vocabulary in column order
Sources

Topic models

cv = CountVectorizer(max_features=max_features, stop_words="english",
min_df=2, max_df=0.9)LDA needs raw counts
X = cv.fit_transform(documents)
lda = LatentDirichletAllocation(n_components=n_topics, random_state=42,
max_iter=50, learning_method="online")
doc_topic = lda.fit_transform(X)(n_docs, n_topics), rows sum to 1
topic_model = BERTopic(
embedding_model="sentence-transformers/all-MiniLM-L6-v2",
min_topic_size=15)library default is 10
topics, probs = topic_model.fit_transform(documents)
info = topic_model.get_topic_info()Topic -1 is the outlier bucket
Sources

Formulas

TF-IDF
\frac{\mathrm{count}(w \text{ in } d)}{|d|} \cdot \log\frac{N}{\mathrm{df}(w)}
Smoothed IDF
\log\frac{n + 1}{d + 1} + 1. A word in every doc keeps IDF 1.
L2 row
divide each row by its norm. Long docs stop dominating.
Naive Bayes
log prior plus the sum of log P(\text{word} \mid \text{class}). Take the max.
Laplace
(\mathrm{count} + \alpha) / (\mathrm{total} + \alpha |V|). No zero probabilities.
Logistic regression
sigmoid of Xw + b. Predict 1 at 0.5 or more.
Macro-F1
mean of per-class F1, equally weighted.
c_v coherence
NPMI of top-word pairs, compared by cosine. Higher is better.
Sources

Defaults

SettingDefaultNote
NB alpha1.0Teaching default. 0.01 is common in practice
LR l20.01Start here and tune. Sparse features overfit
LR epochs, lr500Learning rate 0.05
LR C1.0With max_iter=1000 in scikit-learn
BERTopic min_topic_size10Use 50 or 100 over 10,000 docs
Gibbs alpha, beta0.1Beta is 0.01. Both must be positive
Gibbs n_iters200The demo runs 300
Topic countmax(5, round(sqrt(n_docs)))Clamp to 200 under 40,000 docs
Sources

Troubleshooting

SymptomCauseFix
Accuracy drops after deployTraining and inference preprocess differShip one preprocess function in the model
Tokens change after an upgradeLibrary version driftPin versions. Freeze 20 sample sentences
ponies stems to poniStep 1a ies rule onlyUse full Porter with step 1b
watched stays watchedLemma table has no ed ruleUse WordNet or spaCy
"not good" reads positiveStopword list drops negationsstop_words=None plus bigrams
High score, bad minority classMicro-F1 or accuracy on imbalanceReport macro-F1, AUPRC over 95-5
LDA junk topicsStopwords and frequent termsSet min_df and max_df
Many docs in topic -1HDBSCAN outlier bucketCheck min_topic_size, read the -1 docs
Sources

Use X when

Stem or lemmatize?
Stem for search indexing. Lemmatize for text the user reads.
NLTK or spaCy?
NLTK to teach and swap parts. spaCy for production speed.
TF-IDF or embeddings?
TF-IDF for spam, logs, low data and explainable models.
When a transformer?
Sarcasm, aspects, sentiment shifts and low-resource languages.
LDA or BERTopic?
LDA for long docs with topic mixtures. BERTopic for short text.
No GPU?
Use LDA or NMF.
Sources

Tips

  • Stay sparse. Dense TF-IDF for 100k documents does not fit in memory.
  • Read 5 wrong predictions per class before you trust a metric.
  • Regenerate the baseline on your own pipeline, not the paper's number.
Sources
Original source
---
id: text-processing-classic
title: Classic Text Processing and TF-IDF
code: CT
subtitle: Tokenize, stem, lemmatize, count, weight with TF-IDF, classify sentiment and find topics with LDA or BERTopic.
hue: 30
sources: phases/05-nlp-foundations-to-advanced/01-text-processing, phases/05-nlp-foundations-to-advanced/02-bag-of-words-tfidf, phases/05-nlp-foundations-to-advanced/05-sentiment-analysis, phases/05-nlp-foundations-to-advanced/15-topic-modeling
source_hash: 8993666a55a0debd
---

## Classic text pipeline
type: flow
from: phases/05-nlp-foundations-to-advanced/01-text-processing/docs/en.md, phases/05-nlp-foundations-to-advanced/02-bag-of-words-tfidf/docs/en.md, phases/05-nlp-foundations-to-advanced/05-sentiment-analysis/docs/en.md

1. **Tokenize**: split the string into word tokens. Keep punctuation as its own tokens.
2. **Stem or lemmatize**: stem for speed. Lemmatize with a POS tag when meaning matters.
3. **Negation scope**: prefix tokens after a negation word with `NOT_` up to punctuation.
4. **Count**: bag of words. One row per document, one column per vocabulary word.
5. **Weight**: TF-IDF scales down common words. L2 rows make cosine a dot product.
6. **Classify**: Naive Bayes for a baseline, logistic regression for production.
7. **Evaluate**: per-class precision and recall, macro-F1, confusion matrix.

## Tasks in code
type: task-table
span: 2
from: phases/05-nlp-foundations-to-advanced/01-text-processing/code/main.py, phases/05-nlp-foundations-to-advanced/02-bag-of-words-tfidf/code/main.py, phases/05-nlp-foundations-to-advanced/05-sentiment-analysis/docs/en.md, phases/05-nlp-foundations-to-advanced/15-topic-modeling/code/main.py

| Task | Code |
|---|---|
| Tokenize, stem and lemmatize | `preprocess(text, pos_tagger=demo_pos_tagger)` |
| Porter step 1a | `stem_step_1a(word)` |
| Table lemmatizer with fallback | `lemmatize(word, pos)` |
| Count matrix | `bag_of_words(docs, build_vocab(docs))` |
| TF-IDF rows on the unit sphere | `l2_normalize(tfidf(bow))` |
| Scope negation | `apply_negation(tokens)` |
| Naive Bayes baseline | `train_nb(docs_by_class, vocab, alpha=1.0)` |
| LDA by collapsed Gibbs sampling | `collapsed_gibbs_lda(docs, n_topics=3, n_iters=300, seed=42)` |

## NLTK: tokens, stems, lemmas
type: annotated-code
lang: python
from: phases/05-nlp-foundations-to-advanced/01-text-processing/docs/en.md

```python
nltk.download("punkt_tab")
nltk.download("wordnet")
nltk.download("averaged_perceptron_tagger_eng")
tokens = word_tokenize(text)                      # contractions, Unicode
stems = [PorterStemmer().stem(t) for t in tokens] # all five phases
tagged = pos_tag(tokens)                          # Penn Treebank tags
lemmatizer = WordNetLemmatizer()
lemmas = [lemmatizer.lemmatize(t, nltk_pos_to_wordnet(tag)) for t, tag in tagged]  # V>v J>a R>r else n
```

## Sentiment baseline in scikit-learn
type: annotated-code
lang: python
from: phases/05-nlp-foundations-to-advanced/05-sentiment-analysis/docs/en.md

```python
pipe = Pipeline([
    ("tfidf", TfidfVectorizer(
        ngram_range=(1, 2),       # bigrams, so not_good is a feature
        min_df=2,
        sublinear_tf=True,        # dampens repeated words
        stop_words=None)),        # keeps negations
    ("clf", LogisticRegression(C=1.0, max_iter=1000)),
])
pipe.fit(X_train, y_train)
print(pipe.score(X_test, y_test))  # 75% to 85% on SST-2 with the 3 flags
```

## Vectorizer knobs
type: command-table
from: phases/05-nlp-foundations-to-advanced/02-bag-of-words-tfidf/docs/en.md

| Command | What it does |
|---|---|
| `CountVectorizer()` | Tokenization, vocabulary and BoW in one call |
| `TfidfVectorizer()` | Adds IDF weighting and L2 normalization |
| `ngram_range=(1, 2)` | Includes bigrams. Usually boosts classification |
| `min_df=2` | Drops words in fewer than 2 docs |
| `max_df=0.95` | Drops words in more than 95% of docs |
| `stop_words="english"` | Builtin stopword list. Not for sentiment |
| `sublinear_tf=True` | Uses `1 + log(tf)` instead of raw `tf` |
| `get_feature_names_out()` | Lists the vocabulary in column order |

## Topic models
type: annotated-code
lang: python
from: phases/05-nlp-foundations-to-advanced/15-topic-modeling/docs/en.md

```python
cv = CountVectorizer(max_features=max_features, stop_words="english",
                     min_df=2, max_df=0.9)   # LDA needs raw counts
X = cv.fit_transform(documents)
lda = LatentDirichletAllocation(n_components=n_topics, random_state=42,
                                max_iter=50, learning_method="online")
doc_topic = lda.fit_transform(X)           # (n_docs, n_topics), rows sum to 1
topic_model = BERTopic(
    embedding_model="sentence-transformers/all-MiniLM-L6-v2",
    min_topic_size=15)                     # library default is 10
topics, probs = topic_model.fit_transform(documents)
info = topic_model.get_topic_info()       # Topic -1 is the outlier bucket
```

## Formulas
type: concepts
from: phases/05-nlp-foundations-to-advanced/02-bag-of-words-tfidf/docs/en.md, phases/05-nlp-foundations-to-advanced/05-sentiment-analysis/docs/en.md, phases/05-nlp-foundations-to-advanced/15-topic-modeling/docs/en.md

- **TF-IDF**: $\frac{\mathrm{count}(w \text{ in } d)}{|d|} \cdot \log\frac{N}{\mathrm{df}(w)}$
- **Smoothed IDF**: $\log\frac{n + 1}{d + 1} + 1$. A word in every doc keeps IDF 1.
- **L2 row**: divide each row by its norm. Long docs stop dominating.
- **Naive Bayes**: log prior plus the sum of log $P(\text{word} \mid \text{class})$. Take the max.
- **Laplace**: $(\mathrm{count} + \alpha) / (\mathrm{total} + \alpha |V|)$. No zero probabilities.
- **Logistic regression**: sigmoid of $Xw + b$. Predict 1 at 0.5 or more.
- **Macro-F1**: mean of per-class F1, equally weighted.
- **c_v coherence**: NPMI of top-word pairs, compared by cosine. Higher is better.

## Defaults
type: defaults
from: phases/05-nlp-foundations-to-advanced/05-sentiment-analysis/docs/en.md, phases/05-nlp-foundations-to-advanced/15-topic-modeling/docs/en.md, phases/05-nlp-foundations-to-advanced/15-topic-modeling/code/main.py

| Setting | Default | Note |
|---|---|---|
| NB `alpha` | 1.0 | Teaching default. 0.01 is common in practice |
| LR `l2` | 0.01 | Start here and tune. Sparse features overfit |
| LR `epochs`, `lr` | 500 | Learning rate 0.05 |
| LR `C` | 1.0 | With `max_iter=1000` in scikit-learn |
| BERTopic `min_topic_size` | 10 | Use 50 or 100 over 10,000 docs |
| Gibbs `alpha`, `beta` | 0.1 | Beta is 0.01. Both must be positive |
| Gibbs `n_iters` | 200 | The demo runs 300 |
| Topic count | `max(5, round(sqrt(n_docs)))` | Clamp to 200 under 40,000 docs |

## Troubleshooting
type: troubleshooting
from: phases/05-nlp-foundations-to-advanced/01-text-processing/docs/en.md, phases/05-nlp-foundations-to-advanced/02-bag-of-words-tfidf/docs/en.md, phases/05-nlp-foundations-to-advanced/05-sentiment-analysis/docs/en.md, phases/05-nlp-foundations-to-advanced/15-topic-modeling/docs/en.md

| Symptom | Cause | Fix |
|---|---|---|
| Accuracy drops after deploy | Training and inference preprocess differ | Ship one preprocess function in the model |
| Tokens change after an upgrade | Library version drift | Pin versions. Freeze 20 sample sentences |
| `ponies` stems to `poni` | Step 1a `ies` rule only | Use full Porter with step 1b |
| `watched` stays `watched` | Lemma table has no `ed` rule | Use WordNet or spaCy |
| "not good" reads positive | Stopword list drops negations | `stop_words=None` plus bigrams |
| High score, bad minority class | Micro-F1 or accuracy on imbalance | Report macro-F1, AUPRC over 95-5 |
| LDA junk topics | Stopwords and frequent terms | Set `min_df` and `max_df` |
| Many docs in topic -1 | HDBSCAN outlier bucket | Check `min_topic_size`, read the -1 docs |

## Use X when
type: qa
from: phases/05-nlp-foundations-to-advanced/01-text-processing/docs/en.md, phases/05-nlp-foundations-to-advanced/02-bag-of-words-tfidf/docs/en.md, phases/05-nlp-foundations-to-advanced/05-sentiment-analysis/docs/en.md, phases/05-nlp-foundations-to-advanced/15-topic-modeling/docs/en.md

- **Stem or lemmatize?** Stem for search indexing. Lemmatize for text the user reads.
- **NLTK or spaCy?** NLTK to teach and swap parts. spaCy for production speed.
- **TF-IDF or embeddings?** TF-IDF for spam, logs, low data and explainable models.
- **When a transformer?** Sarcasm, aspects, sentiment shifts and low-resource languages.
- **LDA or BERTopic?** LDA for long docs with topic mixtures. BERTopic for short text.
- **No GPU?** Use LDA or NMF.

## Tips
type: tips
from: phases/05-nlp-foundations-to-advanced/02-bag-of-words-tfidf/docs/en.md, phases/05-nlp-foundations-to-advanced/05-sentiment-analysis/docs/en.md, phases/05-nlp-foundations-to-advanced/15-topic-modeling/docs/en.md

- Stay sparse. Dense TF-IDF for 100k documents does not fit in memory.
- Read 5 wrong predictions per class before you trust a metric.
- Regenerate the baseline on your own pipeline, not the paper's number.
Code et livrables 1
Ce que vous en retenez

Enregistré dans votre carnet sur cet appareil.

Sources et limites

Les repères de lecture sont déclaratifs. Les résultats de tests sont enregistrés séparément ; ils valent pour les cas exécutés.

AI Engineering from Scratch · 3f9715c · MIT

Télécharger ce fichier ↓