Skip to main content

Lab 1 — Build a k-nearest-neighbor classifier

Objective

By the end of this lab you will have a runnable Python program, knn.py, that loads a small labeled flower dataset from CSV, splits it into train and test sets, classifies each test flower by a majority vote of its k nearest neighbors, and reports the classifier's accuracy on the held-out set. It is a complete supervised-learning loop built entirely by hand — no NumPy, no pandas, no scikit-learn — using only the Python standard library (csv, math, collections, pathlib), so it costs nothing and runs anywhere.

Skills exercised:

  • Loading a labeled dataset from CSV into feature vectors and labels
  • Splitting data into disjoint train and test sets deterministically
  • Implementing Euclidean distance and k-nearest-neighbor voting from scratch
  • Measuring a classifier with held-out accuracy

Prerequisites and setup

Prerequisites: the three lessons in this module — Python foundations for AI, Working with datasets, and From data to a first model.

Setup: you need Python 3.10 or newer. There are no packages to install; everything uses the standard library. Verify your version and create a working folder.

python3 --version
mkdir ai101-knn-lab && cd ai101-knn-lab

Expected output from the version command is a line like Python 3.11.6. If the command is not found, install Python from python.org before continuing. In the steps below, use python3 on macOS/Linux and python on Windows.

note

Every step writes or edits files in this one folder. If you close your terminal and come back, re-run the cd ai101-knn-lab command to return to it; all your files are still there.

Step 1 — Create the dataset

Create the labeled dataset your classifier will learn from: two flower species described by petal length and petal width, twelve examples each.

Save this exactly as flowers.csv:

petal_length,petal_width,species
1.4,0.2,alpina
1.3,0.2,alpina
1.5,0.2,alpina
1.4,0.3,alpina
1.7,0.4,alpina
1.5,0.1,alpina
1.6,0.2,alpina
1.4,0.1,alpina
1.2,0.2,alpina
1.5,0.4,alpina
1.3,0.3,alpina
1.6,0.2,alpina
4.7,1.4,montana
4.5,1.5,montana
4.9,1.5,montana
4.0,1.3,montana
4.6,1.5,montana
4.5,1.3,montana
4.7,1.6,montana
5.0,1.7,montana
4.8,1.4,montana
4.3,1.3,montana
5.1,1.6,montana
4.4,1.4,montana

The two species separate cleanly in these two features — alpina has small petals, montana has large ones — which is what will let a simple classifier score well.

Checkpoint: confirm the file has all 24 data rows.

python3 -c "import csv; print(sum(1 for _ in csv.DictReader(open('flowers.csv'))))"

You should see 24. If you see a smaller number, a row is missing — re-save the file with all 24 lines below the header. If you see a FileNotFoundError, check that the file is saved in the current folder as exactly flowers.csv.

Step 2 — Load the dataset into features and labels

Goal: create knn.py with a loader that reads each CSV row into a ((petal_length, petal_width), species) pair — the (X, y) shape from the lessons, one sample per row.

Create knn.py with this content:

import csv
import math
from collections import Counter
from pathlib import Path

K = 3


def load_dataset(path):
"""Load rows as ((petal_length, petal_width), species) tuples."""
samples = []
with open(path, newline="", encoding="utf-8") as f:
for row in csv.DictReader(f):
features = (float(row["petal_length"]), float(row["petal_width"]))
samples.append((features, row["species"]))
return samples

Checkpoint: load the dataset and count the samples.

python3 -c "import knn; from pathlib import Path; print(len(knn.load_dataset(Path('flowers.csv'))))"

You should see 24. If you get ModuleNotFoundError: No module named 'knn', you are running from the wrong folder — cd into ai101-knn-lab where knn.py lives.

Step 3 — Split into train and test

Goal: add a deterministic split so evaluation stays honest. Every third sample (indices 0, 3, 6, ...) becomes a test example; the rest are training data.

Add this function to knn.py:

def train_test_split(samples):
"""Split deterministically: every 3rd row (index 0, 3, 6, ...) is test."""
train, test = [], []
for index, sample in enumerate(samples):
if index % 3 == 0:
test.append(sample)
else:
train.append(sample)
return train, test

Checkpoint: split the dataset and print the two sizes.

python3 -c "import knn; from pathlib import Path; tr, te = knn.train_test_split(knn.load_dataset(Path('flowers.csv'))); print(len(tr), len(te))"

You should see 16 8 — sixteen training samples and eight test samples. If you see 24 0, the split condition is wrong; confirm the test branch uses index % 3 == 0.

Step 4 — Implement distance and k-NN prediction

Goal: add the classifier core — Euclidean distance between two feature vectors, and a predict that labels a new point by a majority vote of its k nearest training neighbors.

Add these two functions to knn.py:

def distance(a, b):
"""Euclidean distance between two feature tuples."""
return math.sqrt(sum((x - y) ** 2 for x, y in zip(a, b)))


def predict(train, features, k=K):
"""Predict a label by majority vote of the k nearest training samples."""
ranked = sorted(train, key=lambda sample: distance(sample[0], features))
top_labels = [label for _, label in ranked[:k]]
return Counter(top_labels).most_common(1)[0][0]

Checkpoint: predict a large-petal flower and a small-petal flower.

python3 -c "import knn; from pathlib import Path; tr, _ = knn.train_test_split(knn.load_dataset(Path('flowers.csv'))); print(knn.predict(tr, (4.6, 1.5)), knn.predict(tr, (1.4, 0.2)))"

You should see montana alpina — the large petals classify as montana and the small ones as alpina. If both print the same label, check that distance squares the difference and that predict slices ranked[:k], not the whole list.

Step 5 — Score the classifier and run it end to end

Goal: add an accuracy metric and a main block that loads, splits, evaluates, and prints a result, then run the whole program.

Add this to the bottom of knn.py:

def accuracy(train, test, k=K):
"""Fraction of test samples the classifier labels correctly."""
if not test:
return 0.0
correct = sum(
1 for features, label in test if predict(train, features, k) == label
)
return correct / len(test)


if __name__ == "__main__":
base = Path(__file__).parent
samples = load_dataset(base / "flowers.csv")
train, test = train_test_split(samples)
print("loaded", len(samples))
print("train", len(train), "test", len(test))
print("accuracy", round(accuracy(train, test), 3))

Checkpoint: run the program.

python3 knn.py

You should see three lines ending with accuracy 1.0 — the classifier labels all eight held-out flowers correctly because the two species separate cleanly. If the accuracy is below 1.0, re-check distance and the vote in predict; if you get a FileNotFoundError, run the command from the ai101-knn-lab folder.

Validation

Verify the whole build against the objective: a working classifier that clears a sensible accuracy bar on the held-out set. Save this as validate.py:

from pathlib import Path

import knn

MIN_ACCURACY = 0.90

base = Path(__file__).parent
samples = knn.load_dataset(base / "flowers.csv")
train, test = knn.train_test_split(samples)
acc = knn.accuracy(train, test)

checks = {
"loaded 24 samples": len(samples) == 24,
"16 train / 8 test": len(train) == 16 and len(test) == 8,
"accuracy at or above 0.90": acc >= MIN_ACCURACY,
}

if all(checks.values()):
print("PASSED - k-NN classifier scored %.2f on the held-out test set" % acc)
else:
failed = [name for name, ok in checks.items() if not ok]
print("FAILED - checks not passed: %s (accuracy=%.2f)" % (failed, acc))

Run it:

python3 validate.py

Checkpoint: the command prints PASSED - k-NN classifier scored 1.00 on the held-out test set. If it prints FAILED, the listed check names tell you what broke: a wrong sample count points to the flowers.csv file (Step 1), a wrong split points to Step 3, and a low accuracy points to distance or predict (Step 4).

Teardown

Nothing is billable — this lab runs entirely on your machine. Keep the ai101-knn-lab folder if you want to reuse knn.py as a reference for the course challenges; otherwise remove it with rm -rf ai101-knn-lab (macOS/Linux) or Remove-Item -Recurse ai101-knn-lab (Windows).

Troubleshooting

SymptomLikely causeFix
ModuleNotFoundError: No module named 'knn'Running from the wrong foldercd into ai101-knn-lab, where knn.py lives
FileNotFoundError: 'flowers.csv'Dataset missing or misnamedRe-save the Step 1 file as exactly flowers.csv in the folder
Checkpoint prints a number other than 24A dataset row is missingRe-save flowers.csv with all 24 data rows below the header
Split prints 24 0 in Step 3Test branch condition invertedEnsure the test branch uses index % 3 == 0
Both predictions print the same labelDistance or slice wrong in Step 4Confirm distance squares differences and predict uses [:k]
Validation prints FAILEDAn earlier step's output is staleRe-check the named failing step, then re-run python3 validate.py