AI Hints
Machine Learning · Lesson 74 of 85

From Scratch: K-Nearest Neighbours

Build it to understand it.

⏱ 2 min read▶ Video at 2:34:04🎓 Free
Watch this lesson in the Machine Learning courseWatch on YouTube ↗Full Machine Learning course

The physicist Richard Feynman left a line on his blackboard. What I cannot create, I do not understand. You've used these algorithms through a library. Now, let's take off the lid, and build the core ones ourselves, in plain NumPy. Interviewers love this too.

From Scratch: K-Nearest Neighbours
From the video: From Scratch: K-Nearest Neighbours
  • What I cannot create, I do not understand
  • Take off the lid
  • Interviewers love this

KNN in twenty lines

We write a class with the same fit and predict methods as scikit learn. Fit does almost nothing, it simply remembers the data. In predict, for each row, we compute the distance to every stored point. Argsort finds the nearest K, and bincount tallies their votes. Twenty lines, with no libraries doing the real work. Run it, on real wine data. We get very high accuracy. You can now see why prediction is slow. There's a full loop over the data, for each new row.

knn_scratch.pypython
import numpy as np
from sklearn.datasets import load_wine
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler

class KNN:
    def __init__(self, k=5):
        self.k = k

    def fit(self, X, y):
        self.X, self.y = X, y
        return self

    def predict(self, X):
        out = []
        for row in X:
            dist = np.sqrt(((self.X - row) ** 2).sum(axis=1))
            nearest = self.y[np.argsort(dist)[:self.k]]
            out.append(np.bincount(nearest).argmax())
        return np.array(out)

X, y = load_wine(return_X_y=True)
X = StandardScaler().fit_transform(X)
X_tr, X_te, y_tr, y_te = train_test_split(X, y, random_state=0)

print("Accuracy:", (KNN(5).fit(X_tr, y_tr).predict(X_te) == y_te).mean().round(3))
Output
Accuracy: 0.978

Logistic regression from scratch

Now logistic regression. Fit starts all of the weights at zero. Then it loops. Compute the chances, using the sigmoid. Then these two lines are gradient descent, with the gradient of a loss called log loss. Notice how similar it is to what we wrote for linear regression. Only the sigmoid is new. Run it, and we get high accuracy, from fifteen lines of real logic. Library versions add many extras, but the heart is exactly what you see here.

logistic_scratch.pypython
import numpy as np
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler

class Logistic:
    def __init__(self, lr=0.1, steps=2000):
        self.lr, self.steps = lr, steps

    def fit(self, X, y):
        self.w, self.b = np.zeros(X.shape[1]), 0.0
        for _ in range(self.steps):
            p = self.chance(X)
            self.w -= self.lr * X.T @ (p - y) / len(y)
            self.b -= self.lr * (p - y).mean()
        return self

    def chance(self, X):
        return 1 / (1 + np.exp(-(X @ self.w + self.b)))

    def predict(self, X):
        return (self.chance(X) >= 0.5).astype(int)

X, y = load_breast_cancer(return_X_y=True)
X = StandardScaler().fit_transform(X)
X_tr, X_te, y_tr, y_te = train_test_split(X, y, random_state=0)

print("Accuracy:", (Logistic().fit(X_tr, y_tr).predict(X_te) == y_te).mean().round(3))
Output
Accuracy: 0.972

Summary

Two algorithms, built from nothing. Notice that once you know gradient descent, logistic regression was easy. Next, we build the two models that many people consider to be a black box, k means and a real decision tree.

Key takeaways
  • KNN: remember, measure, vote
  • Logistic: sigmoid plus descent
  • You can build what you use
Next up: From Scratch: K-Means and Trees — no libraries, no magic