The best way to understand AI is to build it yourself — starting small. In this article, we'll build four progressively more capable AI models using toy examples. Each example teaches a real concept used in production AI systems. No prior machine learning knowledge required — just basic Python familiarity.

What We'll Build
  • 🌡️ Model 1 — Linear Regression: Predicting temperature from altitude
  • 🐾 Model 2 — Logistic Regression: Is this a cat or a dog? (binary classification)
  • 🔢 Model 3 — Neural Network from scratch: Recognizing handwritten digits
  • 💬 Model 4 — Tiny Language Model: Predicting the next character
Setup — Install These Libraries
  • pip install numpy matplotlib scikit-learn torch
  • Python 3.8+ recommended. All examples run in a Jupyter notebook or any Python file.

Model 1 — Linear Regression: Learning a Straight Line

The concept: Linear regression finds the best straight line through data. Every AI model at its core is doing something similar — finding patterns in numbers.

The toy problem: Temperature drops about 6.5°C for every 1,000m increase in altitude. Can a model learn this from data?

import numpy as np import matplotlib.pyplot as plt # Our toy dataset: altitude (meters) → temperature (°C) altitude = np.array([0, 500, 1000, 1500, 2000, 2500, 3000]) temp = np.array([20, 17, 14, 10, 7, 3, 0]) # === THE MODEL: y = weight * x + bias === # We find weight and bias using the closed-form solution X = np.column_stack([altitude, np.ones(len(altitude))]) # add bias column weight, bias = np.linalg.lstsq(X, temp, rcond=None)[0] print(f"Learned: temp = {weight:.4f} × altitude + {bias:.2f}") # Output: temp = -0.0065 × altitude + 20.02 ← model learned the real rule! # Predict temperature at 1,800m prediction = weight * 1800 + bias print(f"Predicted temp at 1800m: {prediction:.1f}°C") # → ~8.3°C
The model learned the rule "temperature drops 6.5°C per 1000m" purely from data — nobody told it the formula.

What This Teaches

Key Concepts in Linear Regression
  • Weight — how much the output changes per unit of input (slope)
  • Bias — the baseline output when input is zero (intercept)
  • Training — finding the weight and bias that minimize prediction error
  • Generalization — the model can now predict for altitudes it never saw
📚 Further Reading

Model 2 — Logistic Regression: Yes or No?

The concept: Classification — deciding which category something belongs to. The backbone of spam filters, fraud detection, medical diagnosis, and more.

The toy problem: Given an animal's weight and height, predict: cat or dog?

from sklearn.linear_model import LogisticRegression from sklearn.preprocessing import StandardScaler import numpy as np # Toy dataset: [weight_kg, height_cm] → label (0=cat, 1=dog) X = np.array([ [4.0, 25], # cat [3.5, 23], # cat [5.0, 28], # cat [25.0, 55], # dog [30.0, 60], # dog [20.0, 50], # dog [6.0, 30], # cat (larger cat) [18.0, 48], # dog (smaller dog) ]) y = np.array([0, 0, 0, 1, 1, 1, 0, 1]) # 0=cat, 1=dog # Scale features (important! keeps numbers in similar range) scaler = StandardScaler() X_scaled = scaler.fit_transform(X) # Train the model model = LogisticRegression() model.fit(X_scaled, y) # Predict new animals new_animal = scaler.transform([[8.0, 35]]) # 8kg, 35cm tall prediction = model.predict(new_animal) probability = model.predict_proba(new_animal) print(f"Prediction: {'Dog' if prediction[0]==1 else 'Cat'}") print(f"Confidence: Cat={probability[0][0]:.0%}, Dog={probability[0][1]:.0%}")

What's Actually Happening

Logistic regression applies the sigmoid function to the weighted sum, squashing the output to a probability between 0 and 1:

σ(z) = 1 / (1 + e^(-z)) Where z = weight₁ × weight_kg + weight₂ × height_cm + bias If σ(z) > 0.5 → Dog If σ(z) ≤ 0.5 → Cat
📚 Further Reading

Model 3 — Neural Network from Scratch

The concept: A multi-layer neural network learns complex non-linear patterns by stacking simple operations. This is the foundation of everything in modern deep learning.

The toy problem: Learn the XOR function — the problem that stumped the Perceptron in 1969.

import numpy as np # XOR truth table: one layer CAN'T solve this, two layers CAN X = np.array([[0,0], [0,1], [1,0], [1,1]]) # inputs y = np.array([[0], [1], [1], [0]]) # expected outputs (XOR) # === NETWORK: 2 inputs → 4 hidden neurons → 1 output === np.random.seed(42) W1 = np.random.randn(2, 4) * 0.5 # weights: input → hidden b1 = np.zeros((1, 4)) # biases for hidden layer W2 = np.random.randn(4, 1) * 0.5 # weights: hidden → output b2 = np.zeros((1, 1)) # bias for output def sigmoid(z): return 1 / (1 + np.exp(-z)) def sigmoid_grad(z): return sigmoid(z) * (1 - sigmoid(z)) # Training loop lr = 0.5 for epoch in range(10000): # Forward pass z1 = X @ W1 + b1 # hidden pre-activation a1 = sigmoid(z1) # hidden activation z2 = a1 @ W2 + b2 # output pre-activation output = sigmoid(z2) # final prediction # Loss (mean squared error) loss = np.mean((output - y) ** 2) # Backward pass (backpropagation) d_out = (output - y) * sigmoid_grad(z2) dW2 = a1.T @ d_out db2 = d_out.sum(axis=0) d_h = d_out @ W2.T * sigmoid_grad(z1) dW1 = X.T @ d_h db1 = d_h.sum(axis=0) # Update weights W2 -= lr * dW2; b2 -= lr * db2 W1 -= lr * dW1; b1 -= lr * db1 if epoch % 2000 == 0: print(f"Epoch {epoch:5d} | Loss: {loss:.4f}") # Final predictions print("\nFinal Predictions (should be ~0, 1, 1, 0):") print(np.round(output, 2))

After training, the network correctly outputs near-zero for 0 XOR 0 and 1 XOR 1, and near-one for 0 XOR 1 and 1 XOR 0. The key: the hidden layer learned to create intermediate representations the output layer could use — something a single layer never could.

📚 Further Reading

Model 4 — Tiny Character-Level Language Model

The concept: A language model learns the statistical patterns of text and can generate new text that follows those patterns. This is the ancestor of GPT.

The toy problem: Train on a tiny text and predict the next character.

import torch import torch.nn as nn import torch.nn.functional as F # Our training text (toy example — works better with more text!) text = "hello world. the quick brown fox jumps over the lazy dog. " * 20 # Build vocabulary chars = sorted(set(text)) vocab = len(chars) ch2ix = {c: i for i, c in enumerate(chars)} ix2ch = {i: c for c, i in ch2ix.items()} encode = lambda s: [ch2ix[c] for c in s] decode = lambda l: ''.join([ix2ch[i] for i in l]) data = torch.tensor(encode(text), dtype=torch.long) # Simple single-layer language model (tiny transformer-like) class TinyLM(nn.Module): def __init__(self, vocab_size, embed_dim=32, context=8): super().__init__() self.embed = nn.Embedding(vocab_size, embed_dim) self.linear = nn.Linear(embed_dim * context, vocab_size) self.context = context def forward(self, x): B, T = x.shape embeds = self.embed(x).view(B, -1) # flatten embeddings return self.linear(embeds) # Training context = 8 model = TinyLM(vocab_size=vocab, context=context) optim = torch.optim.Adam(model.parameters(), lr=1e-2) for step in range(3000): # Random batch of sequences ix = torch.randint(len(data) - context - 1, (32,)) xb = torch.stack([data[i:i+context] for i in ix]) yb = torch.stack([data[i+context] for i in ix]) logits = model(xb) loss = F.cross_entropy(logits, yb) optim.zero_grad(); loss.backward(); optim.step() if step % 500 == 0: print(f"Step {step}: loss={loss.item():.3f}") # Generate text def generate(seed, n=50): idx = torch.tensor([encode(seed[-context:])], dtype=torch.long) out = seed for _ in range(n): logits = model(idx) probs = F.softmax(logits, dim=-1) next_c = torch.multinomial(probs, 1).item() out += ix2ch[next_c] idx = torch.cat([idx[:,1:], torch.tensor([[next_c]])], dim=1) return out print("\nGenerated:", generate("the ", n=60))
This tiny model uses exactly the same principles as GPT — embeddings, context windows, next-token prediction. GPT-3 is this model, but 175 billion times larger.
📚 Further Reading & Next Steps

The Progression: What You've Built

ModelTypeWhat It LearnsReal-World Use
Linear RegressionSupervisedLinear relationshipsHouse prices, sales forecasting
Logistic RegressionClassificationDecision boundariesSpam filters, fraud detection
Neural NetworkDeep LearningNon-linear patternsImage recognition, recommendations
Tiny LMGenerative AILanguage patternsChatGPT, Claude, Copilot
← Math Behind LLMs Next: JEPA vs LLMs →