In classification, we have some information and we want to label it as part of some discrete class.
- Introduction
- Logistic Regression
- Decision Trees
- Gradient-Boosted Trees
- XGBoost
- Random Forests
- Support Vector Machine
- Multiclass Classification
- Clustering
- k-Nearest Neighbours
Classification is sometimes called categorical regression (or logistic regression, if we're only categorizing between binary values), but it's not really regression. Regression refers to predicting continuous values. Classification is more like "threshold prediction", i.e. performing classification is like performing regression and then applying a custom floor/ceiling function to the predicted values.
There are many different branches of classification. In this writeup, we'll cover the most important ones.
Logistic regression is the simplest and most important form of classification to understand. It is a special case of classification that only has binary outputs. When performing logistic regression, we aim to find the best-fitting model to describe the relationship between the binary outcome and the predictor variables.
Just like linear regression, we have a set of weights
Unlike in linear regression, we don't just simply output the weighted + biased sum of the inputs. Instead, we map our intermediate result
and then apply the logistic (sigmoid) function to it:
which will give us an output between 0 and 1. We interpret this as the probability that the input belongs to the positive class (label 1). Then, we set some threshold for our predictions. For example, we can say, "only if something has a chance of at least 70% of being spam will we classify it as spam".
Just like other forms of regression, we need a measure of our accuracy to know how good our model is. In logistic regression, the loss function we use is negative log-likelihood function as our loss function. We try to minimize the average value of this function in order to improve our model. Since there is no closed-form solution, we are forced to use gradient descent in order to optimize the weights.
We perform gradient descent, just like in linear regression, to optimize our weights. The gradients of the loss function with respect to the weights are calculated as follows:
We continuously update the weights using these gradients until convergence:
As a reminder:
-
$L$ is the negative log-likelihood function -
$N$ is the number of samples,$y_i$ is the true label for sample$i$ -
$\hat{y}_i$ is the predicted probability for sample$i$ -
$x_{ij}$ is the value of feature$j$ for sample$i$ .
We continue this descent until one of the convergence criteria is met.
So, in summation, when performing logistic regression, we initialize some set of weights
Decision trees are to logistic regression what neural networks are to linear regression. They are a more complex and capture non-linear relationships between features and labels. However, instead of gradient descent, we use a greedy search algorithm, and instead of minimizing a loss function, we maximize information gain.
The main idea behind decision trees is that, we start with some data, and we try to segregate it consistently based on its features until we reach a point where we can confidently distinguish between classes. This is done by selecting the best features to split on at each node in the tree. This will result in a rooted tree where each leaf node represents a class label.
In each branching node of a graph, a feature must be examined to see whether its above or below some threshold, and classified accordingly. We continue this process recursively until we reach a stopping criterion, such as a maximum tree depth or a minimum number of samples per leaf.
This is the most important part to understand. Say we have some labelled data and we want to understand how to split them in a way such that examples of the same class are grouped together when we split on some feature.
If coming from a regression mindset, you might think of this as an iterative process, but it's not. Instead, we choose an impurity measure and run its corresponding, deterministic (and recursive) algorithm to find the best features and thresholds to split on. The technique we use will depend on the context, but the goal is to choose splits that most improve node purity according to the chosen criterion.
There are three main methods used in decision trees:
If our impurity measure is entropy, we can use the ID3 algorithm to build our decision tree.
The word 'entropy' (from the Greek 'ἐν' meaning 'in'and 'τροπή' meaning 'transformation') is used to describe the 'inner change' or disorder within a system. In the context of information theory, this is what we call our uncertainty about a random variable.
For a node containing
Where:
-
$S$ is the set of samples at the node. -
$p_i$ is the proportion of samples in class$i$ (a.k.a. the probability of class$i$ given random sampling). -
$K$ is the total number of classes.
In other words, our entropy measures the opposite of how confident we are in our predictions. The goal, of course, is to be as confident as possible. So, we must minimize entropy.
To do this, we use the concept of information gain. Information gain is how much our confidence increases (how much entropy decreases) after a certain operation is performed. The ID3 algorithm works with this concept to recursively build our decision trees. Its steps are:
-
Starting at the current root node, calculate the entropy.
-
Look at all the candidate features and split the dataset on each feature.
-
For each split, calculate the new expected entropy, using the same formula as step 1, this time finding the weighted average.
where
- Choose the split that results in the highest information gain (i.e., with the lowest entropy):
where
- Now, each split will create child nodes. Recurse by making each child node the new root and repeating steps 1-5 until it's time to stop.
We stop when one of the following conditions is met:
- Entropy increases after all possible splits.
- All samples at a node belong to the same class.
- There are no remaining features to split on.
- The maximum tree depth is reached.
- There are fewer than a minimum number of samples at a node.
The idea behind using the Gain Ratio is that, oftentimes, when we try to minimize entropy, we will create many splits in order to micro-optimize the entropy. For example, we may get as minute as splitting on specific ID numbers. Of course, this can lead to overfitting. By penalizing a high number of splits, we make trees that generalize better.
The Gain Ratio is a modification of Information Gain method that takes into account the number of branches created by a split.
The Entropy + ID3 method by using Gain Ratio + C4.5 algorithm instead. Note that C4.5 is identical to ID3 (except for the purity measure it uses).
It's defined as:
Where Information Gain is the same as earlier:
And Split Information is the entropy of the split itself, calculated as:
Here,
We divide the number of splits by the total number of samples in order to find out what proportion of the samples fall within that split. Then, we multiply that proportion by the
Notice how the logarithm of a small fraction is very negative, which will give us a very large (positive) split information values. In other words, when we have many small, evenly sized children we get high SplitInfo, and when we have one big child, and a few tiny splits we get low SplitInfo. Thus, the Gain Ratio will be lower for splits that create many small branches. Since we pick the split with the highest Gain Ratio, this new impurity measure will encourage balanced, binary splits.
Note that, despite these optimizations, we often still stick to Entropy + ID3 in practice because it's simpler
The idea behihnd Gini Impurity + Cart is to split the data in binary splits every time.
Gini impurity can be written as:
Now, since we split into 'left' and 'right' branches, to get the final impurity of a split, we take the weighted average of the Gini impurities of the two branches:
(Where
Now, the CART (Classification and Regression Trees) algorithm works similarly to ID3 and C4.5, checking every possible split and picking the one with the least impurity:
Once we choose an impurity measure and run the greedy algorithm, an input is labelled by looking at its features and using them to traverse down the tree from root to leaf. Once we reach a leaf, we assign the most common class that is found within that leaf to our input point.
- Can overfit
- Greedy algorithms often sacrifice global optimality for instant gratification.
- Too flat: can't model complex curves
To solve these issues, we can use gradient boosting and random forests. But notice that, already, we're no longer limited to binary decisions.
Gradient-boosted trees take the basic decision tree idea and turn it into a sequence of weak learners that correct each other’s mistakes. Using them is like performing gradient descent on decision trees, with which we continue itereating until the model converges.
Instead of building a single, deep tree, we build many small trees one after another. Each new tree looks at where the previous trees predicted incorrectly and tries to reduce that error.
- Start with a single tree that predicts all outputs roughly. Often, this just means taking the mean of the target's values and making that the initial prediction.
(Note that the 0 is not exponentiation but the iteration index.) At this point in the iteration, we just have a tree with a single leaf. For classification, we instead use the log-odds of the positive class proportion:
- Compute the residuals (prediction vs. ground truth for every sample).
where
-
Train a new tree to predict these residuals. We train on the residuals in order to understand what we're doing wrong in the current ensemble (group) of trees and move in the opposite direction.
-
Add this tree’s predictions to the previous ones, scaled by a learning rate
$\eta$ :
You can think of this as performing gradient descent on the predictions:
The new indices of
- Recurse steps 2 to 4 until convergence.
Each tree will only focus on the mistakes of the ensemble so far. We adjust in the direction of the negative gradient and scale that adjustment by the learning rate, that way we "converge slower" to create a more complex tree without overfitting. This process gradually improves the model. The small size of each tree and the learning rate prevent overfitting, even when we build hundreds of trees.
At step 3, we have our input features as well as a 'residual' value for each sample. Here, we're working with continuous values (doing regression) since we're predicting residuals. Once again, we use a greedy algorithm to build a decision tree.
For example, if using the Gain Ratio + C4.5 method, we would:
-
Find all possible splits to split on for each feature.
-
Find the split that minimizes the variance of the residuals in each child node. (Since we're doing regression now, we want to minimize variance instead of entropy.)
-
Create child nodes and recurse until a stopping criterion is met.
- Trees are shallow (often 3–8 levels) and additive.
- The same impurity measures (Gini, entropy) to build each tree.
- Trees are invariant to monotonic transformations (don't need normalization).
- We can now use trees to model more complex, non-linear data.
- Work well with smaller datasets.
Extreme Gradient Boosting is an implementation of Gradient-Boosted trees that's implemented in C++ and optimized for speed and performance. It includes several enhancements over basic gradient boosting, such as:
- Regularization: XGBoost includes L1 and L2 regularization to prevent overfitting.
- Automatically handles missing values during training
- Tree pruning (via
max_depthandmin_child_weight) and parallelization for faster training. - Is easier to analyze since it contains a standardized interface and features.
In practice, when we want to make a gradient-boosted tree, we just import the XGBoost library and use its built-in functions to create and train our model. It abstracts away the complexities of implementing gradient boosting from scratch, allowing us to focus on tuning hyperparameters and improving model performance.
Building random forests is simpler than gradient boosting, but more complicated than basic decision trees. The main idea is, we have some data with some set of features. Then, we consider random subsets of these features a given of times and use a greedy build a decision tree for each. This creates an ensemble, which we use to make predictions.
Random forests build an ensemble in a different way: instead of sequentially fixing mistakes, they train many independent decision trees on random subsets of the data and features, then average their predictions.
- Start with the full dataset
$S$ . - If creating an
$m$ -tree ensemble with$n$ features each, trained on$k$ samples, repeat the following$m$ times:- Take the dataset
$S$ and randomly pick$n$ features that it will consider. - Randomly take
$k$ samples with replacement. - Use a greedy algorithm to build a decision tree on this subset.
- Take the dataset
- Now, you have
$m$ k by n decision trees.
When using this ensemble, we pass the input features to each of the
Each tree is a weak, high-variance model, but combining them reduces variance by a lot. Random subsets of data and features ensure that the trees are decorrelated, so their errors tend to cancel out.
When working with a support vector machine (SVM), we try to find the best way to separate our classes. This means forming a line, 2D plane, or etc. to model a boundary. The hyperplane is formed through two support vectors. The best hyperplane is the one that maximizes the margin between the two classes. The margin is defined as the distance between the hyperplane and the closest points from each class, which are called support vectors.
To use an SVM, we simply plot the unlabelled data point and see which side of the hyperplane it's on. But first, we need to understand how the more basic versions of it work.
- In
$\mathbb{R}^n$ , a hyperplane is a$\mathbb{R}^{n-1}$ flat affine space. It can be written as:
(Any point
-
The margin is the perpendicular distance between the hyperplane and the closest points from each class (the support vectors).
-
$\text{exp(x)}$ is$e^{x}$
In general, when working with hyperplanes that correctly classify two classes, we can draw infinitely many hyperplanes that separate them. The Maximal Margin Classifier is the hyperplane that maximizes the margin between the two classes while still classifying all of the data points correctly.
In mathematical form, if we have a set of parameters
(note that we take min instead of the max since the margin,
In other words, we try to reduce both bias and variance at the same time, which creates a nice, generalizable model.
If there are no hyperplanes that can perfectly separate the two classes (or even if there are and we want to improve robustness), we need to further generalize the margin classifiers to fit our data.
A support vector classifier uses softer margins in order to allow for some misclassification. This introduces "slack variables"
and minimizes
where
Think of
So,
Now, imagine even that's not enough. What if the data is completely linearly inseparable? As in, what if some of the points fully surround other points of a different class?
In that case, the job is a lot harder. We can try to map the data onto a higher dimensional space so that it is separable.
To do this, we need some function, call it
For example, if we have two-dimensional points that are not linearly separable, we can map them to three dimensions using:
Now, to to find the optimal hyperplane in this new space, we can use the same techniques as before (maximal margin classifier or support vector classifier).
I.e. we want to find
subject to the constraints
where,
If we have a high-dimensional space and many points, these calculations can get very expensive. However, we can use a trick called the kernel trick to avoid explicitly computing the mapping
The kernel trick allows us to compute the inner products in the high-dimensional space without explicitly mapping the points. We define a kernel function
Common kernel functions include:
-
Linear Kernel:
$K(x_i, x_j) = x_i \cdot x_j$ -
Polynomial Kernel:
$K(x_i, x_j) = (x_i \cdot x_j + 1)^d$ -
Radial Basis Function (RBF) Kernel:
$K(x_i, x_j) = \exp(-\gamma ||x_i - x_j||^2)$
Multiclass classification is the extension of binary classification to handle more than two classes. Instead of simply predicting a yes/no label, we want to assign each input to one of
SVMs are inherently binary, but we can adapt them using one of two common strategies:
-
One-vs-Rest (OvR) / One-vs-All (OvA)
- Train
$K$ separate binary classifiers, each distinguishing one class from all others. - For class
$k$ , label all examples in class$k$ as$+1$ and all others as$-1$ . - At prediction time, compute the decision function for all
$K$ classifiers and pick the class with the highest margin (i.e., the classifier most confident in a positive prediction).
- Train
-
One-vs-One (OvO)
- Train a binary classifier for every pair of classes, resulting in
$K(K-1)/2$ classifiers. - Each classifier distinguishes between two classes, ignoring the rest.
- At prediction, each classifier votes for one of its two classes. The class with the most votes is chosen as the prediction.
- Train a binary classifier for every pair of classes, resulting in
For logistic regression, multiclass prediction is typically handled using softmax regression:
-
$w_k$ is the weight vector for class$k$ . - The denominator ensures that the predicted probabilities sum to 1 over all classes.
- The predicted class is:
The loss function becomes categorical cross-entropy:
where
Decision trees, random forests, and gradient-boosted trees naturally handle multiple classes without modification. Each leaf simply stores the most frequent class among the training samples that reach it. During prediction, the path from root to leaf determines the predicted class.
Extending the idea of distinguishing between classes based on features, we can also perform clustering, which is an unsupervised learning technique. Clustering is when we have a bunch of unlabelled data and we group data with similar features together into the same cluster. It's like an extension of classification, where we rely on the structure of the data itself to find who belongs where.
So far, we've only talked about supervised learning, where we have labels to guide our models.
Although this opens up a whole new field of machine learning, it's worth mentioning since it stems from classification and is very important in common tasks such as efficient searching for RAG.
Some of the most common clustering techniques include:
The agglomerative clustering technique is a bottom-up approach. Start by treating each data point as its own cluster. Then, iterate through each point.
During each iteration, merge the two clusters that are closest together according to some distance metric. This process continues until all points are merged into a single cluster, forming a tree-like structure called a dendrogram.
The key idea is that we can “cut” this tree at different levels to obtain different numbers of clusters. So, when searching for
The problem with the dendrogram is that it takes
k-Means is the most common and important clustering algorithm.
We choose
The algorithm works as follows:
- Initialize
$k$ centroids randomly. - Assign each point to the nearest centroid.
- Recompute each centroid as the mean of the points assigned to it.
- Repeat steps 2–3 until convergence.
Mathematically, we're just trying to minimize the total within-cluster variance:
Where
Once the centroids are roughly at the centre of their respective clusters, the categorization is said to have 'converged', and the algorithm ends.
A method called kmeans++ improves the convergence this by randomizing where the centroids are initialized.
k-Means assumes that clusters actually exist, are roughly spherical, and are evenly sized. Without these assumptions, it may loop forever or simple fail to optimize.
DBScan (Density-Based Spatial Clustering of Applications with Noise) is a density-based clustering algorithm. It's simimlar to k-Means, except, instead of choosing
A cluster is formed by connecting points that are within
In the algorithm, we have two main hyperparameters:
-
$\varepsilon$ : the radius of a neighbourhood -
$\text{minPts}$ : the minimum number of points required to form a dense region
Points are classified as:
-
Core points: have at least
$\text{minPts}$ points within$\varepsilon$ - Border points: are close to a core point but don’t meet the density requirement themselves
- Noise points: do not belong to any cluster
The algorithm goes as follows:
- Start with an unvisited point.
- Look around this point. If it has
$\text{minPts}$ neighbours within$\varepsilon$ , it’s a core point and we start a new cluster. - If it is a border point, we add it to the cluster of the nearest core point.
- If it's a core point, we recursively visit all its neighbours and add them to the cluster if they are core or border points.
Once a cluster is 'exhausted' (i.e., all its core points have been visited), we move on to the next unvisited point and repeat the process until all points have been visited.
If the first point we encounter ends up not being a core point, we simply move on (it's effectively marked as noise).
In higher dimensions, we simply just scale up the search to use
DBSCAN is able to cluster together clusters that have arbitrary shapes.
DBSCAN is a very clean, dynamic, and robust way to cluster irregular data, and it can also handle noise.
However, it's sensitive to its hyperparameters
Gaussian Mixture Models (GMMs) take a probabilistic approach to clustering. Instead of starting with a 'centroid esitmator' and improving on it, we assume that the points are randomly generated from a mixture of several Gaussian distributions.
Instead of assigning each point to a cluster, we assume the points come from
Each of the
-
$\mu_k$ : the mean (centre of the distribution) -
$\Sigma_k$ : the covariance matrix (shape and orientation of the ellipse) -
$\pi_k$ : the mixing weight (what fraction of the data this component explains), where$\sum_k \pi_k = 1$
The full model is:
So, the probability of observing a specific point
After initializing these values randomly, the Expectation-Maximization (EM) algorithm is used to iteratively optimize them:
-
E-step: for each point
$x_i$ , compute the responsibility$r_{ik}$ . -
M-step: update each component's
$\mu_k$ ,$\Sigma_k$ , and$\pi_k$ as the responsibility-weighted mean, covariance, and weight over all points. - Repeat until the change in log-likelihood falls below a threshold.
The result is soft assignments: each point belongs to every cluster with some probability, rather than being hard-assigned to one.
In summary, GMM is like a more expressive form of k-Means. Its can fit more complex clusters (like ellipses instead of circles) and provides probabilistic cluster memberships. However, because it's more complex, it may be heavily and inaccurately skewed due to outliers
It would be a shame to talk about classification methods without at least mentioning k-nearest neighbours. It's not very closely related to any of the other methods we've discussed, but it's so effective that one cannot ignore it. It's the 'laziest' form of classification, since it doesn't even try to learn a model. Instead, it just looks at the training data and makes predictions based on the closest points. Almost like an in-between of clustering and forests.
When using k-NN, we take a point's features and look around at the k closest points in our training data. We then take a majority vote of those k points' labels to determine the label of our input point. it is a very simple algorithm, but it often works very well. Also, k-NN gives us an early and simplistic preview into unsupervised learning.
This project is licensed under the MIT License.












