The baseline and the first model
The scene: 0.85, is that good?
A student runs up to you: "Teacher, my model got 85% accuracy!"
You ask one question: "What percentage of your data is 'yes'?"
They don't know.
If the answer is 85%, their model learned nothing. A model that says "yes to everyone" scores exactly the same.
This chapter is about one number: the pass mark. It is step 6 of the protocol from Chapter 1.
Column names
The main data is synthetic Tashkent rental listings, and its column names are Uzbek. tuman district, xonalar rooms, maydon_m2 area in m², qavat floor, qavatlar_soni floors in the building, metro_km distance to the metro, mebel furnished (0/1), qurilgan_yil year built, narx_usd monthly rent in USD. The target ijaraga_berildi_30k means rented within 30 days.
Without a thermometer, "hot" means nothing
accuracy = 0.85 means nothing on its own. It only means something in comparison.
Compared to what? To the dumbest possible model, one that learns nothing.
If you don't beat it, your complex model, your two weeks of work and your GPU time were wasted.
import pandas as pd from sklearn.model_selection import train_test_split from sklearn.dummy import DummyClassifier from sklearn.metrics import accuracy_score, f1_score df = pd.read_csv(DATA / "uy_ijara.csv") num = ["xonalar", "maydon_m2", "qavat", "qavatlar_soni", "metro_km", "mebel", "qurilgan_yil"] X, y = df[num + ["tuman"]], df["ijaraga_berildi_30k"] X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.3, random_state=42, stratify=y) print(f"Target ratio: {y.mean():.1%} 'yes'")
Target ratio: 50.7% 'yes'
DummyClassifier: four dumb models
It comes ready-made in sklearn, with several kinds of "dumb".
strategies = { "most_frequent": "Always says the most common class", "stratified": "Guesses at random, following the class ratio", "uniform": "Flips a coin (equal odds)", "prior": "Predicts the same as most_frequent", } rows = [] for s, note in strategies.items(): d = DummyClassifier(strategy=s, random_state=42).fit(X_tr[num], y_tr) b = d.predict(X_te[num]) rows.append({"Strategy": s, "Accuracy": round(accuracy_score(y_te, b), 4), "F1": round(f1_score(y_te, b, zero_division=0), 4), "What it does": note}) show(pd.DataFrame(rows))
| Strategy | Accuracy | F1 | What it does |
|---|---|---|---|
| most_frequent | 0.506 | 0.672 | Always says the most common class |
| stratified | 0.494 | 0.494 | Guesses at random, following the class ratio |
| uniform | 0.522 | 0.525 | Flips a coin (equal odds) |
| prior | 0.506 | 0.672 | Predicts the same as most_frequent |
Which one do we take? most_frequent, because it is the "strongest dummy". The other strategies come out lower, so it gives the strictest pass mark.
baseline = accuracy_score(y_te, DummyClassifier(strategy="most_frequent") .fit(X_tr[num], y_tr).predict(X_te[num])) print(f"BASELINE = {baseline:.4f}") print() print("Write this number in your notebook. Every later result is compared to it.")
BASELINE = 0.5056 Write this number in your notebook. Every later result is compared to it.
Now a real model
from sklearn.pipeline import Pipeline from sklearn.compose import ColumnTransformer from sklearn.preprocessing import StandardScaler, OneHotEncoder from sklearn.impute import SimpleImputer from sklearn.linear_model import LogisticRegression pipe = Pipeline([ ("prep", ColumnTransformer([ ("num", Pipeline([("impute", SimpleImputer(strategy="median")), ("scale", StandardScaler())]), num), ("cat", OneHotEncoder(handle_unknown="ignore"), ["tuman"]), ])), ("model", LogisticRegression(max_iter=2000)), ]).fit(X_tr, y_tr) model_acc = accuracy_score(y_te, pipe.predict(X_te)) print(f"BASELINE: {baseline:.4f}") print(f"MODEL : {model_acc:.4f}") print(f"Gap : {model_acc - baseline:+.4f}") print() print("Verdict:", "PASSED, the model really learned" if model_acc > baseline + 0.02 else "FAILED")
BASELINE: 0.5056 MODEL : 0.7778 Gap : +0.2722 Verdict: PASSED, the model really learned
On imbalanced data the baseline fools you
Above, the target was 50/50, so the baseline was low (0.5) and easy to beat.
Now let's look at another dataset: whether students graduate.
Column names in talaba_bitirdi.csv
This synthetic student dataset also has Uzbek column names: kirish_bali entrance score, davomat_foiz attendance %, uy_vazifa_bali homework score, ishlaydi has a job (0/1), masofa_km distance from home in km. The target bitirdi means graduated.
td = pd.read_csv(DATA / "talaba_bitirdi.csv", keep_default_na=False, na_values=[""]) feats = ["kirish_bali", "davomat_foiz", "uy_vazifa_bali", "ishlaydi", "masofa_km"] Xt, yt = td[feats], td["bitirdi"] Xt_tr, Xt_te, yt_tr, yt_te = train_test_split(Xt, yt, test_size=0.3, random_state=42, stratify=yt) baseline_t = accuracy_score(yt_te, DummyClassifier(strategy="most_frequent") .fit(Xt_tr, yt_tr).predict(Xt_te)) model_t = LogisticRegression(max_iter=2000).fit(Xt_tr, yt_tr) acc_t = accuracy_score(yt_te, model_t.predict(Xt_te)) print(f"Target ratio : {yt.mean():.1%} graduated") print(f"BASELINE : {baseline_t:.4f}") print(f"MODEL : {acc_t:.4f}") print(f"Gap : {acc_t - baseline_t:+.4f}")
Target ratio : 88.6% graduated BASELINE : 0.8852 MODEL : 0.8963 Gap : +0.0111
This is the trap your student falls into.
The model gives 0.8963. You could write "almost 90 percent!" in a report. But the baseline is 0.8852, so the model's entire contribution is 0.011: three rows out of 270.
When the baseline is high, drop accuracy
If the baseline is above 0.85, using accuracy as your measure is almost meaningless, because the noise is at the same level.
What to do: switch to F1, recall or PR-AUC. Chapter 9 is about this.
print("The same model, with other metrics:") b = model_t.predict(Xt_te) print(f" Accuracy : {accuracy_score(yt_te, b):.4f} (baseline {baseline_t:.4f}, so little gain)") print(f" F1 : {f1_score(yt_te, b):.4f}") from sklearn.metrics import recall_score, precision_score print(f" Precision: {precision_score(yt_te, b, zero_division=0):.4f}") print(f" Recall : {recall_score(yt_te, b, zero_division=0):.4f}")
The same model, with other metrics: Accuracy : 0.8963 (baseline 0.8852, so little gain) F1 : 0.9447 Precision: 0.8951 Recall : 1.0000
The baseline in regression
For regression there is DummyRegressor. It always predicts the mean (or the median).
from sklearn.dummy import DummyRegressor from sklearn.linear_model import Ridge from sklearn.metrics import root_mean_squared_error, r2_score yn = df["narx_usd"] Xn_tr, Xn_te, yn_tr, yn_te = train_test_split(X, yn, test_size=0.3, random_state=42) d_reg = DummyRegressor(strategy="mean").fit(Xn_tr[num], yn_tr) rmse_baseline = root_mean_squared_error(yn_te, d_reg.predict(Xn_te[num])) pipe_reg = Pipeline([ ("prep", ColumnTransformer([ ("num", Pipeline([("impute", SimpleImputer(strategy="median")), ("scale", StandardScaler())]), num), ("cat", OneHotEncoder(handle_unknown="ignore"), ["tuman"]), ])), ("model", Ridge()), ]).fit(Xn_tr, yn_tr) pred_reg = pipe_reg.predict(Xn_te) print(f"BASELINE (always the mean): RMSE = {rmse_baseline:.1f} R2 = {r2_score(yn_te, d_reg.predict(Xn_te[num])):.4f}") print(f"MODEL (Ridge) : RMSE = {root_mean_squared_error(yn_te, pred_reg):.1f} R2 = {r2_score(yn_te, pred_reg):.4f}")
BASELINE (always the mean): RMSE = 228.6 R2 = -0.0044 MODEL (Ridge) : RMSE = 71.2 R2 = 0.9025
R2 already has the baseline built in
R2 = 0 means "the same as predicting the mean". R2 < 0 means "worse than the mean".
So in regression R2 already measures against the baseline. But comparing RMSE with the baseline is useful: it answers how much better in business language.
The business baseline: the most important one
The dummy model is the statistical baseline. But a real project has a third baseline, and it matters most:
Who or what does this job right now?
At the agency, an experienced employee guesses "this flat will go fast" and is right 70% of the time. Your model gives 0.7778.
You are well above the statistical baseline (0.5056). But above the business baseline (0.70) by only 0.08. That second number is the one the manager cares about.
show(pd.DataFrame([ {"Baseline": "Dummy (most_frequent)", "What it shows": "Whether the model learned anything at all", "Who asks": "You"}, {"Baseline": "A simple rule (if price < X)", "What it shows": "Whether ML's complexity is needed", "Who asks": "A senior engineer"}, {"Baseline": "The current process (a person/old system)", "What it shows": "Whether the project pays off", "Who asks": "The manager: the most important question"}, ]))
| Baseline | What it shows | Who asks |
|---|---|---|
| Dummy (most_frequent) | Whether the model learned anything at all | You |
| A simple rule (if price < X) | Whether ML's complexity is needed | A senior engineer |
| The current process (a person/old system) | Whether the project pays off | The manager: the most important question |
Let's try a simple rule too; sometimes it makes ML unnecessary.
import numpy as np cutoff = X_tr["maydon_m2"].median() rule = (X_te["maydon_m2"] < cutoff).astype(int) # a guess that smaller flats go faster print(f"Rule: if area < {cutoff:.0f} m2, 'goes fast'") print(f" Accuracy: {accuracy_score(y_te, rule):.4f}") print(f" F1 : {f1_score(y_te, rule, zero_division=0):.4f}") print() print(f"Comparison -> dummy {baseline:.4f} | rule {accuracy_score(y_te, rule):.4f} | model {model_acc:.4f}")
Rule: if area < 55 m2, 'goes fast' Accuracy: 0.7694 F1 : 0.7701 Comparison -> dummy 0.5056 | rule 0.7694 | model 0.7778
What we learned today
show(pd.DataFrame([ {"Concept": "Why a baseline", "In one sentence": "Without it, an accuracy number means nothing"}, {"Concept": "DummyClassifier", "In one sentence": "most_frequent is the strictest pass mark"}, {"Concept": "DummyRegressor", "In one sentence": "Always the mean; that is the point where R2=0"}, {"Concept": "A high baseline", "In one sentence": "Above 0.85, drop accuracy and move to F1"}, {"Concept": "The simple-rule baseline", "In one sentence": "Answers whether you need ML at all"}, {"Concept": "The business baseline", "In one sentence": "Who does this now, and how well"}, ]))
| Concept | In one sentence |
|---|---|
| Why a baseline | Without it, an accuracy number means nothing |
| DummyClassifier | most_frequent is the strictest pass mark |
| DummyRegressor | Always the mean; that is the point where R2=0 |
| A high baseline | Above 0.85, drop accuracy and move to F1 |
| The simple-rule baseline | Answers whether you need ML at all |
| The business baseline | Who does this now, and how well |
Next chapter: accuracy throws two very different mistakes into one number. We take it apart into the four cells of the confusion matrix.
Questions about this chapter?
The course, homework review and the other five books are in Uzbek. Questions in English are welcome on Telegram.