Mastering Logistic Regression with PyTorch: An In-Depth Guide

Logistic regression is a core machine learning algorithm for binary classification problems. While simple to understand, it achieves excellent results across a wide range of applications, from spam filtering to disease diagnosis to customer churn prediction.

In this comprehensive guide, we‘ll dive deep into the logistic regression algorithm and demonstrate how to implement it using PyTorch. We‘ll cover the key concepts, walk through the implementation process step-by-step, and share expert tips to optimize your models. Whether you‘re a machine learning beginner or a seasoned practitioner, this guide will equip you with the knowledge and code to apply logistic regression effectively.

Understanding Logistic Regression

At its core, logistic regression is a statistical method to predict a binary outcome (1 or 0, yes or no, true or false) given a set of independent variables. It models the probability that the outcome belongs to a particular class.

Mathematically, logistic regression uses the logistic function (also known as the sigmoid function) to map any real-valued number to a value between 0 and 1:

$$\sigma(x) = \frac{1}{1+e^{-x}}$$

Source: Sigmoid function

The logistic function has several nice properties:

  • It maps any input value to a value between 0 and 1
  • It approaches 1 as x approaches positive infinity, and 0 as x approaches negative infinity
  • It crosses 0.5 at x=0

In logistic regression, we model the probability that the output belongs to the default class (typically class 1) as a linear combination of the input features passed through the logistic function:

$$P(y=1|x) = \sigma(w^Tx + b)$$

Where $w$ and $b$ are the model‘s learnable weight and bias parameters, respectively. We can interpret the output as the probability that the input $x$ belongs to class 1. If this probability is greater than 0.5, we predict class 1, otherwise we predict class 0.

During training, the model learns the optimal values of $w$ and $b$ by minimizing a cost function, typically the binary cross-entropy loss:

$$J(w,b) = -\frac{1}{N}\sum_{i=1}^N y_i \log(p(y_i)) + (1-y_i) \log(1-p(y_i))$$

Where $y_i$ is the true label and $p(y_i)$ is the model‘s predicted probability for the $i$-th training example.

After training, the model predicts a class label for a new input $x$ using:

$$\hat{y} = \begin{cases}
1 & \sigma(w^Tx + b) > 0.5 \
0 & \text{otherwise}
\end{cases}
$$

The decision boundary of the logistic regression classifier is linear, defined by the hyperplane where $w^Tx + b = 0$:

Source: Logistic Regression Decision Boundary

Despite this linear decision boundary, logistic regression can still model nonlinear relationships between the input features and output class probabilities through feature engineering techniques like polynomial features.

Implementing Logistic Regression in PyTorch

Now that we understand the theory behind logistic regression, let‘s see how to implement it in PyTorch. We‘ll use the classic Pima Indians Diabetes dataset, where the goal is to predict whether a patient has diabetes based on diagnostic measurements.

Loading and Preprocessing Data

First, we‘ll load the dataset from CSV using pandas and convert to PyTorch tensors:

import pandas as pd
import torch
from sklearn.model_selection import train_test_split

data = pd.read_csv(‘diabetes.csv‘) 
X = data.drop(‘Outcome‘, axis=1).values
y = data[‘Outcome‘].values

X = torch.tensor(X, dtype=torch.float32)
y = torch.tensor(y, dtype=torch.float32)

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

We split the data into 80% training and 20% test sets. It‘s also good practice to normalize the input features to have zero mean and unit variance:

mean = X_train.mean(dim=0)
std = X_train.std(dim=0)

X_train = (X_train - mean) / std
X_test = (X_test - mean) / std

Defining the Model

Next, we define the logistic regression model using PyTorch‘s nn.Module class:

import torch.nn as nn

class LogisticRegression(nn.Module):
    def __init__(self, input_size):
        super(LogisticRegression, self).__init__()
        self.linear = nn.Linear(input_size, 1) 

    def forward(self, x):
        return torch.sigmoid(self.linear(x))  

The model consists of a single linear layer that maps from the input size (number of features) to a single output, followed by a sigmoid activation function to squash the output to a probability between 0 and 1.

Training the Model

With the model defined, we can train it on the data. We‘ll use binary cross-entropy loss and stochastic gradient descent:

model = LogisticRegression(X_train.shape[1])

criterion = nn.BCELoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)

num_epochs = 100
for epoch in range(num_epochs):
    y_pred = model(X_train)
    loss = criterion(y_pred, y_train.reshape(-1,1))

    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

    if (epoch+1) % 20 == 0:
        print(f‘epoch: {epoch+1}, loss = {loss.item():.4f}‘)

This training loop:

  1. Makes predictions on the training data using the current model parameters.
  2. Calculates the BCE loss between predictions and true labels.
  3. Backpropagates the loss to calculate gradients of the loss with respect to the model parameters.
  4. Uses the optimizer to update the model parameters in the direction that minimizes the loss.
  5. Repeats this process for a number of epochs.

Evaluating the Model

We can check the model‘s accuracy on the test set to gauge how well it generalizes to unseen data:

with torch.no_grad():
    y_pred = model(X_test)
    y_pred_cls = y_pred.round()
    acc = y_pred_cls.eq(y_test.view_as(y_pred_cls)).sum() / y_test.shape[0]

print(f‘accuracy: {acc.item():.4f}‘)

This code:

  1. Disables gradient calculation since we‘re not training.
  2. Makes predictions on the test inputs.
  3. Rounds the predicted probabilities to get the predicted class labels.
  4. Calculates the fraction of correct predictions.

On the diabetes dataset, we achieve an accuracy of around 77%, which is decent but could likely be improved with further feature engineering and hyperparameter tuning.

Logistic Regression Best Practices

Here are some best practices and advanced tips to keep in mind when working with logistic regression models in PyTorch:

  • Normalize inputs: As demonstrated above, normalizing the input features to have zero mean and unit variance can help the model converge faster and more stably during training. PyTorch provides handy utilities like nn.BatchNorm1d for this purpose.

  • Weight initialization: Logistic regression is prone to the "vanishing gradient" problem if the weights are initialized too small or too large. A common initialization strategy is to sample weights from a uniform distribution bounded by $\pm \frac{1}{\sqrt{n}}$, where $n$ is the number of inputs.

  • Regularization: Applying L1 or L2 regularization to the model weights can help prevent overfitting, especially for high-dimensional datasets with many features. In PyTorch, weight regularization can be added by passing weight_decay to the optimizer.

  • Imbalanced data: Logistic regression can struggle with imbalanced datasets where one class is much more common than the other(s). Strategies to handle class imbalance include:

    • Oversampling the minority class or undersampling the majority class
    • Adjusting the class weights in the loss function
    • Using metrics beyond accuracy, like F1 score or ROC AUC, that better reflect performance on imbalanced data
  • Multiclass classification: While we‘ve focused on binary classification, logistic regression extends to multiclass problems as well. This is typically done by training multiple one-vs-rest binary classifiers and selecting the class with the maximum probability. PyTorch‘s nn.Linear layer can output as many classes as needed.

Advanced Topics and Extensions

There are many ways to enhance and extend logistic regression models. Some ideas worth exploring:

  • Feature selection: Identifying the most predictive features and discarding irrelevant ones can improve model accuracy and interpretability. Techniques range from univariate statistical tests, to L1 regularization, to wrapper methods like recursive feature elimination.

  • Polynomial features: We can give logistic regression more flexibility by adding polynomial combinations of the original input features. This can help the model learn nonlinear decision boundaries in the original feature space.

  • Hyperparameter optimization: The model‘s hyperparameters, like the learning rate, regularization strength, and number of training epochs can be systematically tuned using techniques like grid search, random search, or Bayesian optimization to find the optimal configuration.

  • Model interpretability: Logistic regression is appealing in applications that require model interpretability because the learned weights directly correspond to the importance of each input feature. We can inspect the magnitudes and signs of the weights to understand the model‘s predictions.

  • Calibration: While logistic regression outputs are interpreted as probabilities, these probabilities are often not well-calibrated, meaning they don‘t match the actual frequency of positive examples. Techniques like Platt scaling and isotonic regression can calibrate the probabilities to be more reliable.

Conclusion

Logistic regression is a powerful yet simple algorithm for binary classification. In this guide, we covered the key concepts behind logistic regression and demonstrated how to implement it in PyTorch, including data loading, model definition, training, and evaluation.

We also discussed best practices like feature normalization, weight initialization, regularization, and handling imbalanced data that can help you build more robust and accurate logistic regression models. Finally, we explored some advanced topics and extensions to logistic regression.

With these tools in your machine learning toolbox, you‘re well-equipped to apply logistic regression to a wide range of real-world problems, from spam email detection to disease diagnosis to ad click prediction. The PyTorch framework makes it straightforward to experiment with different model architectures and hyperparameters to optimize performance.

Of course, logistic regression is just one of many classification algorithms worth learning. Be sure to check out other techniques like decision trees, support vector machines, naive Bayes, and neural networks, and understand their strengths and weaknesses. Often the best solution will be an ensemble of multiple models.

I hope this guide has been a helpful deep dive into logistic regression with PyTorch. Remember, the most important ingredient in any machine learning project is a curious mind – so keep learning, keep building, and most importantly, have fun!

References

  • Jayalakshmi, T., and A. Santhakumaran. "Statistical normalization and back propagation for classification." International Journal of Computer Theory and Engineering 3.1 (2011): 1793-8201.

  • King, Gary, and Langche Zeng. "Logistic regression in rare events data." Political analysis 9.2 (2001): 137-163.

  • Menard, Scott. Applied logistic regression analysis. Vol. 106. Sage, 2002.

  • Pranckevičius, Tomas, and Virginijus Marcinkevičius. "Comparison of naive bayes, random forest, decision tree, support vector machines, and logistic regression classifiers for text reviews classification." Baltic Journal of Modern Computing 5.2 (2017): 221.

How useful was this post?

Click on a star to rate it!

Average rating 0 / 5. Vote count: 0

No votes so far! Be the first to rate this post.

Similar Posts