// Comprehensive Study Guide · GNU Octave Edition

Artificial Intelligence
— Basics & Essentials

From first principles through neural networks: rigorous mathematics, proofs, GNU Octave code, and interactive demonstrations for the serious student.

Search Algorithms Propositional Logic Probability Theory Linear Algebra Neural Networks Gradient Descent Backpropagation Decision Trees GNU Octave
01

What is Artificial Intelligence?

Artificial Intelligence (AI) is the field of computer science dedicated to building systems that exhibit behaviour ordinarily associated with human intelligence: reasoning, learning, perception, natural language understanding, and decision-making.

Definition — Intelligence (Russell & Norvig)
An agent is intelligent if it acts so as to maximise its expected utility given its goals and perceptual history.

Four Schools of AI

AxisThink HumanlyThink Rationally
Act HumanlyCognitive modelling (Turing Test)General Problem Solver
Act RationallyEmotion-driven agentsRational Agent (mainstream AI)

The Turing Test

Alan Turing (1950) proposed: if an interrogator cannot reliably distinguish a machine's text responses from a human's, the machine is deemed to exhibit intelligent behaviour. Formally, let I be an interrogator, H human, M machine.

P(correct | interrogator) < 0.5 + ε ⟹ machine passes

Branches of AI

🔍
Search & Planning — finding sequences of actions to achieve goals (BFS, A*, MCTS).
Knowledge Representation — encoding facts so a machine can reason over them.
Machine Learning — programs that improve performance from experience.
Natural Language Processing — understanding and generating human language.
Computer Vision — interpreting images and video.
Robotics — perception, planning, and actuation in the physical world.

PEAS Framework

Every AI agent is described by its Performance measure, Environment, Actuators, and Sensors.

AgentPerformanceEnvironmentActuatorsSensors
Chess playerWin/loss ratioChessboardMove piecesBoard state
Self-driving carSafe arrivalRoads, trafficSteering, brakesCameras, LIDAR
Medical diagnosisAccuracyPatient recordsDiagnosis outputSymptoms, tests
02

Search Algorithms

A search problem is a tuple ⟨S, s₀, A, T, g, c⟩ where S is the state space, s₀ the initial state, A actions, T the transition function, g a goal test, and c a cost function.

Uninformed (Blind) Search

AlgorithmComplete?Optimal?TimeSpace
BFSYes (finite b)Yes (unit cost)O(bd)O(bd)
DFSNoNoO(bm)O(bm)
Uniform CostYesYesO(b1+⌊C*/ε⌋)O(b1+⌊C*/ε⌋)
IDDFSYesYes (unit cost)O(bd)O(bd)

b = branching factor, d = solution depth, m = max depth, C* = optimal cost, ε = min edge cost.

A* Search

A* expands nodes in order of f(n) = g(n) + h(n), where g(n) is the path cost from the start and h(n) is the heuristic estimate to the goal.

Theorem — Optimality of A*
If h(n) is admissible (never overestimates the true cost h*(n)), then A* with tree search is optimal.
Let G be an optimal goal with cost C*. Suppose A* returns a suboptimal goal G' with cost g(G') > C*.

At the time G' is selected, there must exist a node n on the frontier that lies on an optimal path to G. Since h is admissible: f(n) = g(n) + h(n) ≤ g(n) + h*(n) = C*.

Therefore f(n) ≤ C* < g(G') = f(G').

A* always expands the node with smallest f, so it would expand n before G'. Contradiction.

Heuristic Design — Manhattan Distance

h_Manhattan(n) = Σᵢ |x_i - x_goal| + |y_i - y_goal| h_Euclidean(n) = √( (x - x_goal)² + (y - y_goal)² ) // Manhattan ≥ Euclidean; Manhattan is admissible on a grid

BFS in GNU Octave

GNU Octave
% Breadth-First Search on an adjacency list
% Graph represented as cell array of neighbour lists

function path = bfs(adj, start, goal)
  n = length(adj);
  visited = false(1, n);
  parent  = zeros(1, n);   % 0 = no parent
  queue   = start;
  visited(start) = true;

  while ~isempty(queue)
    node  = queue(1);
    queue = queue(2:end);   % dequeue

    if node == goal
      path = reconstruct(parent, start, goal);
      return;
    end

    for nb = adj{node}
      if ~visited(nb)
        visited(nb) = true;
        parent(nb)  = node;
        queue       = [queue, nb];   % enqueue
      end
    end
  end
  path = [];   % no path found
end

function path = reconstruct(parent, start, goal)
  path = goal;
  node = goal;
  while node ~= start
    node = parent(node);
    path = [node, path];
  end
end

% --- Example usage ---
adj = {[2,3], [1,4,5], [1,6], [2], [2], [3]};
p   = bfs(adj, 1, 6);
disp(p)   % → 1  3  6

🔍 Interactive — BFS / DFS Visualiser

Press ▶ Run to start…
03

Logic & Inference

Propositional Logic

Propositional logic deals with statements that are either true or false, connected by logical connectives.

SymbolNameMeaningOctave
¬PNegationNOT P~P
P ∧ QConjunctionP AND QP && Q
P ∨ QDisjunctionP OR QP || Q
P → QImplicationIF P THEN Q (¬P ∨ Q)~P || Q
P ↔ QBiconditionalP IFF QP == Q
P ⊕ QXORExclusive ORxor(P,Q)

Truth Table — Modus Ponens

The fundamental inference rule: from P and P→Q, conclude Q.

P, P → Q ———————— Q
Given P = true and P→Q = true.
P→Q ≡ ¬P ∨ Q = false ∨ Q = Q.
Since P→Q is true, Q must be true. ∎

Resolution Principle

Resolution is a complete inference procedure for propositional and first-order logic.

Clause C₁ contains literal L, Clause C₂ contains ¬L Resolvent = (C₁ \ {L}) ∪ (C₂ \ {¬L}) // Repeatedly apply until empty clause (contradiction) or fixed point

Truth Table Generator — GNU Octave

GNU Octave
% Generate full truth table for n propositional variables

function T = truth_table(n)
  rows = 2^n;
  T = false(rows, n);
  for col = 1:n
    period = 2^(n - col);
    for row = 1:rows
      T(row, col) = mod(floor((row-1) / period), 2) == 0;
    end
  end
end

% Truth table for: (P ∧ Q) → R
T = truth_table(3);   % cols: P, Q, R
P = T(:,1); Q = T(:,2); R = T(:,3);
formula = (~(P & Q)) | R;   % (P∧Q)→R ≡ ¬(P∧Q) ∨ R

disp('P  Q  R  | (P∧Q)→R');
for i = 1:rows(T)
  fprintf('%d  %d  %d  |  %d\n', P(i), Q(i), R(i), formula(i));
end

First-Order Logic (FOL)

Definition
FOL extends propositional logic with quantifiers (∀, ∃), predicates, and functions.

Universal: ∀x P(x) — "For all x, P(x) is true."
Existential: ∃x P(x) — "There exists an x such that P(x) is true."

Example — encoding "All humans are mortal":

∀x Human(x) → Mortal(x) Human(Socrates) —————————————————— Mortal(Socrates) [by universal instantiation + modus ponens]

⚡ Interactive — Truth Table Builder

Click "Build Table" to evaluate…
04

Probability & Bayesian Reasoning

Axioms of Probability (Kolmogorov)

1. 0 ≤ P(A) ≤ 1 2. P(Ω) = 1 (sample space has probability 1) 3. P(A ∪ B) = P(A) + P(B) if A ∩ B = ∅ (additivity)

Conditional Probability

P(A | B) = P(A ∩ B) / P(B) where P(B) > 0

Bayes' Theorem

Theorem — Bayes
P(H | E) = P(E | H) · P(H) / P(E)
where H = hypothesis, E = evidence, P(H) = prior, P(H|E) = posterior.
From the definition of conditional probability:
P(H | E) = P(H ∩ E) / P(E) … (1)
P(E | H) = P(E ∩ H) / P(H) → P(E ∩ H) = P(E | H) · P(H) … (2)
Substituting (2) into (1): P(H | E) = P(E | H) · P(H) / P(E). □

Law of Total Probability

P(E) = Σₖ P(E | Hₖ) · P(Hₖ) // {H₁, …, Hₙ} is a partition of the sample space

Naive Bayes Classifier in GNU Octave

GNU Octave
% Naive Bayes classifier (Gaussian likelihood)
% Train on (X, y) where X is n×d feature matrix, y is n×1 class labels

function model = nb_train(X, y)
  classes = unique(y);
  model.classes = classes;
  for k = 1:length(classes)
    idx = (y == classes(k));
    model.prior(k)  = mean(idx);            % P(Cₖ)
    model.mu(k,:)   = mean(X(idx,:), 1);    % μₖ per feature
    model.sigma(k,:)= var(X(idx,:), 0, 1);  % σ²ₖ per feature
  end
end

function ypred = nb_predict(model, X)
  n = rows(X); K = length(model.classes);
  logpost = zeros(n, K);
  for k = 1:K
    % log P(Cₖ) + Σ log N(xⱼ; μₖⱼ, σ²ₖⱼ)
    logpost(:,k) = log(model.prior(k)) + ...
      sum(-0.5*log(2*pi*model.sigma(k,:)) - ...
          (bsxfun(@minus, X, model.mu(k,:))).^2 ./ ...
          (2*model.sigma(k,:)), 2);
  end
  [~, idx] = max(logpost, [], 2);
  ypred = model.classes(idx);
end

% --- Synthetic data example ---
randn('state', 42);
X = [randn(50,2); randn(50,2) + 3];
y = [ones(50,1); 2*ones(50,1)];
model   = nb_train(X, y);
ypred   = nb_predict(model, X);
accuracy = mean(ypred == y);
fprintf('Accuracy: %.1f%%\n', accuracy*100);

🎲 Interactive — Bayes' Theorem Calculator

Classic medical test: what's the probability you have disease D given a positive test?

05

Linear Algebra for AI

Linear algebra is the language of machine learning. Data is matrices; transformations are matrix multiplications.

Vectors and Dot Product

x = [x₁, x₂, …, xₙ]ᵀ ∈ ℝⁿ x · y = xᵀy = Σᵢ xᵢ yᵢ ‖x‖₂ = √(xᵀx) (L2 norm / Euclidean length) cos θ = (x · y) / (‖x‖ ‖y‖) (cosine similarity)

Matrix Multiplication

C = AB, where Cᵢⱼ = Σₖ Aᵢₖ Bₖⱼ A ∈ ℝᵐˣᵖ, B ∈ ℝᵖˣⁿ → C ∈ ℝᵐˣⁿ // In neural nets: Z = WX + b (matrix form)

Eigenvalues & Eigenvectors

Definition
For square matrix A ∈ ℝⁿˣⁿ, a non-zero vector v is an eigenvector with eigenvalue λ if:
Av = λv
Proof that eigenvectors of symmetric A are orthogonal

Let Av₁ = λ₁v₁ and Av₂ = λ₂v₂ with λ₁ ≠ λ₂.
v₂ᵀ(Av₁) = v₂ᵀλ₁v₁ = λ₁(v₂ᵀv₁)
(Av₂)ᵀv₁ = λ₂v₂ᵀv₁
Since A = Aᵀ: v₂ᵀAv₁ = (Av₂)ᵀv₁, so λ₁(v₂ᵀv₁) = λ₂(v₂ᵀv₁).
Since λ₁ ≠ λ₂, we must have v₂ᵀv₁ = 0 (orthogonal). □

PCA — Principal Component Analysis

PCA finds the directions of maximum variance in data by computing eigenvectors of the covariance matrix.

Σ = (1/n) XᵀX (covariance matrix, zero-mean X) Σ vₖ = λₖ vₖ Variance explained = λₖ / Σⱼ λⱼ Z = X V_r (project to r dimensions)
GNU Octave — PCA from scratch
% Principal Component Analysis — implemented from scratch

function [Z, V, explained] = pca_scratch(X, r)
  % X: n×d data matrix (samples × features)
  % r: number of principal components to keep

  % 1. Centre the data
  mu  = mean(X, 1);
  Xc  = bsxfun(@minus, X, mu);

  % 2. Covariance matrix
  Cov = (Xc' * Xc) / (rows(X) - 1);

  % 3. Eigendecomposition
  [V, D] = eig(Cov);

  % 4. Sort descending by eigenvalue
  eigvals = diag(D);
  [eigvals, idx] = sort(eigvals, 'descend');
  V = V(:, idx);

  % 5. Variance explained
  explained = eigvals / sum(eigvals);

  % 6. Project onto top-r components
  Z = Xc * V(:, 1:r);
end

% --- Example: 4D iris-like data → 2D ---
randn('state', 0);
X = randn(150, 4);
X(:,1:2) = X(:,1:2) * 2;   % inflate first two dims

[Z, V, exp_var] = pca_scratch(X, 2);
fprintf('Variance explained by PC1: %.1f%%\n', exp_var(1)*100);
fprintf('Variance explained by PC2: %.1f%%\n', exp_var(2)*100);
scatter(Z(:,1), Z(:,2), 20, 'filled');
xlabel('PC1'); ylabel('PC2'); title('PCA Projection');

📐 Interactive — Vector Operations & Cosine Similarity

06

Neurons & Neural Networks

The McCulloch–Pitts Neuron

x₁ x₂ x₃ w₁ w₂ w₃ Σ wᵢxᵢ + bias σ(z) activation ŷ b
z = w₁x₁ + w₂x₂ + … + wₙxₙ + b = wᵀx + b ŷ = σ(z) (activation function)

Activation Functions

NameFormulaDerivativeRange
Sigmoidσ(z) = 1/(1+e⁻ᶻ)σ(z)(1−σ(z))(0,1)
Tanhtanh(z) = (eᶻ−e⁻ᶻ)/(eᶻ+e⁻ᶻ)1−tanh²(z)(−1,1)
ReLUmax(0, z)0 if z<0; 1 if z>0[0,∞)
Leaky ReLUmax(αz, z), α≈0.01α if z<0; 1 if z≥0(−∞,∞)
Softmaxeᶻᵢ / Σⱼ eᶻʲ(see notes)(0,1), sums to 1

Proof — Sigmoid Derivative

Let σ(z) = 1/(1 + e⁻ᶻ).

dσ/dz = d/dz (1 + e⁻ᶻ)⁻¹ = −(1 + e⁻ᶻ)⁻² · (−e⁻ᶻ) = e⁻ᶻ / (1 + e⁻ᶻ)²

Note that e⁻ᶻ = (1 + e⁻ᶻ) − 1, so: = [(1 + e⁻ᶻ) − 1] / (1 + e⁻ᶻ)² = 1/(1+e⁻ᶻ) − 1/(1+e⁻ᶻ)² = σ(z) − σ(z)² = σ(z)(1 − σ(z)). □

Multi-Layer Perceptron (MLP) in GNU Octave

GNU Octave — Forward Pass
% MLP Forward Pass — 2 hidden layers, sigmoid activation

function sigma = sigmoid(z)
  sigma = 1 ./ (1 + exp(-z));
end

function [a_out, cache] = forward_pass(X, W1, b1, W2, b2, W3, b3)
  % Layer 1
  z1 = X  * W1' + repmat(b1, rows(X), 1);
  a1 = sigmoid(z1);
  % Layer 2
  z2 = a1 * W2' + repmat(b2, rows(X), 1);
  a2 = sigmoid(z2);
  % Output layer
  z3 = a2 * W3' + repmat(b3, rows(X), 1);
  a_out = sigmoid(z3);   % binary classification
  cache = {z1,a1,z2,a2,z3};
end

% --- Initialise a 2→4→4→1 network randomly ---
randn('state', 1);
W1 = randn(4,2) * 0.1;  b1 = zeros(1,4);
W2 = randn(4,4) * 0.1;  b2 = zeros(1,4);
W3 = randn(1,4) * 0.1;  b3 = zeros(1,1);

X = [0 0; 0 1; 1 0; 1 1];   % XOR inputs
[output, ~] = forward_pass(X, W1,b1, W2,b2, W3,b3);
disp(output)

⚡ Interactive — Activation Function Plotter

07

Machine Learning Fundamentals

The Learning Problem

Given a training set D = {(x⁽ⁱ⁾, y⁽ⁱ⁾)}ᵢ₌₁ᴺ drawn i.i.d. from distribution P(X,Y), find a hypothesis h: X → Y from hypothesis class ℋ that minimises the expected risk:

R(h) = E[ℓ(h(X), Y)] (true risk, unknown) R̂(h) = (1/N) Σᵢ ℓ(h(x⁽ⁱ⁾), y⁽ⁱ⁾) (empirical risk, computable) h* = argminₕ∈ℋ R̂(h) (ERM — Empirical Risk Minimisation)

Bias–Variance Decomposition

Theorem — Bias-Variance Tradeoff
For squared error: E[(h(x) − y)²] = Bias²(h(x)) + Var(h(x)) + σ²
where σ² is irreducible noise.
Let f(x) = E[Y|X=x] (true function), h(x) the learned model, and ȳ = E[h(x)].

E[(h(x) − y)²] = E[(h(x) − f(x) + f(x) − y)²] = E[(h(x) − f(x))²] + 2E[(h(x)−f(x))(f(x)−y)] + E[(f(x)−y)²]

The cross-term is 0 (noise independent of h). Expand the first term:
E[(h(x) − ȳ + ȳ − f(x))²] = E[(h(x)−ȳ)²] + (ȳ−f(x))² = Var(h(x)) + Bias²(h(x))

The last term is σ² (irreducible noise). □

Linear Regression — OLS

Model: ŷ = Xθ Loss: J(θ) = (1/2n) ‖Xθ − y‖² Solution: θ* = (XᵀX)⁻¹ Xᵀy (Normal Equations)
Derivation of Normal Equations

J(θ) = (1/2n)(Xθ−y)ᵀ(Xθ−y) = (1/2n)(θᵀXᵀXθ − 2θᵀXᵀy + yᵀy)

∂J/∂θ = (1/n)(XᵀXθ − Xᵀy) = 0
⟹ XᵀXθ = Xᵀy
⟹ θ = (XᵀX)⁻¹Xᵀy (when XᵀX is invertible). □
GNU Octave — Linear Regression
% Linear Regression: Normal Equations vs Gradient Descent

%% Generate synthetic data
rand('state', 42);
n = 100;
x = 2*rand(n,1);
y = 3*x + 1 + 0.4*randn(n,1);   % y = 3x + 1 + noise
X = [ones(n,1), x];                 % design matrix [1, x]

%% 1. Normal Equations (closed-form)
theta_ne = (X'*X) \ (X'*y);
fprintf('Normal Equations: θ₀=%.4f, θ₁=%.4f\n', theta_ne(1), theta_ne(2));

%% 2. Gradient Descent
theta = zeros(2,1);
alpha = 0.1;
iters = 500;
J_hist = zeros(iters,1);

for t = 1:iters
  err  = X*theta - y;
  grad = (X'*err) / n;       % ∇J = (1/n)Xᵀ(Xθ−y)
  theta = theta - alpha*grad;
  J_hist(t) = mean(err.^2) / 2;
end
fprintf('Gradient Descent:  θ₀=%.4f, θ₁=%.4f\n', theta(1), theta(2));

%% Plot loss curve
plot(1:iters, J_hist, 'b-', 'LineWidth', 2);
xlabel('Iteration'); ylabel('MSE Loss');
title('Gradient Descent Convergence');

Loss Functions Summary

NameFormulaUse case
MSE(1/n)Σ(ŷ−y)²Regression
MAE(1/n)Σ|ŷ−y|Robust regression
Binary Cross-Entropy−[y log ŷ + (1−y) log(1−ŷ)]Binary classification
Categorical Cross-Entropy−Σₖ yₖ log ŷₖMulticlass
Hingemax(0, 1 − y·ŷ)SVM
Huber½(ŷ−y)² if |err|≤δ; δ|err|−½δ² otherwiseRobust regression

📈 Interactive — Gradient Descent Visualiser

Minimise J(θ) = (θ−3)² + 2 (single-variable)

Adjust sliders and press ▶ Animate
08

Backpropagation

Backpropagation computes gradients of the loss with respect to all parameters by applying the chain rule of calculus backwards through the network.

Chain Rule

If z = f(g(x)), then dz/dx = (dz/dg)·(dg/dx) In vector form: ∂L/∂x = (∂z/∂x)ᵀ · ∂L/∂z

Backprop Through One Layer

For layer ℓ with z = Wa + b, output a_next = σ(z), and upstream gradient δ = ∂L/∂a_next:

δᶻ = δ ⊙ σ'(z) (element-wise) ∂L/∂W = δᶻ · aᵀ (outer product) ∂L/∂b = δᶻ (sum over batch) ∂L/∂a = Wᵀ · δᶻ (pass gradient back)

Full Backprop in GNU Octave — XOR Network

GNU Octave — XOR with Backprop
% Solve XOR with 2-layer network trained via backprop
% Architecture: 2 → 4 → 1  (sigmoid throughout)

randn('state', 7);
X = [0 0; 0 1; 1 0; 1 1];
y = [0; 1; 1; 0];          % XOR truth table

% Init weights (Xavier-like)
W1 = randn(4,2) / sqrt(2);  b1 = zeros(4,1);
W2 = randn(1,4) / sqrt(4);  b2 = zeros(1,1);

alpha = 1.0;   % learning rate
losses = zeros(5000,1);

for epoch = 1:5000
  %% Forward
  Z1 = W1*X' + b1;          % 4×4
  A1 = 1./(1+exp(-Z1));     % sigmoid
  Z2 = W2*A1 + b2;          % 1×4
  A2 = 1./(1+exp(-Z2));     % output

  %% Loss (binary cross-entropy)
  L = -mean(y'.*log(A2+1e-9) + (1-y').*log(1-A2+1e-9));
  losses(epoch) = L;

  %% Backward
  dA2 = -(y'./(A2+1e-9) - (1-y')./(1-A2+1e-9)) / 4;
  dZ2 = dA2 .* A2 .* (1 - A2);        % σ'(z) = σ(1−σ)
  dW2 = dZ2 * A1';                       % 1×4
  db2 = sum(dZ2, 2);

  dA1 = W2' * dZ2;                       % 4×4
  dZ1 = dA1 .* A1 .* (1 - A1);
  dW1 = dZ1 * X;                         % 4×2
  db1 = sum(dZ1, 2);

  %% Update
  W1 = W1 - alpha * dW1;  b1 = b1 - alpha * db1;
  W2 = W2 - alpha * dW2;  b2 = b2 - alpha * db2;
end

fprintf('\nFinal predictions (should be ~[0,1,1,0]):\n');
disp(round(A2', 3));
fprintf('Final loss: %.6f\n', losses(end));

Computational Graph and Autodiff

Modern deep learning frameworks (PyTorch, JAX) implement automatic differentiation by recording operations as a directed acyclic graph (DAG), then traversing it in reverse (reverse-mode AD = backprop).

Forward: compute f(x) = sin(x² + 3x) step by step v1 = x² → v2 = 3x → v3 = v1+v2 → v4 = sin(v3) Backward: ∂f/∂v3 = cos(v3), ∂f/∂v2 = ∂f/∂v3·1 = cos(v3) ∂f/∂v1 = cos(v3), ∂f/∂x = cos(v3)·(2x+3)
09

Optimisation

Gradient Descent Variants

AlgorithmUpdate RuleNotes
Batch GDθ ← θ − α∇J(θ)Stable, slow on large data
SGDθ ← θ − α∇J(θ; x⁽ⁱ⁾)Noisy, fast, escapes local minima
Mini-batch GDθ ← θ − α∇J(θ; B)Best of both, standard practice
Momentumv ← βv + (1−β)∇J; θ ← θ − αvAccelerates along consistent gradients
Adamm ← β₁m+(1−β₁)g; v ← β₂v+(1−β₂)g²; θ ← θ−α m̂/√v̂Adaptive, widely used

Adam Optimiser — Proof of Update

m_t = β₁ m_{t-1} + (1−β₁) g_t (1st moment, momentum) v_t = β₂ v_{t-1} + (1−β₂) g_t² (2nd moment, RMSProp) m̂_t = m_t / (1 − β₁ᵗ) (bias correction) v̂_t = v_t / (1 − β₂ᵗ) (bias correction) θ_{t+1} = θ_t − α · m̂_t / (√v̂_t + ε) // Typical: α=0.001, β₁=0.9, β₂=0.999, ε=1e-8
GNU Octave — Adam Optimiser
% Adam optimiser applied to a simple 2D loss surface
% f(x,y) = x² + 10y²  (Rosenbrock-like elongated bowl)

function [g] = grad_f(params)
  g = [2*params(1); 20*params(2)];
end

% Hyperparameters
alpha = 0.01;
beta1 = 0.9;  beta2 = 0.999;  eps = 1e-8;

theta = [-3; 2];   % start far from optimum (0,0)
m = zeros(2,1);  v = zeros(2,1);
path = theta';

for t = 1:200
  g  = grad_f(theta);
  m  = beta1*m + (1-beta1)*g;
  v  = beta2*v + (1-beta2)*g.^2;
  mh = m / (1 - beta1^t);
  vh = v / (1 - beta2^t);
  theta = theta - alpha * mh ./ (sqrt(vh) + eps);
  path  = [path; theta'];
end

fprintf('Final θ: [%.6f, %.6f]\n', theta(1), theta(2));

% Plot convergence path
[gx,gy] = meshgrid(linspace(-4,4,100), linspace(-3,3,100));
fval = gx.^2 + 10*gy.^2;
contourf(gx, gy, fval, 30, 'LineColor', 'none'); hold on
plot(path(:,1), path(:,2), 'w-o', 'MarkerSize', 3);
title('Adam Optimisation Path');

L1 and L2 Regularisation

L2 (Ridge): J(θ) = MSE + λ Σᵢ θᵢ² → shrinks weights toward 0 L1 (Lasso): J(θ) = MSE + λ Σᵢ |θᵢ| → induces sparsity Elastic Net: J(θ) = MSE + λ₁‖θ‖₁ + λ₂‖θ‖₂²
10

Decision Trees & Entropy

Information Entropy

Shannon Entropy
H(X) = −Σₖ p(xₖ) log₂ p(xₖ) (measured in bits)
Entropy is maximised by the uniform distribution

Maximise H = −Σpᵢ log pᵢ subject to Σpᵢ = 1 using Lagrange multipliers.
∂/∂pᵢ [−Σpᵢ log pᵢ − λ(Σpᵢ − 1)] = 0
−log pᵢ − 1 − λ = 0 → pᵢ = e^(−1−λ) = constant
With Σpᵢ = 1 and n classes: pᵢ = 1/n, H_max = log₂ n. □

Information Gain

IG(S, A) = H(S) − Σᵥ (|Sᵥ|/|S|) H(Sᵥ) // S: dataset, A: attribute, Sᵥ: subset where A=v Gini(S) = 1 − Σₖ pₖ² (Gini impurity — used by CART)

Decision Tree in GNU Octave

GNU Octave — Entropy & Info Gain
% Compute Shannon entropy and Information Gain

function H = entropy(labels)
  if isempty(labels), H = 0; return; end
  classes = unique(labels);
  H = 0;
  n = length(labels);
  for c = classes'
    p = sum(labels == c) / n;
    if p > 0
      H = H - p * log2(p);
    end
  end
end

function IG = info_gain(y, feature, threshold)
  left  = y(feature <= threshold);
  right = y(feature >  threshold);
  n = length(y);
  IG = entropy(y) ...
     - (length(left)/n)  * entropy(left) ...
     - (length(right)/n) * entropy(right);
end

% --- Example: find best split threshold ---
feature = [1;2;3;4;5;6;7;8];
labels  = [0;0;0;1;1;1;0;1];

fprintf('Parent entropy: %.4f bits\n', entropy(labels));
thresholds = 1:7;
for t = thresholds
  ig = info_gain(labels, feature, t);
  fprintf('  Split ≤ %d : IG = %.4f\n', t, ig);
end

🌳 Interactive — Entropy Calculator

Adjust the proportion of class A in a binary dataset and watch entropy change.

Random Forests

A random forest trains B decision trees on bootstrap samples of the data, and at each node chooses a random subset of m = √d features to split on. Predictions are made by majority vote.

ŷ = mode{ h_b(x) : b = 1, …, B } (classification) ŷ = (1/B) Σ_b h_b(x) (regression) Var(avg) = (1−ρ)σ²/B + ρσ² (ρ = inter-tree correlation) // Low ρ (via feature randomness) gives best variance reduction
11

AI Ethics & Responsible AI

Core Principles

Fairness
An AI system should not discriminate on protected attributes (race, gender, age). Demographic parity: P(ŷ=1|A=0) = P(ŷ=1|A=1).
Transparency
Stakeholders should be able to understand why decisions are made (explainability, interpretability, audit trails).
Privacy
Data should be used only for consented purposes; techniques like differential privacy and federated learning protect individual data.
Safety & Robustness
Systems must behave correctly under distribution shift, adversarial inputs, and edge cases.

Differential Privacy

Definition (ε-Differential Privacy)
A randomised mechanism M satisfies ε-DP if for all adjacent datasets D, D' and all outputs S:
P(M(D) ∈ S) ≤ e^ε · P(M(D') ∈ S)

The Gaussian mechanism adds noise N(0, σ²) where σ ≥ √(2 ln(1.25/δ)) · Δf/ε, where Δf is the sensitivity of the query.

Bias Sources in ML

⚠️
Historical bias — training data reflects past human biases.
Representation bias — some groups are underrepresented in data.
Measurement bias — different error rates across groups due to measurement quality.
Aggregation bias — a single model fails to capture subgroup differences.
Deployment bias — model used in a context different from training.

Asilomar Principles (High-Level)

The 2017 Asilomar AI Principles outline that AI researchers should:

(1) Pursue beneficial AI, not undirected intelligence. (2) Ensure AI systems are safe and transparent. (3) Avoid AI races that sacrifice safety. (4) Support human oversight. (5) Ensure AI benefits are broadly shared.

Fairness Metrics in GNU Octave

GNU Octave — Fairness Metrics
% Compute fairness metrics for a binary classifier
% y_true: ground truth, y_pred: predictions, group: protected attribute (0/1)

function metrics = fairness_metrics(y_true, y_pred, group)
  g = unique(group);
  for k = 1:length(g)
    idx = group == g(k);
    yt = y_true(idx); yp = y_pred(idx);

    TP = sum(yt==1 & yp==1);
    FP = sum(yt==0 & yp==1);
    TN = sum(yt==0 & yp==0);
    FN = sum(yt==1 & yp==0);

    metrics(k).group       = g(k);
    metrics(k).accuracy    = (TP+TN)/length(yt);
    metrics(k).TPR         = TP/(TP+FN+1e-9);  % true positive rate
    metrics(k).FPR         = FP/(FP+TN+1e-9);  % false positive rate
    metrics(k).pred_pos    = mean(yp);          % P(ŷ=1) — demographic parity

    fprintf('Group %d: Acc=%.2f, TPR=%.2f, FPR=%.2f, P(ŷ=1)=%.2f\n', ...
      g(k), metrics(k).accuracy, metrics(k).TPR, metrics(k).FPR, metrics(k).pred_pos);
  end
  dp_gap = abs(metrics(1).pred_pos - metrics(2).pred_pos);
  fprintf('Demographic Parity Gap: %.4f\n', dp_gap);
end

% --- Example ---
y_true = [1;0;1;1;0;1;0;0;1;1];
y_pred = [1;0;1;0;0;1;1;0;1;0];
group  = [0;0;0;0;0;1;1;1;1;1];
fairness_metrics(y_true, y_pred, group);

Quick Reference — Key Formulae

P(H|E) = P(E|H)P(H) / P(E)
θ* = (XᵀX)⁻¹Xᵀy
θ ← θ − α∇_θ J(θ)
∂L/∂x = (∂z/∂x)ᵀ ∂L/∂z
H = −Σ pₖ log₂ pₖ
σ'(z) = σ(z)(1−σ(z))
f(n) = g(n) + h(n)
E[err] = Bias² + Var + σ²