xbe.at

Deep learning / Activation functions and loss

Binary Cross-Entropy Limitations for Imbalanced Datasets

7 min read, 14 sections

Contents
  1. Binary Cross-Entropy: Challenges with Imbalanced Datasets
  2. Understanding Binary Cross-Entropy
  3. The Problem with Imbalanced Datasets
  4. Visualizing the Problem
  5. Why Binary Cross-Entropy Struggles
  6. Alternative 1: Weighted Binary Cross-Entropy
  7. Alternative 2: Focal Loss
  8. Alternative 3: Oversampling
  9. Alternative 4: Undersampling
  10. Real-Life Example: Medical Diagnosis
  11. Real-Life Example: Anomaly Detection in Network Traffic
  12. Comparing Different Approaches
  13. Conclusion and Best Practices
  14. Additional Resources

Binary Cross-Entropy: Challenges with Imbalanced Datasets

Binary cross-entropy is a popular loss function for binary classification tasks. However, it can be problematic when dealing with imbalanced datasets. This presentation will explore why and propose alternative approaches.

import numpy as np
import matplotlib.pyplot as plt

# Create an imbalanced dataset
np.random.seed(42)
class_0 = np.random.normal(0, 1, (1000, 2))
class_1 = np.random.normal(3, 1, (100, 2))

plt.scatter(class_0[:, 0], class_0[:, 1], label='Class 0')
plt.scatter(class_1[:, 0], class_1[:, 1], label='Class 1')
plt.title('Imbalanced Dataset')
plt.legend()
plt.show()

Understanding Binary Cross-Entropy

Binary cross-entropy measures the performance of a classification model whose output is a probability value between 0 and 1. It increases as the predicted probability diverges from the actual label.

def binary_cross_entropy(y_true, y_pred):
    return -np.mean(y_true * np.log(y_pred) + (1 - y_true) * np.log(1 - y_pred))

# Example
y_true = np.array([1, 0, 1, 1, 0])
y_pred = np.array([0.9, 0.1, 0.8, 0.7, 0.2])

loss = binary_cross_entropy(y_true, y_pred)
print(f"Binary Cross-Entropy Loss: {loss:.4f}")

The Problem with Imbalanced Datasets

In imbalanced datasets, the model may achieve high accuracy by simply predicting the majority class, leading to poor performance on the minority class.

from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, f1_score

# Prepare imbalanced dataset
X = np.vstack((class_0, class_1))
y = np.hstack((np.zeros(1000), np.ones(100)))

# Split the data
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# Train a logistic regression model
model = LogisticRegression()
model.fit(X_train, y_train)

# Predict and evaluate
y_pred = model.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
f1 = f1_score(y_test, y_pred)

print(f"Accuracy: {accuracy:.4f}")
print(f"F1 Score: {f1:.4f}")

Visualizing the Problem

Let's visualize how the model performs on our imbalanced dataset. We'll plot the decision boundary and see how it affects both classes.

def plot_decision_boundary(model, X, y):
    x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
    y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
    xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.1),
                         np.arange(y_min, y_max, 0.1))
    Z = model.predict(np.c_[xx.ravel(), yy.ravel()])
    Z = Z.reshape(xx.shape)
    
    plt.contourf(xx, yy, Z, alpha=0.4)
    plt.scatter(X[:, 0], X[:, 1], c=y, alpha=0.8)
    plt.title("Decision Boundary")
    plt.show()

plot_decision_boundary(model, X, y)

Why Binary Cross-Entropy Struggles

Binary cross-entropy treats all misclassifications equally, regardless of class imbalance. This can lead to the model focusing more on the majority class, potentially ignoring the minority class.

# Calculate class weights
class_weights = {0: 1 / np.sum(y == 0), 1: 1 / np.sum(y == 1)}

# Print class weights
print("Class weights:")
print(f"Class 0: {class_weights[0]:.4f}")
print(f"Class 1: {class_weights[1]:.4f}")

# Calculate weighted binary cross-entropy
def weighted_binary_cross_entropy(y_true, y_pred, class_weights):
    return -np.mean(class_weights[1] * y_true * np.log(y_pred) + 
                    class_weights[0] * (1 - y_true) * np.log(1 - y_pred))

# Example
y_true = np.array([1, 0, 1, 1, 0])
y_pred = np.array([0.9, 0.1, 0.8, 0.7, 0.2])

weighted_loss = weighted_binary_cross_entropy(y_true, y_pred, class_weights)
print(f"Weighted Binary Cross-Entropy Loss: {weighted_loss:.4f}")

Alternative 1: Weighted Binary Cross-Entropy

One solution is to use weighted binary cross-entropy, which assigns higher weights to the minority class.

from sklearn.utils.class_weight import compute_class_weight

# Compute class weights
class_weights = compute_class_weight('balanced', classes=np.unique(y), y=y)
sample_weights = np.where(y == 0, class_weights[0], class_weights[1])

# Train a weighted logistic regression model
weighted_model = LogisticRegression(class_weight='balanced')
weighted_model.fit(X_train, y_train)

# Predict and evaluate
y_pred_weighted = weighted_model.predict(X_test)
accuracy_weighted = accuracy_score(y_test, y_pred_weighted)
f1_weighted = f1_score(y_test, y_pred_weighted)

print(f"Weighted Accuracy: {accuracy_weighted:.4f}")
print(f"Weighted F1 Score: {f1_weighted:.4f}")

# Visualize the new decision boundary
plot_decision_boundary(weighted_model, X, y)

Alternative 2: Focal Loss

Focal loss is another alternative that addresses class imbalance by down-weighting easy examples and focusing on hard ones.

def focal_loss(y_true, y_pred, gamma=2.0):
    pt = np.where(y_true == 1, y_pred, 1 - y_pred)
    return -np.mean((1 - pt) ** gamma * np.log(pt))

# Example
y_true = np.array([1, 0, 1, 1, 0])
y_pred = np.array([0.9, 0.1, 0.8, 0.7, 0.2])

focal_loss_value = focal_loss(y_true, y_pred)
print(f"Focal Loss: {focal_loss_value:.4f}")

Alternative 3: Oversampling

Oversampling involves increasing the number of samples in the minority class to balance the dataset.

from imblearn.over_sampling import SMOTE

# Apply SMOTE
smote = SMOTE(random_state=42)
X_resampled, y_resampled = smote.fit_resample(X, y)

# Train a model on the resampled data
oversampled_model = LogisticRegression()
oversampled_model.fit(X_resampled, y_resampled)

# Predict and evaluate
y_pred_oversampled = oversampled_model.predict(X_test)
accuracy_oversampled = accuracy_score(y_test, y_pred_oversampled)
f1_oversampled = f1_score(y_test, y_pred_oversampled)

print(f"Oversampled Accuracy: {accuracy_oversampled:.4f}")
print(f"Oversampled F1 Score: {f1_oversampled:.4f}")

# Visualize the new decision boundary
plot_decision_boundary(oversampled_model, X, y)

Alternative 4: Undersampling

Undersampling involves reducing the number of samples in the majority class to balance the dataset.

from imblearn.under_sampling import RandomUnderSampler

# Apply Random Undersampling
rus = RandomUnderSampler(random_state=42)
X_resampled, y_resampled = rus.fit_resample(X, y)

# Train a model on the resampled data
undersampled_model = LogisticRegression()
undersampled_model.fit(X_resampled, y_resampled)

# Predict and evaluate
y_pred_undersampled = undersampled_model.predict(X_test)
accuracy_undersampled = accuracy_score(y_test, y_pred_undersampled)
f1_undersampled = f1_score(y_test, y_pred_undersampled)

print(f"Undersampled Accuracy: {accuracy_undersampled:.4f}")
print(f"Undersampled F1 Score: {f1_undersampled:.4f}")

# Visualize the new decision boundary
plot_decision_boundary(undersampled_model, X, y)

Real-Life Example: Medical Diagnosis

In medical diagnosis, imbalanced datasets are common. For instance, in detecting a rare disease, the majority of samples will be negative. Using binary cross-entropy might lead to a model that always predicts "no disease," missing crucial positive cases.

import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import classification_report

# Simulate a medical dataset
np.random.seed(42)
n_samples = 1000
n_features = 5

X = np.random.randn(n_samples, n_features)
y = np.random.choice([0, 1], size=n_samples, p=[0.95, 0.05])  # 5% disease prevalence

# Split the data
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# Train a standard model
model = LogisticRegression()
model.fit(X_train, y_train)

# Predict and evaluate
y_pred = model.predict(X_test)
print("Standard Model Results:")
print(classification_report(y_test, y_pred))

# Train a weighted model
weighted_model = LogisticRegression(class_weight='balanced')
weighted_model.fit(X_train, y_train)

# Predict and evaluate
y_pred_weighted = weighted_model.predict(X_test)
print("\nWeighted Model Results:")
print(classification_report(y_test, y_pred_weighted))

Real-Life Example: Anomaly Detection in Network Traffic

In network security, detecting malicious traffic is crucial. However, malicious packets are often rare compared to normal traffic, creating an imbalanced dataset.

from sklearn.ensemble import IsolationForest

# Simulate network traffic data
np.random.seed(42)
n_samples = 10000
n_features = 10

X = np.random.randn(n_samples, n_features)
y = np.ones(n_samples)  # 1 for normal traffic

# Add anomalies (1% of the data)
n_anomalies = int(0.01 * n_samples)
anomaly_indices = np.random.choice(n_samples, n_anomalies, replace=False)
X[anomaly_indices] = np.random.uniform(low=10, high=20, size=(n_anomalies, n_features))
y[anomaly_indices] = -1  # -1 for anomalies

# Split the data
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# Train an Isolation Forest model
clf = IsolationForest(contamination=0.01, random_state=42)
clf.fit(X_train)

# Predict and evaluate
y_pred = clf.predict(X_test)
print("Isolation Forest Results:")
print(classification_report(y_test, y_pred))

# Visualize results (for 2 features)
plt.figure(figsize=(10, 6))
plt.scatter(X_test[:, 0], X_test[:, 1], c=y_pred, cmap='viridis')
plt.title("Anomaly Detection in Network Traffic")
plt.xlabel("Feature 1")
plt.ylabel("Feature 2")
plt.colorbar(label='Prediction')
plt.show()

Comparing Different Approaches

Let's compare the performance of different approaches on our imbalanced dataset.

from sklearn.metrics import precision_recall_fscore_support

def evaluate_model(y_true, y_pred, model_name):
    precision, recall, f1, _ = precision_recall_fscore_support(y_true, y_pred, average='binary')
    print(f"{model_name}:")
    print(f"Precision: {precision:.4f}")
    print(f"Recall: {recall:.4f}")
    print(f"F1 Score: {f1:.4f}\n")

# Evaluate standard model
evaluate_model(y_test, y_pred, "Standard Model")

# Evaluate weighted model
evaluate_model(y_test, y_pred_weighted, "Weighted Model")

# Evaluate oversampled model
evaluate_model(y_test, y_pred_oversampled, "Oversampled Model")

# Evaluate undersampled model
evaluate_model(y_test, y_pred_undersampled, "Undersampled Model")

# Visualize results
models = ['Standard', 'Weighted', 'Oversampled', 'Undersampled']
f1_scores = [f1_score(y_test, y_pred),
             f1_score(y_test, y_pred_weighted),
             f1_score(y_test, y_pred_oversampled),
             f1_score(y_test, y_pred_undersampled)]

plt.bar(models, f1_scores)
plt.title('F1 Scores of Different Models')
plt.ylabel('F1 Score')
plt.show()

Conclusion and Best Practices

When dealing with imbalanced datasets:

  1. Always consider the nature of your problem and the costs associated with different types of errors.
  2. Use appropriate evaluation metrics like precision, recall, and F1 score instead of just accuracy.
  3. Try different approaches: weighted loss functions, resampling techniques, or algorithms designed for imbalanced data.
  4. Collect more data for the minority class if possible.
  5. Consider ensemble methods that combine multiple approaches.

Remember, there's no one-size-fits-all solution. The best approach depends on your specific dataset and problem.

Additional Resources

For more information on handling imbalanced datasets, consider these peer-reviewed articles:

  1. "Learning from Imbalanced Data" by He et al. (2009) ArXiv: https://arxiv.org/abs/1505.01658
  2. "A Survey of Predictive Modelling under Imbalanced Distributions" by Branco et al. (2016) ArXiv: https://arxiv.org/abs/1505.01658
  3. "SMOTE: Synthetic Minority Over-sampling Technique" by Chawla et al. (2002) Journal of Artificial Intelligence Research

These resources provide in-depth discussions on various techniques for handling imbalanced datasets and their applications in different domains.

More in Activation functions and loss

  1. Building a Sigmoid Activation Function from Scratch in Python7 min

    The sigmoid function is a crucial component in neural networks, particularly in binary classification problems. It maps any input value to a number between 0...

  2. Activation Functions Understanding in Neural Networks15 min

    Neural networks utilize activation functions to transform input signals into output activations, introducing essential non-linearity that enables the network...

  3. Building a Softmax Activation Function from Scratch in Python7 min

    The Softmax function is a crucial component in many machine learning models, particularly in multi-class classification problems. It transforms a vector of...

  4. Activation Functions The Driving Force of Neural Networks7 min

    Activation functions are a crucial component in neural networks, acting as the non-linear transformation that allows these networks to learn complex...

  5. Common Loss Functions for Deep Learning Using Python4 min

    In deep learning, loss functions play a crucial role in training models by quantifying the difference between the predicted output and the actual target....

  6. Activation Functions in Neural Networks15 min

    Activation functions serve as crucial non-linear transformations in neural networks, enabling them to learn complex patterns by introducing non-linearity...