Train a tracker classifier
Why it matters Blocklists are written by people and always lag behind: new trackers appear every week. A classifier learns what trackers behave like and can flag ones no list has named yet. Building a small one yourself shows what such tools can do, and how to judge them honestly.
Think of an experienced doorman. Nobody gave them a list of every troublemaker, but they have learned the signs, and they weigh them together: one sign means little, three together mean a lot. A classifier does the same with numbers.
The data
tracker_dataset(seed) in watched.leg7b_web crawls a simulated web and measures six behaviours for every third party it saw, each scaled from 0 to 1: how many sites it is on, how often it sets cookies, how often it sends tiny images (pixels), how often its URLs carry long ID-like values, how often they carry the page address, and how often its responses are small. Each row also has a label y: 1 if the blocklist calls it a tracker, 0 if not.
from watched.leg7b_web import tracker_dataset
data = tracker_dataset(seed=2)
print(data["features"])
row = data["train"][0]
print(row["site"], row["x"], row["y"])Logistic regression in three ideas
Weight: a number saying how much one feature counts as evidence. Positive means "more of this, more tracker-like".
1. Weigh the evidence. Multiply each feature by its weight, add them up, and add a bias (a starting lean): z = w1*x1 + w2*x2 + ... + bias.
2. Squash it into a probability. The sigmoid function 1 / (1 + math.exp(-z)) turns any number into one between 0 and 1. Big positive z gives nearly 1, big negative nearly 0, zero gives 0.5.
3. Learn from mistakes. Start with all weights 0. For each training row, compute error = prediction - y. Then nudge each weight: w = w - lr * error * x, and the bias by bias - lr * error. lr (the learning rate) sets the nudge size. Going through all rows once is an epoch; repeat for many epochs.
import math
def sigmoid(z):
return 1 / (1 + math.exp(-z))
print(sigmoid(0), round(sigmoid(4), 3), round(sigmoid(-4), 3)) # 0.5 0.982 0.018Judging it: precision and recall
You met these in Leg 2. Say "tracker" when the probability is at least 0.5. Then:
- Precision = true trackers flagged / everything flagged. Low precision breaks harmless sites.
- Recall = true trackers flagged / all true trackers. Low recall lets trackers through.
Always measure on held-out rows, ones the model never trained on. Scoring on the training rows flatters it. The tests go further and score your model on a crawl of a web it has never seen.
A common misconception: "The model learned what a tracker is." It learned to agree with the blocklist that labelled the data, mistakes included. If the list is wrong about a site, the model is taught to be wrong too. Lesson 149 comes back to this.
In other languages
The prediction and one learning step, elsewhere:
// JavaScript
const predict = (w, b, x) => 1 / (1 + Math.exp(-(x.reduce((s, v, i) => s + v * w[i], 0) + b)));
function step(w, b, row, lr) {
const err = predict(w, b, row.x) - row.y;
return [w.map((wi, i) => wi - lr * err * row.x[i]), b - lr * err];
}// C#
double Predict(double[] w, double b, double[] x) {
double z = b;
for (int i = 0; i < x.Length; i++) z += w[i] * x[i];
return 1.0 / (1.0 + Math.Exp(-z));
}Recap
- A classifier learns tracker behaviour, so it can flag trackers no list names yet.
- Logistic regression: weigh the features, squash with the sigmoid, nudge weights by the error.
- Judge it by precision and recall on rows it never trained on.
- It learns the labels' view of the world, mistakes included.
Write three functions, in plain Python:
predict(weights, bias, x): the sigmoid1 / (1 + math.exp(-z))ofz = sum(weights[i] * x[i]) + bias.train(rows, epochs=100, lr=0.5): start with zero weights (one per feature) and bias 0.0; for each epoch, for each row in order, computeerror = predict(...) - row["y"], then update every weight by- lr * error * x[i]and the bias by- lr * error. Return(weights, bias).precision_recall(weights, bias, rows, threshold=0.5): flag a row when the prediction is at least the threshold; return(precision, recall), using 0.0 when a division would be by zero.
weights, bias from data["train"] and test_precision, test_recall on data["test"]. Tests score your model on a simulated web it never saw: at least 0.80 for both.
This lesson is locked
Lessons open one at a time. Finish the previous lesson to unlock this one.