Understanding Logistic Regression and Its Implementation Using Gradient Descent
Introduction
Welcome to our new lesson on Logistic Regression and its implementation using the Gradient Descent technique. Having familiarized yourself with the fundamentals of Regression Analysis and the operation of Gradient Descent in optimizing regression models, we'll now address a different kind of problem: Classification. While Regression Analysis is suitable for predicting continuous variables, when predicting categories such as whether an email is spam or not, we need specially designed tools — one of them being Logistic Regression.
In this lesson, we'll guide you through the basic concepts that define Logistic Regression, focusing on its unique components like the Sigmoid function and Log-Likelihood. Eventually, we'll utilize C++ to engineer a straightforward Logistic Regression model using Gradient Descent. By the end of this lesson, you will have broadened your theoretical understanding of another vital machine learning concept and enhanced your practical C++ coding skills.
Classification: From Linear Regression to Logistic Regression
So far, we've dealt with tasks where a continuous output needs prediction based on one or more input variables - these tasks are known as regression tasks. There is, however, another category of tasks known as classification tasks, where the objective is to predict a categorical outcome. These categories are often binary, like "spam"/"not spam" for an email or "malignant"/"benign" for a tumor. The models we've studied so far are not optimal for predicting categorical outcomes - for example, it isn't intuitive to understand what it means for an email to be "0.67" spam. Enter Logistic Regression - a classification algorithm that can predict the probability of a binary outcome.
Sigmoid Function: the Heart of Logistic Regression
Understanding Logistic Regression
The Cost Function in Logistic Regression
Implementing Logistic Regression with Gradient Descent
Here's a simple C++ implementation of a Logistic Regression model using Eigen and double precision, matching the practice code:
#include <iostream>#include <Eigen/Dense>#include <cmath>using namespace std;using namespace Eigen;double sigmoid(double z) { return 1.0 / (1.0 + exp(-z));}VectorXd sigmoid(const VectorXd& z) { return 1.0 / (1.0 + (-z.array()).exp());}double cost_function(const VectorXd& h, const VectorXd& y) { const double epsilon = 1e-15; ArrayXd h_clipped = h.array().min(1.0 - epsilon).max(epsilon); return (-y.array() * h_clipped.log() - (1.0 - y.array()) * (1.0 - h_clipped).log()).mean();}VectorXd logistic_regression(MatrixXd X, VectorXd y, int num_iterations, double learning_rate) { MatrixXd intercept = MatrixXd::Ones(X.rows(), 1); MatrixXd X_new(X.rows(), X.cols() + 1); X_new << intercept, X; X = X_new; VectorXd theta = VectorXd::Zero(X.cols()); for (int i = 0; i < num_iterations; ++i) { VectorXd z = X * theta; VectorXd h = sigmoid(z); VectorXd gradient = (X.transpose() * (h - y)) / y.size(); theta -= learning_rate * gradient; // Recompute after update like in Python z = X * theta; h = sigmoid(z); if (i % 10000 == 0) { double loss = cost_function(h, y); cout << "Loss after " << i << " iterations: " << loss << endl; } } return theta;}
Applying Logistic Regression with Gradient Descent
Now, we can define the predict_prob and predict functions, matching the practice code:
That wraps up our lesson on the fundamentals of Logistic Regression and its C++ implementation using Gradient Descent. Throughout this lesson, we've highlighted the differences between regression and classification tasks, introduced Logistic Regression as a classification algorithm, and elaborated on the components that define it.
You'll have ample opportunities to refine these skills in our forthcoming practice exercises. Remember, the more you practice, the more fluent you'll become. So, practice away and have fun doing it!
The mathematical form of Logistic Regression can be expressed as follows:
P(Y=1∣x)=1+e−(β0+β1x)1
Where:
P(Y=1∣x) is the probability of event Y=1 given x.
β0 and β1 are parameters of the model.
x is the input variable.
β0+β1x is the linear combination of parameters and feature(s).
Log-Likelihood in Logistic Regression plays a similar role to the Least Squares method in Linear Regression. A maximum likelihood estimation method estimates parameters that maximize the likelihood of making the observations we collected. In Logistic Regression, we seek to maximize the log-likelihood.
While Linear Regression makes predictions by directly calculating the output, Logistic Regression does it differently. Instead of directly predicting the output, Logistic Regression calculates a raw model output, then transforms it using the sigmoid function, mapping it to a range between 0 and 1, thus making it a probability.
When providing a high positive input, the output of S(x) is close to 1, and for a large negative input, the output is close to 0. This feature of the Sigmoid function makes it a perfect fit when we want to classify emails into two categories: "spam" or "not-spam".
The cost function for a single training instance can be expressed as:
J(p^,y)=−[ylog(p^)+(1−y)log(1−p^)]
where p^ denotes the predicted probability.
To avoid issues with taking the log of 0, we clip the predicted probabilities to a small epsilon away from 0 and 1:
This function makes sense because −log(t) approaches 0 as t approaches 1, so the cost will be close to 0 if the predicted probability is near the actual target. However, the cost will approach ∞ as t approaches 0, which means that predicting a probability close to 0 for a positive instance (where y=1) will be highly penalized.