A Representation Bake-Off

Chapter 4 · Applied Natural Language Processing

Dr. Batuhan Bardak

After this lecture you can

  • build a pipeline from raw text to an evaluated prediction for a real, imbalanced dataset, using the methods of the previous two chapters
  • explain why accuracy alone is not enough on this imbalanced task, and compute macro-F1 from its per-class arithmetic
  • form a starting expectation for count and embedding representations on a task — and then test that expectation with an experiment
  • measure the out-of-vocabulary rate on Turkish text, and evaluate coverage, the quality of the vectors and the task score separately
  • show with an example the word-order information that a unigram bag and an average of word vectors both lose

Two chapters, two claims

  • Chapter 2: sparse count vectors can be a strong, cheap, interpretable baseline
  • Chapter 3: giving every word form its own independent feature can leave a coverage problem on the inflected forms of an agglutinative language like Turkish
  • Its measurement: build the vocabulary on 90% of a Turkish Wikipedia sample, test on the remaining 10% — 7.33% of test tokens were never seen in training, and 28.99% of distinct word types
  • The two claims do not logically contradict each other; which one dominates on a particular dataset is something you only learn by measuring

A bake-off, in the engineering sense: several candidate methods, one task, one protocol, and the same measuring stick for all of them.

What is held fixed, and what is not

The same in every row

  • the train/test split
  • the LogisticRegression class
  • the class weighting
  • the random seed

Changes from row to row

  • the vectoriser’s vocabulary threshold
  • the tokenisation path
  • the scale of the features
  • the separate training of the dense representations on unlabelled text
  • Every row uses C = 1.0: TF-IDF rows arrive with norm 1.0, the averaged vectors are not normalised — the same C need not mean the same regularisation

The results compare these concrete setups of the methods; we do not read the gap between them as the effect of the representation family alone.

The problem: Turkish product reviews

A binary sentiment task: 235,165 Turkish product reviews, labelled positive or negative.

  • Short and informal texts: misspellings, missing diacritics, run-together words; all of the distributed text has already been lowercased

Three reasons for choosing it:

  1. It is Turkish: chapter 3’s morphology argument can be tested on real user text
  2. Its classes are imbalanced — the positive class is 14.8 times larger — so the metric has to be argued
  3. It is large enough to subsample: a learning curve valid for this data

Barmanbay 2020 — Turkish product reviews

Check the data before you split it

### checks before de-duplication
missing text or label         0
empty texts                  21
texts with both labels       77
### the raw data
rows as downloaded      235,165
after de-duplication    233,508      (1,657 exact duplicates removed)
label 1 (positive)      218,747      93.7%
label 0 (negative)       14,761       6.3%
  • 77 texts carry both labels (297 rows in all); drop_duplicates keeps the first row and positive reviews come first in this file, so all 77 stayed with their positive label
  • De-duplicate on the text before splitting, and report the policy you chose: the 1,657 duplicates are only 0.7% of the file; the step is necessary but not sufficient

The split, and two warnings about it

### the split (stratified, seed 42)
train   40,000    37,471 positive    2,529 negative   rows 8205c2be5ebf
test    10,000     9,368 positive      632 negative   rows 634cdb3bb002
  • A stratified 50,000-review subsample of the 233,508 de-duplicated reviews, then an 80/20 split, again stratified
  • rows is a digest of the selected row positions: the same 12 characters mean the same rows; the class totals do not show that
  • Reproducing it: “stratified with seed 42” does not name a procedure; it names a family of procedures
  • The test set and selection: every number below is read off the same 10,000 test reviews — read these results as exploratory

The simplest rule: always predict “positive”

Predict “positive” for every review. Never look at the text at all.

### majority baseline, in full
                 predicted neg   predicted pos   total
   actual neg          0             632          632
   actual pos          0           9,368        9,368

precision (neg)   undefined — it never predicts negative
recall    (neg)   0.0%       — it finds none of the 632
F1        (neg)   0.00       — 2TP / (2TP + FP + FN) = 0 / 632
F1        (pos)   96.74
macro-F1          48.37

The rule reaches 93.68% accuracy without using the review text at all, because 93.68% of the test set is positive — and it finds none of the 632 negative reviews.

Macro-F1, from its per-class arithmetic

\[\text{precision} = \frac{TP}{TP + FP}, \qquad \text{recall} = \frac{TP}{TP + FN}, \qquad F_1 = \frac{2\,TP}{2\,TP + FP + FN}\]

\[\text{macro-}F_1 = \frac{1}{K}\sum_{k=1}^{K} F_1^{(k)}\]

  • \(F_1\) is the harmonic mean of precision and recall: when either of them drops to zero, so does \(F_1\)
  • Majority rule, negative class: \(TP = 0\), \(FP = 0\), \(FN = 632\), so \(F_1^{(\text{neg})} = 0/632 = 0\); with \(F_1^{(\text{pos})} = 0.9674\), macro-F1 \(= (0 + 0.9674)/2 = 0.4837\)

Macro-F1 is the equally weighted average of the per-class F1 scores. Accuracy weights examples equally; macro-F1 weights classes equally.

Which F1 does your library report?

average= what it computes the majority rule scores
"binary" (the default for two classes) the F1 of class 1 only — here the majority positive class 96.74
"micro" pools all the counts before dividing; here it equals accuracy 93.68
"weighted" weights each class’s F1 by its number of examples 90.62
"macro" weights the classes equally 48.37
  • Choosing macro-F1 as the headline metric and reweighting the training loss by class frequency (class_weight="balanced") are separate design decisions

In this chapter macro-F1 is the main summary score for comparing the methods, and accuracy is reported alongside it for context. Macro-F1 is not a universal answer: recall on the negative class, precision at a given false-alarm budget, or expected business cost can be more direct decision metrics.

Lowercasing Turkish: the default mapping is not Turkish

TR_LOWER = str.maketrans({"I": "ı", "İ": "i"})
def tr_lower(s):
    return s.translate(TR_LOWER).lower()
### the same string, two lowercasing rules
             str.lower()      tr_lower()
"IYI"        iyi              ıyı
"İYİ"        i̇yi̇             iyi
  • Python’s str.lower() implements Unicode’s default case mapping and does not change with the locale: i for I, and i plus a combining dot (U+0307) for İ
  • Both run without an error, and both can break tokenisation and vocabulary matching

Case conversion is language-dependent, and a language-specific lowercasing policy is part of the preprocessing and tokeniser contract.

The tokeniser, and what it throws away

TOKEN_RE = re.compile(r"[a-zçğıöşü]+")
def tokenize(s):
    return TOKEN_RE.findall(tr_lower(s))
  • The pattern keeps only the listed letters: punctuation, digits and emoji all disappear; â, î, û and combining marks split a token
  • !!!, 5/5 or an emoji can carry sentiment; this experiment does not model them, and the effect on the score was not measured
  • tr_lower assumes correct spelling: typed on an English keyboard, IYI may well mean “İYİ”, and tr_lower then produces ıyı, a new error

What ASCII-typed text needs is the restoration of Turkish characters (deasciification); the details are in the Turkish NLP chapter.

The training corpus, in numbers

### the training corpus, 40,000 reviews
tokens                        903,190
distinct word forms            74,317
hapax (seen exactly once)      43,579     58.6% of all forms
mean review length               22.6 tokens
median                             19
95th percentile                    56
longest                           296
  • 58.6% of the vocabulary appears exactly once. In chapter 3’s Turkish Wikipedia sample the rate was 50.0%; the gap cannot be put down to typos alone
  • 30,738 forms are seen at least twice, yet min_df=2 leaves 30,195 columns: min_df counts document frequency, not corpus frequency

895 word forms behind one five-character prefix

prefix distinct forms examples
kulla 895 kullanışlı, kullanıyorum, kullandım
bekle 285 beklediğimden, beklediğim, beklemiyordum
çalış 247 çalışıyor, çalışma, çalışan
çıkar 209 çıkarıyor, çıkar, çıkartıyor
düşün 195 düşünüyorum, düşünmeden, düşünenlere
  • In a bag-of-words model every form in the training vocabulary is its own column: the coefficient learned for kullanışlı does not transfer by itself to kullanıyorum

Not 895 inflections of one stem: inflections, derivations and spelling variants together. A five-character prefix is not stem or morpheme analysis.

Round 1 — the chapter-2 methods

row vectoriser vocabulary threshold and tokens columns
bag of words CountVectorizer min_df=2, word pattern 30,195
tf-idf TfidfVectorizer, sublinear_tf=True min_df=2, word pattern 30,195
tf-idf 1-2 gram the same, with ngram_range=(1, 2) min_df=2, word pattern 101,659
tf-idf char_wb 3-5 analyzer="char_wb", ngram_range=(3, 5) min_df=3; token_pattern is not used 94,939
  • Every vectoriser gets preprocessor=tr_lower, lowercase=False: otherwise scikit-learn calls Python’s str.lower()
  • One classifier for all rows: LogisticRegression(max_iter=2000, class_weight="balanced", random_state=42)

Round 1 — the results

### round 1: counting
                      accuracy   macro-F1   F1(neg)   dims      fit
majority baseline        93.68      48.37      0.00      —        —
bag of words             92.14      73.51     51.30   30,195    0.7 s
tf-idf                   90.07      71.95     49.41   30,195    0.5 s
tf-idf 1-2 gram          92.57      76.21     56.47  101,659    1.0 s
tf-idf char_wb 3-5       88.63      70.39     47.14   94,939    3.3 s
  • All four count methods have lower accuracy than the majority rule’s 93.68%, while scoring 22 to 28 macro-F1 points above it
  • If you chose a method by accuracy alone in this setup, the majority rule would look like a strong candidate
  • That is not a law: other thresholds and training settings can raise both accuracy and negative-class performance; they were not tested here

The character row takes a different path

The same input through the two analyzers: "5/5 harika!! ürün çok i̇yi 👍"

analyzer features what happened
analyzer="word", token_pattern=[a-zçğıöşü]+ ['harika', 'ürün', 'çok', 'i', 'yi'] 5/5, !!, the emoji are gone; the combining dot split “i̇yi” in two
analyzer="char_wb", ngram_range=(3, 5) [' 5/', '5/5', '/5 ', ' ha', 'har', 'ari', ...] digits, the slash, the exclamation marks and the spaces all survive as features
  • In scikit-learn token_pattern is used only when analyzer="word"; char_wb ignores it entirely and runs its own character stream
  • The character scheme uses min_df=3 where the three word schemes use min_df=2: a defensible choice, but it was not swept

The score difference of the character row may reflect this preprocessing choice along with the character features.

It made no difference on this corpus — write it this way anyway

  • With and without lowercase=False, round 1’s table came out byte-identical: not a single uppercase character among the 37,076,865 characters of the 233,508 de-duplicated reviews
  • Not the same as the corpus being clean: U+0307 (COMBINING DOT ABOVE) sits in 4,194 documents of the 50,000-review working subsample — 8.4% of it — every time after a letter i
  • There tavsiye splits into tavsi + ye; deleting the mark takes the 30,195-column vocabulary down to 29,823: 595 columns exist only because of this splitting
result tokens under [a-zçğıöşü]+
'İYİ'.lower() 'i̇yi̇' ['i', 'yi'] split in two
'IYI'.lower() 'iyi' ['iyi'] wrongly merged

The repair is to delete the mark from the text you were given, not to lowercase it differently. Why fix the code anyway? The reader copying it will not have that corpus.

Raw counts ahead of TF-IDF? One change at a time

Between CountVectorizer and TfidfVectorizer three things change at once: IDF weighting, sublinear term frequency (log-TF) and L2 row normalisation. Same C = 1.0:

unigram word features, min_df=2 accuracy macro-F1 F1(neg)
raw counts (= “bag of words”) 92.14 73.51 51.30
counts + L2 88.07 69.57 45.85
log-TF + L2 88.37 70.00 46.53
counts + idf + L2 90.20 72.16 49.74
log-TF + idf, no norm 92.67 71.83 47.61
log-TF + idf + L2 (= “tf-idf”) 90.07 71.95 49.41

Adding L2 row normalisation to raw counts takes macro-F1 from 73.51 down to 69.57; IDF, added on top of it, brings the score up to 72.16. “TF-IDF fell behind here because of IDF” is not what the experiment shows.

With C chosen on validation, the ordering flips

C chosen on a validation split carved out of train (32,000 fit / 8,000 val), then refit on all 40,000 documents:

                     best C on validation    test macro-F1
raw counts                  1.0                  73.51
tf-idf                     10.0                  74.71
  • TF-IDF 74.71 against raw counts 73.51: over 10,000 paired bootstrap resamples the 95% interval of the macro-F1 difference runs from 0.42 to 1.99 points
  • 151 reviews only TF-IDF gets right, 80 only raw counts get right

The “raw counts ahead of TF-IDF” result did not survive an equal tuning budget: it is not a fixed property of the representations, it depended on a shared, untuned C.

Bigrams: the largest gap inside the count family

  • In this setup word unigram + bigram TF-IDF scores 4.26 macro-F1 points above unigram-only TF-IDF, and 2.70 above raw counts
  • Bigrams keep local sequences such as iyi değil (“not good”) and güzel değil (“not nice”) as separate features: negation built with a separate word
  • Part of Turkish negation is a suffix: beğendim (“I liked it”) and beğenmedim (“I did not like it”) are separate features at the unigram level too

This chapter did not measure how much of the 4.26 points comes from which kind of negation; saying that takes a targeted error analysis or an ablation.

What the model learned, in words

towards POSITIVE towards NEGATIVE
gayet (quite) +8.04 etmiyorum (I do not [recommend]) −8.16
ederim ([I] do) +6.94 değil (not) −6.95
mükemmel (perfect) +6.13 kötü (bad) −6.63
teşekkürler (thanks) +6.09 etmem (I would not) −6.35
harika (wonderful) +5.93 beğenmedim (I did not like it) −6.21
memnunum (I am pleased) +5.84 iade (return / refund) −5.85
güzel (nice) +5.48 fakat (however) −5.52

Global associations learned on this training distribution: not universal sentiment scores, and not a complete explanation of one particular prediction.

Round 2 — the chapter-3 methods

word2vec, fastText and doc2vec, trained from scratch on the text of the 40,000 training reviews, without using the labels; no pretrained vectors and no test reviews went into that training.

model training settings from there to a document vector
word2vec vector_size=100, window=5, min_count=2, sg=1, negative=5, epochs=10 the mean of the vectors of its tokens that are in the vocabulary; none at all → the zero vector
fastText the same, with min_n=3, max_n=6, bucket=500_000 every token goes into the mean: a vector is built for out-of-vocabulary forms too, from subword vectors
doc2vec vector_size=100, min_count=2, epochs=20, dm=0 training documents: learned directly; test documents: infer_vector at its default settings

All three matrices go to the same logistic regression as in round 1.

Both rounds, sorted by macro-F1

### both rounds, sorted by macro-F1
tf-idf 1-2 gram          92.57      76.21     56.47  101,659
bag of words             92.14      73.51     51.30   30,195
tf-idf                   90.07      71.95     49.41   30,195
tf-idf char_wb 3-5       88.63      70.39     47.14   94,939
averaged word2vec        79.92      61.39     34.64      100
averaged fastText        79.64      61.36     34.79      100
doc2vec (PV-DBOW)        71.96      55.13     27.66      100
majority baseline        93.68      48.37      0.00        —
  • Unlike the sparse rows, the dense rows cannot be reproduced to the last digit: a re-run with the same seed gave 61.37 macro-F1 for word2vec, 61.29 for fastText and 55.44 for doc2vec
  • Five embedding seeds kept word2vec between 61.57 and 62.06, and fastText between 60.86 and 61.68: read the decimal differences in the dense rows together with that spread

Which gaps are larger than the noise of this test set?

Paired bootstrap, 10,000 test reviews: macro-F1 difference and 95% interval.

tf-idf 1-2 gram     - bag of words         +2.69   [ +1.60,  +3.82]
bag of words        - tf-idf               +1.56   [ +0.53,  +2.55]
tf-idf              - tf-idf char_wb 3-5   +1.57   [ +0.59,  +2.54]
tf-idf char_wb 3-5  - averaged word2vec    +9.01   [ +7.97, +10.01]
averaged word2vec   - averaged fastText    +0.08   [ -0.60,  +0.77]
averaged fastText   - doc2vec (PV-DBOW)    +5.85   [ +5.02,  +6.69]
tf-idf 1-2 gram     - averaged word2vec   +14.83   [+13.65, +16.01]
  • The gap between the families, and the adjacent gaps inside the sparse family, are larger than the sampling noise of this test set; the gap between word2vec and fastText is not
  • These intervals cover test sampling only: not a change of training split, and not the spread over training seeds

The shape of the gap: more negatives found, more false alarms

bigram TF-IDF averaged word2vec
macro-F1 76.21 61.39
F1 on the negative class 56.47 34.64
recall on the negative class 76.3% 84.2%
precision on the negative class 44.8% 21.8%
  • The gap between the best sparse method and the best dense one is 14.8 macro-F1 points; on the negative class it is 21.83 points of F1
  • Averaged word2vec actually finds more negatives; but only 21.8% of the reviews it flags as negative really are negative

These are aggregate rates over the test set. That pattern of false alarms may have as much to do with class_weight="balanced", the decision threshold and the choice of C as with how well the representation separates the classes.

Do not read that ordering as a general ranking

  • The three dense rows are averaged static vectors trained from scratch on this corpus’s 0.9 million tokens, not pretrained ones
  • The scale of the dense vectors was not equalised and C was tuned for no row at all; every row comes from one split and one run
  • The measurement is precisely: static embeddings trained from scratch on 0.9M tokens fell behind, in this setup, counts extracted from the same 40,000 documents — not “static embeddings fall behind counts”
  • Vectors pretrained on large Turkish corpora may give a different result; this chapter did not measure the direction or the size of the difference

What does not depend on corpus size is the averaging defect: that failure is in the pooling step, not in the vectors. That is the transferable result of this chapter; the ranking is the local one.

Do not read the number of dimensions as memory

word TF-IDF matrix dense document matrix
columns 30,195 100
non-zeros per row 19.8 on average 100
measured memory of the training matrix 9.7 MB 16.0 MB
  • A sparse matrix stores only the non-zero values and their indices
  • 100 dimensions is about 1,017 times fewer coordinates than bigram TF-IDF’s 101,659 columns and about 302 times fewer than word TF-IDF’s 30,195 — not, by themselves, ratios of memory or latency

61.39 macro-F1 from 100 numbers per document, against 76.21 from 101,659 columns. Measure the costs in your target use separately.

The out-of-vocabulary test

### coverage of the 10,000 test reviews
test tokens                     229,055
outside the word2vec vocabulary  16,660     7.27% of tokens
distinct test word forms         32,573
outside the vocabulary           15,098     46.4% of forms
test reviews with no usable token     7
  • 46.4% of the distinct word forms in the test set are not in the vocabulary word2vec learned; the document average skips such tokens
  • Seven reviews have no token in the vocabulary: they get the zero vector, and all seven are misclassified
  • The 7.27% is not chapter 3’s 7.33%: counted with that definition — never seen in training — the rates here are 5.10% of tokens and 33.5% of forms

fastText builds a vector for every form

### nearest neighbours for unseen word forms
kargolanmamış  ("not having been shipped")
  word2vec   KeyError
  fastText   kargolanmış, kargolandı, kargolanma, kargolanıp, kargolu

tavsiyelerim  ("my recommendations")
  word2vec   KeyError
  fastText   tavsiyeleri, tavsiyeler, tavsiyeederim, tavsiyedir

hızlıcana  (colloquial "nice and fast")
  word2vec   KeyError
  fastText   hızlıca, hızlıda, hızlısı, hızlıydı, hızlı
  • Coverage goes to 100%; the quality of the vector is not guaranteed. On average 84.5% of the n-grams of the 15,098 out-of-vocabulary test forms also occur in at least one vocabulary word

The first neighbour of kargolanmamış (“not yet shipped”) is kargolanmış (“shipped”), which carries no negative suffix: similarity of form can overshadow the negation.

Two models, two kinds of neighbour

query word2vec fastText
kargo (“shipping”) teslimat, gönderim, gönderi, kargoda, kargoyla, kargolama — different words, same topic kargocu, kargolu, kargosu, kargonuz, kargom, kargomun
kalitesiz (“low quality”) dandik, kullanışsız, dayanıksız, uyduruk, kırılacak — shoddy, unusable, flimsy, fake, about-to-break kalitesizmiş, kalitesizliği, kalitel, kalitesi, kalitesindeki — all forms of the same stem
  • Measured on 500 words drawn at random from the 2,806 vocabulary words with at least six letters and at least 20 occurrences: the share of top-5 neighbours that share the query’s first five characters is 6.5% for word2vec and 60.7% for fastText

fastText’s list contains kalitesi (“its quality”) — not a synonym: a noun form without the negation that -siz carries. The measurement counts orthographic overlap only; it does not say how appropriate the neighbours are in meaning.

Coverage 53.6% → 100%. Macro-F1: 61.39 → 61.36

word2vec fastText
distinct test forms the model has a vector for 53.6% 100%
macro-F1 in this run 61.39 61.36
  • A difference of −0.03 points: the paired bootstrap interval (−0.60 to +0.77) contains zero, and the training seed alone moves each model by about 0.5–0.8 points
  • The two models differ in more than coverage, so this result cannot be put down to out-of-vocabulary coverage alone

Producing a vector does not guarantee the right meaning. Measure intrinsic properties, coverage and task performance separately.

Where the two models disagree

  • Plain word TF-IDF and averaged fastText give the same label on 84.1% of the test documents
  • On the 1,587 documents where they differ, TF-IDF is right 1,315 times and fastText 272
  • McNemar’s test: \(\chi^2 = (|1315 - 272| - 1)^2 / (1315 + 272) = 684.2\) — a statement about error rates, that is, about accuracy
  • The macro-F1 gap needs a paired bootstrap: 10.66 points, with a 95% interval from 9.52 to 11.77

Both results hold only for the two setups examined and this test data. The interesting question is what the 272 look like.

Six disagreements, and two properties counted over all of them

review gold who is right
"en guvenli alısveriş sitesi" positive fastText — diacritics partly dropped
"çok hizli ve temi̇z tesli̇mat" positive fastText — broken diacritics mid-word
"çok sessiz çalışıyor makina ." positive fastText — “makina”, colloquial for “makine”
"sıkıcı edebi yonu cok zayif" negative TF-IDF — fastText says positive
"koku ne çok kötü ne çok iyi" negative TF-IDF — “neither… nor…”
"cok guzel! kedi bayildi buna" positive TF-IDF — fastText says negative
  • Share of tokens outside the TF-IDF vocabulary: 9.49% in the reviews only fastText gets right, 9.48% in those only TF-IDF gets right — it does not separate which model turns out right
  • What does differ is the class: 31% of the reviews only fastText gets right are negative (6.3% in the whole test set), while 98% of those only TF-IDF gets right are positive

Cosine between a review and its negation

### cosine between a review and its negation
                                                  averaged     tf-idf
                                                   fastText
ürünü çok beğendim / ürünü hiç beğenmedim            0.776       0.193
kesinlikle tavsiye ederim / … tavsiye etmem          0.919       0.474
kargo çok hızlı geldi / kargo çok yavaş geldi        0.943       0.572
fiyatına göre gayet kaliteli / … hiç kaliteli değil  0.892       0.690
bu üründen memnun kaldım / … memnun kalmadım         0.962       0.582
  • On these five opposed pairs: averaged fastText 0.78 to 0.96, word TF-IDF 0.19 to 0.69. Averaged word2vec gives 0.72–0.96 on the same pairs, so the high values are not specific to fastText’s subwords

Averaging can reduce the effect of the one word that changed, through the words the two sentences share — but the whole gap cannot be put down to averaging alone.

Why averaging can dilute negation

\[\mathbf{d}_+ - \mathbf{d}_- = \frac{1}{5}\left(\mathbf{v}_{\text{liked}} - \mathbf{v}_{\text{disliked}}\right)\]

  • A five-word review and its negation differ in one word. What matters is the ratio: one differing vector against four shared ones — at twenty words, nineteen shared ones
  • Not guaranteed: it depends on the sum of the shared word vectors dominating the words that differ
  • A subword model adds shared character n-grams: the cosine between beğendim and beğenmedim is 0.74 in fastText and 0.53 in word2vec
  • A high cosine does not rule out separation along the decision direction: “ürünü çok beğendim” gets probability 0.82 of being positive, its negation 0.02 — at cosine 0.776

Averaging can weaken some signals; these examples do not measure its share of the total 14.82-point gap.

The crossover: fewer labels, a different ordering

### macro-F1 by labelled training size (same test set of 10,000)
    n     negatives   tf-idf   tf-idf char   word2vec   fastText
   100        6        48.37       48.37       53.51      53.87
   250       16        48.53       48.84       61.59      63.10
   500       32        49.28       50.60       61.02      61.16
 1,000       63        55.40       58.55       59.52      59.93
 2,500      158        66.61       68.36       60.91      61.19
 5,000      316        71.16       70.47       61.52      60.80
10,000      632        72.55       71.29       61.80      61.56
20,000    1,264        72.42       71.26       60.94      60.96
40,000    2,529        71.95       70.39       61.39      61.36
  • Both families use the text of the 40,000 training reviews, without looking at the labels; what varies is the number of labels given to the classifier
  • Every row comes from a single subsample: with retrained vectors word2vec gives 58.23 at 100 labels and 65.21 at 250

Ten label samples per size: mean macro-F1

n tf-idf tf-idf 1-2 gram fastText tf-idf > fastText
250 48.98 48.70 60.96 0 / 10
1,000 56.57 51.68 61.96 0 / 10
1,250 59.59 53.43 62.00 2 / 10
1,500 61.87 55.63 61.90 4 / 10
1,750 63.95 57.81 61.64 10 / 10
2,500 67.52 63.42 61.55 10 / 10
5,000 70.97 71.49 61.18 10 / 10
  • At the same size, changing only which documents are labelled moves the scores by a few points: at 1,000 labels TF-IDF ranges from 55.14 to 59.80 and fastText from 61.01 to 63.15

In this setup the ordering changes between 1,250 and 1,750 labels. Read any ordering that rests on a single subsample together with that spread.

Do not read the crossover range as a transferable number

  • In the single sequence of subsamples the lines cross at about 1,500 labels: a value read between two measurements, not a measured point
  • For word bigram TF-IDF, the chapter’s highest-scoring model, the range lies further right: it beats fastText in two of ten samples at 2,000 labels and in nine at 2,500
  • All of these numbers were read off the same test set. Plot your own curve for your own task

What transfers is not the threshold but the shape: with few labels a representation learned from unlabelled text can have a head start; as labels accumulate the sparse linear model closes it.

The left edge: 250 labels, 16 of them negative

  • Word TF-IDF scores 48.53 macro-F1 and the majority rule 48.37: this model calls exactly one of the 10,000 test reviews negative
  • Averaged fastText scores 63.10 with the same 250 labels; over ten label samples the gap averages 12.0 points (between 8.2 and 16.1)
  • One explanation consistent with this pattern: 16 negative examples are 16 sparse rows drawn from a 30,195-column space; the embedding offers a shared structure learned beforehand from unlabelled text

Testing this explanation means looking at the low-label coefficients and errors, and at stronger regularisation options; this chapter did not do that.

Then the curve flattens

  • Averaged fastText scores 61.16 macro-F1 with 500 labels and 61.36 with 40,000; over ten samples the means between 500 and 5,000 labels stay in the 61.2–62.0 band
  • Word TF-IDF keeps rising until about 10,000 labels and then drops slightly in this single run
  • What was measured is not “dense representations” but this setup: averaged 100-dimensional static vectors trained from scratch

What generalises is the question itself: on this task, am I limited by labels, by the representation, or by the settings?

What it costs

On one CPU core, 40,000 training documents; single run, not repeated.

                      build the        vectorise      training
                      representation   10,000 test    matrix
tf-idf                     0.5 s          0.11 s        9.7 MB
tf-idf 1-2 gram            0.9 s          0.17 s       15.4 MB
tf-idf char_wb 3-5         2.9 s          0.72 s      144.8 MB
word2vec + averaging      14.7 s          0.11 s       16.0 MB
fastText + averaging      31.6 s          0.27 s       16.0 MB
doc2vec                    8.5 s          1.73 s       16.0 MB
  • training matrix is the memory of the training matrix only: it does not cover the model, the vocabulary or the embedding table, or peak RAM
  • The timings come from single runs; run-to-run difference stayed around ten percent, so do not compare small timing differences

Two entries deserve comment

  • Character n-grams: with the full training set, character TF-IDF scored 70.39 macro-F1 from a 144.8 MB matrix (301 non-zeros per document); word TF-IDF scored 71.95 from 9.7 MB
  • At the 1,000-label point, though, the character features were 3.2 points ahead of word TF-IDF: re-evaluate the memory–score balance against the number of labels and the spelling noise in the text
  • doc2vec must run gradient descent to vectorise a new document: 1.73 seconds for 10,000 reviews, against 0.11 for averaging — a 16-fold difference, and unlike training that cost recurs on every new document
  • In this setup doc2vec’s macro-F1, 55.13, is the lowest of the dense rows examined

No measurement here supports making doc2vec the default on this short, noisy review task. Look at the absolute number too: 0.17 milliseconds per document is still small for most services.

The hybrid, and an honest negative result

setup macro-F1
averaged fastText alone 61.36
char TF-IDF alone 70.39
char TF-IDF ⊕ averaged fastText, plain concatenation (95,039 dimensions) 71.65
char TF-IDF alone, with the same tuning budget (C = 30) 73.46
hybrid with the dense block L2-normalised and scaled (\(\alpha = 2\), C = 10) 74.69
word bigram TF-IDF, untuned 76.21
  • Adjusting the block scales puts the hybrid 3.02 points above the plain concatenation (2.00 to 4.06) — yet untuned bigram TF-IDF is still 1.52 points ahead of the tuned hybrid (0.20 to 2.80)

Reporting the hybrid as a success by comparing it only against its own parts would be exactly the kind of selective baseline this chapter is arguing against.

Choosing, on purpose: the sparse candidates

measured in this experiment choose it when
Bag of words At a fixed C = 1.0 it scored above word TF-IDF (73.51 against 71.95). Does not normalise for document length by itself One of the first baselines for short-document classification; choose its settings on a validation set
TF-IDF (words) With C tuned on validation it scored 74.71 and moved ahead of raw counts; fell behind under a shared, untuned C A general-purpose first sparse baseline; choose the vectoriser and C inside the train/validation boundary
TF-IDF (word unigrams + bigrams) Highest macro-F1 in this chapter: 76.21. With few labels it stayed below unigram TF-IDF A few thousand labels and a short-text task where local patterns may matter — sentiment, intent, triage
TF-IDF (char n-grams) 3.2 points ahead of word TF-IDF at 1,000 labels; 144.8 MB training matrix; lower macro-F1 at full data (70.39) Spelling is variable, and the memory and score you measure are acceptable

Choosing, on purpose: the dense candidates

measured in this experiment choose it when
Averaged word2vec 61.39 macro-F1 from a 100-dimensional document vector. Ahead of word TF-IDF in all ten label samples up to 1,000 labels. 46.4% of the distinct test forms are not in the vocabulary Labels are scarce, or you need dense-vector search; as a candidate to test on the target data
Averaged fastText Built a vector for every test form. Highest score at 250 labels on the single-subsample curve (63.10). Its macro-F1 could not be distinguished from word2vec’s Spelling and form variety are high; with task performance and memory measured
doc2vec (PV-DBOW) Lowest score in this run, 55.13. Inferring a vector for a new document needs extra optimisation (16× averaging) A separate experiment shows that learning the document vector directly beats averaging on your data

The choice of method should follow the task and the data budget.

Choosing a representation, in order

  1. Write down the task, the costs of a false alarm and of a miss, and the main metric
  2. Fix the data version, the train/validation/test splits and the unlabelled text
  3. Keep preprocessing, the vocabulary and IDF away from the test labels
  4. Build the cheap baselines: word counts / tf-idf, word n-grams, character n-grams
  5. With enough unlabelled text, or suitable pretrained vectors, add an embedding candidate
  6. Choose each candidate’s settings with the same validation budget
  7. Decide on the validation set: score + interval · latency · memory · explainability need
  8. Use the locked test set once, for the chosen solution

A list of questions, not a list of thresholds. The numbers this chapter observed are useful as starting expectations at step 5, not as rules at step 1.

The limit both share: word order

### two reviews, same words, opposite meaning
a = "kalite iyi fiyat kötü"      (quality good, price bad)
b = "kalite kötü fiyat iyi"      (quality bad, price good)

cosine, averaged fastText   1.0000
cosine, tf-idf              1.0000
  • Unigram TF-IDF and a plain average of word vectors both produce the same vector for the two reviews: addition does not depend on the order of its terms
  • A classifier that takes only this vector as input cannot tell the two reviews apart — not with more labels, and not with a stronger classifier

Document-level sentiment can tolerate this loss better than aspect-level tasks; for “which component is the customer complaining about” it directly blocks the task.

Bigrams keep local order, not long range

  • The bigrams of the two reviews share nothing; four of the six are in the vocabulary, and the cosine of the two reviews drops from 1.0000 to 0.3247
  • Separate representations do not mean a different, or a correct, label: this model calls both reviews negative (probabilities of being positive 0.26 and 0.15)
  • In the pump the engineer replaced last week failed, five words separate pump from failed: covering that link with one contiguous n-gram takes a seven-token sequence

For longer relations the representation has to be a function of the sequence: sequence-aware encoders, where Part II starts.

Intuition check 1 of 4

Your client has 400 labelled support tickets in Turkish and 2 million unlabelled ones. Which representation?

  • Not one model name, but a comparison plan: first settle the label of a ticket, the business metric and the test design
  • The unlabelled tickets are a resource for both families. TF-IDF is limited to vocabulary and document frequency; an embedding model also extracts co-occurrence structure
  • Here the averaged embeddings were about 11 points ahead of word TF-IDF at 500 labels (62.0 against 50.7): an expectation, not a threshold for this domain
  • Compare word n-gram, character n-gram and embedding candidates with the same validation budget; decide on further labelling from your own learning curve

Intuition check 2 of 4

A colleague reports 93% accuracy and wants to ship. What do you ask for?

  • First the accuracy of the majority rule on the same test set, and the confusion matrix
  • 93% is below the 93.68% you get on this test set by giving every input the majority class. That does not show the model is useless: this chapter’s bigram TF-IDF finds 76.3% of the negatives at 92.57% accuracy
  • So ask for the recall and precision of the negative class, and for the workload its false alarms create at the chosen decision threshold
  • The general form: what does the trivial baseline score? — ask it before you look at any reported score

Intuition check 3 of 4

fastText raised form coverage to 100%. Why did the score not go up?

  • word2vec scored 61.39 macro-F1 and fastText 61.36; the difference cannot be distinguished (paired bootstrap interval −0.60 to +0.77)
  • Two models trained by different methods: the comparison does not isolate coverage
  • Candidate explanations: fastText’s neighbours shift towards forms close in spelling; in averaged vectors the cosine between a sentence and its negation is high for both models
  • An improvement in one property of a representation shows up in the task score only if that property was what limited the task

Intuition check 4 of 4

Would 300-dimensional vectors, or pretrained ones, have changed the verdict?

  • We do not know; these conditions were not measured
  • Larger vectors, or pretrained Turkish ones, may change the neighbourhoods, the cosines of the negation pairs and the task score; neither the direction nor the size can be read off this chapter
  • The one thing that does not change is the mathematical one: the plain average of two sentences that contain the same words the same number of times is the same whatever fixed word vectors are used — the 1.0000 of the word-order example stays
  • The way to settle it is exercise 13

Interview-style questions for discussion

Three of the chapter’s six questions. Almost every answer rests on something this chapter measured.

  1. Embeddings or TF-IDF for text classification?
  2. Why can averaging word vectors fall behind counting words?
  3. How would you compare two representations beyond their headline scores?

First name the axis that decides the task: the number of labels, the variety of word forms, the need for order, or cost. Then give the relevant measurement, and say which new data or experiment would change the conclusion.

What to remember

  • Before any reported score, ask what the trivial baseline scores: here the majority rule reaches 93.68% accuracy and 48.37 macro-F1
  • A representation comparison run without an equal tuning budget may not be measuring what you wanted it to measure: “raw counts ahead of TF-IDF” depended on a shared, untuned C
  • The local result: in this setup, counts extracted from 40,000 documents were ahead of static embeddings trained from scratch on 0.9M tokens
  • Coverage and the task score are separate measurements: fastText took coverage to 100%, and macro-F1 did not change in any distinguishable way
  • With few labels the averaged embeddings were ahead; in this setup the ordering changes between 1,250 and 1,750 labels. The shape transfers, the threshold does not
  • The transferable result: a unigram bag and a plain average of word vectors give the same vector to the same words in a different order

Lab and exercises

  • The chapter has 13 exercises, each labelled Basic, Applied, Advanced or Research
  • If you do only one, do exercise 8: reproduce the paired bootstrap table from round 2, then repeat the experiment with five different train/test splits
  • Exercise 2: drop class_weight="balanced" and rerun every model
  • Exercise 13: replace round 2’s from-scratch vectors with 300-dimensional pretrained Turkish vectors and redraw the learning curve

The chapter: A Representation Bake-Off. The lab follows the chapter notebook, en/chapters/04-bake-off/04-bake-off.ipynb in the notebooks repository.