xbe.at

Machine learning / Advanced ML topics

Exploring the Mathematics of Machine Learning Cost Functions

10 min read, 15 sections

Contents
  1. Understanding the Cost Function in Machine Learning
  2. Why Square the Errors?
  3. The Role of 2m in the Cost Function
  4. Mathematical Derivation vs. Intuition
  5. Alternative Error Measures
  6. Pros and Cons of Squared Errors
  7. Real-Life Example: Image Compression
  8. Real-Life Example: Weather Prediction
  9. Gradient Descent and the Cost Function
  10. The Importance of Normalization
  11. Regularization and the Cost Function
  12. Cross-Validation and the Cost Function
  13. Hyperparameter Tuning with Grid Search
  14. Learning Curves and Overfitting
  15. Additional Resources

Understanding the Cost Function in Machine Learning

The cost function is a crucial component in machine learning, measuring how well a model's predictions align with actual values. Let's explore the mathematical reasoning behind its formulation and why certain choices were made.

import matplotlib.pyplot as plt

# Generate sample data
y_true = np.array([1, 2, 3, 4, 5])
y_pred = np.array([1.2, 2.3, 2.7, 4.1, 5.2])

# Calculate squared errors
squared_errors = (y_pred - y_true) ** 2

# Plot the results
plt.figure(figsize=(10, 6))
plt.scatter(y_true, y_pred, color='blue', label='Predictions')
plt.plot([0, 6], [0, 6], color='red', linestyle='--', label='Perfect predictions')
plt.xlabel('True values')
plt.ylabel('Predicted values')
plt.title('Predictions vs True Values')
plt.legend()
plt.show()

print(f"Squared errors: {squared_errors}")

Why Square the Errors?

Squaring errors addresses several key issues:

  1. It eliminates the problem of positive and negative errors canceling out.
  2. Unlike absolute values, squared errors are differentiable everywhere, facilitating optimization.
  3. It penalizes larger errors more heavily, encouraging the model to minimize outliers.
import matplotlib.pyplot as plt

# Generate sample errors
errors = np.linspace(-5, 5, 100)

# Calculate different error measures
absolute_errors = np.abs(errors)
squared_errors = errors ** 2

# Plot the results
plt.figure(figsize=(10, 6))
plt.plot(errors, absolute_errors, label='Absolute Error')
plt.plot(errors, squared_errors, label='Squared Error')
plt.xlabel('Error')
plt.ylabel('Error Measure')
plt.title('Comparison of Error Measures')
plt.legend()
plt.grid(True)
plt.show()

The Role of 2m in the Cost Function

Dividing by 2m in the cost function \frac{1}{2m}\sum_{i=1}^m(y'_i-y_i)^2 serves two purposes:

  1. It averages the errors across all data points, providing a consistent measure regardless of dataset size.
  2. The factor of 1/2 simplifies the derivative calculation during optimization.

def cost_function(y_true, y_pred):
    m = len(y_true)
    return (1 / (2 * m)) * np.sum((y_pred - y_true) ** 2)

# Example usage
y_true = np.array([1, 2, 3, 4, 5])
y_pred = np.array([1.2, 2.3, 2.7, 4.1, 5.2])

cost = cost_function(y_true, y_pred)
print(f"Cost: {cost}")

Mathematical Derivation vs. Intuition

The cost function's formulation combines mathematical reasoning with practical considerations:

  • Mathematical: Differentiability for optimization, handling of positive/negative errors.
  • Intuitive: Simplicity, interpretability, and effectiveness in practice.
import matplotlib.pyplot as plt

# Generate sample data
x = np.linspace(0, 10, 100)
y_true = 2 * x + 1 + np.random.normal(0, 1, 100)

# Define different cost functions
def mse(y_true, y_pred):
    return np.mean((y_true - y_pred) ** 2)

def mae(y_true, y_pred):
    return np.mean(np.abs(y_true - y_pred))

def huber(y_true, y_pred, delta=1.0):
    error = y_true - y_pred
    return np.mean(np.where(np.abs(error) <= delta,
                            0.5 * error ** 2,
                            delta * (np.abs(error) - 0.5 * delta)))

# Calculate costs for different slopes
slopes = np.linspace(0, 4, 100)
mse_costs = [mse(y_true, slope * x) for slope in slopes]
mae_costs = [mae(y_true, slope * x) for slope in slopes]
huber_costs = [huber(y_true, slope * x) for slope in slopes]

# Plot the results
plt.figure(figsize=(12, 6))
plt.plot(slopes, mse_costs, label='MSE')
plt.plot(slopes, mae_costs, label='MAE')
plt.plot(slopes, huber_costs, label='Huber')
plt.xlabel('Slope')
plt.ylabel('Cost')
plt.title('Comparison of Different Cost Functions')
plt.legend()
plt.grid(True)
plt.show()

Alternative Error Measures

While squared errors are common, other error measures exist:

  • Absolute Error: |y' - y|
  • Cubic Error: (y' - y)^3
  • Fourth Power Error: (y' - y)^4

Each has unique properties and use cases.

import matplotlib.pyplot as plt

# Generate sample errors
errors = np.linspace(-2, 2, 100)

# Calculate different error measures
absolute_errors = np.abs(errors)
squared_errors = errors ** 2
cubic_errors = errors ** 3
fourth_power_errors = errors ** 4

# Plot the results
plt.figure(figsize=(12, 6))
plt.plot(errors, absolute_errors, label='Absolute Error')
plt.plot(errors, squared_errors, label='Squared Error')
plt.plot(errors, cubic_errors, label='Cubic Error')
plt.plot(errors, fourth_power_errors, label='Fourth Power Error')
plt.xlabel('Error')
plt.ylabel('Error Measure')
plt.title('Comparison of Different Error Measures')
plt.legend()
plt.grid(True)
plt.show()

Pros and Cons of Squared Errors

Advantages:

  • Differentiable everywhere
  • Penalizes large errors more heavily
  • Mathematically convenient for optimization

Disadvantages:

  • Sensitive to outliers
  • May not always reflect real-world error importance
import matplotlib.pyplot as plt

# Generate sample data with an outlier
np.random.seed(42)
x = np.linspace(0, 10, 20)
y = 2 * x + 1 + np.random.normal(0, 1, 20)
y[-1] += 10  # Add an outlier

# Fit models with different error measures
def fit_line(x, y, error_func):
    best_m, best_b = 0, 0
    min_error = float('inf')
    for m in np.linspace(0, 5, 100):
        for b in np.linspace(-5, 5, 100):
            y_pred = m * x + b
            error = error_func(y, y_pred)
            if error < min_error:
                min_error = error
                best_m, best_b = m, b
    return best_m, best_b

m_mse, b_mse = fit_line(x, y, lambda y, y_pred: np.mean((y - y_pred) ** 2))
m_mae, b_mae = fit_line(x, y, lambda y, y_pred: np.mean(np.abs(y - y_pred)))

# Plot the results
plt.figure(figsize=(12, 6))
plt.scatter(x, y, color='blue', label='Data points')
plt.plot(x, m_mse * x + b_mse, color='red', label='MSE fit')
plt.plot(x, m_mae * x + b_mae, color='green', label='MAE fit')
plt.xlabel('x')
plt.ylabel('y')
plt.title('Comparison of MSE and MAE fits with an outlier')
plt.legend()
plt.grid(True)
plt.show()

Real-Life Example: Image Compression

In image compression, mean squared error (MSE) is often used to measure the quality of compressed images. Let's see how it works in practice.

import matplotlib.pyplot as plt
from skimage import data, img_as_float
from skimage.metrics import mean_squared_error

# Load a sample image
image = img_as_float(data.camera())

# Add some noise to simulate compression artifacts
noisy_image = image + 0.1 * np.random.randn(*image.shape)
noisy_image = np.clip(noisy_image, 0, 1)

# Calculate MSE
mse = mean_squared_error(image, noisy_image)

# Display the results
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 6))
ax1.imshow(image, cmap='gray')
ax1.set_title('Original Image')
ax2.imshow(noisy_image, cmap='gray')
ax2.set_title(f'Noisy Image (MSE: {mse:.4f})')
plt.tight_layout()
plt.show()

Real-Life Example: Weather Prediction

In weather forecasting, mean squared error is used to evaluate the accuracy of temperature predictions. Let's simulate this scenario.

import matplotlib.pyplot as plt
from sklearn.metrics import mean_squared_error

# Generate simulated weather data
days = np.arange(30)
actual_temps = 20 + 5 * np.sin(days / 7) + np.random.normal(0, 2, 30)
predicted_temps = 20 + 5 * np.sin(days / 7) + np.random.normal(0, 1, 30)

# Calculate MSE
mse = mean_squared_error(actual_temps, predicted_temps)

# Plot the results
plt.figure(figsize=(12, 6))
plt.plot(days, actual_temps, label='Actual Temperatures')
plt.plot(days, predicted_temps, label='Predicted Temperatures')
plt.xlabel('Day')
plt.ylabel('Temperature (°C)')
plt.title(f'Weather Prediction Accuracy (MSE: {mse:.2f})')
plt.legend()
plt.grid(True)
plt.show()

Gradient Descent and the Cost Function

The cost function's form is crucial for gradient descent optimization. Let's visualize how gradient descent works with our squared error cost function.

import matplotlib.pyplot as plt

# Define the cost function
def cost_function(m, b, x, y):
    return np.mean((y - (m * x + b)) ** 2)

# Generate sample data
np.random.seed(42)
x = np.linspace(0, 10, 100)
y = 2 * x + 1 + np.random.normal(0, 1, 100)

# Gradient descent
m, b = 0, 0
learning_rate = 0.01
iterations = 1000
costs = []

for _ in range(iterations):
    y_pred = m * x + b
    error = y_pred - y
    m -= learning_rate * np.mean(error * x)
    b -= learning_rate * np.mean(error)
    costs.append(cost_function(m, b, x, y))

# Plot the results
plt.figure(figsize=(12, 6))
plt.plot(range(iterations), costs)
plt.xlabel('Iteration')
plt.ylabel('Cost')
plt.title('Gradient Descent Optimization')
plt.yscale('log')
plt.grid(True)
plt.show()

print(f"Final parameters: m = {m:.2f}, b = {b:.2f}")

The Importance of Normalization

When using squared errors, it's crucial to normalize your data to prevent features with larger scales from dominating the cost function. Let's see the effect of normalization.

import matplotlib.pyplot as plt
from sklearn.preprocessing import StandardScaler

# Generate sample data
np.random.seed(42)
x1 = np.random.normal(0, 1, 100)
x2 = np.random.normal(0, 1000, 100)
y = 2 * x1 + 0.001 * x2 + np.random.normal(0, 0.1, 100)

# Calculate costs without normalization
costs_unnormalized = ((2 * x1 + 0.001 * x2) - y) ** 2

# Normalize the data
scaler = StandardScaler()
X_normalized = scaler.fit_transform(np.column_stack((x1, x2)))
costs_normalized = ((2 * X_normalized[:, 0] + 0.001 * X_normalized[:, 1]) - y) ** 2

# Plot the results
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 6))
ax1.scatter(x1, costs_unnormalized, alpha=0.5, label='x1')
ax1.scatter(x2, costs_unnormalized, alpha=0.5, label='x2')
ax1.set_title('Costs without Normalization')
ax1.set_xlabel('Feature Value')
ax1.set_ylabel('Cost')
ax1.legend()

ax2.scatter(X_normalized[:, 0], costs_normalized, alpha=0.5, label='x1 (normalized)')
ax2.scatter(X_normalized[:, 1], costs_normalized, alpha=0.5, label='x2 (normalized)')
ax2.set_title('Costs with Normalization')
ax2.set_xlabel('Normalized Feature Value')
ax2.set_ylabel('Cost')
ax2.legend()

plt.tight_layout()
plt.show()

Regularization and the Cost Function

Regularization is often added to the cost function to prevent overfitting. Let's explore how L2 regularization (Ridge regression) affects the cost function.

import matplotlib.pyplot as plt
from sklearn.linear_model import Ridge
from sklearn.preprocessing import PolynomialFeatures
from sklearn.pipeline import make_pipeline

# Generate sample data
np.random.seed(42)
X = np.sort(np.random.rand(40, 1), axis=0)
y = np.cos(1.5 * np.pi * X).ravel() + np.random.randn(40) * 0.1

# Create models with different regularization strengths
alphas = [0, 0.001, 0.01, 0.1]
degrees = [1, 4, 15]

plt.figure(figsize=(14, 10))
for i, degree in enumerate(degrees):
    ax = plt.subplot(3, 1, i + 1)
    for alpha in alphas:
        model = make_pipeline(PolynomialFeatures(degree), Ridge(alpha=alpha))
        model.fit(X, y)
        y_pred = model.predict(X)
        plt.plot(X, y_pred, label=f'alpha = {alpha}')
    
    plt.plot(X, y, 'r.', label='data', markersize=10)
    plt.xlabel('x')
    plt.ylabel('y')
    plt.title(f'Degree {degree} Polynomial')
    plt.legend(loc='lower left')

plt.tight_layout()
plt.show()

Cross-Validation and the Cost Function

Cross-validation helps us choose the best model by evaluating the cost function on different subsets of the data. Let's implement k-fold cross-validation.

from sklearn.model_selection import KFold
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error

# Generate sample data
np.random.seed(42)
X = np.random.rand(100, 1)
y = 2 * X + 1 + np.random.randn(100, 1) * 0.1

# Perform k-fold cross-validation
kf = KFold(n_splits=5, shuffle=True, random_state=42)
mse_scores = []

for train_index, test_index in kf.split(X):
    X_train, X_test = X[train_index], X[test_index]
    y_train, y_test = y[train_index], y[test_index]
    
    model = LinearRegression()
    model.fit(X_train, y_train)
    y_pred = model.predict(X_test)
    
    mse = mean_squared_error(y_test, y_pred)
    mse_scores.append(mse)

print(f"Cross-validation MSE scores: {mse_scores}")
print(f"Average MSE: {np.mean(mse_scores):.4f}")

Hyperparameter Tuning with Grid Search

Grid search is a technique to find the best hyperparameters for a model by systematically working through multiple combinations of parameter tunes, cross-validating as it goes to determine which tune gives the best performance.

from sklearn.model_selection import GridSearchCV
from sklearn.linear_model import Ridge
from sklearn.preprocessing import PolynomialFeatures
from sklearn.pipeline import make_pipeline

# Generate sample data
np.random.seed(42)
X = np.random.rand(100, 1)
y = np.sin(2 * np.pi * X).ravel() + np.random.randn(100) * 0.1

# Create the pipeline
model = make_pipeline(PolynomialFeatures(), Ridge())

# Define the parameter grid
param_grid = {
    'polynomialfeatures__degree': [1, 2, 3, 4],
    'ridge__alpha': [0.01, 0.1, 1, 10]
}

# Perform grid search
grid_search = GridSearchCV(model, param_grid, cv=5, scoring='neg_mean_squared_error')
grid_search.fit(X, y)

print("Best parameters:", grid_search.best_params_)
print("Best MSE:", -grid_search.best_score_)

Learning Curves and Overfitting

Learning curves help us diagnose bias and variance problems in our models by showing how the training and validation errors change as we increase the amount of training data.

import matplotlib.pyplot as plt
from sklearn.model_selection import learning_curve
from sklearn.linear_model import Ridge
from sklearn.preprocessing import PolynomialFeatures
from sklearn.pipeline import make_pipeline

# Generate sample data
np.random.seed(42)
X = np.random.rand(200, 1)
y = np.sin(2 * np.pi * X).ravel() + np.random.randn(200) * 0.1

# Create models with different complexities
degrees = [1, 4, 15]
train_sizes = np.linspace(0.1, 1.0, 10)

plt.figure(figsize=(14, 5))
for i, degree in enumerate(degrees):
    model = make_pipeline(PolynomialFeatures(degree), Ridge())
    
    train_sizes, train_scores, valid_scores = learning_curve(
        model, X, y, train_sizes=train_sizes, cv=5, scoring='neg_mean_squared_error'
    )
    
    train_scores_mean = -np.mean(train_scores, axis=1)
    valid_scores_mean = -np.mean(valid_scores, axis=1)
    
    plt.subplot(1, 3, i+1)
    plt.plot(train_sizes, train_scores_mean, label='Training error')
    plt.plot(train_sizes, valid_scores_mean, label='Validation error')
    plt.title(f'Degree {degree} Polynomial')
    plt.xlabel('Training set size')
    plt.ylabel('Mean Squared Error')
    plt.legend()

plt.tight_layout()
plt.show()

Additional Resources

For those interested in diving deeper into the mathematics behind machine learning cost functions and optimization techniques, here are some valuable resources:

  1. "Understanding Machine Learning: From Theory to Algorithms" by Shalev-Shwartz and Ben-David. Available at: https://arxiv.org/abs/1406.0923
  2. "Optimization Methods for Large-Scale Machine Learning" by Bottou, Curtis, and Nocedal. Available at: https://arxiv.org/abs/1606.04838

These papers provide in-depth discussions on the theoretical foundations and practical applications of cost functions in machine learning.

More in Advanced ML topics

  1. Extracting Data from Images using Python Cross-Validation8 min

    Cross-validation is a crucial technique in machine learning for assessing model performance and preventing overfitting. It involves splitting the dataset...

  2. Exploring Correlations in Machine Learning Using Python8 min

    Correlation is a statistical measure that quantifies the relationship between two variables. In machine learning and AI, understanding correlations is...

  3. Federated Learning with Python Decentralized Machine Learning7 min

    Federated Learning is a machine learning technique that trains algorithms across decentralized devices or servers holding local data samples, without...

  4. Explaining Pretraining, Finetuning, and Transfer Learning in Machine Learning12 min

    Pretraining, finetuning, and transfer learning are essential techniques in machine learning, particularly in deep learning. These methods allow us to...

  5. Generative vs Discriminative Machine Learning Models9 min

    Discriminative models directly learn the mapping between input features and output labels by modeling the conditional probability P(Y|X). These models focus...

  6. Double and Triple Integrals for Machine Learning and AI6 min

    Introduction to Double and Triple Integrals in Machine Learning and AI